Commit 9a3b2b9c authored by Phil Hughes's avatar Phil Hughes

Merge branch...

Merge branch '197337-value-stream-analytics-move-type-of-work-charts-to-separate-module' into 'master'

Move tasks by type components into separate directory

Closes #197337

See merge request gitlab-org/gitlab!28522
parents 15a5a2ad e2ff9db4
...@@ -10,7 +10,7 @@ import { LAST_ACTIVITY_AT, DATE_RANGE_LIMIT } from '../../shared/constants'; ...@@ -10,7 +10,7 @@ import { LAST_ACTIVITY_AT, DATE_RANGE_LIMIT } from '../../shared/constants';
import DateRange from '../../shared/components/daterange.vue'; import DateRange from '../../shared/components/daterange.vue';
import StageTable from './stage_table.vue'; import StageTable from './stage_table.vue';
import DurationChart from './duration_chart.vue'; import DurationChart from './duration_chart.vue';
import TasksByTypeChart from './tasks_by_type_chart.vue'; import TypeOfWorkCharts from './type_of_work_charts.vue';
import UrlSyncMixin from '../../shared/mixins/url_sync_mixin'; import UrlSyncMixin from '../../shared/mixins/url_sync_mixin';
import { toYmd } from '../../shared/utils'; import { toYmd } from '../../shared/utils';
import RecentActivityCard from './recent_activity_card.vue'; import RecentActivityCard from './recent_activity_card.vue';
...@@ -25,7 +25,7 @@ export default { ...@@ -25,7 +25,7 @@ export default {
GroupsDropdownFilter, GroupsDropdownFilter,
ProjectsDropdownFilter, ProjectsDropdownFilter,
StageTable, StageTable,
TasksByTypeChart, TypeOfWorkCharts,
RecentActivityCard, RecentActivityCard,
}, },
mixins: [glFeatureFlagsMixin(), UrlSyncMixin], mixins: [glFeatureFlagsMixin(), UrlSyncMixin],
...@@ -53,6 +53,7 @@ export default { ...@@ -53,6 +53,7 @@ export default {
'isLoading', 'isLoading',
'isLoadingStage', 'isLoadingStage',
'isLoadingTasksByTypeChart', 'isLoadingTasksByTypeChart',
'isLoadingTasksByTypeChartTopLabels',
'isEmptyStage', 'isEmptyStage',
'isSavingCustomStage', 'isSavingCustomStage',
'isCreatingCustomStage', 'isCreatingCustomStage',
...@@ -93,11 +94,13 @@ export default { ...@@ -93,11 +94,13 @@ export default {
shouldDisplayDurationChart() { shouldDisplayDurationChart() {
return this.featureFlags.hasDurationChart && !this.hasNoAccessError && !this.isLoading; return this.featureFlags.hasDurationChart && !this.hasNoAccessError && !this.isLoading;
}, },
shouldDisplayTasksByTypeChart() { shouldDisplayTypeOfWorkCharts() {
return this.featureFlags.hasTasksByTypeChart && !this.hasNoAccessError; return this.featureFlags.hasTasksByTypeChart && !this.hasNoAccessError;
}, },
isTasksByTypeChartLoaded() { isLoadingTypeOfWork() {
return !this.isLoading && !this.isLoadingTasksByTypeChart; return (
this.isLoading || this.isLoadingTasksByTypeChartTopLabels || this.isLoadingTasksByTypeChart
);
}, },
hasDateRangeSet() { hasDateRangeSet() {
return this.startDate && this.endDate; return this.startDate && this.endDate;
...@@ -310,21 +313,14 @@ export default { ...@@ -310,21 +313,14 @@ export default {
/> />
</div> </div>
</div> </div>
<div v-if="shouldDisplayDurationChart" class="mt-3"> <duration-chart v-if="shouldDisplayDurationChart" class="mt-3" :stages="activeStages" />
<duration-chart :stages="activeStages" /> <type-of-work-charts
</div> v-if="shouldDisplayTypeOfWorkCharts"
<template v-if="shouldDisplayTasksByTypeChart"> :is-loading="isLoadingTypeOfWork"
<div class="js-tasks-by-type-chart"> :tasks-by-type-chart-data="tasksByTypeChartData"
<div v-if="isTasksByTypeChartLoaded"> :selected-tasks-by-type-filters="selectedTasksByTypeFilters"
<tasks-by-type-chart @updateFilter="setTasksByTypeFilters"
:chart-data="tasksByTypeChartData" />
:filters="selectedTasksByTypeFilters"
@updateFilter="setTasksByTypeFilters"
/>
</div>
<gl-loading-icon v-else size="md" class="my-4 py-4" />
</div>
</template>
</div> </div>
</div> </div>
</template> </template>
<script>
import { GlStackedColumnChart } from '@gitlab/ui/dist/charts';
export default {
name: 'TasksByTypeChart',
components: {
GlStackedColumnChart,
},
props: {
data: {
type: Array,
required: true,
},
groupBy: {
type: Array,
required: true,
},
seriesNames: {
type: Array,
required: true,
},
},
computed: {
hasData() {
return Boolean(this.data.length);
},
},
};
</script>
<template>
<gl-stacked-column-chart
v-if="hasData"
:data="data"
:group-by="groupBy"
x-axis-type="category"
y-axis-type="value"
:x-axis-title="__('Date')"
:y-axis-title="s__('CycleAnalytics|Number of tasks')"
:series-names="seriesNames"
/>
<div v-else class="bs-callout bs-callout-info">
<p>{{ __('There is no data available. Please change your selection.') }}</p>
</div>
</template>
...@@ -2,14 +2,14 @@ ...@@ -2,14 +2,14 @@
import { GlDropdownDivider, GlSegmentedControl, GlIcon } from '@gitlab/ui'; import { GlDropdownDivider, GlSegmentedControl, GlIcon } from '@gitlab/ui';
import { s__, sprintf } from '~/locale'; import { s__, sprintf } from '~/locale';
import createFlash from '~/flash'; import createFlash from '~/flash';
import { removeFlash } from '../utils'; import { removeFlash } from '../../utils';
import { import {
TASKS_BY_TYPE_FILTERS, TASKS_BY_TYPE_FILTERS,
TASKS_BY_TYPE_SUBJECT_ISSUE, TASKS_BY_TYPE_SUBJECT_ISSUE,
TASKS_BY_TYPE_SUBJECT_FILTER_OPTIONS, TASKS_BY_TYPE_SUBJECT_FILTER_OPTIONS,
TASKS_BY_TYPE_MAX_LABELS, TASKS_BY_TYPE_MAX_LABELS,
} from '../constants'; } from '../../constants';
import LabelsSelector from './labels_selector.vue'; import LabelsSelector from '../labels_selector.vue';
export default { export default {
name: 'TasksByTypeFilters', name: 'TasksByTypeFilters',
......
<script> <script>
import { GlStackedColumnChart } from '@gitlab/ui/dist/charts'; import { GlLoadingIcon } from '@gitlab/ui';
import TasksByTypeChart from './tasks_by_type/tasks_by_type_chart.vue';
import TasksByTypeFilters from './tasks_by_type/tasks_by_type_filters.vue';
import { s__, sprintf } from '~/locale'; import { s__, sprintf } from '~/locale';
import { formattedDate } from '../../shared/utils'; import { formattedDate } from '../../shared/utils';
import { TASKS_BY_TYPE_SUBJECT_ISSUE } from '../constants'; import { TASKS_BY_TYPE_SUBJECT_ISSUE } from '../constants';
import TasksByTypeFilters from './tasks_by_type_filters.vue';
export default { export default {
name: 'TasksByTypeChart', name: 'TypeOfWorkCharts',
components: { components: { GlLoadingIcon, TasksByTypeChart, TasksByTypeFilters },
GlStackedColumnChart,
TasksByTypeFilters,
},
props: { props: {
filters: { isLoading: {
type: Boolean,
required: true,
},
tasksByTypeChartData: {
type: Object, type: Object,
required: true, required: true,
}, },
chartData: { selectedTasksByTypeFilters: {
type: Object, type: Object,
required: true, required: true,
}, },
}, },
computed: { computed: {
hasData() {
return Boolean(this.chartData?.data?.length);
},
summaryDescription() { summaryDescription() {
const { const {
startDate, startDate,
endDate, endDate,
selectedProjectIds, selectedProjectIds,
selectedGroup: { name: groupName }, selectedGroup: { name: groupName },
} = this.filters; } = this.selectedTasksByTypeFilters;
const selectedProjectCount = selectedProjectIds.length; const selectedProjectCount = selectedProjectIds.length;
const str = const str =
...@@ -51,7 +50,7 @@ export default { ...@@ -51,7 +50,7 @@ export default {
}, },
selectedSubjectFilter() { selectedSubjectFilter() {
const { const {
filters: { subject }, selectedTasksByTypeFilters: { subject },
} = this; } = this;
return subject || TASKS_BY_TYPE_SUBJECT_ISSUE; return subject || TASKS_BY_TYPE_SUBJECT_ISSUE;
}, },
...@@ -59,29 +58,21 @@ export default { ...@@ -59,29 +58,21 @@ export default {
}; };
</script> </script>
<template> <template>
<div class="row"> <div class="js-tasks-by-type-chart row">
<div class="col-12"> <gl-loading-icon v-if="isLoading" size="md" class="col-12 my-4 py-4" />
<div v-else class="col-12">
<h3>{{ s__('CycleAnalytics|Type of work') }}</h3> <h3>{{ s__('CycleAnalytics|Type of work') }}</h3>
<div v-if="hasData"> <p>{{ summaryDescription }}</p>
<p>{{ summaryDescription }}</p> <tasks-by-type-filters
<tasks-by-type-filters :selected-label-ids="selectedTasksByTypeFilters.selectedLabelIds"
:selected-label-ids="filters.selectedLabelIds" :subject-filter="selectedSubjectFilter"
:subject-filter="selectedSubjectFilter" @updateFilter="$emit('updateFilter', $event)"
@updateFilter="$emit('updateFilter', $event)" />
/> <tasks-by-type-chart
<gl-stacked-column-chart :data="tasksByTypeChartData.data"
:data="chartData.data" :group-by="tasksByTypeChartData.groupBy"
:group-by="chartData.groupBy" :series-names="tasksByTypeChartData.seriesNames"
x-axis-type="category" />
y-axis-type="value"
:x-axis-title="__('Date')"
:y-axis-title="s__('CycleAnalytics|Number of tasks')"
:series-names="chartData.seriesNames"
/>
</div>
<div v-else class="bs-callout bs-callout-info">
<p>{{ __('There is no data available. Please change your selection.') }}</p>
</div>
</div> </div>
</div> </div>
</template> </template>
...@@ -295,18 +295,12 @@ export const createCustomStage = ({ dispatch, state }, data) => { ...@@ -295,18 +295,12 @@ export const createCustomStage = ({ dispatch, state }, data) => {
}); });
}; };
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, getters }) => {
export const fetchTasksByTypeData = ({ dispatch, state, getters }) => {
const { const {
currentGroupPath, currentGroupPath,
cycleAnalyticsRequestParams: { created_after, created_before, project_ids }, cycleAnalyticsRequestParams: { created_after, created_before, project_ids },
...@@ -316,6 +310,9 @@ export const fetchTasksByTypeData = ({ dispatch, state, getters }) => { ...@@ -316,6 +310,9 @@ export const fetchTasksByTypeData = ({ dispatch, state, getters }) => {
tasksByType: { subject, selectedLabelIds }, tasksByType: { subject, selectedLabelIds },
} = state; } = 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 // dont request if we have no labels selected...for now
if (selectedLabelIds.length) { if (selectedLabelIds.length) {
const params = { const params = {
...@@ -326,13 +323,11 @@ export const fetchTasksByTypeData = ({ dispatch, state, getters }) => { ...@@ -326,13 +323,11 @@ export const fetchTasksByTypeData = ({ dispatch, state, getters }) => {
label_ids: selectedLabelIds, label_ids: selectedLabelIds,
}; };
dispatch('requestTasksByTypeData');
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 Promise.resolve(); return commit(types.RECEIVE_TASKS_BY_TYPE_DATA_SUCCESS, []);
}; };
export const requestUpdateStage = ({ commit }) => commit(types.REQUEST_UPDATE_STAGE); export const requestUpdateStage = ({ commit }) => commit(types.REQUEST_UPDATE_STAGE);
...@@ -403,7 +398,7 @@ export const removeStage = ({ dispatch, state }, stageId) => { ...@@ -403,7 +398,7 @@ export const removeStage = ({ dispatch, state }, stageId) => {
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('fetchTasksByTypeData'); dispatch('fetchTopRankedGroupLabels');
}; };
export const initializeCycleAnalyticsSuccess = ({ commit }) => export const initializeCycleAnalyticsSuccess = ({ commit }) =>
......
...@@ -50,6 +50,7 @@ export default { ...@@ -50,6 +50,7 @@ export default {
state.isLoadingStage = false; state.isLoadingStage = false;
}, },
[types.REQUEST_TOP_RANKED_GROUP_LABELS](state) { [types.REQUEST_TOP_RANKED_GROUP_LABELS](state) {
state.isLoadingTasksByTypeChartTopLabels = true;
state.topRankedLabels = []; state.topRankedLabels = [];
state.tasksByType = { state.tasksByType = {
...state.tasksByType, ...state.tasksByType,
...@@ -58,6 +59,7 @@ export default { ...@@ -58,6 +59,7 @@ export default {
}, },
[types.RECEIVE_TOP_RANKED_GROUP_LABELS_SUCCESS](state, data = []) { [types.RECEIVE_TOP_RANKED_GROUP_LABELS_SUCCESS](state, data = []) {
const { tasksByType } = state; const { tasksByType } = state;
state.isLoadingTasksByTypeChartTopLabels = false;
state.topRankedLabels = data.map(convertObjectPropsToCamelCase); state.topRankedLabels = data.map(convertObjectPropsToCamelCase);
state.tasksByType = { state.tasksByType = {
...tasksByType, ...tasksByType,
...@@ -66,6 +68,7 @@ export default { ...@@ -66,6 +68,7 @@ export default {
}, },
[types.RECEIVE_TOP_RANKED_GROUP_LABELS_ERROR](state) { [types.RECEIVE_TOP_RANKED_GROUP_LABELS_ERROR](state) {
const { tasksByType } = state; const { tasksByType } = state;
state.isLoadingTasksByTypeChartTopLabels = false;
state.topRankedLabels = []; state.topRankedLabels = [];
state.tasksByType = { state.tasksByType = {
...tasksByType, ...tasksByType,
...@@ -130,7 +133,7 @@ export default { ...@@ -130,7 +133,7 @@ export default {
[types.RECEIVE_TASKS_BY_TYPE_DATA_ERROR](state) { [types.RECEIVE_TASKS_BY_TYPE_DATA_ERROR](state) {
state.isLoadingTasksByTypeChart = false; state.isLoadingTasksByTypeChart = false;
}, },
[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.tasksByType = {
...state.tasksByType, ...state.tasksByType,
......
...@@ -9,6 +9,7 @@ export default () => ({ ...@@ -9,6 +9,7 @@ export default () => ({
isLoading: false, isLoading: false,
isLoadingStage: false, isLoadingStage: false,
isLoadingTasksByTypeChart: false, isLoadingTasksByTypeChart: false,
isLoadingTasksByTypeChartTopLabels: false,
isEmptyStage: false, isEmptyStage: false,
errorCode: null, errorCode: null,
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`TasksByTypeChart no data available should render the no data available message 1`] = `
"<div class=\\"row\\">
<div class=\\"col-12\\">
<h3>Type of work</h3>
<div class=\\"bs-callout bs-callout-info\\">
<p>There is no data available. Please change your selection.</p>
</div>
</div>
</div>"
`;
exports[`TasksByTypeChart with data available should render the loading chart 1`] = `
"<div class=\\"row\\">
<div class=\\"col-12\\">
<h3>Type of work</h3>
<div>
<p>Showing data for group 'Gitlab Org' from Dec 11, 2019 to Jan 10, 2020</p>
<tasks-by-type-filters-stub selectedlabelids=\\"1,2,3\\" maxlabels=\\"15\\" subjectfilter=\\"Issue\\"></tasks-by-type-filters-stub>
<gl-stacked-column-chart-stub data=\\"0,1,2,5,2,3,2,4,1\\" option=\\"[object Object]\\" presentation=\\"stacked\\" groupby=\\"Group 1,Group 2,Group 3\\" xaxistype=\\"category\\" xaxistitle=\\"Date\\" yaxistitle=\\"Number of tasks\\" seriesnames=\\"Cool label,Normal label\\" legendaveragetext=\\"Avg\\" legendmaxtext=\\"Max\\" y-axis-type=\\"value\\"></gl-stacked-column-chart-stub>
</div>
</div>
</div>"
`;
...@@ -14,7 +14,7 @@ import 'bootstrap'; ...@@ -14,7 +14,7 @@ import 'bootstrap';
import '~/gl_dropdown'; import '~/gl_dropdown';
import DurationChart from 'ee/analytics/cycle_analytics/components/duration_chart.vue'; import DurationChart from 'ee/analytics/cycle_analytics/components/duration_chart.vue';
import Daterange from 'ee/analytics/shared/components/daterange.vue'; import Daterange from 'ee/analytics/shared/components/daterange.vue';
import TasksByTypeChart from 'ee/analytics/cycle_analytics/components/tasks_by_type_chart.vue'; import TypeOfWorkCharts from 'ee/analytics/cycle_analytics/components/type_of_work_charts.vue';
import waitForPromises from 'helpers/wait_for_promises'; import waitForPromises from 'helpers/wait_for_promises';
import httpStatusCodes from '~/lib/utils/http_status'; import httpStatusCodes from '~/lib/utils/http_status';
import * as commonUtils from '~/lib/utils/common_utils'; import * as commonUtils from '~/lib/utils/common_utils';
...@@ -132,8 +132,8 @@ describe('Cycle Analytics component', () => { ...@@ -132,8 +132,8 @@ describe('Cycle Analytics component', () => {
expect(wrapper.find(DurationChart).exists()).toBe(flag); expect(wrapper.find(DurationChart).exists()).toBe(flag);
}; };
const displaysTasksByType = flag => { const displaysTypeOfWork = flag => {
expect(wrapper.find(TasksByTypeChart).exists()).toBe(flag); expect(wrapper.find(TypeOfWorkCharts).exists()).toBe(flag);
}; };
beforeEach(() => { beforeEach(() => {
...@@ -343,7 +343,7 @@ describe('Cycle Analytics component', () => { ...@@ -343,7 +343,7 @@ describe('Cycle Analytics component', () => {
}); });
it('does not display the tasks by type chart', () => { it('does not display the tasks by type chart', () => {
displaysTasksByType(false); displaysTypeOfWork(false);
}); });
it('does not display the duration chart', () => { it('does not display the duration chart', () => {
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`TasksByTypeChart no data available should render the no data available message 1`] = `
"<div class=\\"bs-callout bs-callout-info\\">
<p>There is no data available. Please change your selection.</p>
</div>"
`;
exports[`TasksByTypeChart with data available should render the loading chart 1`] = `"<gl-stacked-column-chart-stub data=\\"0,1,2,5,2,3,2,4,1\\" option=\\"[object Object]\\" presentation=\\"stacked\\" groupby=\\"Group 1,Group 2,Group 3\\" xaxistype=\\"category\\" xaxistitle=\\"Date\\" yaxistitle=\\"Number of tasks\\" seriesnames=\\"Cool label,Normal label\\" legendaveragetext=\\"Avg\\" legendmaxtext=\\"Max\\" y-axis-type=\\"value\\"></gl-stacked-column-chart-stub>"`;
import { mount, shallowMount } from '@vue/test-utils'; import { mount, shallowMount } from '@vue/test-utils';
import TasksByTypeChart from 'ee/analytics/cycle_analytics/components/tasks_by_type_chart.vue'; import TasksByTypeChart from 'ee/analytics/cycle_analytics/components/tasks_by_type/tasks_by_type_chart.vue';
import { TASKS_BY_TYPE_SUBJECT_ISSUE } from 'ee/analytics/cycle_analytics/constants'; import { tasksByTypeData } from '../../mock_data';
const seriesNames = ['Cool label', 'Normal label']; const { groupBy, data, seriesNames } = tasksByTypeData;
const data = [[0, 1, 2], [5, 2, 3], [2, 4, 1]];
const groupBy = ['Group 1', 'Group 2', 'Group 3'];
const filters = {
selectedGroup: {
id: 22,
name: 'Gitlab Org',
fullName: 'Gitlab Org',
fullPath: 'gitlab-org',
},
selectedProjectIds: [],
startDate: new Date('2019-12-11'),
endDate: new Date('2020-01-10'),
subject: TASKS_BY_TYPE_SUBJECT_ISSUE,
selectedLabelIds: [1, 2, 3],
};
function createComponent({ props = {}, shallow = true, stubs = {} }) { function createComponent({ props = {}, shallow = true, stubs = {} }) {
const fn = shallow ? shallowMount : mount; const fn = shallow ? shallowMount : mount;
return fn(TasksByTypeChart, { return fn(TasksByTypeChart, {
propsData: { propsData: {
filters, groupBy,
chartData: { data,
groupBy, seriesNames,
data,
seriesNames,
},
...props, ...props,
}, },
stubs: { stubs: {
...@@ -60,11 +42,9 @@ describe('TasksByTypeChart', () => { ...@@ -60,11 +42,9 @@ describe('TasksByTypeChart', () => {
beforeEach(() => { beforeEach(() => {
wrapper = createComponent({ wrapper = createComponent({
props: { props: {
chartData: { groupBy: [],
groupBy: [], data: [],
data: [], seriesNames: [],
seriesNames: [],
},
}, },
}); });
}); });
......
...@@ -3,7 +3,7 @@ import axios from 'axios'; ...@@ -3,7 +3,7 @@ import axios from 'axios';
import MockAdapter from 'axios-mock-adapter'; import MockAdapter from 'axios-mock-adapter';
import { shallowMount, mount, createLocalVue } from '@vue/test-utils'; import { shallowMount, mount, createLocalVue } from '@vue/test-utils';
import { GlDropdownItem, GlSegmentedControl } from '@gitlab/ui'; import { GlDropdownItem, GlSegmentedControl } from '@gitlab/ui';
import TasksByTypeFilters from 'ee/analytics/cycle_analytics/components/tasks_by_type_filters.vue'; import TasksByTypeFilters from 'ee/analytics/cycle_analytics/components/tasks_by_type/tasks_by_type_filters.vue';
import LabelsSelector from 'ee/analytics/cycle_analytics/components/labels_selector.vue'; import LabelsSelector from 'ee/analytics/cycle_analytics/components/labels_selector.vue';
import { import {
TASKS_BY_TYPE_SUBJECT_ISSUE, TASKS_BY_TYPE_SUBJECT_ISSUE,
...@@ -11,8 +11,8 @@ import { ...@@ -11,8 +11,8 @@ import {
TASKS_BY_TYPE_FILTERS, TASKS_BY_TYPE_FILTERS,
} from 'ee/analytics/cycle_analytics/constants'; } from 'ee/analytics/cycle_analytics/constants';
import waitForPromises from 'helpers/wait_for_promises'; import waitForPromises from 'helpers/wait_for_promises';
import { shouldFlashAMessage } from '../helpers'; import { shouldFlashAMessage } from '../../helpers';
import { groupLabels } from '../mock_data'; import { groupLabels } from '../../mock_data';
import createStore from 'ee/analytics/cycle_analytics/store'; import createStore from 'ee/analytics/cycle_analytics/store';
import * as getters from 'ee/analytics/cycle_analytics/store/getters'; import * as getters from 'ee/analytics/cycle_analytics/store/getters';
......
import { shallowMount } from '@vue/test-utils';
import { GlLoadingIcon } from '@gitlab/ui';
import TypeOfWorkCharts from 'ee/analytics/cycle_analytics/components/type_of_work_charts.vue';
import TasksByTypeChart from 'ee/analytics/cycle_analytics/components/tasks_by_type/tasks_by_type_chart.vue';
import TasksByTypeFilters from 'ee/analytics/cycle_analytics/components/tasks_by_type/tasks_by_type_filters.vue';
import { tasksByTypeData, taskByTypeFilters } from '../mock_data';
import {
TASKS_BY_TYPE_SUBJECT_MERGE_REQUEST,
TASKS_BY_TYPE_FILTERS,
} from 'ee/analytics/cycle_analytics/constants';
describe('TypeOfWorkCharts', () => {
function createComponent({ props = {}, stubs = {} } = {}) {
return shallowMount(TypeOfWorkCharts, {
propsData: {
isLoading: false,
tasksByTypeChartData: tasksByTypeData,
selectedTasksByTypeFilters: taskByTypeFilters,
...props,
},
stubs: {
TasksByTypeChart: false,
TasksByTypeFilters: false,
...stubs,
},
});
}
let wrapper = null;
const findSubjectFilters = _wrapper => _wrapper.find(TasksByTypeFilters);
const findTasksByTypeChart = _wrapper => _wrapper.find(TasksByTypeChart);
const findLoader = _wrapper => _wrapper.find(GlLoadingIcon);
afterEach(() => {
wrapper.destroy();
});
describe('with data', () => {
beforeEach(() => {
wrapper = createComponent();
});
it('renders the task by type chart', () => {
expect(findTasksByTypeChart(wrapper).exists()).toBe(true);
});
it('does not render the loading icon', () => {
expect(findLoader(wrapper).exists()).toBe(false);
});
});
describe('when a filter is selected', () => {
const payload = {
filter: TASKS_BY_TYPE_FILTERS.SUBJECT,
value: TASKS_BY_TYPE_SUBJECT_MERGE_REQUEST,
};
beforeEach(() => {
wrapper = createComponent();
findSubjectFilters(wrapper).vm.$emit('updateFilter', payload);
return wrapper.vm.$nextTick();
});
it('emits the `updateFilter` event', () => {
expect(wrapper.emitted('updateFilter')).toBeDefined();
expect(wrapper.emitted('updateFilter')[0]).toEqual([payload]);
});
});
describe('while loading', () => {
beforeEach(() => {
wrapper = createComponent({ props: { isLoading: true } });
});
it('renders loading icon', () => {
expect(findLoader(wrapper).exists()).toBe(true);
});
});
});
...@@ -3,7 +3,10 @@ import { TEST_HOST } from 'helpers/test_constants'; ...@@ -3,7 +3,10 @@ import { TEST_HOST } from 'helpers/test_constants';
import { getJSONFixture } from 'helpers/fixtures'; import { getJSONFixture } from 'helpers/fixtures';
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 { DEFAULT_DAYS_IN_PAST } from 'ee/analytics/cycle_analytics/constants'; import {
DEFAULT_DAYS_IN_PAST,
TASKS_BY_TYPE_SUBJECT_ISSUE,
} from 'ee/analytics/cycle_analytics/constants';
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';
...@@ -142,7 +145,7 @@ export const customStageFormErrors = convertObjectPropsToCamelCase(rawCustomStag ...@@ -142,7 +145,7 @@ export const customStageFormErrors = convertObjectPropsToCamelCase(rawCustomStag
const dateRange = getDatesInRange(startDate, endDate, toYmd); const dateRange = getDatesInRange(startDate, endDate, toYmd);
export const tasksByTypeData = getJSONFixture('analytics/type_of_work/tasks_by_type.json').map( export const rawTasksByTypeData = 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;
...@@ -154,7 +157,27 @@ export const tasksByTypeData = getJSONFixture('analytics/type_of_work/tasks_by_t ...@@ -154,7 +157,27 @@ export const tasksByTypeData = getJSONFixture('analytics/type_of_work/tasks_by_t
}, },
); );
export const transformedTasksByTypeData = transformRawTasksByTypeData(tasksByTypeData); export const transformedTasksByTypeData = transformRawTasksByTypeData(rawTasksByTypeData);
export const tasksByTypeData = {
seriesNames: ['Cool label', 'Normal label'],
data: [[0, 1, 2], [5, 2, 3], [2, 4, 1]],
groupBy: ['Group 1', 'Group 2', 'Group 3'],
};
export const taskByTypeFilters = {
selectedGroup: {
id: 22,
name: 'Gitlab Org',
fullName: 'Gitlab Org',
fullPath: 'gitlab-org',
},
selectedProjectIds: [],
startDate: new Date('2019-12-11'),
endDate: new Date('2020-01-10'),
subject: TASKS_BY_TYPE_SUBJECT_ISSUE,
selectedLabelIds: [1, 2, 3],
};
export const rawDurationData = [ export const rawDurationData = [
{ {
......
...@@ -849,7 +849,7 @@ describe('Cycle analytics actions', () => { ...@@ -849,7 +849,7 @@ describe('Cycle analytics actions', () => {
const filter = TASKS_BY_TYPE_FILTERS.SUBJECT; const filter = TASKS_BY_TYPE_FILTERS.SUBJECT;
const value = 'issue'; const value = 'issue';
it(`commits the ${types.SET_TASKS_BY_TYPE_FILTERS} mutation and dispatches 'fetchTasksByTypeData'`, done => { it(`commits the ${types.SET_TASKS_BY_TYPE_FILTERS} mutation and dispatches 'fetchTopRankedGroupLabels'`, done => {
testAction( testAction(
actions.setTasksByTypeFilters, actions.setTasksByTypeFilters,
{ filter, value }, { filter, value },
...@@ -862,7 +862,7 @@ describe('Cycle analytics actions', () => { ...@@ -862,7 +862,7 @@ describe('Cycle analytics actions', () => {
], ],
[ [
{ {
type: 'fetchTasksByTypeData', type: 'fetchTopRankedGroupLabels',
}, },
], ],
done, done,
......
...@@ -15,7 +15,7 @@ import { ...@@ -15,7 +15,7 @@ import {
startDate, startDate,
endDate, endDate,
customizableStagesAndEvents, customizableStagesAndEvents,
tasksByTypeData, rawTasksByTypeData,
transformedTasksByTypeData, transformedTasksByTypeData,
selectedProjects, selectedProjects,
} from '../mock_data'; } from '../mock_data';
...@@ -185,7 +185,7 @@ describe('Cycle analytics mutations', () => { ...@@ -185,7 +185,7 @@ describe('Cycle analytics mutations', () => {
it('sets tasksByType.data to the raw returned chart data', () => { it('sets tasksByType.data to the raw returned chart data', () => {
state = { tasksByType: { data: null } }; state = { tasksByType: { data: null } };
mutations[types.RECEIVE_TASKS_BY_TYPE_DATA_SUCCESS](state, tasksByTypeData); mutations[types.RECEIVE_TASKS_BY_TYPE_DATA_SUCCESS](state, rawTasksByTypeData);
expect(state.tasksByType.data).toEqual(transformedTasksByTypeData); expect(state.tasksByType.data).toEqual(transformedTasksByTypeData);
}); });
......
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