Skip to main content
Announcements
Qlik Connect 2024! Seize endless possibilities! LEARN MORE
Ouadie
Employee
Employee

We all know and love using Insight Advisor right within the Qlik Sense hub or inside Analytics apps, helping us analyze data, create visualizations or build data models.

In this post, we will tap into the Insight Advisor API to leverage its power within a separate web application.

We will create a simple web app that allows to ask natural language questions against our Qlik Sense app and get a recommended visualization as a response that we will then render using nebula.js

Pre-requisites:
You will need to grab the following before starting:

  • Qlik Cloud tenant URL
  • Web Integration ID (you can get this from the Management console under Web, make sure to whitelist our localhost’s origin: http://localhost:1234)
  • App Id

Installation
Run npm install to install the content of package.json

Folder structure:

  • src
    • index.html (UI)
    • index.js (main file)
    • and cloud.engine.js (enigma.js library for engine session handling)

The following sections discuss the main parts of building the web app and calling the API, they are not in any particular order. I will provide the complete code for the project at the end of the post so you can see where everything fits.

1. Connecting to Qlik Cloud

First things first, we need to handle the authentication to Qlik Cloud.

Interactive login process:

 

 

async function getQCSHeaders() {
  await qlikLogin(); // enforce tenant login
  const response = await fetch(`${tenantUrl}/api/v1/csrf-token`, {
    mode: 'cors',
    credentials: 'include',
    headers: {
      'qlik-web-integration-id': webIntegrationId,
    },
  });

  const csrfToken = new Map(response.headers).get('qlik-csrf-token');
  return {
    'qlik-web-integration-id': webIntegrationId,
    'qlik-csrf-token': csrfToken,
  };
}

async function qlikLogin() {
  const loggedIn = await fetch(`${tenantUrl}/api/v1/users/me`, {
    mode: 'cors',
    credentials: 'include',
    headers: {
      'qlik-web-integration-id': webIntegrationId,
    },
  });
  if (loggedIn.status !== 200) {
    if (sessionStorage.getItem('tryQlikAuth') === null) {
      sessionStorage.setItem('tryQlikAuth', 1);
      window.location = `${tenantUrl}/login?qlik-web-integration-id=${webIntegrationId}&returnto=${location.href}`;
      return await new Promise((resolve) => setTimeout(resolve, 10000)); // prevents further code execution
    } else {
      sessionStorage.removeItem('tryQlikAuth');
      const message = 'Third-party cookies are not enabled in your browser settings and/or browser mode.';
      alert(message);
      throw new Error(message);
    }
  }
  sessionStorage.removeItem('tryQlikAuth');
  console.log('Logged in!');
  return true;
}

 

 

 

2. Communicating with the Qlik Cloud Engine

(content of the cloud.engine.js file)

We need to open a session using enigma.js to communicate with the Qlik QIX engine.

 

 

import enigma from "enigma.js";
const schema = require("enigma.js/schemas/12.1306.0.json");

export default class EngineService {
  constructor(engineUri) {
    this.engineUri = engineUri;
  }

  openEngineSession(headers) {
    const params = Object.keys(headers)
      .map((key) => `${key}=${headers[key]}`)
      .join("&");
    const session = enigma.create({
      schema,
      url: `${this.engineUri}?${params}`,
    });
    session.on("traffic:sent", (data) => console.log("sent:", data));
    session.on("traffic:received", (data) => console.log("received:", data));
    return session;
  }

  async closeEngineSession(session) {
    if (session) {
      await session.close();
      console.log("session closed");
    }
  }

  async getOpenDoc(appId, headers) {
    let session = this.openEngineSession(headers);
    let global = await session.open();

    let doc = await global.openDoc(appId);
    return doc;
  }
}

 

 

 

3. Including the Nebula Charts needed and rendering the recommended viz from Insight Advisor

When we eventually get back a recommendation from Insight Advisor, we will use a nebula object to embed it in our web app.

For a full list of available Nebula objects, visit: https://qlik.dev/embed/foundational-knowledge/visualizations/

We need to install “stardust” that contains the main embed function and all the nebula objects we need:

 

 

npm install @nebula.js/stardust

then install all objects needed
npm install @nebula/sn-scatter-plot
npm install @nebula/sn-bar-chart
etc...
import { embed } from '@nebula.js/stardust';
import scatterplot from  '@nebula/sn-scatter-plot';
etc...

 

 

Inside the rendering function, we will use stardust’s embed method to render the recommended chart type we get from Insight Advisor.

 

 

async function fetchRecommendationAndRenderChart(requestPayload) {
  // fetch recommendations for text or metadata
  const recommendations = await getRecommendation(requestPayload);

  const engineUrl = `${tenantUrl.replace('https', 'wss')}/app/${appId}`;
  // fetch rec options which has hypercubeDef
  const recommendation = recommendations.data.recAnalyses[0];
  // get csrf token
  const qcsHeaders = await getQCSHeaders();
  const engineService = new EngineService(engineUrl);
  // get openDoc handle
  const app = await engineService.getOpenDoc(appId, qcsHeaders);
  await renderHypercubeDef(app, recommendation);
}

async function renderHypercubeDef(app, recommendation) {
  const type = recommendation.chartType;

  const nebbie = embed(app, {
    types: [
      {
        name: type,
        load: async () => charts[type],
      },
    ],
  });

  document.querySelector('.curr-selections').innerHTML = '';
  (await nebbie.selections()).mount(document.querySelector('.curr-selections'));

  await nebbie.render({
    type: type,
    element: document.getElementById('chart'),
    properties: { ...recommendation.options }
});

 

 

 

4. Calling the Insight Advisor API for recommendations

You can either call the API with a natural language question or a set of fields and master items with an optional target analysis.

Insight Advisor API endpoints that can be called:

  • api/v1/apps/{appId}/insight-analyses
    Returns information about supported analyses for the app's data model. Lists available analysis types, along with minimum and maximum number of dimensions, measures, and fields.
  • api/v1/apps/{appId}/insight-analyses/model
    Returns information about model used to make analysis recommendations. Lists all fields and master items in the logical model, along with an indication of the validity of the logical model if the default is not used.
  • api/v1/apps/{appId}/insight-analyses/actions/recommend
    Returns analysis recommendations in response to a natural language question, a set of fields and master items, or a set of fields and master items with an optional target analysis.

 

 

// Getting the recommendation
async function getRecommendation(requestPayload) {
  await qlikLogin(); // make sure you are logged in to your tenant
  // build url to execute recommendation call
  const endpointUrl = `${tenantUrl}/api/v1/apps/${appId}/insight-analyses/actions/recommend`;
  let data = {};
  // generate request payload
  if (requestPayload.text) {
    data = JSON.stringify({
      text: requestPayload.text,
    });
  } else if (requestPayload.fields || requestPayload.libItems) {
    data = JSON.stringify({
      fields: requestPayload.fields,
      libItems: requestPayload.libItems,
      targetAnalysis: { id: requestPayload.id },
    });
  }
  const response = await fetch(endpointUrl, {
    credentials: "include",
    mode: "cors",
    method: 'POST',
    headers,
    body: data,
  });

  const recommendationResponse = await response.json();
  return recommendationResponse;
}

 

 

Results:
Screenshot 2024-02-16 182545.png

 

For the complete example that includes calling the API with fields, master items, and a target analysis type, visit qlik.dev post: https://qlik.dev/embed/gen-ai/build-insight-advisor-web-app/ 

 

The full code for this post can be found here:
https://github.com/ouadie-limouni/insight-advisor-api
Make sure to change the variables in index.js.

I hope you find this post helpful, please let me know if you have any question in the comment section below!

Ouadie