AI
An explain-this-data button over a table - a side panel summarises the selected rows (counts, totals, extremes, outliers) through a deterministic provider that one method turns into an LLM call.
Z2UI5_CL_SMP_APP_541
Learn
AI
UI5 1.71
frontend action
The facts
- Repository
- abap2UI5/samples
- Class
Z2UI5_CL_SMP_APP_541- Source file
z2ui5_cl_smp_app_541.clas.abap↗- Category
- AI
- Learning path
- Talk to the user
- Minimum UI5
- 1.71 — the floor abap2UI5 holds its samples to
- In the playground
- runs in the browser, with no system and nothing installed
- Marked
(A)— Performs a frontend action: client->follow_up_action( ), or drag and drop
Keywords: ai, llm, assistant, chat, explain, summary, insight, outlier, table, selection, dynamicsidecontent, start_timer
Controls it builds
- sap.m.Button
- sap.m.Column
- sap.m.ColumnListItem
- sap.m.List
- sap.m.MessageStrip
- sap.m.ObjectNumber
- sap.m.ObjectStatus
- sap.m.OverflowToolbar
- sap.m.Page
- sap.m.Shell
- sap.m.StandardListItem
- sap.m.Table
- sap.m.Text
- sap.m.Title
- sap.m.Toolbar
- sap.m.ToolbarSpacer
- sap.m.VBox
- sap.ui.core.mvc.View
- sap.ui.layout.DynamicSideContent
Read out of the builder chain by the abap2UI5 linter, not from the category this sample is filed under — which is what makes “which samples build one of these” a question the catalogue can answer at all.
Libraries
Run it here
The sample running, in this page — the app on its own, with no editor over it.
Switch to Playground with this code
Nothing is installed and nothing is sent anywhere: the ABAP is compiled to JavaScript in this browser and runs against abap2UI5 itself.
The ABAP
z2ui5_cl_smp_app_541.clas.abap — 384 lines
Click a number to link to a line — shift-click for a range.
Read it on GitHub ↗
" @keywords ai llm assistant chat explain summary insight outlier table selection dynamicsidecontent start_timer
" @summary An explain-this-data button over a table - a side panel summarises the selected rows (counts, totals, extremes, outliers) through a deterministic provider that one method turns into an LLM call.
CLASS z2ui5_cl_smp_app_541 DEFINITION PUBLIC.
PUBLIC SECTION.
INTERFACES z2ui5_if_app.
TYPES:
BEGIN OF ty_s_order,
selected TYPE abap_bool,
id TYPE string,
customer TYPE string,
region TYPE string,
status TYPE string,
state TYPE string,
amount TYPE i,
END OF ty_s_order.
TYPES ty_t_orders TYPE STANDARD TABLE OF ty_s_order WITH EMPTY KEY.
TYPES:
BEGIN OF ty_s_finding,
title TYPE string,
description TYPE string,
icon TYPE string,
END OF ty_s_finding.
TYPES ty_t_findings TYPE STANDARD TABLE OF ty_s_finding WITH EMPTY KEY.
DATA t_orders TYPE ty_t_orders.
DATA t_findings TYPE ty_t_findings.
DATA headline TYPE string.
DATA panel_visible TYPE abap_bool.
DATA busy TYPE abap_bool.
PROTECTED SECTION.
DATA client TYPE REF TO z2ui5_if_client.
METHODS on_init.
METHODS on_event.
METHODS on_event_summarize.
METHODS view_display.
"! The provider seam: the rows in, the explanation out.
"! @parameter t_rows | the rows to explain
"! @parameter result | one finding per line of the explanation
METHODS get_summary
IMPORTING
t_rows TYPE ty_t_orders
RETURNING
VALUE(result) TYPE ty_t_findings.
PRIVATE SECTION.
ENDCLASS.
CLASS z2ui5_cl_smp_app_541 IMPLEMENTATION.
METHOD z2ui5_if_app~main.
me->client = client.
IF client->check_on_init( ).
on_init( ).
ELSEIF client->check_on_navigated( ).
view_display( ).
ELSEIF client->check_on_event( ).
on_event( ).
ENDIF.
ENDMETHOD.
METHOD on_init.
t_orders = VALUE #(
( id = `4711` customer = `Bike Corner` region = `North` status = `Delivered` amount = 1200 )
( id = `4712` customer = `City Cycles` region = `South` status = `Open` amount = 860 )
( id = `4713` customer = `Mountain Outfit` region = `North` status = `Overdue` amount = 2400 )
( id = `4714` customer = `Velo Point` region = `West` status = `Delivered` amount = 640 )
( id = `4715` customer = `Bike Corner` region = `North` status = `Open` amount = 1500 )
( id = `4716` customer = `Urban Wheels` region = `East` status = `Delivered` amount = 9800 )
( id = `4717` customer = `City Cycles` region = `South` status = `Overdue` amount = 720 )
( id = `4718` customer = `Trail Masters` region = `West` status = `Delivered` amount = 1900 )
( id = `4719` customer = `Velo Point` region = `West` status = `Open` amount = 1100 )
( id = `4720` customer = `Mountain Outfit` region = `North` status = `Delivered` amount = 1350 ) ).
LOOP AT t_orders REFERENCE INTO DATA(order).
order->state = SWITCH #( order->status WHEN `Delivered` THEN `Success`
WHEN `Overdue` THEN `Error`
ELSE `Warning` ).
ENDLOOP.
view_display( ).
ENDMETHOD.
METHOD on_event.
CASE client->get_event( ).
WHEN `EXPLAIN`.
" open the panel busy at once; the timer asks for the explanation in
" a second roundtrip - with a real LLM that is where the seconds go.
" The 800 ms only stand in for that time: drop them to 0 then.
panel_visible = abap_true.
busy = abap_true.
headline = `Reading the rows...`.
t_findings = VALUE #( ).
client->follow_up_action( val = z2ui5_if_client=>cs_event-start_timer
t_arg = VALUE #( ( `SUMMARIZE` ) ( `800` ) ) ).
WHEN `SUMMARIZE`.
on_event_summarize( ).
WHEN `CLOSE`.
panel_visible = abap_false.
ENDCASE.
ENDMETHOD.
METHOD on_event_summarize.
" the selection was written back before this handler runs - explain the
" selected rows, or all of them when nothing is selected
DATA(t_scope) = t_orders.
DELETE t_scope WHERE selected = abap_false.
IF t_scope IS INITIAL.
t_scope = t_orders.
headline = |All { lines( t_scope ) } orders. Select rows to explain only those.|.
ELSE.
headline = |The { lines( t_scope ) } selected orders.|.
ENDIF.
t_findings = get_summary( t_scope ).
busy = abap_false.
ENDMETHOD.
METHOD get_summary.
" ---------------------------------------------------------------------
" Plug a real LLM in HERE, and nowhere else: serialize t_rows to JSON,
" send it with an instruction such as "explain this data in at most five
" short findings", and map the reply to result. The button, the busy
" panel and the selection handling stay as they are.
"
" Calling an endpoint needs an HTTP destination or a communication
" arrangement on the system, which a sample in this repository cannot
" bring along. The same idea against a real model - a table whose rows
" go to the model as context, its summary back into a panel - is
" Z2UI5_CL_SMPS_APP_015 in abap2UI5/samples-stack, package 10 (AI / LLM):
" https://github.com/abap2UI5/samples-stack/tree/main/src/10
"
" Until then this deterministic provider explains the rows: totals,
" extremes, the strongest region, the overdue share and the outliers.
" ---------------------------------------------------------------------
TYPES:
BEGIN OF ty_s_region,
region TYPE string,
amount TYPE i,
END OF ty_s_region.
DATA t_regions TYPE SORTED TABLE OF ty_s_region WITH UNIQUE KEY region.
DATA total TYPE i.
DATA overdue_count TYPE i.
DATA overdue_amount TYPE i.
DATA strongest_share TYPE p LENGTH 16 DECIMALS 0.
DATA outliers TYPE string.
IF t_rows IS INITIAL.
result = VALUE #( ( title = `Nothing to explain` description = `The table holds no rows.` icon = `sap-icon://hint` ) ).
RETURN.
ENDIF.
DATA(largest) = t_rows[ 1 ].
DATA(smallest) = t_rows[ 1 ].
LOOP AT t_rows INTO DATA(row).
total = total + row-amount.
IF row-amount > largest-amount.
largest = row.
ENDIF.
IF row-amount < smallest-amount.
smallest = row.
ENDIF.
IF row-status = `Overdue`.
overdue_count = overdue_count + 1.
overdue_amount = overdue_amount + row-amount.
ENDIF.
READ TABLE t_regions ASSIGNING FIELD-SYMBOL(<region>) WITH TABLE KEY region = row-region.
IF sy-subrc = 0.
<region>-amount = <region>-amount + row-amount.
ELSE.
INSERT VALUE #( region = row-region amount = row-amount ) INTO TABLE t_regions.
ENDIF.
ENDLOOP.
DATA(count) = lines( t_rows ).
DATA(average) = total / count.
DATA(strongest) = t_regions[ 1 ].
LOOP AT t_regions INTO DATA(region).
IF region-amount > strongest-amount.
strongest = region.
ENDIF.
ENDLOOP.
" a variable, not arithmetic inside the template: the transpiled runtime
" (playground, node backend) formats such an expression as a float. Packed,
" because amount * 100 leaves the range of TYPE i long before the amount
" does, and one packed operand makes the whole expression packed. Rows
" that all carry 0 EUR have no share to speak of - and no division.
IF total <> 0.
strongest_share = strongest-amount.
strongest_share = strongest_share * 100 / total.
ENDIF.
DATA(threshold) = 2 * average.
LOOP AT t_rows INTO row.
IF row-amount > threshold.
outliers = COND #( WHEN outliers IS INITIAL THEN row-id ELSE |{ outliers }, { row-id }| ).
ENDIF.
ENDLOOP.
result = VALUE #(
( title = `Volume`
description = |{ count } orders worth { total } EUR, { average } EUR per order on average.|
icon = `sap-icon://sales-order` )
( title = `Extremes`
description = |Largest: { largest-id } of { largest-customer } with { largest-amount } EUR. | &&
|Smallest: { smallest-id } of { smallest-customer } with { smallest-amount } EUR.|
icon = `sap-icon://trend-up` )
( title = `Regions`
description = |{ strongest-region } brings the most: { strongest-amount } EUR, | &&
|{ strongest_share }% of the total across { lines( t_regions ) } regions.|
icon = `sap-icon://map` )
( title = `Overdue`
description = COND #( WHEN overdue_count = 0
THEN `No order is overdue.`
ELSE |{ overdue_count } of { count } orders are overdue, { overdue_amount } EUR in total.| )
icon = COND #( WHEN overdue_count = 0 THEN `sap-icon://accept` ELSE `sap-icon://warning` ) )
( title = `Outliers`
description = COND #( WHEN outliers IS INITIAL
THEN `No order is more than twice the average.`
ELSE |More than twice the average: { outliers }. Worth a second look.| )
icon = `sap-icon://alert` ) ).
ENDMETHOD.
METHOD view_display.
DATA(view) = z2ui5_cl_ui5_view_builder=>factory(
)->ele( n = `View` ns = `mvc`
)->a( n = `displayBlock` v = `true`
)->a( n = `height` v = `100%`
)->a( n = `xmlns` v = `sap.m`
)->a( n = `xmlns:mvc` v = `sap.ui.core.mvc`
)->a( n = `xmlns:layout` v = `sap.ui.layout` ).
DATA(page) = view->ele( `Shell`
)->ele( `Page`
)->a( n = `title` v = `abap2UI5 - AI - Explain This Data in a DynamicSideContent Panel`
)->a( n = `showNavButton` b = client->check_app_prev_stack( )
)->a( n = `navButtonPress` v = client->_event_nav_app_leave( ) ).
page->tag( `MessageStrip`
)->a( n = `text` v = `Explain This Data opens a panel beside the table and fills it with a short explanation ` &&
`of the selected rows, or of all of them. One method turns rows into findings - here a deterministic ` &&
`summary, in a real app the call to your language model.`
)->a( n = `type` v = `Information`
)->a( n = `showIcon` b = abap_true
)->a( n = `class` v = `sapUiSmallMargin` ).
" on a narrow screen the panel falls below the table instead of hiding
DATA(side_content) = page->ele( n = `DynamicSideContent` ns = `layout`
)->a( n = `showSideContent` v = client->_bind( panel_visible )
)->a( n = `sideContentVisibility` v = `AlwaysShow`
)->a( n = `sideContentFallDown` v = `BelowM`
)->a( n = `containerQuery` b = abap_true ).
DATA(tab) = side_content->ele( n = `mainContent` ns = `layout`
)->ele( `Table`
)->a( n = `items` v = client->_bind( t_orders )
)->a( n = `mode` v = `MultiSelect` ).
tab->ele( `headerToolbar`
)->ele( `OverflowToolbar`
)->tag( `Title`
)->a( n = `text` v = `Sales Orders`
)->tag( `ToolbarSpacer`
)->tag( `Button`
)->a( n = `text` v = `Explain This Data`
)->a( n = `icon` v = `sap-icon://hint`
)->a( n = `type` v = `Emphasized`
)->a( n = `press` v = client->_event( `EXPLAIN` ) ).
DATA(columns) = tab->ele( `columns` ).
columns->ele( `Column`
)->tag( `Text`
)->a( n = `text` v = `Order` ).
columns->ele( `Column`
)->tag( `Text`
)->a( n = `text` v = `Customer` ).
columns->ele( `Column`
)->a( n = `minScreenWidth` v = `Tablet`
)->a( n = `demandPopin` b = abap_true
)->tag( `Text`
)->a( n = `text` v = `Region` ).
columns->ele( `Column`
)->a( n = `minScreenWidth` v = `Tablet`
)->a( n = `demandPopin` b = abap_true
)->tag( `Text`
)->a( n = `text` v = `Status` ).
columns->ele( `Column`
)->a( n = `hAlign` v = `End`
)->tag( `Text`
)->a( n = `text` v = `Amount` ).
DATA(cells) = tab->ele( `items`
)->ele( `ColumnListItem`
)->a( n = `selected` v = `{SELECTED}`
)->ele( `cells` ).
cells->tag( `Text`
)->a( n = `text` v = `{ID}` ).
cells->tag( `Text`
)->a( n = `text` v = `{CUSTOMER}` ).
cells->tag( `Text`
)->a( n = `text` v = `{REGION}` ).
cells->tag( `ObjectStatus`
)->a( n = `text` v = `{STATUS}`
)->a( n = `state` v = `{STATE}` ).
cells->tag( `ObjectNumber`
)->a( n = `number` v = `{AMOUNT}`
)->a( n = `unit` v = `EUR` ).
DATA(panel) = side_content->ele( n = `sideContent` ns = `layout`
)->ele( `VBox`
)->a( n = `busy` v = client->_bind( busy )
)->a( n = `busyIndicatorDelay` v = `0`
)->a( n = `class` v = `sapUiSmallMarginBeginEnd` ).
panel->ele( `Toolbar`
)->a( n = `style` v = `Clear`
)->tag( `Title`
)->a( n = `text` v = `Explanation`
)->tag( `ToolbarSpacer`
)->tag( `Button`
)->a( n = `icon` v = `sap-icon://decline`
)->a( n = `tooltip` v = `Close`
)->a( n = `type` v = `Transparent`
)->a( n = `press` v = client->_event( `CLOSE` ) ).
panel->tag( `Text`
)->a( n = `text` v = client->_bind( headline )
)->a( n = `class` v = `sapUiTinyMarginBottom` ).
panel->ele( `List`
)->a( n = `items` v = client->_bind( t_findings )
)->a( n = `showSeparators` v = `Inner`
)->a( n = `noDataText` v = `The explanation is on its way.`
)->ele( `items`
)->tag( `StandardListItem`
)->a( n = `title` v = `{TITLE}`
)->a( n = `description` v = `{DESCRIPTION}`
)->a( n = `icon` v = `{ICON}`
)->a( n = `wrapping` b = abap_true ).
panel->tag( `Text`
)->a( n = `text` v = `Written by the built-in rule-based provider - no language model involved.`
)->a( n = `class` v = `sapUiTinyMarginTop` ).
client->view_display( view->stringify( ) ).
ENDMETHOD.
ENDCLASS.
More in AI
- AI — Chat Assistant with FeedInput and FeedListItem
On this page