How to Destroy a Hypercube




The Qlik Sense Workbench API gives us an easy way to create hypercubes from the browser on a webpage. With these hypercubes, we can create custom visualizations on the web that are driven by dynamic Qlik Sense data. However, one method that is not covered in the Workbench API is removing hypercubes once they have been created. When a hypercube is created, it will continue to process the data on the server and send it back to the browser as long as a user has their current session open. This can cause performance issues in single-page applications that use different cubes in different views.

For example, imagine you built a single-page application with a Dashboard view and a Details view. When you initialize your Dashboard view, you may create some hypercubes for the visualizations on that view. Users may navigate to the Details view next, which also initializes its own set of hypercubes. However, your hypercubes from the previous view will remain active. They will continue to utilize resources on the server, send data back to your browser, and execute their callback functions. This will lead to performance problems as you add views; the server will try to send all of the data across the views back to your browser at once.

A solution to this problem is to destroy cubes. While this is not possible with the Workbench API, the Engine API does give us a methodology for doing this. I've recently started a library of JavaScript functions called senseUtils that contains easy to use code for destroying hypercubes. However, this post will explain the details behind how to remove a cube, as well as provide an example of how to incorporate the method in practice manually as opposed to with the library. At the end, I'll show how the library code can be used instead.

Reviewing the createCube function

Before we talk about destroying the cube, let's quickly review the createCube() function. Here is an example from the Help documentation:

[code lang="js"]app.createCube( {qDimensions : [
{ qDef : {qFieldDefs : ["Status"]}},
{ qDef : {qFieldDefs : ["Priority"]}},
{ qDef : {qFieldDefs : ["Case Owner Group"]}}
], qMeasures : [
{ qDef : {qDef : "Sum([Case Count])", qLabel :""}}
],qInitialDataFetch: [{qHeight: 20,qWidth: 4}]},Cases);

// callbacks
function Cases (reply, app) {
}[/code]

In the first line, the createCube function is called on an app variable. The app variable contains a reference to an application previously opened with the Workbench API. The cube definition includes some dimensions, measures, and table size parameters. Finally, it is ended with a callback function called Cases. This function will be executed every time the cube changes; therefore, the callback will always execute with the most up to date data based on user selections.

The callback function has two inputs in this example. The first is a reply variable, which contains the layout and data of the actual cube. This is where you would pull your data.

What actually happens in the cube

The code above allows us to dynamically receive data from the Qlik Sense application and use it in any way we would like. In order to understand how to remove the cube, we should quickly understand what is happening behind the scenes with the createCube() method. The createCube method creates a transient object on the server. The transient object exists only during a user's session where the object was created. As soon as the user's session ends, the object is removed from the server.

Using the Engine API to remove the cube

Because the createCube method created a transient object, we can use the engine API to target that transient object and remove it manually. The Engine API works by sending JSON to the Qlik Sense server over a websocket. The JSON can contain a message to the server about what tasks to perform.

The Engine API has a method called DestroySessionObject. We can send JSON invoking this method over the websocket to target a transient object for removal. An example of this call:

[code lang="js"]var ex_json = {
"jsonrpc": "2.0",
"id": 2,
"method": "DestroySessionObject",
"handle": 1,
"params": [
"LB01"
]
};[/code]

DestroySessionObject needs one parameter: the qId of the object that should be removed. This example tells the server to destroy the object with qId "LB01".

The only other thing we need to destroy a session object is the websocket itself. Websockets have a .send() function that can be used to send a string of JSON.

Let's say we have a websocket reference stored in a variable called ws. We could send the JSON above in the following manner:

[code lang="js"]// convert the JSON into a string
var ex_json_string = JSON.stringify(ex_json);

// sends the JSON over the websocket
ws.send(ex_json_string);[/code]

In order to use this method, we need to know a reference to our websocket connected to the Qlik Sense server and the qId of our cube. The websocket can be found in the app variable used to create the cube: app.model.session.socket. The qId is returned as part of the reply variable in the callback function of a cube: reply.qInfo.qId.

Putting it all together with an example manual implementation

So far, we've reviewed all of the necessary pieces for removing a cube. However, in practice we need a way to keep track of the different cubes and their id's so that we can destroy them when necessary. There are many ways to do this, but I will share just one example that I have used recently.

Let's start with our original cube definition:

