<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Stefan Stoichev]]></title><description><![CDATA[My little writing space ]]></description><link>https://sstoichev.eu/</link><image><url>https://sstoichev.eu/favicon.png</url><title>Stefan Stoichev</title><link>https://sstoichev.eu/</link></image><generator>Ghost 3.21</generator><lastBuildDate>Sun, 27 Dec 2020 17:28:25 GMT</lastBuildDate><atom:link href="https://sstoichev.eu/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[enigma.js - interceptors]]></title><description><![CDATA[Quick introduction to enigma.js interceptors]]></description><link>https://sstoichev.eu/2020/07/08/enigma-js-interceptors/</link><guid isPermaLink="false">5ef05d95d2a7470599d0ebb5</guid><category><![CDATA[qlik]]></category><category><![CDATA[qlik sense]]></category><category><![CDATA[enigma.js]]></category><category><![CDATA[interceptors]]></category><dc:creator><![CDATA[Stefan Stoychev]]></dc:creator><pubDate>Wed, 08 Jul 2020 09:00:00 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1473175561656-f4158e07e68b?ixlib=rb-1.2.1&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=2000&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1473175561656-f4158e07e68b?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=2000&fit=max&ixid=eyJhcHBfaWQiOjExNzczfQ" alt="enigma.js - interceptors"><p>Another interesting feature of enigma.js are interceptors. Interceptors are similar to the mixins functions but they are executed on lower level - request/response level.</p><p>Request interceptors are invoked right before the request is send to the Engine and response interceptors are invoked right after the response from the Engine is received and before the data is send to the calling function.</p><p>You can think of the interceptors as "enigma.js middleware".</p><p>There are couple of functions available for request and response:</p><!--kg-card-begin: markdown--><ul>
<li>Request
<ul>
<li><code>onFulfiled</code> - invoked just before the request is going to be send to the Engine so you can alter the request is needed</li>
</ul>
</li>
<li>Response
<ul>
<li><code>onFulfiled</code> - invoked when the response from the engine is ok (no errors)</li>
<li><code>onRejected</code> - invoked when the Engine returns an error so you can handle the errors on enigma.js level</li>
</ul>
</li>
</ul>
<!--kg-card-end: markdown--><p>The code below shows how to construct request interceptor:</p><pre><code class="language-javascript">const enigma = require('enigma.js');
const schema = require('enigma.js/schemas/12.20.0.json');
const WebSocket = require('ws');

const session = enigma.create({
  schema,
  url: 'ws://localhost:9076/app/engineData',
  createSocket: (url) =&gt; new WebSocket(url),
  requestInterceptors: [{
    onFulfilled: function myHandler(sessionReference, request) {
      // do somthing with the request data here
      // OR return the original request 
      return request
    },
  }],
});</code></pre><h2 id="example">Example</h2><!--kg-card-begin: markdown--><p>A simple example of response interceptor can be - change the return data format of a <code>getLayout()</code> method which gets the current selections data.</p>
<p>This interceptor will &quot;listen&quot; for <code>getLayout</code> requests and if the response is for a specific object type it will &quot;flatten&quot; the returned data and return it in the following format:</p>
<!--kg-card-end: markdown--><pre><code class="language-javascript">[
    ...
    {"field": "name-of-the-field", "value": "selected-value-1"},
    {"field": "name-of-the-field", "value": "selected-value-2"},
    {"field": "some-other-field", "value": "selected-value-1"}
    ...
]</code></pre><ul><li>First lets prepare the session object and define our interceptor there:</li></ul><pre><code class="language-javascript">const session = enigma.create({
    schema,
    url: 'ws://localhost:9076/app/engineData',
    createSocket: (url) =&gt; new WebSocket(url),
    responseInterceptors: [{
        onFulfilled: function toggleDelta(session, request, response) {
          // check if the request method is getLayout and 
          // the response is for object with type current-selections-modified
            if (
                request.method === 'GetLayout' &amp;&amp; 
                response.qInfo.qType == 'current-selections-modified'
            ) {
                // if the above is valid then "flatten" the response
                return response.qSelectionObject.qSelections.map((s) =&gt; {
                    return s.qSelectedFieldSelectionInfo.map((f) =&gt; {
                        return {
                            field: s.qField,
                            value: f.qName
                        }
                    })
                }).flat()
            }

            // for every other response return the original response 
            return response;
        },
    }],
});</code></pre><ul><li>Let's select some values first:</li></ul><pre><code class="language-javascript">    let field = await qDoc.getField('Product Sub Group Desc')
    let selectValues = await field.selectValues([
        { qText: 'Ice Cream' },
        { qText: 'Juice' },
        { qText: 'Chips' }
    ])</code></pre><ul><li>Once we have the interceptor code ready and some selections are made then we can prepare the properties of the object that will hold the selections:</li></ul><pre><code class="language-javascript">    let selectionObjectProps = {
        "qInfo": {
            "qId": "",
            "qType": "current-selections-modified"
        },
        "qSelectionObjectDef": {}
    }</code></pre><p>Check out the type of our object - <strong>current-selections-modified</strong>. Our interceptor will change the data output only if the response is for this object type. This way we will not "disrupt" the data for the other object types.</p><ul><li>once we have the properties set, we continue our enigma calls as usual: create session object and get its layout:</li></ul><pre><code class="language-javascript">let sessionObj = await qDoc.createSessionObject(selectionObjectProps);
