Commit 5d27100f authored by Kushal Pandya's avatar Kushal Pandya

Merge branch '196184-convert-insights-to-echarts' into 'master'

Resolve "Convert Insights to ECharts"

Closes #13006 and #196184

See merge request gitlab-org/gitlab!24661
parents 5e0cb86c 71a6cbe6
---
title: Move insights charts to echarts
merge_request: 24661
author:
type: other
......@@ -96,7 +96,7 @@ The following table lists available parameters for charts:
| Keyword | Description |
|:---------------------------------------------------|:------------|
| [`title`](#title) | The title of the chart. This will displayed on the Insights page. |
| [`type`](#type) | The type of chart: `bar`, `line`, `stacked-bar`, `pie` etc. |
| [`type`](#type) | The type of chart: `bar`, `line` or `stacked-bar`. |
| [`query`](#query) | A hash that defines the conditions for issues / merge requests to be part of the chart. |
## Parameter details
......@@ -132,7 +132,6 @@ Supported values are:
| ----- | ------- |
| `bar` | ![Insights example bar chart](img/insights_example_bar_chart.png) |
| `bar` (time series, i.e. when `group_by` is used) | ![Insights example bar time series chart](img/insights_example_bar_time_series_chart.png) |
| `pie` | ![Insights example pie chart](img/insights_example_pie_chart.png) |
| `line` | ![Insights example stacked bar chart](img/insights_example_line_chart.png) |
| `stacked-bar` | ![Insights example stacked bar chart](img/insights_example_stacked_bar_chart.png) |
......
<script>
import BaseChart from './insights_chart.vue';
import * as chartOptions from '~/lib/utils/chart_utils';
export default {
extends: BaseChart,
computed: {
config() {
return {
type: 'bar',
data: this.data,
options: {
...chartOptions.barChartOptions(),
...this.title(),
...this.commonOptions(),
},
};
},
},
};
</script>
<template>
<div class="chart-canvas-wrapper">
<canvas ref="insightsChart" class="bar" height="300"></canvas>
</div>
</template>
<script>
import Chart from 'chart.js';
export default {
props: {
chartTitle: {
type: String,
required: true,
},
data: {
type: Object,
required: true,
},
},
computed: {
config() {
return {};
},
},
mounted() {
this.drawChart();
},
methods: {
title() {
return {
title: {
display: true,
text: this.chartTitle,
},
};
},
commonOptions() {
return {
responsive: true,
maintainAspectRatio: false,
legend: false,
};
},
drawChart() {
const ctx = this.$refs.insightsChart.getContext('2d');
return new Chart(ctx, this.config);
},
},
};
</script>
<template>
<div class="chart-canvas-wrapper">
<canvas ref="insightsChart" height="300"></canvas>
</div>
</template>
<script>
import BaseChart from './insights_chart.vue';
export default {
extends: BaseChart,
computed: {
config() {
return {
type: 'line',
data: this.data,
options: {
...this.title(),
...this.commonOptions(),
...this.elements(),
...this.scales(),
},
};
},
},
methods: {
elements() {
return {
elements: {
line: {
tension: 0,
fill: false,
},
},
};
},
scales() {
return {
scales: {
yAxes: [
{
ticks: {
beginAtZero: true,
},
},
],
},
};
},
},
};
</script>
<template>
<div class="chart-canvas-wrapper">
<canvas ref="insightsChart" class="line" height="300"></canvas>
</div>
</template>
<script>
import BaseChart from './insights_chart.vue';
import * as chartOptions from '~/lib/utils/chart_utils';
export default {
extends: BaseChart,
computed: {
config() {
return {
type: 'pie',
data: this.data,
options: {
...chartOptions.pieChartOptions(),
...this.title(),
...this.commonOptions(),
},
};
},
},
};
</script>
<template>
<div class="chart-canvas-wrapper">
<canvas ref="insightsChart" class="pie" height="180"></canvas>
</div>
</template>
<script>
import BaseChart from './insights_chart.vue';
import * as chartOptions from '~/lib/utils/chart_utils';
export default {
extends: BaseChart,
computed: {
config() {
return {
type: 'bar',
data: this.data,
options: {
...chartOptions.barChartOptions(),
...this.title(),
...this.commonOptions(),
...this.tooltips(),
...this.scales(),
},
};
},
},
methods: {
tooltips() {
return {
tooltips: {
mode: 'index',
},
};
},
scales() {
return {
scales: {
xAxes: [
{
stacked: true,
},
],
yAxes: [
{
stacked: true,
ticks: {
beginAtZero: true,
},
},
],
},
};
},
},
};
</script>
<template>
<div class="chart-canvas-wrapper">
<canvas ref="insightsChart" class="stacked-bar" height="300"></canvas>
</div>
</template>
<script>
import { GlColumnChart, GlLineChart, GlStackedColumnChart } from '@gitlab/ui/dist/charts';
import { getSvgIconPathContent } from '~/lib/utils/icon_utils';
import ResizableChartContainer from '~/vue_shared/components/resizable_chart/resizable_chart_container.vue';
import InsightsChartError from './insights_chart_error.vue';
import { CHART_TYPES } from '../constants';
const CHART_HEIGHT = 300;
export default {
components: {
GlColumnChart,
GlLineChart,
GlStackedColumnChart,
ResizableChartContainer,
InsightsChartError,
},
props: {
loaded: {
type: Boolean,
required: true,
},
type: {
type: String,
required: true,
},
title: {
type: String,
required: false,
default: '',
},
data: {
type: Object,
required: true,
},
error: {
type: String,
required: false,
default: '',
},
},
data() {
return {
svgs: {},
};
},
computed: {
dataZoomConfig() {
const handleIcon = this.svgs['scroll-handle'];
return handleIcon ? { handleIcon } : {};
},
chartOptions() {
let options = {
yAxis: {
minInterval: 1,
},
};
if (this.type === this.$options.chartTypes.LINE) {
options = {
...options,
xAxis: {
...options.xAxis,
name: this.data.xAxisTitle,
type: 'category',
},
yAxis: {
...options.yAxis,
name: this.data.yAxisTitle,
type: 'value',
},
};
}
return { dataZoom: [this.dataZoomConfig], ...options };
},
isColumnChart() {
return [this.$options.chartTypes.BAR, this.$options.chartTypes.PIE].includes(this.type);
},
isStackedColumnChart() {
return this.type === this.$options.chartTypes.STACKED_BAR;
},
isLineChart() {
return this.type === this.$options.chartTypes.LINE;
},
},
methods: {
setSvg(name) {
return getSvgIconPathContent(name)
.then(path => {
if (path) {
this.$set(this.svgs, name, `path://${path}`);
}
})
.catch(e => {
// eslint-disable-next-line no-console, @gitlab/i18n/no-non-i18n-strings
console.error('SVG could not be rendered correctly: ', e);
});
},
onChartCreated() {
this.setSvg('scroll-handle');
},
},
height: CHART_HEIGHT,
chartTypes: CHART_TYPES,
};
</script>
<template>
<resizable-chart-container v-if="loaded" class="insights-chart">
<h5 class="text-center">{{ title }}</h5>
<gl-column-chart
v-if="isColumnChart"
v-bind="$attrs"
:height="$options.height"
:data="data.datasets"
x-axis-type="category"
:x-axis-title="data.xAxisTitle"
:y-axis-title="data.yAxisTitle"
:option="chartOptions"
@created="onChartCreated"
/>
<gl-stacked-column-chart
v-else-if="isStackedColumnChart"
v-bind="$attrs"
:height="$options.height"
:data="data.datasets"
:group-by="data.labels"
:series-names="data.seriesNames"
x-axis-type="category"
:x-axis-title="data.xAxisTitle"
:y-axis-title="data.yAxisTitle"
:option="chartOptions"
@created="onChartCreated"
/>
<gl-line-chart
v-else-if="isLineChart"
v-bind="$attrs"
:height="$options.height"
:data="data.datasets"
:option="chartOptions"
@created="onChartCreated"
/>
</resizable-chart-container>
<div v-else class="insights-chart">
<insights-chart-error
:chart-name="title"
:title="__('This chart could not be displayed')"
:summary="__('Please check the configuration file for this chart')"
:error="error"
/>
</div>
</template>
<script>
import { mapActions, mapState } from 'vuex';
import _ from 'underscore';
import { GlLoadingIcon } from '@gitlab/ui';
import { isUndefined } from 'lodash';
import { mapActions, mapState } from 'vuex';
import InsightsChartError from './insights_chart_error.vue';
import InsightsConfigWarning from './insights_config_warning.vue';
import Bar from './chart_js/bar.vue';
import LineChart from './chart_js/line.vue';
import Pie from './chart_js/pie.vue';
import StackedBar from './chart_js/stacked_bar.vue';
import InsightsChart from './insights_chart.vue';
export default {
components: {
GlLoadingIcon,
InsightsChartError,
InsightsChart,
InsightsConfigWarning,
Bar,
LineChart,
Pie,
StackedBar,
},
props: {
queryEndpoint: {
......@@ -44,7 +34,7 @@ export default {
return Object.keys(this.chartData);
},
hasChartsConfigured() {
return !_.isUndefined(this.charts) && this.charts.length > 0;
return !isUndefined(this.charts) && this.charts.length > 0;
},
},
watch: {
......@@ -58,15 +48,6 @@ export default {
},
methods: {
...mapActions('insights', ['fetchChartData', 'initChartData', 'setPageLoading']),
chartType(type) {
switch (type) {
case 'line':
// Apparently Line clashes with another component
return 'line-chart';
default:
return type;
}
},
fetchCharts() {
if (this.hasChartsConfigured) {
this.initChartData(this.chartKeys);
......@@ -94,22 +75,16 @@ export default {
<div v-if="hasChartsConfigured" class="js-insights-page-container">
<h4 class="text-center">{{ pageConfig.title }}</h4>
<div v-if="!pageLoading" class="insights-charts" data-qa-selector="insights_charts">
<div v-for="(insights, key, index) in chartData" :key="index" class="insights-chart">
<component
:is="chartType(insights.type)"
v-if="insights.loaded"
:chart-title="key"
:data="insights.data"
/>
<insights-chart-error
v-else
:chart-name="key"
:title="__('This chart could not be displayed')"
:summary="__('Please check the configuration file for this chart')"
:error="insights.error"
<insights-chart
v-for="({ loaded, type, data, error }, key, index) in chartData"
:key="index"
:loaded="loaded"
:type="type"
:title="key"
:data="data"
:error="error"
/>
</div>
</div>
<div v-else class="insights-chart-loading text-center">
<gl-loading-icon :inline="true" size="lg" />
</div>
......
export const CHART_TYPES = {
BAR: 'bar',
LINE: 'line',
STACKED_BAR: 'stacked-bar',
// Only used to convert to bar
PIE: 'pie',
};
export default { CHART_TYPES };
import * as types from './mutation_types';
import axios from '~/lib/utils/axios_utils';
import createFlash from '~/flash';
import { __ } from '~/locale';
import * as types from './mutation_types';
export const requestConfig = ({ commit }) => commit(types.REQUEST_CONFIG);
export const receiveConfigSuccess = ({ commit }, data) =>
commit(types.RECEIVE_CONFIG_SUCCESS, data);
......@@ -38,7 +39,12 @@ export const receiveChartDataError = ({ commit }, { chart, error }) =>
export const fetchChartData = ({ dispatch }, { endpoint, chart }) =>
axios
.post(endpoint, chart)
.then(({ data }) => dispatch('receiveChartDataSuccess', { chart, data }))
.then(({ data }) =>
dispatch('receiveChartDataSuccess', {
chart,
data,
}),
)
.catch(error => {
let message = `${__('There was an error gathering the chart data')}`;
......
import { __ } from '~/locale';
import { CHART_TYPES } from 'ee/insights/constants';
const getAxisTitle = label => {
switch (label) {
case 'day':
return __('Days');
case 'week':
return __('Weeks');
case 'month':
return __('Months');
case 'issue':
return __('Issues');
case 'merge_request':
return __('Merge requests');
default:
return '';
}
};
export const transformChartDataForGlCharts = (
{ type, query: { group_by, issuable_type } },
{ labels, datasets },
) => {
const formattedData = {
xAxisTitle: getAxisTitle(group_by),
yAxisTitle: getAxisTitle(issuable_type),
labels,
datasets: [],
seriesNames: [],
};
switch (type) {
case CHART_TYPES.STACKED_BAR:
formattedData.datasets = datasets.map(dataset => dataset.data);
formattedData.seriesNames = datasets.map(dataset => dataset.label);
break;
case CHART_TYPES.LINE:
formattedData.datasets.push(
...datasets.map(dataset => ({
name: dataset.label,
data: labels.map((label, i) => [label, dataset.data[i]]),
})),
);
break;
default:
formattedData.datasets = { all: labels.map((label, i) => [label, datasets[0].data[i]]) };
}
return formattedData;
};
export default {
transformChartDataForGlCharts,
};
import _ from 'underscore';
import { pick } from 'lodash';
import { transformChartDataForGlCharts } from './helpers';
import * as types from './mutation_types';
export default {
......@@ -7,12 +9,10 @@ export default {
state.configLoading = true;
},
[types.RECEIVE_CONFIG_SUCCESS](state, data) {
const validConfig = _.pick(
state.configData = pick(
data,
Object.keys(data).filter(key => data[key].title && data[key].charts),
);
state.configData = validConfig;
state.configLoading = false;
},
[types.RECEIVE_CONFIG_ERROR](state) {
......@@ -25,7 +25,7 @@ export default {
state.chartData[chart.title] = {
type,
data,
data: transformChartDataForGlCharts(chart, data),
loaded: true,
};
},
......@@ -34,7 +34,7 @@ export default {
state.chartData[chart.title] = {
type,
data: null,
data: {},
loaded: false,
error,
};
......
import { CHART_TYPES } from 'ee/insights/constants';
import { transformChartDataForGlCharts } from 'ee/insights/stores/modules/insights/helpers';
describe('Insights helpers', () => {
describe('transformChartDataForGlCharts', () => {
it('sets the x axis label to "Months"', () => {
const chart = {
type: CHART_TYPES.BAR,
query: { group_by: 'month', issuable_type: 'issue' },
};
const data = {
labels: ['January', 'February'],
datasets: [{ label: 'Dataset 1', data: [1] }, { label: 'Dataset 2', data: [2] }],
};
expect(transformChartDataForGlCharts(chart, data).xAxisTitle).toEqual('Months');
});
it('sets the y axis label to "Issues"', () => {
const chart = {
type: CHART_TYPES.BAR,
query: { group_by: 'month', issuable_type: 'issue' },
};
const data = {
labels: ['January', 'February'],
datasets: [{ label: 'Dataset 1', data: [1] }, { label: 'Dataset 2', data: [2] }],
};
expect(transformChartDataForGlCharts(chart, data).yAxisTitle).toEqual('Issues');
});
it('copies the data to the datasets for stacked bar charts', () => {
const chart = {
type: CHART_TYPES.STACKED_BAR,
query: { group_by: 'month', issuable_type: 'issue' },
};
const data = {
labels: ['January', 'February'],
datasets: [{ label: 'Dataset 1', data: [1] }, { label: 'Dataset 2', data: [2] }],
};
expect(transformChartDataForGlCharts(chart, data).datasets).toEqual([[1], [2]]);
});
it('copies the dataset labels to seriesNames for stacked bar charts', () => {
const chart = {
type: CHART_TYPES.STACKED_BAR,
query: { group_by: 'month', issuable_type: 'issue' },
};
const data = {
labels: ['January', 'February'],
datasets: [{ label: 'Dataset 1', data: [1] }, { label: 'Dataset 2', data: [2] }],
};
expect(transformChartDataForGlCharts(chart, data).seriesNames).toEqual([
'Dataset 1',
'Dataset 2',
]);
});
it('creates an array of objects containing name and data attributes for line charts', () => {
const chart = {
type: CHART_TYPES.LINE,
query: { group_by: 'month', issuable_type: 'issue' },
};
const data = {
labels: ['January', 'February'],
datasets: [{ label: 'Dataset 1', data: [1, 2] }, { label: 'Dataset 2', data: [2, 3] }],
};
expect(transformChartDataForGlCharts(chart, data).datasets).toStrictEqual([
{ name: 'Dataset 1', data: [['January', 1], ['February', 2]] },
{ name: 'Dataset 2', data: [['January', 2], ['February', 3]] },
]);
});
it('creates an object of all containing an array of label / data pairs for bar charts', () => {
const chart = {
type: CHART_TYPES.BAR,
query: { group_by: 'month', issuable_type: 'issue' },
};
const data = {
labels: ['January', 'February'],
datasets: [{ data: [1, 2] }],
};
expect(transformChartDataForGlCharts(chart, data).datasets).toEqual({
all: [['January', 1], ['February', 2]],
});
});
it('creates an object of all containing an array of label / data pairs for pie charts', () => {
const chart = {
type: CHART_TYPES.PIE,
query: { group_by: 'month', issuable_type: 'issue' },
};
const data = {
labels: ['January', 'February'],
datasets: [{ data: [1, 2] }],
};
expect(transformChartDataForGlCharts(chart, data).datasets).toEqual({
all: [['January', 1], ['February', 2]],
});
});
});
});
import createState from 'ee/insights/stores/modules/insights/state';
import mutations from 'ee/insights/stores/modules/insights/mutations';
import * as types from 'ee/insights/stores/modules/insights/mutation_types';
import { CHART_TYPES } from 'ee/insights/constants';
import { configData } from '../../../../javascripts/insights/mock_data';
describe('Insights mutations', () => {
let state;
const chart = {
title: 'Bugs Per Team',
type: 'stacked-bar',
type: CHART_TYPES.STACKED_BAR,
query: {
name: 'filter_issues_by_label_category',
filter_label: 'bug',
category_labels: ['Plan', 'Create', 'Manage'],
group_by: 'month',
issuable_type: 'issue',
},
};
......@@ -83,8 +87,8 @@ describe('Insights mutations', () => {
});
describe(types.RECEIVE_CHART_SUCCESS, () => {
const data = {
labels: ['January'],
const incomingData = {
labels: ['January', 'February'],
datasets: [
{
label: 'Dataset 1',
......@@ -101,24 +105,32 @@ describe('Insights mutations', () => {
],
};
const transformedData = {
datasets: [[1], [2]],
labels: ['January', 'February'],
xAxisTitle: 'Months',
yAxisTitle: 'Issues',
seriesNames: ['Dataset 1', 'Dataset 2'],
};
it('sets charts loaded state to true on success', () => {
mutations[types.RECEIVE_CHART_SUCCESS](state, { chart, data });
mutations[types.RECEIVE_CHART_SUCCESS](state, { chart, data: incomingData });
const { chartData } = state;
expect(chartData[chart.title].loaded).toBe(true);
});
it('sets charts data to incoming data on success', () => {
mutations[types.RECEIVE_CHART_SUCCESS](state, { chart, data });
it('sets charts data to transformed data on success', () => {
mutations[types.RECEIVE_CHART_SUCCESS](state, { chart, data: incomingData });
const { chartData } = state;
expect(chartData[chart.title].data).toBe(data);
expect(chartData[chart.title].data).toStrictEqual(transformedData);
});
it('sets charts type to incoming type on success', () => {
mutations[types.RECEIVE_CHART_SUCCESS](state, { chart, data });
mutations[types.RECEIVE_CHART_SUCCESS](state, { chart, data: incomingData });
const { chartData } = state;
......@@ -137,12 +149,12 @@ describe('Insights mutations', () => {
expect(chartData[chart.title].loaded).toBe(false);
});
it('sets charts data state to null on error', () => {
it('sets charts data state to an empty object on error', () => {
mutations[types.RECEIVE_CHART_ERROR](state, { chart, error });
const { chartData } = state;
expect(chartData[chart.title].data).toBe(null);
expect(Object.keys(chartData[chart.title].data).length).toBe(0);
});
it('sets charts type to incoming type on error', () => {
......
import Vue from 'vue';
import Chart from 'ee/insights/components/chart_js/bar.vue';
import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { chartInfo, chartData } from '../../mock_data';
describe('Insights bar chart component', () => {
let vm;
let mountComponent;
const Component = Vue.extend(Chart);
beforeEach(() => {
mountComponent = data => {
const props = data || {
chartTitle: chartInfo.title,
data: chartData,
};
return mountComponentWithStore(Component, { props });
};
vm = mountComponent();
});
afterEach(() => {
vm.$destroy();
});
it('has the correct config', done => {
expect(vm.config.type).toBe('bar');
expect(vm.config.data).toBe(chartData);
expect(vm.config.options.title.text).toBe(chartInfo.title);
done();
});
});
import Vue from 'vue';
import Chart from 'ee/insights/components/chart_js/line.vue';
import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { chartInfo, chartData } from '../../mock_data';
describe('Insights line chart component', () => {
let vm;
let mountComponent;
const Component = Vue.extend(Chart);
beforeEach(() => {
mountComponent = data => {
const props = data || {
chartTitle: chartInfo.title,
data: chartData,
};
return mountComponentWithStore(Component, { props });
};
vm = mountComponent();
});
afterEach(() => {
vm.$destroy();
});
it('has the correct config', done => {
expect(vm.config.type).toBe('line');
expect(vm.config.data).toBe(chartData);
expect(vm.config.options.title.text).toBe(chartInfo.title);
expect(vm.config.options.elements.line.tension).toBe(0);
expect(vm.config.options.elements.line.fill).toBe(false);
done();
});
});
import Vue from 'vue';
import Chart from 'ee/insights/components/chart_js/stacked_bar.vue';
import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { chartInfo, chartData } from '../../mock_data';
describe('Insights Stacked Bar chart component', () => {
let vm;
let mountComponent;
const Component = Vue.extend(Chart);
beforeEach(() => {
mountComponent = data => {
const props = data || {
chartTitle: chartInfo.title,
data: chartData,
};
return mountComponentWithStore(Component, { props });
};
vm = mountComponent();
});
afterEach(() => {
vm.$destroy();
});
it('has the correct config', done => {
expect(vm.config.type).toBe('bar');
expect(vm.config.data).toBe(chartData);
expect(vm.config.options.title.text).toBe(chartInfo.title);
expect(vm.config.options.tooltips.mode).toBe('index');
expect(vm.config.options.scales.xAxes[0].stacked).toBe(true);
expect(vm.config.options.scales.yAxes[0].stacked).toBe(true);
done();
});
});
import InsightsChartError from 'ee/insights/components/insights_chart_error.vue';
import { shallowMount } from '@vue/test-utils';
describe('Insights chart error component', () => {
const chartName = 'Test chart';
const title = 'This chart could not be displayed';
const summary = 'Please check the configuration file for this chart';
const error = 'Test error';
let wrapper;
beforeEach(() => {
wrapper = shallowMount(InsightsChartError, {
propsData: { chartName, title, summary, error },
});
});
afterEach(() => {
wrapper.destroy();
});
it('renders the component', () => {
expect(wrapper.find('.content-title').text()).toEqual(`${title}: "${chartName}"`);
const summaries = wrapper.findAll('.content-summary');
expect(summaries.at(0).text()).toEqual(summary);
expect(summaries.at(1).text()).toEqual(error);
});
});
import { GlColumnChart, GlLineChart, GlStackedColumnChart } from '@gitlab/ui/dist/charts';
import { shallowMount } from '@vue/test-utils';
import { chartInfo, barChartData, lineChartData, stackedBarChartData } from '../mock_data';
import InsightsChart from 'ee/insights/components/insights_chart.vue';
import InsightsChartError from 'ee/insights/components/insights_chart_error.vue';
import { CHART_TYPES } from 'ee/insights/constants';
describe('Insights chart component', () => {
let wrapper;
const factory = propsData =>
shallowMount(InsightsChart, {
propsData,
stubs: { 'gl-column-chart': true, 'insights-chart-error': true },
});
afterEach(() => {
wrapper.destroy();
});
describe('when chart is loaded', () => {
it('displays a bar chart', () => {
wrapper = factory({
loaded: true,
type: CHART_TYPES.BAR,
title: chartInfo.title,
data: barChartData,
error: '',
});
expect(wrapper.contains(GlColumnChart)).toBe(true);
});
it('displays a line chart', () => {
wrapper = factory({
loaded: true,
type: CHART_TYPES.LINE,
title: chartInfo.title,
data: lineChartData,
error: '',
});
expect(wrapper.contains(GlLineChart)).toBe(true);
});
it('displays a stacked bar chart', () => {
wrapper = factory({
loaded: true,
type: CHART_TYPES.STACKED_BAR,
title: chartInfo.title,
data: stackedBarChartData,
error: '',
});
expect(wrapper.contains(GlStackedColumnChart)).toBe(true);
});
it('displays a bar chart when a pie chart is requested', () => {
wrapper = factory({
loaded: true,
type: CHART_TYPES.PIE,
title: chartInfo.title,
data: barChartData,
error: '',
});
expect(wrapper.contains(GlColumnChart)).toBe(true);
});
});
describe('when chart receives an error', () => {
const error = 'my error';
beforeEach(() => {
wrapper = factory({
loaded: false,
type: chartInfo.type,
title: chartInfo.title,
data: {},
error,
});
});
it('displays info about the error', () => {
expect(wrapper.contains(InsightsChartError)).toBe(true);
});
});
});
import InsightsConfigWarning from 'ee/insights/components/insights_config_warning.vue';
import { shallowMount } from '@vue/test-utils';
describe('Insights config warning component', () => {
const image = 'illustrations/monitoring/getting_started.svg';
const title = 'There are no charts configured for this page';
const summary =
'Please check the configuration file to ensure that a collection of charts has been declared.';
let wrapper;
beforeEach(() => {
wrapper = shallowMount(InsightsConfigWarning, {
propsData: { image, title, summary },
});
});
afterEach(() => {
wrapper.destroy();
});
it('renders the component', () => {
expect(
wrapper
.findAll('.content-image')
.at(0)
.attributes('src'),
).toContain(image);
expect(wrapper.find('.content-title').text()).toEqual(title);
expect(wrapper.find('.content-summary').text()).toEqual(summary);
});
});
......@@ -2,7 +2,7 @@ import Vue from 'vue';
import InsightsPage from 'ee/insights/components/insights_page.vue';
import { createStore } from 'ee/insights/stores';
import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { chartInfo, pageInfo, pageInfoNoCharts, chartData } from '../mock_data';
import { chartInfo, pageInfo, pageInfoNoCharts } from '../mock_data';
describe('Insights page component', () => {
let component;
......@@ -77,55 +77,6 @@ describe('Insights page component', () => {
});
});
describe('when charts loaded', () => {
beforeEach(() => {
component.$store.state.insights.pageLoading = false;
component.$store.state.insights.chartData[chartInfo.title] = {
type: chartInfo.type,
data: chartData,
loaded: true,
};
});
it('displays correct chart post load', done => {
component.$nextTick(() => {
const chartCanvas = component.$el.querySelectorAll(
'.js-insights-page-container .insights-charts .insights-chart canvas',
);
expect(chartCanvas.length).toEqual(1);
expect(chartCanvas[0].classList).toContain('bar');
done();
});
});
});
describe('chart data retrieve error', () => {
const error = 'my error';
beforeEach(() => {
component.$store.state.insights.pageLoading = false;
component.$store.state.insights.chartData[chartInfo.title] = {
type: chartInfo.type,
data: null,
loaded: false,
error,
};
});
it('displays info about the error', done => {
component.$nextTick(() => {
const errorElements = component.$el.querySelectorAll(
'.js-insights-page-container .insights-charts .insights-chart .js-empty-state',
);
expect(errorElements.length).toEqual(1);
expect(errorElements[0].textContent).toContain(error);
done();
});
});
});
describe('pageConfig changes', () => {
it('reflects new state', done => {
// Establish rendered state
......
import { CHART_TYPES } from 'ee/insights/constants';
export const chartInfo = {
title: 'Bugs Per Team',
type: 'bar',
type: CHART_TYPES.BAR,
query: {
name: 'filter_issues_by_label_category',
filter_label: 'bug',
......@@ -8,22 +10,37 @@ export const chartInfo = {
},
};
export const chartData = {
labels: ['January'],
export const barChartData = {
labels: ['January', 'February'],
datasets: {
all: [['January', 1], ['February', 2]],
},
xAxisTitle: 'Months',
yAxisTitle: 'Issues',
};
export const lineChartData = {
labels: ['January', 'February'],
datasets: [
{
label: 'Dataset 1',
fill: true,
backgroundColor: ['rgba(255, 99, 132)'],
data: [1],
data: [['January', 1], ['February', 2]],
name: 'Alpha',
},
{
label: 'Dataset 2',
fill: true,
backgroundColor: ['rgba(54, 162, 235)'],
data: [2],
data: [['January', 1], ['February', 2]],
name: 'Beta',
},
],
xAxisTitle: 'Months',
yAxisTitle: 'Issues',
};
export const stackedBarChartData = {
labels: ['January', 'February'],
datasets: [[1, 2], [1, 2]],
seriesNames: ['Series 1', 'Series 2'],
xAxisTitle: 'Months',
yAxisTitle: 'Issues',
};
export const pageInfo = {
......
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import testAction from 'spec/helpers/vuex_action_helper';
import actionsModule, * as actions from 'ee/insights/stores/modules/insights/actions';
import axios from '~/lib/utils/axios_utils';
import { CHART_TYPES } from 'ee/insights/constants';
const ERROR_MESSAGE = 'TEST_ERROR_MESSAGE';
......@@ -9,11 +12,13 @@ describe('Insights store actions', () => {
const key = 'bugsPerTeam';
const chart = {
title: 'Bugs Per Team',
type: 'stacked-bar',
type: CHART_TYPES.STACKED_BAR,
query: {
name: 'filter_issues_by_label_category',
filter_label: 'bug',
category_labels: ['Plan', 'Create', 'Manage'],
group_by: 'month',
issuable_type: 'issue',
},
};
const page = {
......@@ -149,7 +154,7 @@ describe('Insights store actions', () => {
});
describe('receiveChartDataSuccess', () => {
const chartData = { type: 'bar', data: {} };
const chartData = { type: CHART_TYPES.BAR, data: {} };
it('commits RECEIVE_CHART_SUCCESS', done => {
testAction(
......@@ -194,7 +199,7 @@ describe('Insights store actions', () => {
const payload = { endpoint: `${gl.TEST_HOST}/query`, chart };
const chartData = {
labels: ['January'],
labels: ['January', 'February'],
datasets: [
{
label: 'Dataset 1',
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment