<?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[Stefan Stoichev]]></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 5.79</generator><lastBuildDate>Wed, 08 Apr 2026 05:58:28 GMT</lastBuildDate><atom:link href="https://sstoichev.eu/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Get app reload progress (enigma.js mixin)]]></title><description><![CDATA[Mixin to simplify working with app reload progress and progress messages parsing and format]]></description><link>https://sstoichev.eu/2024/02/23/enigma-mixin-reload-progress/</link><guid isPermaLink="false">65d593308ba7c10dd496cc9e</guid><category><![CDATA[qlik]]></category><category><![CDATA[qlik sense]]></category><category><![CDATA[mixin]]></category><category><![CDATA[api]]></category><dc:creator><![CDATA[Stefan Stoychev]]></dc:creator><pubDate>Fri, 23 Feb 2024 10:00:39 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1639953803381-e9c3f3a38253?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDQ0fHxsb2FkJTIwcHJvZ3Jlc3N8ZW58MHx8fHwxNzA4NDk1NzIzfDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1639953803381-e9c3f3a38253?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDQ0fHxsb2FkJTIwcHJvZ3Jlc3N8ZW58MHx8fHwxNzA4NDk1NzIzfDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" alt="Get app reload progress (enigma.js mixin)"><p></p><p>When I was writing the code for <a href="https://github.com/Informatiqal/qlbuilder?ref=sstoichev.eu" rel="noreferrer">qlBuilder</a> <code>reload</code> command I knew I&apos;ll have to come back to it, at some point, and refactor the part that is displaying the reload progress. The plan was to separate this functionality as a separate module or even a package so it can be used elsewhere as well.</p><p>And that time has finally came. But instead of a separate package this functionality is now part of <a href="https://sstoichev.eu/2020/06/23/enigma-js-mixins/" rel="noreferrer">enigma-mixin</a> package.</p><p>The usage of <code>mGetReloadProgress</code> mixin functionality is a bit niche but it can be used to get the reload progress while reloading an app via <code>enigma.js</code> (same as what Qlik is displaying when reloading an app).</p><p>With vanilla <code>enigma.js</code> we have to use <a href="https://help.qlik.com/en-US/sense-developer/May2023/Subsystems/EngineJSONAPI/Content/service-global-getprogress.htm?ref=sstoichev.eu" rel="noreferrer">GetProgress()</a> API method. But working with it can be a bit tricky. Or at least not trivial.</p><p>The main difference between this and the majority of the other API methods is that reloading an app is a long running process and we&apos;ll have to call <code>GetProgress()</code> periodically (every X ms) while the app is reloading in order to get the current progress.</p><p>The principle here is to not <code>await</code> for the <a href="https://help.qlik.com/en-US/sense-developer/February2024/Subsystems/EngineJSONAPI/Content/service-doc-doreload.htm?ref=sstoichev.eu" rel="noreferrer">DoReload()</a> (or <a href="https://help.qlik.com/en-US/sense-developer/February2024/Subsystems/EngineJSONAPI/Content/service-doc-doreloadex.htm?ref=sstoichev.eu" rel="noreferrer">DoReloadEx()</a>) method but to wrap it in a <code>Promise</code> and inside it to start the reload. After the reload promise is started we&apos;ll start pooling every X ms to get the reload progress. Once the <code>doReload()</code> is complete we&apos;ll resolve the main promise and return the <code>doReload()</code> response.</p><figure class="kg-card kg-code-card"><pre><code class="language-javascript">const doc = await global.openDoc(&quot;some-app-id&quot;);

const reloadApp = await new Promise(function (resolve, reject) {
    // start the app reload
    doc.doReload().then((r) =&gt; {
        // once the app is reloaded resolve the main promise
        resolve(r);
    });

    // while the app is reloading get the reload progress
    // every 200ms and do something with the response data
    setInterval(function () {
        global.getProgress().then(function (msg) {
            //do something with the message here
        })
    }, 200)

});

// continue here after the app reload is complete</code></pre><figcaption><p><span style="white-space: pre-wrap;">Example of how to use getProgress()</span></p></figcaption></figure><p>The above snippet gives us an idea how to work with <code>getProgress()</code> method. The next (main?) issue will be to parse the response data of <code>getProgress()</code>. <strong>And that can also be challenging due to the nature of the messages/progress.</strong></p><p>As you can see getting the reload progress is not as simple as it sounds. </p><p>To simplify the process of getting the reload progress I&apos;ve prepared an <code>enigma.js</code> mixin. More about mixin can be found <a href="https://sstoichev.eu/2020/06/23/enigma-js-mixins/" rel="noreferrer">here</a> and <a href="https://github.com/qlik-oss/enigma.js/blob/master/docs/api.md?ref=sstoichev.eu#mixins" rel="noreferrer">here</a></p><p>By &quot;simplify&quot; I mean that you won&apos;t have to deal with <code>getProgress()</code> response data and the hassle that this can bring. I can talk a bit more about the response structure but that can be ... boring &#x1F604;(Reach out if you want to know more)</p><p>The pseudo code below demonstrates how to import and use the mixin into <code>enigma.js</code> session and how to use it:</p><figure class="kg-card kg-code-card"><pre><code class="language-javascript">import { globalMixin } from &quot;enigma-mixin&quot;;

const session = enigma.create({
  schema,
  mixins: globalMixin,
  url: `ws://my-qlik-sense/app/engineData`,
  createSocket: (url) =&gt; new WebSocket(url),
});

// open the web socket connection
const global = await session.open();
// open the required app
const doc = await global.openDoc(&quot;some-app-id&quot;);

// init the get progress mixin
const reloadProgress = global.mGetReloadProgress();

// once the mixin is initialized we can
// prepare the emitter
reloadProgress.emitter.on(&quot;progress&quot;, (msg) =&gt; {
    // here we will receive the reload messages
    console.log(msg);
});

