Technical Articles
Tracking Fiori Usage in Fiori Launchpad (FLP)
Currently SAP didn’t provide any tools to track the usage of the application (Tiles) in the launchpad. I came across several option & suggestions but none of them were really giving the real usages.
Example:
SE38–>Enter Report name /IWFND/R_METERING_VIEW
This report will show the usage of the OData service. Fiori apps will consume the service several times during execution and you can’t relate from which app this call is coming from.
So I decided to try utilize the following to achieve my goal:
- Use Web APIs Windows hashchange event which will help us to monitor the target mapping changes.
- Use FLP API sap.ushell.services.LaunchPage (ondemand.com) to get user’s tiles & groups & catalogs.
- Fiori launchpad plugin to run my tracking script.
- Custom Odata service to post user tracking logs
- Table to store the logs
- Use of standard table SUI_TM_MM_APP to get some extra information of the used app that is not available in the sap.ushell.services.LaunchPage services.
This way it will log every app had been used even HTML GUI , Webdynpro , Custom SAPUI5 apps maintained as target mapping.
It will exclude the navigation that change the hash within the the same app, so we will not over record the usage of an app.
Let’s start now :
1- WEBIDE Create a plugin, please refer to the following for detailed steps Creating SAP Fiori Launchpad Plugins in SAP Web IDE – SAP Help Portal
- Add below code to the plugin allowing with predefined code.
init: function () { var oComponent = this; var rendererPromise = this._getRenderer(); //Trakcing the usage is working upon 'hashchange' event triggers window.addEventListener("hashchange", oComponent._onHashChange.bind(this), true); //Waiting for the FLP shell container to be ready to start plugin methods rendererPromise.then(function (oRenderer) { oComponent._onFioriTracker(); oComponent._initTrackerOdataService(); }); }, _initTrackerOdataService: function() { //initialization of Tracking service (OData) --> ZFLPTRACKING var sServiceUrl = '/sap/opu/odata/sap/ZFLPTRACKING_SRV/'; var oModel = new sap.ui.model.odata.v2.ODataModel(sServiceUrl, true); oModel.setUseBatch(false); this.setModel(oModel); }, _onFioriTracker: function(oEvent) { // Bullding the array of the tiles once the FLP is loaded // The belwo array will be used to be refreced to get the tiles details upon clicking of a tile this.UserTiles = []; //Custom Tiles had to be treated diffrently to get its informaiton //it's pushed inside the below array to be treated thend again pushed back to this.UserTiles array this.MenuTileArray = []; //Here we will get the tiles from groups & catalogs then start collecting required //information (title , catalog id , group id , target mapping .. etc) sap.ushell.Container.getService("LaunchPage").getGroups().then(function(aGroups) { sap.ushell.Container.getService("LaunchPage").getCatalogs().done(function (oCatalogs) { for (var i = 0; i < aGroups.length; i++) { var aGrpTiles = sap.ushell.Container.getService("LaunchPage").getGroupTiles(aGroups[i]); for (var j = 0; j < aGrpTiles.length; j++) { var sTileTitle = sap.ushell.Container.getService("LaunchPage").getTileTitle(aGrpTiles[j]); if(sTileTitle === "App Launcher – Static") { if( sap.ushell.Container.getService("LaunchPage").getCatalogTilePreviewSubtitle(aGrpTiles[j]) ){ sTileTitle = sap.ushell.Container.getService("LaunchPage").getCatalogTilePreviewTitle(aGrpTiles[j]) + " ( " + sap.ushell.Container.getService("LaunchPage").getCatalogTilePreviewSubtitle(aGrpTiles[j]) + " )"; }else{ sTileTitle = sap.ushell.Container.getService("LaunchPage").getCatalogTilePreviewTitle(aGrpTiles[j]); } } var sTileTarget = sap.ushell.Container.getService("LaunchPage").getCatalogTileTargetURL(aGrpTiles[j]); this.sTileCatalogeId = sap.ushell.Container.getService("LaunchPage").getCatalogTileId(aGrpTiles[j]); var aCatalogTile = oCatalogs.filter(function (c) { return this.sTileCatalogeId.includes(c.id); }.bind(this)); if(aCatalogTile.length > 0){ var sTileCataloge = aCatalogTile[0].title; }else{ var sTileCataloge = sap.ushell.Container.getService("LaunchPage").getCatalogTileTitle(aGrpTiles[j]); } if(sTileTitle === "Menu Custom Tile"){ //Handle Menu Tile: push them in array to handle them later this.MenuTileArray.push(aGrpTiles[j]); }else{ this.UserTiles.push({ tileTitle : sTileTitle, catalogeId : this.sTileCatalogeId, cataloge : sTileCataloge, groupTitle : aGroups[i].getTitle(), groupId : aGroups[i].getId(), target : sTileTarget }); }//-------------if(sTileTitle === "Custom Tile"){ sTileTitle = ""; sTileTarget = ""; this.sTileCatalogeId = ""; sTileCataloge = ""; }//----------for (var j = 0; j < aGrpTiles.length; j++) { aGrpTiles = []; }//-------for (var i = 0; i < aGroups.length; i++) { //Handle Menu Tile for(var x=0; x <this.MenuTileArray; x++ ){ sap.ushell.Container.getService("LaunchPage").getTileView(this.MenuTileArray[x]).then(function(oTileView) { var tileApi = oTileView.getViewData().chip; var tileConfigurationData = sap.ushell.components.tiles.utilsRT.getConfiguration(tileApi); if(tileConfigurationData){ sTileTitle = tileConfigurationData.display_title_text; this.UserTiles.push({ tileTitle : sTileTitle, catalogeId : this.sTileCatalogeId, cataloge : sTileCataloge, groupTitle : aGroups[i].getTitle(), groupId : aGroups[i].getId(), target : sTileTarget }); } //---------if(tileConfigurationData){ }.bind(this)); //--------sap.ushell.Container.getService("LaunchPage").getTileView(aGrpTiles[j]).then(function(oTileView) { } window.dispatchEvent(new HashChangeEvent("hashchange")); }.bind(this)); //--- sap.ushell.Container.getService("LaunchPage").getCatalogs().done(function (oCatalogs) { }.bind(this)); //-- sap.ushell.Container.getService("LaunchPage").getGroups().then(function(aGroups) { }, _onHashChange: function(oEvent) { //This method will handle the target mappinng change 'HASH' var oComponent = this; var sOldHash = sap.ushell.utils.getBasicHash(this._getHashFromURL(oEvent.oldURL)); var sNewHash = sap.ushell.utils.getBasicHash(this._getHashFromURL(oEvent.newURL)); var tile = []; //handle open in new tab or by direct link if(sOldHash == "" && sNewHash == ""){ sNewHash = sap.ushell.utils.getBasicHash(window.location.hash); } //handle the change of URL while navigation within the app or within same target mapping if (sOldHash !== sNewHash) { this.sHash = sNewHash; tile = this.UserTiles.filter(function(o) { if (o.target) { return sap.ushell.utils.getBasicHash(o.target).indexOf(sap.ushell.utils.getBasicHash(oComponent.sHash)) === 0; } }); if(sNewHash === "Shell-home" && tile.length === 0 && true ){ // This to consider or not to consider the FLP home usage tile.tileTitle = "Fiori Launchpad Access"; tile.cataloge = ""; tile.catalogeId = ""; tile.groupId = ""; tile.groupTitle = ""; tile.target = "#Shell-home"; this._postTracking(tile); }else{ this._postTracking(tile[0]); } } }, _getHashFromURL: function(URL) { return URL.substring(URL.indexOf('#')); }, _postTracking: function(tile) { //Posting the application usage recored to backend if(tile){ var oData = { TrackNo : "", // <--- dummy, Tile : tile.tileTitle, Cataloge : tile.cataloge, Catalogeid : tile.catalogeId, Tgroupid : tile.groupId, Tgroup : tile.groupTitle, Target : tile.target }; this.getModel().create("/TrackSet", oData, { success: function(oSuccess) { }.bind(this), error: function(oError) { }.bind(this) }); } },
As I mentioned we use the hashchange event to track the used app. and to get the app information at the initialization I’m building an array of all users tiles along with available information (Tile Title, cataloge,group, target(#SemanticObject-Action), …etc) and a method to handle the change hash then get the clicked tile form our array then prepare the data to be posted at the back end.
*Best thing is debuge the code to have full understanding for the solution and if you have any question please feel free.
- Deploy the BSP application.
2- Create Shell plugin Target Mapping in the launchpad designer. Activating Plug-Ins on the ABAP Platform – SAP Help Portal
3- Assignee the catalog which contain the shell plugin to a role. And assign the role to people who you want to track.
4- Create Table to hole data (Below is suggested structure):
MANDT MANDT CLNT 3 0 0 Client
TRACK_NO GUID RAW 16 0 0 Globally Unique Identifier
FIORI_ID /UI2/AD_MM_FIORI_ID CHAR 20 0 0 SAP Fiori ID
TILE CHAR 100 0 0 Tile
FLP_USER SYUNAME CHAR 12 0 0 User Name
FDATE SYDATUM DATS 8 0 0 System Date
FTIME SYUZEIT TIMS 6 0 0 System Time
CATALOGEID /UI2/PAGE_ID CHAR 100 0 0 Catalog ID
CATALOGE CHAR 200 0 0 Cataloge Title
TGROUPID /UI2/GROUP_ID CHAR 100 0 0 Group ID
TGROUP CHAR 200 0 0 Group Title
TARGET CHAR 200 0 0 Target
SEMANTICOBJECT CHAR 40 0 0 Semantic Object
ACTION CHAR 40 0 0 Action
PARAMETERS CHAR 200 0 0 Parameters
5- Create Odata Service : and implement the create method to post the logs.
Below is the create entity
DATA : ls_output LIKE er_entity,
ls_zflptracking TYPE zflptracking.
io_data_provider->read_entry_data(
IMPORTING es_data = ls_output
).
MOVE-CORRESPONDING ls_output TO ls_zflptracking.
CALL FUNCTION 'GUID_CREATE'
IMPORTING
ev_guid_16 = ls_zflptracking-track_no
* EV_GUID_22 =
* EV_GUID_32 =
.
ls_zflptracking-flp_user = sy-uname.
ls_zflptracking-fdate = sy-datum.
ls_zflptracking-ftime = sy-uzeit.
* X-SAP-UI2-PAGE:X-SAP-UI2-CATALOGPAGE:SAP_SFIN_BC_AA_DOC_PROC:00O2TPKTQCDUX5DENYJNYYXTU
SPLIT ls_zflptracking-catalogeid AT ':' INTO TABLE DATA(lt_segments).
SPLIT ls_zflptracking-target AT '#' INTO TABLE DATA(lt_target).
READ TABLE lt_target INTO DATA(lv_targetval) INDEX 2.
SPLIT lv_targetval AT '-' INTO TABLE DATA(lt_semact).
READ TABLE lt_semact INTO DATA(lv_action2) INDEX 2.
SPLIT lv_action2 AT '?' INTO TABLE DATA(lt_action).
READ TABLE lt_action INTO DATA(lv_action) INDEX 1.
READ TABLE lt_semact INTO DATA(lv_semobj) INDEX 1.
ls_zflptracking-semanticobject = lv_semobj.
ls_zflptracking-action = lv_action.
ls_zflptracking-catalogeid = lt_segments[ 3 ].
"Get FIORI APP ID
SELECT SINGLE fiori_id FROM sui_tm_mm_app
INTO @DATA(lv_fiori_id)
WHERE sem_obj = @lv_semobj AND sem_act = @lv_action.
IF sy-subrc = 0 and lv_fiori_id is NOT INITIAL.
ls_zflptracking-fiori_id = lv_fiori_id.
ENDIF.
INSERT zflptracking FROM ls_zflptracking.
6- You are ready to go.
Here is my ALP Report based CDS query.

Ideas to enhance the plugin:
- Add switch on/off flag
- cash the array of the tile in the browser cashe
looking to hear all your suggestions to improve:)
Thanks to my friend for his help and contribution: Vivekanandhan Srinivasan | SAP People
Hi Musa,
have you thought about sharing your project on a public Git repository using abapGit?
Best Regards
Gregor
I'm not much familier with how to 🙈 but for sure I will try improve the idea and do so.
Sorry for my late response.
Nice job musa.
Hi, Good Day!
Thanks for the wonderful blog, i have one query regarding app type.
With ECC 6.0 i am not able to figure out the app type if it is transaction, custom or web dynpro.
Will you be able to advise here?
Hi ,,
it's one of the Fiori Floorplans.
Check this.
Analytical List Page | SAP Fiori Design Guidelines
Hello and thanx for the great blog.
I'm quite new with Fiori apps. Did you create a consumption CDS from the zflptracking table? if yes witch annotation did you use to achieve your goal?
Thank you
Actually I have used app. "Manage KPIs & Reporting". You need to get your data as Anaytical CDS to supply the report.
check: https://blogs.sap.com/2018/03/18/create-an-analytical-model-based-on-abap-cds-views/
Then using the app. "Manage KPIs & Reporting" you creat a group then KPI then from KPI you can create you ALP report easily drag drop.
Hello Musa and thanx for your reply the link was really hepfull, i'm wondering how you have build your cube. Have you make son specific association?
Thanx again.
Sorry for my late response.
CDS should have the following annotation --> @Analytics.dataCategory: #CUBE
At lease on field should be having the following annotation (Measures --> @DefaultAggregation: #SUM
Then Consume the cub CDS in new CDS (@Analytics.query: true ) and publish Odata then you will be able to use this in KPI & Report app.
CDS Analytical Projection Views – the new Analytical Query Model | SAP Blogs
Hi,
Thanks for this wonderful blog, Can you please suggest an alternate way to achieve same functionality or at least getting tile name and target mapping clicked by the user in spaces and pages as this sap.ushell.services.LaunchPage (ondemand.com) seems to be deprecated for fiori 2020.
Thanks in advance!
Thanks for your comment. Soon I will update with the blog with latest changes (to handle both cases: Groups or Spaces)
Hi Musa,
Did you update the Plug-in related source code to support latest versions of Fiori w.r.t to below error?
"Deprecated as of version 1.99. This service has been deprecated as it only works for the classic homepage."
Thanks
Damodara
Hello Musa,
Thanks for the wonderful blog!
I am referring to your blog to implement a similar solution. But we are facing issue when user clicks shell tiles. When a shell tile is clicked once, two records are being saved in the database table, though network trace shows single POST call, but for other tiles : for custom reports or SAP standard applications this is working fine.
Can you please suggest a probable root case and how can we fix this?
Thanks and Regards
Semanti Sinha Mahapatra
Hello Musa,
Thanks for the wonderful blog!
We are facing issue when user clicks shell tiles. Whenever the app opens in a new tab, two records are being saved in the database table, though network trace shows single POST call, but for other tiles : for custom reports or SAP standard applications this is working fine.
Can you please suggest a probable root case and how can we fix this?
Thanks,
Roshni