Skip to Content
Technical Articles

Add apps and groups to Fiori home page using a script

One limitation of Fiori is it’s currently not possible to export user’s personalization such as manually added groups and apps, as documented in note 2455961.

Of course it’s possible to define catalogs and groups centrally so users don’t have to personalize the Launchpad too much, but sometimes it would be good to have a tool which would allow to load all your custom groups and apps easily, especially if the number of objects is high and it must be maintained in several different clients.

In this blog post I will introduce how Fiori Launchpad handles the customization of users’ home pages by adding custom groups and apps and bundle these techniques together in a simple Python 3 script.

 

The magic of /UI2/PAGE_BUILDER_PERS

User-defined groups and apps are handled by the /UI2/PAGE_BUILDER_PERS OData Service. It’s possible to query which groups and apps users can have available and adding custom ones, simulating user actions.

Let’s start by analyzing two basic queries which would list all groups and apps my user can see. It’s possible to execute these queries using any RESTful client, for simpliciy I use the SAP Gateway Client, accessible via tcode /IWFND/GW_CLIENT.

 

This is how we can list all the available groups:

GET /sap/opu/odata/UI2/PAGE_BUILDER_PERS/Pages?$select=id,title&$filter=catalogId eq '/UI2/FLPD_CATALOG'&sap-client=100&$format=json

 

This is how we list all the available apps, which is how the App Finder displays the app list:

GET /sap/opu/odata/UI2/PAGE_BUILDER_PERS/Pages('%2FUI2%2FFiori2LaunchpadHome')/allCatalogs?$expand=Chips/ChipBags/ChipProperties&$select=id,Chips/title,Chips/url,Chips/ChipBags/ChipProperties/chipId,Chips/ChipBags/ChipProperties/value&$filter=type eq 'CATALOG_PAGE' or type eq 'H' or type eq 'SM_CATALOG' or type eq 'REMOTE'&$orderby=title&sap-client=100&$format=json

 

We can do more. Let’s start by an empty Fiori Launchpad:

 

We can add a new group using POST method by invoking the following:

POST /sap/opu/odata/UI2/PAGE_BUILDER_PERS/PageSets('%2FUI2%2FFiori2LaunchpadHome')/Pages

# Payload (content-type: application/json)
{
  "catalogId" : "/UI2/FLPD_CATALOG",
  "title" : "My first group"
}

We need to get the ID we received back from the POST query for later. As we can see, the new group has been created:

 

Now it’s time to add an app by invoking the following:

POST /sap/opu/odata/UI2/PAGE_BUILDER_PERS/PageChipInstances

# Payload (content-type: application/json)
{
  "pageId" : "006O04N57XIQE1LI39O75AB0X",
  "chipId" : "X-SAP-UI2-PAGE:X-SAP-UI2-CATALOGPAGE:SAP_SD_BC_SO_PROC_OP:ET090PW4NWFHY64E22UBU3LBF"
}

# pageID is the ID of the newly created group
# chipId is the ID of the app retrieved from the UI2/PAGE_BUILDER_PERS/Pages query

Some group ID’s are static, for instance the My Home group will be identified as /UI2/FLPD_CATALOG.

 

There we go, app created under our group!

 

Now we have all the basic pieces to automate the group and app personalization, which will be combined in a script.

 

Here comes the Python

In this scenario, suppose we have a list of apps which we want to add to a number of custom groups. We can place them in a simple CSV file, where the first object is the app name as you can find it in the Fiori Apps Library, and the second one the group name:

Manage Sales Orders;Customer Service
Create Billing Documents;Customer Service
Maintain Business Partner;Customer Service
Manage PIRs;Production Planning
Manage Production Orders;Production Planning
Material Documents Overview;Production Planning
Manage Outbound Deliveries;Supply Chain
Freight Orders;Supply Chain
Manage Purchase Requisitions;Procurement
Manage Purchase Orders;Procurement
Manage Customer Line Items;Finance
Post Incoming Payments;Finance

 

This file will be the input of the Python 3 script:

from csv import reader
from getpass import getpass
from requests import session
from requests.auth import HTTPBasicAuth
from sys import argv
from urllib3 import disable_warnings
from urllib3.exceptions import InsecureRequestWarning


apps = {}
groups = {}
try:
    appfile = argv[1]
except IndexError:
    print('You have to specify semicolon-separated app list!')
    exit(1)
user = input('User name: ')
password = getpass('Password: ')
host = input('Hostname and port: ')
client = input('Client: ')
s = session()