const reloadApp = await new Promise(function (resolve, reject) {
    // or doc.doReloadEx
    doc.doReload().then((r) =&gt; {
        // give it another few ms to make sure we have all the messages
        setTimeout(function () {
            // stop the mixin from pooling for new messages.
            // The app reload is already complete at this point
            reloadProgress.stop();
            // resolve the main promise
            resolve(r);
        }, 300);
    });

    // once the reload is started we&apos;ll start the pooling
    reloadProgress.start();
});</code></pre><figcaption><p><span style="white-space: pre-wrap;">Pseudo code of how to use mGetReloadProgress mixin</span></p></figcaption></figure><p>The parsed and formatted reload messages will be available inside the <code>reloadProgress.emitter.<em>on</em>(&quot;progress&quot;)...</code> function. At the end the messages here will (should) be identical to what is displayed in Qlik.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://sstoichev.eu/content/images/2024/02/image-1.png" class="kg-image" alt="Get app reload progress (enigma.js mixin)" loading="lazy" width="837" height="231" srcset="https://sstoichev.eu/content/images/size/w600/2024/02/image-1.png 600w, https://sstoichev.eu/content/images/2024/02/image-1.png 837w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Qlik (left) and mixin (right) reload results</span></figcaption></figure><p>Follow the link below for the source code and more details about this specific mixin or if you want to know more about the other available mixins. The <code>mGetReloadProgress</code> mixin have a few (optional) parameters that can be set. Like how often to pool for progress and to include/exclude all transient messages.</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/countnazgul/enigma-mixin?tab=readme-ov-file&amp;ref=sstoichev.eu#global"><div class="kg-bookmark-content"><div class="kg-bookmark-title">GitHub - countnazgul/enigma-mixin: Set of custom enigma.js mixins</div><div class="kg-bookmark-description">Set of custom enigma.js mixins. 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/assets/pinned-octocat-093da3e6fa40.svg" alt="Get app reload progress (enigma.js mixin)"><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">countnazgul</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/192dfb6eca2eba77f530f3b6bb7ce298a0b13980c7a53d6fb4bac00cc7c4c775/countnazgul/enigma-mixin" alt="Get app reload progress (enigma.js mixin)"></div></a></figure><p>P.S. The same setup can be used with <code>doSave()</code> method as well. Although it seems that Qlik is not sending any data in the response. Not sure if this is helpful in some way ... just saying &#x1F609;</p><p>Stefan</p>]]></content:encoded></item><item><title><![CDATA[Qlik Error/Messages Codes]]></title><description><![CDATA[List of hard to find Qlik internal codes and their description ]]></description><link>https://sstoichev.eu/2024/02/09/qlik-error-messages-codes/</link><guid isPermaLink="false">65c4bd4019146a263f8e94db</guid><category><![CDATA[qlik]]></category><category><![CDATA[qlik sense]]></category><dc:creator><![CDATA[Stefan Stoychev]]></dc:creator><pubDate>Fri, 09 Feb 2024 13:40:36 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1633265486064-086b219458ec?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDM3fHxjcnlwdG9ncmFwaHl8ZW58MHx8fHwxNzA3NDg1OTQyfDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1633265486064-086b219458ec?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDM3fHxjcnlwdG9ncmFwaHl8ZW58MHx8fHwxNzA3NDg1OTQyfDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" alt="Qlik Error/Messages Codes"><p>While working with Qlik we can encouter errors or messages that can be a bit cryptic and contain just an error/message code.</p>
<p>Very often these codes are followed by a short description. But sometimes either the description is missing or its ... to short :)</p>
<p>Finally found some free time to compile a list of the error/messages codes, their short and long description.</p>
<p>My data source is a few years old so most likely is not complete. But still there are 300+ codes there.</p>
<figure class="kg-card kg-bookmark-card kg-card-hascaption"><a class="kg-bookmark-container" href="https://github.com/countnazgul/qlik-internal-codes?ref=sstoichev.eu"><div class="kg-bookmark-content"><div class="kg-bookmark-title">GitHub - countnazgul/qlik-internal-codes: List of hard to find Qlik internal codes and their description</div><div class="kg-bookmark-description">List of hard to find Qlik internal codes and their description - countnazgul/qlik-internal-codes</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.com/fluidicon.png" alt="Qlik Error/Messages Codes"><span class="kg-bookmark-author">countnazgul</span><span class="kg-bookmark-publisher">GitHub</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/6b6c78e414c779f8567327b6a9db8b8e92c06b245c2bfa6a902a3788ccc36d5e/countnazgul/qlik-internal-codes" alt="Qlik Error/Messages Codes"></div></a><figcaption><p><span>GitHub link</span></p></figcaption></figure>
<p>If you happen to know more of these, please feel free to open PR in the repo.</p>
<p>Stefan</p>]]></content:encoded></item><item><title><![CDATA[Automatiqal CLI Beta! (Cross-Post)]]></title><description><![CDATA[Automatiqal CLI is officially entering its Beta phase! ]]></description><link>https://sstoichev.eu/2023/06/14/automatiqal-cli-beta-cross-post/</link><guid isPermaLink="false">6488c3fd19146a263f8e9481</guid><category><![CDATA[qlik]]></category><category><![CDATA[automatiqal]]></category><category><![CDATA[informatiqal]]></category><dc:creator><![CDATA[Stefan Stoychev]]></dc:creator><pubDate>Wed, 14 Jun 2023 08:30:32 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1639502024019-7d95ad2873e2?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDIwfHxwb3N0aW5nfGVufDB8fHx8MTY4NjY4NTE4N3ww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1639502024019-7d95ad2873e2?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDIwfHxwb3N0aW5nfGVufDB8fHx8MTY4NjY4NTE4N3ww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" alt="Automatiqal CLI Beta! (Cross-Post)"><p>The original announcement can be found <a href="https://informatiqal.com/automatiqal-cli-beta/?ref=sstoichev.eu">here</a></p>
<p><a href="https://informatiqal.com/automatiqal-cli/?ref=sstoichev.eu">Automatiqal CLI</a> is officially entering its Beta phase! </p>
<p>You might not be aware but <strong>Automatiqal CLI</strong> allows Qlik Sense (QSEoW) deployments/administration tasks to be described in YAML/JSON files. </p>
<p><strong>Automatiqal CLI</strong> supports:</p>
<ul><li>multiple authentication methods</li><li>multiple ways to define variables</li><li>define <strong>onError</strong> blocks</li><li>and a lot more!</li></ul>
<p>Please have a look at the introduction video below. More videos will be added soon!</p>
<figure class="kg-card kg-embed-card"><iframe width="200" height="113" src="https://www.youtube.com/embed/ZU13H9uw1lM?start=1&amp;feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen title="Automatiqal CLI - Introduction"></iframe></figure>
<p>Any feedback is greatly appreciated!</p>
<p>Stefan</p>]]></content:encoded></item><item><title><![CDATA[Web app authentication with QSEoW (web ticket + session cookie)]]></title><description><![CDATA[Simple demonstration how to "exchange" Qlik web ticket to session cookie]]></description><link>https://sstoichev.eu/2022/01/05/web-app-authentication-with-qseow-web-ticket-session-cookie/</link><guid isPermaLink="false">61d3fcf4136b2b3ba93c0c5f</guid><category><![CDATA[qlik]]></category><category><![CDATA[qlik sense]]></category><category><![CDATA[ticket]]></category><category><![CDATA[session]]></category><category><![CDATA[authentication]]></category><dc:creator><![CDATA[Stefan Stoychev]]></dc:creator><pubDate>Wed, 05 Jan 2022 11:50:00 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1524178232363-1fb2b075b655?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDF8fHNlc3Npb258ZW58MHx8fHwxNjQxMjgyOTE2&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://images.unsplash.com/photo-1524178232363-1fb2b075b655?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDF8fHNlc3Npb258ZW58MHx8fHwxNjQxMjgyOTE2&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" alt="Web app authentication with QSEoW (web ticket + session cookie)"><p>In one of the previous <a href="https://sstoichev.eu/2021/09/09/qseow-authentication-svelte/">post</a> we saw how to use web ticket to establish connection with Qlik Sense Engine. And probably for most cases the approach there is enough.</p>
<p>But what if our web app have a requirement to communicate with other Qlik service(s) (in addition to the Engine)? For example, we want to list the reload tasks to which the user have access (this information is provided by <a href="https://help.qlik.com/en-US/sense-developer/May2021/Subsystems/RepositoryServiceAPI/Content/Sense_RepositoryServiceAPI/RepositoryServiceAPI-Introduction.htm?ref=sstoichev.eu">Qlik Repository Service REST API</a>)?</p>
<p>The problem with the web ticket is that once it is consumed it can&apos;t be used again. And if we use the ticket to open web socket Engine connection then we can&apos;t use the same ticket to get the list of the reload tasks. We&apos;ll end up with situation where the Engine connection is authenticated and established but upon requesting the list of reload tasks Qlik will response with <code>Unauthenticated</code>.</p>
<p>To overcome this we can &quot;exchange&quot; the web ticket for a Qlik session. The exchange will be in a form of a session cookie. We&apos;ll have the session cookie set in the browser and then our web app can freely open Engine connection (without web ticket) and communicate with other Qlik Sense services. In similar way the Hub is doing it.</p>
<h4 id="pre-requisites-qmc">Pre-requisites (QMC)</h4>
<p>Couple of settings have to be set in the <code>Virtual Proxy</code> before we can start (if they are not set already):</p>
<ul>
<li><code>White list</code> - add the web app url to the host white list</li>
</ul>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="https://sstoichev.eu/content/images/2022/01/image.png" class="kg-image" alt="Web app authentication with QSEoW (web ticket + session cookie)" loading="lazy" width="733" height="125" srcset="https://sstoichev.eu/content/images/size/w600/2022/01/image.png 600w, https://sstoichev.eu/content/images/2022/01/image.png 733w" sizes="(min-width: 720px) 720px"></figure><!--kg-card-begin: markdown--><ul>
<li><code>SameSite attributes</code> - make sure that the <code>SameSite</code> attributes are set to <code>None</code> (at least)</li>
</ul>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="https://sstoichev.eu/content/images/2022/01/image-1.png" class="kg-image" alt="Web app authentication with QSEoW (web ticket + session cookie)" loading="lazy" width="741" height="108" srcset="https://sstoichev.eu/content/images/size/w600/2022/01/image-1.png 600w, https://sstoichev.eu/content/images/2022/01/image-1.png 741w" sizes="(min-width: 720px) 720px"></figure><!--kg-card-begin: markdown--><h3 id="code-workflow">Code workflow</h3>
<p>The code workflow is:</p>
<ul>
<li>get web ticket from Qlik (in the same manner as in the previous post)</li>
<li>use the ticket to retrieve the content of any static Qlik resource via HTTP request. In this example I&apos;m going to use <code>/resources/autogenerated/qlik-styles.css</code> but it can be any other static file that requires authentication in order to be viewed/downloaded</li>
<li>since we are using the ticket in HTTP request the response from Qlik will be the content of the requested resource + <strong>session cookie</strong></li>
<li>once session cookie is set (the browser is taking care of this) we can continue and connect to the Engine and &quot;ask&quot; Qlik for different data (in our case get list of reload tasks)</li>
</ul>
<h3 id="code">Code</h3>
<ul>
<li>retrieve the static file content</li>
</ul>
<pre><code class="language-javascript">export const load = async ({ url, params }) =&gt; {
  // look for &quot;qlikTicket&quot; query parameter
  const qlikTicket = url.searchParams.get(&quot;qlikTicket&quot;);

  // if &quot;qlikTicket&quot; is present
  // use the ticket to retrieve the content of &quot;qlik-styles.css&quot;
  // if all is ok Qlik will send the session cookie
  // and the browser will set it
  if (qlikTicket) {
   const qlikStylesURL = `https://${qsHost}/resources/autogenerated/qlik-styles.css?QlikTicket=${qlikTicket}`;
 
  const qlikStylesResponse = await fetch(qlikStylesURL, {
    credentials: &quot;include&quot;,
   });
 }

  return true;
};
</code></pre>
<ul>
<li>variables</li>
</ul>
<pre><code class="language-javascript">const qsHost = &quot;my-sense-instance.com&quot;;
const reloadURI = `http://localhost:3000`;
const xrfkey = &quot;1234567890123456&quot;;
const qsReloadTasksURL = `https://${qsHost}/qrs/reloadtask?Xrfkey=${xrfkey}`;
 
