Technical Articles
SAP S/4HANA integration with SAP Conversational AI Chatbot using MS Teams – Part 3
Hi Experts,
Hope all is going well!
Quick Overview
Krishna Chaitanya & Myself has been working on SAP Conversational AI Chatbots for a while and want to share one of the interesting use case in which, we will do an end to end integration from the S/4 Hana system to SAP Conversational AI Chatbot and then access the chatbot from the MS Teams.
This blog post is the 4th part of the blog series SAP SAP S/4HANA integration with SAP Conversational AI Chatbot using MS Teams.
- SAP S/4HANA integration with SAP Conversational AI Chatbot using MS Teams – Part 1 ,
- SAP S/4HANA integration with SAP Conversational AI Chatbot using MS Teams – Part 2 ,
- SAP S/4HANA integration with SAP Conversational AI Chatbot using MS Teams – Part 3 and
- SAP S/4HANA integration with SAP Conversational AI Chatbot using MS Teams – Part 4
Let’s have a quick recap of what we have already achieved in this blog series:
- Activated/created the OData service in the S/4 Hana onPrem system
- Exposed the OData service to the SAP BTP trial account via the SAP Cloud connector.
Since SAP Conservational AI is not offered as a service in SAP BTP trial account hence it can’t consume the OData or the Destination Service directly into the SAP CAI chatbot — Community Version. Seems like a problem, isn’t it??
Now in order to overcome this, Lets create a nodeJS application in our local system and deploy it to the cloud foundry(SAP BTP). Once deployed the nodeJS application should access the destinations which in-turn can provide access to the S/4 Hana OData services. NodeJS application will use the incoming data from the created Destinations and expose a public service API.
Agenda:
- Create Destinations — Using this, OData services can be accessed in SAP BTP applications.
- Create Connectivity and Destination instance — It is a necessary step to access OData services in SAP BTP applications.
- Create a nodeJS application in the local system — to consume the OData service and modify it as per our needs
- Deploy the nodeJS application to the SAP BTP using CLI — once deployed the application can consume the OData service and expose it as a public service API
- Test the service — Now hard part is over, let’s test whether its working
Prerequisite:
- Access to SAP BTP Trial account
- Visual studio should be preinstalled in the local system
- nodeJS should be preinstalled with basic understanding of nodeJS.
- All the necessary plugins for cloud foundry CLI should be preinstalled in the local system. Check the link for more details on the same.
Step 1: Create Destinations
The Destination service lets you find the destination information that is required to access a remote service or system from your Cloud Foundry application. Below fields are relevant while creating the destination:
- Name: Name of the Destination, can be any text.
- Type: Select HTTP
- Description: Short Description about the destination. Can be any text.
- URL: Service URL forthe service to be exposed e.g. For purchase order the value should be http://HOST:PORT/sap/opu/odata/sap/MM_PUR_PO_MAINT_V2_SRV
- Proxy Type: Select OnPremise
- Authentication: Select Basic Authentication
- Location ID: Can be any text.
- User: User ID as in the onPrem system
- Password: Password as in the onPrem system for the above user
Properties should be set as:
- HTML5.DynamicDestination: true
- HTML5.Timeout: 12000
- sap-client: provide onPrem system client
- WebIDEEnabled: true
- WebIDESystem: provide onPrem system ID
- WebIDEUsage: odata_gen,odata_abap,dev_abap
Step 2: Create Connectivity and Destination instance
To consume the Destinations created in step 1 from the nodeJS application, Connectivity and Destination instance needs to be created and should be bound to the nodeJS application.
For the connection to an on-premise system, Connectivity service should be used together with (i.e. in addition to) the Destination service.
To consume the Connectivity service from an application, you must create a service instance and bind it to the application.
Step 3: Create nodeJS App:
Lets get started with the nodeJS application. In this application the connectivity and destination services will be used to fetch the data from the back-end S/4 Hana system. Using the configured destinations the data will be fetched from OData Services activated earlier. Data thus received can be modified as per the requirement and exposed as a public API.
- Create a folder “botNodeJS” in the workspace of the visual studio
- Open the terminal and navigate to the folder botNodeJS and run “npm init”
Click enter to finally create a nodeJS project “botnodejs”
- Go Inside folder “botNodeJS” and create the below files if not already there
- manifest.yml
- package.json — auto created on running npm init
- server.js
- Once done your project structure will look as below
- manifest.yml: Copy the below code in the manifest.yml and save it.
--- applications: - name: botnodejs # command: node server.js memory: 128M buildpacks: - nodejs_buildpack services: - connectivity-bot-01 - destination-bot-01
- package.json: Append the below code in package.json and save it.
{ "dependencies": { "axios": "^0.21.0", "express": "^4.17.1" } }
- server.js: Add the below code to server.js and save it. To understand the logic inside each function.
//Load libraries const axios = require('axios') const express = require('express') var url = require('url') const app = express() var queryParam; //to get data from VCAP_SERVICES:: Applications running in Cloud Foundry gain access //to the bound service instances via credentials stored in an environment variable called VCAP_SERVICES. const VCAP_SERVICES = JSON.parse(process.env.VCAP_SERVICES); const destSrvCred = VCAP_SERVICES.destination[0].credentials; const conSrvCred = VCAP_SERVICES.connectivity[0].credentials; app.listen(process.env.PORT, function () { console.log('botNodeJS application started') }) //to fetch auth token using URL, client and secret values const _fetchJwtToken = async function (oauthUrl, oauthClient, oauthSecret) { return new Promise((resolve, reject) => { //prepare URL const tokenUrl = oauthUrl + '/oauth/token?grant_type=client_credentials&response_type=token' //prepare for the call const config = { headers: { Authorization: "Basic " + Buffer.from(oauthClient + ':' + oauthSecret).toString("base64") } } //backend get call to fetch auth token axios.get(tokenUrl, config) .then(response => { resolve(response.data.access_token) }) .catch(error => { reject(error) }) }) } // Reads Destination configuration based on destinationName, destUri(fetched from VCAP_SERVICES) // and jwtToken(fetched from _fetchJwtToken) . Result will be an object with Destination Configuration info const _readDestinationConfig = async function (destinationName, destUri, jwtToken) { return new Promise((resolve, reject) => { //prepare URL const destSrvUrl = destUri + '/destination-configuration/v1/destinations/' + destinationName // preparation for the call const config = { headers: { Authorization: 'Bearer ' + jwtToken } } //backend get call to fetch destination config axios.get(destSrvUrl, config) .then(response => { resolve(response.data.destinationConfiguration) }) .catch(error => { reject(error) }) }) } //podetails: entity using which application exposes the PO Data app.get('/podetails', async function (req, res) { // call destination service // //fetch detination auth token const destJwtToken = await _fetchJwtToken(destSrvCred.url, destSrvCred.clientid, destSrvCred.clientsecret) //read destination config const destiConfi = await _readDestinationConfig('S4_purchase_order', destSrvCred.uri, destJwtToken) //to fetch query parameter from URL queryParam = url.parse(req.url, true).query; // call onPrem/Remote system using the connectivity service via the Cloud Connector// // fetch connectivity auth token const connJwtToken = await _fetchJwtToken(conSrvCred.token_service_url, conSrvCred.clientid, conSrvCred.clientsecret) try { // method to make a call to onPrem/Remote system, and save the result in variable "result" const result = await _poDetails(conSrvCred.onpremise_proxy_host, conSrvCred.onpremise_proxy_http_port, connJwtToken, destiConfi) res.json(result); } //catch block to handle any errors catch (e) { console.log('Catch an error: ', e) res.json({ "d": { "error": "error" } }) } }) //to make a backend call to the onPrejm/Remote system using connProxyHost, connProxyPort, ConnJwtToken (fetched //using the connectivity service) and destiConfi (destination configuration fetched using destination service) const _poDetails = async function (connProxyHost, connProxyPort, connJwtToken, destiConfi) { return new Promise((resolve, reject) => { // make target URL const targetUrl = destiConfi.URL + "/C_PurchaseOrderTP(PurchaseOrder='" + queryParam.number + "',DraftUUID=guid'00000000-0000-0000-0000-000000000000',IsActiveEntity=true)" //encode user creds fetched from the destination configuration const encodedUser = Buffer.from(destiConfi.User + ':' + destiConfi.Password).toString("base64") //preparation for the onPrem/Remote system call const config = { headers: { Authorization: "Basic " + encodedUser, 'Proxy-Authorization': 'Bearer ' + connJwtToken, 'SAP-Connectivity-SCC-Location_ID': destiConfi.CloudConnectorLocationId }, proxy: { host: connProxyHost, port: connProxyPort } } // get call to the onPrem/Remote system to fetch data axios.get(targetUrl, config) .then(response => { resolve(response.data) }) .catch(error => { reject(error) }) }) } //prdetails: entity using which application exposes the PR Data app.get('/prdetails', async function (req, res) { // call destination service //fetch detination auth token const destJwtToken = await _fetchJwtToken(destSrvCred.url, destSrvCred.clientid, destSrvCred.clientsecret) //read destination config const destiConfi = await _readDestinationConfig('S4_purchase_req', destSrvCred.uri, destJwtToken) //to fetch query parameter from URL queryParam = url.parse(req.url, true).query; // call onPrem/Remote system using the connectivity service via the Cloud Connector// // fetch connectivity auth token const connJwtToken = await _fetchJwtToken(conSrvCred.token_service_url, conSrvCred.clientid, conSrvCred.clientsecret) try { // method to make a call to onPrem/Remote system, and save the result in variable "result" const result = await _prDetails(conSrvCred.onpremise_proxy_host, conSrvCred.onpremise_proxy_http_port, connJwtToken, destiConfi) res.json(result); } //catch block to handle any errors catch (e) { console.log('Catch an error: ', e) res.json({ "d": { "error": "error" } }) } }) //to make a backend call to the onPrem/Remote system using connProxyHost, connProxyPort, ConnJwtToken (fetched //using the connectivity service) and destiConfi (destination configuration fetched using destination service) const _prDetails = async function (connProxyHost, connProxyPort, connJwtToken, destiConfi) { return new Promise((resolve, reject) => { // make target URL const targetUrl = destiConfi.URL + "/C_PurchaseReqnHeader(PurchaseRequisition='" + queryParam.number + "',DraftUUID=guid'00000000-0000-0000-0000-000000000000',IsActiveEntity=true)" //encode user creds fetched from the destination configuration const encodedUser = Buffer.from(destiConfi.User + ':' + destiConfi.Password).toString("base64") //preparation for the onPrem/Remote system call const config = { headers: { Authorization: "Basic " + encodedUser, 'Proxy-Authorization': 'Bearer ' + connJwtToken, 'SAP-Connectivity-SCC-Location_ID': destiConfi.CloudConnectorLocationId }, proxy: { host: connProxyHost, port: connProxyPort } } // get call to the onPrem/Remote system to fetch data axios.get(targetUrl, config) .then(response => { resolve(response.data) }) .catch(error => { reject(error) }) }) } //sodetails: entity using which application exposes the SO Data app.get('/sodetails', async function (req, res) { var destinationNames = 'S4_sales_order'; // call destination service // //fetch detination auth token const destJwtToken = await _fetchJwtToken(destSrvCred.url, destSrvCred.clientid, destSrvCred.clientsecret) //read destination config const destiConfi = await _readDestinationConfig( destinationNames, destSrvCred.uri, destJwtToken) //to fetch query parameter from URL queryParam = url.parse(req.url, true).query; // call onPrem/Remote system using the connectivity service via the Cloud Connector// // fetch connectivity auth token const connJwtToken = await _fetchJwtToken(conSrvCred.token_service_url, conSrvCred.clientid, conSrvCred.clientsecret) try { // method to make a call to onPrem/Remote system, and save the result in variable "result" const result = await _soDetails(conSrvCred.onpremise_proxy_host, conSrvCred.onpremise_proxy_http_port, connJwtToken, destiConfi) res.json(result); } //catch block to handle any errors catch (e) { console.log('Catch an error: ', e) res.json({ "d": { "error": "error" } }) } }) //to make a backend call to the onPrejm/Remote system using connProxyHost, connProxyPort, ConnJwtToken (fetched //using the connectivity service) and destiConfi (destination configuration fetched using destination service) const _soDetails = async function (connProxyHost, connProxyPort, connJwtToken, destiConfi) { return new Promise((resolve, reject) => { // make target URL const targetUrl = destiConfi.URL + "/zabibot01('" + queryParam.number + "')" //encode user creds fetched from the destination configuration const encodedUser = Buffer.from(destiConfi.User + ':' + destiConfi.Password).toString("base64") //preparation for the onPrem/Remote system call const config = { headers: { Authorization: "Basic " + encodedUser, 'Proxy-Authorization': 'Bearer ' + connJwtToken, 'SAP-Connectivity-SCC-Location_ID': destiConfi.CloudConnectorLocationId }, proxy: { host: connProxyHost, port: connProxyPort } } // get call to the onPrem/Remote system to fetch data axios.get(targetUrl, config) .then(response => { resolve(response.data) }) .catch(error => { reject(error) }) }) }
- Open the integrated terminal in visual studio and navigate to the “botnodejs” folder and execute “npm install”
Step 4: Lets deploy the app to the SAP BTP trial account
-
- Open the terminal in the Visual studio and navigate to botNodeJS folder.
- Execute “cf login” and provide the sap btp trial account credentials to login to the SAP BTP.
- Use command “cf push” to deploy the nodeJS app to the sap BTP trial account. Once succesfully deployed, message similar to the below will be displayed on the terminal.
- After successful completion of the deployment, the “botnodejs” app can be seen in the SAP BTP trial account. Make sure the state of the application is started.
- Click on the highlighted link with text botnodejs to display the application overview. In this section check the application routes s
Step 5: Lets test our service API for data
In the next blog post we will create a SAP CAI chatbot, integrate it to the service API created by the nodeJS application so that we can have a working chatbot. Once done, integrate the chatbot with MS Teams so that users can easily access the chatbot during their day to day work.
Feedback :
Thanks for taking your time to go through this article, we hope you liked the content. We would appreciate, if you can spare few minutes to leave us feedback on your experience.
See you in the next part of this blog post 🙂 🙂
Hi Kunj/Krishna,
Thanks! for the detailed blog. I was trying to access 4th part using link given in content, but its not working. Can you check that.
Looking forward to next part.
/Rakesh
please share part4
Hi Kunj Bihari Shuklaand Krishna Chaitanya
Thanks for sharing the informative blog. Can you please share the link to Part-4 as it seems the link shared by you is broken.
thanks
Pankaj
Kunj Bihari Shukla Krishna Chaitanya
Please update the link for Part 4