Technology Blog Posts by Members
cancel
Showing results for 
Search instead for 
Did you mean: 
Ajay105
Explorer
842

Introduction

SAP Analytics Cloud (SAC) is widely used by organizations to provide interactive storytelling and track the business KPIs with advanced visualizations. However, companies often need to obtain KPI information in ways other than the SAC user interface, including in custom web apps. Although SAC does not support the direct embedding of KPI tiles into external apps, it does provide REST APIs that allow programmatic access to widget-level data from SAC stories. These APIs can be used to collect and display KPI tile data, including number (value), number state (status), title, and subtitle, in a bespoke user interface.

In this blog, I walk through a detailed, end-to-end implementation that illustrates how to fetch KPI tile data from a SAP Analytics Cloud story using the widgetquery/getWidgetData REST API. The approach uses Python for backend processing and Flask as a lightweight web framework to securely call SAC APIs and output KPI values on a web page.

Configuration of the Project

We may test API access and retrieve KPI tile data using a straightforward Python script before developing the Flask application. This program shows you how to:

  1. Use OAuth 2.0 to authenticate with SAC
  2. Acquire a token of access
  3. Use the SAC widgetquery/getWidgetData RESTAPI.
  4. Show the console’s KPI values
import requests
import webbrowser
import urllib.parse

# ---------------- CONFIG ----------------
TENANT_URL = "https://<your-tenant>.hanacloudservices.cloud.sap"

CLIENT_ID = "<YOUR_CLIENT_ID>"
CLIENT_SECRET = "<YOUR_CLIENT_SECRET>"

AUTHORIZATION_ENDPOINT = "https://<your-tenant>.hana.ondemand.com/oauth/authorize"
TOKEN_ENDPOINT = "https://<your-tenant>.hana.ondemand.com/oauth/token"

REDIRECT_URI = "https://your-app-domain.com/oauth/callback"  # used only to capture code manually

STORY_ID = "<your-storyid>"
WIDGET_IDS = [
    "Chart_1", "Chart_2", "Chart_3",
    "Chart_4", "Chart_5", "Chart_6", "Chart_8"
]

# ---------------- STEP 1: LOGIN ----------------
params = {
    "response_type": "code",
    "client_id": CLIENT_ID,
    "redirect_uri": REDIRECT_URI
}

auth_url = AUTHORIZATION_ENDPOINT + "?" + urllib.parse.urlencode(params)

print("\n Opening browser for SAC login...")
webbrowser.open(auth_url)

code = input("\n Paste the authorization code here: ").strip()

# ---------------- STEP 2: TOKEN ----------------
payload = {
    "grant_type": "authorization_code",
    "code": code,
    "redirect_uri": REDIRECT_URI,
    "client_id": CLIENT_ID,
    "client_secret": CLIENT_SECRET
}

token_resp = requests.post(
    TOKEN_ENDPOINT,
    data=payload,
    headers={"Content-Type": "application/x-www-form-urlencoded"}
)

token_resp.raise_for_status()
access_token = token_resp.json()["access_token"]

print("\n Access token received")

# ---------------- STEP 3: FETCH KPI ----------------
headers = {
    "Authorization": f"Bearer {access_token}",
    "Accept": "application/json"
}

print("\n KPI VALUES\n" + "-" * 40)

for widget_id in WIDGET_IDS:
    url = f"{TENANT_URL}/widgetquery/getWidgetData"
    params = {
        "storyId": STORY_ID,
        "widgetId": widget_id,
        "type": "kpiTile"
    }

    r = requests.get(url, headers=headers, params=params)
    if r.ok:
        data = r.json()
        number = data.get("number", "N/A")
        title = data.get("title", widget_id)
        print(f"{title}: {number}")
    else:
        print(f"{widget_id}:  Error")
print("\n Done")

pythonkpi_output_terminal.png

 How this operate

  1. Login Step: Launches a web browser to obtain the permission code and log into SAC.
  2. Token Step: Provides an access token in exchange for the authorization code.
  3. Fetch KPI Step: Prints the KPI number and title after contacting the SAC REST API for each widget ID.

RESTAPI_Flow_diagram.jpg

 

Code of Application