# Disable warnings related to insecure certificates, if applicable
disable_warnings(InsecureRequestWarning)

# Get CSRF token
header = {'x-csrf-token': 'Fetch'}
r = s.head('{0}/sap/opu/odata/UI2/PAGE_BUILDER_PERS?sap-client={1}'
           .format(host, client), auth=HTTPBasicAuth(user, password),
           headers=header, verify=False)
csrf = r.headers['x-csrf-token']

# Get available groups
print('\nGetting available groups, it could take a while...')
r = s.get(('{0}/sap/opu/odata/UI2/PAGE_BUILDER_PERS/Pages?$select=id,title&'
           '$filter=catalogId eq \'/UI2/FLPD_CATALOG\''
           '&sap-client={1}&$format=json').format(host, client),
          auth=HTTPBasicAuth(user, password), verify=False)
for group in r.json()['d']['results']:
    groups[group['title'].lower()] = group['id']

# Get available apps
print('Getting available apps, it could take a while...\n')
r = s.get(('{0}/sap/opu/odata/UI2/PAGE_BUILDER_PERS/Pages'
           '(\'%2FUI2%2FFiori2LaunchpadHome\')/allCatalogs?'
           '$expand=Chips/ChipBags/ChipProperties&'
           '$select=id,Chips/title,Chips/url,'
           'Chips/ChipBags/ChipProperties/chipId,'
           'Chips/ChipBags/ChipProperties/value&'
           '$filter=type eq \'CATALOG_PAGE\' or type eq \'H\''
           'or type eq \'SM_CATALOG\' or type eq \'REMOTE\'&'
           '$orderby=title&sap-client={1}&$format=json'.format(host, client)),
          auth=HTTPBasicAuth(user, password), verify=False)
for catalog in r.json()['d']['results']:
    for chip in catalog['Chips']['results']:
        # Only process applauncher chips
        if chip['url'].startswith(
                '/sap/bc/ui5_ui5/ui2/ushell/chips/applauncher'):
            for chipbag in chip['ChipBags']['results']:
                for property in chipbag['ChipProperties']['results']:
                    apps[property['value'].lower()] = property['chipId']

try:
    with open(appfile) as fd:
        for app in reader(fd, delimiter=';'):
            appname = app[0]
            group = app[1]
            if group.lower() == 'my home':
                group = '/UI2/FLPD_CATALOG'

            # Create group if it does not exist yet
            if group.lower() not in groups:
                header = {'x-csrf-token': csrf, 'accept': 'application/json'}
                payload = {'catalogId': '/UI2/FLPD_CATALOG', 'title': group}
                r = s.post(('{0}/sap/opu/odata/UI2/PAGE_BUILDER_PERS/PageSets'
                            '(\'%2FUI2%2FFiori2LaunchpadHome\')/'
                            'Pages?sap-client={1}').format(host, client),
                           auth=HTTPBasicAuth(user, password),
                           headers=header, json=payload, verify=False)
                groups[group.lower()] = r.json()['d']['id']
                print('Group {} created'.format(group))

            # Add app to group
            try:
                header = {'x-csrf-token': csrf, 'accept': 'application/json'}
                payload = {'pageId': groups[group.lower()],
                           'chipId': apps[appname.lower()]}
                r = s.post(('{0}/sap/opu/odata/UI2/PAGE_BUILDER_PERS/'
                            'PageChipInstances?sap-client={1}')
                           .format(host, client),
                           auth=HTTPBasicAuth(user, password),
                           headers=header, json=payload, verify=False)
                print('App {} added to group {}'.format(appname, group))
            except KeyError:
                print('Could not find app {}'.format(appname))
except FileNotFoundError as e:
    print(e)

 

The script will execute the following:

  1. Ask connection settings (username and password, hostname and client)
  2. Read the content of the CSV file passed as a parameter
  3. Get the list of available groups from Fiori
  4. Get the list of available apps from Fiori
  5. For each line in the input CSV file:
    1. it will check whether the group is created already. If not, the script will create it
    2. it will add the app to the group

 

Let’s execute it!

 

And this is the final result 🙂

 

Further evolutions

The script as of it is now is very basic. A few improvements could be made so it will be possible to create link apps, arranging the order of the apps (these tasks are possible via /UI2/PAGE_BUILDER_PERS), or copy all the user settings from a client to another one as a migration tool.

3 Comments
You must be Logged on to comment or reply to a post.