let qlikApps = [];
let qlikReloadTasks = [];
</code></pre>
<ul>
<li>Notification events</li>
</ul>
<pre><code class="language-javascript">// redirect to the login page if the Engine connection response indicates that we have to authenticate
session.on(&quot;notification:OnAuthenticationInformation&quot;, (data) =&gt; {
 if (data.loginUri) goto(data.loginUri);
});

// once the Engine connection is established, get the list of the reload tasks (in the logged in user context)
session.on(&quot;notification:OnConnected&quot;, async () =&gt; {
 qlikReloadTasks = await fetch(qsReloadTasksURL, {
   credentials: &quot;include&quot;,
   headers: {
     &quot;X-Qlik-Xrfkey&quot;: `${xrfkey}`,
   },
 }).then((res) =&gt; res.json());
});
</code></pre>
<ul>
<li>HTML structure</li>
</ul>
<pre><code class="language-html">&lt;main&gt;
  &lt;div&gt;
    &lt;h1&gt;Qlik apps:&lt;/h1&gt;

    {#each qlikApps as qlikApp}
      &lt;div title={qlikApp.qDocId}&gt;{qlikApp.qDocName}&lt;/div&gt;
    {/each}
  &lt;/div&gt;

  &lt;div&gt;
    &lt;h1&gt;Qlik Reload Tasks:&lt;/h1&gt;

    {#each qlikReloadTasks as reloadTask}
      &lt;div title={reloadTask.id}&gt;{reloadTask.name}&lt;/div&gt;
    {/each}
  &lt;/div&gt;
&lt;/main&gt;
</code></pre>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h3 id="result">Result</h3>
<p>Once the whole process is complete we should see something like:</p>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="https://sstoichev.eu/content/images/2022/01/image-2.png" class="kg-image" alt="Web app authentication with QSEoW (web ticket + session cookie)" loading="lazy" width="1160" height="252" srcset="https://sstoichev.eu/content/images/size/w600/2022/01/image-2.png 600w, https://sstoichev.eu/content/images/size/w1000/2022/01/image-2.png 1000w, https://sstoichev.eu/content/images/2022/01/image-2.png 1160w" sizes="(min-width: 720px) 720px"></figure><!--kg-card-begin: markdown--><p>And if we inspect the network request will see:</p>
<ul>
<li>the <code>qlik-styles.css</code> request is with attached <code>QlikTicket</code> the response is <code>200 OK</code> and the response headers are containing the session cookie</li>
</ul>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://sstoichev.eu/content/images/2022/01/image-7.png" class="kg-image" alt="Web app authentication with QSEoW (web ticket + session cookie)" loading="lazy" width="814" height="234" srcset="https://sstoichev.eu/content/images/size/w600/2022/01/image-7.png 600w, https://sstoichev.eu/content/images/2022/01/image-7.png 814w" sizes="(min-width: 720px) 720px"><figcaption>qlik-styles.css response</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://sstoichev.eu/content/images/2022/01/image-8.png" class="kg-image" alt="Web app authentication with QSEoW (web ticket + session cookie)" loading="lazy" width="810" height="419" srcset="https://sstoichev.eu/content/images/size/w600/2022/01/image-8.png 600w, https://sstoichev.eu/content/images/2022/01/image-8.png 810w" sizes="(min-width: 720px) 720px"><figcaption>qlik-styles.css response headers</figcaption></figure><!--kg-card-begin: markdown--><ul>
<li>we can see the cookie being set in the browser</li>
</ul>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://sstoichev.eu/content/images/2022/01/image-3.png" class="kg-image" alt="Web app authentication with QSEoW (web ticket + session cookie)" loading="lazy" width="829" height="138" srcset="https://sstoichev.eu/content/images/size/w600/2022/01/image-3.png 600w, https://sstoichev.eu/content/images/2022/01/image-3.png 829w" sizes="(min-width: 720px) 720px"><figcaption>Browser (session) cookie</figcaption></figure><!--kg-card-begin: markdown--><ul>
<li>both the Engine connection and Repository API request are made without any ticket added to the urls</li>
</ul>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://sstoichev.eu/content/images/2022/01/image-6.png" class="kg-image" alt="Web app authentication with QSEoW (web ticket + session cookie)" loading="lazy" width="780" height="155" srcset="https://sstoichev.eu/content/images/size/w600/2022/01/image-6.png 600w, https://sstoichev.eu/content/images/2022/01/image-6.png 780w" sizes="(min-width: 720px) 720px"><figcaption>Web Socket (Engine) connection</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://sstoichev.eu/content/images/2022/01/image-5.png" class="kg-image" alt="Web app authentication with QSEoW (web ticket + session cookie)" loading="lazy" width="722" height="211" srcset="https://sstoichev.eu/content/images/size/w600/2022/01/image-5.png 600w, https://sstoichev.eu/content/images/2022/01/image-5.png 722w" sizes="(min-width: 720px) 720px"><figcaption>HTTP (Repository API) connection</figcaption></figure><!--kg-card-begin: markdown--><h3 id="complete-code">Complete code</h3>
<p>The full code is available at the repo below. Follow the readme there to install and run it.</p>
<!--kg-card-end: markdown--><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/countnazgul/qseow-svelte-authentication-session?ref=sstoichev.eu"><div class="kg-bookmark-content"><div class="kg-bookmark-title">GitHub - countnazgul/qseow-svelte-authentication-session</div><div class="kg-bookmark-description">Contribute to countnazgul/qseow-svelte-authentication-session development by creating an account on GitHub.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.com/fluidicon.png" alt="Web app authentication with QSEoW (web ticket + session cookie)"><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">countnazgul</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/b3cdf5c14f25fd2ed4bc992d857ecd14443ffcca062f68637ec309d3ca22abfb/countnazgul/qseow-svelte-authentication-session" alt="Web app authentication with QSEoW (web ticket + session cookie)"></div></a></figure><p>Hope you liked it!</p><p>Stefan</p>]]></content:encoded></item><item><title><![CDATA[Meet Informatiqal]]></title><description><![CDATA[Informatiqal - Ask us about Qlik Sense API :) 
qlik-rest-api Node package]]></description><link>https://sstoichev.eu/2021/11/02/informatiqal/</link><guid isPermaLink="false">615c149c5a6b2a2e5d17cd6b</guid><category><![CDATA[qlik sense]]></category><category><![CDATA[qlik]]></category><category><![CDATA[informatiqal]]></category><dc:creator><![CDATA[Stefan Stoychev]]></dc:creator><pubDate>Tue, 02 Nov 2021 15:37:11 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1431576901776-e539bd916ba2?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDIxfHxpbmZvcm1hdGlvbnxlbnwwfHx8fDE2MzU3ODQ4MzQ&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1431576901776-e539bd916ba2?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDIxfHxpbmZvcm1hdGlvbnxlbnwwfHx8fDE2MzU3ODQ4MzQ&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" alt="Meet Informatiqal"><p>The initial idea was simple ... on paper. Spend few days just to prove that its possible. And it was doable. But to achieve it I had to build solid foundation. </p><p>I&apos;m not going to say what was the initial idea ... yet. But will share where it&apos;s &quot;home&quot; will be and will release all the components of the &quot;foundation&quot;.</p><p>Since there will be multiple components I&apos;ve decided that it&apos;s a good idea to separate them in their own space. </p><p>So let me introduce you to <em><strong>Informatiqal</strong></em></p><p><em><strong>Informatiqal</strong></em> has it&apos;s own website and it&apos;s own GitHub organization:</p><figure class="kg-card kg-bookmark-card kg-card-hascaption"><a class="kg-bookmark-container" href="https://informatiqal.com/?ref=sstoichev.eu"><div class="kg-bookmark-content"><div class="kg-bookmark-title">I N F O R M A T I Q A L</div><div class="kg-bookmark-description">Ask us about Qlik Sense API :)</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://informatiqal.com/favicon.ico" alt="Meet Informatiqal"><span class="kg-bookmark-author">I N F O R M A T I Q A L</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://static.ghost.org/v4.0.0/images/publication-cover.jpg" alt="Meet Informatiqal"></div></a><figcaption>Official website</figcaption></figure><figure class="kg-card kg-bookmark-card kg-card-hascaption"><a class="kg-bookmark-container" href="https://github.com/Informatiqal?ref=sstoichev.eu"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Informatiqal</div><div class="kg-bookmark-description">Ask us about Qlik Sense API :). Informatiqal has 5 repositories available. Follow their code on GitHub.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.githubassets.com/favicons/favicon.svg" alt="Meet Informatiqal"><span class="kg-bookmark-author">GitHub</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://avatars.githubusercontent.com/u/87044481?s=280&amp;v=4" alt="Meet Informatiqal"></div></a><figcaption>GitHub page</figcaption></figure><p>Code for a few mode modules is publicly available but want to meet you with the &quot;base of the base&quot;. The others are still WIP.</p><h2 id="qlik-rest-api">Qlik REST API</h2><p>This a &quot;generic&quot; package. It simplifies the communication with various Qlik Sense REST API endpoints (Repository, Proxy, SaaS etc.) but still we have to know what actual endpoint have to be called and what data has to be passed. </p><p>But on top of it few more packages are/will be created which are going to make the development process more pleasant.</p><p><em><strong>qlik-rest-api</strong></em> can be used from within both NodeJS and browser(s). The example below shows how certificate authentication can be used (from NodeJS) to communicate with Qlik Repository REST API.</p><pre><code class="language-javascript">import fs from &quot;fs&quot;;
import https from &quot;https&quot;;

