Skip to Content
Personal Insights
Author's profile photo Leo Richard Irudayam

Random collection of useful code fragments for CAP CDS – Part 2

This blog post is the next part of my series of a random collection of useful code fragments. You can find part 1 here, where I have focussed especially on Fiori Elements and SAPUI5.

The CAP documentation has added some detailed explanation on how fine granular you can restrict access rights on to entities of your service (to the documentation).

 

Restrict role- and attribute based

Sometimes it might be useful to restrict the access not only based on the user’s role but also to limit the result set based on specific attributes within your entity.

In the following you see an example of an entity called MaintenanceProtocol which includes a status attribute and protocol type enumeration. With this restriction definition you can give different options of read and update writes depending on attributes of each tuple.

entity MaintenanceProtocol @(restrict : [{
    grant : [
      'READ',
      'UPDATE'
    ],
    to    : 'Reviewer',
    where : 'DocumentStatus = ''draft'' OR DocumentStatus = ''incomplete'''
  },
  {
    grant : [
      'READ',
    ],
    to    : 'Reviewer',
    where : 'DocumentStatus = ''complete'' AND ProtocolType = ''full-protocol'''
  }]) as
    select from my.MaintenanceProtocol {
      *
    };

 

Existence-based where clause

As where clauses can be very complex, it is sometimes useful to query within the where clauses other entities as well. If the simple existence is enough you can use the exists-clause in your service entity definition:

where exists(select 1 as Result from my.MaintenanceProtocol
    where
      PlaneStatusHistory.PlaneId = Plane.ID AND (PlaneStatusHistory.Status = 'in maintenance' OR PlaneStatusHistory.Status = 'flight permission denied')
    );

 

SAPUI5 Process Flow based on CAP Function

As this is a more complex feature, the first step is creating a base JSON model of your process flow and adding it in your srv folder, ideally in a util subdirectory.

It can look as the following, e.g.

{
	"nodes": [
		{
			"id": "1",
			"lane": "0",
			"title": "Protocol",
			"titleAbbreviation": "GS",
			"children": [ {
				"nodeId": 10,
				"connectionLabel": {
					"id": "myButtonId1To10",
					"text": "-",
					"enabled": true,
					"icon": "sap-icon://time-account",
					"state": "Neutral"
				}
			}],
			"state": "Neutral",
			"stateText": "created",
			"focused": true,
			"highlighted": false,
			"texts": null
		}, 
        {
			"id": "10",
			"lane": "1",
			"title": "Protocol",
			"titleAbbreviation": "GS",
			"children": [ {
				"nodeId": 20,
				"connectionLabel": {
					"id": "myButtonId10To20",
					"text": "-",
					"enabled": true,
					"icon": "sap-icon://time-account",
					"state": "Neutral"
				}
			} ],
			"state": "Neutral",
			"stateText": "submitted",
			"focused": false,
			"highlighted": false,
			"texts": null
		}
    ]
}

 

In a next step you add an function to your service definition cds file:

function GetProcessFlowData() returns String;

 

In your service JS file, you add your logic. In this case, I’ve excluded many more nodes and involved entities to simplify the code. You are here free, to make your process flow diagram as flexible as you need it and to populate it with the right data:

const processBase = require('./util/process.json');

const {
    AvgTimeFromCreationTillSubmission
} = cds.entities('my.service.AnalyticsService')

module.exports = async (srv) => {

    srv.on("GetProcessFlowData", async (req,res) => {
        const tx = cds.transaction(req);

        try {
            let processFlow = JSON.parse(JSON.stringify(processBase));

            const creationTillSubmission = await tx.run(SELECT.one.from(AvgTimeFromCreationTillSubmission).where({ Key: 'Singleton' }));
            
            if (creationTillSubmission && creationTillSubmission.Delta) {
                processFlow.nodes[0].children[0].connectionLabel.text = (Math.round((creationTillSubmission.Delta / 60 / 60 / 24) * 10) / 10) + " d";
            }

            req.reply(processFlow);
        } catch (error) {
            req._.req.logger.error(error.message, {event: req.event, error});
            req.reject(500, error);
        }
    });

}

 

In a next step for the frontend, I’ve decided to use this as an own model, so I add the following passage into my manifest.json file:

"dataSources": {
    "processFlow": {
        "uri": "/analytics/GetProcessFlowData()",
        "type": "JSON"
    }
},
...
"models": {
    "processFlow": {
        "type": "sap.ui.model.json.JSONModel",
        "dataSource": "processFlow"
    }
},

 

And as a final step, you need to only include this into your view:

<c:ProcessFlow id="processflow" showLabels="true" scrollable="true" foldedCorners="true" wheelZoomable="false" nodes="{processFlow>/value/nodes}" lanes="{processFlow>/value/lanes}">
    <c:nodes>
        <c:ProcessFlowNode laneId="{processFlow>lane}" nodeId="{processFlow>id}" title="{processFlow>title}" titleAbbreviation="{processFlow>titleAbbreviation}" children="{path:'processFlow>children', formatter:'.formatConnectionLabels'}" state="{processFlow>state}" stateText="{processFlow>stateText}" texts="{processFlow>texts}" highlighted="{processFlow>highlighted}" focused="{processFlow>focused}" type="{processFlow>type}" />
    </c:nodes>
    <c:lanes>
        <c:ProcessFlowLaneHeader laneId="{processFlow>id}" iconSrc="{processFlow>icon}" text="{processFlow>label}" position="{processFlow>position}" />
    </c:lanes>
</c:ProcessFlow>

Don’t forget to alias :c as sap.suite.ui.commons.

 

I hope you enjoy this random collection of code. Maybe there will be soon part 3 🙂

Assigned Tags

      Be the first to leave a comment
      You must be Logged on to comment or reply to a post.