Wednesday, 3 November 2021

A simple dashboard using SAP CPQ’s Custom Table API and python app

This blog post will give you an overview of how you can make use of CPQ’s Custom Table API to build your own custom dashboard using python programming.

SAP ABAP Exam, SAP ABAP Exam Prep, SAP ABAP Prep, SAP ABAP Career, SAP ABAP Certification, SAP ABAP Guides, SAP ABAP Development

Use case:


Consider a simple use case wherein we have a list of of quotes stored in some custom table and the requirement is to create a graphical representation for the total count of quotes for each status. So we will be using a bar and a pie chart for this example.

Prerequisites:


3. SAP CPQ user. I have used the CPQ sandbox tenant for this example

The blog post will be divided into 3 sections:


1. Creating custom table in CPQ
2. Creating a python app to make REST API calls
3. Host app in SAP BTP

Creating a custom table in CPQ

For this example to work we will need to create a custom table which holds the list of quotes along with it’s status. Create a custom table by the name Quote_Status.

Go to Setup ->Product Catalog->Custom Tables and Define a new table

SAP ABAP Exam, SAP ABAP Exam Prep, SAP ABAP Prep, SAP ABAP Career, SAP ABAP Certification, SAP ABAP Guides, SAP ABAP Development

The table structure should be as shown below.

SAP ABAP Exam, SAP ABAP Exam Prep, SAP ABAP Prep, SAP ABAP Career, SAP ABAP Certification, SAP ABAP Guides, SAP ABAP Development

Download the below data set and save it as a .xls or .xlsx file. Then import the same into the newly created Quote_Status table.

QUOTENUMBER,STATUS
1234,Preparing
1235,Preparing
1236,Preparing
1237,Preparing
1238,Preparing
1239,Preparing
1240,Preparing
1241,Preparing
1242,Preparing
1243,Preparing
1244,Preparing
1245,Preparing
1246,Preparing
1247,Preparing
1248,Preparing
1249,Preparing
1250,Preparing
1251,Preparing
1252,Preparing
1253,Preparing
1254,Preparing
1255,Preparing
1256,Preparing
1257,Preparing
1258,Preparing
1259,Preparing
1260,Preparing
1261,Preparing
1262,Preparing
1263,Preparing
1264,Preparing
1265,Preparing
1266,Preparing
1267,Preparing
1268,Preparing
1269,Preparing
1270,Preparing
1271,Preparing
1272,Preparing
1273,Preparing
1274,Preparing
1275,Preparing
1276,Preparing
1277,Preparing
1278,Preparing
1279,Preparing
1280,Preparing
1281,Preparing
1282,Preparing
1283,Preparing
1284,Preparing
1285,Preparing
1286,Preparing
1287,Preparing
1288,Preparing
1289,Preparing
1290,Preparing
1291,Preparing
1292,Preparing
1293,Preparing
1294,Preparing
1295,Preparing
1296,Preparing
1297,Preparing
1298,Preparing
1299,Preparing
1300,Preparing
1301,Preparing
1302,Preparing
1303,Preparing
1304,Preparing
1305,Preparing
1306,Preparing
1307,Preparing
1308,Preparing
1309,Preparing
1310,Preparing
1311,Preparing
1312,Preparing
1313,Preparing
1314,Preparing
1315,Preparing
1316,Preparing
1317,Preparing
1318,Preparing
1319,Preparing
1320,Preparing
1321,Preparing
1322,Preparing
1323,Preparing
1324,Preparing
1325,Preparing
1326,Preparing
1327,Preparing
1328,Preparing
1329,Preparing
1330,Preparing
1331,Preparing
1332,Preparing
1333,Preparing
1334,Preparing
1335,Preparing
1336,Customer Accepted
1337,Customer Accepted
1338,Customer Accepted
1339,Customer Accepted
1340,Customer Accepted
1341,Customer Accepted
1342,Customer Accepted
1343,Customer Accepted
1344,Customer Accepted
1345,Customer Accepted
1346,Customer Accepted
1347,Customer Accepted
1348,Customer Accepted
1349,Customer Accepted
1350,Customer Accepted
1351,Customer Accepted
1352,Customer Accepted
1353,Customer Accepted
1354,Customer Accepted
1355,Customer Accepted
1356,Customer Accepted
1357,Customer Accepted
1358,Customer Accepted
1359,Customer Accepted
1360,Customer Accepted
1361,Customer Accepted
1362,Customer Accepted
1363,Customer Accepted
1364,Customer Accepted
1365,Customer Accepted
1366,Customer Accepted
1367,Customer Accepted
1368,Customer Accepted
1369,Customer Accepted
1370,Customer Accepted
1371,Customer Accepted
1372,Customer Accepted
1373,Customer Accepted
1374,Customer Accepted
1375,Customer Accepted
1376,Customer Accepted
1377,Customer Accepted
1378,Customer Accepted
1379,Expired
1380,Expired
1381,Expired
1382,Expired
1383,Expired
1384,Expired
1385,Expired
1386,Expired
1387,Expired
1388,Expired
1389,Expired
1390,Expired
1391,Expired
1392,Expired
1393,Expired
1394,Expired
1395,Expired
1396,Expired
1397,Expired
1398,Expired
1399,Expired
1400,Expired
1401,Expired
1402,Expired
1403,Expired
1404,Expired
1405,Expired
1406,Expired
1407,Open
1408,Open
1409,Open
1410,Open
1411,Open
1412,Open
1413,Open
1414,Open
1415,Open
1416,Open
1417,Open
1418,Open
1419,Open
1420,Open
1421,Open
1422,Open
1423,Open
1424,Open
1425,Open
1426,Open
1427,Open
1428,Open
1429,Open
1430,Open
1431,Open
1432,Open

