Commit 13b0dbf7 authored by Martin Wortschack's avatar Martin Wortschack

Merge branch '197337-move-tasks-by-type-vuex-into-module' into 'master'

Move type of work vuex code into module

Closes #214892

See merge request gitlab-org/gitlab!29674
parents 962ae39d b5afaac5
......@@ -52,8 +52,6 @@ export default {
'featureFlags',
'isLoading',
'isLoadingStage',
'isLoadingTasksByTypeChart',
'isLoadingTasksByTypeChartTopLabels',
'isEmptyStage',
'isSavingCustomStage',
'isCreatingCustomStage',
......@@ -63,25 +61,30 @@ export default {
'selectedStage',
'stages',
'summary',
'topRankedLabels',
'currentStageEvents',
'customStageFormEvents',
'errorCode',
'startDate',
'endDate',
'tasksByType',
'medians',
'customStageFormErrors',
]),
...mapState('typeOfWork', [
'isLoadingTasksByTypeChart',
'isLoadingTasksByTypeChartTopLabels',
'topRankedLabels',
'subject',
'selectedLabelIds',
]),
...mapGetters([
'hasNoAccessError',
'currentGroupPath',
'tasksByTypeChartData',
'activeStages',
'selectedProjectIds',
'enableCustomOrdering',
'cycleAnalyticsRequestParams',
]),
...mapGetters('typeOfWork', ['tasksByTypeChartData']),
shouldRenderEmptyState() {
return !this.selectedGroup;
},
......@@ -95,12 +98,10 @@ export default {
return this.featureFlags.hasDurationChart && !this.hasNoAccessError && !this.isLoading;
},
shouldDisplayTypeOfWorkCharts() {
return this.featureFlags.hasTasksByTypeChart && !this.hasNoAccessError;
return this.featureFlags.hasTasksByTypeChart && !this.hasNoAccessError && !this.isLoading;
},
isLoadingTypeOfWork() {
return (
this.isLoading || this.isLoadingTasksByTypeChartTopLabels || this.isLoadingTasksByTypeChart
);
return this.isLoadingTasksByTypeChartTopLabels || this.isLoadingTasksByTypeChart;
},
hasDateRangeSet() {
return this.startDate && this.endDate;
......@@ -111,7 +112,8 @@ export default {
startDate,
endDate,
selectedProjectIds,
tasksByType: { subject, selectedLabelIds },
subject,
selectedLabelIds,
} = this;
return {
selectedGroup,
......@@ -152,16 +154,15 @@ export default {
'showCustomStageForm',
'showEditCustomStageForm',
'setDateRange',
'fetchTasksByTypeData',
'createCustomStage',
'updateStage',
'removeStage',
'setFeatureFlags',
'clearCustomStageFormErrors',
'updateStage',
'setTasksByTypeFilters',
'reorderStage',
]),
...mapActions('typeOfWork', ['setTasksByTypeFilters']),
onGroupSelect(group) {
this.setSelectedGroup(group);
this.fetchCycleAnalyticsData();
......
......@@ -3,14 +3,7 @@ import createFlash from '~/flash';
import { __, sprintf } from '~/locale';
import httpStatus from '~/lib/utils/http_status';
import * as types from './mutation_types';
import { removeFlash } from '../utils';
const handleErrorOrRethrow = ({ action, error }) => {
if (error?.response?.status === httpStatus.FORBIDDEN) {
throw error;
}
action();
};
import { removeFlash, handleErrorOrRethrow } from '../utils';
const isStageNameExistsError = ({ status, errors }) => {
const ERROR_NAME_RESERVED = 'is reserved';
......@@ -105,13 +98,9 @@ export const fetchStageMedianValues = ({ state, dispatch, getters }) => {
};
export const requestCycleAnalyticsData = ({ commit }) => commit(types.REQUEST_CYCLE_ANALYTICS_DATA);
export const receiveCycleAnalyticsDataSuccess = ({ state, commit, dispatch }) => {
export const receiveCycleAnalyticsDataSuccess = ({ commit, dispatch }) => {
commit(types.RECEIVE_CYCLE_ANALYTICS_DATA_SUCCESS);
const { featureFlags: { hasTasksByTypeChart = false } = {} } = state;
const promises = [];
if (hasTasksByTypeChart) promises.push('fetchTopRankedGroupLabels');
return Promise.all(promises.map(func => dispatch(func)));
dispatch('typeOfWork/fetchTopRankedGroupLabels');
};
export const receiveCycleAnalyticsDataError = ({ commit }, { response }) => {
......@@ -168,44 +157,6 @@ export const showEditCustomStageForm = ({ commit, dispatch }, selectedStage = {}
export const requestGroupStagesAndEvents = ({ commit }) =>
commit(types.REQUEST_GROUP_STAGES_AND_EVENTS);
export const receiveTopRankedGroupLabelsSuccess = ({ commit, dispatch }, data) => {
commit(types.RECEIVE_TOP_RANKED_GROUP_LABELS_SUCCESS, data);
dispatch('fetchTasksByTypeData');
};
export const receiveTopRankedGroupLabelsError = ({ commit }, error) => {
commit(types.RECEIVE_TOP_RANKED_GROUP_LABELS_ERROR, error);
createFlash(__('There was an error fetching the top labels for the selected group'));
};
export const requestTopRankedGroupLabels = ({ commit }) =>
commit(types.REQUEST_TOP_RANKED_GROUP_LABELS);
export const fetchTopRankedGroupLabels = ({
dispatch,
state,
getters: {
currentGroupPath,
cycleAnalyticsRequestParams: { created_after, created_before },
},
}) => {
dispatch('requestTopRankedGroupLabels');
const { subject } = state.tasksByType;
return Api.cycleAnalyticsTopLabels(currentGroupPath, {
subject,
created_after,
created_before,
})
.then(({ data }) => dispatch('receiveTopRankedGroupLabelsSuccess', data))
.catch(error =>
handleErrorOrRethrow({
error,
action: () => dispatch('receiveTopRankedGroupLabelsError', error),
}),
);
};
export const receiveGroupStagesAndEventsError = ({ commit }, error) => {
commit(types.RECEIVE_GROUP_STAGES_AND_EVENTS_ERROR, error);
createFlash(__('There was an error fetching value stream analytics stages.'));
......@@ -295,41 +246,6 @@ export const createCustomStage = ({ dispatch, state }, data) => {
});
};
export const receiveTasksByTypeDataError = ({ commit }, error) => {
commit(types.RECEIVE_TASKS_BY_TYPE_DATA_ERROR, error);
createFlash(__('There was an error fetching data for the tasks by type chart'));
};
export const fetchTasksByTypeData = ({ dispatch, commit, state, getters }) => {
const {
currentGroupPath,
cycleAnalyticsRequestParams: { created_after, created_before, project_ids },
} = getters;
const {
tasksByType: { subject, selectedLabelIds },
} = state;
// ensure we clear any chart data currently in state
commit(types.REQUEST_TASKS_BY_TYPE_DATA);
// dont request if we have no labels selected...for now
if (selectedLabelIds.length) {
const params = {
created_after,
created_before,
project_ids,
subject,
label_ids: selectedLabelIds,
};
return Api.cycleAnalyticsTasksByType(currentGroupPath, params)
.then(({ data }) => commit(types.RECEIVE_TASKS_BY_TYPE_DATA_SUCCESS, data))
.catch(error => dispatch('receiveTasksByTypeDataError', error));
}
return commit(types.RECEIVE_TASKS_BY_TYPE_DATA_SUCCESS, []);
};
export const requestUpdateStage = ({ commit }) => commit(types.REQUEST_UPDATE_STAGE);
export const receiveUpdateStageSuccess = ({ commit, dispatch }, updatedData) => {
commit(types.RECEIVE_UPDATE_STAGE_SUCCESS);
......@@ -396,11 +312,6 @@ export const removeStage = ({ dispatch, state }, stageId) => {
.catch(error => dispatch('receiveRemoveStageError', error));
};
export const setTasksByTypeFilters = ({ dispatch, commit }, data) => {
commit(types.SET_TASKS_BY_TYPE_FILTERS, data);
dispatch('fetchTopRankedGroupLabels');
};
export const initializeCycleAnalyticsSuccess = ({ commit }) =>
commit(types.INITIALIZE_CYCLE_ANALYTICS_SUCCESS);
......
......@@ -2,7 +2,6 @@ import dateFormat from 'dateformat';
import { isNumber } from 'lodash';
import httpStatus from '~/lib/utils/http_status';
import { dateFormats } from '../../shared/constants';
import { getTasksByTypeData } from '../utils';
export const hasNoAccessError = state => state.errorCode === httpStatus.FORBIDDEN;
......@@ -18,17 +17,6 @@ export const cycleAnalyticsRequestParams = ({ startDate = null, endDate = null }
created_before: endDate ? dateFormat(endDate, dateFormats.isoDate) : null,
});
export const tasksByTypeChartData = ({ tasksByType, startDate, endDate }) => {
if (tasksByType && tasksByType.data.length) {
return getTasksByTypeData({
data: tasksByType.data,
startDate,
endDate,
});
}
return { groupBy: [], data: [], seriesNames: [] };
};
const filterStagesByHiddenStatus = (stages = [], isHidden = true) =>
stages.filter(({ hidden = false }) => hidden === isHidden);
......
......@@ -5,6 +5,7 @@ import * as getters from './getters';
import mutations from './mutations';
import state from './state';
import durationChart from './modules/duration_chart/index';
import typeOfWork from './modules/type_of_work/index';
Vue.use(Vuex);
......@@ -14,5 +15,5 @@ export default () =>
getters,
mutations,
state,
modules: { durationChart },
modules: { durationChart, typeOfWork },
});
import Api from 'ee/api';
import createFlash from '~/flash';
import { __ } from '~/locale';
import * as types from './mutation_types';
import { handleErrorOrRethrow } from '../../../utils';
export const receiveTopRankedGroupLabelsSuccess = ({ commit, dispatch }, data) => {
commit(types.RECEIVE_TOP_RANKED_GROUP_LABELS_SUCCESS, data);
dispatch('fetchTasksByTypeData');
};
export const receiveTopRankedGroupLabelsError = ({ commit }, error) => {
commit(types.RECEIVE_TOP_RANKED_GROUP_LABELS_ERROR, error);
createFlash(__('There was an error fetching the top labels for the selected group'));
};
export const fetchTopRankedGroupLabels = ({ dispatch, commit, state, rootGetters }) => {
commit(types.REQUEST_TOP_RANKED_GROUP_LABELS);
const {
currentGroupPath,
cycleAnalyticsRequestParams: { created_after, created_before },
} = rootGetters;
const { subject } = state;
return Api.cycleAnalyticsTopLabels(currentGroupPath, {
subject,
created_after,
created_before,
})
.then(({ data }) => dispatch('receiveTopRankedGroupLabelsSuccess', data))
.catch(error =>
handleErrorOrRethrow({
error,
action: () => dispatch('receiveTopRankedGroupLabelsError', error),
}),
);
};
export const receiveTasksByTypeDataError = ({ commit }, error) => {
commit(types.RECEIVE_TASKS_BY_TYPE_DATA_ERROR, error);
createFlash(__('There was an error fetching data for the tasks by type chart'));
};
export const fetchTasksByTypeData = ({ dispatch, commit, state, rootGetters }) => {
const {
currentGroupPath,
cycleAnalyticsRequestParams: { created_after, created_before, project_ids },
} = rootGetters;
const { subject, selectedLabelIds } = state;
// ensure we clear any chart data currently in state
commit(types.REQUEST_TASKS_BY_TYPE_DATA);
// dont request if we have no labels selected...for now
if (selectedLabelIds.length) {
const params = {
created_after,
created_before,
project_ids,
subject,
label_ids: selectedLabelIds,
};
return Api.cycleAnalyticsTasksByType(currentGroupPath, params)
.then(({ data }) => commit(types.RECEIVE_TASKS_BY_TYPE_DATA_SUCCESS, data))
.catch(error => dispatch('receiveTasksByTypeDataError', error));
}
return commit(types.RECEIVE_TASKS_BY_TYPE_DATA_SUCCESS, []);
};
export const setTasksByTypeFilters = ({ dispatch, commit }, data) => {
commit(types.SET_TASKS_BY_TYPE_FILTERS, data);
dispatch('fetchTasksByTypeData');
};
import { getTasksByTypeData } from '../../../utils';
export const tasksByTypeChartData = ({ data = [] } = {}, _, rootState = {}) => {
const { startDate = null, endDate = null } = rootState;
return data.length
? getTasksByTypeData({
data,
startDate,
endDate,
})
: { groupBy: [], data: [], seriesNames: [] };
};
export default () => ({ tasksByTypeChartData });
import * as actions from './actions';
import * as getters from './getters';
import mutations from './mutations';
import state from './state';
export default {
namespaced: true,
actions,
getters,
mutations,
state,
};
export const REQUEST_TOP_RANKED_GROUP_LABELS = 'REQUEST_TOP_RANKED_GROUP_LABELS';
export const RECEIVE_TOP_RANKED_GROUP_LABELS_SUCCESS = 'RECEIVE_TOP_RANKED_GROUP_LABELS_SUCCESS';
export const RECEIVE_TOP_RANKED_GROUP_LABELS_ERROR = 'RECEIVE_TOP_RANKED_GROUP_LABELS_ERROR';
export const REQUEST_TASKS_BY_TYPE_DATA = 'REQUEST_TASKS_BY_TYPE_DATA';
export const RECEIVE_TASKS_BY_TYPE_DATA_SUCCESS = 'RECEIVE_TASKS_BY_TYPE_DATA_SUCCESS';
export const RECEIVE_TASKS_BY_TYPE_DATA_ERROR = 'RECEIVE_TASKS_BY_TYPE_DATA_ERROR';
export const SET_TASKS_BY_TYPE_SUBJECT = 'SET_TASKS_BY_TYPE_SUBJECT';
export const SET_TASKS_BY_TYPE_LABELS = 'SET_TASKS_BY_TYPE_LABELS';
export const SET_TASKS_BY_TYPE_FILTERS = 'SET_TASKS_BY_TYPE_FILTERS';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import * as types from './mutation_types';
import { transformRawTasksByTypeData, toggleSelectedLabel } from '../../../utils';
import { TASKS_BY_TYPE_FILTERS } from '../../../constants';
export default {
[types.REQUEST_TOP_RANKED_GROUP_LABELS](state) {
state.isLoadingTasksByTypeChartTopLabels = true;
state.topRankedLabels = [];
state.selectedLabelIds = [];
},
[types.RECEIVE_TOP_RANKED_GROUP_LABELS_SUCCESS](state, data = []) {
state.isLoadingTasksByTypeChartTopLabels = false;
state.topRankedLabels = data.map(convertObjectPropsToCamelCase);
state.selectedLabelIds = data.map(({ id }) => id);
},
[types.RECEIVE_TOP_RANKED_GROUP_LABELS_ERROR](state) {
state.isLoadingTasksByTypeChartTopLabels = false;
state.topRankedLabels = [];
state.selectedLabelIds = [];
},
[types.REQUEST_TASKS_BY_TYPE_DATA](state) {
state.isLoadingTasksByTypeChart = true;
},
[types.RECEIVE_TASKS_BY_TYPE_DATA_ERROR](state) {
state.isLoadingTasksByTypeChart = false;
},
[types.RECEIVE_TASKS_BY_TYPE_DATA_SUCCESS](state, data = []) {
state.isLoadingTasksByTypeChart = false;
state.data = transformRawTasksByTypeData(data);
},
[types.SET_TASKS_BY_TYPE_FILTERS](state, { filter, value }) {
const { selectedLabelIds } = state;
switch (filter) {
case TASKS_BY_TYPE_FILTERS.LABEL:
state.selectedLabelIds = toggleSelectedLabel({ selectedLabelIds, value });
break;
case TASKS_BY_TYPE_FILTERS.SUBJECT:
state.subject = value;
break;
default:
break;
}
},
};
import { TASKS_BY_TYPE_SUBJECT_ISSUE } from '../../../constants';
export default () => ({
isLoadingTasksByTypeChart: false,
isLoadingTasksByTypeChartTopLabels: false,
subject: TASKS_BY_TYPE_SUBJECT_ISSUE,
selectedLabelIds: [],
topRankedLabels: [],
data: [],
});
......@@ -22,21 +22,10 @@ export const SHOW_CUSTOM_STAGE_FORM = 'SHOW_CUSTOM_STAGE_FORM';
export const SHOW_EDIT_CUSTOM_STAGE_FORM = 'SHOW_EDIT_CUSTOM_STAGE_FORM';
export const CLEAR_CUSTOM_STAGE_FORM_ERRORS = 'CLEAR_CUSTOM_STAGE_FORM_ERRORS';
export const REQUEST_TOP_RANKED_GROUP_LABELS = 'REQUEST_TOP_RANKED_GROUP_LABELS';
export const RECEIVE_TOP_RANKED_GROUP_LABELS_SUCCESS = 'RECEIVE_TOP_RANKED_GROUP_LABELS_SUCCESS';
export const RECEIVE_TOP_RANKED_GROUP_LABELS_ERROR = 'RECEIVE_TOP_RANKED_GROUP_LABELS_ERROR';
export const REQUEST_GROUP_STAGES_AND_EVENTS = 'REQUEST_GROUP_STAGES_AND_EVENTS';
export const RECEIVE_GROUP_STAGES_AND_EVENTS_SUCCESS = 'RECEIVE_GROUP_STAGES_AND_EVENTS_SUCCESS';
export const RECEIVE_GROUP_STAGES_AND_EVENTS_ERROR = 'RECEIVE_GROUP_STAGES_AND_EVENTS_ERROR';
export const SET_TASKS_BY_TYPE_SUBJECT = 'SET_TASKS_BY_TYPE_SUBJECT';
export const SET_TASKS_BY_TYPE_LABELS = 'SET_TASKS_BY_TYPE_LABELS';
export const REQUEST_TASKS_BY_TYPE_DATA = 'REQUEST_TASKS_BY_TYPE_DATA';
export const RECEIVE_TASKS_BY_TYPE_DATA_SUCCESS = 'RECEIVE_TASKS_BY_TYPE_DATA_SUCCESS';
export const RECEIVE_TASKS_BY_TYPE_DATA_ERROR = 'RECEIVE_TASKS_BY_TYPE_DATA_ERROR';
export const REQUEST_CREATE_CUSTOM_STAGE = 'REQUEST_CREATE_CUSTOM_STAGE';
export const RECEIVE_CREATE_CUSTOM_STAGE_SUCCESS = 'RECEIVE_CREATE_CUSTOM_STAGE_SUCCESS';
export const RECEIVE_CREATE_CUSTOM_STAGE_ERROR = 'RECEIVE_CREATE_CUSTOM_STAGE_ERROR';
......@@ -48,8 +37,6 @@ export const RECEIVE_UPDATE_STAGE_ERROR = 'RECEIVE_UPDATE_STAGE_ERROR';
export const REQUEST_REMOVE_STAGE = 'REQUEST_REMOVE_STAGE';
export const RECEIVE_REMOVE_STAGE_RESPONSE = 'RECEIVE_REMOVE_STAGE_RESPONSE';
export const SET_TASKS_BY_TYPE_FILTERS = 'SET_TASKS_BY_TYPE_FILTERS';
export const INITIALIZE_CYCLE_ANALYTICS = 'INITIALIZE_CYCLE_ANALYTICS';
export const INITIALIZE_CYCLE_ANALYTICS_SUCCESS = 'INITIALIZE_CYCLE_ANALYTICS_SUCCESS';
......
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import * as types from './mutation_types';
import { transformRawStages, transformRawTasksByTypeData, toggleSelectedLabel } from '../utils';
import { TASKS_BY_TYPE_FILTERS } from '../constants';
import { transformRawStages } from '../utils';
export default {
[types.SET_FEATURE_FLAGS](state, featureFlags) {
......@@ -49,32 +48,6 @@ export default {
state.isEmptyStage = true;
state.isLoadingStage = false;
},
[types.REQUEST_TOP_RANKED_GROUP_LABELS](state) {
state.isLoadingTasksByTypeChartTopLabels = true;
state.topRankedLabels = [];
state.tasksByType = {
...state.tasksByType,
selectedLabelIds: [],
};
},
[types.RECEIVE_TOP_RANKED_GROUP_LABELS_SUCCESS](state, data = []) {
const { tasksByType } = state;
state.isLoadingTasksByTypeChartTopLabels = false;
state.topRankedLabels = data.map(convertObjectPropsToCamelCase);
state.tasksByType = {
...tasksByType,
selectedLabelIds: data.map(({ id }) => id),
};
},
[types.RECEIVE_TOP_RANKED_GROUP_LABELS_ERROR](state) {
const { tasksByType } = state;
state.isLoadingTasksByTypeChartTopLabels = false;
state.topRankedLabels = [];
state.tasksByType = {
...tasksByType,
selectedLabelIds: [],
};
},
[types.REQUEST_STAGE_MEDIANS](state) {
state.medians = {};
},
......@@ -127,19 +100,6 @@ export default {
convertObjectPropsToCamelCase(ev, { deep: true }),
);
},
[types.REQUEST_TASKS_BY_TYPE_DATA](state) {
state.isLoadingTasksByTypeChart = true;
},
[types.RECEIVE_TASKS_BY_TYPE_DATA_ERROR](state) {
state.isLoadingTasksByTypeChart = false;
},
[types.RECEIVE_TASKS_BY_TYPE_DATA_SUCCESS](state, data = []) {
state.isLoadingTasksByTypeChart = false;
state.tasksByType = {
...state.tasksByType,
data: transformRawTasksByTypeData(data),
};
},
[types.REQUEST_CREATE_CUSTOM_STAGE](state) {
state.isSavingCustomStage = true;
state.customStageFormErrors = {};
......@@ -177,25 +137,6 @@ export default {
[types.RECEIVE_REMOVE_STAGE_RESPONSE](state) {
state.isLoading = false;
},
[types.SET_TASKS_BY_TYPE_FILTERS](state, { filter, value }) {
const {
tasksByType: { selectedLabelIds, ...tasksByTypeRest },
} = state;
let updatedFilter = {};
switch (filter) {
case TASKS_BY_TYPE_FILTERS.LABEL:
updatedFilter = {
selectedLabelIds: toggleSelectedLabel({ selectedLabelIds, value }),
};
break;
case TASKS_BY_TYPE_FILTERS.SUBJECT:
updatedFilter = { subject: value };
break;
default:
break;
}
state.tasksByType = { ...tasksByTypeRest, selectedLabelIds, ...updatedFilter };
},
[types.INITIALIZE_CYCLE_ANALYTICS](
state,
{
......
import { TASKS_BY_TYPE_SUBJECT_ISSUE } from '../constants';
export default () => ({
featureFlags: {},
......@@ -8,8 +6,6 @@ export default () => ({
isLoading: false,
isLoadingStage: false,
isLoadingTasksByTypeChart: false,
isLoadingTasksByTypeChartTopLabels: false,
isEmptyStage: false,
errorCode: null,
......@@ -28,16 +24,9 @@ export default () => ({
stages: [],
summary: [],
topRankedLabels: [],
medians: {},
customStageFormEvents: [],
customStageFormErrors: null,
customStageFormInitialData: null,
tasksByType: {
subject: TASKS_BY_TYPE_SUBJECT_ISSUE,
selectedLabelIds: [],
data: [],
},
});
import { isNumber } from 'lodash';
import dateFormat from 'dateformat';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import httpStatus from '~/lib/utils/http_status';
import { convertToSnakeCase } from '~/lib/utils/text_utility';
import { hideFlash } from '~/flash';
import {
......@@ -329,3 +330,10 @@ export const getTasksByTypeData = ({ data = [], startDate = null, endDate = null
groupBy,
};
};
export const handleErrorOrRethrow = ({ action, error }) => {
if (error?.response?.status === httpStatus.FORBIDDEN) {
throw error;
}
action();
};
import { createLocalVue, shallowMount, mount } from '@vue/test-utils';
import Vuex from 'vuex';
import Vue from 'vue';
import store from 'ee/analytics/cycle_analytics/store';
import Component from 'ee/analytics/cycle_analytics/components/base.vue';
import { GlEmptyState } from '@gitlab/ui';
......@@ -291,16 +290,15 @@ describe('Cycle Analytics component', () => {
expect(second.classes('active')).toBe(false);
});
it('can navigate to different stages', done => {
it('can navigate to different stages', () => {
selectStageNavItem(2).trigger('click');
Vue.nextTick(() => {
return wrapper.vm.$nextTick().then(() => {
const first = selectStageNavItem(0);
const third = selectStageNavItem(2);
expect(third.classes('active')).toBe(true);
expect(first.classes('active')).toBe(false);
done();
});
});
});
......
......@@ -10,7 +10,10 @@ import {
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { getDateInPast, getDatesInRange } from '~/lib/utils/datetime_utility';
import { toYmd } from 'ee/analytics/shared/utils';
import { transformRawTasksByTypeData } from 'ee/analytics/cycle_analytics/utils';
import {
getTasksByTypeData,
transformRawTasksByTypeData,
} from 'ee/analytics/cycle_analytics/utils';
const fixtureEndpoints = {
customizableCycleAnalyticsStagesAndEvents: 'analytics/value_stream_analytics/stages.json', // customizable stages and events endpoint
......@@ -145,7 +148,7 @@ export const customStageFormErrors = convertObjectPropsToCamelCase(rawCustomStag
const dateRange = getDatesInRange(startDate, endDate, toYmd);
export const rawTasksByTypeData = getJSONFixture('analytics/type_of_work/tasks_by_type.json').map(
export const apiTasksByTypeData = getJSONFixture('analytics/type_of_work/tasks_by_type.json').map(
labelData => {
// add data points for our mock date range
const maxValue = 10;
......@@ -157,7 +160,8 @@ export const rawTasksByTypeData = getJSONFixture('analytics/type_of_work/tasks_b
},
);
export const transformedTasksByTypeData = transformRawTasksByTypeData(rawTasksByTypeData);
export const rawTasksByTypeData = transformRawTasksByTypeData(apiTasksByTypeData);
export const transformedTasksByTypeData = getTasksByTypeData(apiTasksByTypeData);
export const tasksByTypeData = {
seriesNames: ['Cool label', 'Normal label'],
......
......@@ -4,16 +4,11 @@ import testAction from 'helpers/vuex_action_helper';
import * as getters from 'ee/analytics/cycle_analytics/store/getters';
import * as actions from 'ee/analytics/cycle_analytics/store/actions';
import * as types from 'ee/analytics/cycle_analytics/store/mutation_types';
import {
TASKS_BY_TYPE_FILTERS,
TASKS_BY_TYPE_SUBJECT_ISSUE,
} from 'ee/analytics/cycle_analytics/constants';
import createFlash from '~/flash';
import httpStatusCodes from '~/lib/utils/http_status';
import {
selectedGroup,
allowedStages as stages,
groupLabels,
startDate,
endDate,
customizableStagesAndEvents,
......@@ -180,87 +175,6 @@ describe('Cycle analytics actions', () => {
});
});
describe('fetchTopRankedGroupLabels', () => {
beforeEach(() => {
gon.api_version = 'v4';
state = { selectedGroup, tasksByType: { subject: TASKS_BY_TYPE_SUBJECT_ISSUE }, ...getters };
});
describe('succeeds', () => {
beforeEach(() => {
mock.onGet(endpoints.tasksByTypeTopLabelsData).replyOnce(200, groupLabels);
});
it('dispatches receiveTopRankedGroupLabelsSuccess if the request succeeds', () => {
return testAction(
actions.fetchTopRankedGroupLabels,
null,
state,
[],
[
{ type: 'requestTopRankedGroupLabels' },
{ type: 'receiveTopRankedGroupLabelsSuccess', payload: groupLabels },
],
);
});
describe('receiveTopRankedGroupLabelsSuccess', () => {
beforeEach(() => {
setFixtures('<div class="flash-container"></div>');
});
it(`commits the ${types.RECEIVE_TOP_RANKED_GROUP_LABELS_SUCCESS} mutation and dispatches the 'fetchTasksByTypeData' action`, done => {
testAction(
actions.receiveTopRankedGroupLabelsSuccess,
null,
state,
[
{
type: types.RECEIVE_TOP_RANKED_GROUP_LABELS_SUCCESS,
payload: null,
},
],
[{ type: 'fetchTasksByTypeData' }],
done,
);
});
});
});
describe('with an error', () => {
beforeEach(() => {
mock.onGet(endpoints.fetchTopRankedGroupLabels).replyOnce(404);
});
it('dispatches receiveTopRankedGroupLabelsError if the request fails', () => {
return testAction(
actions.fetchTopRankedGroupLabels,
null,
state,
[],
[
{ type: 'requestTopRankedGroupLabels' },
{ type: 'receiveTopRankedGroupLabelsError', payload: error },
],
);
});
});
describe('receiveTopRankedGroupLabelsError', () => {
beforeEach(() => {
setFixtures('<div class="flash-container"></div>');
});
it('flashes an error message if the request fails', () => {
actions.receiveTopRankedGroupLabelsError({
commit: () => {},
});
shouldFlashAMessage('There was an error fetching the top labels for the selected group');
});
});
});
describe('fetchCycleAnalyticsData', () => {
function mockFetchCycleAnalyticsAction(overrides = {}) {
const mocks = {
......@@ -845,31 +759,6 @@ describe('Cycle analytics actions', () => {
});
});
describe('setTasksByTypeFilters', () => {
const filter = TASKS_BY_TYPE_FILTERS.SUBJECT;
const value = 'issue';
it(`commits the ${types.SET_TASKS_BY_TYPE_FILTERS} mutation and dispatches 'fetchTopRankedGroupLabels'`, done => {
testAction(
actions.setTasksByTypeFilters,
{ filter, value },
{},
[
{
type: types.SET_TASKS_BY_TYPE_FILTERS,
payload: { filter, value },
},
],
[
{
type: 'fetchTopRankedGroupLabels',
},
],
done,
);
});
});
describe('createCustomStage', () => {
describe('with valid data', () => {
const customStageData = {
......
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
import * as rootGetters from 'ee/analytics/cycle_analytics/store/getters';
import * as getters from 'ee/analytics/cycle_analytics/store/modules/type_of_work/getters';
import * as actions from 'ee/analytics/cycle_analytics/store/modules/type_of_work/actions';
import * as types from 'ee/analytics/cycle_analytics/store/modules/type_of_work/mutation_types';
import {
TASKS_BY_TYPE_FILTERS,
TASKS_BY_TYPE_SUBJECT_ISSUE,
} from 'ee/analytics/cycle_analytics/constants';
import httpStatusCodes from '~/lib/utils/http_status';
import { groupLabels, endpoints, startDate, endDate } from '../../../mock_data';
import { shouldFlashAMessage } from '../../../helpers';
const error = new Error(`Request failed with status code ${httpStatusCodes.NOT_FOUND}`);
describe('Type of work actions', () => {
let mock;
let state = {
isLoadingTasksByTypeChart: false,
isLoadingTasksByTypeChartTopLabels: false,
subject: TASKS_BY_TYPE_SUBJECT_ISSUE,
topRankedLabels: [],
selectedLabelIds: [],
data: [],
};
const mockedState = {
...rootGetters,
...getters,
...state,
rootState: { startDate, endDate },
};
beforeEach(() => {
mock = new MockAdapter(axios);
});
afterEach(() => {
mock.restore();
state = { ...mockedState, selectedGroup: null };
});
describe('fetchTopRankedGroupLabels', () => {
beforeEach(() => {
gon.api_version = 'v4';
state = { ...mockedState, subject: TASKS_BY_TYPE_SUBJECT_ISSUE };
});
describe('succeeds', () => {
beforeEach(() => {
mock.onGet(endpoints.tasksByTypeTopLabelsData).replyOnce(200, groupLabels);
});
it('dispatches receiveTopRankedGroupLabelsSuccess if the request succeeds', () => {
return testAction(
actions.fetchTopRankedGroupLabels,
null,
state,
[{ type: 'REQUEST_TOP_RANKED_GROUP_LABELS' }],
[{ type: 'receiveTopRankedGroupLabelsSuccess', payload: groupLabels }],
);
});
describe('receiveTopRankedGroupLabelsSuccess', () => {
beforeEach(() => {
setFixtures('<div class="flash-container"></div>');
});
it(`commits the ${types.RECEIVE_TOP_RANKED_GROUP_LABELS_SUCCESS} mutation and dispatches the 'fetchTasksByTypeData' action`, () => {
return testAction(
actions.receiveTopRankedGroupLabelsSuccess,
null,
state,
[
{
type: types.RECEIVE_TOP_RANKED_GROUP_LABELS_SUCCESS,
payload: null,
},
],
[{ type: 'fetchTasksByTypeData' }],
);
});
});
});
describe('with an error', () => {
beforeEach(() => {
mock.onGet(endpoints.fetchTopRankedGroupLabels).replyOnce(404);
});
it('dispatches receiveTopRankedGroupLabelsError if the request fails', () => {
return testAction(
actions.fetchTopRankedGroupLabels,
null,
state,
[{ type: 'REQUEST_TOP_RANKED_GROUP_LABELS' }],
[{ type: 'receiveTopRankedGroupLabelsError', payload: error }],
);
});
});
describe('receiveTopRankedGroupLabelsError', () => {
beforeEach(() => {
setFixtures('<div class="flash-container"></div>');
});
it('flashes an error message if the request fails', () => {
actions.receiveTopRankedGroupLabelsError({
commit: () => {},
});
shouldFlashAMessage('There was an error fetching the top labels for the selected group');
});
});
});
describe('setTasksByTypeFilters', () => {
const filter = TASKS_BY_TYPE_FILTERS.SUBJECT;
const value = 'issue';
it(`commits the ${types.SET_TASKS_BY_TYPE_FILTERS} mutation and dispatches 'fetchTasksByTypeData'`, () => {
return testAction(
actions.setTasksByTypeFilters,
{ filter, value },
{},
[
{
type: types.SET_TASKS_BY_TYPE_FILTERS,
payload: { filter, value },
},
],
[
{
type: 'fetchTasksByTypeData',
},
],
);
});
});
});
import { tasksByTypeChartData } from 'ee/analytics/cycle_analytics/store/modules/type_of_work/getters';
import {
rawTasksByTypeData,
transformedTasksByTypeData,
startDate,
endDate,
} from '../../../mock_data';
describe('Type of work getters', () => {
describe('tasksByTypeChartData', () => {
const rootState = { startDate, endDate };
describe('with data', () => {
it('correctly transforms the raw task by type data', () => {
expect(tasksByTypeChartData(rawTasksByTypeData, null, rootState)).toEqual(
transformedTasksByTypeData,
);
});
});
describe('with no data', () => {
it('returns all required properties', () => {
expect(tasksByTypeChartData()).toEqual({ groupBy: [], data: [], seriesNames: [] });
});
});
});
});
import mutations from 'ee/analytics/cycle_analytics/store/modules/type_of_work/mutations';
import * as types from 'ee/analytics/cycle_analytics/store/modules/type_of_work/mutation_types';
import { TASKS_BY_TYPE_FILTERS } from 'ee/analytics/cycle_analytics/constants';
import { apiTasksByTypeData, rawTasksByTypeData } from '../../../mock_data';
let state = null;
describe('Cycle analytics mutations', () => {
beforeEach(() => {
state = {};
});
afterEach(() => {
state = null;
});
it.each`
mutation | stateKey | value
${types.REQUEST_TOP_RANKED_GROUP_LABELS} | ${'topRankedLabels'} | ${[]}
${types.RECEIVE_TOP_RANKED_GROUP_LABELS_ERROR} | ${'topRankedLabels'} | ${[]}
${types.REQUEST_TASKS_BY_TYPE_DATA} | ${'isLoadingTasksByTypeChart'} | ${true}
${types.RECEIVE_TASKS_BY_TYPE_DATA_ERROR} | ${'isLoadingTasksByTypeChart'} | ${false}
`('$mutation will set $stateKey=$value', ({ mutation, stateKey, value }) => {
mutations[mutation](state);
expect(state[stateKey]).toEqual(value);
});
describe(`${types.RECEIVE_TASKS_BY_TYPE_DATA_SUCCESS}`, () => {
it('sets isLoadingTasksByTypeChart to false', () => {
mutations[types.RECEIVE_TASKS_BY_TYPE_DATA_SUCCESS](state, {});
expect(state.isLoadingTasksByTypeChart).toEqual(false);
});
it('sets data to the raw returned chart data', () => {
state = { data: null };
mutations[types.RECEIVE_TASKS_BY_TYPE_DATA_SUCCESS](state, apiTasksByTypeData);
expect(state.data).toEqual(rawTasksByTypeData);
});
});
describe(`${types.SET_TASKS_BY_TYPE_FILTERS}`, () => {
it('will update the tasksByType state key', () => {
state = {};
const subjectFilter = { filter: TASKS_BY_TYPE_FILTERS.SUBJECT, value: 'cool-subject' };
mutations[types.SET_TASKS_BY_TYPE_FILTERS](state, subjectFilter);
expect(state.subject).toEqual('cool-subject');
});
it('will toggle the specified label id in the selectedLabelIds state key', () => {
state = {
selectedLabelIds: [10, 20, 30],
};
const labelFilter = { filter: TASKS_BY_TYPE_FILTERS.LABEL, value: 20 };
mutations[types.SET_TASKS_BY_TYPE_FILTERS](state, labelFilter);
expect(state.selectedLabelIds).toEqual([10, 30]);
mutations[types.SET_TASKS_BY_TYPE_FILTERS](state, labelFilter);
expect(state.selectedLabelIds).toEqual([10, 30, 20]);
});
});
});
import mutations from 'ee/analytics/cycle_analytics/store/mutations';
import * as types from 'ee/analytics/cycle_analytics/store/mutation_types';
import { TASKS_BY_TYPE_FILTERS } from 'ee/analytics/cycle_analytics/constants';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import {
......@@ -15,8 +14,6 @@ import {
startDate,
endDate,
customizableStagesAndEvents,
rawTasksByTypeData,
transformedTasksByTypeData,
selectedProjects,
} from '../mock_data';
......@@ -47,8 +44,6 @@ describe('Cycle analytics mutations', () => {
${types.RECEIVE_STAGE_DATA_ERROR} | ${'isEmptyStage'} | ${true}
${types.RECEIVE_STAGE_DATA_ERROR} | ${'isLoadingStage'} | ${false}
${types.REQUEST_CYCLE_ANALYTICS_DATA} | ${'isLoading'} | ${true}
${types.REQUEST_TOP_RANKED_GROUP_LABELS} | ${'topRankedLabels'} | ${[]}
${types.RECEIVE_TOP_RANKED_GROUP_LABELS_ERROR} | ${'topRankedLabels'} | ${[]}
${types.RECEIVE_GROUP_STAGES_AND_EVENTS_ERROR} | ${'stages'} | ${[]}
${types.REQUEST_GROUP_STAGES_AND_EVENTS} | ${'stages'} | ${[]}
${types.RECEIVE_GROUP_STAGES_AND_EVENTS_ERROR} | ${'customStageFormEvents'} | ${[]}
......@@ -57,8 +52,6 @@ describe('Cycle analytics mutations', () => {
${types.RECEIVE_CREATE_CUSTOM_STAGE_SUCCESS} | ${'isSavingCustomStage'} | ${false}
${types.RECEIVE_CREATE_CUSTOM_STAGE_ERROR} | ${'isSavingCustomStage'} | ${false}
${types.RECEIVE_CREATE_CUSTOM_STAGE_ERROR} | ${'customStageFormErrors'} | ${{}}
${types.REQUEST_TASKS_BY_TYPE_DATA} | ${'isLoadingTasksByTypeChart'} | ${true}
${types.RECEIVE_TASKS_BY_TYPE_DATA_ERROR} | ${'isLoadingTasksByTypeChart'} | ${false}
${types.REQUEST_UPDATE_STAGE} | ${'isLoading'} | ${true}
${types.REQUEST_UPDATE_STAGE} | ${'isSavingCustomStage'} | ${true}
${types.REQUEST_UPDATE_STAGE} | ${'customStageFormErrors'} | ${null}
......@@ -176,21 +169,6 @@ describe('Cycle analytics mutations', () => {
});
});
describe(`${types.RECEIVE_TASKS_BY_TYPE_DATA_SUCCESS}`, () => {
it('sets isLoadingTasksByTypeChart to false', () => {
mutations[types.RECEIVE_TASKS_BY_TYPE_DATA_SUCCESS](state, {});
expect(state.isLoadingTasksByTypeChart).toEqual(false);
});
it('sets tasksByType.data to the raw returned chart data', () => {
state = { tasksByType: { data: null } };
mutations[types.RECEIVE_TASKS_BY_TYPE_DATA_SUCCESS](state, rawTasksByTypeData);
expect(state.tasksByType.data).toEqual(transformedTasksByTypeData);
});
});
describe(`${types.RECEIVE_STAGE_MEDIANS_SUCCESS}`, () => {
it('sets each id as a key in the median object with the corresponding value', () => {
const stateWithData = {
......@@ -206,29 +184,6 @@ describe('Cycle analytics mutations', () => {
});
});
describe(`${types.SET_TASKS_BY_TYPE_FILTERS}`, () => {
it('will update the tasksByType state key', () => {
state = { tasksByType: {} };
const subjectFilter = { filter: TASKS_BY_TYPE_FILTERS.SUBJECT, value: 'cool-subject' };
mutations[types.SET_TASKS_BY_TYPE_FILTERS](state, subjectFilter);
expect(state.tasksByType).toEqual({ subject: 'cool-subject' });
});
it('will toggle the specified label id in the tasksByType.selectedLabelIds state key', () => {
state = {
tasksByType: { selectedLabelIds: [10, 20, 30] },
};
const labelFilter = { filter: TASKS_BY_TYPE_FILTERS.LABEL, value: 20 };
mutations[types.SET_TASKS_BY_TYPE_FILTERS](state, labelFilter);
expect(state.tasksByType).toEqual({ selectedLabelIds: [10, 30] });
mutations[types.SET_TASKS_BY_TYPE_FILTERS](state, labelFilter);
expect(state.tasksByType).toEqual({ selectedLabelIds: [10, 30, 20] });
});
});
describe(`${types.INITIALIZE_CYCLE_ANALYTICS}`, () => {
const initialData = {
group: { fullPath: 'cool-group' },
......
......@@ -32,7 +32,7 @@ import {
endDate,
issueStage,
rawCustomStage,
transformedTasksByTypeData,
rawTasksByTypeData,
} from './mock_data';
const labelEventIds = labelEvents.map(ev => ev.identifier);
......@@ -239,9 +239,9 @@ describe('Cycle analytics utils', () => {
const groupBy = getDatesInRange(startDate, endDate, toYmd);
// only return the values, drop the date which is the first paramater
const extractSeriesValues = ({ series }) => series.map(kv => kv[1]);
const data = transformedTasksByTypeData.map(extractSeriesValues);
const data = rawTasksByTypeData.map(extractSeriesValues);
const labels = transformedTasksByTypeData.map(d => {
const labels = rawTasksByTypeData.map(d => {
const { label } = d;
return label.title;
});
......@@ -257,7 +257,7 @@ describe('Cycle analytics utils', () => {
describe('with data', () => {
beforeEach(() => {
transformed = getTasksByTypeData({ data: transformedTasksByTypeData, startDate, endDate });
transformed = getTasksByTypeData({ data: rawTasksByTypeData, startDate, endDate });
});
it('will return an object with the properties needed for the chart', () => {
......
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