Commit 0aa28970 authored by Kushal Pandya's avatar Kushal Pandya

Merge branch...

Merge branch '225660-vsa-median-data-is-being-requested-unnecessarily-for-hidden-stages' into 'master'

Median data is being requested unnecessarily for hidden stages

See merge request gitlab-org/gitlab!37336
parents 1645f0fd 4a6fe9f7
...@@ -71,10 +71,9 @@ const fetchStageMedian = (currentGroupPath, stageId, params) => ...@@ -71,10 +71,9 @@ const fetchStageMedian = (currentGroupPath, stageId, params) =>
...data, ...data,
})); }));
export const fetchStageMedianValues = ({ state, dispatch, getters }) => { export const fetchStageMedianValues = ({ dispatch, getters }) => {
const { currentGroupPath, cycleAnalyticsRequestParams } = getters; const { currentGroupPath, cycleAnalyticsRequestParams, activeStages } = getters;
const { stages } = state; const stageIds = activeStages.map(s => s.slug);
const stageIds = stages.map(s => s.slug);
dispatch('requestStageMedianValues'); dispatch('requestStageMedianValues');
return Promise.all( return Promise.all(
......
...@@ -20,27 +20,23 @@ export const receiveDurationDataError = ({ commit }) => { ...@@ -20,27 +20,23 @@ export const receiveDurationDataError = ({ commit }) => {
createFlash(__('There was an error while fetching value stream analytics duration data.')); createFlash(__('There was an error while fetching value stream analytics duration data.'));
}; };
export const fetchDurationData = ({ dispatch, rootGetters, rootState }) => { export const fetchDurationData = ({ dispatch, rootGetters }) => {
dispatch('requestDurationData'); dispatch('requestDurationData');
const { const { cycleAnalyticsRequestParams, activeStages, currentGroupPath } = rootGetters;
stages,
selectedGroup: { fullPath },
} = rootState;
const { cycleAnalyticsRequestParams } = rootGetters;
return Promise.all( return Promise.all(
stages.map(stage => { activeStages.map(stage => {
const { slug } = stage; const { slug } = stage;
return Api.cycleAnalyticsDurationChart(fullPath, slug, cycleAnalyticsRequestParams).then( return Api.cycleAnalyticsDurationChart(
({ data }) => ({ currentGroupPath,
slug, slug,
selected: true, cycleAnalyticsRequestParams,
data, ).then(({ data }) => ({
}), slug,
); selected: true,
data,
}));
}), }),
) )
.then(data => dispatch('receiveDurationDataSuccess', data)) .then(data => dispatch('receiveDurationDataSuccess', data))
...@@ -56,23 +52,18 @@ export const receiveDurationMedianDataError = ({ commit }) => { ...@@ -56,23 +52,18 @@ export const receiveDurationMedianDataError = ({ commit }) => {
}; };
export const fetchDurationMedianData = ({ dispatch, rootState, rootGetters }) => { export const fetchDurationMedianData = ({ dispatch, rootState, rootGetters }) => {
const { const { startDate, endDate } = rootState;
stages, const { cycleAnalyticsRequestParams, activeStages, currentGroupPath } = rootGetters;
selectedGroup: { fullPath },
startDate,
endDate,
} = rootState;
const { cycleAnalyticsRequestParams } = rootGetters;
const offsetValue = getDayDifference(new Date(startDate), new Date(endDate)); const offsetValue = getDayDifference(new Date(startDate), new Date(endDate));
const offsetCreatedAfter = getDateInPast(new Date(startDate), offsetValue); const offsetCreatedAfter = getDateInPast(new Date(startDate), offsetValue);
const offsetCreatedBefore = getDateInPast(new Date(endDate), offsetValue); const offsetCreatedBefore = getDateInPast(new Date(endDate), offsetValue);
return Promise.all( return Promise.all(
stages.map(stage => { activeStages.map(stage => {
const { slug } = stage; const { slug } = stage;
return Api.cycleAnalyticsDurationChart(fullPath, slug, { return Api.cycleAnalyticsDurationChart(currentGroupPath, slug, {
...cycleAnalyticsRequestParams, ...cycleAnalyticsRequestParams,
created_after: dateFormat(offsetCreatedAfter, dateFormats.isoDate), created_after: dateFormat(offsetCreatedAfter, dateFormats.isoDate),
created_before: dateFormat(offsetCreatedBefore, dateFormats.isoDate), created_before: dateFormat(offsetCreatedBefore, dateFormats.isoDate),
......
---
title: VSA fetch duration data for active stages only
merge_request: 37336
author:
type: fixed
...@@ -19,7 +19,12 @@ import { ...@@ -19,7 +19,12 @@ import {
const stageData = { events: [] }; const stageData = { events: [] };
const error = new Error(`Request failed with status code ${httpStatusCodes.NOT_FOUND}`); const error = new Error(`Request failed with status code ${httpStatusCodes.NOT_FOUND}`);
const flashErrorMessage = 'There was an error while fetching value stream analytics data.'; const flashErrorMessage = 'There was an error while fetching value stream analytics data.';
const [selectedStage] = stages;
stages[0].hidden = true;
const activeStages = stages.filter(({ hidden }) => !hidden);
const hiddenStage = stages[0];
const [selectedStage] = activeStages;
const selectedStageSlug = selectedStage.slug; const selectedStageSlug = selectedStage.slug;
const [selectedValueStream] = valueStreams; const [selectedValueStream] = valueStreams;
...@@ -46,6 +51,7 @@ describe('Cycle analytics actions', () => { ...@@ -46,6 +51,7 @@ describe('Cycle analytics actions', () => {
hasDurationChart: true, hasDurationChart: true,
hasDurationChartMedian: true, hasDurationChartMedian: true,
}, },
activeStages,
}; };
mock = new MockAdapter(axios); mock = new MockAdapter(axios);
}); });
...@@ -266,7 +272,10 @@ describe('Cycle analytics actions', () => { ...@@ -266,7 +272,10 @@ describe('Cycle analytics actions', () => {
.mockImplementation(actions.receiveStageMedianValuesError({ commit: () => {} })), .mockImplementation(actions.receiveStageMedianValuesError({ commit: () => {} })),
commit: () => {}, commit: () => {},
state: { ...state }, state: { ...state },
getters, getters: {
...getters,
activeStages,
},
}), }),
}); });
...@@ -378,9 +387,7 @@ describe('Cycle analytics actions', () => { ...@@ -378,9 +387,7 @@ describe('Cycle analytics actions', () => {
return testAction( return testAction(
actions.setDefaultSelectedStage, actions.setDefaultSelectedStage,
null, null,
{ state,
activeStages: stages,
},
[], [],
[ [
{ type: 'setSelectedStage', payload: selectedStage }, { type: 'setSelectedStage', payload: selectedStage },
...@@ -399,13 +406,10 @@ describe('Cycle analytics actions', () => { ...@@ -399,13 +406,10 @@ describe('Cycle analytics actions', () => {
}); });
it('will select the first active stage', () => { it('will select the first active stage', () => {
stages[0].hidden = true;
return testAction( return testAction(
actions.setDefaultSelectedStage, actions.setDefaultSelectedStage,
null, null,
{ state,
activeStages: getters.activeStages({ stages }),
},
[], [],
[ [
{ type: 'setSelectedStage', payload: stages[1] }, { type: 'setSelectedStage', payload: stages[1] },
...@@ -648,26 +652,44 @@ describe('Cycle analytics actions', () => { ...@@ -648,26 +652,44 @@ describe('Cycle analytics actions', () => {
describe('fetchStageMedianValues', () => { describe('fetchStageMedianValues', () => {
let mockDispatch = jest.fn(); let mockDispatch = jest.fn();
const fetchMedianResponse = activeStages.map(({ slug: id }) => ({ events: [], id }));
beforeEach(() => { beforeEach(() => {
state = { ...state, stages: [{ slug: selectedStageSlug }], selectedGroup }; state = { ...state, stages, selectedGroup };
mock = new MockAdapter(axios); mock = new MockAdapter(axios);
mock.onGet(endpoints.stageMedian).reply(httpStatusCodes.OK, { events: [] }); mock.onGet(endpoints.stageMedian).reply(httpStatusCodes.OK, { events: [] });
mockDispatch = jest.fn(); mockDispatch = jest.fn();
}); });
it('dispatches receiveStageMedianValuesSuccess with received data on success', () => { it('dispatches receiveStageMedianValuesSuccess with received data on success', () => {
return testAction(
actions.fetchStageMedianValues,
null,
state,
[],
[
{ type: 'requestStageMedianValues' },
{ type: 'receiveStageMedianValuesSuccess', payload: fetchMedianResponse },
],
);
});
it('does not request hidden stages', () => {
return actions return actions
.fetchStageMedianValues({ .fetchStageMedianValues({
state, state,
getters, getters: {
...getters,
activeStages,
},
commit: () => {}, commit: () => {},
dispatch: mockDispatch, dispatch: mockDispatch,
}) })
.then(() => { .then(() => {
expect(mockDispatch).toHaveBeenCalledWith('requestStageMedianValues'); expect(mockDispatch).not.toHaveBeenCalledWith('receiveStageMedianValuesSuccess', {
expect(mockDispatch).toHaveBeenCalledWith('receiveStageMedianValuesSuccess', [ events: [],
{ events: [], id: selectedStageSlug }, id: hiddenStage.id,
]); });
}); });
}); });
...@@ -677,17 +699,16 @@ describe('Cycle analytics actions', () => { ...@@ -677,17 +699,16 @@ describe('Cycle analytics actions', () => {
}); });
it('will dispatch receiveStageMedianValuesError', () => { it('will dispatch receiveStageMedianValuesError', () => {
return actions return testAction(
.fetchStageMedianValues({ actions.fetchStageMedianValues,
state, null,
getters, state,
commit: () => {}, [],
dispatch: mockDispatch, [
}) { type: 'requestStageMedianValues' },
.then(() => { { type: 'receiveStageMedianValuesError', payload: error },
expect(mockDispatch).toHaveBeenCalledWith('requestStageMedianValues'); ],
expect(mockDispatch).toHaveBeenCalledWith('receiveStageMedianValuesError', error); );
});
}); });
}); });
}); });
......
...@@ -20,16 +20,23 @@ import { shouldFlashAMessage } from '../../../helpers'; ...@@ -20,16 +20,23 @@ import { shouldFlashAMessage } from '../../../helpers';
const selectedGroup = { fullPath: group.path }; const selectedGroup = { fullPath: group.path };
const [stage1, stage2] = stages; const [stage1, stage2] = stages;
const hiddenStage = { ...stage1, hidden: true, id: 3, slug: 3 };
const activeStages = [stage1, stage2];
const rootState = { const rootState = {
startDate, startDate,
endDate, endDate,
stages: [stage1, stage2], stages: [...activeStages, hiddenStage],
selectedGroup, selectedGroup,
featureFlags: { featureFlags: {
hasDurationChart: true, hasDurationChart: true,
hasDurationChartMedian: true, hasDurationChartMedian: true,
}, },
getters,
rootGetters: {
...rootGetters,
activeStages,
},
}; };
describe('DurationChart actions', () => { describe('DurationChart actions', () => {
...@@ -49,59 +56,54 @@ describe('DurationChart actions', () => { ...@@ -49,59 +56,54 @@ describe('DurationChart actions', () => {
mock.onGet(endpoints.durationData).reply(200, [...rawDurationData]); mock.onGet(endpoints.durationData).reply(200, [...rawDurationData]);
}); });
it("dispatches the 'receiveDurationDataSuccess' action on success", () => { it("dispatches the 'requestDurationData' and 'receiveDurationDataSuccess' actions on success", () => {
const dispatch = jest.fn(); return testAction(
actions.fetchDurationData,
return actions null,
.fetchDurationData({ { activeStages },
dispatch, [],
rootState, [
rootGetters, { type: 'requestDurationData' },
}) {
.then(() => { type: 'receiveDurationDataSuccess',
expect(dispatch).toHaveBeenCalledWith( payload: transformedDurationData,
'receiveDurationDataSuccess', },
transformedDurationData, ],
); );
});
}); });
it("dispatches the 'requestDurationData' action", () => { it('does not request hidden stages', () => {
const dispatch = jest.fn(); const dispatch = jest.fn();
return actions return actions
.fetchDurationData({ .fetchDurationData({
dispatch, dispatch,
rootState, ...rootState,
rootGetters,
}) })
.then(() => { .then(() => {
expect(dispatch).toHaveBeenNthCalledWith(1, 'requestDurationData'); const requestedUrls = mock.history.get.map(({ url }) => url);
expect(requestedUrls).not.toContain(
`/groups/foo/-/analytics/value_stream_analytics/stages/${hiddenStage.id}/duration_chart`,
);
}); });
}); });
it("dispatches the 'receiveDurationDataError' action when there is an error", () => { describe('receiveDurationDataError', () => {
const brokenRootState = { beforeEach(() => {
...rootState, mock.onGet(endpoints.durationData).reply(404);
stages: [ });
{
id: 'oops',
},
],
};
const dispatch = jest.fn();
return actions it("dispatches the 'receiveDurationDataError' action when there is an error", () => {
.fetchDurationData({ const dispatch = jest.fn();
dispatch,
getters, return actions
rootState: brokenRootState, .fetchDurationData({
rootGetters, dispatch,
}) ...rootState,
.then(() => { })
expect(dispatch).toHaveBeenCalledWith('receiveDurationDataError'); .then(() => {
}); expect(dispatch).toHaveBeenCalledWith('receiveDurationDataError');
});
});
}); });
}); });
...@@ -292,42 +294,41 @@ describe('DurationChart actions', () => { ...@@ -292,42 +294,41 @@ describe('DurationChart actions', () => {
}); });
it('dispatches the receiveDurationMedianDataSuccess action on success', () => { it('dispatches the receiveDurationMedianDataSuccess action on success', () => {
const dispatch = jest.fn(); return testAction(
actions.fetchDurationMedianData,
return actions null,
.fetchDurationMedianData({ { ...rootState, activeStages },
dispatch, [],
rootState, [
rootGetters,
})
.then(() => {
expect(dispatch).toHaveBeenCalledWith(
'receiveDurationMedianDataSuccess',
transformedDurationMedianData,
);
});
});
it('dispatches the receiveDurationMedianDataError action when there is an error', () => {
const brokenRootState = {
...rootState,
stages: [
{ {
id: 'oops', type: 'receiveDurationMedianDataSuccess',
payload: transformedDurationMedianData,
}, },
], ],
}; );
const dispatch = jest.fn(); });
return actions describe('receiveDurationMedianDataError', () => {
.fetchDurationMedianData({ beforeEach(() => {
dispatch, mock.onGet(endpoints.durationData).reply(404);
rootState: brokenRootState, });
rootGetters,
}) it('dispatches the receiveDurationMedianDataError action when there is an error', () => {
.then(() => { const dispatch = jest.fn();
expect(dispatch).toHaveBeenCalledWith('receiveDurationMedianDataError'); return actions
}); .fetchDurationMedianData({
dispatch,
rootState,
rootGetters: { activeStages },
})
.then(() => {
const requestedUrls = mock.history.get.map(({ url }) => url);
expect(requestedUrls).not.toContain(
`/groups/foo/-/analytics/value_stream_analytics/stages/${hiddenStage.id}/duration_chart`,
);
expect(dispatch).toHaveBeenCalledWith('receiveDurationMedianDataError');
});
});
}); });
}); });
......
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