Commit 1bf6dc56 authored by Fatih Acet's avatar Fatih Acet

Merge branch '36675-add-median-line-to-cycle-analytics-duration-chart' into 'master'

Resolve "Add median line to cycle analytics duration chart"

See merge request gitlab-org/gitlab!21637
parents 473113a3 684b03d0
......@@ -74,6 +74,7 @@ export default {
'currentGroupPath',
'durationChartPlottableData',
'tasksByTypeChartData',
'durationChartMedianData',
]),
shouldRenderEmptyState() {
return !this.selectedGroup;
......@@ -121,6 +122,7 @@ export default {
this.initDateRange();
this.setFeatureFlags({
hasDurationChart: this.glFeatures.cycleAnalyticsScatterplotEnabled,
hasDurationChartMedian: this.glFeatures.cycleAnalyticsScatterplotMedianEnabled,
hasTasksByTypeChart: this.glFeatures.tasksByTypeChart,
});
},
......@@ -303,6 +305,7 @@ export default {
:y-axis-title="s__('CycleAnalytics|Total days to completion')"
:tooltip-date-format="$options.durationChartTooltipDateFormat"
:scatter-data="durationChartPlottableData"
:median-line-data="durationChartMedianData"
/>
<div v-else ref="duration-chart-no-data" class="bs-callout bs-callout-info">
{{ __('There is no data available. Please change your selection.') }}
......
import dateFormat from 'dateformat';
import Api from 'ee/api';
import { getDayDifference, getDateInPast } from '~/lib/utils/datetime_utility';
import createFlash, { hideFlash } from '~/flash';
import { __ } from '~/locale';
import httpStatus from '~/lib/utils/http_status';
import * as types from './mutation_types';
import { dateFormats } from '../../shared/constants';
import { nestQueryStringKeys } from '../utils';
const removeError = () => {
......@@ -375,9 +378,13 @@ export const removeStage = ({ dispatch, state }, stageId) => {
export const requestDurationData = ({ commit }) => commit(types.REQUEST_DURATION_DATA);
export const receiveDurationDataSuccess = ({ commit }, data) =>
export const receiveDurationDataSuccess = ({ commit, state, dispatch }, data) => {
commit(types.RECEIVE_DURATION_DATA_SUCCESS, data);
const { featureFlags: { hasDurationChartMedian = false } = {} } = state;
if (hasDurationChartMedian) dispatch('fetchDurationMedianData');
};
export const receiveDurationDataError = ({ commit }) => {
commit(types.RECEIVE_DURATION_DATA_ERROR);
createFlash(__('There was an error while fetching cycle analytics duration data.'));
......@@ -417,18 +424,73 @@ export const fetchDurationData = ({ state, dispatch, getters }) => {
.catch(() => dispatch('receiveDurationDataError'));
};
export const requestDurationMedianData = ({ commit }) => commit(types.REQUEST_DURATION_MEDIAN_DATA);
export const receiveDurationMedianDataSuccess = ({ commit }, data) =>
commit(types.RECEIVE_DURATION_MEDIAN_DATA_SUCCESS, data);
export const receiveDurationMedianDataError = ({ commit }) => {
commit(types.RECEIVE_DURATION_MEDIAN_DATA_ERROR);
createFlash(__('There was an error while fetching cycle analytics duration median data.'));
};
export const fetchDurationMedianData = ({ state, dispatch }) => {
dispatch('requestDurationMedianData');
const {
stages,
selectedGroup: { fullPath },
startDate,
endDate,
selectedProjectIds,
} = state;
const offsetValue = getDayDifference(new Date(startDate), new Date(endDate));
const offsetCreatedAfter = getDateInPast(new Date(startDate), offsetValue);
const offsetCreatedBefore = getDateInPast(new Date(endDate), offsetValue);
return Promise.all(
stages.map(stage => {
const { slug } = stage;
return Api.cycleAnalyticsDurationChart(slug, {
group_id: fullPath,
created_after: dateFormat(offsetCreatedAfter, dateFormats.isoDate),
created_before: dateFormat(offsetCreatedBefore, dateFormats.isoDate),
project_ids: selectedProjectIds,
}).then(({ data }) => ({
slug,
selected: true,
data,
}));
}),
)
.then(data => {
dispatch('receiveDurationMedianDataSuccess', data);
})
.catch(() => dispatch('receiveDurationMedianDataError'));
};
export const updateSelectedDurationChartStages = ({ state, commit }, stages) => {
const updatedDurationStageData = state.durationData.map(stage => {
const selected = stages.reduce((result, object) => {
if (object.slug === stage.slug) return true;
return result;
}, false);
return {
...stage,
selected,
};
const setSelectedPropertyOnStages = data =>
data.map(stage => {
const selected = stages.reduce((result, object) => {
if (object.slug === stage.slug) return true;
return result;
}, false);
return {
...stage,
selected,
};
});
const { durationData, durationMedianData } = state;
const updatedDurationStageData = setSelectedPropertyOnStages(durationData);
const updatedDurationStageMedianData = setSelectedPropertyOnStages(durationMedianData);
commit(types.UPDATE_SELECTED_DURATION_CHART_STAGES, {
updatedDurationStageData,
updatedDurationStageMedianData,
});
commit(types.UPDATE_SELECTED_DURATION_CHART_STAGES, updatedDurationStageData);
};
import dateFormat from 'dateformat';
import httpStatus from '~/lib/utils/http_status';
import { dateFormats } from '../../shared/constants';
import { getDurationChartData, getTasksByTypeData } from '../utils';
import { getDurationChartData, getDurationChartMedianData, getTasksByTypeData } from '../utils';
export const hasNoAccessError = state => state.errorCode === httpStatus.FORBIDDEN;
......@@ -26,6 +26,18 @@ export const durationChartPlottableData = state => {
return plottableData.length ? plottableData : null;
};
export const durationChartMedianData = state => {
const { durationMedianData, startDate, endDate } = state;
const selectedStagesDurationMedianData = durationMedianData.filter(stage => stage.selected);
const plottableData = getDurationChartMedianData(
selectedStagesDurationMedianData,
startDate,
endDate,
);
return plottableData.length ? plottableData : [];
};
export const tasksByTypeChartData = ({ tasksByType, startDate, endDate }) => {
if (tasksByType && tasksByType.data.length) {
return getTasksByTypeData({
......
......@@ -54,3 +54,7 @@ export const RECEIVE_REMOVE_STAGE_RESPONSE = 'RECEIVE_REMOVE_STAGE_RESPONSE';
export const REQUEST_DURATION_DATA = 'REQUEST_DURATION_DATA';
export const RECEIVE_DURATION_DATA_SUCCESS = 'RECEIVE_DURATION_DATA_SUCCESS';
export const RECEIVE_DURATION_DATA_ERROR = 'RECEIVE_DURATION_DATA_ERROR';
export const REQUEST_DURATION_MEDIAN_DATA = 'REQUEST_DURATION_MEDIAN_DATA';
export const RECEIVE_DURATION_MEDIAN_DATA_SUCCESS = 'RECEIVE_DURATION_MEDIAN_DATA_SUCCESS';
export const RECEIVE_DURATION_MEDIAN_DATA_ERROR = 'RECEIVE_DURATION_MEDIAN_DATA_ERROR';
......@@ -20,8 +20,12 @@ export default {
state.startDate = startDate;
state.endDate = endDate;
},
[types.UPDATE_SELECTED_DURATION_CHART_STAGES](state, updatedDurationStageData) {
[types.UPDATE_SELECTED_DURATION_CHART_STAGES](
state,
{ updatedDurationStageData, updatedDurationStageMedianData },
) {
state.durationData = updatedDurationStageData;
state.durationMedianData = updatedDurationStageMedianData;
},
[types.REQUEST_CYCLE_ANALYTICS_DATA](state) {
state.isLoading = true;
......@@ -175,4 +179,15 @@ export default {
state.durationData = [];
state.isLoadingDurationChart = false;
},
[types.REQUEST_DURATION_MEDIAN_DATA](state) {
state.isLoadingDurationChartMedianData = true;
},
[types.RECEIVE_DURATION_MEDIAN_DATA_SUCCESS](state, data) {
state.durationMedianData = data;
state.isLoadingDurationChartMedianData = false;
},
[types.RECEIVE_DURATION_MEDIAN_DATA_ERROR](state) {
state.durationMedianData = [];
state.isLoadingDurationChartMedianData = false;
},
};
......@@ -10,6 +10,7 @@ export default () => ({
isLoadingStage: false,
isLoadingTasksByTypeChart: false,
isLoadingDurationChart: false,
isLoadingDurationChartMedianData: false,
isEmptyStage: false,
errorCode: null,
......@@ -37,4 +38,5 @@ export default () => ({
},
durationData: [],
durationMedianData: [],
});
......@@ -7,6 +7,7 @@ class Analytics::CycleAnalyticsController < Analytics::ApplicationController
before_action do
push_frontend_feature_flag(:customizable_cycle_analytics)
push_frontend_feature_flag(:cycle_analytics_scatterplot_enabled)
push_frontend_feature_flag(:cycle_analytics_scatterplot_median_enabled)
push_frontend_feature_flag(:tasks_by_type_chart)
end
end
......@@ -14,7 +14,9 @@ import {
endDate,
customizableStagesAndEvents,
rawDurationData,
rawDurationMedianData,
transformedDurationData,
transformedDurationMedianData,
} from '../mock_data';
const stageData = { events: [] };
......@@ -44,12 +46,13 @@ describe('Cycle analytics actions', () => {
beforeEach(() => {
state = {
startDate: '2019-01-14',
endDate: '2019-02-15',
startDate,
endDate,
stages: [],
featureFlags: {
hasDurationChart: true,
hasTasksByTypeChart: true,
hasDurationChartMedian: true,
},
};
mock = new MockAdapter(axios);
......@@ -851,20 +854,50 @@ describe('Cycle analytics actions', () => {
});
describe('receiveDurationDataSuccess', () => {
const payload = { durationData: transformedDurationData, isLoadingDurationChart: false };
describe('with hasDurationChartMedian feature flag enabled', () => {
it('commits the transformed duration data and dispatches fetchDurationMedianData', () => {
testAction(
actions.receiveDurationDataSuccess,
transformedDurationData,
state,
[
{
type: types.RECEIVE_DURATION_DATA_SUCCESS,
payload: transformedDurationData,
},
],
[
{
type: 'fetchDurationMedianData',
},
],
);
});
});
testAction(
actions.receiveDurationDataSuccess,
payload,
state,
[
{
type: types.RECEIVE_DURATION_DATA_SUCCESS,
payload,
describe('with hasDurationChartMedian feature flag disabled', () => {
const disabledState = {
...state,
featureFlags: {
hasDurationChartMedian: false,
},
],
[],
);
};
it('commits the transformed duration data', () => {
testAction(
actions.receiveDurationDataSuccess,
transformedDurationData,
disabledState,
[
{
type: types.RECEIVE_DURATION_DATA_SUCCESS,
payload: transformedDurationData,
},
],
[],
);
});
});
});
describe('receiveDurationDataError', () => {
......@@ -900,6 +933,7 @@ describe('Cycle analytics actions', () => {
const stateWithDurationData = {
...state,
durationData: transformedDurationData,
durationMedianData: transformedDurationMedianData,
};
testAction(
......@@ -909,7 +943,10 @@ describe('Cycle analytics actions', () => {
[
{
type: types.UPDATE_SELECTED_DURATION_CHART_STAGES,
payload: transformedDurationData,
payload: {
updatedDurationStageData: transformedDurationData,
updatedDurationStageMedianData: transformedDurationMedianData,
},
},
],
[],
......@@ -920,6 +957,7 @@ describe('Cycle analytics actions', () => {
const stateWithDurationData = {
...state,
durationData: transformedDurationData,
durationMedianData: transformedDurationMedianData,
};
testAction(
......@@ -929,13 +967,22 @@ describe('Cycle analytics actions', () => {
[
{
type: types.UPDATE_SELECTED_DURATION_CHART_STAGES,
payload: [
transformedDurationData[0],
{
...transformedDurationData[1],
selected: false,
},
],
payload: {
updatedDurationStageData: [
transformedDurationData[0],
{
...transformedDurationData[1],
selected: false,
},
],
updatedDurationStageMedianData: [
transformedDurationMedianData[0],
{
...transformedDurationMedianData[1],
selected: false,
},
],
},
},
],
[],
......@@ -946,6 +993,7 @@ describe('Cycle analytics actions', () => {
const stateWithDurationData = {
...state,
durationData: transformedDurationData,
durationMedianData: transformedDurationMedianData,
};
testAction(
......@@ -955,19 +1003,152 @@ describe('Cycle analytics actions', () => {
[
{
type: types.UPDATE_SELECTED_DURATION_CHART_STAGES,
payload: [
{
...transformedDurationData[0],
selected: false,
},
{
...transformedDurationData[1],
selected: false,
},
],
payload: {
updatedDurationStageData: [
{
...transformedDurationData[0],
selected: false,
},
{
...transformedDurationData[1],
selected: false,
},
],
updatedDurationStageMedianData: [
{
...transformedDurationMedianData[0],
selected: false,
},
{
...transformedDurationMedianData[1],
selected: false,
},
],
},
},
],
[],
);
});
});
describe('fetchDurationMedianData', () => {
beforeEach(() => {
mock.onGet(endpoints.durationData).reply(200, [...rawDurationMedianData]);
});
it('dispatches requestDurationMedianData when called', done => {
const stateWithStages = {
...state,
stages: [stages[0], stages[1]],
selectedGroup,
};
const dispatch = jest.fn();
actions
.fetchDurationMedianData({
dispatch,
state: stateWithStages,
})
.then(() => {
expect(dispatch).toHaveBeenNthCalledWith(1, 'requestDurationMedianData');
done();
})
.catch(done.fail);
});
it('dispatches the receiveDurationMedianDataSuccess action on success', done => {
const stateWithStages = {
...state,
stages: [stages[0], stages[1]],
selectedGroup,
};
const dispatch = jest.fn();
actions
.fetchDurationMedianData({
dispatch,
state: stateWithStages,
})
.then(() => {
expect(dispatch).toHaveBeenCalledWith(
'receiveDurationMedianDataSuccess',
transformedDurationMedianData,
);
done();
})
.catch(done.fail);
});
it('dispatches the receiveDurationMedianDataError action when there is an error', done => {
const brokenState = {
...state,
stages: [
{
id: 'oops',
},
],
selectedGroup,
};
const dispatch = jest.fn();
actions
.fetchDurationMedianData({
dispatch,
state: brokenState,
})
.then(() => {
expect(dispatch).toHaveBeenCalledWith('receiveDurationMedianDataError');
done();
})
.catch(done.fail);
});
});
describe('receiveDurationMedianDataSuccess', () => {
it('commits the transformed duration median data', done => {
testAction(
actions.receiveDurationMedianDataSuccess,
transformedDurationMedianData,
state,
[
{
type: types.RECEIVE_DURATION_MEDIAN_DATA_SUCCESS,
payload: transformedDurationMedianData,
},
],
[],
done,
);
});
});
describe('receiveDurationMedianDataError', () => {
beforeEach(() => {
setFixtures('<div class="flash-container"></div>');
});
it("commits the 'RECEIVE_DURATION_MEDIAN_DATA_ERROR' mutation", () => {
testAction(
actions.receiveDurationMedianDataError,
{},
state,
[
{
type: types.RECEIVE_DURATION_MEDIAN_DATA_ERROR,
},
],
[],
);
});
it('will flash an error', () => {
actions.receiveDurationMedianDataError({
commit: () => {},
});
shouldFlashAMessage(
'There was an error while fetching cycle analytics duration median data.',
);
});
});
......
......@@ -3,7 +3,9 @@ import {
startDate,
endDate,
transformedDurationData,
transformedDurationMedianData,
durationChartPlottableData,
durationChartPlottableMedianData,
} from '../mock_data';
let state = null;
......@@ -95,4 +97,28 @@ describe('Cycle analytics getters', () => {
expect(getters.durationChartPlottableData(stateWithDurationData)).toBeNull();
});
});
describe('durationChartPlottableMedianData', () => {
it('returns plottable median data for selected stages', () => {
const stateWithDurationMedianData = {
startDate,
endDate,
durationMedianData: transformedDurationMedianData,
};
expect(getters.durationChartMedianData(stateWithDurationMedianData)).toEqual(
durationChartPlottableMedianData,
);
});
it('returns an empty array if there is no plottable median data for the selected stages', () => {
const stateWithDurationMedianData = {
startDate,
endDate,
durationMedianData: [],
};
expect(getters.durationChartMedianData(stateWithDurationMedianData)).toEqual([]);
});
});
});
......@@ -19,6 +19,7 @@ import {
tasksByTypeData,
transformedDurationData,
transformedTasksByTypeData,
transformedDurationMedianData,
} from '../mock_data';
let state = null;
......@@ -33,34 +34,35 @@ describe('Cycle analytics mutations', () => {
});
it.each`
mutation | stateKey | value
${types.HIDE_CUSTOM_STAGE_FORM} | ${'isCreatingCustomStage'} | ${false}
${types.SHOW_CUSTOM_STAGE_FORM} | ${'isCreatingCustomStage'} | ${true}
${types.EDIT_CUSTOM_STAGE} | ${'isEditingCustomStage'} | ${true}
${types.REQUEST_STAGE_DATA} | ${'isLoadingStage'} | ${true}
${types.RECEIVE_STAGE_DATA_ERROR} | ${'isEmptyStage'} | ${true}
${types.RECEIVE_STAGE_DATA_ERROR} | ${'isLoadingStage'} | ${false}
${types.REQUEST_CYCLE_ANALYTICS_DATA} | ${'isLoading'} | ${true}
${types.REQUEST_GROUP_LABELS} | ${'labels'} | ${[]}
${types.RECEIVE_GROUP_LABELS_ERROR} | ${'labels'} | ${[]}
${types.RECEIVE_SUMMARY_DATA_ERROR} | ${'summary'} | ${[]}
${types.REQUEST_SUMMARY_DATA} | ${'summary'} | ${[]}
${types.RECEIVE_GROUP_STAGES_AND_EVENTS_ERROR} | ${'stages'} | ${[]}
${types.REQUEST_GROUP_STAGES_AND_EVENTS} | ${'stages'} | ${[]}
${types.RECEIVE_GROUP_STAGES_AND_EVENTS_ERROR} | ${'customStageFormEvents'} | ${[]}
${types.REQUEST_GROUP_STAGES_AND_EVENTS} | ${'customStageFormEvents'} | ${[]}
${types.REQUEST_CREATE_CUSTOM_STAGE} | ${'isSavingCustomStage'} | ${true}
${types.RECEIVE_CREATE_CUSTOM_STAGE_RESPONSE} | ${'isSavingCustomStage'} | ${false}
${types.REQUEST_TASKS_BY_TYPE_DATA} | ${'isLoadingTasksByTypeChart'} | ${true}
${types.RECEIVE_TASKS_BY_TYPE_DATA_ERROR} | ${'isLoadingTasksByTypeChart'} | ${false}
${types.REQUEST_UPDATE_STAGE} | ${'isLoading'} | ${true}
${types.RECEIVE_UPDATE_STAGE_RESPONSE} | ${'isLoading'} | ${false}
${types.REQUEST_REMOVE_STAGE} | ${'isLoading'} | ${true}
${types.RECEIVE_REMOVE_STAGE_RESPONSE} | ${'isLoading'} | ${false}
${types.REQUEST_DURATION_DATA} | ${'isLoadingDurationChart'} | ${true}
${types.RECEIVE_DURATION_DATA_ERROR} | ${'isLoadingDurationChart'} | ${false}
${types.REQUEST_STAGE_MEDIANS} | ${'medians'} | ${{}}
${types.RECEIVE_STAGE_MEDIANS_ERROR} | ${'medians'} | ${{}}
mutation | stateKey | value
${types.HIDE_CUSTOM_STAGE_FORM} | ${'isCreatingCustomStage'} | ${false}
${types.SHOW_CUSTOM_STAGE_FORM} | ${'isCreatingCustomStage'} | ${true}
${types.EDIT_CUSTOM_STAGE} | ${'isEditingCustomStage'} | ${true}
${types.REQUEST_STAGE_DATA} | ${'isLoadingStage'} | ${true}
${types.RECEIVE_STAGE_DATA_ERROR} | ${'isEmptyStage'} | ${true}
${types.RECEIVE_STAGE_DATA_ERROR} | ${'isLoadingStage'} | ${false}
${types.REQUEST_CYCLE_ANALYTICS_DATA} | ${'isLoading'} | ${true}
${types.REQUEST_GROUP_LABELS} | ${'labels'} | ${[]}
${types.RECEIVE_GROUP_LABELS_ERROR} | ${'labels'} | ${[]}
${types.RECEIVE_SUMMARY_DATA_ERROR} | ${'summary'} | ${[]}
${types.REQUEST_SUMMARY_DATA} | ${'summary'} | ${[]}
${types.RECEIVE_GROUP_STAGES_AND_EVENTS_ERROR} | ${'stages'} | ${[]}
${types.REQUEST_GROUP_STAGES_AND_EVENTS} | ${'stages'} | ${[]}
${types.RECEIVE_GROUP_STAGES_AND_EVENTS_ERROR} | ${'customStageFormEvents'} | ${[]}
${types.REQUEST_GROUP_STAGES_AND_EVENTS} | ${'customStageFormEvents'} | ${[]}
${types.REQUEST_CREATE_CUSTOM_STAGE} | ${'isSavingCustomStage'} | ${true}
${types.RECEIVE_CREATE_CUSTOM_STAGE_RESPONSE} | ${'isSavingCustomStage'} | ${false}
${types.REQUEST_TASKS_BY_TYPE_DATA} | ${'isLoadingTasksByTypeChart'} | ${true}
${types.RECEIVE_TASKS_BY_TYPE_DATA_ERROR} | ${'isLoadingTasksByTypeChart'} | ${false}
${types.REQUEST_UPDATE_STAGE} | ${'isLoading'} | ${true}
${types.RECEIVE_UPDATE_STAGE_RESPONSE} | ${'isLoading'} | ${false}
${types.REQUEST_REMOVE_STAGE} | ${'isLoading'} | ${true}
${types.RECEIVE_REMOVE_STAGE_RESPONSE} | ${'isLoading'} | ${false}
${types.REQUEST_DURATION_DATA} | ${'isLoadingDurationChart'} | ${true}
${types.RECEIVE_DURATION_DATA_ERROR} | ${'isLoadingDurationChart'} | ${false}
${types.REQUEST_STAGE_MEDIANS} | ${'medians'} | ${{}}
${types.RECEIVE_STAGE_MEDIANS_ERROR} | ${'medians'} | ${{}}
${types.REQUEST_DURATION_MEDIAN_DATA} | ${'isLoadingDurationChartMedianData'} | ${true}
`('$mutation will set $stateKey=$value', ({ mutation, stateKey, value }) => {
mutations[mutation](state);
......@@ -68,13 +70,13 @@ describe('Cycle analytics mutations', () => {
});
it.each`
mutation | payload | expectedState
${types.SET_FEATURE_FLAGS} | ${{ hasDurationChart: true }} | ${{ featureFlags: { hasDurationChart: true } }}
${types.SET_SELECTED_GROUP} | ${{ fullPath: 'cool-beans' }} | ${{ selectedGroup: { fullPath: 'cool-beans' }, selectedProjectIds: [] }}
${types.SET_SELECTED_PROJECTS} | ${[606, 707, 808, 909]} | ${{ selectedProjectIds: [606, 707, 808, 909] }}
${types.SET_DATE_RANGE} | ${{ startDate, endDate }} | ${{ startDate, endDate }}
${types.SET_SELECTED_STAGE} | ${{ id: 'first-stage' }} | ${{ selectedStage: { id: 'first-stage' } }}
${types.UPDATE_SELECTED_DURATION_CHART_STAGES} | ${transformedDurationData} | ${{ durationData: transformedDurationData }}
mutation | payload | expectedState
${types.SET_FEATURE_FLAGS} | ${{ hasDurationChart: true }} | ${{ featureFlags: { hasDurationChart: true } }}
${types.SET_SELECTED_GROUP} | ${{ fullPath: 'cool-beans' }} | ${{ selectedGroup: { fullPath: 'cool-beans' }, selectedProjectIds: [] }}
${types.SET_SELECTED_PROJECTS} | ${[606, 707, 808, 909]} | ${{ selectedProjectIds: [606, 707, 808, 909] }}
${types.SET_DATE_RANGE} | ${{ startDate, endDate }} | ${{ startDate, endDate }}
${types.SET_SELECTED_STAGE} | ${{ id: 'first-stage' }} | ${{ selectedStage: { id: 'first-stage' } }}
${types.UPDATE_SELECTED_DURATION_CHART_STAGES} | ${{ updatedDurationStageData: transformedDurationData, updatedDurationStageMedianData: transformedDurationMedianData }} | ${{ durationData: transformedDurationData, durationMedianData: transformedDurationMedianData }}
`(
'$mutation with payload $payload will update state with $expectedState',
({ mutation, payload, expectedState }) => {
......@@ -218,6 +220,37 @@ describe('Cycle analytics mutations', () => {
});
});
describe(`${types.RECEIVE_DURATION_MEDIAN_DATA_SUCCESS}`, () => {
it('sets the data correctly and falsifies isLoadingDurationChartMedianData', () => {
const stateWithData = {
isLoadingDurationChartMedianData: true,
durationMedianData: [['something', 'random']],
};
mutations[types.RECEIVE_DURATION_MEDIAN_DATA_SUCCESS](
stateWithData,
transformedDurationMedianData,
);
expect(stateWithData.isLoadingDurationChartMedianData).toBe(false);
expect(stateWithData.durationMedianData).toBe(transformedDurationMedianData);
});
});
describe(`${types.RECEIVE_DURATION_MEDIAN_DATA_ERROR}`, () => {
it('falsifies isLoadingDurationChartMedianData and sets durationMedianData to an empty array', () => {
const stateWithData = {
isLoadingDurationChartMedianData: true,
durationMedianData: [['something', 'random']],
};
mutations[types.RECEIVE_DURATION_MEDIAN_DATA_ERROR](stateWithData);
expect(stateWithData.isLoadingDurationChartMedianData).toBe(false);
expect(stateWithData.durationMedianData).toStrictEqual([]);
});
});
describe(`${types.RECEIVE_STAGE_MEDIANS_SUCCESS}`, () => {
it('sets each id as a key in the median object with the corresponding value', () => {
const stateWithData = {
......
......@@ -18657,6 +18657,9 @@ msgstr ""
msgid "There was an error while fetching cycle analytics duration data."
msgstr ""
msgid "There was an error while fetching cycle analytics duration median data."
msgstr ""
msgid "There was an error while fetching cycle analytics summary data."
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