[code lang="js"]app.createCube( {qDimensions : [
{ qDef : {qFieldDefs : ["Status"]}},
{ qDef : {qFieldDefs : ["Priority"]}},
{ qDef : {qFieldDefs : ["Case Owner Group"]}}
], qMeasures : [
{ qDef : {qDef : "Sum([Case Count])", qLabel :""}}
],qInitialDataFetch: [{qHeight: 20,qWidth: 4}]},Cases);

// callbacks
function Cases (reply, app) {
}[/code]

We need to keep track of the qId in order to be able to destroy this cube, but it is only available in the callback function. One way to handle this is to wrap our createCube call in another function. We will set up this function so that it builds the code to destroy the cube when it can, and then returns that code back to us for access later. Let's start by wrapping the cube in a function and initializing the result that will be returned:

[code lang="js"]function createMyCube(app) {

// Initialize result of this function
var result;

// Initialize two properties for our result: our qId, and our function to destroy the cube once it has been created
result.qId = "N/A";
result.destroy() = function() {return "no cube defined"};

// Create the cube
app.createCube( {qDimensions : [
{ qDef : {qFieldDefs : ["Status"]}},
{ qDef : {qFieldDefs : ["Priority"]}},
{ qDef : {qFieldDefs : ["Case Owner Group"]}}
], qMeasures : [
{ qDef : {qDef : "Sum([Case Count])", qLabel :""}}
],qInitialDataFetch: [{qHeight: 20,qWidth: 4}]},Cases);

// callbacks
function Cases (reply, app) {
}

// Return the result variable at the end of the function
return result;
}[/code]

The code above initializes an object to return called result. This object will hold our qId and our destroy function. We initialize it with some dummy values; we can't populate it until the callback function runs, which is where we get the qId. At the bottom of the function, we return theresult object. This means that if you write var a = createMyCube(app), the variable a will contain the result object. It can then be referenced later, which could include calling the destroy() function.

I also included app as a parameter for my function. I did this in case my mashup utilizes data cubes from more than one application; this allows me to specify which application should be used to create and destroy the cube.

Next, let's update the callback function to define the qId and destroy() function:
[code lang="js"]
function createMyCube(app) {
var result;
result.qId = "N/A";
result.destroy() = function() {return "no cube defined"};

// Create the cube
app.createCube( {qDimensions : [
{ qDef : {qFieldDefs : ["Status"]}},
{ qDef : {qFieldDefs : ["Priority"]}},
{ qDef : {qFieldDefs : ["Case Owner Group"]}}
], qMeasures : [
{ qDef : {qDef : "Sum([Case Count])", qLabel :""}}
],qInitialDataFetch: [{qHeight: 20,qWidth: 4}]},Cases);

// callbacks
function Cases (reply, app) {

// pull the qId from the reply object
result.qId = reply.qInfo.qId;

// create a local variable to hold the json
var json = {
"jsonrpc": "2.0",
"id": 2,
"method": "DestroySessionObject",
"handle": 1,
"params": [
result.qId
]
};

// convert our JSON object in the json variable to a string
var json_string = JSON.stringify(json);
result.destroy() = function() {
// Create the destroy function that will send the JSON over the app socket
app.model.session.socket.send(json_string);
};
}
return result;
}
[/code]
Now we have a function that will create our cube and return an object containing a destroy() for our cube. The function above could be used like so:
[code lang="js"]
// creates the cube
var myCube = createMyCube(app);

// returns the qId of the cube
myCube.qId;

// removes the cube
myCube.destroy();
[/code]

Using senseUtils instead
The above example illustrates how you can use the Engine API to do more complicated mashup functions. This use-case is covered in the senseUtils library, so rather than manually building the destroy function each time, you can load in the library and use the DestroyObj method instead. Assuming you have an app variable containing a reference to a Qlik app and a qId variable containing the id of a hypercube, you can run the following code with senseUtils to destroy the cube:
[code lang="js"]
senseUtils.destroyObj(app,qId);
[/code]

Alternatively, you could create your hypercube as a multiCube, which has built-in methods for removing the cube.

-Speros

TAGS: APIs, Performance, Qlik Sense

Related News

Student retention is near the top of every institution's agenda. Student attrition results in...

Read More

A minor but frequent pain point at our Qlik Sense clients has been addressed in the Qlik Sense...

Read More