let sessionObjLayout = await sessionObj.getLayout(); // out interceptor will be invoked here
console.log(sessionObjLayout)</code></pre><p>The result of the last row will print the following "flatten" array:</p><pre><code class="language-javascript">[
  { field: 'Product Sub Group Desc', value: 'Juice' },
  { field: 'Product Sub Group Desc', value: 'Ice Cream' },
  { field: 'Product Sub Group Desc', value: 'Chips' }
]</code></pre><p>It's probably not the most practical example but it will give you an idea what enigma.js interceptors are. There are couple of more examples in enigma.js repository itself:</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/qlik-oss/enigma.js/tree/master/examples/interceptors"><div class="kg-bookmark-content"><div class="kg-bookmark-title">qlik-oss/enigma.js</div><div class="kg-bookmark-description">JavaScript library for consuming Qlik’s Associative Engine. - qlik-oss/enigma.js</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.githubassets.com/favicons/favicon.svg" alt="enigma.js - interceptors"><span class="kg-bookmark-author">qlik-oss</span><span class="kg-bookmark-publisher">GitHub</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://avatars3.githubusercontent.com/u/23365920?s=400&amp;v=4" alt="enigma.js - interceptors"></div></a></figure><h2 id="conclusion">Conclusion</h2><p>The same interceptor can be written as mixin as well and instead intercepting the response just call the mixin which will return the same result. But if you want low level / fine grain control over the request/response traffic then interceptors are the way.</p><h2 id="code">Code</h2><p>Check out the repository below for the full example code</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/countnazgul/enigma-js-interceptors"><div class="kg-bookmark-content"><div class="kg-bookmark-title">countnazgul/enigma-js-interceptors</div><div class="kg-bookmark-description">Small script to demonstrate the usage of enigma.js interceptors - countnazgul/enigma-js-interceptors</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.githubassets.com/favicons/favicon.svg" alt="enigma.js - interceptors"><span class="kg-bookmark-author">countnazgul</span><span class="kg-bookmark-publisher">GitHub</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://avatars2.githubusercontent.com/u/22850?s=400&amp;v=4" alt="enigma.js - interceptors"></div></a></figure><p>Hope you liked it!</p><p>Stefan</p>]]></content:encoded></item><item><title><![CDATA[Custom enigma.js mixins]]></title><description><![CDATA[Quick introduction to enigma-mixin package]]></description><link>https://sstoichev.eu/2020/07/03/introducing-enigma-mixin/</link><guid isPermaLink="false">5d3587ac3c4ccc7663e24925</guid><category><![CDATA[qlik]]></category><category><![CDATA[qlik sense]]></category><category><![CDATA[enigma.js]]></category><category><![CDATA[mixin]]></category><dc:creator><![CDATA[Stefan Stoychev]]></dc:creator><pubDate>Fri, 03 Jul 2020 09:00:00 GMT</pubDate><media:content url="https://sstoichev.eu/content/images/2019/07/garett-mizunaka-xFjti9rYILo-unsplash.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://sstoichev.eu/content/images/2019/07/garett-mizunaka-xFjti9rYILo-unsplash.jpg" alt="Custom enigma.js mixins"><p>In the <a href="https://sstoichev.eu/2020/06/23/enigma-js-mixins/">previous post</a> we saw what are <a href="https://github.com/qlik-oss/enigma.js">enigma.js</a> <a href="https://github.com/qlik-oss/enigma.js/blob/master/docs/api.md#mixins">mixins</a>. I've started an repo some time ago (and <em>npm </em>package) where I've been adding mixins that I've found useful (or mixins that I've found boring to write all the time). And want to introduce you to this list.</p><p>At the moment the repo contains only mixins that are relevant to application/document. </p><p>(to distinct the mixins methods from the default enigma.js ones all mixin methods, from this package, are having prefix <code>m</code>)</p><p>The mixins are "split" into groups:</p><!--kg-card-begin: markdown--><ul>
<li>Selections
<ul>
<li>
<p><code>mSelectionsAll</code> - returns the &quot;native&quot; reponse from Qlik ragarding the current selections</p>
</li>
<li>
<p><code>mSelectionsFields</code> - an array of only the field names, having selections in it</p>
</li>
<li>
<p><code>mSelectionsSimple</code> - without any parameters returns a flat array with all selections</p>
<pre><code class="language-javascript">[
  {field: &quot;Field 1&quot;: value: 'Value 1'},
  {field: &quot;Field 1&quot;: value: 'Value 2'},
  {field: &quot;Field 2&quot;: value: 'Value 10'},
  ...
]
</code></pre>
<p>if <code>groupByField</code> parameter (optional and default is <code>false</code>) is passed then the result will be grouped by a field name:</p>
<pre><code class="language-javascript">[
  {field: &quot;Field 1&quot;: values: [...]},
  {field: &quot;Field 2&quot;: values: [...]},
  ...
]
</code></pre>
</li>
<li>
<p><code>mSelectInField</code> - provide an field name and array of values to be selected</p>
</li>
</ul>
</li>
<li>Tables and fields
<ul>
<li><code>mGetTablesAndFields</code> - returns an array with <code>{table: &quot;data-table-name&quot;, field: &quot;field-name&quot;}</code> values</li>
<li><code>mGetTables</code> - an array with all data tables</li>
<li><code>mGetFields</code> - an array with all fields</li>
<li><code>mCreateSessionListbox</code> - creates a listbox <strong>session</strong> object for the provided field</li>
</ul>
</li>
<li>Variables
<ul>
<li><code>mVariableGetAll</code> - array with all variables</li>
<li><code>mVariableUpdateById</code> - updates the definition of an existing variable by providing the variable ID</li>
<li><code>mVariableUpdateByName</code> - updates the definition of an existing variable by providing the variable name</li>
<li><code>mVariableCreate</code> - creates a new variable from the provided name, comment (optional) and definition</li>
</ul>
</li>
<li>Extensions
<ul>
<li><code>mExtensionObjectsAll</code> - returns an array with all extension objects in the current app. Returned fields: <code>appId</code>, <code>appName</code>, <code>objId</code>, <code>objType</code>, <code>extName</code>, <code>extVersion</code>, <code>extVisible</code>, <code>extIsBundle</code>, <code>extIsLibrary</code>, <code>qProps</code></li>
</ul>
</li>
<li>Unbuild
<ul>
<li><code>mUnbuild</code> -  extracts all parts of an Qlik Sense app (apart from the data itself) into JSON object</li>
</ul>
</li>
<li>Build
<ul>
<li><code>mBuild</code> - the opposite of un-building - provide a json object with the app components and this mixin will update (or create) all the objects in the target app</li>
</ul>
</li>
</ul>
<!--kg-card-end: markdown--><p>If you want to use the <em>build</em> and <em>unbuild </em>commands outside JavaScript then I highly recommend using <em>Corectl </em>tool</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/qlik-oss/corectl"><div class="kg-bookmark-content"><div class="kg-bookmark-title">qlik-oss/corectl</div><div class="kg-bookmark-description">corectl is a command line tool to perform reloads, fetch metadata and evaluate expressions in Qlik Core apps. - qlik-oss/corectl</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.githubassets.com/favicons/favicon.svg" alt="Custom enigma.js mixins"><span class="kg-bookmark-author">qlik-oss</span><span class="kg-bookmark-publisher">GitHub</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://avatars3.githubusercontent.com/u/23365920?s=400&amp;v=4" alt="Custom enigma.js mixins"></div></a></figure><h2 id="installation">Installation</h2><p>The installation is very simple:</p><pre><code class="language-javascript">npm install --save enigma-mixins</code></pre><h3 id="usage">Usage</h3><p>(make sure that enigma.js is already installed)</p><pre><code class="language-javascript">import enigma from "enigma.js";
import schema from "enigma.js/schemas/12.20.0.json";
import enigmaMixins from "enigma-mixins";

// Create enigma session as usual but include the mixin property
const session = enigma.create({
  schema,
  mixins: enigmaMixins, // this is where the mixinx are added
  url: "ws://localhost:4848/app/engineData",
  createSocket: (url) =&gt; new WebSocket(url),
});</code></pre><p>Once the session is established and the document is open, then you'll have all the mixins available:</p><figure class="kg-card kg-image-card"><img src="https://sstoichev.eu/content/images/2020/07/mixin.png" class="kg-image" alt="Custom enigma.js mixins"></figure><h3 id="contribution">Contribution</h3><p>Feel free to open an PR with your mixins :)</p><h3 id="repository-and-npm">Repository and npm</h3><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/countnazgul/enigma-mixin"><div class="kg-bookmark-content"><div class="kg-bookmark-title">countnazgul/enigma-mixin</div><div class="kg-bookmark-description">Set of Enigma mixin. Contribute to countnazgul/enigma-mixin development by creating an account on GitHub.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.githubassets.com/favicons/favicon.svg" alt="Custom enigma.js mixins"><span class="kg-bookmark-author">countnazgul</span><span class="kg-bookmark-publisher">GitHub</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://avatars2.githubusercontent.com/u/22850?s=400&amp;v=4" alt="Custom enigma.js mixins"></div></a></figure><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://www.npmjs.com/package/enigma-mixin"><div class="kg-bookmark-content"><div class="kg-bookmark-title">enigma-mixin</div><div class="kg-bookmark-description">Set of Qlik Sense enigma.js mixin</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://static.npmjs.com/1996fcfdf7ca81ea795f67f093d7f449.png" alt="Custom enigma.js mixins"><span class="kg-bookmark-publisher">npm</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://static.npmjs.com/338e4905a2684ca96e08c7780fc68412.png" alt="Custom enigma.js mixins"></div></a></figure><p>Hope you liked it!</p><p>Stefan</p>]]></content:encoded></item><item><title><![CDATA[enigma.js - mixins]]></title><description><![CDATA[Quick introduction to enigma.js mixins - what are they and how to use them]]></description><link>https://sstoichev.eu/2020/06/23/enigma-js-mixins/</link><guid isPermaLink="false">5ef05d7dd2a7470599d0ebb1</guid><category><![CDATA[qlik]]></category><category><![CDATA[qlik sense]]></category><category><![CDATA[api]]></category><category><![CDATA[mixns]]></category><dc:creator><![CDATA[Stefan Stoychev]]></dc:creator><pubDate>Tue, 23 Jun 2020 09:15:00 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1506238359040-22480587eb7a?ixlib=rb-1.2.1&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=2000&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1506238359040-22480587eb7a?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=2000&fit=max&ixid=eyJhcHBfaWQiOjExNzczfQ" alt="enigma.js - mixins"><p>You know it already but <a href="https://github.com/qlik-oss/enigma.js">enigma.js</a> is the JavaScript wrapper around the Qlik's Engine API. </p><p>If you are building a mashup/web app or some sort of automation that require Engine communication you'll have to use Engine API and here enigma.js is your friend.</p><p>Just to mention that JavaScript is not the only "flavor" of Enigma. You can find enigma for <em>Go</em> and <em>.NET:</em></p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/qlik-oss/enigma-go"><div class="kg-bookmark-content"><div class="kg-bookmark-title">qlik-oss/enigma-go</div><div class="kg-bookmark-description">Go library for consuming Qlik’s Associative Engine. - qlik-oss/enigma-go</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.githubassets.com/favicons/favicon.svg" alt="enigma.js - mixins"><span class="kg-bookmark-author">qlik-oss</span><span class="kg-bookmark-publisher">GitHub</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://avatars3.githubusercontent.com/u/23365920?s=400&amp;v=4" alt="enigma.js - mixins"></div></a></figure><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/q2g/enigma.net"><div class="kg-bookmark-content"><div class="kg-bookmark-title">q2g/enigma.net</div><div class="kg-bookmark-description">.NET library for consuming Qlik’s Associative Engine. - q2g/enigma.net</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.githubassets.com/favicons/favicon.svg" alt="enigma.js - mixins"><span class="kg-bookmark-author">q2g</span><span class="kg-bookmark-publisher">GitHub</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://avatars2.githubusercontent.com/u/29521584?s=400&amp;v=4" alt="enigma.js - mixins"></div></a></figure><p>But in this post I want to introduce you to one very cool (but not very popular) functionality that Enigma provides - <strong>mixins.</strong></p><p>Mixins allow us to extend, with additional methods, or override existing methods. Mixins are bound to at least one type (type being - Doc, GenericObject, GenericBookmark etc.). The official documentation can be found in the official enigma.js <a href="https://github.com/qlik-oss/enigma.js/blob/master/docs/api.md#mixins">repo</a>.</p><h2 id="let-s-write-some-code-basics-">Let's write some code (basics)</h2><p></p><p>Essentially each mixin is a JS object with few properties - init, types and extends and/or overrides.</p><p>A very simple mixin will be:</p><pre><code class="language-javascript">const docMixin = {
    types: ['Doc'],
    init(args) {
        console.log(`My mixin is being initialized`);        
    },
    extend: {
        myMixin() {
            console.log('My mixin was called!');
        },

    }
}
</code></pre><p>From the snipped above, we can see: </p><ul><li>this snipped is available for docs (apps)</li><li>when the mixin is initialised a message will be printed in the console</li><li>when its called the mixin will only print a message</li></ul><p>To use the mixin we have to add this object (or objects) to the Enigma session:</p><pre><code class="language-javascript">const session = enigma.create({
    schema,
    mixins: [docMixin],
    url: 'ws://localhost:9076/app/engineData',
    createSocket: (url) =&gt; new WebSocket(url),
});</code></pre><p>Once we have the session established then we can open an app and use our mixin:</p><pre><code class="language-javascript">let global = await session.open()
let doc = await global.openDoc('/data/Consumer Sales.qvf')
doc.myMixin()</code></pre><p>In our case the mixin is pretty general (just printing a message) and we might want to use it for other types and not just in the apps context. To add it to more types just edit the <em>type </em>array:</p><pre><code class="language-javascript">...
types: ['Doc', 'GenericObject', 'GenericBookmark']
...</code></pre><h2 id="a-bit-more-advanced-example">A bit more advanced example</h2><p>Getting the layout of an object is a two step process:</p><pre><code class="language-javascript">let qObject = await doc.getObject('some-object-id-here');
let qLayout = await qObject.getLayout();</code></pre><p>What about if we write an mixin that is doing these two steps for us so we can have a single command to get the layout? An example mixin can look like this:</p><pre><code class="language-javascript">const docMixin = {
    types: ['Doc'],
    init(args) { },
    extend: {
        getLayoutMixin(objectId) {
            let qObject = await doc.getObject(objectId);
            let qLayout = await qObject.getLayout();

            return [ qObject, qLayout ]
        }
    }
}

const session = enigma.create({
    schema,
    mixins: [docMixin],
    url: 'ws://localhost:9076/app/engineData',
    createSocket: (url) =&gt; new WebSocket(url),
});

let global = await session.open()
let doc = await global.openDoc('/data/Consumer Sales.qvf')

let [myObject, myObjectLayout] = await doc.getLayoutMixin('some-object-id');</code></pre><h2 id="conclusion">Conclusion</h2><p>Personally I found the mixins to be very cool and handy feature. But also I found them very underused (including myself here as well). </p><p>I've been setting my own collection of useful mixins in my free time and the next post will be about this collection but you can have a look in the repo if you want:</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/countnazgul/enigma-mixin"><div class="kg-bookmark-content"><div class="kg-bookmark-title">countnazgul/enigma-mixin</div><div class="kg-bookmark-description">Set of Enigma mixin. Contribute to countnazgul/enigma-mixin development by creating an account on GitHub.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.githubassets.com/favicons/favicon.svg" alt="enigma.js - mixins"><span class="kg-bookmark-author">countnazgul</span><span class="kg-bookmark-publisher">GitHub</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://avatars2.githubusercontent.com/u/22850?s=400&amp;v=4" alt="enigma.js - mixins"></div></a></figure><p>Hope you liked it!</p><p>Stefan</p>]]></content:encoded></item><item><title><![CDATA[Introducing qlbuilder]]></title><description><![CDATA[CLI tool for Qlik Sense which allow developers to write scripts locally and sync with Qlik Sense]]></description><link>https://sstoichev.eu/2019/10/08/introducing-qlbuilder/</link><guid isPermaLink="false">5d95b3683c4ccc7663e24936</guid><category><![CDATA[qlik]]></category><category><![CDATA[qlik sense]]></category><category><![CDATA[nodejs]]></category><dc:creator><![CDATA[Stefan Stoychev]]></dc:creator><pubDate>Tue, 08 Oct 2019 08:30:00 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1516981879613-9f5da904015f?ixlib=rb-1.2.1&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><h3 id="qlbuilder">qlbuilder</h3>
<img src="https://images.unsplash.com/photo-1516981879613-9f5da904015f?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&ixid=eyJhcHBfaWQiOjExNzczfQ" alt="Introducing qlbuilder"><p>In general <code>qlbuilder</code> is a tool that communicates with Qlik apps (using the Engine API) and set the script. The script is located on the local machine and can be &quot;stitched&quot; from multiple files.</p>
<h3 id="why">Why?</h3>
<p>Everyone have their own preferred IDE/text editor and we can write own <code>Qlik Sense</code> load scripts there and use <code>include</code>/<code>must_include</code> inside the app to &quot;bring&quot; the script inside the app.</p>
<p>But it will be nice if we can write the scrips locally and upload them directly to the app as it is.</p>
<p>The basic idea is that we will write each <code>Qlik Sense</code> script tab in a separate <code>.qvs</code> file and <code>qlbuilder</code> will combine them (when needed) in a single file and set it's content to the dedicated <code>Qlik Sense</code> app (on dedicated QS environment).</p>
<p><code>qlbuilder</code> can perform few more actions, apart from combining and setting the script: check for syntax errors, reload app, &quot;watching&quot; for script changes and check for syntax errors etc.</p>
<p>With this approach I can write my script in <code>Visual Studio Code</code> (in combination with <a href="https://marketplace.visualstudio.com/items?itemName=Gimly81.qlik">Qlik for Visual Studio Code<br>
</a> extension) and use all VS Code shortcuts, themes etc.</p>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h3 id="installation">Installation</h3>
<p><code>qlbuilder</code> is a <code>Node JS</code> module and can be installed as a global package</p>
<pre><code class="language-javascript">npm install -g qlbuilder
</code></pre>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h3 id="folderstructure">Folder structure</h3>
<p>The folder structure used from <code>qlbuilder</code> looks like this:</p>
<pre><code class="language-yml">project-name
│   config.yml
│───dist
│      LoadScript.qvs
└───src
    │   0--Main.qvs
    │   1--Config.qvs
    │   2--Mapping.qvs
    │   ...
</code></pre>
<p><code>src</code> is the main folder. In this folder are located all <code>qvs</code> files. Each file will result in a separate script tab, when uploaded. The <code>.qvs</code> files are combined in <strong>alphabetical order</strong> (for now. please have a look at the <strong>Road map</strong> section at the end of this post)</p>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h3 id="configfiles">Config files</h3>
<p>There are two configuration files that are used from <code>qlbuilder</code>:</p>
<p><strong>config.yml</strong></p>
<p>This file is mandatory and it's created during the project/folder creation. This file is located in the root folder and defines the <code>Qlik Sense</code> environments and apps ID that are relevant to this script.</p>
<p>The file is in <code>yaml</code> format and looks like this:</p>
<pre><code class="language-yaml">- name: dev
  host: 192.168.0.100 # IP/FQDN of QS engine (central node) 
  secure: false # default value is true 
  appId: 12345678-1234-1234-1234-12345678901
  authentication:
    type: winform
</code></pre>
<p>The above config defines:</p>
<ul>
<li>QS environment called <code>dev</code></li>
<li>the environment's host is <code>192.168.0.100</code></li>
<li>the environment is access through <code>http</code></li>
<li>the <code>appId</code> (that this script belongs to) is <code>12345678-1234-1234-1234-12345678901</code></li>
<li>the authentication for this environment is Windows/Forms - <code>winform</code></li>
</ul>
<p>(for more information on authentication types and other possible settings please have a look at <a href="https://github.com/countnazgul/qlBuilder">qlbuilder GitHub repo</a>)</p>
<p>The config contains the &quot;public&quot; environment information and can be commit to git repo.</p>
<p><strong>.qlbuilder.yml</strong></p>
<p>The non-public info (authentication information) is held in another configuration file <code>.qlbuilder.yml</code> (there is option to use environment variables as well)</p>
<p>This file is not created through the install/create process and should be located in current user home folder. For example: <code>C:\Users\My-User-Name\.qlbuilder.yml</code>. Using the above example, with <code>winform</code> authentication and environment named <code>dev</code>, the config will looks like:</p>
<pre><code class="language-yml">dev:
  QLIK_USER: DOMAIN\username
  QLIK_PASSWORD: my-secret-password
</code></pre>
<p>This information will be used by <code>qlbuilder</code> during the authentication process to generate the <code>sessionId</code></p>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h3 id="commands">Commands</h3>
<p><code>qlbuilder</code> is executed from command line and accept few parameters:</p>
<ul>
<li>
<p><code>create [project name]</code> - create the initial project folder structure, sample script and config file. If folder with the same name already exists, and the developer agree to re-create, then this command will re-create only the <code>qlbuilder</code> specific files and folders and will not modify any other folders or files</p>
</li>
<li>
<p><code>build</code> - combines all <code>qvs</code> files from <code>src</code> folder into a single load script. The result script is created in <code>dist</code> folder (ideally this file should not be edited)</p>
</li>
<li>
<p><code>checkscript [env name]</code> - check the script for syntax errors. The check is made against temp (session) Qlik app</p>
</li>
<li>
<p><code>getscript [env name]</code> - gets the script from the app id (specified in <code>config.yml</code>) and splits it into multiple <code>qvs</code> files (in <code>src</code> folder). <strong>This operation deletes all files in <code>src</code> folder as first step!</strong></p>
</li>
<li>
<p><code>reload [env name]</code> - reloads the QS app and output the reload progress. (the reload is performed on the QS Engine itself)</p>
</li>
<li>
<p><code>setscript [env name]</code> -</p>
<ul>
<li>builds the script from the files in <code>src</code> folder</li>
<li>checks the script for syntax errors (if there are errors the process is stopped)</li>
<li>opens the QS app and replaces the script with the build one</li>
<li>saves the app</li>
</ul>
</li>
<li>
<p><code>watch [env name]</code> - now this is a funny one :) ... when started in <code>watch</code> mode <code>qlbuilder</code> will check for syntax error on each file save (any <code>qvs</code> file in <code>src</code> folder). During <code>watch</code> mode few sub-commands that can be used:</p>
<ul>
<li><code>s</code> or <code>set</code> - sets the script to the app and saves it</li>
<li><code>r</code> or <code>rl</code> - reloads the app (if successful the app is saved)</li>
<li><code>c</code> or <code>cls</code> - clears the console</li>
<li><code>x</code> - exit <code>qlbuilder</code></li>
<li><code>?</code> - outputs these commands again</li>
</ul>
</li>
</ul>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h3 id="versioncontrol">Version Control</h3>
<p>Having the script locally allows us to put the project folder under version control and keep history of the script changes (anyone thinking about git hooks as well? 😉)</p>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h3 id="roadmap">Road map</h3>
<p>More or less <code>qlbuilder</code> includes all the features I had in mind initially. But there are few things that can be added:</p>
<ul>
<li><code>include</code> / <code>must_include</code> - (optional config) detect if these statements are present in the script. If found - get the content of the included scripts and replace the statement with the actual script. This can &quot;save&quot; apps from failing because of external script being edited after the app is published</li>
<li>different way to organize script files? - at the moment the script files are combined based on alphabetical order. It might be a bit annoying when adding new file in the middle of the script - all files that follow need to be renamed</li>
</ul>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><p>For more information please have a read at the readme at the <a href="https://github.com/countnazgul/qlBuilder">qlbuilder GitHub repo</a></p>
<p>Fell free to <a href="https://github.com/countnazgul/qlBuilder/issues">open issues there</a> if you experiencing any problems or have requests/suggestions</p>
<p><u><strong>Any suggestions are very welcome!</strong></u></p>
<p><strong>Hope you liked it!</strong><br>
<strong>Stefan</strong></p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Deploy Qlik extension/mashup with Qlik-CLI]]></title><description><![CDATA[Quick way to upload local extension/mashup to Qlik using Qlik-CLI]]></description><link>https://sstoichev.eu/2019/07/18/deploy-extension-mashup-with-qlik-cli/</link><guid isPermaLink="false">5d2c7d5484544811d554f5da</guid><category><![CDATA[qlik]]></category><category><![CDATA[qlik sense]]></category><category><![CDATA[powershell]]></category><category><![CDATA[qlik-cli]]></category><dc:creator><![CDATA[Stefan Stoychev]]></dc:creator><pubDate>Thu, 18 Jul 2019 08:15:00 GMT</pubDate><media:content url="https://sstoichev.eu/content/images/2019/07/photo-1556912743-4a6f9e70dbd4-1.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><h1 id="deployqlikextensionmashupwithqlikcli">Deploy Qlik extension/mashup with Qlik-CLI</h1>
<img src="https://sstoichev.eu/content/images/2019/07/photo-1556912743-4a6f9e70dbd4-1.jpg" alt="Deploy Qlik extension/mashup with Qlik-CLI"><p>Creating Qlik extensions/mashups is usually quite fun. But it will be better if you have a way to develop locally and deploy to Qlik.</p>
<p>Usually I'm using <a href="https://sstoichev.eu/2019/07/18/deploy-extension-mashup-with-qlik-cli/link-here">qExt</a> or <a href="https://sstoichev.eu/2019/07/18/deploy-extension-mashup-with-qlik-cli/link-here">sense-go</a> but in this specific case I didn't had the chance to use either (enterprise restrictions)</p>
<p>Luckily for me <a href="https://github.com/ahaydon/Qlik-Cli">Qlik-CLI</a> was available. And <code>Qlik-CLI</code> is doing a great job dealing with Qlik Repository API.</p>
<p>I've decided to write a small <code>PowerShell</code> script that can be used to import the extension from my local PC to Qlik.</p>
<p>The script will perform the following actions when started:</p>
<ul>
<li>generate <code>wbfolder.wbl</code> file - this is not mandatory but without this file the extension files will not be available for edit in <code>dev-hub</code></li>
<li>compress the <code>src</code> folder and place the archive into the <code>build</code> folder</li>
<li>connect to Qlik</li>
<li>check if extension with the same name already exists and if there is one - delete it (assume this is an older version)</li>
<li>import the archive from <code>build</code> folder</li>
</ul>
<p>After the script is done the new extension/mashup code will be available.</p>
<h2 id="folderstructure">Folder structure</h2>
<p>The script assume the following folder structure (need to exists before run)</p>
<pre><code>root
│
└──+ build
│    └─── extension-name.zip (auto-generated and uploaded)
│   
└──+ src
│    │───+ static (optional)
│    │     │─── image.png
│    │     └─── ...
│    │      
│    │─── extension-name.css
│    │─── extension-name.js
│    │─── extension-name.qext
│    │─── wbfolder.wbl
│    └─── ...
│
└─── upload.ps1
</code></pre>
<h2 id="usage">Usage</h2>
<ul>
<li>copy the code from below and create <code>upload.ps1</code> file in your root folder (the file name can be anything).</li>
<li>edit the initial variables (<code>$extensionName</code> and <code>$qEnv</code>) to match your extension name and QS DNS</li>
<li>every time when need to upload the extension/mashup just start the <code>upload.ps1</code></li>
</ul>
<h2 id="summary">Summary</h2>
<p>Probably not the most elegant way to achieve this but might help if you need quick and &quot;dirty&quot; way to make (or edit exiting one) extension/mashup and use your text editor/IDE of choice.</p>
<p>And since this approach is using <code>PowerShell</code> you can extend it to your needs ;)</p>
<h2 id="powershellscript">PowerShell Script</h2>
<pre><code class="language-powershell">$extensionName = &quot;mashup-extension-name&quot;
$qEnv = &quot;computerName&quot;