Creating a python app to design the UI and make API calls

Once the custom table has been created and loaded with quote information, the next step is to build and deploy the python app.

Below is the python app that will be used make REST API call to CPQ and also to design the UI for the dashboard. Download and save the below code as App.py

from dash_bootstrap_components._components.Card import Card
import pandas as pd
from collections import Counter
from dash import html,dcc
from dash.dependencies import Input, Output
import plotly.express as px
import requests
import json
import dash
import dash_bootstrap_components as dbc
from jupyter_dash import JupyterDash
from flask import request
import os

tenant_url='YOUR_TENANT_URL'
cf_port = os.getenv("PORT")

data = {
    "username":'YOUR_USER_NAME',
    #DO_NOT_CHANGE THE value of GRANT_TYPE. The value has to be 'password' and NOT your password
    "grant_type":"password",
    "password":'YOUR_PASSWORD',
    "domain":'YOUR_DOMAIN'

}
#Get API token using basic auth
def getApiToken():
    base_url=tenant_url+"/basic/api/token"
    #my_headers={"Authorization" : "Bearer "+token}
    r= requests.post(base_url, data=data)
    response= json.loads(r.text)
    #print(response['access_token'])
    access_token=response['access_token']
    # print(jwt_token)
    return access_token

api_token=getApiToken()

#Get JWT token using api token
def getJWT(token):
    base_url=tenant_url+"/api/rd/v1/core/GenerateJWT"
    my_headers={"Authorization" : "Bearer "+token}
    r= requests.post(base_url, headers=my_headers)
    response= json.loads(r.text)
    jwt_token=response['token']
    #print(jwt_token)
    return jwt_token

jwt_token=getJWT(api_token)
#print('Jwt Token is : ' +jwt_token)

#Quote_Status is the name of the custom table that was created in CPQ
#Ensure the table has data
# use the jwt token to get data from custom table
base_url = tenant_url+"/api/custom-table/v1/customTables/Quote_Status/entries"
# #my_headers = {"Authorization" : "Bearer "+jwt_token}
my_headers = {"Authorization" : jwt_token}
response = requests.get(base_url, headers=my_headers)
data = json.loads(response.text)
print(data)

cnt = Counter()
for i in data['value']:
    cnt[i['STATUS']] += 1

df=pd.DataFrame(cnt.items(),columns=['Status', 'Count'])
print(df)
app = JupyterDash(external_stylesheets=[dbc.themes.SLATE])