The full Flask application that is used to retrieve KPI tile data and authenticate with SAP Analytics Cloud is shown below. This code manages widget data fetching, token retrieval, login, and creates an eye-catching KPI dashboard in the browser.

HTML_TEMPLATE = """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SAC KPI Dashboard</title>
<style>
    body {
        font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
        background: #f4f6f8;
        margin: 0; padding: 0;
    }
    h1 { text-align: center; margin-top: 20px; color: #333; }
    .container {
        display: flex;
        flex-wrap: wrap;
        justify-content: center;
        margin: 40px auto;
        max-width: 1200px;
        gap: 20px;
    }
    .card {
        color: #fff;
        width: 250px;
        height: 150px;
        border-radius: 16px;
        box-shadow: 0 10px 20px rgba(0,0,0,0.2);
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        transition: transform 0.3s, box-shadow 0.3s;
        cursor: pointer;
        text-align: center;
        padding: 10px;
    }
    .card:hover {
        transform: translateY(-10px);
        box-shadow: 0 20px 30px rgba(0,0,0,0.3);
    }
    .number {
        font-weight: bold;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
        max-width: 90%;
    }
    .title {
        font-size: 1em;
        margin-top: 10px;
        color: #e0e0e0;
    }
    /* Rainbow colors for cards */
    .rainbow-0 { background: linear-gradient(135deg, #ff6b6b, #f06595); }
    .rainbow-1 { background: linear-gradient(135deg, #feca57, #ff9f43); }
    .rainbow-2 { background: linear-gradient(135deg, #1dd1a1, #10ac84); }
    .rainbow-3 { background: linear-gradient(135deg, #54a0ff, #2e86de); }
    .rainbow-4 { background: linear-gradient(135deg, #5f27cd, #341f97); }
    .rainbow-5 { background: linear-gradient(135deg, #ee5253, #c0392b); }
    .rainbow-6 { background: linear-gradient(135deg, #48dbfb, #00d2d3); }
</style>
<script>
    // Adjust font size based on length
    function adjustFontSize() {
        const numbers = document.querySelectorAll('.number');
        numbers.forEach(num => {
            const length = num.innerText.length;
            if(length <= 5) num.style.fontSize = '2.5em';
            else if(length <= 8) num.style.fontSize = '2em';
            else num.style.fontSize = '1.5em';
        });
    }
    window.onload = adjustFontSize;
</script>
</head>
<body>
<h1>SAP Analytics Cloud - RESTAPI Fetched Sales KPI Dashboard</h1>
<div class="container">
    {% for kpi in kpis %}
    <div class="card rainbow-{{ loop.index0 % 7 }}">
        <div class="number">{{ kpi.number }}</div>
        <div class="title">{{ kpi.title }}</div>
    </div>
    {% endfor %}
</div>
</body>
</html>
"""
from flask import Flask, render_template_string
import requests
import urllib.parse
import webbrowser

# ---------------- CONFIG ----------------
TENANT_URL = "https://yourtenant.hanacloudservices.cloud.sap"
CLIENT_ID = "<YOUR_CLIENT_ID>"
CLIENT_SECRET = "<YOUR_CLIENT_SECRET>"
AUTHORIZATION_ENDPOINT = "https://yourtenant.hana.ondemand.com/oauth/authorize"
TOKEN_ENDPOINT = "https://yourtenant.hana.ondemand.com/oauth/token"
REDIRECT_URI = "https://your-app-domain.com/oauth/callback"

STORY_ID = "<your_storyid>"
WIDGET_IDS = [
    "Chart_1", "Chart_2", "Chart_3",
    "Chart_4", "Chart_5", "Chart_6", "Chart_8"
]

# ---------------- FLASK APP ----------------
app = Flask(__name__)

