Extend SAP SuccessFactors on SAP BTP with CAP – Prepare the UI with CDS Annotations


Prerequisites

To follow-up with this blog post you must have read and completed the following previous blog posts in this series:

Create Annotations File

It is a best practice to maintain the UI annotations in a separate file referencing your CAP service. So, let´s create that file.

1. On the left-hand pane of SAP Business Application Studio, select the srv folder, then click on the three dots to the right of the project name and select New File

Figure%201%20-%20Create%20New%20File

Figure 1 – Create New File

2. On the dialog name the file projman-service-ui.cds and click OK

Figure 2 – Set File Name

Annotations Coding

Copy and paste the code snippet below into the recently created file:

using sfsf.projman.service.ProjectManager as service from './projman-service'; namespace sfsf.projman.service.ui; ////////////////////////////////////////////////////////////////////////////
//
// UI annotations for the Project Root Entity
//
annotate service.Project with @(UI : { UpdateHidden : false, DeleteHidden : false, CreateHidden : false, Identification : [ {Value : name} ], HeaderInfo : { $Type : 'UI.HeaderInfoType', TypeName : 'Project', TypeNamePlural : 'Projects', Title : { $Type : 'UI.DataField', Value : name }, Description : { $Type : 'UI.DataField', Value : description } }, SelectionFields : [ name, startDate, endDate, status_ID ], LineItem : [ { $Type : 'UI.DataField', Value : name }, { $Type : 'UI.DataField', Value : description }, { $Type : 'UI.DataField', Value : startDate }, { $Type : 'UI.DataField', Value : endDate }, { $Type : 'UI.DataField', Value : status.name, Criticality : status.criticality, ![@UI.Importance] : #High } ], HeaderFacets : [{ $Type : 'UI.ReferenceFacet', Target : '@UI.FieldGroup#Detail' }], Facets : [ { $Type : 'UI.ReferenceFacet', ID : 'ProjectsDetails', Target : '@UI.FieldGroup#Details', Label : 'Details' }, { $Type : 'UI.ReferenceFacet', ID : 'ProjcetTeam', Target : 'team/@UI.LineItem', Label : 'Team' }, { $Type : 'UI.ReferenceFacet', ID : 'ProjcetActivity', Target : 'activities/@UI.LineItem', Label : 'Activities' } ], DataPoint #ProjName : { Value : name, Title : 'Project Title' }, FieldGroup #Detail : {Data : [{ $Type : 'UI.DataField', Value : status_ID, Criticality : status.criticality }]}, FieldGroup #Details : { $Type : 'UI.FieldGroupType', Data : [ { $Type : 'UI.DataField', Value : startDate, Label : 'Start' }, { $Type : 'UI.DataField', Value : endDate, Label : 'End' } ] },
}); annotate service.Project with { ID @( title : 'Project ID', UI.Hidden : true ) @readonly; name @(title : 'Project Title'); description @( title : 'Description', UI.MultiLineText ); startDate @(title : 'Start'); endDate @(title : 'End'); status @( Common : { Text : status.name, TextArrangement : #TextOnly, ValueListWithFixedValues, FieldControl : #Mandatory }, title : 'Status' );
} annotate service.Project @(Capabilities : { Insertable : true, Deletable : true, Updatable : true,
}); ////////////////////////////////////////////////////////////////////////////
//
// UI annotations for the Member Child Entity
//
annotate service.Member with @(UI : { UpdateHidden : false, DeleteHidden : false, CreateHidden : false, Identification : [{Value : member_name}], HeaderInfo : { $Type : 'UI.HeaderInfoType', TypeName : 'Member', TypeNamePlural : 'Members', Title : { $Type : 'UI.DataField', Value : member_userId }, Description : { $Type : 'UI.DataField', Value : member.title } }, SelectionFields : [ member.division, member.department, member.email, role.name ], LineItem : [ { $Type : 'UI.DataField', Value : member_userId, Label : 'Name' }, { $Type : 'UI.DataField', Value : member.title }, { $Type : 'UI.DataField', Value : member.email }, { $Type : 'UI.DataField', Value : member.division }, { $Type : 'UI.DataField', Value : member.department }, { $Type : 'UI.DataField', Value : role_ID, Label : 'Role' } ], HeaderFacets : [{ $Type : 'UI.ReferenceFacet', Target : '@UI.FieldGroup#Detail' }], Facets : [{ $Type : 'UI.ReferenceFacet', ID : 'ProjectsDetails', Target : '@UI.FieldGroup#Details', Label : 'Details' }], FieldGroup #Detail : {Data : [{ $Type : 'UI.DataField', Value : role_ID, Label : 'Role' }]}, FieldGroup #Details : { $Type : 'UI.FieldGroupType', Data : [ { $Type : 'UI.DataField', Value : member.email, Label : 'e-Mail' }, { $Type : 'UI.DataField', Value : member.division, Label : 'Division' }, { $Type : 'UI.DataField', Value : member.department, Label : 'Department' } ] },
}); annotate service.Member with { ID @( Common : { Text : member_name, TextArrangement : #TextOnly, }, title : 'Member ID' ) @readonly; parent @( title : 'Project ID', UI.Hidden : true ); member @( Common : { Text : member.defaultFullName, TextArrangement : #TextOnly, FieldControl : #Mandatory, ValueList : { $Type : 'Common.ValueListType', CollectionPath : 'SFSF_User', Parameters : [ { $Type : 'Common.ValueListParameterInOut', LocalDataProperty : 'member_userId', ValueListProperty : 'userId' }, { $Type : 'Common.ValueListParameterDisplayOnly', ValueListProperty : 'defaultFullName' } ], Label : 'Employees' } }, title : 'Name' ); role @( Common : { Text : role.name, TextArrangement : #TextOnly, ValueListWithFixedValues, FieldControl : #Mandatory }, title : 'Role' ); member_name @(title : 'Name', UI.Hidden: true); hasAssignment @(UI.Hidden: true);
} annotate service.Member @(Capabilities : { SearchRestrictions : { $Type : 'Capabilities.SearchRestrictionsType', Searchable : true }, Insertable : true, Deletable : true, Updatable : true
}); ////////////////////////////////////////////////////////////////////////////
//
// UI annotations for the Activity Child Entity
//
annotate service.Activity with @(UI : { UpdateHidden : false, DeleteHidden : false, CreateHidden : false, Identification : [{Value : name}], HeaderInfo : { $Type : 'UI.HeaderInfoType', TypeName : 'Activity', TypeNamePlural : 'Activities', Title : { $Type : 'UI.DataField', Value : name }, Description : { $Type : 'UI.DataField', Value : description } }, SelectionFields : [ name, assignedTo_ID, dueDate, status_ID ], LineItem : [ { $Type : 'UI.DataField', Value : name }, { $Type : 'UI.DataField', Value : description }, { $Type : 'UI.DataField', Value : assignedTo_ID, Label : 'Assigned To' }, { $Type : 'UI.DataField', Value : assignedTo.role.name }, { $Type : 'UI.DataField', Value : dueDate }, { $Type : 'UI.DataField', Value : status_ID, Label : 'Status', Criticality : status.criticality } ], HeaderFacets : [{ $Type : 'UI.ReferenceFacet', Target : '@UI.FieldGroup#Detail' }], Facets : [{ $Type : 'UI.ReferenceFacet', ID : 'ActivityDetails', Target : '@UI.FieldGroup#Details', Label : 'Details' }], FieldGroup #Detail : {Data : [{ $Type : 'UI.DataField', Value : status_ID, Criticality : status.criticality }]}, FieldGroup #Details : { $Type : 'UI.FieldGroupType', Data : [ { $Type : 'UI.DataField', Value : assignedTo_ID, Label : 'Assigned To' }, { $Type : 'UI.DataField', Value : assignedTo.role.name, Label : 'Role' }, { $Type : 'UI.DataField', Value : dueDate, Label : 'Due Date' } ] },
}); annotate service.Activity with { ID @( title : 'Activity ID', UI.Hidden : true ) @readonly; parent @( title : 'Project ID', UI.Hidden : true ); name @(title : 'Activity'); description @( title : 'Description', UI.MultiLineText ); assignedTo @( Common : { Text : assignedTo.member_name, TextArrangement : #TextOnly, ValueListWithFixedValues, FieldControl : #Mandatory, ValueList : { $Type : 'Common.ValueListType', CollectionPath : 'Member', Parameters : [ { $Type : 'Common.ValueListParameterOut', LocalDataProperty : 'assignedTo_ID', ValueListProperty : 'ID' }, { $Type : 'Common.ValueListParameterIn', LocalDataProperty : 'parent_ID', ValueListProperty : 'parent_ID' }, { $Type : 'Common.ValueListParameterDisplayOnly', ValueListProperty : 'member_name' } ] } }, title : 'Assigned To' ); dueDate @(title : 'Due Date'); status @( Common : { Text : status.name, TextArrangement : #TextOnly, ValueListWithFixedValues, FieldControl : #Mandatory }, title : 'Status' );
} annotate service.Activity @(Capabilities : { SearchRestrictions : { $Type : 'Capabilities.SearchRestrictionsType', Searchable : true }, Insertable : true, Deletable : true, Updatable : true
}); ////////////////////////////////////////////////////////////////////////////
//
// UI annotations for the Employee Child Entity
//
annotate service.Employee with { userId @(title : 'User ID') @readonly; username @(title : 'User Name') @readonly; defaultFullName @(title : 'Name'); email @(title : 'e-Mail'); division @(title : 'Division'); department @(title : 'Department'); title @(title : 'Title');
} annotate service.Employee @(Capabilities : { Insertable : false, Deletable : false, Updatable : false
}); ////////////////////////////////////////////////////////////////////////////
//
// UI annotations for the SFSF_User Entity
//
annotate service.SFSF_User with @(UI : { CreateHidden : true, UpdateHidden : true, DeleteHidden : true, Identification : [{ $Type : 'UI.DataField', Value : defaultFullName }], HeaderInfo : { $Type : 'UI.HeaderInfoType', TypeName : 'User', TypeNamePlural : 'Users', Title : { $Type : 'UI.DataField', Value : defaultFullName }, Description : { $Type : 'UI.DataField', Value : title } }, SelectionFields : [ userId, username, division, department ], LineItem : [ { $Type : 'UI.DataField', Value : userId }, { $Type : 'UI.DataField', Value : defaultFullName }, { $Type : 'UI.DataField', Value : email }, { $Type : 'UI.DataField', Value : title }, { $Type : 'UI.DataField', Value : division }, { $Type : 'UI.DataField', Value : department } ],
}); annotate service.SFSF_User with { userId @( Common : { Text : defaultFullName, TextArrangement : #TextSeparate }, title : 'User ID' ) @readonly; username @(title : 'User Name') @readonly; defaultFullName @(title : 'Name'); email @(title : 'e-Mail'); division @(title : 'Division'); department @(title : 'Department'); title @(title : 'Title');
} annotate service.SFSF_User @(Capabilities : { SearchRestrictions : { $Type : 'Capabilities.SearchRestrictionsType', Searchable : false }, Insertable : false, Deletable : false, Updatable : false
}); ////////////////////////////////////////////////////////////////////////////
//
// UI annotations for the Role Entity
//
annotate service.Role with { ID @Common : { Text : name, TextArrangement : #TextOnly } @title : 'Role ID'; name @title : 'Role'
} annotate service.Role @(Capabilities : { Insertable : false, Deletable : false, Updatable : false
}); ////////////////////////////////////////////////////////////////////////////
//
// UI annotations for the Status Entity
//
annotate service.Status with { ID @Common : { Text : name, TextArrangement : #TextOnly } @title : 'Status ID'; name @title : 'Status'
} annotate service.Status @(Capabilities : { Insertable : false, Deletable : false, Updatable : false
});

The above code contains all required CDS annotations to make the UI look and behave like expected by the business rules defined in the series introduction and preparation.

However, the purpose of this blog post is not to go deep into the topic of CDS UI annotations and explain them in detail, but rather give you a starting point to further explore it in the future.

But, don’t worry! Here you have a really nice GitHub Repository from SAP Samples that’s a reach source of reference and feature showcase of CDS UI annotations to be used in Fiori Elements applications, where all the coding above is demonstrated and explained:

And you can also refer to the advanced topic of Serving Fiori UIs from the official CAP documentation:

But, before we move on to testing the application, it’s important to highlight just two pieces of those annotations: the value help for the “member” attribute in the Member entity and the value help for the  “assignedTo” attribute of the Activity Entity.

Notice that the collection path for the first value help is “SFSF_User“, that’s bringing the employees from SAP SuccessFactors which are later stored in the Employee entity of the application’s model by the business logic implemented in the last blog post, whilst the collection path for the second one is “Member“, because, according to the business rules, you can only assign activities to employees who are part of the project team.

Therefore, to filter the records in the second value help so only members of the current project are displayed, we set the Common.ValueListParameterIn parameter from the ValueList annotation to the Activity’s parent_ID which is actually the current project being edited.

Test the Application

Finally, it’s come the time to test everything that we have done so far! For that, we can rely on the nice Fiori Preview feature that’s available during development.

So, let’s do it!

From the service home page click on the Fiori Preview link next to the Project entity

.Figure%203%20-%20Service%20Home%20Page

Figure 3 – Service Home Page

After a couple seconds, the page below should be displayed:

Figure%204%20-%20Fiori%20Preview

Figure 4 – Fiori Preview

The three projects from the initial test data CSV files are listed. Click on the first one (S/4HANA Cloud implementation) and you should get to the object page below:

Figure%205%20-%20Project%20Object%20Page

Figure 5 – Project Object Page

Now, you have the full project details including the composition entities: Member and Activity.

The Fiori Preview is basically a complete Fiori Elements application based on the List report object page template which is locally available for testing during development.

Let’s focus on the key part of this application extension for our initial testing: the communication with SAP SuccessFactors.

Click on the Edit button at the top-right corner of the page. This should put the object in “draft editing mode” and make the page look like this:

Figure%206%20-%20Edit%20Project

Figure 6 – Edit Project

Now, let’s add a new team member. Click on the Create button right on top of the Members Smart Table:

Figure%207%20-%20New%20Member

Figure 7 – New Member

Click on the value help in the Name field to open-up the dialog for searching an employee from SAP SuccessFactors:

Figure 8 – Employees from SAP SuccessFactors

NOTE: the employees list from the above screenshot is from an SAP SuccessFactors demo tenant. Your list may differ from that one depending on the SAP SuccessFactors tenant you are using.

Click on the line for Sarah Lynn Moultone then click OK, to select that employee: This will take you back to the object page for the new member, but, now, you’ll notice that the Sarah Lynn Moultone is displayed in the Name field as well as in the page header.

Click on the Role dropdown list and select Solution Architect:

Figure%209%20-%20Data%20for%20New%20Member

Figure 9 – Data for New Member

Click on the Apply button in the bottom-right corner. This will take you back to the Edit Project page, but now, with the new member added to the Members Smart Table:

Figure%2010%20-%20New%20Member%20Added

Figure 10 – New Member Added

Click on the Save button in the bottom-right corner to save the project and go back to the standard object page:

Figure%2011%20-%20Project%20saved%20with%20New%20Team%20Member

Figure 11 – Project saved with New Team Member

Let’s check what happened on the SAP SuccessFactors side after this operation.

Log in to your SAP SuccessFactors tenant (the one you set up in the destination following the instructions in series introduction and preparation) and search for the employee Sarah Lynn Moultone (or any other that you used to add as the new team member of the project), then click on it to open the employee profile:

Figure%2012%20-%20Search%20Employee

Figure 12 – Search Employee

Figure%2013%20-%20Employee%20Profile

Figure 13 – Employee Profile

Now, search for the Special Assignments block from the profile to verify whether the project is showing up as an assignment.

NOTE: the Employee Profile from the SAP SuccessFactors tenant must be configured to display such information (use the Configure People Profile functionality from the Admin Center for that).

HINT: As it’s a configurable block, it can also be placed in different sections within the profile in each tenant. To facilitate, try to search for the S/4HANA Cloud implementation project in the page, after making sure that the assignments block is properly configured in the profile.

Figure%2014%20-%20Special%20Assignment%20in%20SuccessFactors

Figure 14 – Special Assignment in SAP SuccessFactors

Other Tests

The test sequence we’ve executed is the most important in the context of extending the SAP SuccessFactors solution with data flowing in both directions: from and to SAP SuccessFactors.

Here is a list of other tests that you can execute on your own to make sure the application is working fine:

  • Create a new project
  • Assign team members to newly created project – check whether special assignment is properly created in SAP SuccessFactors
  • Assign activities to team members
  • Remove a team member – all assigned activities should be cascadely deleted
  • Change an existing team member by another employee – corresponding assignment shoud be created in SAP SuccessFactors and assigned activities should be automatically transfered to the new employee
  • Delete assigned activities
  • Delete the project – all team members and assigned activities should be cascadely deleted
  • Test the filters in the list report pages: projects main page and employees value help
  • After completing all those operations, make sure the Employee entity has only employees that are assigned as team members of the active projects (via the Employee link in the service home page).

If all those tests are successful, then we can say your application is working pretty fine according to the defined business rules.

Conclusion

Congratulations! You have successfully prepared your CAP service to properly serve a Fiori Elements UI application and also fully tested the look and behavior of your service. The next step is to make your application secure for when it’s deployed and publicly available in the cloud.

NOTE: all the instructions provided in this blog post apply exactly the same to any CAP servicethat should be consumed by and HTML5 UI which rely on OData annotations.

Please, do not hesitate to submit your questions in SAP Community through the Q&A tag link: https://answers.sap.com/index.html

Next blog post in this series

  • Add Security