#----------------------------------APP LAYOUT---------------------------#
app.layout = html.Div([ 
        dbc.Card(
        [
            
        dbc.CardBody(
            [ 
        dbc.Row([
                dbc.Col([html.Label(['Select Chart Type'],style={"font-size":"200 px", "color":"white"}),
        dcc.Dropdown(
            id='my_dropdown',
            options=[
                     {'label': 'Bar Chart', 'value':'Bar Chart' },
                     {'label': 'Pie Chart', 'value': 'Pie Chart'},
                                         
            ],
            value='Bar Chart',
            multi=False,
            clearable=False,
            style={"width": "100%","color":"black"},
            

        )], width=6, align='start', style={"font-size":"200 px","height":"30px"})   ,
    
    ])
        ])]
            
        ,style={"width": "50%", "height":"100px"}, color='info', inverse=True),
    html.Br(),
    html.Div([
        dbc.Card(
        dbc.CardBody([ 
        dbc.Row([
                dbc.Col([
                    dcc.Graph(id='the_graph')
                ], width=6)      
    ])
        ]) 
        ) 
    ])
])

#----------------------------------------App Callback Decorators--------------------------------------#

@app.callback(
    Output(component_id='the_graph', component_property='figure'),
    [Input(component_id='my_dropdown', component_property='value')]
)
def update_output_div(my_dropdown):
    if my_dropdown:
        if my_dropdown == 'Pie Chart':
            piechart=px.pie(
                        df, values=df.Count, names=df.Status, title='Quote Status'
                    ).update_layout(
                        template='ggplot2'
                    )
            return piechart

        elif my_dropdown == 'Bar Chart':
            barchart=px.bar(
                        df, x=df.Count, y=df.Status, title='Quote Status'
                    ).update_layout(
                        template='ggplot2'
                    )
            return barchart
    else:
        return  dash.no_update, dash.no_update

if __name__ == '__main__':
if cf_port is None:
app.run_server(host='0.0.0.0', port=5000, debug=True)
else:
app.run_server(host='0.0.0.0', port=int(cf_port), debug=True)

Please note that you have to provide your SAP CPQ tenant’s url ,domain and credentials(basic auth with rest api user) in the App.py file before pushing this app to SAP BTP.

We get the custom table data using the below API endpoint  in our python code.

/api/custom-table/v1/customTables/Quote_Status/entries

In order to deploy this app to SAP BTP you will need 4 other files along with the App.py file.

1. Procfile
2. manifest.yaml
3. requirements.txt
4. runtime.txt

All of these files will have to be saved in the same directory as the App.py file.

Save the below as Procfile

web: python App.py

Save the below as manifest.yaml

applications:
- name: quote-status-dash
  random-route: true
  memory: 1024M
  command: python App.py
  timeout: 300

Save the below as requirements.txt

pandas
dash
plotly
requests
dash_auth
jupyter_dash
flask
dash_bootstrap_components
 
Save the below as runtime.txt

3.x

Once you’re done you should be able to see all your files in the same folder, something like this as shown below. I’ve saved all my files under the directory CPQ_QuoteStatus_Dropdown. Now that you have all the required files we can now push this to SAP BTP.

SAP ABAP Exam, SAP ABAP Exam Prep, SAP ABAP Prep, SAP ABAP Career, SAP ABAP Certification, SAP ABAP Guides, SAP ABAP Development

Host app in SAP BTP

Go to the directory/folder where you have saved the above files using your command line or code editor(like VS Code) and login to your CF using your email id and password.

cf login

SAP ABAP Exam, SAP ABAP Exam Prep, SAP ABAP Prep, SAP ABAP Career, SAP ABAP Certification, SAP ABAP Guides, SAP ABAP Development

Push the app to SAP BTP using the below command

cf push quote-status-dash

SAP ABAP Exam, SAP ABAP Exam Prep, SAP ABAP Prep, SAP ABAP Career, SAP ABAP Certification, SAP ABAP Guides, SAP ABAP Development

Once successfully hosted, you can login to your SAP BTP cockpit and check if the app is up and running. Once the app is deployed successfully you should get an Application route url.

SAP ABAP Exam, SAP ABAP Exam Prep, SAP ABAP Prep, SAP ABAP Career, SAP ABAP Certification, SAP ABAP Guides, SAP ABAP Development

Click on the above link and you should be able to view your dashboard.

SAP ABAP Exam, SAP ABAP Exam Prep, SAP ABAP Prep, SAP ABAP Career, SAP ABAP Certification, SAP ABAP Guides, SAP ABAP Development

SAP ABAP Exam, SAP ABAP Exam Prep, SAP ABAP Prep, SAP ABAP Career, SAP ABAP Certification, SAP ABAP Guides, SAP ABAP Development

No comments:

Post a Comment