function generateWBL {
    # remove the existing wbfolder.wbl (if exists)
    Remove-Item .\src\wbfolder.wbl -ErrorAction Ignore

    # list all files under the src folder and add them to the wbl file
    $existingFiles = Get-ChildItem -Path .\src\ -File |
        foreach {$_.name} |
        Out-File -FilePath .\src\wbfolder.wbl
}

function compressFolder{
    # archive the content of the src folder
    Compress-Archive -Path .\src\* -DestinationPath .\build\$extensionName.zip -Force
    Write-Host ZIP file created
}

function connectQlik {
    ## connect to Qlik using Qlik-CLI
    Connect-Qlik -computerName &quot;$qEnv&quot; -TrustAllCerts
    Write-Host Connected to Qlik
}

function removeOldExtVersion {
    # Query Qlik for extension with the same name
    $ext = Get-QlikExtension -Filter &quot;name eq '$extensionName'&quot; | Measure-Object

    # if there is such extension - delete it
    if ( $ext.Count -gt 0) {
        Remove-QlikExtension -ename &quot;$extensionName&quot;
        Write-Host Older version found and removed
    }
}

function importExtension {
    # Import the archive for the build folder
    Import-QlikExtension -ExtensionPath .\build\$extensionName.zip
    Write-Host New version was imported
}

