Do not input private or sensitive data. View Qlik Privacy & Cookie Policy.
Skip to main content

Announcements
Save $650 on Qlik Connect, Dec 1 - 7, our lowest price of the year. Register with code CYBERWEEK: Register
cancel
Showing results for 
Search instead for 
Did you mean: 
Nikhil_Qlik1
Partner - Contributor
Partner - Contributor

Fetch Object Data from Qlik Cloud Dashboard

Hello,

Following is the script which we are using to fetch the object metadata from qlik cloud dashboard. We want to list out the dimension, measures and their underlying definitions. However when we run the script we do not the expression used for the measure. We only get label of the measure not the underlying expression.

 

Example -

Let's suppose there is a bar chart on qlik cloud dashboard which has month as dimension and sum(sales) as measure. We want the output like - 
Dimension - Month, Expression - Sum(Sales)

Script - 
import websocket
import json
import ssl
import os
from datetime import datetime
# Assuming config.py exists and contains TENANT_URL, API_KEY, APP_ID
from config import TENANT_URL, API_KEY, APP_ID

TENANT = TENANT_URL.replace("https://", "").rstrip("/")
WS_URL = f"wss://{TENANT}/app/{APP_ID}"

headers = {
    "Authorization": f"Bearer {API_KEY}"
}

state = {
    "doc_handle": None,
    "infos": [],
    "current_index": 0,
    "object_handles": {},
    "output_data": []
}

def on_message(ws, message):
    response = json.loads(message)
    print(response)

    if response.get("id") == 1:
        state["doc_handle"] = response["result"]["qReturn"]["qHandle"]
        ws.send(json.dumps({
            "jsonrpc": "2.0",
            "id": 2,
            "handle": state["doc_handle"],
            "method": "GetAllInfos",
            "params": {}
        }))

    elif response.get("id") == 2:
        state["infos"] = response["result"]["qInfos"]
        print(f"\n[✓] Found {len(state['infos'])} objects in the app\n")
        request_next_object(ws)

    elif response.get("id", 0) >= 1000 and "qReturn" in response.get("result", {}):
        qid = response["id"] - 1000
        obj_handle = response["result"]["qReturn"]["qHandle"]
        state["object_handles"][qid] = obj_handle
        ws.send(json.dumps({
            "jsonrpc": "2.0",
            "id": 2000 + qid,
            "handle": obj_handle,
            "method": "GetLayout",
            "params": {}
        }))

    elif response.get("id", 0) >= 2000:
        qid = response["id"] - 2000
        layout = response.get("result", {}).get("qLayout", {})
        metadata = extract_metadata(state["infos"][qid], layout)
        print_metadata(metadata)
        state["output_data"].append(metadata)
        state["current_index"] += 1
        request_next_object(ws)

def request_next_object(ws):
    if state["current_index"] < len(state["infos"]):
        info = state["infos"][state["current_index"]]
        qid = state["current_index"]
        obj_id = info["qId"]
        ws.send(json.dumps({
            "jsonrpc": "2.0",
            "id": 1000 + qid,
            "handle": state["doc_handle"],
            "method": "GetObject",
            "params": {
                "qId": obj_id
            }
        }))
    else:
        print("\n Completed extracting all metadata.")
        save_output_to_file()
        ws.close()

def extract_metadata(info, layout):
    qid = info["qId"]
    qtype = info["qType"]
    title = layout.get("title") or layout.get("qMeta", {}).get("title", "N/A")

    metadata = {
        "id": qid,
        "type": qtype,
        "title": title,
        "dimensions": [],
        "measures": [],
        "expressions": [] # Changed to a list to hold multiple expressions
    }

    if "qHyperCube" in layout:
        dims = layout["qHyperCube"].get("qDimensionInfo", [])
        meas = layout["qHyperCube"].get("qMeasureInfo", [])

        # Extract dimensions and their expressions
        for d in dims:
            metadata["dimensions"].append(d.get("qFallbackTitle"))
            if d.get("qDef", {}).get("qDef"): # Check for calculated dimension expression
                metadata["expressions"].append(d["qDef"]["qDef"])

        # Extract measures and their expressions
        for m in meas:
            metadata["measures"].append(m.get("qFallbackTitle"))
            if m.get("qDef", {}).get("qDef"): # Check for measure expression
                metadata["expressions"].append(m["qDef"]["qDef"])

    elif "qListObject" in layout:
        # For listboxes, the dimension definition is often here
        dim_info = layout["qListObject"].get("qDimensionInfo", {})
        metadata["dimensions"].append(dim_info.get("qFallbackTitle", "N/A"))
        if dim_info.get("qDef", {}).get("qDef"): # Check for calculated dimension expression in listbox
            metadata["expressions"].append(dim_info["qDef"]["qDef"])

    # This might still be relevant for specific text objects or variables
    if "qStringExpression" in layout and layout.get("qStringExpression", {}).get("qExpr"):
        metadata["expressions"].append(layout["qStringExpression"]["qExpr"])

    # If the expressions list is empty, set it to None for consistency with previous structure
    # if not metadata["expressions"]:
    #     metadata["expressions"] = None # Optional: if you prefer None for no expressions

    return metadata

def print_metadata(metadata):
    print("---")
    print(f"ID: {metadata['id']}")
    print(f"Type: {metadata['type']}")
    print(f"Title: {metadata['title']}")
    if metadata["dimensions"]:
        print("Dimensions:", metadata["dimensions"])
    if metadata["measures"]:
        print("Measures:", metadata["measures"])
    if metadata["expressions"]: # Changed to expressions
        print("Expressions:", metadata["expressions"]) # Changed to expressions

def save_output_to_file(filename="output_metadata.json"):
    with open(filename, "w", encoding="utf-8") as f:
        json.dump(state["output_data"], f, indent=4)
    print(f"\n[✓] Metadata saved to {filename}")

def on_open(ws):
    print("[✓] WebSocket connected")
    ws.send(json.dumps({
        "jsonrpc": "2.0",
        "id": 1,
        "method": "OpenDoc",
        "handle": -1,
        "params": [APP_ID]
    }))

def on_error(ws, error):
    print("Error:", error)

def on_close(ws, code, msg):
    print("WebSocket closed")

websocket.enableTrace(False)

ws = websocket.WebSocketApp(
    WS_URL,
    on_open=on_open,
    on_message=on_message,
    on_error=on_error,
    on_close=on_close,
    header=headers
)

ws.run_forever(sslopt={"cert_reqs": ssl.CERT_NONE})

 

Labels (1)
1 Solution

Accepted Solutions
rwunderlich
Partner Ambassador/MVP
Partner Ambassador/MVP

One problem I see is that you are only looking at the qDef property. If the Measure references a Master Measure, then qDef will be empty and qLibraryId will have the id of the Master Measure. 

I recommend the excellent free Qlik Explorer for Developers to examine and understand the structure of Qlik Objects. 

I note there is a new Python Qlik SDK you may find easier to use than crafting the websocket calls. https://qlik.dev/toolkits/platform-sdk/

-Rob

 

View solution in original post

1 Reply
rwunderlich
Partner Ambassador/MVP
Partner Ambassador/MVP

One problem I see is that you are only looking at the qDef property. If the Measure references a Master Measure, then qDef will be empty and qLibraryId will have the id of the Master Measure. 

I recommend the excellent free Qlik Explorer for Developers to examine and understand the structure of Qlik Objects. 

I note there is a new Python Qlik SDK you may find easier to use than crafting the websocket calls. https://qlik.dev/toolkits/platform-sdk/

-Rob