Экспорт данных визуализации с помощью Qlik Engine JSON API

Наша организация использует Qlik Sense Enterprise, и мы стремимся автоматизировать процесс загрузки данных, используемых для визуализаций (формат может быть Excel или CSV) вместо ручного процесса, который приводит к следующему (показан обрезанный снимок экрана):

С портала qlik sense

Для нашего случая использования предположим, что есть только одно приложение с одним листом внутри и на этом листе 3 визуализации.

Я написал сценарий python, который в настоящее время подключен к локальному хосту, и я могу получить app_id, sheet_id и id трех представленных диаграмм с помощью Qlik Engine JSON API. Код работает следующим образом:

  1. Получить список_документов (список_приложений)
  2. Выберите приложение, так как у нас только одно приложение, мы выбираем индекс как 0
  3. Создайте объект сеанса (я видел, как Qlik Engine на Dev Hub демонстрирует такое поведение, поэтому я выполнил этот шаг)
  4. Получить макет приложения
  5. Выберите лист, так как у нас один лист, мы выбираем индекс как 0
  6. Просмотрите визуализации и напечатайте их имена.

Для справки я предоставил приведенный ниже код, а ссылку pastebin можно найти здесь

import requests
import websocket, ssl
import json,  csv
import os, time
from pprint import pprint 
 
#Connecting to the server. The lines below will be replaced by the certificates and headers for enterprise usage later
ws = websocket.WebSocket()
ws.connect("ws://localhost:4848/app/")
 
#For getting the doc (app) list
doclist_req = {
    "handle": -1,
    "method": "GetDocList",
    "params": [],
    "outKey": -1,
    "id": 1
}
 
ws.send(json.dumps(doclist_req))
result = ws.recv()
ws.send(json.dumps(doclist_req))
result = ws.recv()
result_json = json.loads(result)
print(result_json)
print()
 
#For opening the doc (app)
app_req = {
    "jsonrpc": "2.0",
    "method": "OpenDoc",
    "handle": -1,
    "params": [
        #Can iterate if multiple apps are there
        #Since only one app was present we used the index 0
        result_json['result']['qDocList'][0]['qDocId']
    ],
    "outKey": -1,
    "id": 2
}
 
#The first call seems to be for establishing the connection and second to actually send the request body
ws.send(json.dumps(app_req))
result = ws.recv()
ws.send(json.dumps(app_req))
result = ws.recv()
result_json = json.loads(result)
print(result_json)
print()
app_req_handle = result_json['result']['qReturn']['qHandle']
 
#For creating the session object necessary for fetching dimensions, fields, etc.
session_req = {
    "jsonrpc": "2.0",
    "method": "CreateSessionObject",
    "handle": app_req_handle,
    "params": [
        {
            "qInfo": {
                "qType": "SheetList"
            },
            "qAppObjectListDef": {
                "qType": "sheet",
                "qData": {
                    "title": "/qMetaDef/title",
                    "description": "/qMetaDef/description",
                    "thumbnail": "/thumbnail",
                    "cells": "/cells",
                    "rank": "/rank",
                    "columns": "/columns",
                    "rows": "/rows"
                }
            }
        }
    ],
    "outKey": -1,
    "id": 3
}
 
ws.send(json.dumps(session_req))
result = ws.recv()
ws.send(json.dumps(session_req))
result = ws.recv()
result_json = json.loads(result)
print(result_json)
print()
session_req_handle = result_json['result']['qReturn']['qHandle']
 
#For fetching the layout of the sheets
layout_req = {
    "jsonrpc": "2.0",
    "method": "GetLayout",
    "handle": session_req_handle,
    "params": [],
    "outKey": -1,
    "id": 4
}
 
ws.send(json.dumps(layout_req))
result = ws.recv()
ws.send(json.dumps(layout_req))
result = ws.recv()
result_json = json.loads(result)
print(result_json)
print()
 
#Since only one sheet was present we used the index 0
list_of_charts = result_json['result']['qLayout']['qAppObjectList']['qItems'][0]['qData']['cells']
for chart in list_of_charts:
    print(chart['name'])
print()
 
ws.close()

Я изучил множество страниц в сообществе Qlik, а также SO и ExportData, кажется, подходящее решение, но я не могу написать для него правильный запрос JSON. Для справки, вывод, который я получаю в своем терминале при выполнении скрипта python, приведен ниже. Я новичок в этом, и я был бы очень благодарен вам за любую помощь.