function main {
  generateWBL
  compressFolder
  connectQlik
  removeOldExtVersion
  importExtension
  
  Write-Host DONE
}

main
</code></pre>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Global Environment Values Approach (Qlik Sense)]]></title><description><![CDATA[Utilise Qlik Sense Custom Properties to create Global Environment values which can be used in load script]]></description><link>https://sstoichev.eu/2019/07/16/qlik-sense-environment-values-approach/</link><guid isPermaLink="false">5d2c579884544811d554f5d8</guid><category><![CDATA[qlik]]></category><category><![CDATA[qlik sense]]></category><dc:creator><![CDATA[Stefan Stoychev]]></dc:creator><pubDate>Tue, 16 Jul 2019 08:49:00 GMT</pubDate><media:content url="https://sstoichev.eu/content/images/2019/07/nasa-Q1p7bh3SHj8-unsplash.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://sstoichev.eu/content/images/2019/07/nasa-Q1p7bh3SHj8-unsplash.jpg" alt="Global Environment Values Approach (Qlik Sense)"><p> </p><!--kg-card-begin: markdown--><p>With multi-stage Qlik environment (DEV, TEST, PROD etc) the load scripts are usually including some kind of a check - &quot;<strong>In which environment am I?</strong>&quot;.</p>
<p>Based on the result different/additional actions might be performed. For example:</p>
<ul>
<li>if the environment is not PROD then mask/scramble the client names</li>
<li>use different data connection names (providers) on each environment</li>
<li>...</li>
</ul>
<p>It will be nice if there is something more centralised that can provide this information (and not only) to any app on the cluster. Something like a global meta-data &quot;storage&quot; that can be changed/updated (by the administrators)</p>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><p>In general my approach is to use a dedicated Custom Property and each value to be in format <code>key:pair</code>. Values can be accessed for reading using a (dedicated) REST data connection in the beginning of each script. If we keep the keys the same then we can write one script and use it on every environment.</p>
<!--kg-card-end: markdown--><p></p><!--kg-card-begin: markdown--><h3 id="customproperty">Custom Property</h3>
<p>We will create new custom property (in my case I've named it <code>ENVIRONMENT</code>) and all the global values will be added to this CP.</p>
<p>In <code>QMC</code>:</p>
<ul>
<li>create new custom property</li>
<li>add as many CP values as you want as <code>key:value</code> pairs (I've picket <code>:</code> as a separator but this can be anything). For example:
<ul>
<li><code>name:DEV</code></li>
<li><code>central repository ip:192.168.1.10</code></li>
<li><code>central repository host:qlik.central.repo</code></li>
</ul>
</li>
</ul>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://sstoichev.eu/content/images/2019/07/custom-property.PNG" class="kg-image" alt="Global Environment Values Approach (Qlik Sense)"><figcaption>Created ENVIRONMENT custom property</figcaption></figure><!--kg-card-begin: markdown--><h3 id="dataconnection">Data Connection</h3>
<p>We will use the Repository Service REST API to get the <code>ENVIRONMENT</code> custom property values. We need to create a new data connection that will be used to extract the data.</p>
<p>During the installation QS creates few <code>monitor_apps_REST_*</code> data connection which are used by the documents in the <code>Monitoring apps</code> stream (like <code>License Monitor</code> or <code>Operations Monitor</code>). We can use one of these connectors, as a base and create new one that will return the values only for our custom property.</p>
<ul>
<li>open any application and create new <code>Data Connection</code> (at this point there is no need the connection to be a specific type. We are creating just a placeholder)</li>
<li>In QMC -&gt; <strong>Data Connection</strong> -&gt; <strong>monitor_apps_REST_task</strong> -&gt; copy the <code>Connection string</code></li>
<li>go back and edit the placeholder connection (in my case <code>Environment</code>)</li>
<li>replace the connection string with the one from <code>monitor_apps_REST_task</code> and apply the change</li>
<li>go back to the application and edit the <code>Environment</code> connection</li>
<li>in the URL replace <code>/task/</code> with <code>/custompropertydefinition/</code></li>
<li>in the <code>Query parameters</code> section add new one:
<ul>
<li>Name: <code>filter</code></li>
<li>Value: <code>name eq 'ENVIRONMENT'</code></li>
</ul>
</li>
<li>Save</li>
</ul>
<p>At this point you can preview the data from the connection and if everything is ok you should see something like this:</p>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="https://sstoichev.eu/content/images/2019/07/data-connection-1.PNG" class="kg-image" alt="Global Environment Values Approach (Qlik Sense)"></figure><!--kg-card-begin: markdown--><h3 id="usage">Usage</h3>
<p>Once all is set we can create a small QS script that gets the raw values and &quot;convert&quot; them to a more readable QS format in form of a table with two columns: <code>ENV_NAME</code> and <code>ENV_VALUE</code></p>
<pre><code class="language-sql">LIB CONNECT TO 'Environment';

RestConnectorMasterTable:
SQL SELECT 
  &quot;__KEY_root&quot;,
  (SELECT 
    &quot;@Value&quot;,
    &quot;__FK_choiceValues&quot;
   FROM &quot;choiceValues&quot; FK &quot;__FK_choiceValues&quot; ArrayValueAlias &quot;@Value&quot;)
FROM JSON (wrap on) &quot;root&quot; PK &quot;__KEY_root&quot;;

ENVIRONMENT:
Load
  subfield([@Value], ':', 1) as ENV_NAME,
  subfield([@Value], ':', 2) as ENV_VALUE
;
Load
  [@Value],
  [__FK_choiceValues] as [__KEY_root]
RESIDENT 
  RestConnectorMasterTable
WHERE 
  NOT IsNull([__FK_choiceValues]);

DROP TABLE RestConnectorMasterTable;
</code></pre>
<p>This script can be placed in the beginning of each app OR even better - host the script somewhere and each app (on each environment) can <code>Must_Include</code> it.</p>
<p>Upon reload only one table is returned:</p>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="https://sstoichev.eu/content/images/2019/07/data-connection-data-1.PNG" class="kg-image" alt="Global Environment Values Approach (Qlik Sense)"></figure><!--kg-card-begin: markdown--><h3 id="conclusion">Conclusion</h3>
<p>Having a centralised place for a cluster-wide variables is a nice to have. I've seen multiple approaches on how to deal with this: specific document prefixes for each env, flat files in each data folder etc. But the problem there is that there is always enough space for someone to make a mistake (overwrite the flat file when moving data is my favorite)</p>
<p>Another plus for custom property and data connection is that these objects are subject of security rules. Which means that we can grant everyone read access over them but very limited audience can edit them which (might 🤞) lead to less errors and maintenance.</p>
<!--kg-card-end: markdown--><p></p><!--kg-card-begin: markdown--><h3 id="bonus">Bonus</h3>
<p>If you have/use <a href="https://github.com/ahaydon/Qlik-Cli">Qlik-CLI</a> then the <code>PowerShell</code> script below will do the &quot;dirty&quot; setup for you. The script will:</p>
<ul>
<li>create new custom property with some pre-defined values</li>
<li>create new REST data connection using the <code>monitor_apps_REST_task</code> as a template</li>
<li>edit the new data connection
<ul>
<li>change the URL</li>
<li>add the additional query parameter</li>
</ul>
</li>
</ul>
<pre><code class="language-powershell"># QS Central Repo Address
$computerName = &quot;qlik.central.repo&quot; # or localhost if started on the server itself

# Properties for the new data connection
# ideally the user/pass should be the ones
# under which the QS service is running
$dataConnProps = @{
  name = &quot;Environment&quot;
  userName = &quot;domain\username&quot;
  password = &quot;password&quot;
}

# Properties for the new custom property
# Can populate some values when creating it
# &quot;Objects = App&quot; is just to create the CP (cant create it without it from Qlik-CLI)
$customPropProps = @{
  name = &quot;ENVIRONMENT&quot;
  values = @(&quot;name:DEV&quot;, &quot;central repository ip:192.168.1.10&quot;)
  objects = @(&quot;App&quot;)
}


function createNewDataConnection {
    # Get the existing monitor_apps_REST_task connection details
    $connector = Get-QlikDataConnection -filter &quot;name eq 'monitor_apps_REST_task'&quot;

    # Replace the endoint to point to return custom prop details instead of a task
    # Add one more query parameter which will return data only for the Environment custom prop
    $newConnectionString = $connector.connectionString.
        Replace(&quot;/qrs/task/full&quot;, &quot;/qrs/custompropertydefinition/full&quot;).
        Replace(&quot;queryParameters=xrfkey%20000000000000000&quot;, &quot;queryParameters=xrfkey%20000000000000000%1filter%2name eq '$($customPropProps.name)'&quot;)

    # Create the connection
    New-QlikDataConnection -name $dataConnProps.name -connectionstring $newConnectionString -type &quot;QvRestConnector.exe&quot; -username $dataConnProps.userName
}

function createNewCustomProperty {

    # Create the Environment custom property
    New-QlikCustomProperty -name $($customPropProps.name) -choiceValues $($customPropProps.values) -objectTypes $($customPropProps.objects)
}

function connectToQlik {
    Connect-Qlik -computerName $computerName -TrustAllCerts
}

function main {
    connectToQlik
    createNewCustomProperty
    createNewDataConnection
}

main
</code></pre>
<!--kg-card-end: markdown--><p>Stefan</p>]]></content:encoded></item><item><title><![CDATA[Qlik for Visual Studio Code]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>Recently I've started working on one of the idea from my wild ides list. For info <a href="https://github.com/countnazgul/qlikview-projects-documentation">click here</a> (but more on it later when there is something working to be shown). For this we need to write our QV scrips in external text editor. My personal choose is <a href="https://code.visualstudio.com/">VS Code</a></p>]]></description><link>https://sstoichev.eu/2017/04/23/qlik-for-visual-studio-code/</link><guid isPermaLink="false">597a16505a9595161cd1c4d5</guid><dc:creator><![CDATA[Stefan Stoychev]]></dc:creator><pubDate>Sun, 23 Apr 2017 19:48:56 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>Recently I've started working on one of the idea from my wild ides list. For info <a href="https://github.com/countnazgul/qlikview-projects-documentation">click here</a> (but more on it later when there is something working to be shown). For this we need to write our QV scrips in external text editor. My personal choose is <a href="https://code.visualstudio.com/">VS Code</a>.</p>
<p>Quick search in the VS Code extension marketplace shows that there is <a href="https://marketplace.visualstudio.com/items?itemName=Gimly81.qlik">Qlik for Visual Studio Code</a> extension for highlighting QV code developed by <a href="https://github.com/Gimly">Xavier Hahn</a>. The extension is working very nice and if we open a file with <code>.qvs</code> extension then the text will be highlighted with the QV syntax.</p>
<p>I've spend few days copy/pasting and web scraping the Qlik Help website but managed to add and snippets integration in the extension. So now when <code>.qvs</code> file is open there will be code highlight and IntelliSense (auto-complete but not only) integration as well.</p>
<ul>
<li>
<p>Highlight (screen from VS Code marketiplace)<br>
<img src="https://raw.githubusercontent.com/Gimly/vscode-qlik/master/images/syntax.png" alt="VS Code Marketplace"></p>
</li>
<li>
<p>IntelliSense  integration - filter the functions as you type<br>
<img src="https://sstoichev.eu/content/images/2017/04/VSCode1.png" alt="IntelliSense  integration"></p>
</li>
<li>
<p>Descriptions - press the <code>i</code> button to view the function description (not for all functions. Will slowly add as much as I can)<br>
<img src="https://sstoichev.eu/content/images/2017/04/VSCode3.png" alt="Function description"></p>
</li>
<li>
<p>Quick code - pick function from the drop-down and VS Code will point you to the first parameter(<code>YearId</code> in this example). When finished with it just press <code>Tab</code> key to move to the next one (<code>WeekId</code>)<br>
<img src="https://sstoichev.eu/content/images/2017/04/VSCode2.png" alt="Auto complete"></p>
</li>
</ul>
<h5 id="install">Install</h5>
<p>To install the extension:</p>
<ul>
<li>open VS Code and press <code>Ctrl + P</code></li>
<li>type/paste <code>ext install qlik</code></li>
<li>click on the extension on the left side</li>
<li>click on <code>Install</code> button</li>
<li>after the installation is complete VS Code will want to be restarted</li>
<li>after the restart every <code>.qvs</code> file will benefit from the extension</li>
</ul>
<h5 id="usingqvsfiles">Using <code>.qvs</code> files</h5>
<p>External script files can be used in QlikView and Qlik Sense using <code>Inclide</code> or <code>Must_Include</code> commands. Both commands will try to execute the script in the file but <code>Must_Incliude</code> will result in script error if the external file is not found. <code>Include</code> will ignore the error and the script will continue.</p>
<p>Usage examples:</p>
<ul>
<li><code> $(Include=..\scripts\MappingTablesLoad.qvs);</code></li>
<li><code> $(Must_Include=c:\Projects\TempProject\scripts\FactLoad.qvs);</code></li>
</ul>
<p>Hope you liked it!<br>
Stefan</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Qlik Sense Custom Hub]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>I like how flexible Qlik Sense is. With its massive API if we want we can re-build the GUI (and not only) from scratch and just use the background services (like the Engine or Repository).</p>
<p>I have some (small) issues with how the current Hub looks and behaves and decided</p>]]></description><link>https://sstoichev.eu/2016/11/13/qlik-sense-custom-hub/</link><guid isPermaLink="false">597a16505a9595161cd1c4d4</guid><category><![CDATA[qlik sense]]></category><category><![CDATA[hub]]></category><dc:creator><![CDATA[Stefan Stoychev]]></dc:creator><pubDate>Sun, 13 Nov 2016 22:12:34 GMT</pubDate><media:content url="https://sstoichev.eu/content/images/2016/11/CustomHUB1-1.png" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://sstoichev.eu/content/images/2016/11/CustomHUB1-1.png" alt="Qlik Sense Custom Hub"><p>I like how flexible Qlik Sense is. With its massive API if we want we can re-build the GUI (and not only) from scratch and just use the background services (like the Engine or Repository).</p>
<p>I have some (small) issues with how the current Hub looks and behaves and decided to build something simple that fits my needs.</p>
<p>At the end I've wanted something simple that will provide me with quick search capabilities, few shortcuts and the options to create, delete and duplicate files.</p>
<p>And below is the final result</p>
<p>The code is available in <a href="https://github.com/countnazgul/sense-custom-hub">GitHub</a></p>
<p><img src="https://sstoichev.eu/content/images/2016/11/CustomHUB1.png" alt="Qlik Sense Custom Hub"></p>
<p>Just a search box, list with all the apps (to which I have access) and create new app button.</p>
<h5 id="searching">Searching</h5>
<p>For the search <a href="http://easyautocomplete.com/">easyautocomplete</a> plugin is used.</p>
<p>The search works as any other search box. The search is performed on the QS apps titles<br>
<img src="https://sstoichev.eu/content/images/2016/11/CustomHUB2.png" alt="Qlik Sense Custom Hub"></p>
<h5 id="createnewapp">Create new app</h5>
<p>Pressing the top right green button will show the create new app modal</p>
<p><img src="https://sstoichev.eu/content/images/2016/11/CustomHUB_createApp.png" alt="Qlik Sense Custom Hub"></p>
<p>Here the name of the new app is specified and which will be the initial screen after the app is created. As developer (and not only) &quot;Data editor&quot;  or &quot;Data manager&quot; are the main ones.</p>
<h5 id="options">Options</h5>
<p>Agains each app few shortcuts are present</p>
<p><img src="https://sstoichev.eu/content/images/2016/11/CustomHUB_appoptions.png" alt="Qlik Sense Custom Hub"></p>
<ul>
<li>to which stream the app belongs</li>
<li>open the &quot;App Overview&quot;</li>
<li>open the &quot;Data Manager&quot;</li>
<li>open the &quot;Data Editor&quot;</li>
<li>open the &quot;Data Model Viewer&quot;</li>
<li>Duplicate app <strong>(not available in Desktop mode)</strong></li>
<li>Delete app</li>
</ul>
<p>Hope you liked it!<br>
Stefan</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Termux]]></title><description><![CDATA[Use Node from Chromebook with Termux]]></description><link>https://sstoichev.eu/2016/10/31/termux/</link><guid isPermaLink="false">597a16505a9595161cd1c4d3</guid><category><![CDATA[chromebook]]></category><category><![CDATA[linux]]></category><category><![CDATA[termux]]></category><category><![CDATA[gulp]]></category><category><![CDATA[nodemon]]></category><dc:creator><![CDATA[Stefan Stoychev]]></dc:creator><pubDate>Mon, 31 Oct 2016 01:40:33 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>Recently I've found a very neat <code>Adnroid</code> app which I can run on my Chromebook - <a href="https://play.google.com/store/apps/details?id=com.termux">Termux</a>- <strong>&quot;Termux combines powerful terminal emulation with an extensive Linux package collection.&quot;</strong></p>
<p>In essence after installing it you will have a nice Linux distro on your Android device without root.</p>
<p>In my case I've decided to try if I can use Termux for Node development.</p>
<h5 id="nodedevelopment">Node development</h5>
<p>The initial plan was to install <code>node</code> inside Termux and to use it to install <code>npm</code> packages and run code.</p>
<p>After installing the app there is a need to run <code>termux-setup-storage</code> command from within Termux to get access to the Chromebook storage. After this I've tried to install some packages directly in the <code>Downloads</code> folder but unfortunately because Termux do not provide root access I was unable to install them.</p>
<p>The plan was changed then. I've decided to install <code>npm</code> packages inside <code>Termux</code>, write my code inside some editor (like <a href="https://chrome.google.com/webstore/detail/caret/fljalecfjciodhpcledpamjachpmelml">Caret</a>) and save it somewhere in the <code>Downloads</code> folder and then move only the code file(s) into <code>Termux</code> where the <code>npm</code> packages are installed and the server is running.</p>
<p>Installing the packages inside Termux and copying manually the main js file worked just fine but this needed to be more automated.</p>
<h5 id="gulp">Gulp</h5>
<p>Since I was going to use Node I've decided to use <a href="http://gulpjs.com/">Gulp</a> for the automated copy (and for the other tasks as well). I'm using the code below to sync the files between <code>source</code> folder and <code>destination</code> folder with <a href="https://www.npmjs.com/package/gulp-newer">gulp-newer</a> plugin (there are few other plungs that can be used as well)</p>
<pre><code class="language-language-javascript">var gulp = require('gulp');
var newer = require('gulp-newer');

