From ALV TO Fiori: Building an Explore using RAP Custom Entities.
Share

[[{“value”:”

Introduction:     

While traditional SAP transactions like SUIM and PFCG provide critical user role assignments, they often lack user-friendly, responsive interfaces for auditors and managers who need a quick, highly searchable overview.

We build a complete full-stack solution to bridge this gap:

  • Backend: A RAP-based OData service that dynamically aggregates role assignments, composite role resolution, and menu hierarchy data.

  • Frontend: A SAP Fiori Elements List Report app equipped with advanced filtering, sorting, and export capabilities.

┌─────────────────────────────────────────────────────────────┐
│ SAP Fiori Launchpad │
│ (Fiori Elements List Report) │
│ ┌─────────────┐ │
│ │ Search, │ │
│ │ Filter, │ │
│ │ Export │ │
│ └──────┬──────┘ │
└──────────┼──────────────────────────────────────────────────┘
│ OData V4
┌──────────┼──────────────────────────────────────────────────┐
│ SAP S/4HANA / ABAP Environment │
│ ┌──────┴──────────────────────────────────────────────┐ │
│ │ RAP Business Object │ │
│ │ ┌─────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ CDS Entity │ │ Behavior │ │ Service │ │ │
│ │ │(ZI_RoleApp) │ │ Definition │ │ Definition │ │ │
│ │ └─────────────┘ └──────────────┘ └──────────────┘ │ │
│ │ │ │
│ │ Data Sources: AGR_USERS, AGR_AGRS, AGR_HIER, │ │
│ │ AGR_BUFFI, CL_PFCG_MENU_TOOLS │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘

image.png

 

 

Part 1: The Backend (ABAP RAP)

Step 1.1: Custom Structure & Table Types
First, define the output structure in a DDIC structure or directly in CDS.
Here we’ll use a custom entity since we’re aggregating data from multiple
sources dynamically.

TYPES: BEGIN OF ty_alv,
uname TYPE xubname,
comp_role TYPE agr_name,
single_role TYPE agr_name,
reporttype TYPE agr_hier-reporttype,
report TYPE agr_hier-report,
disp_name TYPE string,
url TYPE string,
END OF ty_alv.

DATA: lt_alv TYPE TABLE OF ty_alv,
ls_alv TYPE ty_alv,
mt_agr_menu TYPE tt_susr_role_menu,
lr_agr_menu TYPE REF TO susr_role_menu,
lv_tcode TYPE tstc-tcode,
lv_url_type TYPE urltype,
ls_node_detail TYPE cl_pfcg_menu_tools=>ty_node_detail.

Step 1.2: Create the CDS Custom Entity (Data Model)
In ADT, create a Custom Entity (ZI_RoleApplication) that defines the shape
of our data. Since we’re computing this dynamically from role tables, a
custom entity with an implementation class is the right approach.

@EndUserText.label: ‘Role Application Explorer’
@ObjectModel.query.implementedBy: ‘ABAP:ZCL_ROLE_APP_QUERY’
define custom entity ZI_RoleApplication
{
key uname : xubname;
comp_role : agr_name;
single_role : agr_name;
reporttype : char4;
report : char40;
@Semantics.name: { label: ‘Application Name’ }
disp_name : abap.string( 256 );
@Semantics.url: true
url : abap.string( 1024 );
}

Step 1.3: Implement the Query Provider Class
This is where the magic happens. The query class fetches and aggregates
all role and menu data, just like your original report — but now exposed as
an OData service.

CLASS zcl_role_app_query DEFINITION
PUBLIC
FINAL
CREATE PUBLIC .
PUBLIC SECTION.
INTERFACES if_rap_query_provider.
PRIVATE SECTION.

TYPES: BEGIN OF ty_role_app,
uname TYPE xubname,
comp_role TYPE agr_name,
single_role TYPE agr_name,
reporttype TYPE agr_hier-reporttype,
report TYPE agr_hier-report,
disp_name TYPE string,
url TYPE string,
END OF ty_role_app.

METHODS get_role_applications
IMPORTING
iv_user TYPE xubname
iv_app_filter TYPE string OPTIONAL
RETURNING
VALUE(rt_result) TYPE TABLE OF ty_role_app.
ENDCLASS.

CLASS zcl_role_app_query IMPLEMENTATION.
METHOD if_rap_query_provider~select.

DATA: lt_result TYPE TABLE OF ty_role_app,
lv_user TYPE xubname,
lv_filter TYPE string.

” Extract filter parameters from the request
LOOP AT io_request->get_filter( )->get_as_ranges( ) INTO DATA(ls_filter).
CASE ls_filter-name.
WHEN ‘UNAME’.
READ TABLE ls_filter-range INTO DATA(ls_user_range) INDEX 1.
IF sy-subrc = 0.
lv_user = ls_user_range-low
ENDIF.

WHEN ‘DISP_NAME’.
READ TABLE ls_filter-range INTO DATA(ls_app_range) INDEX 1.
IF sy-subrc = 0.
lv_filter = ls_app_range-low.
ENDIF.
ENDCASE.
ENDLOOP.

” Fetch data using our business logic
lt_result = get_role_applications(
iv_user = lv_user
iv_app_filter = lv_filter ).

” Handle paging
DATA(lv_offset) = io_request->get_paging( )->get_offset( ).
DATA(lv_max_rows) = io_request->get_paging( )->get_page_size( ).

IF lv_max_rows > 0.
DATA(lt_paged) = VALUE TABLE OF ty_role_app(
FOR ls_row IN lt_result FROM lv_offset + 1
TO lv_offset + lv_max_rows ( ls_row ) ).
ELSE.
lt_paged = lt_result.
ENDIF.

” Set total count and return data
io_response->set_total_number_of_records( lines( lt_result ) ).
io_response->set_data( lt_paged ).
ENDMETHOD.

METHOD get_role_applications.
DATA: lt_user_roles TYPE TABLE OF agr_users,
lt_comp_to_single TYPE TABLE OF agr_agrs,
mt_role_all TYPE RANGE OF agr_name,
mt_agr_menu TYPE TABLE OF susr_role_menu,
ls_node_detail TYPE cl_pfcg_menu_tools=>ty_node_detail.

“——————————————————————
” Step 1: Fetch all roles assigned to the user
“——————————————————————

SELECT agr_name
FROM agr_users
INTO TABLE @LT_user_roles
WHERE uname = @iv_user.

IF lt_user_roles IS INITIAL.
RETURN. ” No roles found
ENDIF.

“——————————————————————
” Step 2: Resolve composite roles to single roles
“——————————————————————
SELECT agr_name, child_agr
FROM agr_agrs
INTO TABLE @LT_comp_to_single
FOR ALL ENTRIES IN @LT_user_roles
WHERE agr_name = @LT_user_roles-agr_name.

” Build range table of single roles for AGR_HIER query
LOOP AT lt_user_roles INTO DATA(ls_user_role).
READ TABLE lt_comp_to_single WITH KEY agr_name = ls_user_roleagr_name
TRANSPORTING NO FIELDS.

IF sy-subrc <> 0.
” Direct single role assignment
APPEND VALUE #( sign = ‘I’ option = ‘EQ’ low = ls_user_role-agr_name )
TO mt_role_all.
ENDIF.
ENDLOOP.

” Add child single roles from composite roles
LOOP AT lt_comp_to_single INTO DATA(ls_comp).
APPEND VALUE #( sign = ‘I’ option = ‘EQ’ low = ls_comp-child_agr )
TO mt_role_all.
ENDLOOP.

DELETE ADJACENT DUPLICATES FROM mt_role_all COMPARING low.

“——————————————————————
” Step 3: Fetch menu data for all single roles
“——————————————————————
IF mt_role_all IS INITIAL.
RETURN.
ENDIF.

SELECT h~agr_name,
h~object_id,
h~parent_id,
h~reporttype,
h~report,
b~url
FROM agr_hier AS h
LEFT OUTER JOIN agr_buffi AS b
ON h~agr_name = b~agr_name
AND h~object_id = b~object_id
INTO CORRESPONDING FIELDS OF TABLE @MT_agr_menu
WHERE h~agr_name IN @MT_role_all.

IF mt_agr_menu IS INITIAL.
RETURN.
ENDIF.

” Remove empty entries
DELETE mt_agr_menu WHERE reporttype IS INITIAL
OR reporttype = ‘ ‘.

“——————————————————————
” Step 4: Process each menu node and build result
“——————————————————————

LOOP AT mt_agr_menu REFERENCE INTO DATA(lr_agr_menu).
DATA(lv_tcode) = lr_agr_menu->report.
DATA(lv_url_type) = lr_agr_menu->reporttype.

” Get application display name
cl_pfcg_menu_tools=>get_node_detail(
EXPORTING
iv_reporttype = lr_agr_menu->reporttype
iv_tcode = lv_tcode
iv_url_type = lv_url_type
iv_url = lr_agr_menu->url
iv_folder = space

IMPORTING
es_node_detail = ls_node_detail ).

” Apply optional application filter
IF iv_app_filter IS NOT INITIAL.
DATA(lv_search) = |*{ to_upper( iv_app_filter ) }*|.

IF to_upper( ls_node_detail-node_name ) NP lv_search
AND to_upper( lr_agr_menu->report ) NP lv_search.
CONTINUE.
ENDIF.
ENDIF.

” Map to output structure
DATA(ls_result) = VALUE ty_role_app(
uname = iv_user
single_role = lr_agr_menu->agr_name
reporttype = lr_agr_menu->reporttype
report = lr_agr_menu->report
disp_name = ls_node_detail-node_name
url = lr_agr_menu->url ).

” Find composite role mapping
READ TABLE lt_comp_to_single INTO DATA(ls_mapping)
WITH KEY child_agr = lr_agr_menu->agr_name.

IF sy-subrc = 0.
ls_result-comp_role = ls_mapping-agr_name.
ELSE.
ls_result-comp_role = ‘DIRECT ASSIGNMENT’.
ENDIF.

APPEND ls_result TO rt_result.
ENDLOOP.
” Sort final result
SORT rt_result BY comp_role single_role disp_name.
ENDMETHOD.

ENDCLASS.

Step 1.4: Service Definition
@EndUserText.label: ‘Role Application Explorer Service’
define service ZUI_ROLE_APPLICATION {
expose ZI_RoleApplication as RoleApplication;
}

 

image.png

 

Step 1.5: Service Binding
Create a Service Binding in ADT:
• Binding Type: OData V4 – UI
• Service Definition: ZUI_ROLE_APPLICATION
• Publish the service and note the service URL

Part 2: The Frontend (SAP Fiori Elements)

Now that our OData service is live, let’s build a Fiori Elements List Report
app. This provides a modern, responsive UI with zero custom JavaScript.

Step 2.1: App Structure

role-app-explorer/
├── webapp/
│ ├── manifest.json
│ ├── Component.js
│ ├── annotations/
│ │ └── annotation.xml
│ └── i18n/
│ └── i18n.properties
├── package.json
└── ui5.yaml

Step 2.2: manifest.json

The frontend app configuration uses SAP Fiori Elements List Report templates via standard OData V4 annotations.

{
“_version”: “1.12.0”,
“sap.app”: {
“id”: “com.sap.roleappexplorer”,
“type”: “application”,
“i18n”: “i18n/i18n.properties”,
“applicationVersion”: {
“version”: “1.0.0”
},
“title”: “{{appTitle}}”,
“description”: “{{appDescription}}”,
“dataSources”: {
“mainService”: {
“uri”:
“/sap/opu/odata4/sap/zui_role_application/srvd/sap/zui_role_application/0
001/”,
“type”: “OData”,
“settings”: {
“odataVersion”: “4.0”
}
}
}
},
“sap.ui5”: {
“dependencies”: {
“minUI5Version”: “1.120.0”,
“libs”: {
“sap.fe.templates”: {}
}
},
“models”: {
“i18n”: {
“type”: “sap.ui.model.resource.ResourceModel”,
“settings”: {
“bundleName”: “com.sap.roleappexplorer.i18n.i18n”
}
},
“”: {
“dataSource”: “mainService”,
“preload”: true,
“settings”: {
“synchronizationMode”: “None”,
“operationMode”: “Server”,
“autoExpandSelect”: true,
“earlyRequests”: true
}
}
},
“routing”: {
“routes”: [
{
“pattern”: “:?query:”,
“name”: “RoleApplicationList”,
“target”: “RoleApplicationList”
}
],
“targets”: {
“RoleApplicationList”: {
“type”: “Component”,
“id”: “RoleApplicationList”,
“name”: “sap.fe.templates.ListReport”,
“options”: {
“settings”: {
“entitySet”: “RoleApplication”,
“variantManagement”: “Page”,
“navigation”: {},
“controlConfiguration”: {
“@com.sap.vocabularies.UI.v1.LineItem”: {
“tableSettings”: {
“type”: “ResponsiveTable”,
“multiSelect”: true,
“enableExport”: true
}
}
}
}
}
}
}
}
}
}

image.png

Step 2.3: Annotation File (annotations/annotation.xml)
This file defines the UI layout — filters, columns, and value helps.

<edmx:Edmx xmlns:edmx=”http://docs.oasis-open.org/odata/ns/edmx”
Version=”4.0″>
<edmx:Reference Uri=”https://sap.github.io/odatavocabularies/vocabularies/UI.xml”>
<edmx:Include Alias=”UI” Namespace=”com.sap.vocabularies.UI.v1″/>
</edmx:Reference>
<edmx:Reference
Uri=”/sap/opu/odata4/sap/zui_role_application/srvd/sap/zui_role_applicati
on/0001/$metadata”>
<edmx:Include Alias=”ZUI_ROLE_APPLICATION”
Namespace=”ZUI_ROLE_APPLICATION”/>
</edmx:Reference>
<edmx:DataServices>
<Schema xmlns=”http://docs.oasis-open.org/odata/ns/edm”>
<!– Selection Fields (Filters) –>
<Annotations
Target=”ZUI_ROLE_APPLICATION.ZI_RoleApplicationType”>
<Annotation Term=”UI.SelectionFields”>
<Collection>
<PropertyPath>uname</PropertyPath>
<PropertyPath>comp_role</PropertyPath>
<PropertyPath>single_role</PropertyPath>
<PropertyPath>reporttype</PropertyPath>
<PropertyPath>disp_name</PropertyPath>
</Collection>
</Annotation>
<!– Line Item (Table Columns) –>
<Annotation Term=”UI.LineItem”>
<Collection>
<Record Type=”UI.DataField”>
<PropertyValue Property=”Value” Path=”uname”/>
<PropertyValue Property=”Label” String=”User ID”/>
</Record>
<Record Type=”UI.DataField”>
<PropertyValue Property=”Value” Path=”comp_role”/>
<PropertyValue Property=”Label” String=”Composite Role”/>
</Record>
<Record Type=”UI.DataField”>
<PropertyValue Property=”Value” Path=”single_role”/>
<PropertyValue Property=”Label” String=”Single Role”/>
</Record>
<Record Type=”UI.DataField”>
<PropertyValue Property=”Value” Path=”reporttype”/>
<PropertyValue Property=”Label” String=”Type”/>
</Record>
<Record Type=”UI.DataField”>
<PropertyValue Property=”Value” Path=”report”/>
<PropertyValue Property=”Label” String=”Report/T-Code”/>
</Record>
<Record Type=”UI.DataField”>
<PropertyValue Property=”Value” Path=”disp_name”/>
<PropertyValue Property=”Label” String=”Application Name”/>
</Record>
<Record Type=”UI.DataFieldWithUrl”>
<PropertyValue Property=”Value” Path=”url”/>
<PropertyValue Property=”Url” Path=”url”/>
<PropertyValue Property=”Label” String=”URL”/>
</Record>
</Collection>
</Annotation>
<!– Header Info –>
<Annotation Term=”UI.HeaderInfo”>
<Record>
<PropertyValue Property=”TypeName” String=”Role Application”/>
<PropertyValue Property=”TypeNamePlural” String=”Role
Applications”/>
<PropertyValue Property=”Title”>
<Record Type=”UI.DataField”>
<PropertyValue Property=”Value” Path=”disp_name”/>
</Record>
</PropertyValue>
</Record>
</Annotation>
</Annotations>
</Schema>
</edmx:DataServices>
</edmx:Edmx>

Step 2.4: i18n.properties

  • i18n.properties: Contains UI-friendly language labels (userId, compositeRole, etc.).

  • Component.js: Standard bootstrap code extending sap.fe.core.AppComponent.

 

 

Step 2.5: Component.js

sap.ui.define([
“sap/fe/core/AppComponent”
], function(AppComponent) {
“use strict”;
return AppComponent.extend(“com.sap.roleappexplorer.Component”, {
metadata: {
manifest: “json”
}
});
});

 

Part 3: Testing the Application

  1. Backend Validation: Test via ADT or Postman targeting endpoint:

    /sap/opu/odata4/sap/zui_role_application/srvd/sap/zui_role_application/0001/RoleApplication?$filter=uname eq 'DEVELOPER'&$top=20

  2. Frontend Deployment: Deploy the application via SAP Business Application Studio or VS Code Fiori Tools, configure the Launchpad tile, and execute user searches, filters, or Excel data exports effortlessly.

Conclusion:

By wrapping legacy role-exploration logic inside a RAP-based OData custom entity and consuming it via Fiori Elements, developers can seamlessly transform static background reports into modern, responsive, enterprise-grade applications. This architectural shift provides auditors and managers with an intuitive interface while keeping business logic clean, maintainable, and completely isolated within the ABAP backend. We encourage you to leverage RAP Custom Entities in your next optimization initiative to elevate traditional SAP GUI transactions into cutting-edge user experiences.

“}]] 

  Read More Technology Blog Posts by Members articles 

#abap

By ali

Leave a Reply