Back to Blog
Our path towards advanced Quality reporting & analytics

Our path towards advanced Quality reporting & analytics

Ricardo Tapia

As announced early this year we’re working on a brand new Qualibrate, the NextGen Cloud. The project is focused on modernizing our entire application architecture to make our Cloud Service even more robust, scalable, and able to deliver even more efficiency & power to our customers.

The new platform will also offer enhanced capabilities than our current offerings, with a primary focus on user experience and learnings from our proud customers.

During the Heineken Customer Success event, we showed, for the very first time, a preview of the NextGen platform. If you missed it here you can see the highlights of the day.

"Software only becomes valuable when you ship it to customers. Before then it’s just a costly accumulation of hard work and assumptions.”

– DARRAGH CURRAN
VP OF ENGINEERING, INTERCOM

The roadmap of Qualibrate is drafted together with our stakeholders: we listened to our customers and partners. We analyzed the solutions available on the market and the main struggles test managers face in their day to day testing strategy and practices.

The main outcome of this analysis is that Quality is not just about software but processes too: we realized that having a quick and deeper insight into the quality of overall project or process is the key to our customer's success.

With lots of coffees, markers, white boards & discussions we were looking for the best possible solution which can help us to deliver nothing less when it comes to user experience. We wanted to offer a modern analytical solution embedded into our platform, so our customers do not spend time on Analytical Tools with no compromise on quality.

To give users the flexibility of advanced reporting and simplicity in generating great dashboards, we had to make some important decisions. Krunal, our Lead Cloud Architect, decided not to reinvent the wheel but to optimally leverage on the resources. We came across this beautiful open source product Cube.js from Statsbot.co, built for a specific purpose and also very flexible.

Cube js is designed to work with raw data stored in modern data warehouses. It not only queries and transforms data on the fly, it also does background jobs to optimize and pre-aggregate heavy calculations.

We started exploring the feasibility for integrating CubeJs in our NextGen platform and we saw an opportunity to make improvements on the MongoDB connector. That moment, our relationship with the community began with a small contribution to mongobi connector for CueJs.

We thought that CubeJs offering is perfect for our use case, but there was a big caveat: our current platform uses the Vue.js and we found not enough documentation for using CubeJs core with vanilla JS. I, leading next generation UI for Qualibrate, wrote VueJs component to fill the gap along with detailed documentation.

Once we were able to map our database schemas and curate our relationships, our work began. We tried to match the React components as closely as possible in Vue.js. And here is the result.

Set up a demo backend

If you already have Cube.js Backend up and running you can skip this step.
Since we’re using MongoDB for our product, we’ll use it for our tutorial as well. Please follow this guide to set up the Cube.js backend.

Using the Vue.js library

Let’s create a new Vue CLI project using @vue/cli and navigate into it

$ vue create cubejs-mongo-example && cd cubejs-mongo-example

Next, install the @cubejs-client/vue dependency

$ npm install --save @cubejs-client/core @cubejs-client/vue

Also, we're going to add vue-multiselect, vue-chartkick and Chart.js for this example

$ npm install --save vue-multiselect vue-chartkick chart.js

Now we can start the development server

$ npm run serve

Voila! Your application will be launched and is available at http://localhost:8080, and should look like this:

Welcome to your vue.js app

Environment Variables

Vue applications configuration variables can be loaded through a .env file and will be available if using the VUE_APP_ convention (More information available here)

VUE_APP_CUBEJS_API_TOKEN=<YOUR-API-TOKEN>
VUE_APP_CUBEJS_API_URL=<YOUR-API-URL>

Whenever your application is running, this configuration will be accessible through the process.env object.

Configure the dependencies

src/main.js is the default entry point to any generated @vue/cli project;
it’s where the Vue app is mounted. In this case, we’re putting global components so that they’re available everywhere.

import Vue from 'vue';
import VueChartkick from 'vue-chartkick';
import Chart from 'chart.js';
import App from './App.vue';

Vue.config.productionTip = false;
// Add ChartKick components with Chart.js Adapter
Vue.use(VueChartkick, { adapter: Chart });

new Vue({
  render: h => h(App),
}).$mount('#app');

Using the QueryRenderer

The <query-renderer/> component takes a Cube.js object and a formulated query in order to fetch the data and return a resultSet. It handles the request asynchronously and renders the slot once it’s been resolved.

import Vue from 'vue';
import VueChartkick from 'vue-chartkick';
import Chart from 'chart.js';
import App from './App.vue';

Vue.config.productionTip = false;
// Add ChartKick components with Chart.js Adapter
Vue.use(VueChartkick, { adapter: Chart });

new Vue({
  render: h => h(App),
}).$mount('#app');



Here is a simple example of a <chart-renderer/> component for displaying information coming from resultSet mapping the result into an object that is compatible with the vue-chartkick line-chart data series:

<template>
  <div class="chart-renderer">
    <line-chart :data="series"></line-chart>
  </div>
</template>

<script>
export default {
  name: 'ChartRenderer',
  props: {
    resultSet: {
      type: Object,
      required: true,
    },
  },
  computed: {
    series() {
      const seriesNames = this.resultSet.seriesNames();
      const pivot = this.resultSet.chartPivot();
      const series = [];
      seriesNames.forEach((e) => {
        const data = pivot.map(p => [p.x, p[e.key]]);
        series.push({ name: e.key, data });
      });
      return series;
    },
  },
};
</script>