var source = '/data/data/com.termux/files/home/storage/downloads/nodetest';
var destination = '../nodetest'

gulp.task('sync', function() {
	return gulp.src(source + '/**/*')
		.pipe(newer(destination))
		.pipe(gulp.dest(destinatioin));
});

gulp.task('watch', function() {
	gulp.watch(source + '/**/*', ['sync']);
});
</code></pre>
<p>After starting the <code>gulp watch</code> task every time file in the <code>source</code>  folder is changed it will be moved in the <code>destination</code> folder.</p>
<h5 id="nodemon">nodemon</h5>
<p>I've also installed <a href="http://nodemon.io/">nodemon</a> so after every file change the server will be restarted automatically. In my case I've used <code>--delay 2.5</code> as a parameter in <code>nodemon</code> to delay the restart with 2.5 seconds - just to make sure that all files are copied.</p>
<h5 id="result">Result</h5>
<p>If <code>Termux</code> is used on phone or tabled to view your web app you just need to navigate to <code>http://localhost</code>. For Chromebooks the situation is a bit more different. You need to find the <code>ip</code> address of the Android instance. This can be done by executing the following command from <code>Termux</code>:</p>
<pre><code>ip addr list
</code></pre>
<p>And look for the non-localhost address. (in my case this was <code>192.168.254.2</code>)</p>
<h5 id="conclusion">Conclusion</h5>
<p>In conclusion I can say that <code>Termux</code> opens a <strong>lot</strong> of possibilities for Chromebook/Android developers without entering to Dev mode.</p>
<p>Hope you like it!<br>
Stefan</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[qSocks snippets extension for VSCode]]></title><description><![CDATA[Build your qSocks based app quickly using snippets from within VSCode]]></description><link>https://sstoichev.eu/2016/10/10/qsocks-snippets-extension-for-vscode/</link><guid isPermaLink="false">597a16505a9595161cd1c4d1</guid><category><![CDATA[qlik sense]]></category><category><![CDATA[snippet]]></category><category><![CDATA[vscode]]></category><dc:creator><![CDATA[Stefan Stoychev]]></dc:creator><pubDate>Mon, 10 Oct 2016 10:55:03 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>At the moment <a href="https://code.visualstudio.com">VSCode</a> is my preferred code editor (although I'm side using <a href="https://atom.io/">Atom</a> sometimes as well). And when I'm writing something which uses <a href="https://github.com/mindspank/qsocks">qSocks</a> I've always have GitHub and <a href="http://help.qlik.com/">Qlik Sense help site</a> open so I can check the available methods and what parameters need to be provided for these methods. Plus I'm a &quot;bit&quot; lazy and always copy/paste code from another projects I have and adapt it to the current needs.</p>
<p>For these reasons I've made <a href="https://marketplace.visualstudio.com/items?itemName=stefanstoichev.qsockssnippet">VSCode snippets extension</a>. The extension is used from within VSCode and include all qSocks methods as snippets. Just type <code>qsocck</code> and pick the method that you need.</p>
<p>For example if <code>qsocks.global.qTProduct</code> method is picked the following code will be generated:</p>
<pre><code class="language-language-javascript">global.qTProduct().then(function(product) {

});
</code></pre>
<p>If your local variable is different from <code>global</code> you just need to replace it in the generated code. Also the returned variable <code>product</code> can be replaced if you are not happy with the convention. But the main code will be generated automatically.</p>
<p>The same is applied and for the request parameters. For example the <code>qsocks.global.createSessionAppFromApp</code> method expect source app id as parameter. The snipped will generate the following code:</p>
<pre><code class="language-language-javascript">global.createSessionAppFromApp('qSrcAppId').then(function(sessionApp) {

});&quot;
</code></pre>
<p>In the above example <code>qSrcAppId</code> need to be replaced with the app id you need (also <code>global</code> and <code>sessionApp</code> if you want)</p>
<p>Another advantage of using snippets is that you can prepare your logic very quickly instead writing (or copy/paste from somewhere) all the code. Just chain your methods, quickly picking them from the list, change the necessary input/output variables and focus on building your internal logic.</p>
<p>The extension include snippets for all qSocks methods:</p>
<ul>
<li>main (config and connection)</li>
<li>doc</li>
<li>global</li>
<li>field</li>
<li>generic Bookmark</li>
<li>generic Dimension</li>
<li>generic Measure</li>
<li>generic Object</li>
<li>generic Variable</li>
</ul>
<p>To install the extension:</p>
<ul>
<li>open VSCode</li>
<li>press <code>Ctrl+P</code></li>
<li>paste <code>ext install qsockssnippet</code></li>
<li>follow the instructions (VSCode need to be restarted in order to install the extension)</li>
<li>thats it - just start typing <code>qsocks</code> and pick the method.</li>
</ul>
<p>Hope you like it!<br>
Stefan</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Qlik Sense Mail Notifications]]></title><description><![CDATA[How to have mail notifications in Qlik Sense. More detailed explanations on log4net method]]></description><link>https://sstoichev.eu/2016/04/24/qlik-sense-tasks-notifications/</link><guid isPermaLink="false">597a16505a9595161cd1c4cf</guid><category><![CDATA[qlik]]></category><category><![CDATA[qlik sense]]></category><category><![CDATA[notifications]]></category><category><![CDATA[log4net]]></category><dc:creator><![CDATA[Stefan Stoychev]]></dc:creator><pubDate>Sun, 24 Apr 2016 10:23:00 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>One feature that is missing in Qlik Sense is to send mail notifications when task is completed (fail or success). This feature is available in QlikView Server and I'm finding it very useful.</p>
<p>I've found 3 ways that this can be achieved in QS:</p>
<ul>
<li>Log files scraping</li>
<li>QRS API</li>
<li>log4net</li>
</ul>
<p>I'll cover the third option in details</p>
<h5 id="logfiles">Log files</h5>
<p>Every QS service have it's own log folder where the activity is logged. Batch file can be written that monitor the <code>Scheduler</code> service log file and on change to scrap the content and mail the result.</p>
<h5 id="qrsapi">QRS API</h5>
<p>Ideally this is the most correct way to achieve mail notifications. Qlik Sense Repository Service (QRS) is a REST API service which can be consumed from any language. <a href="https://help.qlik.com/en-US/sense-developer/2.2/Subsystems/RepositoryServiceAPI/Content/RepositoryServiceAPI/RepositoryServiceAPI-Notification-Add-Change-Subscription.htm?q=notification">Notification</a> end points can be set to visit specific url on specific event. Additional web server need to be set and running to handle the calls from QRS.<br>
The downside of this approach is that the <code>notification</code> is session based. Which means that if you restart your web server or QRS service is restarted all notifications are gone and need to be created again. Of course this is not a blocker and with additional logic can be handled. Personally I like this approach and will build something in the future using it (already started it <a href="https://github.com/countnazgul/notification-engine">here</a>)</p>
<h5 id="log4net">log4net</h5>
<p><a href="https://twitter.com/mindspank">Alex</a> point few days back that QS have build in <a href="https://logging.apache.org/log4net/">log4net</a> functionality which can be used to trigger some events when the QRS log file is updated - send mail, append to another file, add event in windows events, write to database, send udp request etc (the full list is <a href="http://help.qlik.com/en-US/sense/2.2/Subsystems/PlanningQlikSenseDeployments/Content/Server/Server-Logging-Using-Appenders-QSRollingFileAppender-Built-in-Appenders.htm">here</a>).</p>
<p>Basically you just need to create <code>LocalLogConfig.xml</code> file in the service folder which you want to monitor - <code>C:\ProgramData\Qlik\Sense\&lt;ServiceName&gt;</code></p>
<p>For example below is what the content of <code>LocalLogConfig.xml</code> looks like if you want to send UDP request to UDP server for the Scheduler service:</p>
<pre><code class="language-language-xml">&lt;?xml version=&quot;1.0&quot;?&gt;
&lt;configuration&gt;
    &lt;appender name=&quot;NodeSchedulerLogger&quot; type=&quot;log4net.Appender.UdpAppender&quot;&gt;
        &lt;param name=&quot;remoteAddress&quot; value=&quot;localhost&quot; /&gt;
        &lt;param name=&quot;remotePort&quot; value=&quot;9998&quot; /&gt;
        &lt;layout type=&quot;log4net.Layout.PatternLayout&quot;&gt;
            &lt;param name=&quot;conversionpattern&quot; value=&quot;scheduler;%message;%property{ObjectId};%property{ObjectName};%property{Context};%property{Command};%property{Result};&quot; /&gt;

        &lt;/layout&gt;
    &lt;/appender&gt;
   &lt;logger name=&quot;AuditActivity.Scheduler&quot;&gt;
      &lt;appender-ref ref=&quot;NodeSchedulerLogger&quot; /&gt;
   &lt;/logger&gt;