{'jsonrpc': '2.0', 'id': 1, 'result': {'qDocList': [{'qDocName': 'Test_2.qvf', 'qConnectedUsers': 0, 'qFileTime': 44188.190983796296, 'qFileSize ': 851968,' qDocId ':' C: \ Users \ mohdm \ Documents \ Qlik \ Sense \ Apps \ Test_2.qvf ',' qMeta ': {' hassectionaccess ': False,' encrypted ': False},' qLastReloadTime ' : '2020-12-22T19: 12: 22.245Z', 'qTitle': 'Test_2', 'qThumbnail': {}}]}}

{'jsonrpc': '2.0', ' id ': 2,' result ': {' qReturn ': {' qType ':' Doc ',' qHandle ': 1,' qGenericId ':' C: \ Users \ mohdm \ Documents \ Qlik \ Sense \ Apps \ Test_2 .qvf '}},' изменить ': 1}

{'jsonrpc': '2.0', 'id': 3, 'result': {'qReturn': {'qType': 'GenericObject', 'qHandle': 2, 'qGenericType': 'SheetList', ' qGenericId ':' 4b344780-a350-48db-8b65-27bb5a2c62b2 '}},' изменить ': 1}

{'jsonrpc': '2.0', 'id': 4, 'result': {'qLayout': {'qInfo': {'qId': '4b344780- a350-48db-8b65-27b b5a2c62b2 ',' qType ':' SheetList '},' qMeta ': {' привилегии ': [' чтение ',' обновление ',' удаление ',' exportdata ']},' qSelectionInfo ': {},' qAppObjectList ' : {'qItems': [{'qInfo': {'qId': '8a0f6a01-ef89-4d65-821f-371c26208dcf', 'qType': 'sheet'}, 'qMeta': {'привилегии': ['читать ',' update ',' delete ',' exportdata '],' title ':' Sheet_1 ',' description ':' '},' qData ': {' rank ': None,' thumbnail ': {' qStaticContentUrl ' : {}}, 'columns': 24, 'rows': 12, 'cells': [{'name': 'kfxNpV', 'type': 'auto-chart', 'col': 0, 'row' : 0, 'colspan': 15, 'rowspan': 6, 'bounds': {'y': 0, 'x': 0, 'width': 62,5, 'height': 50}}, {'имя' : 'qHzmARQ', 'type': 'qlik-barplus-chart', 'col': 0, 'row': 6, 'colspan': 21, 'rowspan': 6, 'bounds': {'y': 50, 'x': 0, 'width': 87,5, 'height': 50}}, {'name': 'BXBQmw', 'type': 'auto-chart', 'col': 15, 'row'. : 0, 'colspan': 9, 'rowspan': 6, 'bounds': {'y': 0, 'x': 62,5, 'width': 37,5, 'height': 50}}], 'title' : 'Sheet_1', 'description': ''}}]}}}}

kfxNpV
qHzmARQ
BXBQmw

Первоначально я разместил этот вопрос по адресу Qlik community, но ответа не последовало.


person Mohammad Anas    schedule 23.12.2020    source источник


Ответы (2)


В этом случае я обычно наблюдаю за общением Qlik из браузера.

(в Chrome)

  • открыть приложение
  • откройте инструменты разработчика браузера (нажмите F12)
  • перейти к сети (1)
  • перейдите к WS (2)
  • нажмите нужную сессию сокета (3)
  • пресс-сообщения (4)
  • проверить, что отправлено / получено в сокете

(если вы не видите сокеты на вкладке «Сеть», просто обновите страницу)

введите описание изображения здесь

Ниже приведен код Javascript / узла, который экспортирует данные для одного объекта. (Я жестко кодирую идентификатор объекта в моем случае)

const fs = require('fs');
const axios = require('axios');

const enigma = require('enigma.js');
const WebSocket = require('ws');
const schema = require('enigma.js/schemas/12.20.0.json');

const session = enigma.create({
    schema,
    url: 'ws://localhost:9076/app/engineData',
    createSocket: url => new WebSocket(url)
});

