The first step towards building our NextGen Quality Analytics Platform
Tags: vue, cubejs, qualibrate
Published: 2019-04-26 15:24:04


Here at qualibrate we’ve been trying to manage the best way to give users the flexibility of advanced reporting and simplicity in generating great dashboards, and while Kibana, Grafana, et al. are great products, we wanted to give our users a seamless experience when they’re using our platform. After discussing the variety of options available versus the case of building our own, we found [Cube.js](https://cube.dev) from [Statsbot.co](https://statsbot.co). We thought that their offering was perfect for our use case, but there was a big caveat—our current platform uses the Vue.js for which there was no implementation. That’s when the spark ignited; why should we reinvent the wheel by trying to copy an incredible product, when instead we could take advantage of what a great community gives us and try to work on this together. We started using Cube.js in our workflow when we saw there was a opportunity to make an improvement on the MongoDB connector. That moment, our relationship with the community began. 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.

This 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](https://cube.dev/blog/building-mongodb-dashboard-using-node.js) 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.

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

Next, install the `@cubejs-client/vue` dependency.

```sh
$ 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

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

Now we can start the development server

```sh
$ npm run serve
```

Voila! Your application will be launched and is available at http://localhost:8080 and should look like this: 
![](https://thepracticaldev.s3.amazonaws.com/i/o3egr2d80hsaxkcz22ea.png)

## 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]( https://cli.vuejs.org/guide/mode-and-env.html#environment-variables ))

```sh
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 entrypoint 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.

```js
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.

```js
<template>
  <div class="hello">
    // Query Renderer component
    <query-renderer :cubejs-api="cubejsApi" :query="query">
      <template v-slot="{ loading, resultSet }">
          <chart-renderer
            v-if="!loading"
            :result-set="resultSet" />
      </template>
    </query-renderer>
  </div>
</template>

<script>
import cubejs from '@cubejs-client/core';
import { QueryRenderer } 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: {
    QueryRenderer,
    ChartRenderer,
  },
  data() {
    const query = {
      measures: ['Orders.count'],
      timeDimensions: [
        {
          dimension: 'LineItems.createdAt',
          granularity: 'month',
        },
      ],
    };

    return {
      cubejsApi,
      selected: undefined,
      query,
    };
  },
  methods: {
    customLabel(a) {
      return a.title;
    },
  },
};
</script>
```

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:

```js
<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>
```

Up until this point it should look something like this:
![](https://thepracticaldev.s3.amazonaws.com/i/xtfxpj2fr9gdbowl3azj.png)

## 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](https://vuejs.org/v2/guide/components-slots.html#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` component](https://cube.dev/docs/@cubejs-client-vue)


```js
<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.

```html
<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.
![](https://thepracticaldev.s3.amazonaws.com/i/phyungp77v22pc7yqsfa.png)

```js
<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](https://cube.dev/docs/@cubejs-client-vue#query-builder).