&lt;/configuration&gt;
</code></pre>
<p>The above xml define new appender <code>NodeSchedulerLogger</code> from <code>log4net.Appender.UdpAppender</code> type. On new log event log4net will send udp to <code>remoteAddress</code> and <code>remotePort</code>. <code>layout</code> section specify the format of the request and the fields you want (in my case I wanted <code>message</code>, <code>ObjectId</code>, <code>ObjectName</code>, <code>Context</code>, <code>Command</code> and <code>Result</code> fields.</p>
<p>After this is set I've build small NodeJs UDP server which parse the incoming messages and when the parser find event indicating that task is finished connection to QRS is established and using the API the script is getting the last execution script log and mail it as attachment.</p>
<p><strong>Repo:</strong> <a href="https://github.com/countnazgul/qlik-sense-log4net">qlik-sense-log4net</a></p>
<p>I'll try and move with the server part as quickly as possible. Also will include log4net &quot;recepies&quot; in the repo itself and probably will make separate blog post only for them.</p>
<h5 id="nearfuture">Near Future</h5>
<p>I'm having a bold plan to extend the NodeJs server part. This will include GUI part where different scenarios will be available to setup.</p>
<p>For example:</p>
<ul>
<li>Setup mail recipients for all tasks</li>
<li>Setup mail recipients per single taks or group of tasks or for tasks  with specific custom property</li>
<li>Custom mail messages based on the tasks</li>
</ul>
<h5 id="somewhereinthefuturejustanideas">Somewhere in the future (just an ideas)</h5>
<ul>
<li>Setup notifications based not only on tasks but also on events from the other services</li>
<li>Setup notifications on new/updated documents. For example mail to users which can access a stream when app is added or updated in the stream</li>
</ul>
<h5 id="morelog4netintergrations">More log4net intergrations</h5>
<p>Very good practical usage of log4net approach can be found <a href="https://s3.amazonaws.com/branchsessions/index.html">here</a>. Using Qlik Sense Proxy Service logs <a href="https://twitter.com/mindspank">Alex</a> has build this web site which show global <a href="https://sstoichev.eu/2016/04/24/qlik-sense-tasks-notifications/branch.qlik.com">Qlik Branch</a> users acitivity using the logs from Qlik Sense Proxy Service (QPS).</p>
<p>Also you can check <a href="https://github.com/mindspank/qlik-udp-listener">Alex's example</a> for using log4net with QPS</p>
<p>Hope you like it!<br>
Stefan</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Qlikview Notify]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>Recently we had data issue and few documents was affected by this. We notified the users by adding textboxes in these documents with a warning an explanation. But while this particular data issue was still active 2-3 more data issues was found and editing the text boxes was not quite</p>]]></description><link>https://sstoichev.eu/2016/04/10/qlikview-notify/</link><guid isPermaLink="false">597a16505a9595161cd1c4ce</guid><category><![CDATA[qlikview]]></category><category><![CDATA[notify]]></category><dc:creator><![CDATA[Stefan Stoychev]]></dc:creator><pubDate>Sun, 10 Apr 2016 11:57:07 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>Recently we had data issue and few documents was affected by this. We notified the users by adding textboxes in these documents with a warning an explanation. But while this particular data issue was still active 2-3 more data issues was found and editing the text boxes was not quite an option.</p>
<ul>
<li>We didn't had enough space in the main sheet</li>
<li>there was no guarantee that the users will navigate straight to the main sheet</li>
<li>editing all the documents, saving and publish them again was a bit slow process since the apps was few GB each in size</li>
</ul>
<p>For these reasons I've made a small QV extension that can show notifications which are setup in the server part of this solution</p>
<p><img src="https://raw.githubusercontent.com/countnazgul/qlikview-notify-popup/master/Extension/screenshot.png" alt="screenshot"></p>
<h4 id="serversetup">Server Setup</h4>
<p>Clone the <a href="https://github.com/countnazgul/qlikview-notify-popup.git">Github Repo</a>. Navigate to the folder where the file is unzipped and just <code>npm install</code> to downnload the required packages.</p>
<p>You can edit the port on which the server is listening. Just edit the <code>port</code> section in <code>config.js</code> file inside <code>Server</code> folder. The default port is <code>8080</code></p>
<h4 id="serverusage">Server Usage</h4>
<p>After the server is started just navigate to the main page. There you will find simple interface where notifications can be added or edited.</p>
<p><img src="https://sstoichev.eu/content/images/2016/04/server.png" alt></p>
<p>Available fields:</p>
<ul>
<li>Document - in which document(s) the notification should be shown. If more than one document is specified please separate them with <code>;</code></li>
<li>Short Description - the actual text of the notification</li>
<li>Detailed description - will be shown in notification detail page (when &quot;Details&quot; button in the notification is pressed)</li>
<li>Enabled - if the notification is active or not</li>
<li>Valid to - after this date and time the notification will not be shown (will still be active though)</li>
</ul>
<h4 id="extensionsetup">Extension Setup</h4>
<p>After the extension is installed (download the latest qar file from the releases section) navigate to <code>C:\Users\USERNAME\AppData\Local\QlikTech\QlikView\Extensions\Document\notify</code> and open <code>Script.js</code> in any text editor.</p>
<p>Edit <code>serverUrl</code> and <code>txBoxId</code> values to match your environment (you can leave txBoxId as it is. See below why this variable is needed)</p>
<h4 id="extensionusage">Extension Usage</h4>
<p>I haven't found a way to get the qv document name from within document extension API. Because of this to get the notifications create new textbox and add the following expression in it <code>=DocumentName()</code> Change the textbox id to <code>Popup</code> (or to whatever id is specified in the <code>txBoxId</code> variable in extension <code>Script.js</code> file). Put this textbox somewhere where is not visible (<strong>do not hide it with show/hide condition because it will not be calculated</strong>)</p>
<p>If you want to change the style of the extension edit the <code>styles.css</code> file. Be aware that this change will affect all notifications in all documents</p>
<p>Add the extension to the documents <code>Settings --&gt; Document properties --&gt; Extensions</code></p>
<p>Hope you like it!<br>
Stefan</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[GetProgress() (Qlik Sense)]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>Part of one of my projects was to reload app on the fly. The whole process is working fine (more on this in a different post) but wanted to show the reload progress to the user.</p>
<p>For this purpose we can use <code>GetProgress()</code> method from <a href="https://help.qlik.com/en-US/sense-developer/2.2/Subsystems/EngineAPI/Content/Classes/GlobalClass/Global-class-GetProgress-method.htm?q=getprogress">Qlik Sense Engine API</a></p>
<p>The</p>]]></description><link>https://sstoichev.eu/2016/03/14/getprogress-qlik-sense/</link><guid isPermaLink="false">597a16505a9595161cd1c4cd</guid><dc:creator><![CDATA[Stefan Stoychev]]></dc:creator><pubDate>Mon, 14 Mar 2016 04:42:01 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>Part of one of my projects was to reload app on the fly. The whole process is working fine (more on this in a different post) but wanted to show the reload progress to the user.</p>
<p>For this purpose we can use <code>GetProgress()</code> method from <a href="https://help.qlik.com/en-US/sense-developer/2.2/Subsystems/EngineAPI/Content/Classes/GlobalClass/Global-class-GetProgress-method.htm?q=getprogress">Qlik Sense Engine API</a></p>
<p>The code below uses <a href="https://github.com/mindspank/qsocks">qsocks</a> and shows how to get the reload progress from NodeJS app:</p>
<pre><code class="language-language-javascript">    qsocks.Connect(qsConfig).then(function(global) {
        global.productVersion()
            .then(function(version) {
                return console.log('*** Connected to QS Engine Service')
            })
            .then(function() {
                return global.openDoc('DOCUMENT ID')
            })
            .then(function(doc) {

                var reloaded = null; // when the app is finished to reload
                doc.doReload().then(function() {
                    reloaded = true;
                    console.log('Reloaded');
                });

                var persistentProgress = '';

                var progress = setInterval(function() {

                    if (reloaded != true) {
                        global.getProgress(0).then(function(msg) {
                            if (msg.qPersistentProgress) {
                                persistentProgress = msg.qPersistentProgress;
                                console.log(msg.qPersistentProgress + ' &lt;-- ' + msg.qTransientProgress)
                            } else {
                                if (msg.qTransientProgress) {
                                    console.log(persistentProgress + ' &lt;-- ' + msg.qTransientProgress)
                                }
                            }
                        })
                    } else {
                        clearInterval(progress)
                    }
                }, 500); // get the reload progress every 500ms
            })
    })
