Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 
SrinivasaMural
Advisor
Advisor
2,716

In the Blog, we will understand how to set up Joule through BTP account, Connect to an On-Premise API, build a capability with the help of Joule and Integrate the bot to your application.

Create a Sample Application

Let’s create a sample RAP based application with a header table and an item table as shown below:

SrinivasaMural_0-1718612205146.png

Order category has a domain with fixed values as seen below:

SrinivasaMural_1-1718612205149.png

SrinivasaMural_2-1718612205152.png

As we Know, to use RAP based application we must create a Root View and a projection view. Let’s create for both Header and item table.

Header Entity

 

 

 

@AbapCatalog.viewEnhancementCategory: [#NONE]
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Header Table'
@Metadata.ignorePropagatedAnnotations: true
@ObjectModel.usageType:{
  serviceQuality: #X,
  sizeCategory: #S,
  dataClass: #MIXED
}
define root view entity ZI_JOULE_HEADER
  as select from zjoule_header
  composition[0..*] of ZI_JOULE_ITEM as _Item
{
  key order_id       as OrderId,
      order_category as OrderCategory,
      created_on     as CreatedOn,
      created_by     as CreatedBy,
      _Item
}

 

 

Item Entity:

 

 

 

@AbapCatalog.viewEnhancementCategory: [#NONE]
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Joule Item'
@Metadata.ignorePropagatedAnnotations: true
@ObjectModel.usageType:{
  serviceQuality: #X,
  sizeCategory: #S,
  dataClass: #MIXED
}
define view entity ZI_JOULE_ITEM
  as select from zjoule_item
  association to parent ZI_JOULE_HEADER as _header on $projection.OrderId = _header.OrderId
{
  key cast(order_id as vbeln_va preserving type ) as OrderId,
  key order_item as OrderItem,
      @Semantics.quantity.unitOfMeasure: 'Unit'
      quantity   as Quantity,
      unit       as Unit,
      @Semantics.amount.currencyCode: 'Currency'
      price      as Price,
      currency   as Currency,
      model      as Model,
      _header
}

 

 

Header Projection:

 

 

 

@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Projection for Header Table'
@Metadata.ignorePropagatedAnnotations: true
@Metadata.allowExtensions: true
@ObjectModel.usageType:{
    serviceQuality: #X,
    sizeCategory: #S,
    dataClass: #MIXED
}

define root view entity ZC_JOULE_HEADER
  provider contract transactional_query
  as projection on ZI_JOULE_HEADER
{
  key OrderId,
      OrderCategory,
      CreatedOn,
      CreatedBy,
      /* Associations */
      _Item : redirected to composition child ZC_JOULE_ITEM
}

 

 

 

Item Projection:

 

 

 

@AbapCatalog.viewEnhancementCategory: [#NONE]
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Projection for Item Table'
@Metadata.ignorePropagatedAnnotations: true
@Metadata.allowExtensions: true
@ObjectModel.usageType:{
  serviceQuality: #X,
  sizeCategory: #S,
  dataClass: #MIXED
}
define view entity ZC_JOULE_ITEM
  as projection on ZI_JOULE_ITEM
{
  key OrderId,
  key OrderItem,
      @Semantics.quantity.unitOfMeasure: 'Unit'
      Quantity,
      Unit,
      @Semantics.amount.currencyCode: 'Currency'
      Price,
      Currency,
      Model,
      /* Associations */
      _header : redirected to parent ZC_JOULE_HEADER
}

 

 

Let’s also create the Metadata extension for both the projection views.

Header table:

 

 

 

@Metadata.layer: #CORE
@UI:{ headerInfo: {
    typeName: 'Order',
    typeNamePlural: 'Orders',
    title: {
        type: #STANDARD,
        value: 'OrderId'
    }
} }
annotate entity ZC_JOULE_HEADER with
{
  .facet: [{
                id: 'Order',
                purpose: #STANDARD,
                position: 10,
                label: 'Order',
                type: #IDENTIFICATION_REFERENCE
              },
              { 
                id: 'Items',
                purpose: #STANDARD,
                position: 20,
                label: 'Booking',
                type: #LINEITEM_REFERENCE,
                targetElement: '_Item'
             }]   
   
  :{ lineItem: [{position: 10,importance: #HIGH }],
           identification: [{position: 10 }],
           selectionField: [{position: 10 }] }
  OrderId;
  :{ lineItem: [{position: 20,importance: #HIGH }],
        identification: [{position: 20 }],selectionField: [{position: 20 }] }  
  OrderCategory;
  :{ lineItem: [{position: 30,importance: #MEDIUM }],
         identification: [{position: 30 }],selectionField: [{position: 30 }] }  
  CreatedOn;
  :{ lineItem: [{position: 40,importance: #MEDIUM }],
       identification: [{position: 40 }] }  
  CreatedBy;
}

 

 

 

Item Table:

 

 

 

@Metadata.layer: #CORE
@UI.headerInfo: {
    typeName: 'Order',
    typeNamePlural: 'Orders',
    title: {
        type: #STANDARD,
        label: 'Order',
        value: 'OrderId'
    }
}
annotate entity ZC_JOULE_ITEM with
{
  .facet: [{id: 'Order',
                purpose: #STANDARD,
                type: #IDENTIFICATION_REFERENCE,
                label: 'Order',
                position: 10 }]
                
  :{lineItem: [{  position: 10,importance: #HIGH }],
                identification: [{position: 10 }]
  }
  OrderId;
  :{lineItem: [{  position: 20,importance: #HIGH }],
                identification: [{position: 20 }]
  }  
  OrderItem;
  :{lineItem: [{  position: 30,importance: #HIGH }],
                identification: [{position: 30 }]
  }  
  Quantity;
  :{lineItem: [{  position: 40,importance: #HIGH }],
                identification: [{position: 40 }]
  }  
  Unit;
  :{lineItem: [{  position: 50,importance: #HIGH }],
                identification: [{position: 50 }]
  }  
  Price;
  :{lineItem: [{  position: 60,importance: #HIGH }],
                identification: [{position: 60 }]
  }  
  Currency;
  :{lineItem: [{  position: 70,importance: #HIGH }],
                identification: [{position: 70 }] }  
  Model;
}

 

 

Once we create Entities, we will create a behaviour definition with managed scenario. As we are using ODATA V4, also create a draft table for the same. We will not be implementing the class since it’s a managed scenario.

 

 

managed implementation in class zbp_i_joule_header unique;
strict ( 2 );
with draft;

with privileged mode disabling NoCheckWhenPrivileged;
define authorization context NoCheckWhenPrivileged { 'O_OIU_TXN'; }
define own authorization context { 'O_OIU_TXN'; }

define behavior for ZI_JOULE_HEADER alias Order
persistent table zjoule_header
draft table zjoule_draft_hd
lock master total etag CreatedOn
authorization master ( instance )
etag master CreatedOn
{
  create;
  update;
  delete;
  association _Item { create; with draft; }
  draft action Edit;
  draft action Discard;
  draft action Resume;
  draft action Activate optimized;
  draft determine action Prepare { }
  field ( mandatory ) OrderCategory;
  mapping for zjoule_header
    {
      OrderId       = order_id;
      OrderCategory = order_category;
      CreatedOn     = created_on;
      CreatedBy     = created_by;
    }
}

define behavior for ZI_JOULE_ITEM alias Items
persistent table zjoule_item
draft table zjoule_draft_it
lock dependent by _header
authorization dependent by _header
//etag master <field_name>
{
  update;
  delete;
  field ( readonly ) OrderId;
  field(mandatory) Currency, Price, Quantity, Unit, Model;
  association _header { with draft; }
  mapping for zjoule_item
    {
      OrderId   = order_id;
      OrderItem = order_item;
      Currency  = currency;
      Price     = price;
      Quantity  = quantity;
      Unit      = unit;
      Model     = model;
    }
}

 

 

Create a Service Definition and a Service binding, Register the service to test.

 

 

 

@EndUserText.label: 'Service Definitions'
define service ZU_JOULEHEADER {
  expose ZC_JOULE_HEADER as Orders;
  expose ZC_JOULE_ITEM   as Items;
}

 

 

 

SrinivasaMural_7-1718612205166.png

I have created some sample data to test the application.

SrinivasaMural_8-1718612205170.png

Connect your BTP account to Access Joule services

You must subscribe to SAP DAS and then create an instance of SAP DAS. You must be a global account admin to perform those tasks. Then, add the entitlement SAP DAS for your global account in the SAP BTP control center. Kindly refer to the below url for Global account setup

https://help.sap.com/docs/joule/serviceguide/prerequisites?locale=en-US

In the SAP BTP cockpit, create a subaccount. Open your sub account and establish Trust configurations( Preferably create a Custom IDP ). Then, go to service marketplace, look for SAP DAS and open the same. Based on the entitlements, you will see the development plan under Application plans. Click on the actions menu and select create. Now, you have subscribed to SAP DAS.

To develop capabilities with Joule, you need to assign roles to your user. Refer the below URL

https://help.sap.com/docs/joule/service-guide/assign-roles?locale=en-US 

Create a Service Instance and Service Key

Inside your subaccount, go to the service marketplace, look for the app SAP Digital Assistant and click to open. After adding your entitlements you will see designer plan under service plans. Click on the actions to create the instance.

Select the SAP Digital Assistant that you created, click on the actions menu to choose service key. Choose a name for your service and proceed.

Installing SAPDAS CLI:

Prerequisites - You should have installed Node.js( minimum version 18 ). Open the terminal and run the following commands to:

Install sapdas

 

 

 

npm install -g sapdas-cli

 

 

 

Get a version

 

 

 

npm install -g sapdas-cli@<version>

 

 

Replace <version> with a version number.

Creating a Capability with Joule for the custom RAP application

First we must connect to our On-premise system. Go to your cloud connectors and maintain the hosts( Virtual and Internal) for your system through which API calls will be made.

Refer the below URL to maintain the configuration in your cloud connector.

https://help.sap.com/docs/SAP_ENTITLEMENT_MANAGEMENT/75ca22b17bc74bcaa6f768d06fd89437/63c78fa61a9743... 

Once cloud connector is connected, you can see the same appears in your sub account as shown below:

 

SrinivasaMural_2-1718632154102.png

Next go to Destination-> Create Destination. Give a name, URL( which is the host from your cloud connector), Proxy type( On-Premise ), Authentication( Basic ), User and password for the system. Also do mention the sap-client in the additional properties. Then click on check connection to see if the connection is working.

SrinivasaMural_3-1718632469565.png

 

We can use VS code for our development. The below chart shows how the hierarchy is designed for our capability.

SrinivasaMural_0-1718636070067.png

There are 2 Main folders namely

Dataset which contains Entities and Intents,

Dialog which contains Functions and Nodes.

Entities:

These are individual values which are searched for when a question is asked by the user.

 

 

fuzzy_matching: true
values:
  - value: 'Iphone'
  - value: 'Samsung'
  - value: 'One Plus'

 

 

Intents:

These are example questions that user may ask the bot

 

 

description: Phone
examples:
  - Give me the count of [Iphone](Models) bought?
  - Can you tell me how many [Iphone](Models) were bought?
  - What is the number of [Iphone](Models) bought?

 

 

Functions:

This is where the API calls are made to connect to the system to get the response for the question.

 

 

parameters:
  - name: model_input

action_groups:
  - actions:
      - type: api-request
        method: GET
        system_alias: APIService
        path: "/sap/opu/odata4/sap/zu_jouleheader/srvd/sap/zu_jouleheader/0001/Items?$filter=Model%20eq%20%27<? model_input.urlEncode() ?>%27"
        result_variable: count_result

result:
  success: <? count_result.status_code == 200 ?>
  quantity:  <? count_result.body.value[0].Quantity ?>

 

 

Nodes:

This is where the success/Error messages are shown to the user.

 

 

title: Display API Answer
type: node
condition: $count_result.success == true
response:
  messages:
    - type: text
      content: "Total Phones bought for the model <? $model_name ?> is <? $count_result.quantity ?>"

finally:
  behavior: wait
title: Display API Error
type: node
condition: anything_else
response:
  messages:
    - type: text
      content: An error occured when fetching the information for <? $count_result ?>. It could be API Timeout

finally:
  behavior: wait
type: node
title: Find Model Counts
condition: '#Modeld'
slot_filling:
  slots:
    - check_for: "@Models"
      save_as: 
        variable: "model_name"
        value: "@Models"
      not_present:
        prompts:
          - type: text
            content: "For Which Model of Phone would you like to know the count?"

dialog_functions:
  - name: find_totalphones_call
    parameters: 
      - name: model_input
        value: "$model_name"
    result_variable: count_result

finally:
  behavior: skip

 

 

 Dialog tree: 

 

 

- find_totalphones_node:
  - display_result: []
  - display_error: []

 

 

 Capability:

 

 

schema_version: 1.4.1

metadata:
  namespace: namespace
  name: Model_capability
  version: 1.0.0
  display_name: Model_capability
  description: Model Capability

system_aliases:
  APIService:
    destination: Orders

 

 

A file with the name da.sapdas.yaml must be available to complete the capability.

 

 

schema_version: 1.0.0
name: integration
capabilities:
  - type: local
    folder: ./Phone

conversational_search: # optional: enable conversational search feature
  enabled: true
  product_filter: # products the search will be perfomed on (SAP Help Product IDs)
    - SAP_S4HANA_CLOUD  

 

 

We can now deploy the application with the following commands.

 

 

sapdas login --default-idp --apiurl <JOULE Subscription URL> --authurl <Auth URL from Joule Service key>

 

 

We can find the Joule Subscription URL and Auth URL along with Client ID, Service key from our BTP subaccount. One you click enter, we will be redirected to IDP where a temporary auth code is generated. Copy the code and paste to login.

Compile, Deploy and Integrate the bot to your application:

Run the below commands to compile and deploy the capability:

 

 

sapdas compile
sapdas deploy -n order

 

 

Here, order is the bot name which will be used in our application.

We have to inject a piece of code for the RAP application. In our test folder of the application, find the file flpSandbox.html and insert the below code.

 

 

<script
    src="<your_das_tenant_URL>/resources/public/webclient/bootstrap.js"
    data-bot-name="botName"
    data-expander-type="DEFAULT"
    data-expander-preferences="JTdCJTIyYWNjZW50Q29sb3IlMjIlM0ElMjIlMjNFMDVBNDclMjIlMkMlMjJiYWNrZ3JvdW5kQ29sb3IlMjIlM0ElMjIlMjNGRkZGRkYlMjIlMkMlMjJjb21wbGVtZW50YXJ5Q29sb3IlMjIlM0ElMjIlMjNGRkZGRkYlMjIlMkMlMjJvbmJvYXJkaW5nTWVzc2FnZSUyMiUzQSUyMkNvbWUlMjBzcGVhayUyMHRvJTIwbWUhJTIyJTJDJTIyZXhwYW5kZXJUaXRsZSUyMiUzQSUyMiUyMiUyQyUyMmV4cGFuZGVyTG9nbyUyMiUzQSUyMkNVWF9BdmF0YXIuc3ZnJTIyJTJDJTIydGhlbWUlMjIlM0ElMjJzYXBfaG9yaXpvbiUyMiU3RA=="
</script>

 

 

Now we can launch our application to ask some question to Joule.

SrinivasaMural_1-1718638252545.png

These are the responses for the intents that we provided for our capability.

SrinivasaMural_3-1718638463133.png

Any number of capabilities can be built to obtain answers based on our API calls. Consequently even post calls/Navigations to other apps etc can also be built in a similar manner.

Thanks,

Srinivasa Muralidhar

 

 

  • SAP Managed Tags:
4 Comments