Commit b5afaac5 authored by Ezekiel Kigbo's avatar Ezekiel Kigbo

Map type of work vuex to module

Replaces the state / getters
in the base.vue with their respective
code from the tasks by type module

Update vuex and base specs

Fix utils_spec test
parent 2ddaad86
...@@ -52,8 +52,6 @@ export default { ...@@ -52,8 +52,6 @@ export default {
'featureFlags', 'featureFlags',
'isLoading', 'isLoading',
'isLoadingStage', 'isLoadingStage',
'isLoadingTasksByTypeChart',
'isLoadingTasksByTypeChartTopLabels',
'isEmptyStage', 'isEmptyStage',
'isSavingCustomStage', 'isSavingCustomStage',
'isCreatingCustomStage', 'isCreatingCustomStage',
...@@ -63,25 +61,30 @@ export default { ...@@ -63,25 +61,30 @@ export default {
'selectedStage', 'selectedStage',
'stages', 'stages',
'summary', 'summary',
'topRankedLabels',
'currentStageEvents', 'currentStageEvents',
'customStageFormEvents', 'customStageFormEvents',
'errorCode', 'errorCode',
'startDate', 'startDate',
'endDate', 'endDate',
'tasksByType',
'medians', 'medians',
'customStageFormErrors', 'customStageFormErrors',
]), ]),
...mapState('typeOfWork', [
'isLoadingTasksByTypeChart',
'isLoadingTasksByTypeChartTopLabels',
'topRankedLabels',
'subject',
'selectedLabelIds',
]),
...mapGetters([ ...mapGetters([
'hasNoAccessError', 'hasNoAccessError',
'currentGroupPath', 'currentGroupPath',
'tasksByTypeChartData',
'activeStages', 'activeStages',
'selectedProjectIds', 'selectedProjectIds',
'enableCustomOrdering', 'enableCustomOrdering',
'cycleAnalyticsRequestParams', 'cycleAnalyticsRequestParams',
]), ]),
...mapGetters('typeOfWork', ['tasksByTypeChartData']),
shouldRenderEmptyState() { shouldRenderEmptyState() {
return !this.selectedGroup; return !this.selectedGroup;
}, },
...@@ -95,12 +98,10 @@ export default { ...@@ -95,12 +98,10 @@ export default {
return this.featureFlags.hasDurationChart && !this.hasNoAccessError && !this.isLoading; return this.featureFlags.hasDurationChart && !this.hasNoAccessError && !this.isLoading;
}, },
shouldDisplayTypeOfWorkCharts() { shouldDisplayTypeOfWorkCharts() {
return this.featureFlags.hasTasksByTypeChart && !this.hasNoAccessError; return this.featureFlags.hasTasksByTypeChart && !this.hasNoAccessError && !this.isLoading;
}, },
isLoadingTypeOfWork() { isLoadingTypeOfWork() {
return ( return this.isLoadingTasksByTypeChartTopLabels || this.isLoadingTasksByTypeChart;
this.isLoading || this.isLoadingTasksByTypeChartTopLabels || this.isLoadingTasksByTypeChart
);
}, },
hasDateRangeSet() { hasDateRangeSet() {
return this.startDate && this.endDate; return this.startDate && this.endDate;
...@@ -111,7 +112,8 @@ export default { ...@@ -111,7 +112,8 @@ export default {
startDate, startDate,
endDate, endDate,
selectedProjectIds, selectedProjectIds,
tasksByType: { subject, selectedLabelIds }, subject,
selectedLabelIds,
} = this; } = this;
return { return {
selectedGroup, selectedGroup,
...@@ -152,16 +154,15 @@ export default { ...@@ -152,16 +154,15 @@ export default {
'showCustomStageForm', 'showCustomStageForm',
'showEditCustomStageForm', 'showEditCustomStageForm',
'setDateRange', 'setDateRange',
'fetchTasksByTypeData',
'createCustomStage', 'createCustomStage',
'updateStage', 'updateStage',
'removeStage', 'removeStage',
'setFeatureFlags', 'setFeatureFlags',
'clearCustomStageFormErrors', 'clearCustomStageFormErrors',
'updateStage', 'updateStage',
'setTasksByTypeFilters',
'reorderStage', 'reorderStage',
]), ]),
...mapActions('typeOfWork', ['setTasksByTypeFilters']),
onGroupSelect(group) { onGroupSelect(group) {
this.setSelectedGroup(group); this.setSelectedGroup(group);
this.fetchCycleAnalyticsData(); this.fetchCycleAnalyticsData();
......
...@@ -3,14 +3,7 @@ import createFlash from '~/flash'; ...@@ -3,14 +3,7 @@ import createFlash from '~/flash';
import { __, sprintf } from '~/locale'; import { __, sprintf } from '~/locale';
import httpStatus from '~/lib/utils/http_status'; import httpStatus from '~/lib/utils/http_status';
import * as types from './mutation_types'; import * as types from './mutation_types';
import { removeFlash } from '../utils'; import { removeFlash, handleErrorOrRethrow } from '../utils';
const handleErrorOrRethrow = ({ action, error }) => {
if (error?.response?.status === httpStatus.FORBIDDEN) {
throw error;
}
action();
};
const isStageNameExistsError = ({ status, errors }) => { const isStageNameExistsError = ({ status, errors }) => {
const ERROR_NAME_RESERVED = 'is reserved'; const ERROR_NAME_RESERVED = 'is reserved';
...@@ -105,8 +98,10 @@ export const fetchStageMedianValues = ({ state, dispatch, getters }) => { ...@@ -105,8 +98,10 @@ export const fetchStageMedianValues = ({ state, dispatch, getters }) => {
}; };
export const requestCycleAnalyticsData = ({ commit }) => commit(types.REQUEST_CYCLE_ANALYTICS_DATA); export const requestCycleAnalyticsData = ({ commit }) => commit(types.REQUEST_CYCLE_ANALYTICS_DATA);
export const receiveCycleAnalyticsDataSuccess = ({ commit }) => export const receiveCycleAnalyticsDataSuccess = ({ commit, dispatch }) => {
commit(types.RECEIVE_CYCLE_ANALYTICS_DATA_SUCCESS); commit(types.RECEIVE_CYCLE_ANALYTICS_DATA_SUCCESS);
dispatch('typeOfWork/fetchTopRankedGroupLabels');
};
export const receiveCycleAnalyticsDataError = ({ commit }, { response }) => { export const receiveCycleAnalyticsDataError = ({ commit }, { response }) => {
const { status } = response; const { status } = response;
......
...@@ -2,7 +2,6 @@ import dateFormat from 'dateformat'; ...@@ -2,7 +2,6 @@ import dateFormat from 'dateformat';
import { isNumber } from 'lodash'; import { isNumber } from 'lodash';
import httpStatus from '~/lib/utils/http_status'; import httpStatus from '~/lib/utils/http_status';
import { dateFormats } from '../../shared/constants'; import { dateFormats } from '../../shared/constants';
import { getTasksByTypeData } from '../utils';
export const hasNoAccessError = state => state.errorCode === httpStatus.FORBIDDEN; export const hasNoAccessError = state => state.errorCode === httpStatus.FORBIDDEN;
......
import Api from 'ee/api'; import Api from 'ee/api';
import createFlash from '~/flash'; import createFlash from '~/flash';
import { __ } from '~/locale'; import { __ } from '~/locale';
import httpStatus from '~/lib/utils/http_status';
import * as types from './mutation_types'; import * as types from './mutation_types';
import { handleErrorOrRethrow } from '../../../utils';
const handleErrorOrRethrow = ({ action, error }) => {
if (error?.response?.status === httpStatus.FORBIDDEN) {
throw error;
}
action();
};
export const receiveTopRankedGroupLabelsSuccess = ({ commit, dispatch }, data) => { export const receiveTopRankedGroupLabelsSuccess = ({ commit, dispatch }, data) => {
commit(types.RECEIVE_TOP_RANKED_GROUP_LABELS_SUCCESS, data); commit(types.RECEIVE_TOP_RANKED_GROUP_LABELS_SUCCESS, data);
...@@ -21,19 +14,13 @@ export const receiveTopRankedGroupLabelsError = ({ commit }, error) => { ...@@ -21,19 +14,13 @@ export const receiveTopRankedGroupLabelsError = ({ commit }, error) => {
createFlash(__('There was an error fetching the top labels for the selected group')); createFlash(__('There was an error fetching the top labels for the selected group'));
}; };
export const requestTopRankedGroupLabels = ({ commit }) => export const fetchTopRankedGroupLabels = ({ dispatch, commit, state, rootGetters }) => {
commit(types.REQUEST_TOP_RANKED_GROUP_LABELS); commit(types.REQUEST_TOP_RANKED_GROUP_LABELS);
const {
export const fetchTopRankedGroupLabels = ({
dispatch,
state,
getters: {
currentGroupPath, currentGroupPath,
cycleAnalyticsRequestParams: { created_after, created_before }, cycleAnalyticsRequestParams: { created_after, created_before },
}, } = rootGetters;
}) => { const { subject } = state;
dispatch('requestTopRankedGroupLabels');
const { subject } = state.tasksByType;
return Api.cycleAnalyticsTopLabels(currentGroupPath, { return Api.cycleAnalyticsTopLabels(currentGroupPath, {
subject, subject,
...@@ -49,29 +36,21 @@ export const fetchTopRankedGroupLabels = ({ ...@@ -49,29 +36,21 @@ export const fetchTopRankedGroupLabels = ({
); );
}; };
export const receiveTasksByTypeDataSuccess = ({ commit }, data) => {
commit(types.RECEIVE_TASKS_BY_TYPE_DATA_SUCCESS, data);
};
export const receiveTasksByTypeDataError = ({ commit }, error) => { export const receiveTasksByTypeDataError = ({ commit }, error) => {
commit(types.RECEIVE_TASKS_BY_TYPE_DATA_ERROR, error); commit(types.RECEIVE_TASKS_BY_TYPE_DATA_ERROR, error);
createFlash(__('There was an error fetching data for the tasks by type chart')); createFlash(__('There was an error fetching data for the tasks by type chart'));
}; };
export const requestTasksByTypeData = ({ commit }) => commit(types.REQUEST_TASKS_BY_TYPE_DATA); export const fetchTasksByTypeData = ({ dispatch, commit, state, rootGetters }) => {
export const fetchTasksByTypeData = ({ dispatch, state, getters }) => {
const { const {
currentGroupPath, currentGroupPath,
cycleAnalyticsRequestParams: { created_after, created_before, project_ids }, cycleAnalyticsRequestParams: { created_after, created_before, project_ids },
} = getters; } = rootGetters;
const { const { subject, selectedLabelIds } = state;
tasksByType: { subject, selectedLabelIds },
} = state;
// ensure we clear any chart data currently in state // ensure we clear any chart data currently in state
dispatch('requestTasksByTypeData'); commit(types.REQUEST_TASKS_BY_TYPE_DATA);
// dont request if we have no labels selected...for now // dont request if we have no labels selected...for now
if (selectedLabelIds.length) { if (selectedLabelIds.length) {
...@@ -84,27 +63,13 @@ export const fetchTasksByTypeData = ({ dispatch, state, getters }) => { ...@@ -84,27 +63,13 @@ export const fetchTasksByTypeData = ({ dispatch, state, getters }) => {
}; };
return Api.cycleAnalyticsTasksByType(currentGroupPath, params) return Api.cycleAnalyticsTasksByType(currentGroupPath, params)
.then(({ data }) => dispatch('receiveTasksByTypeDataSuccess', data)) .then(({ data }) => commit(types.RECEIVE_TASKS_BY_TYPE_DATA_SUCCESS, data))
.catch(error => dispatch('receiveTasksByTypeDataError', error)); .catch(error => dispatch('receiveTasksByTypeDataError', error));
} }
return dispatch('receiveTasksByTypeDataSuccess', []); return commit(types.RECEIVE_TASKS_BY_TYPE_DATA_SUCCESS, []);
}; };
export const setTasksByTypeFilters = ({ dispatch, commit }, data) => { export const setTasksByTypeFilters = ({ dispatch, commit }, data) => {
commit(types.SET_TASKS_BY_TYPE_FILTERS, data); commit(types.SET_TASKS_BY_TYPE_FILTERS, data);
dispatch('fetchTopRankedGroupLabels'); dispatch('fetchTasksByTypeData');
};
export const initializeCycleAnalyticsSuccess = ({ commit }) =>
commit(types.INITIALIZE_CYCLE_ANALYTICS_SUCCESS);
export const initializeCycleAnalytics = ({ dispatch, commit }, initialData = {}) => {
commit(types.INITIALIZE_CYCLE_ANALYTICS, initialData);
if (initialData?.group?.fullPath) {
return dispatch('fetchCycleAnalyticsData').then(() =>
dispatch('initializeCycleAnalyticsSuccess'),
);
}
return dispatch('initializeCycleAnalyticsSuccess');
}; };
import { getTasksByTypeData } from '../../../utils'; import { getTasksByTypeData } from '../../../utils';
export const tasksByTypeChartData = ({ tasksByType, startDate, endDate }) => { export const tasksByTypeChartData = ({ data = [] } = {}, _, rootState = {}) => {
if (tasksByType && tasksByType.data.length) { const { startDate = null, endDate = null } = rootState;
return getTasksByTypeData({ return data.length
data: tasksByType.data, ? getTasksByTypeData({
data,
startDate, startDate,
endDate, endDate,
}); })
} : { groupBy: [], data: [], seriesNames: [] };
return { groupBy: [], data: [], seriesNames: [] };
}; };
export default { tasksByTypeChartData }; export default () => ({ tasksByTypeChartData });
...@@ -2,11 +2,10 @@ export const REQUEST_TOP_RANKED_GROUP_LABELS = 'REQUEST_TOP_RANKED_GROUP_LABELS' ...@@ -2,11 +2,10 @@ 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_SUCCESS = 'RECEIVE_TOP_RANKED_GROUP_LABELS_SUCCESS';
export const RECEIVE_TOP_RANKED_GROUP_LABELS_ERROR = 'RECEIVE_TOP_RANKED_GROUP_LABELS_ERROR'; export const RECEIVE_TOP_RANKED_GROUP_LABELS_ERROR = 'RECEIVE_TOP_RANKED_GROUP_LABELS_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 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_SUCCESS = 'RECEIVE_TASKS_BY_TYPE_DATA_SUCCESS';
export const RECEIVE_TASKS_BY_TYPE_DATA_ERROR = 'RECEIVE_TASKS_BY_TYPE_DATA_ERROR'; 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'; export const SET_TASKS_BY_TYPE_FILTERS = 'SET_TASKS_BY_TYPE_FILTERS';
...@@ -7,28 +7,17 @@ export default { ...@@ -7,28 +7,17 @@ export default {
[types.REQUEST_TOP_RANKED_GROUP_LABELS](state) { [types.REQUEST_TOP_RANKED_GROUP_LABELS](state) {
state.isLoadingTasksByTypeChartTopLabels = true; state.isLoadingTasksByTypeChartTopLabels = true;
state.topRankedLabels = []; state.topRankedLabels = [];
state.tasksByType = { state.selectedLabelIds = [];
...state.tasksByType,
selectedLabelIds: [],
};
}, },
[types.RECEIVE_TOP_RANKED_GROUP_LABELS_SUCCESS](state, data = []) { [types.RECEIVE_TOP_RANKED_GROUP_LABELS_SUCCESS](state, data = []) {
const { tasksByType } = state;
state.isLoadingTasksByTypeChartTopLabels = false; state.isLoadingTasksByTypeChartTopLabels = false;
state.topRankedLabels = data.map(convertObjectPropsToCamelCase); state.topRankedLabels = data.map(convertObjectPropsToCamelCase);
state.tasksByType = { state.selectedLabelIds = data.map(({ id }) => id);
...tasksByType,
selectedLabelIds: data.map(({ id }) => id),
};
}, },
[types.RECEIVE_TOP_RANKED_GROUP_LABELS_ERROR](state) { [types.RECEIVE_TOP_RANKED_GROUP_LABELS_ERROR](state) {
const { tasksByType } = state;
state.isLoadingTasksByTypeChartTopLabels = false; state.isLoadingTasksByTypeChartTopLabels = false;
state.topRankedLabels = []; state.topRankedLabels = [];
state.tasksByType = { state.selectedLabelIds = [];
...tasksByType,
selectedLabelIds: [],
};
}, },
[types.REQUEST_TASKS_BY_TYPE_DATA](state) { [types.REQUEST_TASKS_BY_TYPE_DATA](state) {
state.isLoadingTasksByTypeChart = true; state.isLoadingTasksByTypeChart = true;
...@@ -38,28 +27,19 @@ export default { ...@@ -38,28 +27,19 @@ export default {
}, },
[types.RECEIVE_TASKS_BY_TYPE_DATA_SUCCESS](state, data = []) { [types.RECEIVE_TASKS_BY_TYPE_DATA_SUCCESS](state, data = []) {
state.isLoadingTasksByTypeChart = false; state.isLoadingTasksByTypeChart = false;
state.tasksByType = { state.data = transformRawTasksByTypeData(data);
...state.tasksByType,
data: transformRawTasksByTypeData(data),
};
}, },
[types.SET_TASKS_BY_TYPE_FILTERS](state, { filter, value }) { [types.SET_TASKS_BY_TYPE_FILTERS](state, { filter, value }) {
const { const { selectedLabelIds } = state;
tasksByType: { selectedLabelIds, ...tasksByTypeRest },
} = state;
let updatedFilter = {};
switch (filter) { switch (filter) {
case TASKS_BY_TYPE_FILTERS.LABEL: case TASKS_BY_TYPE_FILTERS.LABEL:
updatedFilter = { state.selectedLabelIds = toggleSelectedLabel({ selectedLabelIds, value });
selectedLabelIds: toggleSelectedLabel({ selectedLabelIds, value }),
};
break; break;
case TASKS_BY_TYPE_FILTERS.SUBJECT: case TASKS_BY_TYPE_FILTERS.SUBJECT:
updatedFilter = { subject: value }; state.subject = value;
break; break;
default: default:
break; break;
} }
state.tasksByType = { ...tasksByTypeRest, selectedLabelIds, ...updatedFilter };
}, },
}; };
...@@ -5,7 +5,7 @@ export default () => ({ ...@@ -5,7 +5,7 @@ export default () => ({
isLoadingTasksByTypeChartTopLabels: false, isLoadingTasksByTypeChartTopLabels: false,
subject: TASKS_BY_TYPE_SUBJECT_ISSUE, subject: TASKS_BY_TYPE_SUBJECT_ISSUE,
topRankedLabels: [],
selectedLabelIds: [], selectedLabelIds: [],
topRankedLabels: [],
data: [], data: [],
}); });
import { isNumber } from 'lodash'; import { isNumber } from 'lodash';
import dateFormat from 'dateformat'; import dateFormat from 'dateformat';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import httpStatus from '~/lib/utils/http_status';
import { convertToSnakeCase } from '~/lib/utils/text_utility'; import { convertToSnakeCase } from '~/lib/utils/text_utility';
import { hideFlash } from '~/flash'; import { hideFlash } from '~/flash';
import { import {
...@@ -329,3 +330,10 @@ export const getTasksByTypeData = ({ data = [], startDate = null, endDate = null ...@@ -329,3 +330,10 @@ export const getTasksByTypeData = ({ data = [], startDate = null, endDate = null
groupBy, groupBy,
}; };
}; };
export const handleErrorOrRethrow = ({ action, error }) => {
if (error?.response?.status === httpStatus.FORBIDDEN) {
throw error;
}
action();
};
import { createLocalVue, shallowMount, mount } from '@vue/test-utils'; import { createLocalVue, shallowMount, mount } from '@vue/test-utils';
import Vuex from 'vuex'; import Vuex from 'vuex';
import Vue from 'vue';
import store from 'ee/analytics/cycle_analytics/store'; import store from 'ee/analytics/cycle_analytics/store';
import Component from 'ee/analytics/cycle_analytics/components/base.vue'; import Component from 'ee/analytics/cycle_analytics/components/base.vue';
import { GlEmptyState } from '@gitlab/ui'; import { GlEmptyState } from '@gitlab/ui';
...@@ -291,16 +290,15 @@ describe('Cycle Analytics component', () => { ...@@ -291,16 +290,15 @@ describe('Cycle Analytics component', () => {
expect(second.classes('active')).toBe(false); expect(second.classes('active')).toBe(false);
}); });
it('can navigate to different stages', done => { it('can navigate to different stages', () => {
selectStageNavItem(2).trigger('click'); selectStageNavItem(2).trigger('click');
Vue.nextTick(() => { return wrapper.vm.$nextTick().then(() => {
const first = selectStageNavItem(0); const first = selectStageNavItem(0);
const third = selectStageNavItem(2); const third = selectStageNavItem(2);
expect(third.classes('active')).toBe(true); expect(third.classes('active')).toBe(true);
expect(first.classes('active')).toBe(false); expect(first.classes('active')).toBe(false);
done();
}); });
}); });
}); });
......
...@@ -10,7 +10,10 @@ import { ...@@ -10,7 +10,10 @@ import {
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { getDateInPast, getDatesInRange } from '~/lib/utils/datetime_utility'; import { getDateInPast, getDatesInRange } from '~/lib/utils/datetime_utility';
import { toYmd } from 'ee/analytics/shared/utils'; 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 = { const fixtureEndpoints = {
customizableCycleAnalyticsStagesAndEvents: 'analytics/value_stream_analytics/stages.json', // customizable stages and events endpoint customizableCycleAnalyticsStagesAndEvents: 'analytics/value_stream_analytics/stages.json', // customizable stages and events endpoint
...@@ -145,7 +148,7 @@ export const customStageFormErrors = convertObjectPropsToCamelCase(rawCustomStag ...@@ -145,7 +148,7 @@ export const customStageFormErrors = convertObjectPropsToCamelCase(rawCustomStag
const dateRange = getDatesInRange(startDate, endDate, toYmd); 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 => { labelData => {
// add data points for our mock date range // add data points for our mock date range
const maxValue = 10; const maxValue = 10;
...@@ -157,7 +160,8 @@ export const rawTasksByTypeData = getJSONFixture('analytics/type_of_work/tasks_b ...@@ -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 = { export const tasksByTypeData = {
seriesNames: ['Cool label', 'Normal label'], seriesNames: ['Cool label', 'Normal label'],
......
...@@ -4,16 +4,11 @@ import testAction from 'helpers/vuex_action_helper'; ...@@ -4,16 +4,11 @@ import testAction from 'helpers/vuex_action_helper';
import * as getters from 'ee/analytics/cycle_analytics/store/getters'; import * as getters from 'ee/analytics/cycle_analytics/store/getters';
import * as actions from 'ee/analytics/cycle_analytics/store/actions'; import * as actions from 'ee/analytics/cycle_analytics/store/actions';
import * as types from 'ee/analytics/cycle_analytics/store/mutation_types'; 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 createFlash from '~/flash';
import httpStatusCodes from '~/lib/utils/http_status'; import httpStatusCodes from '~/lib/utils/http_status';
import { import {
selectedGroup, selectedGroup,
allowedStages as stages, allowedStages as stages,
groupLabels,
startDate, startDate,
endDate, endDate,
customizableStagesAndEvents, customizableStagesAndEvents,
...@@ -180,87 +175,6 @@ describe('Cycle analytics actions', () => { ...@@ -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', () => { describe('fetchCycleAnalyticsData', () => {
function mockFetchCycleAnalyticsAction(overrides = {}) { function mockFetchCycleAnalyticsAction(overrides = {}) {
const mocks = { const mocks = {
...@@ -845,31 +759,6 @@ describe('Cycle analytics actions', () => { ...@@ -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('createCustomStage', () => {
describe('with valid data', () => { describe('with valid data', () => {
const customStageData = { 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 mutations from 'ee/analytics/cycle_analytics/store/mutations';
import * as types from 'ee/analytics/cycle_analytics/store/mutation_types'; 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 { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { import {
...@@ -15,8 +14,6 @@ import { ...@@ -15,8 +14,6 @@ import {
startDate, startDate,
endDate, endDate,
customizableStagesAndEvents, customizableStagesAndEvents,
rawTasksByTypeData,
transformedTasksByTypeData,
selectedProjects, selectedProjects,
} from '../mock_data'; } from '../mock_data';
...@@ -47,8 +44,6 @@ describe('Cycle analytics mutations', () => { ...@@ -47,8 +44,6 @@ describe('Cycle analytics mutations', () => {
${types.RECEIVE_STAGE_DATA_ERROR} | ${'isEmptyStage'} | ${true} ${types.RECEIVE_STAGE_DATA_ERROR} | ${'isEmptyStage'} | ${true}
${types.RECEIVE_STAGE_DATA_ERROR} | ${'isLoadingStage'} | ${false} ${types.RECEIVE_STAGE_DATA_ERROR} | ${'isLoadingStage'} | ${false}
${types.REQUEST_CYCLE_ANALYTICS_DATA} | ${'isLoading'} | ${true} ${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.RECEIVE_GROUP_STAGES_AND_EVENTS_ERROR} | ${'stages'} | ${[]}
${types.REQUEST_GROUP_STAGES_AND_EVENTS} | ${'stages'} | ${[]} ${types.REQUEST_GROUP_STAGES_AND_EVENTS} | ${'stages'} | ${[]}
${types.RECEIVE_GROUP_STAGES_AND_EVENTS_ERROR} | ${'customStageFormEvents'} | ${[]} ${types.RECEIVE_GROUP_STAGES_AND_EVENTS_ERROR} | ${'customStageFormEvents'} | ${[]}
...@@ -57,8 +52,6 @@ describe('Cycle analytics mutations', () => { ...@@ -57,8 +52,6 @@ describe('Cycle analytics mutations', () => {
${types.RECEIVE_CREATE_CUSTOM_STAGE_SUCCESS} | ${'isSavingCustomStage'} | ${false} ${types.RECEIVE_CREATE_CUSTOM_STAGE_SUCCESS} | ${'isSavingCustomStage'} | ${false}
${types.RECEIVE_CREATE_CUSTOM_STAGE_ERROR} | ${'isSavingCustomStage'} | ${false} ${types.RECEIVE_CREATE_CUSTOM_STAGE_ERROR} | ${'isSavingCustomStage'} | ${false}
${types.RECEIVE_CREATE_CUSTOM_STAGE_ERROR} | ${'customStageFormErrors'} | ${{}} ${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} | ${'isLoading'} | ${true}
${types.REQUEST_UPDATE_STAGE} | ${'isSavingCustomStage'} | ${true} ${types.REQUEST_UPDATE_STAGE} | ${'isSavingCustomStage'} | ${true}
${types.REQUEST_UPDATE_STAGE} | ${'customStageFormErrors'} | ${null} ${types.REQUEST_UPDATE_STAGE} | ${'customStageFormErrors'} | ${null}
...@@ -176,21 +169,6 @@ describe('Cycle analytics mutations', () => { ...@@ -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}`, () => { describe(`${types.RECEIVE_STAGE_MEDIANS_SUCCESS}`, () => {
it('sets each id as a key in the median object with the corresponding value', () => { it('sets each id as a key in the median object with the corresponding value', () => {
const stateWithData = { const stateWithData = {
...@@ -206,29 +184,6 @@ describe('Cycle analytics mutations', () => { ...@@ -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}`, () => { describe(`${types.INITIALIZE_CYCLE_ANALYTICS}`, () => {
const initialData = { const initialData = {
group: { fullPath: 'cool-group' }, group: { fullPath: 'cool-group' },
......
...@@ -32,7 +32,7 @@ import { ...@@ -32,7 +32,7 @@ import {
endDate, endDate,
issueStage, issueStage,
rawCustomStage, rawCustomStage,
transformedTasksByTypeData, rawTasksByTypeData,
} from './mock_data'; } from './mock_data';
const labelEventIds = labelEvents.map(ev => ev.identifier); const labelEventIds = labelEvents.map(ev => ev.identifier);
...@@ -239,9 +239,9 @@ describe('Cycle analytics utils', () => { ...@@ -239,9 +239,9 @@ describe('Cycle analytics utils', () => {
const groupBy = getDatesInRange(startDate, endDate, toYmd); const groupBy = getDatesInRange(startDate, endDate, toYmd);
// only return the values, drop the date which is the first paramater // only return the values, drop the date which is the first paramater
const extractSeriesValues = ({ series }) => series.map(kv => kv[1]); 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; const { label } = d;
return label.title; return label.title;
}); });
...@@ -257,7 +257,7 @@ describe('Cycle analytics utils', () => { ...@@ -257,7 +257,7 @@ describe('Cycle analytics utils', () => {
describe('with data', () => { describe('with data', () => {
beforeEach(() => { 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', () => { 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