Skip to Content
Technical Articles
Author's profile photo Musa AlHashem

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

 

Assigned Tags

      14 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Gregor Wolf
      Gregor Wolf

      Hi Musa,

      have you thought about sharing your project on a public Git repository using abapGit?

      Best Regards
      Gregor

      Author's profile photo Musa AlHashem
      Musa AlHashem
      Blog Post Author

      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.

      Author's profile photo Ahmed Almudaweb
      Ahmed Almudaweb

      Nice job  musa.

      Author's profile photo d sarangdhar mahajan
      d sarangdhar mahajan

      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?

      Author's profile photo Musa AlHashem
      Musa AlHashem
      Blog Post Author

      Hi ,,

      it's one of the Fiori Floorplans.
      Check this.
      Analytical List Page | SAP Fiori Design Guidelines

      Author's profile photo Marco Sposa
      Marco Sposa

      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

      Author's profile photo Musa AlHashem
      Musa AlHashem
      Blog Post Author

      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.

      Author's profile photo Marco Sposa
      Marco Sposa

      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.

      Author's profile photo Musa Alhashem
      Musa Alhashem

      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

      Author's profile photo Shilpa Khare
      Shilpa Khare

      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!

      Author's profile photo Musa Alhashem
      Musa Alhashem

      Thanks for your comment. Soon I will update with the blog with latest changes (to handle both cases: Groups or Spaces)

      Author's profile photo Damodara Reddy Annem
      Damodara Reddy Annem

      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

      Author's profile photo Semanti Sinha Mahapatra
      Semanti Sinha Mahapatra

      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

      Author's profile photo Roshni Srivastava
      Roshni Srivastava

      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