Chart data series

Up until this point, it should look something like this:


Using the QueryBuilder

The <query-builder/> component is a utility that gets the schema information from the API and allows you to generate the query with some helper methods. Some of these include setMeasures and addMeasures, which change the underlying query and update the resultSet on the background. Every available method is part of the scoped slots object.
The only required prop is cubejsApi. It expects an instance of your Cube.js API client returned by the Cube.js method.
Here you can find more detailed reference of the [QueryBuilder] https://cube.dev/docs/@cubejs-client-vue) component

<template>
  <div class="hello">
    <query-builder :cubejs-api="cubejsApi">
      <template v-slot="{ measures, setMeasures, availableMeasures, loading, resultSet }">
        // Render whatever content
      </template>
    </query-builder>
  </div>
</template>

<script>
import cubejs from '@cubejs-client/core';
import { QueryBuilder } from '@cubejs-client/vue';

const cubejsApi = cubejs(
  process.env.VUE_APP_CUBEJS_API_TOKEN,
  { apiUrl: process.env.VUE_APP_CUBEJS_API_URL },
);

export default {
  name: 'HelloWorld',
  components: {
    QueryBuilder,
  },
  data() {
    return {
      cubejsApi,
    };
  },
};
</script>


Then we’re going to add the capability to select some dimensions and measures. It will update the query and render the content accordingly.

The setMeasures and setDimensions methods set the collection directly to the query since these multiselect controls make it a bit easier.

availableMeasures and availableDimensions get the information
from the schema to show allowed inputs.

<template>
  <div class="hello">
    <query-builder :cubejs-api="cubejsApi" :query="query">
      <template v-slot="{ measures, setMeasures, availableMeasures, dimensions, setDimensions, availableDimensions, loading, resultSet }">
        <div class="selects">
          <multiselect
            :multiple="true"
            :customLabel="customLabel"
            @input="setMeasures"
            :value="measures"
            :options="availableMeasures"
            placeholder="Please Select"
            label="Title"
            track-by="name"/>
          <multiselect
            :multiple="true"
            :customLabel="customLabel"
            @input="setDimensions"
            :value="dimensions"
            :options="availableDimensions"
            placeholder="Please Select"
            label="Title"
            track-by="name"/>
        </div>
          <chart-renderer
            v-if="!loading && measures.length > 0"
            :result-set="resultSet" />
      </template>
    </query-builder>
  </div>
</template>

This is a fully working example and should look like this.

Working example
<template>
  <div class="hello">
    <query-builder :cubejs-api="cubejsApi" :query="query">
      <template v-slot="{ measures, setMeasures, availableMeasures, dimensions, setDimensions, availableDimensions, loading, resultSet }">
        <div class="selects">
          <multiselect
            :multiple="true"
            :customLabel="customLabel"
            @input="setMeasures"
            :value="measures"
            :options="availableMeasures"
            placeholder="Please Select"
            label="Title"
            track-by="name"/>
          <multiselect
            :multiple="true"
            :customLabel="customLabel"
            @input="setDimensions"
            :value="dimensions"
            :options="availableDimensions"
            placeholder="Please Select"
            label="Title"
            track-by="name"/>
        </div>
          <chart-renderer
            v-if="!loading && measures.length > 0"
            :result-set="resultSet" />
      </template>
    </query-builder>
  </div>
</template>

<script>
import cubejs from '@cubejs-client/core';
import Multiselect from 'vue-multiselect';
import { QueryBuilder } from '@cubejs-client/vue';
import ChartRenderer from './ChartRenderer.vue';

const cubejsApi = cubejs(
  process.env.VUE_APP_CUBEJS_API_TOKEN,
  { apiUrl: process.env.VUE_APP_CUBEJS_API_URL },
);

export default {
  name: 'HelloWorld',
  components: {
    Multiselect,
    QueryBuilder,
    ChartRenderer,
  },
  data() {
    const query = {
      measures: [],
      timeDimensions: [
      ],
    };

    return {
      cubejsApi,
      selected: undefined,
      query,
    };
  },
  methods: {
    customLabel(a) {
      return a.title;
    },
  },
};
</script>
<style src="vue-multiselect/dist/vue-multiselect.min.css"></style>

<style scoped>
.hello {
  padding: 0 10rem;
}

.selects {
  display: flex;
}

.selects .multiselect {
  margin: 0.5rem;
}
</style>



Similar to measures, availableMeasures, and updateMeasures, there are properties to render and dimensions, segments, time, filters, and chart types to manage. You can find the full list of properties in the documentation.

I hope this guide was useful. In case you have any doubt feel free to write it in the comments below.

Share on social media: 

More from the Blog

SAP Activate – What it is & Why You would use it?

What is SAP activate? Activate is SAP’s implementation toolkit. It is more than just a methodology. It’s not pure Agile, not really Waterfall, it is somewhere in between...

Read Story

Software quality: a mind game

Why many technology initiatives are failing? Tools are necessary but not sufficient. Software quality is a mind game: the right mindset is the key to success. Read the story of René, Test manager in Province Noord-Holland.

Read Story

Service Virtualization for SAP – You Could Save €240k (and Hit Deadlines)

Does your SAP implementation involve multiple workstreams? Could 3rd party integration slow your progress? If yes to either, Service Virtualization could help you reduce costly delays and help you keep your SAP project under control.

Read Story