Commit 747dbd8b authored by Ezekiel Kigbo's avatar Ezekiel Kigbo

Fetch chart data when the date range changes

Ensure that the chart data gets refreshed when a
the date range specified gets updated. Also adds
additional specs to ensure the correct error messages
are displayed if any of the requests fail after we
select a group'
parent 8776744c
<script> <script>
import { GlEmptyState, GlDaterangePicker, GlLoadingIcon } from '@gitlab/ui'; import { GlEmptyState, GlDaterangePicker, GlLoadingIcon } from '@gitlab/ui';
import { mapActions, mapState, mapGetters } from 'vuex'; import { mapActions, mapState, mapGetters } from 'vuex';
import { __ } from '~/locale';
import createFlash from '~/flash';
import { getDateInPast } from '~/lib/utils/datetime_utility'; import { getDateInPast } from '~/lib/utils/datetime_utility';
import { featureAccessLevel } from '~/pages/projects/shared/permissions/constants'; import { featureAccessLevel } from '~/pages/projects/shared/permissions/constants';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
...@@ -46,6 +48,7 @@ export default { ...@@ -46,6 +48,7 @@ export default {
...mapState([ ...mapState([
'isLoading', 'isLoading',
'isLoadingStage', 'isLoadingStage',
'isLoadingChartData',
'isEmptyStage', 'isEmptyStage',
'isAddingCustomStage', 'isAddingCustomStage',
'isSavingCustomStage', 'isSavingCustomStage',
...@@ -60,6 +63,7 @@ export default { ...@@ -60,6 +63,7 @@ export default {
'errorCode', 'errorCode',
'startDate', 'startDate',
'endDate', 'endDate',
'tasksByType',
]), ]),
...mapGetters(['currentStage', 'defaultStage', 'hasNoAccessError', 'currentGroupPath']), ...mapGetters(['currentStage', 'defaultStage', 'hasNoAccessError', 'currentGroupPath']),
shouldRenderEmptyState() { shouldRenderEmptyState() {
...@@ -106,12 +110,20 @@ export default { ...@@ -106,12 +110,20 @@ export default {
this.setCycleAnalyticsDataEndpoint(group.full_path); this.setCycleAnalyticsDataEndpoint(group.full_path);
this.setSelectedGroup(group); this.setSelectedGroup(group);
this.fetchCycleAnalyticsData(); this.fetchCycleAnalyticsData();
this.fetchTasksByTypeData(group.path); this.fetchGroupLabels(this.currentGroupPath)
.then(() => {
// TODO: Move this request into the `fetchCycleAnalyticsData` request, because we
// need to send the group labels, this request should fire after fetchGroupLablels is completed
// https://gitlab.com/gitlab-org/gitlab/merge_requests/18514
this.fetchTasksByTypeData();
})
.catch(() => createFlash(__('There was an error fetching data for the chart')));
}, },
onProjectsSelect(projects) { onProjectsSelect(projects) {
const projectIds = projects.map(value => value.id); const projectIds = projects.map(value => value.id);
this.setSelectedProjects(projectIds); this.setSelectedProjects(projectIds);
this.fetchCycleAnalyticsData(); this.fetchCycleAnalyticsData();
this.fetchTasksByTypeData();
}, },
onStageSelect(stage) { onStageSelect(stage) {
this.hideCustomStageForm(); this.hideCustomStageForm();
......
...@@ -30,5 +30,5 @@ export const EMPTY_STAGE_TEXT = { ...@@ -30,5 +30,5 @@ export const EMPTY_STAGE_TEXT = {
), ),
}; };
export const TASKS_BY_TYPE_SUBJECT_ISSUE = 'issues'; export const TASKS_BY_TYPE_SUBJECT_ISSUE = 'Issue';
export const TASKS_BY_TYPE_SUBJECT_MERGE_REQUEST = 'merge_requests'; export const TASKS_BY_TYPE_SUBJECT_MERGE_REQUEST = 'MergeRequest';
...@@ -32,7 +32,10 @@ export const setDateRange = ( ...@@ -32,7 +32,10 @@ export const setDateRange = (
if (skipFetch) return false; if (skipFetch) return false;
return dispatch('fetchCycleAnalyticsData', { state, dispatch }); return Promise.all([
dispatch('fetchCycleAnalyticsData', { state, dispatch }),
dispatch('fetchTasksByTypeData'),
]);
}; };
export const requestStageData = ({ commit }) => commit(types.REQUEST_STAGE_DATA); export const requestStageData = ({ commit }) => commit(types.REQUEST_STAGE_DATA);
...@@ -208,23 +211,24 @@ export const receiveTasksByTypeDataError = ({ commit }, error) => { ...@@ -208,23 +211,24 @@ export const receiveTasksByTypeDataError = ({ commit }, error) => {
}; };
export const requestTasksByTypeData = ({ commit }) => commit(types.REQUEST_TASKS_BY_TYPE_DATA); export const requestTasksByTypeData = ({ commit }) => commit(types.REQUEST_TASKS_BY_TYPE_DATA);
export const fetchTasksByTypeData = ({ dispatch, state }, groupPath) => { export const fetchTasksByTypeData = ({ dispatch, state, getters }) => {
const endpoint = '/-/analytics/type_of_work/tasks_by_type'; const endpoint = '/-/analytics/type_of_work/tasks_by_type';
const { currentGroupPath } = getters;
const { const {
tasksByType: { labelIds, subject }, tasksByType: { labelIds, subject },
selectedProjectIds, selectedProjectIds,
timeFrameCreatedBefore, startDate,
timeFrameCreatedAfter, endDate,
} = state; } = state;
// dont request if we have no labels selected...for now // dont request if we have no labels selected...for now
if (!labelIds.length) { if (labelIds.length) {
const params = { const params = {
group_id: groupPath, group_id: currentGroupPath,
label_ids: labelIds, label_ids: `[${labelIds}]`,
project_ids: selectedProjectIds, project_ids: selectedProjectIds || [],
created_before: timeFrameCreatedBefore, created_after: dateFormat(startDate, dateFormats.isoDate),
created_after: timeFrameCreatedAfter, created_before: dateFormat(endDate, dateFormats.isoDate),
subject, subject,
}; };
...@@ -236,4 +240,5 @@ export const fetchTasksByTypeData = ({ dispatch, state }, groupPath) => { ...@@ -236,4 +240,5 @@ export const fetchTasksByTypeData = ({ dispatch, state }, groupPath) => {
.then(data => dispatch('receiveTasksByTypeDataSuccess', data)) .then(data => dispatch('receiveTasksByTypeDataSuccess', data))
.catch(error => dispatch('receiveTasksByTypeDataError', error)); .catch(error => dispatch('receiveTasksByTypeDataError', error));
} }
return Promise.resolve();
}; };
...@@ -60,12 +60,26 @@ export default { ...@@ -60,12 +60,26 @@ export default {
}, },
[types.REQUEST_GROUP_LABELS](state) { [types.REQUEST_GROUP_LABELS](state) {
state.labels = []; state.labels = [];
state.tasksByType = {
...state.tasksByType,
labelIds: [],
};
}, },
[types.RECEIVE_GROUP_LABELS_SUCCESS](state, data = []) { [types.RECEIVE_GROUP_LABELS_SUCCESS](state, data = []) {
const { tasksByType } = state;
state.labels = data.map(convertObjectPropsToCamelCase); state.labels = data.map(convertObjectPropsToCamelCase);
state.tasksByType = {
...tasksByType,
labelIds: data.map(({ id }) => id),
};
}, },
[types.RECEIVE_GROUP_LABELS_ERROR](state) { [types.RECEIVE_GROUP_LABELS_ERROR](state) {
const { tasksByType } = state;
state.labels = []; state.labels = [];
state.tasksByType = {
...tasksByType,
labelIds: [],
};
}, },
[types.HIDE_CUSTOM_STAGE_FORM](state) { [types.HIDE_CUSTOM_STAGE_FORM](state) {
state.isAddingCustomStage = false; state.isAddingCustomStage = false;
......
...@@ -13,6 +13,7 @@ export default () => ({ ...@@ -13,6 +13,7 @@ export default () => ({
isLoading: false, isLoading: false,
isLoadingStage: false, isLoadingStage: false,
isLoadingChartData: false,
isEmptyStage: false, isEmptyStage: false,
errorCode: null, errorCode: null,
...@@ -35,6 +36,6 @@ export default () => ({ ...@@ -35,6 +36,6 @@ export default () => ({
subject: TASKS_BY_TYPE_SUBJECT_ISSUE, // issues | merge_requests, defaults to issues subject: TASKS_BY_TYPE_SUBJECT_ISSUE, // issues | merge_requests, defaults to issues
// list of selected labels for the tasks by type chart // list of selected labels for the tasks by type chart
labelIds: [], labelIds: [],
data: [] data: [],
}, },
}); });
...@@ -429,9 +429,16 @@ describe('Cycle Analytics component', () => { ...@@ -429,9 +429,16 @@ describe('Cycle Analytics component', () => {
mock mock
.onGet('/groups/foo/-/labels') .onGet('/groups/foo/-/labels')
.replyOnce(httpStatusCodes.OK, { response: { ...mockData.groupLabels } }) .replyOnce(httpStatusCodes.OK, [...mockData.groupLabels])
.onGet('/groups/foo/-/cycle_analytics') .onGet('/groups/foo/-/cycle_analytics')
.replyOnce(httpStatusCodes.OK, { response: { status: httpStatusCodes.OK } }) .replyOnce(httpStatusCodes.OK, {
...mockData.cycleAnalyticsData,
response: { status: httpStatusCodes.OK },
})
.onGet('/groups/foo/-/cycle_analytics/events/issue.json')
.replyOnce(httpStatusCodes.OK, {
response: { events: mockData.issueEvents },
})
.onGet('/-/analytics/type_of_work/tasks_by_type') .onGet('/-/analytics/type_of_work/tasks_by_type')
.replyOnce(httpStatusCodes.BAD_REQUEST, { .replyOnce(httpStatusCodes.BAD_REQUEST, {
response: { status: httpStatusCodes.BAD_REQUEST }, response: { status: httpStatusCodes.BAD_REQUEST },
......
...@@ -79,7 +79,10 @@ describe('Cycle analytics actions', () => { ...@@ -79,7 +79,10 @@ describe('Cycle analytics actions', () => {
{ startDate, endDate }, { startDate, endDate },
state, state,
[{ type: types.SET_DATE_RANGE, payload: { startDate, endDate } }], [{ type: types.SET_DATE_RANGE, payload: { startDate, endDate } }],
[{ type: 'fetchCycleAnalyticsData', payload: { dispatch, state } }], [
{ type: 'fetchCycleAnalyticsData', payload: { dispatch, state } },
{ type: 'fetchTasksByTypeData' },
],
done, done,
); );
}); });
......
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