</code></pre>
<p>Hope you like it!<br>
Stefan</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[US Map Simplified (Qlik Sense Extension)]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>Couple of weeks back Alexander Karlsson (<a href="https://twitter.com/mindspank">@mindspank</a>) posted a <a href="https://twitter.com/mindspank/status/654129751519395840">tweet</a> with idea for a new Qlik Sense extension.<br>
The extension should look like this:<br>
<img src="https://pbs.twimg.com/media/CRPhNXWUEAAblV7.jpg" alt="US states"></p>
<p>Basically the extension should have a rectangle for each state. The rectangles should be ordered so they will look like USA map. In each rectangle there</p>]]></description><link>https://sstoichev.eu/2016/03/14/us-map-simplified-qlik-sense-extension/</link><guid isPermaLink="false">597a16505a9595161cd1c4cb</guid><category><![CDATA[qlik sense]]></category><category><![CDATA[extension]]></category><category><![CDATA[states]]></category><dc:creator><![CDATA[Stefan Stoychev]]></dc:creator><pubDate>Mon, 14 Mar 2016 02:47:28 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>Couple of weeks back Alexander Karlsson (<a href="https://twitter.com/mindspank">@mindspank</a>) posted a <a href="https://twitter.com/mindspank/status/654129751519395840">tweet</a> with idea for a new Qlik Sense extension.<br>
The extension should look like this:<br>
<img src="https://pbs.twimg.com/media/CRPhNXWUEAAblV7.jpg" alt="US states"></p>
<p>Basically the extension should have a rectangle for each state. The rectangles should be ordered so they will look like USA map. In each rectangle there should be two text values - top one is the state name in short format (WA, ID, MT etc.) and some value (user specified)</p>
<p>This looked simple enough so i can use it as use case and practice my d3 skills.</p>
<p>Overall after I've managed to put the rectangles in place and &quot;bind&quot; them to QS hypercube there was nothing much interesting to be done.<br>
But I've wanted to make this extension a bit more useful and interesting.</p>
<h6 id="useful">Useful</h6>
<p>The useful part was that the extension objects (texts and rects) can be auto sized based on the extension object size (based on the screen resolution)</p>
<p><img src="https://sstoichev.eu/content/images/2015/10/US-States-Dynamic-Sizing.gif" alt="Auto size"></p>
<h6 id="interesting">Interesting</h6>
<p>The interesting part that I've added option to enable <a href="http://bl.ocks.org/skokenes/511c5b658c405ad68941">d3 Lasso support</a>. With it you can easily select group of states</p>
<p><img src="https://sstoichev.eu/content/images/2015/10/US-States-Lasso.gif" alt="d3 Lasso"></p>
<h6 id="dimensionsandexpressions">Dimensions and expressions</h6>
<p>You can add 1 dimension ( the field that contains the states names) and 2 expressions:</p>
<ul>
<li>the calculation that will be shown as value</li>
<li>(optional) the color of the rectangle. If this expression is not available the color will be based on the defined option</li>
</ul>
<h6 id="options">Options</h6>
<p>Various of options are available and they are mostly about the colors ,font colors and sizes.</p>
<p>You can find the extension in <a href="http://branch.qlik.com/projects/showthread.php?660-US-Simplified-Map">Qlik Branch</a> and in <a href="https://github.com/countnazgul/qlik-sense-us-states">Github</a></p>
<p>Hope you like it!<br>
Stefan</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Qlikview IP --> IP Range mapping]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>Recently I had a task to map set of ip addresses to country in Qlikview. I've found a nice dataset that can give me this link but the dataset was for ip range to country.</p>
<p>For example:</p>
<pre><code>RangeFrom   , RangeTo      , Country
1.0.0.1, 1.10.0.1, US
</code></pre>
<p>The</p>]]></description><link>https://sstoichev.eu/2015/10/18/qlikview-ip-ip-range-mapping/</link><guid isPermaLink="false">597a16505a9595161cd1c4ca</guid><category><![CDATA[qlikview]]></category><category><![CDATA[intervalmatch]]></category><category><![CDATA[ip]]></category><category><![CDATA[range]]></category><dc:creator><![CDATA[Stefan Stoychev]]></dc:creator><pubDate>Sun, 18 Oct 2015 09:02:34 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>Recently I had a task to map set of ip addresses to country in Qlikview. I've found a nice dataset that can give me this link but the dataset was for ip range to country.</p>
<p>For example:</p>
<pre><code>RangeFrom   , RangeTo      , Country
1.0.0.1, 1.10.0.1, US
</code></pre>
<p>The solution I've come up with is to use <code>IntervalMatch()</code> function and map the ip list with the range. But the ip addresses are not a normal numeric so I need to convert it to numeric somehow.<br>
My solution is to make the following covertion:</p>
<pre><code>1.0.0.1 --&gt; 001000000100
192.168.1.1 --&gt; 192168001001
...
</code></pre>
<p>Basically each subnet is converted to a number with leading zeros (if needed) and then all are concatenated in single number.<br>
In QV this can be done with the following script:</p>
<pre><code>num( SubField( ip, '.', 1), 000 ) &amp; num( SubField( ip, '.', 2), 000 ) &amp; num( SubField( ip, '.', 3), 000 ) &amp; num( SubField( ipS, '.', 4), 000 ) as ipNum
</code></pre>
<p>If we apply the same logic and for <code>RangeFrom</code> and <code>RangeTo</code> fields then we will have the ips in number format and <code>IntervalMtch()</code> function can be applied.</p>
<p>The script below demonstrate this logic:</p>
<pre><code>ipList:
Load
	num( SubField( IPaddress, '.', 1), 000 ) &amp; num( SubField( IPaddress, '.', 2), 000 ) &amp; num( SubField( IPaddress, '.', 3), 000 ) &amp; num( SubField( IPaddress, '.', 4), 000 )	as ipNum,
	IPaddress