def get_access_token():
    # Step 1: login manually
    params = {"response_type": "code", "client_id": CLIENT_ID, "redirect_uri": REDIRECT_URI}
    auth_url = AUTHORIZATION_ENDPOINT + "?" + urllib.parse.urlencode(params)
    print("\nOpen this URL in browser to login to SAC:")
    print(auth_url)
    webbrowser.open(auth_url)
    code = input("\nPaste the authorization code here: ").strip()

    # Step 2: get token
    payload = {
        "grant_type": "authorization_code",
        "code": code,
        "redirect_uri": REDIRECT_URI,
        "client_id": CLIENT_ID,
        "client_secret": CLIENT_SECRET
    }
    r = requests.post(TOKEN_ENDPOINT, data=payload, headers={"Content-Type": "application/x-www-form-urlencoded"})
    r.raise_for_status()
    return r.json()["access_token"]

def fetch_kpis(token):
    headers = {"Authorization": f"Bearer {token}", "Accept": "application/json"}
    kpis = []
    for widget_id in WIDGET_IDS:
        url = f"{TENANT_URL}/widgetquery/getWidgetData"
        params = {"storyId": STORY_ID, "widgetId": widget_id, "type": "kpiTile"}
        r = requests.get(url, headers=headers, params=params)
        if r.ok:
            data = r.json()
            kpis.append({
                "title": data.get("title", widget_id),
                "number": data.get("number", "N/A")
            })
        else:
            kpis.append({"title": widget_id, "number": "Error"})
    return kpis
# HTML Code will be written Here
@app.route("/")
def dashboard():
    token = get_access_token()
    kpis = fetch_kpis(token)
    return render_template_string(HTML_TEMPLATE, kpis=kpis)

if __name__ == "__main__":
    app.run(debug=True,port=8000)

webpageoutput_terminal.png

 An explanation of the code

  1. /route logic
    • To manually retrieve an OAuth token, use get_access_token().
    • Uses fetch_kpis() to retrieve KPI tile data.
    • Uses the HTML_TEMPLATE to render all KPIs with rainbow-colored cards.
  2. Managing OAuth (get_access_token)
    • Launches the browser's SAC login.
    • The authorization code is pasted by the user. To call SAC REST APIs, code is exchanged for access tokens.
  3. Data retrieval for widgets (fetch_kpis)
    • Repeats over every WIDGET_IDS.
    • For every widget, the widgetquery/getWidgetData endpoint is called.
    • Gathers the number and title for the display.
  4. Using HTML Templates for UI rendering
    • Flex arrangement is used for tiles.
    • Each KPI card has a rainbow gradient background.
    • For a contemporary appearance, use shadow and hover effects.
    • The length of the integer automatically modifies the font size.

Result/Output

The KPI data is retrieved and shown in a personalized web dashboard after the program has launched and the user has successfully logged in using SAP Analytics Cloud OAuth.

SAP Analytics Cloud Story KPI Tiles

The original KPI tiles as they appear in the SAP Analytics Cloud narrative are depicted in the image below. Business users in SAC are in charge of configuring and maintaining these KPIs.
SAC_Story_pic.png

 

Custom Web Dashboard with KPI Tiles

The widgetquery/getWidgetData REST API is used to retrieve the same KPI values, which are then shown in a specially created web application. Custom Web Dashboard Screenshot
webpage_pic_restapi.png

Important Findings

The KPI numbers in the SAC story and the web dashboard are same.
Every KPI tile shows:

  1. Value or Number 
  2. The title

The website's dashboard uses the following to improve visualization:

  1. Gradient cards with rainbows
  2. Hover animations and shadows
  3. Adaptable design

This proves to the secure consumption and reuse of SAC KPI data outside of the SAC user interface without requiring the duplication of business logic.

Conclusion

In this blog post, we shown a simple yet effective technique for extracting SAP REST APIs are used to tile Analytics Cloud KPI data and display it on a special webpage.

Important findings:
  • SAC KPIs can be consumed using custom dashboards outside of the SAC UI.

  • Python provides an adaptable and lightweight backend for API integration. REST APIs enable KPI tracking in real-time, while front-end. Style increases visibility.

  • Setting up the environment and security is essential for a safe execution.

  • SAC insights can be incorporated into executive dashboards, intranet portals, or external web apps, businesses are able to give users a consolidated view of key performance indicators without having to Launch SAC directly.

1 Comment
NizareDamoumat
Participant
0 Likes

Hi, 
Thank you for the content.
Is it possible to implement KPI respecting Fiori Design System in SAC ?
Regards,