// read the certificates
const crt = fs.readFileSync(&quot;path/to/certificate.pem&quot;);
const key = fs.readFileSync(&quot;path/to/certificate_key.pem&quot;);

// init https agent
const httpsAgent = new https.Agent({
  rejectUnauthorized: false,
  cert: crt,
  key: key,
});

// create the config object by passing the created httpAgent
const config = {
  host: &quot;my-qlik-sense-instance&quot;,
  port: 4242, // optional. default is 4242
  httpsAgent: httpsAgent,
  authentication: {
    user_dir: &quot;SOME_DIR&quot;,
    user_name: &quot;SOME_USER&quot;,
  },
};

// initialize the repo client by passing the conifg object
const repoClient = new QlikRepositoryClient(config);

// invoke Repository API &quot;about&quot; endpoint
const result = await repoClient.Get(&quot;about&quot;);</code></pre><p>And the result variable will be in format:</p><figure class="kg-card kg-image-card"><img src="https://sstoichev.eu/content/images/2021/11/image-1.png" class="kg-image" alt="Meet Informatiqal" loading="lazy" width="514" height="108"></figure><p>And the <em>data </em>property will hold the response data:</p><figure class="kg-card kg-image-card"><img src="https://sstoichev.eu/content/images/2021/11/image.png" class="kg-image" alt="Meet Informatiqal" loading="lazy" width="513" height="344"></figure><p>Feel free to check the package repository page. Don&apos;t forget to check the Wiki section for more details and examples.</p><figure class="kg-card kg-bookmark-card kg-card-hascaption"><a class="kg-bookmark-container" href="https://github.com/Informatiqal/qlik-rest-api?ref=sstoichev.eu"><div class="kg-bookmark-content"><div class="kg-bookmark-title">GitHub - Informatiqal/qlik-rest-api: Interact with multiple Qlik Sense REST APIs from a single package</div><div class="kg-bookmark-description">Interact with multiple Qlik Sense REST APIs from a single package - GitHub - Informatiqal/qlik-rest-api: Interact with multiple Qlik Sense REST APIs from a single package</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.com/fluidicon.png" alt="Meet Informatiqal"><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">Informatiqal</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/cf1c546dfec43261609d2c5e9347e117cebdb6b62152b7f7644f1ba51ac4e121/Informatiqal/qlik-rest-api" alt="Meet Informatiqal"></div></a><figcaption>Repository</figcaption></figure><p>Dev documentation of all exposed methods, types and interfaces can be found at:</p><figure class="kg-card kg-bookmark-card kg-card-hascaption"><a class="kg-bookmark-container" href="https://informatiqal.github.io/qlik-rest-api/modules.html?ref=sstoichev.eu"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Qlik REST API</div><div class="kg-bookmark-description">Documentation for Qlik REST API</div><div class="kg-bookmark-metadata"></div></div></a><figcaption>Developer documentation</figcaption></figure><p>P.S. Please feel free to open issue for any question or bug you encounter :)</p><p>Hope you liked it!</p><p>Stefan</p>]]></content:encoded></item><item><title><![CDATA[Web app authentication with QSEoW (web ticket)]]></title><description><![CDATA[Simple demonstration how to establish authenticated connection with Qlik when hosting the web app outside Qlik itself]]></description><link>https://sstoichev.eu/2021/09/09/qseow-authentication-svelte/</link><guid isPermaLink="false">606dc4845a6b2a2e5d17ca67</guid><category><![CDATA[qlik sense]]></category><category><![CDATA[web app]]></category><category><![CDATA[authentication]]></category><dc:creator><![CDATA[Stefan Stoychev]]></dc:creator><pubDate>Thu, 09 Sep 2021 13:35:33 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1509822929063-6b6cfc9b42f2?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDF8fGxvZ2lufGVufDB8fHx8MTYxNzg4OTUzOQ&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://images.unsplash.com/photo-1509822929063-6b6cfc9b42f2?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDF8fGxvZ2lufGVufDB8fHx8MTYxNzg4OTUzOQ&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" alt="Web app authentication with QSEoW (web ticket)"><p>This post is long overdue but finally found the time to complete it.</p>
<p>Here we&apos;ll have a look how to establish connection (via <a href="https://github.com/qlik-oss/enigma.js/?ref=sstoichev.eu">enigma.js</a>) to Qlik Sense Enterprise on Windows (<code>QSEoW</code>) when hosting an web app outside of Qlik (not as an extension). And more specifically we&apos;ll have a look at what is the authentication work flow.</p>
<p>The (very simple) web app will just list the apps that are available for the logged in user. I&apos;ll be using <code>Svelte</code> as web framework (more specifically <a href="https://kit.svelte.dev/?ref=sstoichev.eu">SvelteKit</a>) but the general work flow should work in the same way with other frameworks as well.</p>
<p>Let&apos;s begin!</p>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h3 id="the-problem">The problem</h3>
<p>When our web app is hosted by Qlik (as an extension) we can only access it through Qlik&apos;s extensions url (like: <code>https://my-sense-instance.com/extensions/MyMashup/index.html</code>) It&apos;s not a very nice URL. And every time when new version of our web app is ready we&apos;ll have to remove the old extension and re-upload the new version. (of course this can be automated)</p>
<p>The benefit of having the web app hosted by Qlik is that in order to access it you&apos;ll have to be authenticated. If not - Qlik will handle the redirection.</p>
<p>In our case we will redirect the user to Qlik&apos;s login page, receive web ticket and use the ticket to establish connection with the engine.</p>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h2 id="scenario">Scenario</h2>
<p>The scenario is very simple:</p>
<ul>
<li>using <code>enigma.js</code> to establish session with Qlik Engine.</li>
<li>once the session is esablished get list of all user&apos;s apps</li>
<li>display the apps list on the page</li>
</ul>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h2 id="session-events">Session events</h2>
<p>Crucial part of the authentication work flow is enigma&apos;s capability to &quot;listen&quot; for specific events (check out the <a href="https://github.com/qlik-oss/enigma.js/blob/master/docs/api.md?ref=sstoichev.eu">Session API</a> part of the documentation)</p>
<p>Enigma can &quot;listen&quot; for various events (like session closed, session suspended, all the traffic send/received to/from the Engine or any named notification).</p>
<p>In our case we&apos;ll listen for <code>OnAuthenticationInformation</code> event. This event can tell us if we must authenticate and if we must it will provide us with the URL to the Qlik&apos;s login page. Once authenticated there Qlik will re-direct us back to the web app but with the Qlik ticket in the url. The ticket then can be used when establishing connection with enigma</p>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h2 id="general-workflow">General workflow</h2>
<ol>
<li>When the user opens the web page we&apos;ll try and connect to QSEoW via Web Sockets (<code>await session.open())</code></li>
<li>If the users is not authenticated Qlik will &quot;raise&quot; <code>OnAuthenticationInformation</code> notification event. The event contains simple JSON object:</li>
</ol>
<!--kg-card-end: markdown--><pre><code class="language-javascript">{
  mustAuthenticate: true,
  loginUri: &quot;https://...&quot;
}
</code></pre><!--kg-card-begin: markdown--><ol start="3">
<li>In our code we are listening for <code>OnAuthenticationInformation</code> events and when receive one (and <code>mustAuthenticate: true</code>) we are going to redirecting the user to the <code>loginUri</code> value from the event</li>
<li><code>loginUri</code> is the main Qlik login page (not part of our web app!). The user enters their credentials there</li>
<li>If the credentials are ok then Qlik redirect back to our web app page but with <code>qlikTicket</code> added to the url as a parameter</li>
<li>Again we send <code>session.open()</code> but this time we&apos;ll provide the <code>qlikTicket</code></li>
<li>Qlik replies with <code>SESSION_CREATED</code></li>
<li>And at this point we are all set and can continue communicating with the Engine. All requests will be made in the scope of the logged user</li>
</ol>
<p>The image below illustrate what is the general work flow to establish session and authenticate</p>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card kg-width-full"><img src="https://sstoichev.eu/content/images/2021/04/test-4.png" class="kg-image" alt="Web app authentication with QSEoW (web ticket)" loading="lazy" width="643" height="841" srcset="https://sstoichev.eu/content/images/size/w600/2021/04/test-4.png 600w, https://sstoichev.eu/content/images/2021/04/test-4.png 643w"></figure><!--kg-card-begin: markdown--><h2 id="code">Code</h2>
<!--kg-card-end: markdown--><p>The first bit of code is to handle the URL parameter. If the URL contains `qlikTicket` we&apos;ll pass the value as property. When the page is loaded for the first time the value of `qlikTicket` will be empty.</p><pre><code class="language-javascript">&lt;script context=&quot;module&quot;&gt;
  export const load = async ({ url }) =&gt; {
    let qlikTicket = url.searchParams.get(&quot;qlikTicket&quot;);
    return { props: { qlikTicket } };
  };
&lt;/script&gt;</code></pre><p>Importing enigma and `goto` from Svelte navigation. `goto` will be use to re-direct to the login url.</p><pre><code class="language-javascript">&lt;script&gt;
  import { goto } from &quot;$app/navigation&quot;;
  import enigma from &quot;enigma.js&quot;;
  import schema from &quot;enigma.js/schemas/12.67.2.json&quot;;</code></pre><p>Define Qlik host and URL for the web app. The web app location can be found &#xA0;programmatically of course.</p><pre><code class="language-javascript">  const qsHost = &quot;wss://my-sense-instance.com&quot;;
  const reloadURI = `http://localhost:3000`;</code></pre><!--kg-card-begin: markdown--><p>Define few variables:</p>
<ul>
<li><code>qlikTicket</code> - will hold the Qlik ticket (when we have it)</li>
<li><code>qlikApps</code> - will hold the list of the Qlik apps</li>
<li><code>qlikTicketString</code> - the ticket have to be passed as URL parameter itself. This variable will contain this string</li>
</ul>
<!--kg-card-end: markdown--><pre><code class="language-javascript">export let qlikTicket;
let qlikApps = [];
let qlikTicketString = qlikTicket ? `&amp;QlikTicket=${qlikTicket}` : &quot;&quot;;</code></pre><!--kg-card-begin: markdown--><p>Generate the session object. The interesting part here is the <code>url</code>. The url is containing three &quot;elements&quot;:</p>
<ul>
<li><code>${qsHost}/app/engineData</code> - setting the Qlik host as usual</li>
<li><code>reloadURI=${encodeURIComponent(reloadURI)</code> - this part tells Qlik to return to this location once the user is successfully authenticated (in our case this will be <code>http://localhost:3000</code>)</li>
<li><code>${qlikTicketString}</code> - when we have the ticket it will be passed here</li>
</ul>
<!--kg-card-end: markdown--><pre><code class="language-javascript">const session = enigma.create({
    schema,
    url: `${qsHost}/app/engineData?reloadURI=${encodeURIComponent(
      reloadURI
    )}${qlikTicketString}`,
    createSocket: (url) =&gt; new WebSocket(url),
  });</code></pre><p>The session event part. If notification of type `OnAuthenticationInformation` is received and the event data contains `loginUri` then re-direct the page to this url. Once the user authenticate there Qlik will return us to our web page but with the ticket appended at the end. The url will looks like:</p><!--kg-card-begin: markdown--><blockquote>
<p><a href="http://localhost:3000/?qlikTicket=YMnqm8WvW8AXSA99&amp;ref=sstoichev.eu">http://localhost:3000/?qlikTicket=YMnqm8WvW8AXSA99</a></p>
</blockquote>
<!--kg-card-end: markdown--><pre><code class="language-javascript">  session.on(&quot;notification:OnAuthenticationInformation&quot;, (data) =&gt; {
    if (data.loginUri) goto(data.loginUri);
  });</code></pre><p>The main logic. Once the session is successfully established - get the list of the apps and populate `qlikApps` variable with it.</p><pre><code class="language-javascript">  (async function () {
      let global = await session.open();
      qlikApps = await global.getDocList();
      console.log(qlikApps);
      await session.close();
  })();
&lt;/script&gt;</code></pre><p>The HTML part is very basic:</p><pre><code class="language-html">&lt;main&gt;
  &lt;h1&gt;Qlik apps:&lt;/h1&gt;

  {#each qlikApps as qlikApp}
    &lt;div&gt;{qlikApp.qDocName}&lt;/div&gt;
  {/each}
&lt;/main&gt;</code></pre><p>And the result:</p><figure class="kg-card kg-image-card"><img src="https://sstoichev.eu/content/images/2021/04/result.png" class="kg-image" alt="Web app authentication with QSEoW (web ticket)" loading="lazy" width="445" height="442"></figure><p>Once the local development is complete the bundle can be hosted anywhere</p><!--kg-card-begin: markdown--><h2 id="complete-code">Complete code</h2>
<!--kg-card-end: markdown--><p>The full code is available at the repo below. Follow the readme there to install and run it.</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/countnazgul/qseow-svelte-authentication?ref=sstoichev.eu"><div class="kg-bookmark-content"><div class="kg-bookmark-title">GitHub - countnazgul/qseow-svelte-authentication</div><div class="kg-bookmark-description">Contribute to countnazgul/qseow-svelte-authentication 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="Web app authentication with QSEoW (web ticket)"><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">countnazgul</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/82e4c170ccba685615c4b91ef7caac5e6ee569794cebfd2fcd301e9bb73adb3f/countnazgul/qseow-svelte-authentication" alt="Web app authentication with QSEoW (web ticket)"></div></a></figure><p>Hope you liked it!</p>]]></content:encoded></item><item><title><![CDATA[Export QMC tables]]></title><description><![CDATA[Browser extension to export Qlik Sense QMC table to CSV file or copy the table to the clipboard]]></description><link>https://sstoichev.eu/2021/01/08/export-qmc-tables/</link><guid isPermaLink="false">5fd7d1e04d73ed59345e9748</guid><category><![CDATA[qlik]]></category><category><![CDATA[qlik sense]]></category><category><![CDATA[qmc]]></category><category><![CDATA[chrome extension]]></category><category><![CDATA[export]]></category><category><![CDATA[table]]></category><dc:creator><![CDATA[Stefan Stoychev]]></dc:creator><pubDate>Fri, 08 Jan 2021 10:00:00 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1530018607912-eff2daa1bac4?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MXwxMTc3M3wwfDF8c2VhcmNofDJ8fHRhYmxlfGVufDB8fHw&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://images.unsplash.com/photo-1530018607912-eff2daa1bac4?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MXwxMTc3M3wwfDF8c2VhcmNofDJ8fHRhYmxlfGVufDB8fHw&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" alt="Export QMC tables"><p>Administrating Qlik Sense sometimes means spending a lot of the time in QMC.</p>
<p>And sometimes I need to export some of the tables there just to keep the info somewhere else while I&apos;m on a different screen in QMC.</p>
<p>Finally sat down to solve this specific issue - <code>Chrome</code>/<code>Edge</code> extension that can export QMC tables to CSV file or copy the content to the clipboard.</p>
<h3 id="ui">UI</h3>
<p>The extension layout is &quot;separated&quot; into two areas:</p>
<ul>
<li><strong>Copy</strong> - exports the table and paste it into the clipboard. The only option here is the delimiter - comma (default) or tabs (will make the life easier if we are pasting in <code>Excel</code></li>
<li><strong>Export</strong> - exports the table and downloads it as <code>CSV</code> file. The file name can be specified (without the <code>.csv</code>) or it will be auto generated (in <code>uuid</code> format)</li>
</ul>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="https://sstoichev.eu/content/images/2020/12/image.png" class="kg-image" alt="Export QMC tables" loading="lazy" width="384" height="135"></figure><!--kg-card-begin: markdown--><h3 id="browserpermissions">Browser permissions</h3>
<p>The extension require the following browser permissions:</p>
<ul>
<li><strong>activeTab</strong> - access to the current active tab</li>
<li><strong>hosts</strong> - access to the host matching the patterns <code>https://*/qmc/*</code> and  <code>https://*/*/qmc/*</code>. This should filter only the host with QMC (with or without virtual proxy). If the extension is started on a host which is not matching the patterns (for example <a href="https://stackoverflow.com/?ref=sstoichev.eu">https://stackoverflow.com/</a>) the following error message will be shown:</li>
</ul>
<!--kg-card-end: markdown--><p></p><figure class="kg-card kg-image-card"><img src="https://sstoichev.eu/content/images/2020/12/image-2.png" class="kg-image" alt="Export QMC tables" loading="lazy" width="384" height="136"></figure><p>If the extension is started on correct host but there are no tables found then the following message will be shown:</p><figure class="kg-card kg-image-card"><img src="https://sstoichev.eu/content/images/2020/12/image-1.png" class="kg-image" alt="Export QMC tables" loading="lazy" width="384" height="135"></figure><!--kg-card-begin: markdown--><h3 id="installation">Installation</h3>
<p>The extension is available for Chrome, Edge (Chromium) and Firefox and can be downloaded from Google and Microsoft extension stores:</p>
<ul>
<li><a href="https://chrome.google.com/webstore/detail/export-qlik-sense-qmc-tab/dbnjjihpapafmihpnionckfipbmalhko?ref=sstoichev.eu">Chrome Web Store</a></li>
<li><a href="https://microsoftedge.microsoft.com/addons/detail/export-qlik-sense-qmc-tab/blejofjglpgmigmclppeokgfaknefnek?ref=sstoichev.eu">Edge Add-ons Store</a></li>
<li><a href="https://addons.mozilla.org/en-GB/firefox/addon/qlik-sense-qmc-tables-as-csv/?ref=sstoichev.eu">Firefox Add-ons store</a></li>
</ul>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><p>Hope you liked it!<br>
Stefan</p>
<!--kg-card-end: markdown--><p></p>]]></content:encoded></item><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&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=2000&amp;fit=max&amp;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 &quot;enigma.js middleware&quot;.</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(&apos;enigma.js&apos;);
const schema = require(&apos;enigma.js/schemas/12.20.0.json&apos;);
const WebSocket = require(&apos;ws&apos;);

const session = enigma.create({
  schema,
  url: &apos;ws://localhost:9076/app/engineData&apos;,
  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">[
    ...
    {&quot;field&quot;: &quot;name-of-the-field&quot;, &quot;value&quot;: &quot;selected-value-1&quot;},
    {&quot;field&quot;: &quot;name-of-the-field&quot;, &quot;value&quot;: &quot;selected-value-2&quot;},
    {&quot;field&quot;: &quot;some-other-field&quot;, &quot;value&quot;: &quot;selected-value-1&quot;}
    ...
]</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: &apos;ws://localhost:9076/app/engineData&apos;,
    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 === &apos;GetLayout&apos; &amp;&amp; 
                response.qInfo.qType == &apos;current-selections-modified&apos;
            ) {
                // if the above is valid then &quot;flatten&quot; 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&apos;s select some values first:</li></ul><pre><code class="language-javascript">    let field = await qDoc.getField(&apos;Product Sub Group Desc&apos;)
    let selectValues = await field.selectValues([
        { qText: &apos;Ice Cream&apos; },
        { qText: &apos;Juice&apos; },
        { qText: &apos;Chips&apos; }
    ])</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 = {
        &quot;qInfo&quot;: {
            &quot;qId&quot;: &quot;&quot;,
            &quot;qType&quot;: &quot;current-selections-modified&quot;
        },
        &quot;qSelectionObjectDef&quot;: {}
    }</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 &quot;disrupt&quot; 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 &quot;flatten&quot; array:</p><pre><code class="language-javascript">[
  { field: &apos;Product Sub Group Desc&apos;, value: &apos;Juice&apos; },
  { field: &apos;Product Sub Group Desc&apos;, value: &apos;Ice Cream&apos; },
  { field: &apos;Product Sub Group Desc&apos;, value: &apos;Chips&apos; }
]</code></pre><p>It&apos;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?ref=sstoichev.eu"><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&#x2019;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">GitHub</span><span class="kg-bookmark-publisher">qlik-oss</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?ref=sstoichev.eu"><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">GitHub</span><span class="kg-bookmark-publisher">countnazgul</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?ref=sstoichev.eu">enigma.js</a> <a href="https://github.com/qlik-oss/enigma.js/blob/master/docs/api.md?ref=sstoichev.eu#mixins">mixins</a>. I&apos;ve started an repo some time ago (and <em>npm </em>package) where I&apos;ve been adding mixins that I&apos;ve found useful (or mixins that I&apos;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 &quot;split&quot; 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: &apos;Value 1&apos;},
  {field: &quot;Field 1&quot;: value: &apos;Value 2&apos;},
  {field: &quot;Field 2&quot;: value: &apos;Value 10&apos;},
  ...
]
</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?ref=sstoichev.eu"><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">GitHub</span><span class="kg-bookmark-publisher">qlik-oss</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 &quot;enigma.js&quot;;
import schema from &quot;enigma.js/schemas/12.20.0.json&quot;;
import enigmaMixins from &quot;enigma-mixins&quot;;

// 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: &quot;ws://localhost:4848/app/engineData&quot;,
  createSocket: (url) =&gt; new WebSocket(url),
});</code></pre><p>Once the session is established and the document is open, then you&apos;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" loading="lazy" width="255" height="479"></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?ref=sstoichev.eu"><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">GitHub</span><span class="kg-bookmark-publisher">countnazgul</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?ref=sstoichev.eu"><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-author">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&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=2000&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ" alt="enigma.js - mixins"><p>You know it already but <a href="https://github.com/qlik-oss/enigma.js?ref=sstoichev.eu">enigma.js</a> is the JavaScript wrapper around the Qlik&apos;s Engine API. </p><p>If you are building a mashup/web app or some sort of automation that require Engine communication you&apos;ll have to use Engine API and here enigma.js is your friend.</p><p>Just to mention that JavaScript is not the only &quot;flavor&quot; 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?ref=sstoichev.eu"><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&#x2019;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">GitHub</span><span class="kg-bookmark-publisher">qlik-oss</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?ref=sstoichev.eu"><div class="kg-bookmark-content"><div class="kg-bookmark-title">q2g/enigma.net</div><div class="kg-bookmark-description">.NET library for consuming Qlik&#x2019;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">GitHub</span><span class="kg-bookmark-publisher">q2g</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?ref=sstoichev.eu#mixins">repo</a>.</p><h2 id="let-s-write-some-code-basics-">Let&apos;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: [&apos;Doc&apos;],
    init(args) {
        console.log(`My mixin is being initialized`);        
    },
    extend: {
        myMixin() {
            console.log(&apos;My mixin was called!&apos;);
        },

    }
}
</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: &apos;ws://localhost:9076/app/engineData&apos;,
    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(&apos;/data/Consumer Sales.qvf&apos;)
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: [&apos;Doc&apos;, &apos;GenericObject&apos;, &apos;GenericBookmark&apos;]
...</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(&apos;some-object-id-here&apos;);
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: [&apos;Doc&apos;],
    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: &apos;ws://localhost:9076/app/engineData&apos;,
    createSocket: (url) =&gt; new WebSocket(url),
});

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