;
Load 
	distinct 
	IPaddress
From
	[ipList.txt] (txt, codepage is 1251, embedded labels, delimiter is '|', msq)
;

ranges:
Load
	num( SubField( RangeFrom, '.', 1), 000 ) &amp; num( SubField( RangeFrom, '.', 2), 000 ) &amp; num( SubField( RangeFrom, '.', 3), 000 ) &amp; num( SubField( RangeFrom, '.', 4), 000 ) as RangeFromNum,
	num( SubField( RangeTo, '.', 1), 000 ) &amp; num( SubField( RangeTo, '.', 2), 000 ) &amp; num( SubField( RangeTo, '.', 3), 000 ) &amp; num( SubField( RangeTo, '.', 4), 000 ) as RangeToNum,	
	RangeFrom,
	RangeTo,
	Country
;
Load
    @1		as RangeFrom, 
    @2		as RangeTo, 
    @3		as Country
From
	[ip-country-range.csv] (txt, codepage is 1251, no labels, delimiter is ',', msq)
;

left join ( ipList )
IntervalMatch ( ipNum ) Load RangeFromNum, RangeToNum Resident ranges;

left join ( ipList )
Load * Resident ranges;

Drop Table ranges;
</code></pre>
<p>Hope you like it!<br>
Stefan</p>
<!--kg-card-end: markdown-->]]></content:encoded></item></channel></rss>