Working as BTP Developer I naturally look for the same features for Fiori and Fiori Launchpad what I had on ABAP stack in S/4HANA systems. Cloud is a different world and many features onPremise systems have appear on BTP in a different format after a while.
Here my goal was to display some KPI numbers on BTP launchpad service aka. SAP Build Work Zone tiles. The official name to provide dynamic information on tiles is called Dynamic App Launcher. This is what we’ll implement.
You can look for the official reference here.
We’ll follow a top-down approach to follow the processing of the request.
We’ll see here the small adjustment needed to the manifest.json
file of the Fiori application, so that during deployment to Build Work Zone instead of a Static App Launcher, a dynamic one is created.
{
"_version": "1.49.0",
"sap.app": {
...
"dataSources": {
"mainService": {
"uri": "odata/v4/catalog/",
"type": "OData",
"settings": {
"odataVersion": "4.0",
"annotations": [
"annotation"
]
}
},
"annotation": {
"type": "ODataAnnotation",
"uri": "annotations/annotation.xml",
"settings": {
"localUri": "annotations/annotation.xml"
}
}
},
"crossNavigation": {
"inbounds": {
"com-myapp-inbound": {
"signature": {
"parameters": {},
"additionalParameters": "allowed"
},
"semanticObject": "BO",
"action": "display",
"title": "{{flpTitle}}",
"subTitle": "{{flpSubtitle}}",
"icon": "sap-icon://inventory",
"indicatorDataSource": {
"dataSource": "mainService",
"path": "getTileInfo(appName='PO')",
"refresh": 600
}
}
}
}
}
...
}
The additional configuration is the indicatorDataSource
block. We tell the Launchpad Service from where to take the data to be displayed on the Tile.
The path
points to the resource in your service endpoint, which should return either a single number or an object in a dedicated format.
Here we address a function returning an object with all possible information for demonstration purposes, but You can address simply any entityset with a $count request like "path": "Orders/$count"
. You can add filters as well, just conform to the OData URI syntax.
Refresh interval is in seconds and can be 10 or above, less is considered as 10, but setting 0 means it will be loaded 1 time only.
As result the Application Launcher (Tile) in Build Work Zone turns to dynamic after deployment.
We addressed a function so let’s define it in the srv/service.cds
file.
@requires: 'authenticated-user'
@protocol: 'odata'
service CatalogService {
type DynamicAppLauncher {
subtitle : String;
title : String;
icon : String;
info : String;
infoState : String;
number : Decimal(9, 2);
numberDigits : Integer;
numberFactor : String;
numberState : String;
numberUnit : String;
stateArrow : String;
}
// Delivers KPI Information for Launchpad Tiles
function getTileInfo(appName : String) returns DynamicAppLauncher;
}
First what You notice here, that we defined an additional type DynamicAppLauncher
. This is the expected structure of the dynamic tile information. For more details look at the SAP help site. Second You see that properties defined statically in manifest.json
already present here as well. This is very flexible and a good feature from SAP. It means we can override static properties like title and subtitle with dynamic values. All you need to do is to include that property in the returned object, or omit if You are fine with the title defined in manifest.json
for example.
It’s time to implement the logic behind the function to retrieve the dynamic data finally in srv/service.js
.
const { TileInfo } = require("./handlers/TileInfo");
srv.on("getTileInfo", async (req) => {
let tileInfo = new TileInfo(req.data.appName);
return await tileInfo.collect();
});
Okay good, but what is this ? 🙂
This is the entry of the service implementation. I call it entry point because You should not start to implement complex logic here and write thousand of lines of code which makes Your service and code unreadable, what no one dares to touch. Rather do modularization and support reusability. Create the classes which are responsible for a certain tasks, instantiate and invoked them. Believe me You’ll become a beloved colleague. You can operate with classes and inheritance in node.js already. Check out the node.green site, which explain You what is possible on given version of node on server side. Classes are however just shortcuts to the functional prototyping with a nice syntax and readability in JS, what can be placed easy into node modules. Okay let’s stop the architect rap and continue :).
The class responsible to retrieve the dynamic information is placed in file srv/handlers/TileInfo.js
.
/**
* Collecting Dynamic App Launcher Information
*/
class TileInfo {
APP_NAMES = Object.freeze({
purchaseOrder: "PO"
});
#appName;
/**
* Instance setup
* @param {*} appName Application Name
*/
constructor(appName) {
this.#appName = appName ? appName : "UNKNOWN";
}
/**
* Collect Tile Properties
* @returns
*/
async collect() {
try {
switch (this.#appName) {
case this.APP_NAMES.purchaseOrder:
return {
subtitle: "This is the: subtitle",
title: "This is the: title",
icon: "sap-icon://developer-settings",
info: "This is the: info",
infoState: "Critical",
number: "44.44",
numberDigits: 1,
numberFactor: "k",
numberState: "Negative",
numberUnit: "EUR",
stateArrow: "Down"
};
// break;
default:
return {};
}
}
catch (error) {
return {};
}
}
}
module.exports = { TileInfo };
During instantiation of the class the application name (appName)
is passed to the constructor, the one we placed in the manifest.json as function parameter in the path, and sent by Build Work Zone within the request. Here we only return a sample object for demonstration purposes. Replace it to populate the response with useful information according to Your needs. You can notice the UNKNOWN
application we default when nothing is passed. The purpose of this is not to break the runtime for unknown or misconfigured applications names, but rather display only the static content of the tile configured in the manifest.json
like the title and icon. You can spot out and fix such situations seeing the 3 dots instead of the KPI number.
I started this based on this Q&A.