let [myObject, myObjectLayout] = await doc.getLayoutMixin(&apos;some-object-id&apos;);</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&apos;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?ref=sstoichev.eu"><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">GitHub</span><span class="kg-bookmark-publisher">countnazgul</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&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;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&apos;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&amp;ref=sstoichev.eu">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
&#x2502;   config.yml
&#x2502;&#x2500;&#x2500;&#x2500;dist
&#x2502;      LoadScript.qvs
&#x2514;&#x2500;&#x2500;&#x2500;src
    &#x2502;   0--Main.qvs
    &#x2502;   1--Config.qvs
    &#x2502;   2--Mapping.qvs
    &#x2502;   ...
</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&apos;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&apos;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?ref=sstoichev.eu">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? &#x1F609;)</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?ref=sstoichev.eu">qlbuilder GitHub repo</a></p>
<p>Fell free to <a href="https://github.com/countnazgul/qlBuilder/issues?ref=sstoichev.eu">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&apos;m using <a href="link-here">qExt</a> or <a href="link-here">sense-go</a> but in this specific case I didn&apos;t had the chance to use either (enterprise restrictions)</p>
<p>Luckily for me <a href="https://github.com/ahaydon/Qlik-Cli?ref=sstoichev.eu">Qlik-CLI</a> was available. And <code>Qlik-CLI</code> is doing a great job dealing with Qlik Repository API.</p>
<p>I&apos;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
&#x2502;
&#x2514;&#x2500;&#x2500;+ build
&#x2502;    &#x2514;&#x2500;&#x2500;&#x2500; extension-name.zip (auto-generated and uploaded)
&#x2502;   
&#x2514;&#x2500;&#x2500;+ src
&#x2502;    &#x2502;&#x2500;&#x2500;&#x2500;+ static (optional)
&#x2502;    &#x2502;     &#x2502;&#x2500;&#x2500;&#x2500; image.png
&#x2502;    &#x2502;     &#x2514;&#x2500;&#x2500;&#x2500; ...
&#x2502;    &#x2502;      
&#x2502;    &#x2502;&#x2500;&#x2500;&#x2500; extension-name.css
&#x2502;    &#x2502;&#x2500;&#x2500;&#x2500; extension-name.js
&#x2502;    &#x2502;&#x2500;&#x2500;&#x2500; extension-name.qext
&#x2502;    &#x2502;&#x2500;&#x2500;&#x2500; wbfolder.wbl
&#x2502;    &#x2514;&#x2500;&#x2500;&#x2500; ...
&#x2502;
&#x2514;&#x2500;&#x2500;&#x2500; 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 &apos;$extensionName&apos;&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&apos;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&apos;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)" loading="lazy"><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 &apos;ENVIRONMENT&apos;</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)" loading="lazy"></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 &apos;Environment&apos;;

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], &apos;:&apos;, 1) as ENV_NAME,
  subfield([@Value], &apos;:&apos;, 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)" loading="lazy"></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&apos;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 &#x1F91E;) 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?ref=sstoichev.eu">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 &apos;monitor_apps_REST_task&apos;&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 &apos;$($customPropProps.name)&apos;&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&apos;ve started working on one of the idea from my wild ides list. For info <a href="https://github.com/countnazgul/qlikview-projects-documentation?ref=sstoichev.eu">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</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&apos;ve started working on one of the idea from my wild ides list. For info <a href="https://github.com/countnazgul/qlikview-projects-documentation?ref=sstoichev.eu">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/?ref=sstoichev.eu">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&amp;ref=sstoichev.eu">Qlik for Visual Studio Code</a> extension for highlighting QV code developed by <a href="https://github.com/Gimly?ref=sstoichev.eu">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&apos;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" loading="lazy"></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" loading="lazy"></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" loading="lazy"></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" loading="lazy"></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&apos;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?ref=sstoichev.eu">GitHub</a></p>
<p><img src="https://sstoichev.eu/content/images/2016/11/CustomHUB1.png" alt="Qlik Sense Custom Hub" loading="lazy"></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/?ref=sstoichev.eu">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" loading="lazy"></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" loading="lazy"></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" loading="lazy"></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></channel></rss>