diff --git a/tutorials/cap-extend-sfsf-add-launchpad/access-repo.png b/tutorials/cap-extend-sfsf-add-launchpad/access-repo.png new file mode 100644 index 0000000000..98e2d30806 Binary files /dev/null and b/tutorials/cap-extend-sfsf-add-launchpad/access-repo.png differ diff --git a/tutorials/cap-extend-sfsf-add-launchpad/access-role.png b/tutorials/cap-extend-sfsf-add-launchpad/access-role.png new file mode 100644 index 0000000000..70ba30707b Binary files /dev/null and b/tutorials/cap-extend-sfsf-add-launchpad/access-role.png differ diff --git a/tutorials/cap-extend-sfsf-add-launchpad/add-app.png b/tutorials/cap-extend-sfsf-add-launchpad/add-app.png new file mode 100644 index 0000000000..2f1617f36b Binary files /dev/null and b/tutorials/cap-extend-sfsf-add-launchpad/add-app.png differ diff --git a/tutorials/cap-extend-sfsf-add-launchpad/add-catalog.png b/tutorials/cap-extend-sfsf-add-launchpad/add-catalog.png new file mode 100644 index 0000000000..9108f17994 Binary files /dev/null and b/tutorials/cap-extend-sfsf-add-launchpad/add-catalog.png differ diff --git a/tutorials/cap-extend-sfsf-add-launchpad/add-group.png b/tutorials/cap-extend-sfsf-add-launchpad/add-group.png new file mode 100644 index 0000000000..49e89c3de6 Binary files /dev/null and b/tutorials/cap-extend-sfsf-add-launchpad/add-group.png differ diff --git a/tutorials/cap-extend-sfsf-add-launchpad/assign-app.png b/tutorials/cap-extend-sfsf-add-launchpad/assign-app.png new file mode 100644 index 0000000000..fe88c5f2a6 Binary files /dev/null and b/tutorials/cap-extend-sfsf-add-launchpad/assign-app.png differ diff --git a/tutorials/cap-extend-sfsf-add-launchpad/cap-extend-sfsf-add-launchpad.md b/tutorials/cap-extend-sfsf-add-launchpad/cap-extend-sfsf-add-launchpad.md new file mode 100644 index 0000000000..1f00d9640c --- /dev/null +++ b/tutorials/cap-extend-sfsf-add-launchpad/cap-extend-sfsf-add-launchpad.md @@ -0,0 +1,155 @@ +--- +title: Add the SAP SuccessFactors Extension to the Launchpad +description: In this last phase of the development journey you will finally add your deployed extension to an SAP Fiori Launchpad on BTP. +auto_validation: true +time: 7 +tags: [ tutorial>beginner, software-product>sap-btp--cloud-foundry-environment] +primary_tag: software-product-function>sap-cloud-application-programming-model +--- + +## Prerequisites + - Complete the tutorial: [**Prepare to Develop the SAP SuccessFactors Extension**](cap-extend-sfsf-intro) + - Complete the tutorial: [**Jump start the SAP SuccessFactors Extension CAP Project**](cap-extend-sfsf-jumpstart) + - Complete the tutorial: [**Import SAP SuccessFactors OData Services definitions**](cap-extend-sfsf-import-services) + - Complete the tutorial: [**Create the CDS Data Model for the SAP SuccessFactors Extension**](cap-extend-sfsf-data-model) + - Complete the tutorial: [**Create the CAP Service for the SAP SuccessFactors Extension**](cap-extend-sfsf-create-service) + - Complete the tutorial: [**Add Business Logic to the SAP SuccessFactors Extension**](cap-extend-sfsf-add-logic) + - Complete the tutorial: [**Prepare the SAP SuccessFactors Extension UI with CDS Annotations**](cap-extend-sfsf-ui-annotations) + - Complete the tutorial: [**Add Security to the SAP SuccessFactors Extension**](cap-extend-sfsf-add-security) + - Complete the tutorial: [**Deploy the SAP SuccessFactors Extension Database to SAP HANA Cloud**](cap-extend-sfsf-deploy-hc) + - Complete the tutorial: [**Develop the SAP Fiori Elements Application for the SAP SuccessFactors Extension**](cap-extend-sfsf-fiori-elements) + - Complete the tutorial: [**Deploy the SAP SuccessFactors Extension to Cloud Foundry**](cap-extend-sfsf-deploy-cf) + +## Details +### You will learn + - How to **access the Launchpad Service Site Manager** + - How to **prepare the business content** for the Launchpad Site + - How to **assign the application** to the **business content** and **launchpad roles** + - How to **create the Launchpad Site** + - How to **test the Launchpad Site and your application** + +--- + +[ACCORDION-BEGIN [Step 1: ](Access the Launchpad Service Site Manager)] + +In the **SAP BTP cockpit**, on the **left-hand pane** expand the **Services** menu and click on **Instances and Subscriptions**. On the right side, locate the **Launchpad Service** under **Subscriptions** and click on the **Go to Application** icon next to it. + +![Figure 1 – Go to Application on Launchpad Service subscription](launch-service.png) + +The **Site Manager UI** will open-up in a new browser tab. So, the first thing you need to do is to **refresh** (grab) the content from the **HTML5 applications** repository for bring in our application from there. + +Click on the **sandwich menu** at the top-left corner of the page to expand the app menu and click on the **Provider Manager** option. Then, click on the **refresh button** at the far right of the **HTML5 apps** provider to bring-in the content from there. + +![Figure 2 – Refresh content from the HTML5 apps repository](refresh-content.png) + +[DONE] +[ACCORDION-END] + +[ACCORDION-BEGIN [Step 2: ](Prepare Business Content: Application, Group and Catalog)] + +To learn about the **concepts** managed by the **SAP Launchpad Service**, please refer to the official documentation in [**this link**](https://help.sap.com/viewer/8c8e1958338140699bd4811b37b82ece/Cloud/en-US/3f619a13ca2a4a59a14bec8507c3fb69.html). + +Click on the **Content Manager** option, then on the **Content Explorer** tab and, finally, on the **HTML5 Apps** tile. + +![Figure 3 – Access HTML5 Apps Repository](access-repo.png) + +Select the **Manage Projects** app, then click on the **Add to My Content** button. + +![Figure 4 – Add Manage Projects App to My Content](add-app.png) + +Click on the **My Content** tab, then on the **New** button and select **Catalog** from the menu. + +![Figure 5 – Add New Catalog](add-catalog.png) + +Type **Project Management** as **Title** and **Project Management Catalog** as **Description**. Click on the **Assign Items** search bar and, then, on the "**+**" icon to the right of the **Manage Projects** app. Finally, click on the **Save** button. + +![Figure 6 – Project Management Catalog Information](catalog-info.png) + +Go back to the **Content Manager** home page, click on the **New** button and select **Group** from the menu. + +![Figure 7 – Add New Group](add-group.png) + +Type **SuccessFactors Extensions** as **Title** and **SuccessFactors Extensions Group** as **Description**. Click on the **Assign Items** search bar and, then, on the "**+**" icon to the right of the **Manage Projects** app. Finally, click on the **Save** button. + +![Figure 8 – SuccessFactors Extensions Group Information](group-info.png) + +[DONE] +[ACCORDION-END] + +[ACCORDION-BEGIN [Step 3: ](Assign the Application to Everyone)] + +Go back to the **Content Manager** home page and click on the **Everyone** role in the list. + +![Figure 9 – Access the Everyone Role](access-role.png) + +Click on the **Edit** button at the top-right and then on the **Assign Items** search bar. Click on the "**+**" icon to the right of the **Manage Projects** app. Finally, click on the **Save** button. + +![Figure 10 – Assign the Manage Projects app to the Everyone Role](assign-app.png) + +[DONE] +[ACCORDION-END] + +[ACCORDION-BEGIN [Step 4: ](Create the Launchpad Site)] + +Click on the **Site Directory** option and then on the **Create Site** button. + +![Figure 11 – Create New Site](create-site.png) + +Name the site **BTP Extensions** and click on the **Create** button. + +![Figure 12 – Site Name](site-name.png) + +[DONE] +[ACCORDION-END] + +[ACCORDION-BEGIN [Step 5: ](Test the Site and Application)] + +Click on the link below the **URL** field to **launch the site**, which will open-up in a new browser tab. + +![Figure 13 – Launch the Site](launch-site.png) + +Click on the **Maintain Projects** tile to **launch the application**. + +> **NOTE**: the title "**Maintain Projects**" is taken from the **SAP Fiori Launchpad (FLP) configuration** that was done when the SAP Fiori Elements application has been created following this tutorial: [**Develop the SAP Fiori Elements Application for the SAP SuccessFactors Extension**](cap-extend-sfsf-fiori-elements). + +![Figure 14 – Launch Application](launch-app.png) + +The **List Report page** for the **Project** entity will be loaded: + +![Figure 15 – List Report page for Project entity](list-report.png) + +Now, click on the **Go** button and the **initial test data** (loaded to the HANA database from the CSV files) will be displayed: + +![Figure 16 – Initial test data displayed](test-data.png) + +And that's it! You have successfully added a **fully working SAP Fiori Elements application** deployed to **SAP BTP's HTML5 Apps Repository** to an **SAP Launchpad Service** site. + +You can, now, test it at your will, in the same way you did using the **Fiori Preview** in **step 6** of the tutorial: [**Prepare the SAP SuccessFactors Extension UI with CDS Annotations**](cap-extend-sfsf-ui-annotations). + +And this is the **final step** to **Extend SAP SuccessFactors on SAP BTP with CAP**! + +[DONE] +[ACCORDION-END] + +[ACCORDION-BEGIN [Step 6: ](Check Your Knowledge)] + +[VALIDATE_1] +[ACCORDION-END] + +[ACCORDION-BEGIN [Step 7: ](Stretch Yourself)] + +**Congratulations**! After having gone through and completed the tutorials in this group you should have successfully **Extended SAP SuccessFactors on SAP BTP with CAP** with a **simple project management application**! + +But there's a lot of room for improvement in this solution, so don't miss the opportunity to challenge yourself and go deeper in learning the technologies showcased in this tutorials group by enhancing your application! + +Here's a list of suggestions for enhancements: + +- Directly assign an SAP SuccessFactors employee to a project as the "Project Owner" adding the corresponding assignment to the employee profile +- Implement a mechanism to remove the assignment from the employee profile whenever he/she is removed from a project team (either by member deletion or replacement). The same would apply to the "Project Owner" +- Implement validations such as: projects must start at least from the current date (not before) and must end after the start date and activities due dates must be within the project's start and end dates +- Implement a mechanism to avoid activities to be added before there's at least one member in the team to assign them +- Implement restrictions such as: users can only view/manage projects that have been created by themselves, only employees who report to the project creator can be assigned as team members, etc. +- And so on, and so forth… the sky's the limit! + +[DONE] +[ACCORDION-END] diff --git a/tutorials/cap-extend-sfsf-add-launchpad/catalog-info.png b/tutorials/cap-extend-sfsf-add-launchpad/catalog-info.png new file mode 100644 index 0000000000..4024f68381 Binary files /dev/null and b/tutorials/cap-extend-sfsf-add-launchpad/catalog-info.png differ diff --git a/tutorials/cap-extend-sfsf-add-launchpad/create-site.png b/tutorials/cap-extend-sfsf-add-launchpad/create-site.png new file mode 100644 index 0000000000..1a27c8d315 Binary files /dev/null and b/tutorials/cap-extend-sfsf-add-launchpad/create-site.png differ diff --git a/tutorials/cap-extend-sfsf-add-launchpad/group-info.png b/tutorials/cap-extend-sfsf-add-launchpad/group-info.png new file mode 100644 index 0000000000..1d70a90052 Binary files /dev/null and b/tutorials/cap-extend-sfsf-add-launchpad/group-info.png differ diff --git a/tutorials/cap-extend-sfsf-add-launchpad/launch-app.png b/tutorials/cap-extend-sfsf-add-launchpad/launch-app.png new file mode 100644 index 0000000000..9f67313b3e Binary files /dev/null and b/tutorials/cap-extend-sfsf-add-launchpad/launch-app.png differ diff --git a/tutorials/cap-extend-sfsf-add-launchpad/launch-service.png b/tutorials/cap-extend-sfsf-add-launchpad/launch-service.png new file mode 100644 index 0000000000..50ce37cb8e Binary files /dev/null and b/tutorials/cap-extend-sfsf-add-launchpad/launch-service.png differ diff --git a/tutorials/cap-extend-sfsf-add-launchpad/launch-site.png b/tutorials/cap-extend-sfsf-add-launchpad/launch-site.png new file mode 100644 index 0000000000..e5b954c681 Binary files /dev/null and b/tutorials/cap-extend-sfsf-add-launchpad/launch-site.png differ diff --git a/tutorials/cap-extend-sfsf-add-launchpad/list-report.png b/tutorials/cap-extend-sfsf-add-launchpad/list-report.png new file mode 100644 index 0000000000..6a2e48dfd3 Binary files /dev/null and b/tutorials/cap-extend-sfsf-add-launchpad/list-report.png differ diff --git a/tutorials/cap-extend-sfsf-add-launchpad/refresh-content.png b/tutorials/cap-extend-sfsf-add-launchpad/refresh-content.png new file mode 100644 index 0000000000..d7b1b59c97 Binary files /dev/null and b/tutorials/cap-extend-sfsf-add-launchpad/refresh-content.png differ diff --git a/tutorials/cap-extend-sfsf-add-launchpad/site-name.png b/tutorials/cap-extend-sfsf-add-launchpad/site-name.png new file mode 100644 index 0000000000..6937230a78 Binary files /dev/null and b/tutorials/cap-extend-sfsf-add-launchpad/site-name.png differ diff --git a/tutorials/cap-extend-sfsf-add-launchpad/test-data.png b/tutorials/cap-extend-sfsf-add-launchpad/test-data.png new file mode 100644 index 0000000000..c4d489395e Binary files /dev/null and b/tutorials/cap-extend-sfsf-add-launchpad/test-data.png differ diff --git a/tutorials/cap-extend-sfsf-add-logic/cap-extend-sfsf-add-logic.md b/tutorials/cap-extend-sfsf-add-logic/cap-extend-sfsf-add-logic.md new file mode 100644 index 0000000000..823f646b1a --- /dev/null +++ b/tutorials/cap-extend-sfsf-add-logic/cap-extend-sfsf-add-logic.md @@ -0,0 +1,566 @@ +--- +title: Add Business Logic to the SAP SuccessFactors Extension +description: In this phase of the development you will add the business logic of the extension. +auto_validation: true +time: 15 +tags: [ tutorial>beginner, software-product>sap-btp--cloud-foundry-environment] +primary_tag: software-product-function>sap-cloud-application-programming-model +--- + +## Prerequisites + - Complete the tutorial: [**Prepare to Develop the SAP SuccessFactors Extension**](cap-extend-sfsf-intro) + - Complete the tutorial: [**Jump start the SAP SuccessFactors Extension CAP Project**](cap-extend-sfsf-jumpstart) + - Complete the tutorial: [**Import SAP SuccessFactors OData Services definitions**](cap-extend-sfsf-import-services) + - Complete the tutorial: [**Create the CDS Data Model for the SAP SuccessFactors Extension**](cap-extend-sfsf-data-model) + - Complete the tutorial: [**Create the CAP Service for the SAP SuccessFactors Extension**](cap-extend-sfsf-create-service) + +## Details +### You will learn + - How to **create the code file** of the **service module** to attach the service handlers + - How to **write the service module code** + - How to **organize your code** in the CAP project + - How to **write the service handlers code** + - How to **attach the service handlers** to the **service module events** (OData operations) + +--- + +[ACCORDION-BEGIN [Step 1: ](Create the Service Module Code File)] + +The **business logic** of the application is implemented via **custom service handlers** for the various operations executed on its entities (create, read, update, delete, etc.). Those handlers are defined in a **module** within a `JavasScript` file with the **same name** of the service but with the `.js` extension. + +So, now it's time for you to create it. + +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](create-file.png) + +On the **dialog** name the file `projman-service.js` and click **OK**. + +![Figure 2 – Set File Name](set-file-name.png) + +[DONE] +[ACCORDION-END] + +[ACCORDION-BEGIN [Step 2: ](Code the Service Module)] + +Copy and paste the **code snippet below** into the recently created file: + +```JavasScript +const cds = require('@sap/cds'); + +module.exports = cds.service.impl(async function () { + /*** SERVICE ENTITIES ***/ + const { + Project, + Member, + SFSF_User, + } = this.entities; + + /*** HANDLERS REGISTRATION ***/ + // ON events + + // BEFORE events + + // AFTER events +}); +``` +Here you import the `@sap/cds` dependency and reference it as `cds`. Then, you implement the service module and, inside it, reference three entities: `Project`, `Member` and `SFSF_User`, as you are supposed to develop handlers for them. + +Finally, you make some **comments** as **placeholders** to mark where you will further put your code. + +[DONE] +[ACCORDION-END] + +[ACCORDION-BEGIN [Step 3: ](Organize Your Code)] + +It is a best practice to have your code organized into **files** representing the nature of the code (i.e. **utility functions** should go into some `utils` file, **handlers** should go into some `handlers` file and so on). Those files represent your "code library", so it's appropriate to store them into some `lib` folder. + +So, now you'll create the `lib` folder and its contents. + +In the **Terminal** press `CTRL+C` to terminate the `cds watch` command (if not yet terminated). + +![Figure 3 – Terminate cds watch](terminate-watch.png) + +Type `cd srv` and press **Enter**. + +![Figure 4 – Change to srv directory](change-to-srv.png) + +Type `mkdir lib` and press **Enter**. + +![Figure 5 – Create lib directory](create-lib.png) + +Type `touch lib/handlers.js` and press **Enter**. + +![Figure 6 – Create handlers.js file](create-handlers.png) + +[DONE] +[ACCORDION-END] + +[ACCORDION-BEGIN [Step 4: ](Code the Service Handlers)] + +Now, you'll develop the required service handlers according to the business rules that have been defined in the [**group introduction and preparation**](cap-extend-sfsf-intro). + +### Users Read Handler + +On the **left-hand pane** expand the `lib` folder, then click on the `hanlers.js` file to open it. + +![Figure 7 – Create handlers.js file](open-handlers.png) + +Copy and paste the **following code snippet** into `handlers.js`: + +```JavasScript +const cds = require('@sap/cds'); + +let userService = null; +let assService = null; + +(async function () { + // Connect to external SFSF OData services + userService = await cds.connect.to('PLTUserManagement'); + assService = await cds.connect.to('ECEmployeeProfile'); +})(); + +/*** HELPERS ***/ + +// Remove the specified columns from the ORDER BY clause of a SELECT statement +function removeColumnsFromOrderBy(query, columnNames) { + if (query.SELECT && query.SELECT.orderBy) { + columnNames.forEach(columnName => { + // Look for column in query and its respective index + const element = query.SELECT.orderBy.find(column => column.ref[0] === columnName); + const idx = query.SELECT.orderBy.indexOf(element); + + if (idx > -1) { + // Remove column from oder by list + query.SELECT.orderBy.splice(idx, 1); + if (!query.SELECT.orderBy.length) { + // If list ends up empty, remove it from query + delete query.SELECT.orderBy; + } + } + }); + } + + return query; +} + +/*** HANDLERS ***/ + +// Read SFSF users +async function readSFSF_User(req) { + try { + // Columns that are not sortable must be removed from "order by" + req.query = removeColumnsFromOrderBy(req.query, ['defaultFullName']); + + // Handover to the SF OData Service to fecth the requested data + const tx = userService.tx(req); + return await tx.run(req.query); + } catch (err) { + req.error(err.code, err.message); + } +} + +module.exports = { + readSFSF_User +} +``` + +Back to `projman-service.js` add the following lines right under `const cds = require('@sap/cds');`: + +```JavasScript +const { + readSFSF_User +} = require('./lib/handlers'); +``` + +Add the **following line** right under the comment `// ON events`: + +```JavasScript +this.on('READ', SFSF_User, readSFSF_User); +``` +Your `projman-service.js` code should now look like this: + +![Figure 8 – Current stage of projman-service.js](current-stage.png) + +Quickly analyze what's been done. + +In the `lib/handlers.js` file you connected `cds` to the SAP SuccessFactors external services, coded one helper function that removes undesired columns from the `order by` clause of a query's select statement and, finally, coded the handler function to read the users from SAP SuccessFactors using the `PLTUserManagement` service. + +The code logic is well explained in the detailed comments. + +Now, test that handler. + +In the **Terminal** type `cd ..` and press **Enter** to go back to the **project root directory**. + +![Figure 9 – Change back to project root](change-to-project.png) + +Once again type `cds watch` and press **Enter**. Then `CTRL+Click` on the `http://localhost:4004` link to launch the **application home page**. + +![Figure 10 – Application home page](service-endpoints.png) + +Click on the `SFSF_User` link + +![Figure 11 – Users from SAP SuccessFactors](sfsf-users.png) + +Now, you should be able to view the users that are being read from **SAP SuccessFactors** via the **User entity** from the `PLTUserManagement` service. + +### Other Handlers + +That was the most important handler you should first implement as it's the one responsible for bringing the SAP SuccessFactors' employees into your application. + +Now, you can "fast forward" and implement all the other handlers of your application at once. + +Open the `lib/hanlers.js` file, then copy and paste the **following code** over (overwrite) the current content: + +```JavasScript +const cds = require('@sap/cds'); + +let userService = null; +let assService = null; + +(async function () { + // Connect to external SFSF OData services + userService = await cds.connect.to('PLTUserManagement'); + assService = await cds.connect.to('ECEmployeeProfile'); +})(); + +/*** HELPERS ***/ + +// Remove the specified columns from the ORDER BY clause of a SELECT statement +function removeColumnsFromOrderBy(query, columnNames) { + if (query.SELECT && query.SELECT.orderBy) { + columnNames.forEach(columnName => { + // Look for column in query and its respective index + const element = query.SELECT.orderBy.find(column => column.ref[0] === columnName); + const idx = query.SELECT.orderBy.indexOf(element); + + if (idx > -1) { + // Remove column from oder by list + query.SELECT.orderBy.splice(idx, 1); + if (!query.SELECT.orderBy.length) { + // If list ends up empty, remove it from query + delete query.SELECT.orderBy; + } + } + }); + } + + return query; +} + +// Helper for employee create execution +async function executeCreateEmployee(req, userId) { + const employee = await cds.tx(req).run(SELECT.one.from('Employee').columns(['userId']).where({ userId: { '=': userId } })); + if (!employee) { + const sfsfUser = await userService.tx(req).run(SELECT.one.from('User').columns(['userId', 'username', 'defaultFullName', 'email', 'division', 'department', 'title']).where({ userId: { '=': userId } })); + if (sfsfUser) { + await cds.tx(req).run(INSERT.into('Employee').entries(sfsfUser)); + } + } +} + +// Helper for employee update execution +async function executeUpdateEmployee(req, entity, entityID, userId) { + // Need to check whether column has changed + const column = 'member_userId'; + const query = SELECT.one.from(entity).columns([column]).where({ ID: { '=': entityID } }); + const item = await cds.tx(req).run(query); + if (item && item[column] != userId) { + // Member has changed, then: + // Make sure there's an Employee entity for the new assignment + await executeCreateEmployee(req, userId); + + // Create new assignment + await createAssignment(req, entity, entityID, userId); + } + return req; +} + +// Helper for assignment creation +async function createAssignment(req, entity, entityID, userId) { + const columns = m => { m.member_userId`as userId`, m.parent(p => { p.name`as name`, p.description`as description`, p.startDate`as startDate`, p.endDate`as endDate` }), m.role(r => { r.name`as role` }) }; + const item = await cds.tx(req).run(SELECT.one.from(entity).columns(columns).where({ ID: { '=': entityID } })); + if (item) { + const assignment = { + userId: userId, + project: item.parent.name, + description: item.role.role + " of " + item.parent.description, + startDate: item.parent.startDate, + endDate: item.parent.endDate + }; + console.log(assignment); + const element = await assService.tx(req).run(INSERT.into('Background_SpecialAssign').entries(assignment)); + if (element) { + await cds.tx(req).run(UPDATE.entity(entity).with({ hasAssignment: true }).where({ ID: entityID })); + } + } + return req; +} + +// Helper for cascade deletion +async function deepDelete(tx, ID, childEntity) { + return await tx.run(DELETE.from(childEntity).where({ parent_ID: { '=': ID } })); +} + +/*** HANDLERS ***/ + +// Read SFSF users +async function readSFSF_User(req) { + try { + // Columns that are not sortable must be removed from "order by" + req.query = removeColumnsFromOrderBy(req.query, ['defaultFullName']); + + // Handover to the SF OData Service to fecth the requested data + const tx = userService.tx(req); + return await tx.run(req.query); + } catch (err) { + req.error(err.code, err.message); + } +} + +// Before create/update: member +async function createEmployee(req) { + try { + // Add SFSF User to Employees entity if it does not exist yet + const item = req.data; + const userId = (item.member_userId) ? item.member_userId : null; + if (userId) { + await executeCreateEmployee(req, userId); + } + return req; + } catch (err) { + req.error(err.code, err.message); + } +} + +// After create: member +async function createItem(data, req) { + try { + // Create assignment in SFSF + console.log('After create.'); + await createAssignment(req, req.entity, data.ID, data.member_userId); + return data; + } catch (err) { + req.error(err.code, err.message); + } +} + +// Before update: member +async function updateEmployee(req) { + try { + // Need to check if team member was updated + if (req.data.member_userId) { + const ID = (req.params[0]) ? ((req.params[0].ID) ? req.params[0].ID : req.params[0]) : req.data.ID; + const userId = req.data.member_userId; + await executeUpdateEmployee(req, req.entity, ID, userId); + } + return req; + } catch (err) { + req.error(err.code, err.message); + } +} + +// Before delete: project or member +async function deleteChildren(req) { + try { + // Cascade deletion + if (req.entity.indexOf('Project') > -1) { + await deepDelete(cds.tx(req), req.data.ID, 'Activity'); + await deepDelete(cds.tx(req), req.data.ID, 'Member'); + } else { + const item = await cds.tx(req).run(SELECT.one.from(req.entity).columns(['parent_ID']).where({ ID: { '=': req.data.ID } })); + if (item) { + await deepDelete(cds.tx(req), item.parent_ID, 'Activity'); + } + } + return req; + } catch (err) { + req.error(err.code, err.message); + } +} + +// After delete/update: member +async function deleteUnassignedEmployees(data, req) { + try { + // Build clean-up filter + const members = SELECT.distinct.from('Member').columns(['member_userId as userId']); + const unassigned = SELECT.distinct.from('Employee').columns(['userId']).where({ userId: { 'NOT IN': members } }); + + // Get the unassigned employees for deletion + let deleted = await cds.tx(req).run(unassigned); + + // Make sure result is an array + deleted = (deleted.length === undefined) ? [deleted] : deleted; + + // Clean-up Employees + for (var i = 0; i < deleted.length; i++) { + const clean_up = DELETE.from('Employee').where({ userId: { '=': deleted[i].userId } }); + await cds.tx(req).run(clean_up); + } + return data; + } catch (err) { + req.error(err.code, err.message); + } +} + +// Before "save" project (exclusive for Fiori Draft support) +async function beforeSaveProject(req) { + try { + if (req.data.team) { + // Capture IDs and users from saved members + let users = [] + req.data.team.forEach(member => { users.push({ ID: member.ID, member_userId: member.member_userId }); }); + + // Get current members + let members = await cds.tx(req).run(SELECT.from('Member').columns(['ID', 'member_userId']).where({ parent_ID: { '=': req.data.ID } })); + if (members) { + // Make sure result is an array + members = (members.length === undefined) ? [members] : members; + + // Process deleted members + const deleted = []; + members.forEach(member => { + const element = users.find(user => user.ID === member.ID); + if (!element) deleted.push(member); + }); + for (var i = 0; i < deleted.length; i++) { + // Delete members' activities + await cds.tx(req).run(DELETE.from('Activity').where({ assignedTo_ID: { '=': deleted[i].ID } })); + if (req.data.activities) { + let idx = 0; + do { + idx = req.data.activities.findIndex(activity => activity.assignedTo_ID === deleted[i].ID); + if (idx > -1) { + req.data.activities.splice(idx, 1); + } + } while (idx > -1) + } + } + + // Process added members + const added = []; + users.forEach(user => { + const element = members.find(member => user.ID === member.ID); + if (!element) added.push(user); + }); + for (var i = 0; i < added.length; i++) { + await executeCreateEmployee(req, added[i].member_userId); + } + + // Process updated members + const updated = []; + users.forEach(user => { + const element = members.find(member => user.ID === member.ID); + if (element) updated.push(user); + }); + for (var i = 0; i < updated.length; i++) { + await executeUpdateEmployee(req, 'Member', updated[i].ID, updated[i].member_userId); + } + } + } + return req; + } catch (err) { + req.error(err.code, err.message); + } +} + +// After "save" project (exclusive for Fiori Draft support) +async function afterSaveProject(data, req) { + try { + if (data.team) { + // Look for members with unassigned elementId + let unassigned = await cds.tx(req).run(SELECT.from('Member').columns(['ID', 'member_userId']).where({ parent_ID: { '=': data.ID }, and: { hasAssignment: { '=': false } } })); + if (unassigned) { + // Make sure result is an array + unassigned = (unassigned.length === undefined) ? [unassigned] : unassigned; + + // Create SFSF assignment + for (var i = 0; i < unassigned.length; i++) { + await createAssignment(req, 'Member', unassigned[i].ID, unassigned[i].member_userId); + } + } + } + await deleteUnassignedEmployees(data, req); + + return data; + } catch (err) { + req.error(err.code, err.message); + } +} + +module.exports = { + readSFSF_User, + createEmployee, + createItem, + updateEmployee, + deleteChildren, + deleteUnassignedEmployees, + beforeSaveProject, + afterSaveProject +``` + +You just added three additional helpers: two for employee creation/update and one for the special assignment creation in SAP SuccessFactors. + +Then, you added the required handlers for the before and after events (OData operations). + +The code logic is well explained in the comments details. + +[DONE] +[ACCORDION-END] + +[ACCORDION-BEGIN [Step 5: ](Attach the Service Handlers to the Service Module Events)] + +Open the `srv/projman-service.js` file, then copy and paste the **following code** over (overwrite) the current content: + +```JavasScript +const cds = require('@sap/cds'); +const { + readSFSF_User, + createEmployee, + updateEmployee, + createItem, + deleteChildren, + deleteUnassignedEmployees, + beforeSaveProject, + afterSaveProject +} = require('./lib/handlers'); + +module.exports = cds.service.impl(async function () { + /*** SERVICE ENTITIES ***/ + const { + Project, + Member, + SFSF_User, + } = this.entities; + + /*** HANDLERS REGISTRATION ***/ + // ON events + this.on('READ', SFSF_User, readSFSF_User); + + // BEFORE events + this.before('CREATE', Member, createEmployee); + this.before('UPDATE', Member, updateEmployee); + this.before('DELETE', Project, deleteChildren); + this.before('DELETE', Member, deleteChildren); + this.before('SAVE', Project, beforeSaveProject); // Fiori Draft support + + // AFTER events + this.after('CREATE', Member, createItem); + this.after('UPDATE', Member, deleteUnassignedEmployees); + this.after('DELETE', Project, deleteUnassignedEmployees); + this.after('DELETE', Member, deleteUnassignedEmployees); + this.after('SAVE', Project, afterSaveProject); // Fiori Draft support +}); +``` + +Here you just imported the handler functions from the `lib/handlers.js` file and attached them to their corresponding events (OData operations). + +And, with that, you completed the coding of the business logic for your application. + +[DONE] +[ACCORDION-END] + +[ACCORDION-BEGIN [Step 6: ](Check Your Knowledge)] + +[VALIDATE_1] +[ACCORDION-END] diff --git a/tutorials/cap-extend-sfsf-add-logic/change-to-project.png b/tutorials/cap-extend-sfsf-add-logic/change-to-project.png new file mode 100644 index 0000000000..23878005eb Binary files /dev/null and b/tutorials/cap-extend-sfsf-add-logic/change-to-project.png differ diff --git a/tutorials/cap-extend-sfsf-add-logic/change-to-srv.png b/tutorials/cap-extend-sfsf-add-logic/change-to-srv.png new file mode 100644 index 0000000000..2ce1c09f0d Binary files /dev/null and b/tutorials/cap-extend-sfsf-add-logic/change-to-srv.png differ diff --git a/tutorials/cap-extend-sfsf-add-logic/create-file.png b/tutorials/cap-extend-sfsf-add-logic/create-file.png new file mode 100644 index 0000000000..20d904e0a7 Binary files /dev/null and b/tutorials/cap-extend-sfsf-add-logic/create-file.png differ diff --git a/tutorials/cap-extend-sfsf-add-logic/create-handlers.png b/tutorials/cap-extend-sfsf-add-logic/create-handlers.png new file mode 100644 index 0000000000..8217327b14 Binary files /dev/null and b/tutorials/cap-extend-sfsf-add-logic/create-handlers.png differ diff --git a/tutorials/cap-extend-sfsf-add-logic/create-lib.png b/tutorials/cap-extend-sfsf-add-logic/create-lib.png new file mode 100644 index 0000000000..3de5824e13 Binary files /dev/null and b/tutorials/cap-extend-sfsf-add-logic/create-lib.png differ diff --git a/tutorials/cap-extend-sfsf-add-logic/current-stage.png b/tutorials/cap-extend-sfsf-add-logic/current-stage.png new file mode 100644 index 0000000000..190e36f93b Binary files /dev/null and b/tutorials/cap-extend-sfsf-add-logic/current-stage.png differ diff --git a/tutorials/cap-extend-sfsf-add-logic/open-handlers.png b/tutorials/cap-extend-sfsf-add-logic/open-handlers.png new file mode 100644 index 0000000000..a75bf9b149 Binary files /dev/null and b/tutorials/cap-extend-sfsf-add-logic/open-handlers.png differ diff --git a/tutorials/cap-extend-sfsf-add-logic/service-endpoints.png b/tutorials/cap-extend-sfsf-add-logic/service-endpoints.png new file mode 100644 index 0000000000..cd8f35b76f Binary files /dev/null and b/tutorials/cap-extend-sfsf-add-logic/service-endpoints.png differ diff --git a/tutorials/cap-extend-sfsf-add-logic/set-file-name.png b/tutorials/cap-extend-sfsf-add-logic/set-file-name.png new file mode 100644 index 0000000000..ae8b37cda5 Binary files /dev/null and b/tutorials/cap-extend-sfsf-add-logic/set-file-name.png differ diff --git a/tutorials/cap-extend-sfsf-add-logic/sfsf-users.png b/tutorials/cap-extend-sfsf-add-logic/sfsf-users.png new file mode 100644 index 0000000000..675184f003 Binary files /dev/null and b/tutorials/cap-extend-sfsf-add-logic/sfsf-users.png differ diff --git a/tutorials/cap-extend-sfsf-add-logic/terminate-watch.png b/tutorials/cap-extend-sfsf-add-logic/terminate-watch.png new file mode 100644 index 0000000000..cc31835865 Binary files /dev/null and b/tutorials/cap-extend-sfsf-add-logic/terminate-watch.png differ diff --git a/tutorials/cap-extend-sfsf-add-security/additional-deps.png b/tutorials/cap-extend-sfsf-add-security/additional-deps.png new file mode 100644 index 0000000000..751705ca51 Binary files /dev/null and b/tutorials/cap-extend-sfsf-add-security/additional-deps.png differ diff --git a/tutorials/cap-extend-sfsf-add-security/admin-role.png b/tutorials/cap-extend-sfsf-add-security/admin-role.png new file mode 100644 index 0000000000..dd68b9b202 Binary files /dev/null and b/tutorials/cap-extend-sfsf-add-security/admin-role.png differ diff --git a/tutorials/cap-extend-sfsf-add-security/cap-extend-sfsf-add-security.md b/tutorials/cap-extend-sfsf-add-security/cap-extend-sfsf-add-security.md new file mode 100644 index 0000000000..708e406dd5 --- /dev/null +++ b/tutorials/cap-extend-sfsf-add-security/cap-extend-sfsf-add-security.md @@ -0,0 +1,150 @@ +--- +title: Add Security to the SAP SuccessFactors Extension +description: In this phase of the development you will add authentication and authorization to the extension. +auto_validation: true +time: 10 +tags: [ tutorial>beginner, software-product>sap-btp--cloud-foundry-environment] +primary_tag: software-product-function>sap-cloud-application-programming-model +--- + +## Prerequisites + - Complete the tutorial: [**Prepare to Develop the SAP SuccessFactors Extension**](cap-extend-sfsf-intro) + - Complete the tutorial: [**Jump start the SAP SuccessFactors Extension CAP Project**](cap-extend-sfsf-jumpstart) + - Complete the tutorial: [**Import SAP SuccessFactors OData Services definitions**](cap-extend-sfsf-import-services) + - Complete the tutorial: [**Create the CDS Data Model for the SAP SuccessFactors Extension**](cap-extend-sfsf-data-model) + - Complete the tutorial: [**Create the CAP Service for the SAP SuccessFactors Extension**](cap-extend-sfsf-create-service) + - Complete the tutorial: [**Add Business Logic to the SAP SuccessFactors Extension**](cap-extend-sfsf-add-logic) + - Complete the tutorial: [**Prepare the SAP SuccessFactors Extension UI with CDS Annotations**](cap-extend-sfsf-ui-annotations) + +## Details +### You will learn + - How to **require user authentication** in your CAP service + - How to **define authorization** to access the service entities + - How to **setup the XSUAA service instance** for future deployment + - How to **add required dependencies** to the CAP project to handle application security + +--- + +[ACCORDION-BEGIN [Step 1: ](Set Service to Require Authentication)] + +So far, you have been working on a local project which only developers would have access to. Therefore, it's OK to leave your service "unprotected" as it's not yet publicly available. But, as soon as you deploy it to the cloud, it will be available to anyone who knows the service endpoint (which is not quite difficult). + +In such scenario, anyone can access the service anonymously and freely make OData calls to it, which will probably cause a big mess in your application and related data. So, it's **imperative to "protect" your service** against such risky situation by **granting access only to authenticated users**. + +Then, to make the service available only to authenticated users, you just need to add the `requires` annotation to the **service definition** (in the `projman-service.cds` file), like demonstrated below: + +![Figure 1 – Annotation for requiring the user to be authenticated](require-auth.png) + +Now, in the service home page, when you **click on any entity link** (i.e. `SFSF_User`) a pop-up in the browser will ask for the **username** and **password**. + +![Figure 2 – Login pop-up for mocked authentication](login-pop-up.png) + +As you are still in development you can provide whatever information you like and click **sign in**, because in this environment CAP is using what we call **mocked authentication** which does not utilize real users. So, after clicking sign in, you may see the data from the selected entity: + +![Figure 3 – Data retrieved after sign in](sfsf-users.png) + +[DONE] +[ACCORDION-END] + +[ACCORDION-BEGIN [Step 2: ](Set Authorization for Service Entities Access)] + +But, in an **administrative scenario** like this one, just **being an authenticated user is not enough**. It's appropriate that, additionally, the authenticated user must be granted some **administrative role** (i.e. an **Admin** role). + +Therefore, you need to specify that the entities from our service are only **viewable** and **maintainable** by users with the **Admin role**. To do so, you also **annotate your entities** with the **requires** annotation, but now you specify the **Admin** role, instead of `authenticated-user`, like demonstrated below: + +![Figure 4 – Restricting access to service entities for the Admin role](admin-role.png) + +Now, if you click again on the same entity link (i.e. `SFS_User`) you should receive an **HTTP 403 (Forbidden)** error: + +![Figure 5 – Unauthorized Access](unauthorized.png) + +It happened because the **mocked user** (any dummy data you provided) was **not granted the Admin role**, thus cannot access any entity annotated as such. + +So, you should fix it now. To do so, you just need to add an `auth` block into the `cds.requires` section of the `package.json` file, like demonstrated below: + +![Figure 6 – Authorization config for development](mock-auth.png) + +This block instructs CAP to use the **mock strategy** (which is default) during development and it creates **two mock users**: **John** who's granted the **Admin** role and **Mary** who's granted the **Viewer** role (which has no authorization in the context of your application). + +Now, you need to **login as john** (with any password that you like), but CAP has already opened a session to the first mock user you utilized (who has no Admin role granted) and won't pop-up for a new login (so, you stick with the forbidden error). + +To work this around, you will need to completely **close your browser** (not only the Business Application Studio tab), open it again and go back to your SAP BTP cockpit to access **Business Application Studio** from the **Instances and Subscriptions** page. Click on your Dev Space and when the IDE loads again make sure that `cds watch` is properly running, if not, run it again. Launch the **service home page** and click any entity link (i.e. `SFSF_User`). + +> **NOTE**: you can also try to open the `http://localhost:4040` in an **incognito window** to achieve that goal + +This time the **sign in pop-up** should appear and you **must login with john** to overcome the forbidden error: + +![Figure 7 – Entity data successfully retrieved after login with Admin role](sfsf-users.png) + +To learn more about **authentication** and **authorization** in CAP, you can refer to this two links from the official documentation: + +- [Authentication](https://cap.cloud.sap/docs/node.js/authentication) +- [Authorization and Access Control](https://cap.cloud.sap/docs/guides/authorization) + +[DONE] +[ACCORDION-END] + +[ACCORDION-BEGIN [Step 3: ](Setup XSUAA)] + +OK, so far you have defined a simple authorization schema to secure all service entities and verified that it's working as expected. But, in production, security will be handled by the **User Account and Authentication** (UAA) mechanism of Cloud Foundry which, in **SAP BTP**, is implemented by the **XSUAA service**. + +Therefore, you need to **configure the XSUAA service instance** to recognize and apply the schema you have defined when the application is deployed on SAP BTP. + +For that, you need to create a file named `xs-security.json` in the **project root folder**, and the good news is that CAP can do it for you automatically based on the security schema from the service definition. + +In the **Terminal** press `CTRL+C` to terminate the `cds watch` command, then type the command: + +- `cds compile srv/ --to xsuaa >xs-security.json` + +Then, press **Enter**. + +![Figure 8 – xs-security.json creation](xs-security.png) + +On the **left-hand pane** click on the `xs-security.json` file to open it. Examine the contents and add the **two lines** indicated in the **screenshot below** right before "**scopes**" (if not already generated by the compiler, as older versions of it used to add them automatically). In the **role-templates collection**, change the description of the **Admin** role from "**generated**" to "**Project Administrator**": + +![Figure 9 – Security descriptor content](security-desc.png) + +This file is used during the **creation** or **update** of the **XSUAA service instance** and controls the roles, scopes, attributes and role templates that will be part of the security for your application. + +Now, quickly understand what's done in this descriptor. + +Basically, you define a **scope** named **Admin** for the application and assign it to the **Admin role template**. This way, when the application is **deployed to SAP BTP** the **Admin role** will be **automatically created** in the platform and **associated** to the `sfsf-projman` application. + +But roles are not directly assigned to the application's users. The assignment is done through **role collections**. So, just add one to the file like demonstrated in the screenshot below: + +![Figure 10 – Create Role Collection](role-collection.png) + +After deployment, you just need to assign it to the user who should be granted such authorization. + +[DONE] +[ACCORDION-END] + +[ACCORDION-BEGIN [Step 4: ](Add Required Dependencies)] + +Now, you just need to add four dependencies to the CAP service for it to properly handle security and other related stuff – such as the user JSON Web Token (JWT) – during runtime. Those are: + +- `@sap/cds-dk` +- `@sap/xsenv` +- `@sap/xssec` +- `passport` + +Therefore, in the **Terminal**, just type the following commands pressing **Enter** after each one: + +- `npm install @sap/xsenv` +- `npm install @sap/cds-dk` +- `npm install @sap/xssec` +- `npm install passport` + +When the commands complete, your `package.json` file should like the screenshot below: + +![Figure 11 – Installed dependencies](additional-deps.png) + +[DONE] +[ACCORDION-END] + +[ACCORDION-BEGIN [Step 5: ](Check Your Knowledge)] + + + +[VALIDATE_1] +[ACCORDION-END] diff --git a/tutorials/cap-extend-sfsf-add-security/login-pop-up.png b/tutorials/cap-extend-sfsf-add-security/login-pop-up.png new file mode 100644 index 0000000000..d592036d21 Binary files /dev/null and b/tutorials/cap-extend-sfsf-add-security/login-pop-up.png differ diff --git a/tutorials/cap-extend-sfsf-add-security/mock-auth.png b/tutorials/cap-extend-sfsf-add-security/mock-auth.png new file mode 100644 index 0000000000..de6b2e50d9 Binary files /dev/null and b/tutorials/cap-extend-sfsf-add-security/mock-auth.png differ diff --git a/tutorials/cap-extend-sfsf-add-security/require-auth.png b/tutorials/cap-extend-sfsf-add-security/require-auth.png new file mode 100644 index 0000000000..c4b0162f74 Binary files /dev/null and b/tutorials/cap-extend-sfsf-add-security/require-auth.png differ diff --git a/tutorials/cap-extend-sfsf-add-security/role-collection.png b/tutorials/cap-extend-sfsf-add-security/role-collection.png new file mode 100644 index 0000000000..9fb4ee96f9 Binary files /dev/null and b/tutorials/cap-extend-sfsf-add-security/role-collection.png differ diff --git a/tutorials/cap-extend-sfsf-add-security/security-desc.png b/tutorials/cap-extend-sfsf-add-security/security-desc.png new file mode 100644 index 0000000000..e06f68726a Binary files /dev/null and b/tutorials/cap-extend-sfsf-add-security/security-desc.png differ diff --git a/tutorials/cap-extend-sfsf-add-security/sfsf-users.png b/tutorials/cap-extend-sfsf-add-security/sfsf-users.png new file mode 100644 index 0000000000..675184f003 Binary files /dev/null and b/tutorials/cap-extend-sfsf-add-security/sfsf-users.png differ diff --git a/tutorials/cap-extend-sfsf-add-security/unauthorized.png b/tutorials/cap-extend-sfsf-add-security/unauthorized.png new file mode 100644 index 0000000000..3f4a53ab68 Binary files /dev/null and b/tutorials/cap-extend-sfsf-add-security/unauthorized.png differ diff --git a/tutorials/cap-extend-sfsf-add-security/xs-security.png b/tutorials/cap-extend-sfsf-add-security/xs-security.png new file mode 100644 index 0000000000..2c667de72b Binary files /dev/null and b/tutorials/cap-extend-sfsf-add-security/xs-security.png differ diff --git a/tutorials/cap-extend-sfsf-create-service/cap-extend-sfsf-create-service.md b/tutorials/cap-extend-sfsf-create-service/cap-extend-sfsf-create-service.md index 6a29a4aebc..b6d3f672c9 100644 --- a/tutorials/cap-extend-sfsf-create-service/cap-extend-sfsf-create-service.md +++ b/tutorials/cap-extend-sfsf-create-service/cap-extend-sfsf-create-service.md @@ -21,13 +21,13 @@ primary_tag: software-product-function>sap-cloud-application-programming-model --- -[ACCORDION-BEGIN [Step 1: ](Create the Service Definition File)] +[ACCORDION-BEGIN [Step 1: ](Create the service definition file)] In the previous tutorial from this group, you have defined the data model for your solution and populated it with some initial test data. But, when you first run the application you noticed no services have been defined, thus the entities where not exposed. In this step you will create the service definition file for further coding. -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**. +On the **left-hand pane** of **SAP Business Application Studio**, (1) select the `srv` folder, then (2) click on the **three dots** to the right of the project name and (3) select **New File**. ![Figure 1 – Create New File](create-file.png) @@ -38,7 +38,7 @@ On the **dialog** name the file `projman-service.cds` and click **OK**. [DONE] [ACCORDION-END] -[ACCORDION-BEGIN [Step 2: ](Code the Service Definition)] +[ACCORDION-BEGIN [Step 2: ](Code the service definition)] Copy and paste the **code snippet below** into the recently created file: @@ -79,7 +79,7 @@ service ProjectManager @(path : '/projman') { [DONE] [ACCORDION-END] -[ACCORDION-BEGIN [Step 3: ](Understand the Definition Code)] +[ACCORDION-BEGIN [Step 3: ](Understand the definition code)] Quickly analyze the service definition code: @@ -98,7 +98,7 @@ You also annotate the **Project** entity (the **root entity** of your model) wit [DONE] [ACCORDION-END] -[ACCORDION-BEGIN [Step 4: ](Test the Service)] +[ACCORDION-BEGIN [Step 4: ](Test the service)] Now that you have properly defined a service and exposed the desired entities, you should notice that, in the **Terminal**, `cds watch` has updated: @@ -119,7 +119,7 @@ If you click the `SFSF_User` link you'll get an error, because that's not an ent [DONE] [ACCORDION-END] -[ACCORDION-BEGIN [Step 5: ](Check Your Knowledge)] +[ACCORDION-BEGIN [Step 5: ](Check your knowledge)] [VALIDATE_1] [ACCORDION-END] diff --git a/tutorials/cap-extend-sfsf-data-model/cap-extend-sfsf-data-model.md b/tutorials/cap-extend-sfsf-data-model/cap-extend-sfsf-data-model.md index d389a6bc23..e3c429d3f0 100644 --- a/tutorials/cap-extend-sfsf-data-model/cap-extend-sfsf-data-model.md +++ b/tutorials/cap-extend-sfsf-data-model/cap-extend-sfsf-data-model.md @@ -21,7 +21,7 @@ primary_tag: software-product-function>sap-cloud-application-programming-model --- -[ACCORDION-BEGIN [Step 1: ](Review the Data Model)] +[ACCORDION-BEGIN [Step 1: ](Review the data model)] Before you move on with the coding, just quickly review the conceptual data model of your solution: @@ -41,9 +41,9 @@ Now, having that model in mind, you can make its definition into the CAP project [DONE] [ACCORDION-END] -[ACCORDION-BEGIN [Step 2: ](Create CDS Data Model File)] +[ACCORDION-BEGIN [Step 2: ](Create CDS data model file)] -On the **left-hand pane** of **SAP Business Application Studio**, select the `db` folder, then click on the **three dots** to the right of the project name and select **New File**. +On the **left-hand pane** of **SAP Business Application Studio**, (1) select the `db` folder, then (2) click on the **three dots** to the right of the project name and (3) select **New File**. ![Figure 2 – Create New File](create-file.png) @@ -54,7 +54,7 @@ On the **dialog**, name the file `projman-model.cds` and click **OK**. [DONE] [ACCORDION-END] -[ACCORDION-BEGIN [Step 3: ](Perform the Model Coding)] +[ACCORDION-BEGIN [Step 3: ](Perform the model coding)] Copy and paste the **code snippet below** into the recently created file: @@ -133,7 +133,7 @@ You specify that the entities **Employee** (the "bridge" between SAP SuccessFact [DONE] [ACCORDION-END] -[ACCORDION-BEGIN [Step 4: ](Load Initial Test Data)] +[ACCORDION-BEGIN [Step 4: ](Load initial test data)] Now, populate your data model with some initial test data. This can be done by creating some **files in CSV format** into a **subfolder** of the `db` folder named "**data**" with the specific naming convention of `-.csv`. @@ -246,7 +246,7 @@ fcba3d35-013a-4ab6-9ab9-9fb938eff8a4;3ca47b3e-eff3-430e-9ae0-1937dd094212;3470cf [DONE] [ACCORDION-END] -[ACCORDION-BEGIN [Step 5: ](Execute the First Test)] +[ACCORDION-BEGIN [Step 5: ](Execute the first test)] Now it has come the time to test the project as it is so far. You will start by installing the required project dependencies. @@ -266,7 +266,7 @@ Type `cds watch` and press **Enter** to **run the project** and **watch for chan ![Figure 12 – Project first run](first-run.png) -Notice that CDS has **automatically set the database** to an **in-memory SQLite database** and filled it with the **CSV files**. It also says that it could not find any service definitions from the loaded models: that's because you really haven't defined any yet (you will do it in the next tutorial from this group). +Notice that CDS has **automatically set the database** to an **in-memory `SQLite` database** and filled it with the **CSV files**. It also says that it could not find any service definitions from the loaded models: that's because you really haven't defined any yet (you will do it in the next tutorial from this group). `CTRL+Click` on the `http://localhost:4004` link that is displayed in the terminal to open the project home page in a new browser tab. @@ -281,7 +281,7 @@ But don't worry! They will show up in this page as soon as you complete the next [DONE] [ACCORDION-END] -[ACCORDION-BEGIN [Step 6: ](Check Your Knowledge)] +[ACCORDION-BEGIN [Step 6: ](Check your knowledge)] [VALIDATE_1] [ACCORDION-END] diff --git a/tutorials/cap-extend-sfsf-deploy-cf/add-user.png b/tutorials/cap-extend-sfsf-deploy-cf/add-user.png new file mode 100644 index 0000000000..62ba93775d Binary files /dev/null and b/tutorials/cap-extend-sfsf-deploy-cf/add-user.png differ diff --git a/tutorials/cap-extend-sfsf-deploy-cf/build-mta.png b/tutorials/cap-extend-sfsf-deploy-cf/build-mta.png new file mode 100644 index 0000000000..af763e4cb0 Binary files /dev/null and b/tutorials/cap-extend-sfsf-deploy-cf/build-mta.png differ diff --git a/tutorials/cap-extend-sfsf-deploy-cf/cap-extend-sfsf-deploy-cf.md b/tutorials/cap-extend-sfsf-deploy-cf/cap-extend-sfsf-deploy-cf.md new file mode 100644 index 0000000000..a9755add03 --- /dev/null +++ b/tutorials/cap-extend-sfsf-deploy-cf/cap-extend-sfsf-deploy-cf.md @@ -0,0 +1,124 @@ +--- +title: Deploy the SAP SuccessFactors Extension to Cloud Foundry +description: In this phase of the development you will deploy the finished extension to BTP Cloud Foundry environment. +auto_validation: true +time: 8 +tags: [ tutorial>beginner, software-product>sap-btp--cloud-foundry-environment] +primary_tag: software-product-function>sap-cloud-application-programming-model +--- + +## Prerequisites + - Complete the tutorial: [**Prepare to Develop the SAP SuccessFactors Extension**](cap-extend-sfsf-intro) + - Complete the tutorial: [**Jump start the SAP SuccessFactors Extension CAP Project**](cap-extend-sfsf-jumpstart) + - Complete the tutorial: [**Import SAP SuccessFactors OData Services definitions**](cap-extend-sfsf-import-services) + - Complete the tutorial: [**Create the CDS Data Model for the SAP SuccessFactors Extension**](cap-extend-sfsf-data-model) + - Complete the tutorial: [**Create the CAP Service for the SAP SuccessFactors Extension**](cap-extend-sfsf-create-service) + - Complete the tutorial: [**Add Business Logic to the SAP SuccessFactors Extension**](cap-extend-sfsf-add-logic) + - Complete the tutorial: [**Prepare the SAP SuccessFactors Extension UI with CDS Annotations**](cap-extend-sfsf-ui-annotations) + - Complete the tutorial: [**Add Security to the SAP SuccessFactors Extension**](cap-extend-sfsf-add-security) + - Complete the tutorial: [**Deploy the SAP SuccessFactors Extension Database to SAP HANA Cloud**](cap-extend-sfsf-deploy-hc) + - Complete the tutorial: [**Develop the SAP Fiori Elements Application for the SAP SuccessFactors Extension**](cap-extend-sfsf-fiori-elements) + +## Details +### You will learn + - How to **build the MTA archive** + - How to **deploy the MTA archive** to Cloud Foundry + - How to **assign a role collection** to a BTP business user + - How to **launch and test** the deployed application + +--- + +[ACCORDION-BEGIN [Step 1: ](Build the MTA Archive)] + +Before you build the MTA Archive for deployment, do not forget to switch back the database from **in-memory `SQLite`** to **SAP HANA** (if not already). For that, just open the `package.json` file from the CAP service and set the `cds.requires.db.kind` parameter to `hana` like demonstrated below: + +![Figure 1 – Switch the database back to HANA](switch-hana.png) + +Now you can start the procedure to build the archive. You begin by building the CAP service. + +In the **Terminal** type `cds build –-production` and press **Enter** (don't forget to press `CTRL+C` to terminate `cds watch` if it was previously running). + +![Figure 2 – Build CAP service](cds-build.png) + +On the **left-hand pane** right-click the `mta.yaml` file and select **Build MTA Project**. + +![Figure 3 – Build MTA Project](build-mta.png) + +It's going to take some seconds until the build process completes. When it does, it will display a message "**Terminal will be reused by tasks**" in a dedicated terminal named "**Task Build MTA Project**". Look for the message "**Build succeeded**" among the lines in that console to make sure the build process was successful. + +![Figure 4 – Successful build](successful-build.png) + +If the process is successful you'll notice that a file named `sfsf-projman_1.0.0.mtar` will be generated into a new folder named `mta_archives` like demonstrated below: + +![Figure 5 – MTA Archive generated](mta-archive.png) + +[DONE] +[ACCORDION-END] + +[ACCORDION-BEGIN [Step 2: ](Deploy the MTA Archive)] + +> **IMPORTANT NOTE**: before you trigger the process to deploy the MTA archive to Cloud Foundry you **MUST** make sure you **comply with two requirements**: +> +> 1. You are properly **logged to Cloud Foundry** as instructed in **step 2** of the tutorial: [**Jump start the SAP SuccessFactors Extension CAP Project**](cap-extend-sfsf-jumpstart) +> 2. Your **SAP HANA Cloud database is up and running** (remember that everything shuts down overnight on **trial accounts**!) + +On the **left-hand pane** right-click the `sfsf-projman_1.0.0.mtar` file and select **Deploy MTA Archive**. + +![Figure 6 – Deploy MTA Archive](deploy-mta.png) + +It's going to take a while until the deployment process finishes. When it does, it will display a message "**Terminal will be reused by tasks**" in a dedicated terminal named "**Task Deploy MTA Archive**". Look for the message "**Process finished**" in the last lines and see that **there's no errors/failures listed** among the other lines in that console to make sure the deployment process was truly successful. + +![Figure 7 – Successful deployment](successful-deploy.png) + +[DONE] +[ACCORDION-END] + + +[ACCORDION-BEGIN [Step 3: ](Assign Admin Role to your Business User)] + +Before you jump into testing the application, you must remember that it's meant for **project administrators**, meaning, users with the Admin role assigned. So, assign the role to your user in SAP BTP. + +In the **SAP BTP cockpit**, on the **left-hand pane** expand the **Security** menu and click on **Role Collections**. On the right side, using the **search box**, search for the `sfsf_projman_Administrator` role and click on it. + +![Figure 8 – Find Role Collection](find-role.png) + +Click on the **Edit** button at the top-right corner, then, in the **Users** list, select the **Default identity provider** from the dropdown list and provide your **SAP BTP user ID** and your **e-mail** (usually in trial accounts they are both your e-mail). Click on the "**+**"" button on top of the list and then click on the **Save** button at the top-right corner. + +![Figure 9 – Add user to Role Collection](add-user.png) + +Now you are OK to test the application. + +[DONE] +[ACCORDION-END] + +[ACCORDION-BEGIN [Step 4: ](Test the Deployed Application)] + +On the **left-hand pane**, click on **HTML5 Applications** and locate your **Business Solution** (`sfsf-projman-`). Then, click on the link of the **Application Name**. + +![Figure 10 – Start application](launch-app.png) + +An **SAP Fiori Launchpad** will open-up in a new browser tab with the **application tile** in it: + +![Figure 11 – SAP Fiori Launchpad](launchpad.png) + +Click on the tile and the **List Report page** for the **Project** entity will be loaded: + +![Figure 12 – List Report page for Project entity](list-report.png) + +Now, click on the **Go** button and the **initial test data** (loaded to the HANA database from the CSV files) will be displayed: + +![Figure 13 – Initial test data displayed](test-data.png) + +And that's it! You have successfully deployed a **fully working SAP Fiori Elements application** to **SAP BTP Cloud Foundry**! + +You can, now, test it at your will, in the same way you did using the **Fiori Preview** in **step 6** of the tutorial: [**Prepare the SAP SuccessFactors Extension UI with CDS Annotations**](cap-extend-sfsf-ui-annotations). + +Finally, the last step in the journey of this tutorial group is to add your application to a **user-driven launchpad** using **SAP BTP's Launchpad Service**. + +[DONE] +[ACCORDION-END] + +[ACCORDION-BEGIN [Step 5: ](Check Your Knowledge)] + +[VALIDATE_1] +[ACCORDION-END] diff --git a/tutorials/cap-extend-sfsf-deploy-cf/cds-build.png b/tutorials/cap-extend-sfsf-deploy-cf/cds-build.png new file mode 100644 index 0000000000..0e2ffcae5e Binary files /dev/null and b/tutorials/cap-extend-sfsf-deploy-cf/cds-build.png differ diff --git a/tutorials/cap-extend-sfsf-deploy-cf/deploy-mta.png b/tutorials/cap-extend-sfsf-deploy-cf/deploy-mta.png new file mode 100644 index 0000000000..1496232bb0 Binary files /dev/null and b/tutorials/cap-extend-sfsf-deploy-cf/deploy-mta.png differ diff --git a/tutorials/cap-extend-sfsf-deploy-cf/find-role.png b/tutorials/cap-extend-sfsf-deploy-cf/find-role.png new file mode 100644 index 0000000000..238308346f Binary files /dev/null and b/tutorials/cap-extend-sfsf-deploy-cf/find-role.png differ diff --git a/tutorials/cap-extend-sfsf-deploy-cf/launch-app.png b/tutorials/cap-extend-sfsf-deploy-cf/launch-app.png new file mode 100644 index 0000000000..6e2fa751a1 Binary files /dev/null and b/tutorials/cap-extend-sfsf-deploy-cf/launch-app.png differ diff --git a/tutorials/cap-extend-sfsf-deploy-cf/launchpad.png b/tutorials/cap-extend-sfsf-deploy-cf/launchpad.png new file mode 100644 index 0000000000..147fae3ebf Binary files /dev/null and b/tutorials/cap-extend-sfsf-deploy-cf/launchpad.png differ diff --git a/tutorials/cap-extend-sfsf-deploy-cf/list-report.png b/tutorials/cap-extend-sfsf-deploy-cf/list-report.png new file mode 100644 index 0000000000..6a2e48dfd3 Binary files /dev/null and b/tutorials/cap-extend-sfsf-deploy-cf/list-report.png differ diff --git a/tutorials/cap-extend-sfsf-deploy-cf/mta-archive.png b/tutorials/cap-extend-sfsf-deploy-cf/mta-archive.png new file mode 100644 index 0000000000..abb135604c Binary files /dev/null and b/tutorials/cap-extend-sfsf-deploy-cf/mta-archive.png differ diff --git a/tutorials/cap-extend-sfsf-deploy-cf/successful-build.png b/tutorials/cap-extend-sfsf-deploy-cf/successful-build.png new file mode 100644 index 0000000000..844548a2ab Binary files /dev/null and b/tutorials/cap-extend-sfsf-deploy-cf/successful-build.png differ diff --git a/tutorials/cap-extend-sfsf-deploy-cf/successful-deploy.png b/tutorials/cap-extend-sfsf-deploy-cf/successful-deploy.png new file mode 100644 index 0000000000..73dee840e5 Binary files /dev/null and b/tutorials/cap-extend-sfsf-deploy-cf/successful-deploy.png differ diff --git a/tutorials/cap-extend-sfsf-deploy-cf/switch-hana.png b/tutorials/cap-extend-sfsf-deploy-cf/switch-hana.png new file mode 100644 index 0000000000..4aa1b72cac Binary files /dev/null and b/tutorials/cap-extend-sfsf-deploy-cf/switch-hana.png differ diff --git a/tutorials/cap-extend-sfsf-deploy-cf/test-data.png b/tutorials/cap-extend-sfsf-deploy-cf/test-data.png new file mode 100644 index 0000000000..c4d489395e Binary files /dev/null and b/tutorials/cap-extend-sfsf-deploy-cf/test-data.png differ diff --git a/tutorials/cap-extend-sfsf-deploy-hc/cap-extend-sfsf-deploy-hc.md b/tutorials/cap-extend-sfsf-deploy-hc/cap-extend-sfsf-deploy-hc.md new file mode 100644 index 0000000000..af2595b4e3 --- /dev/null +++ b/tutorials/cap-extend-sfsf-deploy-hc/cap-extend-sfsf-deploy-hc.md @@ -0,0 +1,106 @@ +--- +title: Deploy the SAP SuccessFactors Extension Database to SAP HANA Cloud +description: In this phase of the development you will deploy the extension database to SAP HANA Cloud. +auto_validation: true +time: 7 +tags: [ tutorial>beginner, software-product>sap-btp--cloud-foundry-environment] +primary_tag: software-product-function>sap-cloud-application-programming-model +--- + +## Prerequisites + - Complete the tutorial: [**Prepare to Develop the SAP SuccessFactors Extension**](cap-extend-sfsf-intro) + - Complete the tutorial: [**Jump start the SAP SuccessFactors Extension CAP Project**](cap-extend-sfsf-jumpstart) + - Complete the tutorial: [**Import SAP SuccessFactors OData Services definitions**](cap-extend-sfsf-import-services) + - Complete the tutorial: [**Create the CDS Data Model for the SAP SuccessFactors Extension**](cap-extend-sfsf-data-model) + - Complete the tutorial: [**Create the CAP Service for the SAP SuccessFactors Extension**](cap-extend-sfsf-create-service) + - Complete the tutorial: [**Add Business Logic to the SAP SuccessFactors Extension**](cap-extend-sfsf-add-logic) + - Complete the tutorial: [**Prepare the SAP SuccessFactors Extension UI with CDS Annotations**](cap-extend-sfsf-ui-annotations) + - Complete the tutorial: [**Add Security to the SAP SuccessFactors Extension**](cap-extend-sfsf-add-security) + +## Details +### You will learn + - How to **setup your project for SAP HANA** + - How to **deploy the database to SAP HANA Cloud** + - How to work with a "**hybrid approach**" (database in the cloud and application running locally) + +--- + +[ACCORDION-BEGIN [Step 1: ](Setup Project for SAP HANA)] + +So far, all data persistence of your application has been utilizing a **default `SQLite` in-memory database**, which means that, each time you restart the application, all data that has been added/updated is completely lost. + +It might be OK for local development, but when it goes to a cloud landscape (either development, QA or production) the **data must be persisted** in a **standard relational database** such as **SAP HANA Cloud** (which is going to be the selected database for your application). + +Therefore, it's now time to prepare your project to deploy the data model to **SAP HANA Cloud** when it goes to BTP. For that, you must **add the appropriate dependencies** to your project using the `cds add` command. + +In the **Terminal** type `cds add hana` and press **Enter**. + +![Figure 1 – Add SAP HANA dependencies to project](dependencies.png) + +On the **left-hand pane** click on the `package.json` file to open it. Examine the contents and notice the modifications in the `dependencies` and `cds.requires` sections and the new `cds.hana` section. Change the `db.kind` from the `cds.requires` section to `hana`. + +![Figure 2 – Changes made to package.json](package-json.png) + +In the **Terminal** type `npm install` and press **Enter** to install the newly added dependency locally. + +![Figure 3 – Install new dependency](install-dep.png) + +[DONE] +[ACCORDION-END] + +[ACCORDION-BEGIN [Step 2: ](Deploy the Database to SAP HANA Cloud)] + +> **IMPORTANT NOTE**: before executing the next steps first make sure: +> +> 1. You are properly **logged to Cloud Foundry** as instructed in **step 2** of the tutorial: [**Jump start the SAP SuccessFactors Extension CAP Project**](cap-extend-sfsf-jumpstart) +> 2. Your SAP HANA Cloud database instance (created following [**this tutorial**](hana-cloud-deploying)) is up and running – remember: on **trial accounts** everything (including **SAP HANA database**) **shuts down overnight**! + +In the **Terminal** type `cds deploy –-to hana` and press **Enter**. + +![Figure 4 – Deploying to the HANA Cloud database](deploy.png) + +It's going to take a while before the command completes as it's performing these operations: + +1. **Build** the database artifacts from the CAP project in the **gen folder** that's created under the **project root folder**, for further deployment to SAP HANA Cloud. +2. Create a **service instance** named `sfsf-projman-db` of type **SAP HANA Schemas & HDI Containers** with the `hdi-shared` plan, that handles the **HDI container** +3. Create the **SAP HANA database artifacts from the application** inside the HDI container +4. **Bind the service instance** to the local project by modifying the `default-env.json` file, adding the **database credentials** (actually the **service key** from the **service binding**). + +After the command completes, you will have achieved what is called a "**hybrid approach**" where your project runs locally, but the **database** (and the corresponding persistence) **runs in the cloud**. + +Take a quick look at the `default-env.json` to verify what happened after command completion: on the **left-hand pane** click on the `default-env.json` file to open it: + +![Figure 5 – Changes in default-env.json](default-env.png) + +Notice that a `hana` section has been added to the `VCAP_SERVICES` environment variable, with the **service instance name** of the **HDI container** and the **credentials** (service key) to connect to the SAP HANA Cloud database. + +If you click on the **Cloud Foundry icon** (small light bulb) on the left-hand pane and **expand the services node**, you'll also see the newly created service instance: + +![Figure 6 – HDI container service instance](hdi-service.png) + +[DONE] +[ACCORDION-END] + +[ACCORDION-BEGIN [Step 3: ](Run Project locally with the Database in the Cloud)] + +Now, checkout what happens when you execute the application. + +In the **Terminal** type `cds watch` and press **Enter**. + +![Figure 7 – Project connected to SAP HANA Cloud database](connect-to-hc.png) + +Notice that, now, CDS is **connecting to SAP HANA Cloud** as the persistence mechanism of the application. + +Again, in this scenario the **project** is running **locally**, but the **database** is running in the **cloud** (hybrid approach). + +If you want to **switch to the `SQLite` in-memory database** again for development, just go back to `package.json` and change the `db.kind` parameter in the `cds.requires` section back to `sql`, and notice that `cds watch` immediately recognizes the change and **switches the connection** to `sqlite` with the `:memory:` credentials: + +![Figure 8 – Switching back to SQLite in-memory](sqlite.png) + +[DONE] +[ACCORDION-END] + +[ACCORDION-BEGIN [Step 4: ](Check Your Knowledge)] + +[VALIDATE_1] +[ACCORDION-END] diff --git a/tutorials/cap-extend-sfsf-deploy-hc/connect-to-hc.png b/tutorials/cap-extend-sfsf-deploy-hc/connect-to-hc.png new file mode 100644 index 0000000000..bff6a804b7 Binary files /dev/null and b/tutorials/cap-extend-sfsf-deploy-hc/connect-to-hc.png differ diff --git a/tutorials/cap-extend-sfsf-deploy-hc/default-env.png b/tutorials/cap-extend-sfsf-deploy-hc/default-env.png new file mode 100644 index 0000000000..c471b8217b Binary files /dev/null and b/tutorials/cap-extend-sfsf-deploy-hc/default-env.png differ diff --git a/tutorials/cap-extend-sfsf-deploy-hc/dependencies.png b/tutorials/cap-extend-sfsf-deploy-hc/dependencies.png new file mode 100644 index 0000000000..d344d2bcf5 Binary files /dev/null and b/tutorials/cap-extend-sfsf-deploy-hc/dependencies.png differ diff --git a/tutorials/cap-extend-sfsf-deploy-hc/deploy.png b/tutorials/cap-extend-sfsf-deploy-hc/deploy.png new file mode 100644 index 0000000000..43621a5c4c Binary files /dev/null and b/tutorials/cap-extend-sfsf-deploy-hc/deploy.png differ diff --git a/tutorials/cap-extend-sfsf-deploy-hc/hdi-service.png b/tutorials/cap-extend-sfsf-deploy-hc/hdi-service.png new file mode 100644 index 0000000000..c67d8b62e7 Binary files /dev/null and b/tutorials/cap-extend-sfsf-deploy-hc/hdi-service.png differ diff --git a/tutorials/cap-extend-sfsf-deploy-hc/install-dep.png b/tutorials/cap-extend-sfsf-deploy-hc/install-dep.png new file mode 100644 index 0000000000..55e6b57276 Binary files /dev/null and b/tutorials/cap-extend-sfsf-deploy-hc/install-dep.png differ diff --git a/tutorials/cap-extend-sfsf-deploy-hc/package-json.png b/tutorials/cap-extend-sfsf-deploy-hc/package-json.png new file mode 100644 index 0000000000..f662c7c37d Binary files /dev/null and b/tutorials/cap-extend-sfsf-deploy-hc/package-json.png differ diff --git a/tutorials/cap-extend-sfsf-deploy-hc/sqlite.png b/tutorials/cap-extend-sfsf-deploy-hc/sqlite.png new file mode 100644 index 0000000000..90b4778d34 Binary files /dev/null and b/tutorials/cap-extend-sfsf-deploy-hc/sqlite.png differ diff --git a/tutorials/cap-extend-sfsf-fiori-elements/add-appname.png b/tutorials/cap-extend-sfsf-fiori-elements/add-appname.png new file mode 100644 index 0000000000..6d484758d5 Binary files /dev/null and b/tutorials/cap-extend-sfsf-fiori-elements/add-appname.png differ diff --git a/tutorials/cap-extend-sfsf-fiori-elements/add-host.png b/tutorials/cap-extend-sfsf-fiori-elements/add-host.png new file mode 100644 index 0000000000..329d6a5b3f Binary files /dev/null and b/tutorials/cap-extend-sfsf-fiori-elements/add-host.png differ diff --git a/tutorials/cap-extend-sfsf-fiori-elements/approuter-01.png b/tutorials/cap-extend-sfsf-fiori-elements/approuter-01.png new file mode 100644 index 0000000000..b0a1627b32 Binary files /dev/null and b/tutorials/cap-extend-sfsf-fiori-elements/approuter-01.png differ diff --git a/tutorials/cap-extend-sfsf-fiori-elements/approuter-02.png b/tutorials/cap-extend-sfsf-fiori-elements/approuter-02.png new file mode 100644 index 0000000000..e1b445bdd0 Binary files /dev/null and b/tutorials/cap-extend-sfsf-fiori-elements/approuter-02.png differ diff --git a/tutorials/cap-extend-sfsf-fiori-elements/approuter-03.png b/tutorials/cap-extend-sfsf-fiori-elements/approuter-03.png new file mode 100644 index 0000000000..7477533e99 Binary files /dev/null and b/tutorials/cap-extend-sfsf-fiori-elements/approuter-03.png differ diff --git a/tutorials/cap-extend-sfsf-fiori-elements/bind-services.png b/tutorials/cap-extend-sfsf-fiori-elements/bind-services.png new file mode 100644 index 0000000000..0fbadaeb3d Binary files /dev/null and b/tutorials/cap-extend-sfsf-fiori-elements/bind-services.png differ diff --git a/tutorials/cap-extend-sfsf-fiori-elements/blocks-transfer.png b/tutorials/cap-extend-sfsf-fiori-elements/blocks-transfer.png new file mode 100644 index 0000000000..6a58194df3 Binary files /dev/null and b/tutorials/cap-extend-sfsf-fiori-elements/blocks-transfer.png differ diff --git a/tutorials/cap-extend-sfsf-fiori-elements/cap-extend-sfsf-fiori-elements.md b/tutorials/cap-extend-sfsf-fiori-elements/cap-extend-sfsf-fiori-elements.md new file mode 100644 index 0000000000..016809b2fa --- /dev/null +++ b/tutorials/cap-extend-sfsf-fiori-elements/cap-extend-sfsf-fiori-elements.md @@ -0,0 +1,266 @@ +--- +title: Develop the SAP Fiori Elements Application for the SAP SuccessFactors Extension +description: In this phase of the development you will create the extension UI through a Fiori Elements application. +auto_validation: true +time: 15 +tags: [ tutorial>beginner, software-product>sap-btp--cloud-foundry-environment] +primary_tag: software-product-function>sap-cloud-application-programming-model +--- + +## Prerequisites + - Complete the tutorial: [**Prepare to Develop the SAP SuccessFactors Extension**](cap-extend-sfsf-intro) + - Complete the tutorial: [**Jump start the SAP SuccessFactors Extension CAP Project**](cap-extend-sfsf-jumpstart) + - Complete the tutorial: [**Import SAP SuccessFactors OData Services definitions**](cap-extend-sfsf-import-services) + - Complete the tutorial: [**Create the CDS Data Model for the SAP SuccessFactors Extension**](cap-extend-sfsf-data-model) + - Complete the tutorial: [**Create the CAP Service for the SAP SuccessFactors Extension**](cap-extend-sfsf-create-service) + - Complete the tutorial: [**Add Business Logic to the SAP SuccessFactors Extension**](cap-extend-sfsf-add-logic) + - Complete the tutorial: [**Prepare the SAP SuccessFactors Extension UI with CDS Annotations**](cap-extend-sfsf-ui-annotations) + - Complete the tutorial: [**Add Security to the SAP SuccessFactors Extension**](cap-extend-sfsf-add-security) + - Complete the tutorial: [**Deploy the SAP SuccessFactors Extension Database to SAP HANA Cloud**](cap-extend-sfsf-deploy-hc) + +## Details +### You will learn + - How to **add MTA configuration** to the project structure + - How to **add `AppRouter` configuration** to the MTA file + - How to **create the Fiori Elements app** and set it up into the MTA file + - How to **launch the Fiori Elements app** + - How to **add additional configuration** to the MTA file, which is **relevant for Cloud Foundry deployment** + +--- + +[ACCORDION-BEGIN [Step 1: ](Add MTA Configuration to the Project)] + +To deploy your application to Cloud Foundry on SAP BTP, you are going to use the **MTA** (Multi-Target Application) approach, which facilitates the deployment as it **pushes everything at once** to the platform: UI application, backend service, database, service instances creation and binding, etc. + +As you have already done almost everything that is required to setup your project for deployment following the previous tutorials, you can now **add the MTA configuration** to it, which will be described using **YAML** standard in a file named `mta.yaml`. + +Fortunately, once again, **CAP can add that file automatically** for you, populating it with all that has been configured in the project so far. But, before you do it, just open the `package.json` file and change the **application description** as demonstrated below, so it can be reflected in the `mta.yaml` file: + +![Figure 1 – Adjust project description](project-desc.png) + +In the **Terminal** type `cds add mta` and press **Enter** (don't forget to press `CTRL+C` to terminate `cds watch` if it was previously running). + +![Figure 2 – Add MTA Configuration](mta-config.png) + +On the **left-hand pane**, click on the `mta.yaml` file to open it. + +![Figure 3 – mta.yaml contents](mta-file.png) + +If you explore the file, you'll notice that it contains the descriptors for the **server module**, which is the **actual CAP service**, with a dependency pointing to the **service instance of the HDI container** (described in the **resources section**) and a **sidecar module** (suffixed `-db-deployer`), which is an **auxiliary app that runs only during deployment** and takes care of the creation/update of the **HANA database artifacts** into the HDI container (notice that it also depends on the HDI service instance). + +All of that has been extracted from the **CAP configuration** (`cds.requires` section) in the `package.json` file. + +The next step is to create an `approuter` module with a **unique route name** (in the context of the subaccount region) to take care of the **authentication flow** when the application is accessed from the UI in SAP BTP as well as other stuff, such as **mapping** the **backend service** route to the same domain as the HTML5 application as a destination to avoid problems like CORS (Cross-Origin Resource Sharing) issues. + +[DONE] +[ACCORDION-END] + +[ACCORDION-BEGIN [Step 2: ](Add AppRouter Configuration to the MTA file)] + +Before you move forward with the creation of the `approuter` module, you need to make a **copy** of your `xs-security.json` file as the **module generator will overwrite it** with some specific `approuter` configuration. + +To do that, simply **select the file** in the left-hand pane then press `CTRL+C` and `CTRL+V` in sequence; With that, you should obtain the following result: + +![Figure 4 – Copy of xs-security.json](copy-xs-security.png) + +Now, you can move on to the `approunter` configuration. + +On the **left-hand pane**, right-click on the `mta.yaml` file and select **Create MTA Module from Template**. + +![Figure 5 – Create MTA Module from Template](new-mta-module.png) + +In the **Wizard**, select **`Approuter` Configuration** and click on **Start**. + +![Figure 6 – Start Approuter Configuration](approuter-01.png) + +In the next **Dialog**, select **Managed `Approuter`** as your HTML5 application runtime. As the unique name for the solution, type `sfsf-projman`, appending a "**-**" followed by the **subdomain** of your SAP BTP subaccount (like demonstrated in the screenshot below – this will make sure the name is really unique in the context of the subaccount region) and select **Yes** as the answer for the last question. Then, click on **Next**. + +![Figure 7 – Approuter Configuration](approuter-02.png) + +The essential difference between the **managed** and **standalone** `approuter` is that the **managed** is centrally **governed by the launchpad service** through an **HTLM5 application repository** that ties your solution to a URL from the launchpad service, whilst the **standalone** just creates another application in the **Cloud Foundry space** in parallel to the CAP service application. So, basically, in the first case you access your solution **from a list of applications in the HTML5 repository** – and you're also able to **add it to a user-driven launchpad** – and in the second you do it **directly from the list of applications** in the Cloud Foundry space. + +You can get the **subdomain** of your SAP BTP subaccount in the **Overview** page from the cockpit like demonstrated below: + +![Figure 8 – Subdomain in Cockpit Overview Page](subdomain.png) + +In the **last step**, select **overwrite** (first option) and click on **Finish**. + +![Figure 9 – Overwrite xs-security.json](approuter-03.png) + +Now, **merge** the generated `xs-security.json` into the copy you previously created. + +On the **left-hand pane**, click on `xs-security.json` and then on `xs-security_copy.json` to open both files. You must **transfer (copy & paste) the blocks indicated** in the screenshot below from `xs-security.json` to the corresponding sections in `xs-security_copy.json`. + +![Figure 10 – Blocks to transfer](blocks-transfer.png) + +Then, after the transfer, the `xs-security_copy.json` file should look like this: + +![Figure 11 – Final state of xs-security_copy.json](xs-security-copy.png) + +The `uaa.user` scope associated to the `Token_Exchange` role template is required to perform the **OAuth 2.0 authentication flow** executed by the `approuter`. Basically, before loading the **HTML5 application** (which you are going to build in a minute) the router **authenticates the user** logged in to SAP BTP via OAuth 2.0 and gets a **JWT** (JSON Web Token) which is, then, **forwarded** to the HTML5 application and, from there, to the **CAP service**, making sure that only **authenticated** and **authorized** (Admin) users have access to the application. + +You can now delete the generated `xs-security.json` and rename the `xs-security_copy.json` to its original name. + +On the **left-hand pane**, right-click on `xs-security.json` and select **Delete**. In the **Dialog**, click **OK**. + +![Figure 12 – Delete xs-security.json](delete-xs-security.png) + +On the **left-hand pane**, click on `xs-security_copy.json` and press **F2** to rename it. In the **Dialog**, remove the `_copy` and click **OK**. + +![Figure 13 – Rename xs-security_copy.json](rename-xs-security-copy.png) + +Now, open the `mta.yaml` file and notice how much configuration has been automatically added to it! Among the new content there's the definition of the **destination** and **UAA** services that are required by the router. + +You shall remember that the **CAP service** also **depends** on those service instances, thus **they must be bound** to it as well. For that, you just append their definition to the "**requires**" section of the `sfsf-projman-srv` definition following the order: first destination and then XSUAA, like demonstrated below: + +![Figure 14 – Bind destination and xsuaa instances to CAP service](bind-services.png) + +And that's it! This completes the `approuter` configuration. + +[DONE] +[ACCORDION-END] + +[ACCORDION-BEGIN [Step 3: ](Create the Fiori Elements App as an MTA Module)] + +Now it's finally come the time to create your **SAP Fiori Elements app** (the front-end of your project management solution) and, to facilitate and speed-up the project configuration, you will **add it** to your project directly as an **MTA module**. + +On the **left-hand pane**, right-click on the `mta.yaml` file and select **Create MTA Module from Template**. + +![Figure 15 – Create MTA Module from Template](new-mta-module.png) + +In the **Wizard**, select **SAP Fiori Application** and click on **Start**. + +![Figure 16 – Start SAP Fiori Application creation](fiori-01.png) + +In the next **Dialog**, make sure that the application type is **SAP Fiori Elements** and select **List Report Object Page**, then click on **Next**. + +![Figure 17 – Floorplan selection](fiori-02.png) + +In the **Data Source and Service Selection** dialog, select **Use a Local CAP Project** as the **Data Source**, then click on the **browse button** in the **CAP project path** field (small folder) to select the `sfsf-projman` folder under `projects` and, finally, select `sfsf.projman.service.ProjectManager (Node.js)` as the **OData service**. Then, click on **Next**. + +![Figure 18 – Data Source and Service Selection](fiori-03.png) + +In the **Entity Selection** dialog, select **Project** as the **Main entity**, **None** as the **Navigation entity** and leave **Yes** as the answer to the last question, then click on **Next**. + +![Figure 19 – Data Source and Service Selection](fiori-04.png) + +Fill-in the information in the **Project Attributes** dialog like **demonstrated below**, then click on **Next**. + +![Figure 20 – Data Source and Service Selection](fiori-05.png) + +In the **Deployment Configuration** dialog, select **Cloud Foundry** as the **target** and **None** as the **Destination name**, then click on **Next**. + +![Figure 21 – Deployment Configuration](fiori-06.png) + +Fill-in the information in the **SAP Fiori Launchpad Configuration** dialog like **demonstrated below**, then click on **Finish**. + +![Figure 22 – Deployment Configuration](fiori-07.png) + +Now, if you open again the `mta.yaml` file you'll notice that an **additional configuration** (two modules) related to the SAP Fiori app has been added to it like demonstrated below: + +![Figure 23 – Fiori app configuration in mta.yaml](fiori-in-mta.png) + +Also, the **actual SAP Fiori application** has been created under the `app` folder in a folder named `sfsf-projman` like you can see in the following screenshot: + +![Figure 24 – SAP Fiori application folders and files](fiori-app-folders.png) + +[DONE] +[ACCORDION-END] + +[ACCORDION-BEGIN [Step 4: ](Launch the Fiori Elements App)] + +You can, now, test the newly created SAP Fiori app. In the **Terminal**, run `cds watch` and `CTRL+Click` the `http://localhost:4004` link to open the CAP application home page. Notice that under **Web Applications** the link to the **SAP Fiori app home page** is listed: + +![Figure 25 – Link to Fiori app home page](service-home.png) + +Click on that link and a **SAP Fiori Launchpad** will open-up with the **application tile** in it: + +![Figure 26 – SAP Fiori Launchpad](launchpad.png) + +> **NOTE**: newer versions of the **Fiori Generator** might skip this tile and jump direct into the **List Report** from the **next screenshot**. + +Click on the tile – if the sign-in dialog pops-up just enter **john** in the username with **any password** as it's the **mock user** with the **Admin role** you defined in the CAP service for authentication during development – and the **List Report page** for the **Project** entity will be loaded: + +![Figure 27 – List Report page for Project entity](list-report.png) + +Now, click on the **Go** button and the **initial test data** (loaded from the CSV files) will be displayed: + +![Figure 28 – Initial test data displayed](test-data.png) + +And that's it! There you have it: a **fully working SAP Fiori Elements application** easily created with the help of **SAP Business Application Studio** standard **MTA modules generator**, almost ready to deploy! + +You can, now, test it at your will, in the same way you did using the **Fiori Preview** in **step 6** of the tutorial: [**Prepare the SAP SuccessFactors Extension UI with CDS Annotations**](cap-extend-sfsf-ui-annotations). + +[DONE] +[ACCORDION-END] + +[ACCORDION-BEGIN [Step 5: ](Add Additional MTA Configuration)] + +A few more configurations are required to get the solution fully ready for deployment. + +Basically, you will define an `appname` with a `very unique name` (the **same** you used in the `approuter` configuration) and a **destination** to the **CAP service** (which is the backend of your application) to be **mapped** by the `approuter` in a single entry point – remember, the `approuter` is responsible for creating a unique entry point for the solution (UI and backend) putting **everything under the same domain** and taking care of the **authentication flow**. + +After doing that, you will adjust the `xs-app.json` file (which holds the configuration of the routes to be mapped by the `approuter`) to **point to the destination of the backend service**. + +On the **left-hand pane**, click on the `mta.yaml` file to open it and, under "**parameters**" add the **following line**: + +> **NOTE**: use the **exact same name** you provided in the **Add `AppRouter` Configuration to the MTA file** step (`sfsf-projman-`) + +![Figure 29 – Add appname in the parameters section of mta.yaml](add-appname.png) + +Now, under "**modules**" expand the `sfsf-projman-srv` module and add the following line to the "**parameters**" section: + +![Figure 30 – Add host parameter to sfsf-projman-srv module](add-host.png) + +Create a "**requires**" section to `resources > sfsf-projman-destination-service` using the following code snippet (right under the `type: org.cloudfoundry.managed-service`): + +```YAML + requires: + - name: srv-api +``` + +Append the following code snippet to `resources > sfsf-projman-destination-service > parameters > config > inti_data > instance > destinations` (right under the `URL: https://ui5.sap.com`): + +```YAML + - Name: sfsf-projman-app-api + Description: Project Manager CAP service + Authentication: NoAuthentication + ProxyType: Internet + Type: HTTP + URL: ~{srv-api/srv-url} + HTML5.DynamicDestination: true + HTML5.ForwardAuthToken: true +``` + +Your `mta.yaml` should now look like this: + +![Figure 31 – New destination defined into the destination service](new-dest.png) + +Basically, what's been done here is that you instructed the **destination service** to create a **dynamic destination** to the **CAP service** which will be used by the **HTML5 runtime** to invoke it **forwarding the JWT** grabbed by the `approuter` during the **authentication flow**, so it can check whether the user has been properly **authenticated** and has the **authorization** (Admin role) to access the service. Notice that the service is referencing the CAP service via the name `srv-api` that has been given to it in its definition at the beginning of the `mta.yaml` file. + +On the **left-hand pane**, click on the `xs-app.json` file under `app/sfsf-projman` folder to open it and add the **following code snippet** as the **very first route** in the "**routes**" collection: + +```JSON + { + "source": "^/projman/", + "destination": "sfsf-projman-app-api", + "csrfProtection": false, + "authenticationType": "none" + }, +``` + +Your `xs-app.json` should now look like this: + +![Figure 32 – Final version of xs-app.json](xs-app.png) + +As you can see, you have mapped the `/projman` service route to the `single entry point` managed by the router using the **destination** defined in the `mta.yaml` file. + +And that concludes the **full project configuration** required to **deploy** the application to **Cloud Foundry** via the **MTA approach**. + +[DONE] +[ACCORDION-END] + +[ACCORDION-BEGIN [Step 6: ](Check Your Knowledge)] + +[VALIDATE_1] +[ACCORDION-END] diff --git a/tutorials/cap-extend-sfsf-fiori-elements/copy-xs-security.png b/tutorials/cap-extend-sfsf-fiori-elements/copy-xs-security.png new file mode 100644 index 0000000000..90d3609156 Binary files /dev/null and b/tutorials/cap-extend-sfsf-fiori-elements/copy-xs-security.png differ diff --git a/tutorials/cap-extend-sfsf-fiori-elements/delete-xs-security.png b/tutorials/cap-extend-sfsf-fiori-elements/delete-xs-security.png new file mode 100644 index 0000000000..938ec08d97 Binary files /dev/null and b/tutorials/cap-extend-sfsf-fiori-elements/delete-xs-security.png differ diff --git a/tutorials/cap-extend-sfsf-fiori-elements/fiori-01.png b/tutorials/cap-extend-sfsf-fiori-elements/fiori-01.png new file mode 100644 index 0000000000..f391e79405 Binary files /dev/null and b/tutorials/cap-extend-sfsf-fiori-elements/fiori-01.png differ diff --git a/tutorials/cap-extend-sfsf-fiori-elements/fiori-02.png b/tutorials/cap-extend-sfsf-fiori-elements/fiori-02.png new file mode 100644 index 0000000000..7f39c23705 Binary files /dev/null and b/tutorials/cap-extend-sfsf-fiori-elements/fiori-02.png differ diff --git a/tutorials/cap-extend-sfsf-fiori-elements/fiori-03.png b/tutorials/cap-extend-sfsf-fiori-elements/fiori-03.png new file mode 100644 index 0000000000..94ed84d72c Binary files /dev/null and b/tutorials/cap-extend-sfsf-fiori-elements/fiori-03.png differ diff --git a/tutorials/cap-extend-sfsf-fiori-elements/fiori-04.png b/tutorials/cap-extend-sfsf-fiori-elements/fiori-04.png new file mode 100644 index 0000000000..84ca59f97b Binary files /dev/null and b/tutorials/cap-extend-sfsf-fiori-elements/fiori-04.png differ diff --git a/tutorials/cap-extend-sfsf-fiori-elements/fiori-05.png b/tutorials/cap-extend-sfsf-fiori-elements/fiori-05.png new file mode 100644 index 0000000000..efa6e121b8 Binary files /dev/null and b/tutorials/cap-extend-sfsf-fiori-elements/fiori-05.png differ diff --git a/tutorials/cap-extend-sfsf-fiori-elements/fiori-06.png b/tutorials/cap-extend-sfsf-fiori-elements/fiori-06.png new file mode 100644 index 0000000000..bd98a66e2b Binary files /dev/null and b/tutorials/cap-extend-sfsf-fiori-elements/fiori-06.png differ diff --git a/tutorials/cap-extend-sfsf-fiori-elements/fiori-07.png b/tutorials/cap-extend-sfsf-fiori-elements/fiori-07.png new file mode 100644 index 0000000000..e699af8d9c Binary files /dev/null and b/tutorials/cap-extend-sfsf-fiori-elements/fiori-07.png differ diff --git a/tutorials/cap-extend-sfsf-fiori-elements/fiori-app-folders.png b/tutorials/cap-extend-sfsf-fiori-elements/fiori-app-folders.png new file mode 100644 index 0000000000..bd6150c62c Binary files /dev/null and b/tutorials/cap-extend-sfsf-fiori-elements/fiori-app-folders.png differ diff --git a/tutorials/cap-extend-sfsf-fiori-elements/fiori-in-mta.png b/tutorials/cap-extend-sfsf-fiori-elements/fiori-in-mta.png new file mode 100644 index 0000000000..1d8aa4c3c0 Binary files /dev/null and b/tutorials/cap-extend-sfsf-fiori-elements/fiori-in-mta.png differ diff --git a/tutorials/cap-extend-sfsf-fiori-elements/launchpad.png b/tutorials/cap-extend-sfsf-fiori-elements/launchpad.png new file mode 100644 index 0000000000..147fae3ebf Binary files /dev/null and b/tutorials/cap-extend-sfsf-fiori-elements/launchpad.png differ diff --git a/tutorials/cap-extend-sfsf-fiori-elements/list-report.png b/tutorials/cap-extend-sfsf-fiori-elements/list-report.png new file mode 100644 index 0000000000..6a2e48dfd3 Binary files /dev/null and b/tutorials/cap-extend-sfsf-fiori-elements/list-report.png differ diff --git a/tutorials/cap-extend-sfsf-fiori-elements/mta-config.png b/tutorials/cap-extend-sfsf-fiori-elements/mta-config.png new file mode 100644 index 0000000000..020d2bd05e Binary files /dev/null and b/tutorials/cap-extend-sfsf-fiori-elements/mta-config.png differ diff --git a/tutorials/cap-extend-sfsf-fiori-elements/mta-file.png b/tutorials/cap-extend-sfsf-fiori-elements/mta-file.png new file mode 100644 index 0000000000..71e1a7a1c3 Binary files /dev/null and b/tutorials/cap-extend-sfsf-fiori-elements/mta-file.png differ diff --git a/tutorials/cap-extend-sfsf-fiori-elements/new-dest.png b/tutorials/cap-extend-sfsf-fiori-elements/new-dest.png new file mode 100644 index 0000000000..6072998a24 Binary files /dev/null and b/tutorials/cap-extend-sfsf-fiori-elements/new-dest.png differ diff --git a/tutorials/cap-extend-sfsf-fiori-elements/new-mta-module.png b/tutorials/cap-extend-sfsf-fiori-elements/new-mta-module.png new file mode 100644 index 0000000000..696e70f18a Binary files /dev/null and b/tutorials/cap-extend-sfsf-fiori-elements/new-mta-module.png differ diff --git a/tutorials/cap-extend-sfsf-fiori-elements/project-desc.png b/tutorials/cap-extend-sfsf-fiori-elements/project-desc.png new file mode 100644 index 0000000000..33b53e251c Binary files /dev/null and b/tutorials/cap-extend-sfsf-fiori-elements/project-desc.png differ diff --git a/tutorials/cap-extend-sfsf-fiori-elements/rename-xs-security-copy.png b/tutorials/cap-extend-sfsf-fiori-elements/rename-xs-security-copy.png new file mode 100644 index 0000000000..98ccc0e3d0 Binary files /dev/null and b/tutorials/cap-extend-sfsf-fiori-elements/rename-xs-security-copy.png differ diff --git a/tutorials/cap-extend-sfsf-fiori-elements/service-home.png b/tutorials/cap-extend-sfsf-fiori-elements/service-home.png new file mode 100644 index 0000000000..e6b1fd38d0 Binary files /dev/null and b/tutorials/cap-extend-sfsf-fiori-elements/service-home.png differ diff --git a/tutorials/cap-extend-sfsf-fiori-elements/subdomain.png b/tutorials/cap-extend-sfsf-fiori-elements/subdomain.png new file mode 100644 index 0000000000..1664843849 Binary files /dev/null and b/tutorials/cap-extend-sfsf-fiori-elements/subdomain.png differ diff --git a/tutorials/cap-extend-sfsf-fiori-elements/test-data.png b/tutorials/cap-extend-sfsf-fiori-elements/test-data.png new file mode 100644 index 0000000000..c4d489395e Binary files /dev/null and b/tutorials/cap-extend-sfsf-fiori-elements/test-data.png differ diff --git a/tutorials/cap-extend-sfsf-fiori-elements/xs-app.png b/tutorials/cap-extend-sfsf-fiori-elements/xs-app.png new file mode 100644 index 0000000000..8fd1acf203 Binary files /dev/null and b/tutorials/cap-extend-sfsf-fiori-elements/xs-app.png differ diff --git a/tutorials/cap-extend-sfsf-fiori-elements/xs-security-copy.png b/tutorials/cap-extend-sfsf-fiori-elements/xs-security-copy.png new file mode 100644 index 0000000000..10b6e98922 Binary files /dev/null and b/tutorials/cap-extend-sfsf-fiori-elements/xs-security-copy.png differ diff --git a/tutorials/cap-extend-sfsf-import-services/cap-extend-sfsf-import-services.md b/tutorials/cap-extend-sfsf-import-services/cap-extend-sfsf-import-services.md index d900e93905..20e775c965 100644 --- a/tutorials/cap-extend-sfsf-import-services/cap-extend-sfsf-import-services.md +++ b/tutorials/cap-extend-sfsf-import-services/cap-extend-sfsf-import-services.md @@ -21,7 +21,7 @@ primary_tag: software-product-function>sap-cloud-application-programming-model --- -[ACCORDION-BEGIN [Step 1: ](Get Services Metadata)] +[ACCORDION-BEGIN [Step 1: ](Get services metadata)] The first thing you need to do is to get the XML files (EDM XML – Entity Data Model XML – in short **EDMX**) which define the entities that compose the OData services to be consumed in the application. You can get such files from **SAP API Business Hub**. @@ -65,7 +65,7 @@ Now that you've got both EDMX files in our local computer, it's time to import t [DONE] [ACCORDION-END] -[ACCORDION-BEGIN [Step 3: ](Checkout the Import Results)] +[ACCORDION-BEGIN [Step 3: ](Checkout the import results)] Now, inspect what happened after executing those command lines. First, click on the `ECEmployeeProfile.csn` file in the `srv/external` folder: @@ -98,11 +98,11 @@ With that, you are simply setting the **destination name** and the **path** wher [DONE] [ACCORDION-END] -[ACCORDION-BEGIN [Step 4: ](Create Destination and XSUAA service instances)] +[ACCORDION-BEGIN [Step 4: ](Create destination and XSUAA service instances)] In order to use that destination for testing your application during development, you need to **bind your project** to **two service instances** in BTP: one for the **destination service** and another for the **XSUAA service**. So, start by creating those service instances and a service key (credentials) for each one in BTP. -In the **BTP cockpit** of your trial account, on the left-hand pane click on **Instances and Subscriptions** then on the **Create** button at the **top-right corner**. +In the **BTP cockpit** of your trial account, on the left-hand pane (1) click on **Instances and Subscriptions** then (2) on the **Create** button at the **top-right corner**. ![Figure 12 – Instances and Subscriptions](instances-subscriprions.png) @@ -110,7 +110,7 @@ Fill-in the **service instance information** like in the **screenshot below** an ![Figure 13 – Destination Service instance](destination-service.png) -On **Instances and Subscriptions** click on the **Create** button at the top-right corner again. +(1) On **Instances and Subscriptions** (2) click on the **Create** button at the top-right corner again. ![Figure 14 – Instances and Subscriptions](instances-subscriprions.png) @@ -143,7 +143,7 @@ Done! You have successfully created both service instances and their correspondi [DONE] [ACCORDION-END] -[ACCORDION-BEGIN [Step 5: ](Bind Destination and XSUAA service instances to the local CAP project)] +[ACCORDION-BEGIN [Step 5: ](Bind destination and XSUAA service instances to the local CAP project)] To be able to use the service instances you created in the previous step during development, you need to bind them to the CAP Project. This is done by creating a file named `default-env.json`. In the file you define the so-called `VCAP_SERVICES` object, which is actually an **environment variable** that **holds all the binding information** of an application. @@ -192,11 +192,11 @@ Whenever you **deploy** an application to **Cloud Foundry**, that variable is au } ``` -3- Back in the cockpit, click on the line corresponding to the `sfsf-dest` service instance, then on the **Service Keys** tab on the right, then on the **three dots** next to the service key and finally select **View**. +3- Back in the cockpit, (1) click on the line corresponding to the `sfsf-dest` service instance, then (2) on the **Service Keys** tab on the right, then (3) on the **three dots** next to the service key and finally (4) select **View**. ![Figure 22 – View Service Key](view-sk.png) -4- On the **dialog**, click on **Copy JSON** and then on **Close**. +4- On the **dialog**, (1) click on **Copy JSON** and then (2) on **Close**. ![Figure 23 – Copy Service Key](copy-json.png) @@ -211,7 +211,7 @@ With that, you're done **binding** the project to the **destination** and **XSUA [DONE] [ACCORDION-END] -[ACCORDION-BEGIN [Step 6: ](Check Your Knowledge)] +[ACCORDION-BEGIN [Step 6: ](Check your knowledge)] [VALIDATE_1] [ACCORDION-END] diff --git a/tutorials/cap-extend-sfsf-intro/cap-extend-sfsf-intro.md b/tutorials/cap-extend-sfsf-intro/cap-extend-sfsf-intro.md index 015a56de97..a0d4a23974 100644 --- a/tutorials/cap-extend-sfsf-intro/cap-extend-sfsf-intro.md +++ b/tutorials/cap-extend-sfsf-intro/cap-extend-sfsf-intro.md @@ -23,7 +23,7 @@ primary_tag: software-product-function>sap-cloud-application-programming-model --- -[ACCORDION-BEGIN [Step 1: ](Understand the Business Scenario)] +[ACCORDION-BEGIN [Step 1: ](Understand the business scenario)] You will build a simple **project management** application on top of **SAP SuccessFactors** following these business rules: 1. Each project is composed by members (team) and activities; @@ -36,7 +36,7 @@ You will build a simple **project management** application on top of **SAP Succe [DONE] [ACCORDION-END] -[ACCORDION-BEGIN [Step 2: ](Understand the Application Architecture)] +[ACCORDION-BEGIN [Step 2: ](Understand the application architecture)] Here's a diagram representing the overall architecture for the application: ![Figure 1 – Application Architecture](architecture.png) @@ -45,12 +45,12 @@ The solution is a standalone application that will read the basic employee (SF u The application data model will be stored in an **SAP HANA Cloud** database. -In this tutorial group, the application will be deployed on **Cloud Foundry**, although it could be also deployed to the **Kyma Runtime** as depicted in the architecture. +In this tutorial group, the application will be deployed on **Cloud Foundry**, although it could be also deployed to the **`Kyma` Runtime** as depicted in the architecture. [DONE] [ACCORDION-END] -[ACCORDION-BEGIN [Step 3: ](Understand the Data Model)] +[ACCORDION-BEGIN [Step 3: ](Understand the data model)] Here's a diagram representing the data model for the application: ![Figure 2 – Data Model Diagram](data-model.png) @@ -66,7 +66,7 @@ In the same way, there's no direct association between the projects and the back [DONE] [ACCORDION-END] -[ACCORDION-BEGIN [Step 4: ](Perform Initial Preparation)] +[ACCORDION-BEGIN [Step 4: ](Perform initial preparation)] From the **global account** page on SAP BTP cockpit, click on the **trial** subaccount ![Figure 3 – SAP BTP trial access](trial.png) @@ -106,7 +106,7 @@ Click on **Close** [DONE] [ACCORDION-END] -[ACCORDION-BEGIN [Step 5: ](Check Your Understanding)] +[ACCORDION-BEGIN [Step 5: ](Check your knowledge)] [VALIDATE_1] [ACCORDION-END] diff --git a/tutorials/cap-extend-sfsf-jumpstart/cap-extend-sfsf-jumpstart.md b/tutorials/cap-extend-sfsf-jumpstart/cap-extend-sfsf-jumpstart.md index 56b3f3820c..bbfb3daa3d 100644 --- a/tutorials/cap-extend-sfsf-jumpstart/cap-extend-sfsf-jumpstart.md +++ b/tutorials/cap-extend-sfsf-jumpstart/cap-extend-sfsf-jumpstart.md @@ -1,5 +1,5 @@ --- -title: Jumpstart the SAP SuccessFactors Extension CAP Project +title: Jump start the SAP SuccessFactors Extension CAP Project description: First step to start developing the SAP SuccessFactors extension auto_validation: true time: 6 @@ -83,7 +83,7 @@ Click on the **small notifications icon** in the bottom right corner to view the [DONE] [ACCORDION-END] -[ACCORDION-BEGIN [Step 3: ](Create the CAP Project)] +[ACCORDION-BEGIN [Step 3: ](Create the CAP project)] Now it's time to create the CAP Project and understand it's structure. From the **top menu**, click on **Terminal** and select **New Terminal** @@ -104,14 +104,14 @@ From the **top menu**, click on **File** and select **Open Workspace…** ![Figure 17 – Open Workspace](open-workspace.png) -In the **dialog**, select the `sfsf-projman` folder under projects and the **VS Code Workspace (*.code-workspace)** from the **Format dropdown**, then click **Open** +In the **dialog**, (1) select the `sfsf-projman` folder under projects (2) and the **VS Code Workspace (*.code-workspace)** from the **Format dropdown**, then (3) click **Open** ![Figure 18 – Set Workspace](set-workspace.png) [DONE] [ACCORDION-END] -[ACCORDION-BEGIN [Step 4: ](Understand the Project Structure)] +[ACCORDION-BEGIN [Step 4: ](Understand the project structure)] SAP Business Application Studio will restart and, after a few seconds, you should see the **following structure** at the bottom of the left-hand side pane: ![Figure 19 – Project Structure](project-structure.png) @@ -128,7 +128,7 @@ You can see that, under the project folder, `cds` has created three folders: `ap [DONE] [ACCORDION-END] -[ACCORDION-BEGIN [Step 5: ](Check Your Knowledge)] +[ACCORDION-BEGIN [Step 5: ](Check your knowledge)] [VALIDATE_1] [ACCORDION-END] diff --git a/tutorials/cap-extend-sfsf-ui-annotations/added-member.png b/tutorials/cap-extend-sfsf-ui-annotations/added-member.png new file mode 100644 index 0000000000..1f60e7a9d9 Binary files /dev/null and b/tutorials/cap-extend-sfsf-ui-annotations/added-member.png differ diff --git a/tutorials/cap-extend-sfsf-ui-annotations/cap-extend-sfsf-ui-annotations.md b/tutorials/cap-extend-sfsf-ui-annotations/cap-extend-sfsf-ui-annotations.md new file mode 100644 index 0000000000..6d0cf56860 --- /dev/null +++ b/tutorials/cap-extend-sfsf-ui-annotations/cap-extend-sfsf-ui-annotations.md @@ -0,0 +1,765 @@ +--- +title: Prepare the SAP SuccessFactors Extension UI with CDS Annotations +description: In this phase of the development you will add CDS annotations to the CAP service for further usage by the Fiori Elements UI. +auto_validation: true +time: 15 +tags: [ tutorial>beginner, software-product>sap-btp--cloud-foundry-environment] +primary_tag: software-product-function>sap-cloud-application-programming-model +--- + +## Prerequisites + - Complete the tutorial: [**Prepare to Develop the SAP SuccessFactors Extension**](cap-extend-sfsf-intro) + - Complete the tutorial: [**Jump start the SAP SuccessFactors Extension CAP Project**](cap-extend-sfsf-jumpstart) + - Complete the tutorial: [**Import SAP SuccessFactors OData Services definitions**](cap-extend-sfsf-import-services) + - Complete the tutorial: [**Create the CDS Data Model for the SAP SuccessFactors Extension**](cap-extend-sfsf-data-model) + - Complete the tutorial: [**Create the CAP Service for the SAP SuccessFactors Extension**](cap-extend-sfsf-create-service) + - Complete the tutorial: [**Add Business Logic to the SAP SuccessFactors Extension**](cap-extend-sfsf-add-logic) + +## Details +### You will learn + - How to **create the annotations file** + - How to **code the CDS annotations** into the file + - Understand the **value help** annotations + - How to **test the application UI** using the **Fiori preview** feature + - How to **check the result** of an **employee's project assignment** on SAP SuccessFactors + - How to do **further testing** + +--- + +[ACCORDION-BEGIN [Step 1: ](Create the Annotations File)] + +It is a best practice to maintain the UI annotations in a separate file referencing your CAP service. So, go ahead and create that file. + +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](create-file.png) + +On the **dialog** name the file `projman-service-ui.cds` and click **OK**. + +![Figure 2 – Set File Name](set-file-name.png) + +[DONE] +[ACCORDION-END] + +[ACCORDION-BEGIN [Step 2: ](Code the CDS Annotations)] + +Copy and paste the **code snippet below** into the recently created file: + +```CDS Annotations +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 +}); +``` + +[DONE] +[ACCORDION-END] + +[ACCORDION-BEGIN [Step 3: ](Understand the Value Help Annotations)] + +The code from the previous step contains all required CDS annotations to make the UI look and behave like expected by the business rules defined in the [**group introduction and preparation**](cap-extend-sfsf-intro). + +However, the purpose of this tutorial 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 done in the previous step is demonstrated and explained: + +- [Fiori Elements: annotations reference and feature showcase](https://github.com/SAP-samples/fiori-elements-feature-showcase) + +And you can also refer to the advanced topic of **Serving Fiori UIs** from the official **CAP documentation**: + +- [Serving Fiori UIs](https://cap.cloud.sap/docs/advanced/fiori) + +But, before you 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 tutorial, 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, you set the `Common.ValueListParameterIn` parameter from the `ValueList` annotation to the activity's `parent_ID` which is actually the current project being edited. + +[DONE] +[ACCORDION-END] + +[ACCORDION-BEGIN [Step 4: ](Test the Application UI)] + +Finally, it's come the time to test everything that you have done so far! For that, you can rely on the nice **Fiori Preview** feature that's available during development. + +So, go ahead and do it! + +From the **service home page** click on the **Fiori Preview** link next to the `Project` entity. + +![Figure 3 – Service Home Page](service-home.png) + +After a couple seconds, the page below should be displayed: + +![Figure 4 – Fiori Preview](fiori-preview.png) + +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](object-page.png) + +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. + +You'll focus on the **key part** of this application extension for your 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](edit-project.png) + +Now, add a new team member. Click on the **Create** button right on top of the **Members** Smart Table: + +![Figure 7 – New Member](new-member.png) + +> **NOTE**: as **SAP UI5 framework** - which powers-up the **Fiori Elements UI** - is loaded directly from its host in the internet and keeps constantly evolving, it is possible that the new member object page is skipped. In that case, you can fill the data from the next instructions directly in the **Members** Smart Table. + +![Figure 8 – Employees from SAP SuccessFactors](sfsf-employees.png) + +> **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` (or some other user from your tenant) 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` (or the user you picked) 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](member-data.png) + +Click on the **Apply** button at 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](added-member.png) + +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](project-saved.png) + +[DONE] +[ACCORDION-END] + +[ACCORDION-BEGIN [Step 5: ](Check Operation Result on SAP SuccessFactors)] + +You'll now 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 the [**group introduction and preparation**](cap-extend-sfsf-intro)) and search for the employee `Sarah Lynn Moultone` (or any other that you utilized to add as the new team member of the project), then click on it to open the employee profile: + +![Figure 12 – Search Employee](search-employee.png) + +![Figure 13 – Employee Profile](employee-profile.png) + +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](special-assignment.png) + +[DONE] +[ACCORDION-END] + +[ACCORDION-BEGIN [Step 6: ](Execute Other Tests)] + +The **test sequence** you'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 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 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 you can say your application is working pretty fine according to the defined business rules. + +[DONE] +[ACCORDION-END] + +[ACCORDION-BEGIN [Step 7: ](Check Your Knowledge)] + +[VALIDATE_1] +[ACCORDION-END] diff --git a/tutorials/cap-extend-sfsf-ui-annotations/create-file.png b/tutorials/cap-extend-sfsf-ui-annotations/create-file.png new file mode 100644 index 0000000000..20d904e0a7 Binary files /dev/null and b/tutorials/cap-extend-sfsf-ui-annotations/create-file.png differ diff --git a/tutorials/cap-extend-sfsf-ui-annotations/edit-project.png b/tutorials/cap-extend-sfsf-ui-annotations/edit-project.png new file mode 100644 index 0000000000..a5f834d6b1 Binary files /dev/null and b/tutorials/cap-extend-sfsf-ui-annotations/edit-project.png differ diff --git a/tutorials/cap-extend-sfsf-ui-annotations/employee-profile.png b/tutorials/cap-extend-sfsf-ui-annotations/employee-profile.png new file mode 100644 index 0000000000..9958febd63 Binary files /dev/null and b/tutorials/cap-extend-sfsf-ui-annotations/employee-profile.png differ diff --git a/tutorials/cap-extend-sfsf-ui-annotations/fiori-preview.png b/tutorials/cap-extend-sfsf-ui-annotations/fiori-preview.png new file mode 100644 index 0000000000..b37ff8d08c Binary files /dev/null and b/tutorials/cap-extend-sfsf-ui-annotations/fiori-preview.png differ diff --git a/tutorials/cap-extend-sfsf-ui-annotations/member-data.png b/tutorials/cap-extend-sfsf-ui-annotations/member-data.png new file mode 100644 index 0000000000..333ecb2477 Binary files /dev/null and b/tutorials/cap-extend-sfsf-ui-annotations/member-data.png differ diff --git a/tutorials/cap-extend-sfsf-ui-annotations/new-member.png b/tutorials/cap-extend-sfsf-ui-annotations/new-member.png new file mode 100644 index 0000000000..5a9b8e2b55 Binary files /dev/null and b/tutorials/cap-extend-sfsf-ui-annotations/new-member.png differ diff --git a/tutorials/cap-extend-sfsf-ui-annotations/object-page.png b/tutorials/cap-extend-sfsf-ui-annotations/object-page.png new file mode 100644 index 0000000000..2261fd6129 Binary files /dev/null and b/tutorials/cap-extend-sfsf-ui-annotations/object-page.png differ diff --git a/tutorials/cap-extend-sfsf-ui-annotations/project-saved.png b/tutorials/cap-extend-sfsf-ui-annotations/project-saved.png new file mode 100644 index 0000000000..3767d0fd5c Binary files /dev/null and b/tutorials/cap-extend-sfsf-ui-annotations/project-saved.png differ diff --git a/tutorials/cap-extend-sfsf-ui-annotations/search-employee.png b/tutorials/cap-extend-sfsf-ui-annotations/search-employee.png new file mode 100644 index 0000000000..c103a3cd9c Binary files /dev/null and b/tutorials/cap-extend-sfsf-ui-annotations/search-employee.png differ diff --git a/tutorials/cap-extend-sfsf-ui-annotations/service-home.png b/tutorials/cap-extend-sfsf-ui-annotations/service-home.png new file mode 100644 index 0000000000..99efcff7c6 Binary files /dev/null and b/tutorials/cap-extend-sfsf-ui-annotations/service-home.png differ diff --git a/tutorials/cap-extend-sfsf-ui-annotations/set-file-name.png b/tutorials/cap-extend-sfsf-ui-annotations/set-file-name.png new file mode 100644 index 0000000000..60becf2493 Binary files /dev/null and b/tutorials/cap-extend-sfsf-ui-annotations/set-file-name.png differ diff --git a/tutorials/cap-extend-sfsf-ui-annotations/sfsf-employees.png b/tutorials/cap-extend-sfsf-ui-annotations/sfsf-employees.png new file mode 100644 index 0000000000..376bfc66e3 Binary files /dev/null and b/tutorials/cap-extend-sfsf-ui-annotations/sfsf-employees.png differ diff --git a/tutorials/cap-extend-sfsf-ui-annotations/special-assignment.png b/tutorials/cap-extend-sfsf-ui-annotations/special-assignment.png new file mode 100644 index 0000000000..c5a5da5f48 Binary files /dev/null and b/tutorials/cap-extend-sfsf-ui-annotations/special-assignment.png differ