Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 
ale_biagi
Product and Topic Expert
Product and Topic Expert

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 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 3 - Service Home Page


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


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 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 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 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 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 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 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 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 12 - Search Employee



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 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 should be created in SAP SuccessFactors and assigned activities should be automatically transferred 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 service that should be consumed by an 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



 

 

 

 
4 Comments
SamSun
Product and Topic Expert
Product and Topic Expert
0 Kudos

Hi Alessandro,


Thank you so much for your blog it's very useful.

Now I have confronted with the issue when I save the project and get a issue that comes from the DB.

Would you please have a look at this problem?

Thank you so much for your help.

ale_biagi
Product and Topic Expert
Product and Topic Expert
0 Kudos
Hi Sam,

Without knowing what you actually did along the previous steps I really cannot tell what's causing the issue.

In this specific blog where you are posting your comment, you where not supposed to have deployed the DB to HANA Cloud yet, but looking at the message it seems you already did it - maybe because you are not following the order of the posts - and, somehow, the MEMBER table hasn't got created in the HDI container schema - maybe due to a typo in the model or some other reason -, so when its used by the business logic it cannot be found.

I suggest you restart the series from scratch following the order of the posts.

Regards,

Alessandro
SamSun
Product and Topic Expert
Product and Topic Expert
0 Kudos
Hi Alessandro,

 

Later to reply and I found the issue.

Because the HANA Cloud table with the namespace when created in the schema so that the issue has been raised.

Solution:

Add the name namespace when handle with table in the lib.js file. check the code https://github.com/milkdeliver/SFSF-PROJMAN/blob/main/srv/lib/handlers.js


 

BTW, it's a very useful blog and helps me a lot.

 

BR,

Sam
ale_biagi
Product and Topic Expert
Product and Topic Expert
0 Kudos

If you look back at this post, you'll notice that the code for handlers.js has been updated to comply with such requirement from the newer versions of CDS:

const cds = require('@sap/cds');
const namespace = 'sfsf.projman.model.db.';

let userService = null;
let assService = null;

...

...and:

...
// Get current members
let members = await cds.tx(req).run(SELECT.from(namespace + 'Member').columns(['ID', 'member_userId']).where({ parent_ID: { '=': req.data.ID } }));

...

So, your issue must have been related to a matter of  "timing" (you got the source code before it's been updated).

BR.

Alessandro