(async function () {
    // open new session
    let global = await session.open();

    // open the app
    let doc = await global.openDoc("C:\\Users\\USERNAME\\Documents\\Qlik\\Sense\\Apps\\Consumer_Sales.qvf");

    // get the required object
    let qObj = await doc.getObject("MEAjCJ");

    // OOXML - export the data in Excel (xlsx) format
    let data = await qObj.exportData("OOXML");

    // generate the full download link
    let downloadLink = `http://localhost:4848${data.qUrl}`;

    // download and save the file
    await axios.get(downloadLink, { responseType: "stream" })
        .then(response => {
            response.data.pipe(fs.createWriteStream("export.xlsx"));
        });
})()

Официальная документация для метода ExportData: здесь

Обновление - фрагмент кода Python

Цикл по объектам листа может выглядеть так:

id = 5

list_of_charts = result_json['result']['qLayout']['qAppObjectList']['qItems'][0]['qData']['cells']
for chart in list_of_charts:

    obj_req = {
        "jsonrpc": "2.0",
        "method": "GetObject",
        "handle": app_req_handle,
        "params": [ chart["name"] ],
        "outKey": -1,
        "id": id
    }

    ws.send(json.dumps(obj_req))
    result = ws.recv()
    ws.send(json.dumps(obj_req))
    result = ws.recv()
    result_json = json.loads(result)
    # print(result_json)

    obj_req_handle = result_json['result']['qReturn']['qHandle']

    export_req = {
        "jsonrpc": "2.0",
        "method": "ExportData",
        "handle": obj_req_handle,
        "params": [ "OOXML" ],
        "outKey": -1,
        "id": 6
    }

    ws.send(json.dumps(export_req))
    result = ws.recv()
    ws.send(json.dumps(export_req))
    result = ws.recv()
    result_json = json.loads(result)

    downloadURL = "http://localhost:4848" + result_json["result"]["qUrl"]
    r = requests.get(downloadURL, allow_redirects=True)
    open('export_python_' + chart["name"] + '.xlsx', 'wb').write(r.content)

    id += 1
person Stefan Stoichev    schedule 24.12.2020
comment
Это действительно потрясающе, и большое спасибо за помощь @Stefan. Я написал эквивалент этого кода на Python из-за того, что я не знаю node.js и борюсь с ним. Код работает с запросом JSON с единственной проблемой, заключающейся в том, что он создает 6 папок для 3 объектов (в основном, выполняется дважды, я думаю). Если это не проблема, не могли бы вы просмотреть код, который я написал на Python, и высказать свое мнение? Также дайте мне знать, если мне нужно опубликовать измененный код в исходном вопросе или на pastebin. - person Mohammad Anas; 24.12.2020
comment
Я не парень с питоном, но я запустил ваш код и добавил часть экспорта (обновил свой ответ с помощью фрагмента). В моем случае он работал нормально - сгенерировал правильное количество файлов и запустился только один раз - person Stefan Stoichev; 26.12.2020
comment
Спасибо за ответ, Стефан. Я думаю, причина в том, что URL-адрес, сгенерированный на машине localhost, указывает на каталог Exports, находящийся в C: \ Users \ user_name \ Documents \ Qlik \ Sense \ Exports, где он обращается к файлу, когда вы вызываете его в скрипте с помощью запросов модуль. Я предполагаю, что Qlik Enterprise Server работает по той же концепции, когда файл хранится на сервере. - person Mohammad Anas; 27.12.2020

Я не уверен насчет Python, но считаю, что обычно самая сложная часть - это правильная аутентификация. Библиотеки, подобные тем, которые существуют для JavaScript, обычно значительно упрощают это. Лично я предпочитаю использовать C # для таких устройств, где .NET SDK предоставляет операторов для выполнения сантехнических работ за вас. Это пример того, как это могло бы выглядеть при использовании этих двух пакетов nuget:

Этот код загружает файлы xlsx для всех визуализаций на первом листе приложения.

var location = Location.FromUri(uri);
location.AsNtlmUserViaProxy();
var restClient = new RestClient(uri);
restClient.AsNtlmUserViaProxy();

using (var app = location.App(new AppIdentifier {AppId = appId}))
{
  var theSheet = app.GetSheets().First();
  var objs = theSheet.GetChildInfos().Select(info => app.GetGenericObject(info.Id));
  foreach (var o in objs)
  {
    var exportResult = o.ExportData(NxExportFileType.EXPORT_OOXML);
    var data = restClient.GetBytes(exportResult.Url);
    using (var writer = new BinaryWriter(new FileStream(o.Id + ".xlsx", FileMode.OpenOrCreate)))
    {
      writer.Write(data);
    }
  }
}
person Øystein Kolsrud    schedule 30.12.2020