Commit 54cc5381 authored by Phil Hughes's avatar Phil Hughes

Merge branch...

Merge branch '217858-improve-productivity-analytics-error-when-query-takes-too-long-to-calculate' into 'master'

Improve Productivity Analytics error message

Closes #217858

See merge request gitlab-org/gitlab!39074
parents d3936df4 c992efda
---
title: 'Productivity Analytics: Improve error message when query takes too long to calculate'
merge_request: 39074
author:
type: other
...@@ -71,6 +71,7 @@ export default { ...@@ -71,6 +71,7 @@ export default {
...mapGetters(['getMetricTypes']), ...mapGetters(['getMetricTypes']),
...mapGetters('charts', [ ...mapGetters('charts', [
'chartLoading', 'chartLoading',
'chartErrorCode',
'chartHasData', 'chartHasData',
'getColumnChartData', 'getColumnChartData',
'getColumnChartDatazoomOption', 'getColumnChartDatazoomOption',
...@@ -199,6 +200,7 @@ export default { ...@@ -199,6 +200,7 @@ export default {
__('You can filter by \'days to merge\' by clicking on the columns in the chart.') __('You can filter by \'days to merge\' by clicking on the columns in the chart.')
" "
:is-loading="chartLoading(chartKeys.main)" :is-loading="chartLoading(chartKeys.main)"
:error-code="chartErrorCode(chartKeys.main)"
:chart-data="getColumnChartData(chartKeys.main)" :chart-data="getColumnChartData(chartKeys.main)"
> >
<gl-column-chart <gl-column-chart
......
...@@ -3,6 +3,7 @@ import { isEmpty } from 'lodash'; ...@@ -3,6 +3,7 @@ import { isEmpty } from 'lodash';
import { GlDeprecatedDropdown, GlDeprecatedDropdownItem, GlLoadingIcon } from '@gitlab/ui'; import { GlDeprecatedDropdown, GlDeprecatedDropdownItem, GlLoadingIcon } from '@gitlab/ui';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import httpStatusCodes from '~/lib/utils/http_status';
export default { export default {
name: 'MetricChart', name: 'MetricChart',
...@@ -28,6 +29,11 @@ export default { ...@@ -28,6 +29,11 @@ export default {
required: false, required: false,
default: false, default: false,
}, },
errorCode: {
type: Number,
required: false,
default: null,
},
metricTypes: { metricTypes: {
type: Array, type: Array,
required: false, required: false,
...@@ -52,9 +58,23 @@ export default { ...@@ -52,9 +58,23 @@ export default {
const foundMetric = this.metricTypes.find(m => m.key === this.selectedMetric); const foundMetric = this.metricTypes.find(m => m.key === this.selectedMetric);
return foundMetric ? foundMetric.label : s__('MetricChart|Please select a metric'); return foundMetric ? foundMetric.label : s__('MetricChart|Please select a metric');
}, },
isServerError() {
return this.errorCode === httpStatusCodes.INTERNAL_SERVER_ERROR;
},
hasChartData() { hasChartData() {
return !isEmpty(this.chartData); return !isEmpty(this.chartData);
}, },
infoMessage() {
if (this.isServerError) {
return s__(
'MetricChart|There is too much data to calculate. Please change your selection.',
);
} else if (!this.hasChartData) {
return s__('MetricChart|There is no data available. Please change your selection.');
}
return null;
},
}, },
methods: { methods: {
isSelectedMetric(key) { isSelectedMetric(key) {
...@@ -68,8 +88,8 @@ export default { ...@@ -68,8 +88,8 @@ export default {
<h5 v-if="title">{{ title }}</h5> <h5 v-if="title">{{ title }}</h5>
<gl-loading-icon v-if="isLoading" size="md" class="my-4 py-4" /> <gl-loading-icon v-if="isLoading" size="md" class="my-4 py-4" />
<template v-else> <template v-else>
<div v-if="!hasChartData" ref="noData" class="bs-callout bs-callout-info"> <div v-if="infoMessage" data-testid="infoMessage" class="bs-callout bs-callout-info">
{{ __('There is no data available. Please change your selection.') }} {{ infoMessage }}
</div> </div>
<template v-else> <template v-else>
<gl-deprecated-dropdown <gl-deprecated-dropdown
......
...@@ -15,6 +15,8 @@ import { getScatterPlotData, getMedianLineData } from '../../../utils'; ...@@ -15,6 +15,8 @@ import { getScatterPlotData, getMedianLineData } from '../../../utils';
export const chartLoading = state => chartKey => state.charts[chartKey].isLoading; export const chartLoading = state => chartKey => state.charts[chartKey].isLoading;
export const chartErrorCode = state => chartKey => state.charts[chartKey].errorCode;
/** /**
* Creates a series object for the column chart with the given chartKey. * Creates a series object for the column chart with the given chartKey.
* *
......
...@@ -6,6 +6,7 @@ exports[`MetricChart component template when isLoading is false and chart data i ...@@ -6,6 +6,7 @@ exports[`MetricChart component template when isLoading is false and chart data i
<div <div
class="bs-callout bs-callout-info" class="bs-callout bs-callout-info"
data-testid="infoMessage"
> >
There is no data available. Please change your selection. There is no data available. Please change your selection.
......
...@@ -19,6 +19,8 @@ import { GlColumnChart } from '@gitlab/ui/dist/charts'; ...@@ -19,6 +19,8 @@ import { GlColumnChart } from '@gitlab/ui/dist/charts';
import * as commonUtils from '~/lib/utils/common_utils'; import * as commonUtils from '~/lib/utils/common_utils';
import * as urlUtils from '~/lib/utils/url_utility'; import * as urlUtils from '~/lib/utils/url_utility';
import UrlSyncMixin from 'ee/analytics/shared/mixins/url_sync_mixin'; import UrlSyncMixin from 'ee/analytics/shared/mixins/url_sync_mixin';
import MetricChart from 'ee/analytics/productivity_analytics/components/metric_chart.vue';
import httpStatusCodes from '~/lib/utils/http_status';
const localVue = createLocalVue(); const localVue = createLocalVue();
localVue.use(Vuex); localVue.use(Vuex);
...@@ -42,7 +44,7 @@ describe('ProductivityApp component', () => { ...@@ -42,7 +44,7 @@ describe('ProductivityApp component', () => {
const mainChartData = { 1: 2, 2: 3 }; const mainChartData = { 1: 2, 2: 3 };
const createComponent = ({ props = {}, scatterplotEnabled = true } = {}) => { const createComponent = ({ props = {}, options = {}, scatterplotEnabled = true } = {}) => {
wrapper = shallowMount(ProductivityApp, { wrapper = shallowMount(ProductivityApp, {
localVue, localVue,
store, store,
...@@ -57,6 +59,7 @@ describe('ProductivityApp component', () => { ...@@ -57,6 +59,7 @@ describe('ProductivityApp component', () => {
provide: { provide: {
glFeatures: { productivityAnalyticsScatterplotEnabled: scatterplotEnabled }, glFeatures: { productivityAnalyticsScatterplotEnabled: scatterplotEnabled },
}, },
...options,
}); });
wrapper.vm.$store.dispatch('setEndpoint', TEST_HOST); wrapper.vm.$store.dispatch('setEndpoint', TEST_HOST);
...@@ -499,6 +502,42 @@ describe('ProductivityApp component', () => { ...@@ -499,6 +502,42 @@ describe('ProductivityApp component', () => {
expect(findMrTableSection().exists()).toBe(false); expect(findMrTableSection().exists()).toBe(false);
}); });
}); });
describe('with a server error', () => {
beforeEach(() => {
createComponent({
options: {
stubs: {
'metric-chart': MetricChart,
},
},
});
wrapper.vm.$store.dispatch('charts/receiveChartDataError', {
chartKey: chartKeys.main,
error: { response: { status: httpStatusCodes.INTERNAL_SERVER_ERROR } },
});
});
it('sets isLoading=false on the metric chart', () => {
expect(findMainMetricChart().props('isLoading')).toBe(false);
});
it('passes a 500 status code to the metric chart', () => {
expect(findMainMetricChart().props('errorCode')).toBe(
httpStatusCodes.INTERNAL_SERVER_ERROR,
);
});
it('does not render any other charts', () => {
expect(findSecondaryChartsSection().exists()).toBe(false);
});
it('renders the proper info message', () => {
expect(findMainMetricChart().text()).toContain(
'There is too much data to calculate. Please change your selection.',
);
});
});
}); });
}); });
}); });
......
...@@ -2,6 +2,7 @@ import { shallowMount } from '@vue/test-utils'; ...@@ -2,6 +2,7 @@ import { shallowMount } from '@vue/test-utils';
import MetricChart from 'ee/analytics/productivity_analytics/components/metric_chart.vue'; import MetricChart from 'ee/analytics/productivity_analytics/components/metric_chart.vue';
import { GlLoadingIcon, GlDeprecatedDropdown, GlDeprecatedDropdownItem } from '@gitlab/ui'; import { GlLoadingIcon, GlDeprecatedDropdown, GlDeprecatedDropdownItem } from '@gitlab/ui';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import httpStatusCodes from '~/lib/utils/http_status';
describe('MetricChart component', () => { describe('MetricChart component', () => {
let wrapper; let wrapper;
...@@ -37,7 +38,7 @@ describe('MetricChart component', () => { ...@@ -37,7 +38,7 @@ describe('MetricChart component', () => {
}); });
const findLoadingIndicator = () => wrapper.find(GlLoadingIcon); const findLoadingIndicator = () => wrapper.find(GlLoadingIcon);
const findNoDataSection = () => wrapper.find({ ref: 'noData' }); const findInfoMessage = () => wrapper.find('[data-testid="infoMessage"]');
const findMetricDropdown = () => wrapper.find(GlDeprecatedDropdown); const findMetricDropdown = () => wrapper.find(GlDeprecatedDropdown);
const findMetricDropdownItems = () => findMetricDropdown().findAll(GlDeprecatedDropdownItem); const findMetricDropdownItems = () => findMetricDropdown().findAll(GlDeprecatedDropdownItem);
const findChartSlot = () => wrapper.find({ ref: 'chart' }); const findChartSlot = () => wrapper.find({ ref: 'chart' });
...@@ -98,13 +99,25 @@ describe('MetricChart component', () => { ...@@ -98,13 +99,25 @@ describe('MetricChart component', () => {
expect(findChartSlot().exists()).toBe(false); expect(findChartSlot().exists()).toBe(false);
}); });
describe('and there is no error', () => {
it('shows a "no data" info text', () => { it('shows a "no data" info text', () => {
expect(findNoDataSection().text()).toContain( expect(findInfoMessage().text()).toContain(
'There is no data available. Please change your selection.', 'There is no data available. Please change your selection.',
); );
}); });
}); });
describe('and there is a 500 error', () => {
it('shows a "too much data" info text', () => {
factory({ isLoading, chartData: [], errorCode: httpStatusCodes.INTERNAL_SERVER_ERROR });
expect(findInfoMessage().text()).toContain(
'There is too much data to calculate. Please change your selection.',
);
});
});
});
describe('and chartData is not empty', () => { describe('and chartData is not empty', () => {
const chartData = [[0, 1]]; const chartData = [[0, 1]];
......
...@@ -15095,6 +15095,12 @@ msgstr "" ...@@ -15095,6 +15095,12 @@ msgstr ""
msgid "MetricChart|Selected" msgid "MetricChart|Selected"
msgstr "" msgstr ""
msgid "MetricChart|There is no data available. Please change your selection."
msgstr ""
msgid "MetricChart|There is too much data to calculate. Please change your selection."
msgstr ""
msgid "Metrics" msgid "Metrics"
msgstr "" msgstr ""
......
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