Commit 8b6589fe authored by Ezekiel Kigbo's avatar Ezekiel Kigbo

Merge branch '349739-roadmap-open-closed-as-progress-bar' into 'master'

Roadmap - Introduce progress tracking setting

See merge request gitlab-org/gitlab!79593
parents aeef5e96 fad39868
<script> <script>
import { GlPopover, GlProgressBar, GlIcon } from '@gitlab/ui'; import { GlPopover, GlProgressBar, GlIcon } from '@gitlab/ui';
import { mapState } from 'vuex';
import { __, sprintf } from '~/locale'; import { __, sprintf } from '~/locale';
import { import {
EPIC_DETAILS_CELL_WIDTH, EPIC_DETAILS_CELL_WIDTH,
...@@ -7,6 +8,7 @@ import { ...@@ -7,6 +8,7 @@ import {
PRESET_TYPES, PRESET_TYPES,
SMALL_TIMELINE_BAR, SMALL_TIMELINE_BAR,
TIMELINE_CELL_MIN_WIDTH, TIMELINE_CELL_MIN_WIDTH,
PROGRESS_COUNT,
} from '../constants'; } from '../constants';
import CommonMixin from '../mixins/common_mixin'; import CommonMixin from '../mixins/common_mixin';
...@@ -55,6 +57,7 @@ export default { ...@@ -55,6 +57,7 @@ export default {
}, },
}, },
computed: { computed: {
...mapState(['progressTracking']),
timelineBarInnerStyle() { timelineBarInnerStyle() {
return { return {
maxWidth: `${this.clientWidth - EPIC_DETAILS_CELL_WIDTH}px`, maxWidth: `${this.clientWidth - EPIC_DETAILS_CELL_WIDTH}px`,
...@@ -75,33 +78,49 @@ export default { ...@@ -75,33 +78,49 @@ export default {
timelineBarTitle() { timelineBarTitle() {
return this.isTimelineBarSmall ? '...' : this.epic.title; return this.isTimelineBarSmall ? '...' : this.epic.title;
}, },
epicTotalWeight() { progressTrackingIsCount() {
if (this.epic.descendantWeightSum) { return this.progressTracking === PROGRESS_COUNT;
const { openedIssues, closedIssues } = this.epic.descendantWeightSum; },
epicDescendants() {
return this.progressTrackingIsCount
? this.epic.descendantCounts
: this.epic.descendantWeightSum;
},
epicTotal() {
if (this.epicDescendants) {
const { openedIssues, closedIssues } = this.epicDescendants;
return openedIssues + closedIssues; return openedIssues + closedIssues;
} }
return undefined; return undefined;
}, },
epicWeightPercentage() { epicPercentage() {
return this.epicTotalWeight return this.epicTotal
? Math.round( ? Math.round((this.epicDescendants.closedIssues / this.epicTotal) * PERCENTAGE)
(this.epic.descendantWeightSum.closedIssues / this.epicTotalWeight) * PERCENTAGE,
)
: 0; : 0;
}, },
epicWeightPercentageText() { epicPercentageText() {
return sprintf(__(`%{percentage}%% weight completed`), { const str = this.progressTrackingIsCount
percentage: this.epicWeightPercentage, ? __('%{percentage}%% issues closed')
}); : __('%{percentage}%% weight completed');
return sprintf(str, { percentage: this.epicPercentage });
}, },
popoverWeightText() {
if (this.epic.descendantWeightSum) { popoverText() {
return sprintf(__('%{completedWeight} of %{totalWeight} weight completed'), { if (this.epicDescendants) {
completedWeight: this.epic.descendantWeightSum.closedIssues, const str = this.progressTrackingIsCount
totalWeight: this.epicTotalWeight, ? __('%{completed} of %{total} issues closed')
: __('%{completed} of %{total} weight completed');
return sprintf(str, {
completed: this.epicDescendants.closedIssues,
total: this.epicTotal,
}); });
} }
return __('- of - weight completed'); return this.progressTrackingIsCount
? __('- of - issues closed')
: __('- of - weight completed');
},
progressIcon() {
return this.progressTrackingIsCount ? 'issue-closed' : 'weight';
}, },
}, },
methods: { methods: {
...@@ -125,19 +144,19 @@ export default { ...@@ -125,19 +144,19 @@ export default {
<div v-if="!isTimelineBarSmall" class="gl-display-flex gl-align-items-center"> <div v-if="!isTimelineBarSmall" class="gl-display-flex gl-align-items-center">
<gl-progress-bar <gl-progress-bar
class="epic-bar-progress gl-flex-grow-1 gl-mr-2" class="epic-bar-progress gl-flex-grow-1 gl-mr-2"
:value="epicWeightPercentage" :value="epicPercentage"
aria-hidden="true" aria-hidden="true"
/> />
<div class="gl-font-sm gl-display-flex gl-align-items-center gl-white-space-nowrap"> <div class="gl-font-sm gl-display-flex gl-align-items-center gl-white-space-nowrap">
<gl-icon class="gl-mr-1" :size="12" name="weight" /> <gl-icon class="gl-mr-1" :size="12" :name="progressIcon" />
<p class="gl-m-0" :aria-label="epicWeightPercentageText">{{ epicWeightPercentage }}%</p> <p class="gl-m-0" :aria-label="epicPercentageText">{{ epicPercentage }}%</p>
</div> </div>
</div> </div>
</div> </div>
</a> </a>
<gl-popover :target="generateKey(epic)" :title="epic.title" placement="left"> <gl-popover :target="generateKey(epic)" :title="epic.title" placement="left">
<p class="gl-text-gray-500 gl-m-0">{{ timeframeString(epic) }}</p> <p class="gl-text-gray-500 gl-m-0">{{ timeframeString(epic) }}</p>
<p class="gl-m-0">{{ popoverWeightText }}</p> <p class="gl-m-0">{{ popoverText }}</p>
</gl-popover> </gl-popover>
</div> </div>
</template> </template>
...@@ -71,7 +71,7 @@ export default { ...@@ -71,7 +71,7 @@ export default {
}; };
}, },
computed: { computed: {
...mapState(['presetType', 'epicsState', 'sortedBy', 'filterParams']), ...mapState(['presetType', 'epicsState', 'sortedBy', 'filterParams', 'progressTracking']),
selectedEpicStateTitle() { selectedEpicStateTitle() {
if (this.epicsState === EPICS_STATES.ALL) { if (this.epicsState === EPICS_STATES.ALL) {
return __('All epics'); return __('All epics');
......
<script>
import { GlFormGroup, GlFormRadioGroup } from '@gitlab/ui';
import { mapActions, mapState } from 'vuex';
import { __ } from '~/locale';
import { PROGRESS_TRACKING_OPTIONS } from '../constants';
export default {
components: {
GlFormGroup,
GlFormRadioGroup,
},
computed: {
...mapState(['progressTracking']),
},
methods: {
...mapActions(['setProgressTracking']),
handleProgressTrackingChange(option) {
if (option !== this.progressTracking) {
this.setProgressTracking(option);
}
},
},
i18n: {
header: __('Progress tracking'),
},
PROGRESS_TRACKING_OPTIONS,
};
</script>
<template>
<div>
<gl-form-group
class="gl-mb-0"
:label="$options.i18n.header"
data-testid="roadmap-progress-tracking"
>
<gl-form-radio-group
:checked="progressTracking"
stacked
:options="$options.PROGRESS_TRACKING_OPTIONS"
@change="handleProgressTrackingChange"
/>
</gl-form-group>
</div>
</template>
...@@ -2,12 +2,14 @@ ...@@ -2,12 +2,14 @@
import { GlDrawer } from '@gitlab/ui'; import { GlDrawer } from '@gitlab/ui';
import RoadmapDaterange from './roadmap_daterange.vue'; import RoadmapDaterange from './roadmap_daterange.vue';
import RoadmapEpicsState from './roadmap_epics_state.vue'; import RoadmapEpicsState from './roadmap_epics_state.vue';
import RoadmapProgressTracking from './roadmap_progress_tracking.vue';
export default { export default {
components: { components: {
GlDrawer, GlDrawer,
RoadmapDaterange, RoadmapDaterange,
RoadmapEpicsState, RoadmapEpicsState,
RoadmapProgressTracking,
}, },
props: { props: {
isOpen: { isOpen: {
...@@ -47,6 +49,7 @@ export default { ...@@ -47,6 +49,7 @@ export default {
<template #default> <template #default>
<roadmap-daterange :timeframe-range-type="timeframeRangeType" /> <roadmap-daterange :timeframe-range-type="timeframeRangeType" />
<roadmap-epics-state /> <roadmap-epics-state />
<roadmap-progress-tracking />
</template> </template>
</gl-drawer> </gl-drawer>
</template> </template>
import { s__ } from '~/locale'; import { s__, __ } from '~/locale';
/* /*
Update the counterparts in roadmap.scss when making changes. Update the counterparts in roadmap.scss when making changes.
...@@ -61,3 +61,21 @@ export const EPIC_LEVEL_MARGIN = { ...@@ -61,3 +61,21 @@ export const EPIC_LEVEL_MARGIN = {
}; };
export const ROADMAP_PAGE_SIZE = 50; export const ROADMAP_PAGE_SIZE = 50;
export const PROGRESS_WEIGHT = 'WEIGHT';
export const PROGRESS_COUNT = 'COUNT';
export const PROGRESS_TRACKING_OPTIONS = [
{ text: __('Use issue weight'), value: PROGRESS_WEIGHT },
{ text: __('Use issue count'), value: PROGRESS_COUNT },
];
export const UNSUPPORTED_ROADMAP_PARAMS = [
'scope',
'utf8',
'state',
'sort',
'timeframe_range_type',
'layout',
'progress',
];
...@@ -51,6 +51,7 @@ export default { ...@@ -51,6 +51,7 @@ export default {
'not[author_username]': notAuthorUsername, 'not[author_username]': notAuthorUsername,
'not[my_reaction_emoji]': notMyReactionEmoji, 'not[my_reaction_emoji]': notMyReactionEmoji,
'not[label_name][]': notLabelName, 'not[label_name][]': notLabelName,
progress: this.progressTracking,
}; };
}, },
}, },
......
...@@ -17,6 +17,8 @@ fragment BaseEpic on Epic { ...@@ -17,6 +17,8 @@ fragment BaseEpic on Epic {
descendantCounts { descendantCounts {
openedEpics openedEpics
closedEpics closedEpics
closedIssues
openedIssues
} }
group { group {
id id
......
...@@ -11,7 +11,7 @@ import EpicItem from './components/epic_item.vue'; ...@@ -11,7 +11,7 @@ import EpicItem from './components/epic_item.vue';
import EpicItemContainer from './components/epic_item_container.vue'; import EpicItemContainer from './components/epic_item_container.vue';
import roadmapApp from './components/roadmap_app.vue'; import roadmapApp from './components/roadmap_app.vue';
import { DATE_RANGES } from './constants'; import { DATE_RANGES, PROGRESS_WEIGHT, UNSUPPORTED_ROADMAP_PARAMS } from './constants';
import createStore from './store'; import createStore from './store';
import { import {
...@@ -74,7 +74,7 @@ export default () => { ...@@ -74,7 +74,7 @@ export default () => {
}); });
const filterParams = { const filterParams = {
...convertObjectPropsToCamelCase(rawFilterParams, { ...convertObjectPropsToCamelCase(rawFilterParams, {
dropKeys: ['scope', 'utf8', 'state', 'sort', 'timeframe_range_type', 'layout'], // These keys are unsupported/unnecessary dropKeys: UNSUPPORTED_ROADMAP_PARAMS,
}), }),
// We shall put parsed value of `confidential` only // We shall put parsed value of `confidential` only
// when it is defined. // when it is defined.
...@@ -103,6 +103,7 @@ export default () => { ...@@ -103,6 +103,7 @@ export default () => {
timeframeRangeType, timeframeRangeType,
presetType, presetType,
timeframe, timeframe,
progressTracking: rawFilterParams.progress || PROGRESS_WEIGHT,
}; };
}, },
created() { created() {
...@@ -121,6 +122,7 @@ export default () => { ...@@ -121,6 +122,7 @@ export default () => {
isChildEpics: this.isChildEpics, isChildEpics: this.isChildEpics,
hasFiltersApplied: this.hasFiltersApplied, hasFiltersApplied: this.hasFiltersApplied,
allowSubEpics: this.allowSubEpics, allowSubEpics: this.allowSubEpics,
progressTracking: this.progressTracking,
}); });
}, },
methods: { methods: {
......
...@@ -326,3 +326,6 @@ export const setFilterParams = ({ commit }, filterParams) => ...@@ -326,3 +326,6 @@ export const setFilterParams = ({ commit }, filterParams) =>
commit(types.SET_FILTER_PARAMS, filterParams); commit(types.SET_FILTER_PARAMS, filterParams);
export const setSortedBy = ({ commit }, sortedBy) => commit(types.SET_SORTED_BY, sortedBy); export const setSortedBy = ({ commit }, sortedBy) => commit(types.SET_SORTED_BY, sortedBy);
export const setProgressTracking = ({ commit }, progressTracking) =>
commit(types.SET_PROGRESS_TRACKING, progressTracking);
...@@ -32,3 +32,4 @@ export const SET_EPICS_STATE = 'SET_EPICS_STATE'; ...@@ -32,3 +32,4 @@ export const SET_EPICS_STATE = 'SET_EPICS_STATE';
export const SET_DATERANGE = 'SET_DATERANGE'; export const SET_DATERANGE = 'SET_DATERANGE';
export const SET_FILTER_PARAMS = 'SET_FILTER_PARAMS'; export const SET_FILTER_PARAMS = 'SET_FILTER_PARAMS';
export const SET_SORTED_BY = 'SET_SORTED_BY'; export const SET_SORTED_BY = 'SET_SORTED_BY';
export const SET_PROGRESS_TRACKING = 'SET_PROGRESS_TRACKING';
...@@ -140,4 +140,8 @@ export default { ...@@ -140,4 +140,8 @@ export default {
state.sortedBy = sortedBy; state.sortedBy = sortedBy;
resetEpics(state); resetEpics(state);
}, },
[types.SET_PROGRESS_TRACKING](state, progressTracking) {
state.progressTracking = progressTracking;
},
}; };
...@@ -2,6 +2,7 @@ export default () => ({ ...@@ -2,6 +2,7 @@ export default () => ({
// API Calls // API Calls
basePath: '', basePath: '',
epicsState: '', epicsState: '',
progressTracking: '',
filterParams: null, filterParams: null,
// Data // Data
......
import { GlPopover, GlProgressBar } from '@gitlab/ui'; import { GlIcon, GlPopover, GlProgressBar } from '@gitlab/ui';
import Vue from 'vue';
import Vuex from 'vuex';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import EpicItemTimeline from 'ee/roadmap/components/epic_item_timeline.vue'; import EpicItemTimeline from 'ee/roadmap/components/epic_item_timeline.vue';
import { DATE_RANGES, PRESET_TYPES } from 'ee/roadmap/constants'; import { DATE_RANGES, PRESET_TYPES, PROGRESS_COUNT, PROGRESS_WEIGHT } from 'ee/roadmap/constants';
import createStore from 'ee/roadmap/store';
import { getTimeframeForRangeType } from 'ee/roadmap/utils/roadmap_utils'; import { getTimeframeForRangeType } from 'ee/roadmap/utils/roadmap_utils';
import { mockTimeframeInitialDate, mockFormattedEpic } from 'ee_jest/roadmap/mock_data'; import { mockTimeframeInitialDate, mockFormattedEpic } from 'ee_jest/roadmap/mock_data';
Vue.use(Vuex);
const mockTimeframeMonths = getTimeframeForRangeType({ const mockTimeframeMonths = getTimeframeForRangeType({
timeframeRangeType: DATE_RANGES.CURRENT_YEAR, timeframeRangeType: DATE_RANGES.CURRENT_YEAR,
presetType: PRESET_TYPES.MONTHS, presetType: PRESET_TYPES.MONTHS,
...@@ -17,8 +22,16 @@ const createComponent = ({ ...@@ -17,8 +22,16 @@ const createComponent = ({
timeframe = mockTimeframeMonths, timeframe = mockTimeframeMonths,
timeframeItem = mockTimeframeMonths[0], timeframeItem = mockTimeframeMonths[0],
timeframeString = '', timeframeString = '',
progressTracking = PROGRESS_WEIGHT,
} = {}) => { } = {}) => {
const store = createStore();
store.dispatch('setInitialData', {
progressTracking,
});
return shallowMount(EpicItemTimeline, { return shallowMount(EpicItemTimeline, {
store,
propsData: { propsData: {
epic, epic,
startDate: epic.originalStartDate, startDate: epic.originalStartDate,
...@@ -51,7 +64,7 @@ describe('EpicItemTimelineComponent', () => { ...@@ -51,7 +64,7 @@ describe('EpicItemTimelineComponent', () => {
}); });
it('shows the progress bar with correct value', () => { it('shows the progress bar with correct value', () => {
expect(wrapper.find(GlProgressBar).attributes('value')).toBe('60'); expect(wrapper.findComponent(GlProgressBar).attributes('value')).toBe('60');
}); });
it('shows the percentage', () => { it('shows the percentage', () => {
...@@ -61,6 +74,19 @@ describe('EpicItemTimelineComponent', () => { ...@@ -61,6 +74,19 @@ describe('EpicItemTimelineComponent', () => {
it('contains a link to the epic', () => { it('contains a link to the epic', () => {
expect(getEpicBar(wrapper).attributes('href')).toBe(mockFormattedEpic.webUrl); expect(getEpicBar(wrapper).attributes('href')).toBe(mockFormattedEpic.webUrl);
}); });
it.each`
progressTracking | icon
${PROGRESS_WEIGHT} | ${'weight'}
${PROGRESS_COUNT} | ${'issue-closed'}
`(
'displays icon $icon when progressTracking equals $progressTracking',
({ progressTracking, icon }) => {
wrapper = createComponent({ progressTracking });
expect(wrapper.findComponent(GlIcon).props('name')).toBe(icon);
},
);
}); });
describe('popover', () => { describe('popover', () => {
...@@ -70,21 +96,37 @@ describe('EpicItemTimelineComponent', () => { ...@@ -70,21 +96,37 @@ describe('EpicItemTimelineComponent', () => {
expect(wrapper.find(GlPopover).text()).toContain('Jun 26, 2017 – Mar 10, 2018'); expect(wrapper.find(GlPopover).text()).toContain('Jun 26, 2017 – Mar 10, 2018');
}); });
it('shows the weight completed', () => { it.each`
wrapper = createComponent(); progressTracking | option | text
${PROGRESS_WEIGHT} | ${'weight'} | ${'3 of 5 weight completed'}
expect(wrapper.find(GlPopover).text()).toContain('3 of 5 weight completed'); ${PROGRESS_COUNT} | ${'issues'} | ${'3 of 5 issues closed'}
}); `(
'shows $option completed when progressTracking equals $progressTracking',
it('shows the weight completed with no numbers when there is no weights information', () => { ({ progressTracking, text }) => {
wrapper = createComponent({ wrapper = createComponent({ progressTracking });
epic: {
...mockFormattedEpic, expect(wrapper.findComponent(GlPopover).text()).toContain(text);
descendantWeightSum: undefined, },
}, );
});
it.each`
expect(wrapper.find(GlPopover).text()).toContain('- of - weight completed'); progressTracking | option | text
}); ${PROGRESS_WEIGHT} | ${'weight'} | ${'- of - weight completed'}
${PROGRESS_COUNT} | ${'issues'} | ${'- of - issues closed'}
`(
'shows $option completed with no numbers when there is no $option information and progressTracking equals $progressTracking',
({ progressTracking, text }) => {
wrapper = createComponent({
progressTracking,
epic: {
...mockFormattedEpic,
descendantWeightSum: undefined,
descendantCounts: undefined,
},
});
expect(wrapper.findComponent(GlPopover).text()).toContain(text);
},
);
}); });
}); });
...@@ -3,7 +3,7 @@ import Vue, { nextTick } from 'vue'; ...@@ -3,7 +3,7 @@ import Vue, { nextTick } from 'vue';
import Vuex from 'vuex'; import Vuex from 'vuex';
import RoadmapFilters from 'ee/roadmap/components/roadmap_filters.vue'; import RoadmapFilters from 'ee/roadmap/components/roadmap_filters.vue';
import { PRESET_TYPES, EPICS_STATES, DATE_RANGES } from 'ee/roadmap/constants'; import { PRESET_TYPES, EPICS_STATES, DATE_RANGES, PROGRESS_WEIGHT } from 'ee/roadmap/constants';
import createStore from 'ee/roadmap/store'; import createStore from 'ee/roadmap/store';
import { getTimeframeForRangeType } from 'ee/roadmap/utils/roadmap_utils'; import { getTimeframeForRangeType } from 'ee/roadmap/utils/roadmap_utils';
import { import {
...@@ -55,6 +55,7 @@ const createComponent = ({ ...@@ -55,6 +55,7 @@ const createComponent = ({
sortedBy, sortedBy,
filterParams, filterParams,
timeframe, timeframe,
progressTracking: PROGRESS_WEIGHT,
}); });
return shallowMountExtended(RoadmapFilters, { return shallowMountExtended(RoadmapFilters, {
...@@ -116,7 +117,7 @@ describe('RoadmapFilters', () => { ...@@ -116,7 +117,7 @@ describe('RoadmapFilters', () => {
await nextTick(); await nextTick();
expect(global.window.location.href).toBe( expect(global.window.location.href).toBe(
`${TEST_HOST}/?state=${EPICS_STATES.CLOSED}&sort=end_date_asc&layout=MONTHS&author_username=root&label_name%5B%5D=Bug&milestone_title=4.0&confidential=true`, `${TEST_HOST}/?state=${EPICS_STATES.CLOSED}&sort=end_date_asc&layout=MONTHS&author_username=root&label_name%5B%5D=Bug&milestone_title=4.0&confidential=true&progress=WEIGHT`,
); );
}); });
}); });
...@@ -148,10 +149,10 @@ describe('RoadmapFilters', () => { ...@@ -148,10 +149,10 @@ describe('RoadmapFilters', () => {
}); });
it('renders epics state toggling dropdown', () => { it('renders epics state toggling dropdown', () => {
const epicsStateDropdown = wrapper.find(GlDropdown); const epicsStateDropdown = wrapper.findComponent(GlDropdown);
expect(epicsStateDropdown.exists()).toBe(true); expect(epicsStateDropdown.exists()).toBe(true);
expect(epicsStateDropdown.findAll(GlDropdownItem)).toHaveLength(3); expect(epicsStateDropdown.findAllComponents(GlDropdownItem)).toHaveLength(3);
}); });
it('does not render settings button', () => { it('does not render settings button', () => {
......
import { GlFormGroup, GlFormRadioGroup } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import createStore from 'ee/roadmap/store';
import RoadmapProgressTracking from 'ee/roadmap/components/roadmap_progress_tracking.vue';
import { PROGRESS_WEIGHT, PROGRESS_TRACKING_OPTIONS } from 'ee/roadmap/constants';
describe('RoadmapProgressTracking', () => {
let wrapper;
const createComponent = () => {
const store = createStore();
store.dispatch('setInitialData', {
progressTracking: PROGRESS_WEIGHT,
});
wrapper = shallowMountExtended(RoadmapProgressTracking, {
store,
});
};
const findFormGroup = () => wrapper.findComponent(GlFormGroup);
const findFormRadioGroup = () => wrapper.findComponent(GlFormRadioGroup);
beforeEach(() => {
createComponent();
});
afterEach(() => {
wrapper.destroy();
});
describe('template', () => {
it('renders form group', () => {
expect(findFormGroup().exists()).toBe(true);
expect(findFormGroup().attributes('label')).toBe('Progress tracking');
});
it('renders radio form group', () => {
expect(findFormRadioGroup().exists()).toBe(true);
expect(findFormRadioGroup().props('options')).toEqual(PROGRESS_TRACKING_OPTIONS);
});
});
});
...@@ -3,6 +3,7 @@ import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; ...@@ -3,6 +3,7 @@ import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import RoadmapSettings from 'ee/roadmap/components/roadmap_settings.vue'; import RoadmapSettings from 'ee/roadmap/components/roadmap_settings.vue';
import RoadmapDaterange from 'ee/roadmap/components/roadmap_daterange.vue'; import RoadmapDaterange from 'ee/roadmap/components/roadmap_daterange.vue';
import RoadmapEpicsState from 'ee/roadmap/components/roadmap_epics_state.vue'; import RoadmapEpicsState from 'ee/roadmap/components/roadmap_epics_state.vue';
import RoadmapProgressTracking from 'ee/roadmap/components/roadmap_progress_tracking.vue';
describe('RoadmapSettings', () => { describe('RoadmapSettings', () => {
let wrapper; let wrapper;
...@@ -16,6 +17,7 @@ describe('RoadmapSettings', () => { ...@@ -16,6 +17,7 @@ describe('RoadmapSettings', () => {
const findSettingsDrawer = () => wrapper.findComponent(GlDrawer); const findSettingsDrawer = () => wrapper.findComponent(GlDrawer);
const findDaterange = () => wrapper.findComponent(RoadmapDaterange); const findDaterange = () => wrapper.findComponent(RoadmapDaterange);
const findEpicsSate = () => wrapper.findComponent(RoadmapEpicsState); const findEpicsSate = () => wrapper.findComponent(RoadmapEpicsState);
const findProgressTracking = () => wrapper.findComponent(RoadmapProgressTracking);
beforeEach(() => { beforeEach(() => {
createComponent(); createComponent();
...@@ -38,5 +40,9 @@ describe('RoadmapSettings', () => { ...@@ -38,5 +40,9 @@ describe('RoadmapSettings', () => {
it('renders roadmap epics state component', () => { it('renders roadmap epics state component', () => {
expect(findEpicsSate().exists()).toBe(true); expect(findEpicsSate().exists()).toBe(true);
}); });
it('renders roadmap progress tracking component', () => {
expect(findProgressTracking().exists()).toBe(true);
});
}); });
}); });
import Vue from 'vue';
import Vuex from 'vuex';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import EpicItemTimelineComponent from 'ee/roadmap/components/epic_item_timeline.vue'; import EpicItemTimelineComponent from 'ee/roadmap/components/epic_item_timeline.vue';
import { DATE_RANGES, PRESET_TYPES } from 'ee/roadmap/constants'; import { DATE_RANGES, PRESET_TYPES, PROGRESS_WEIGHT } from 'ee/roadmap/constants';
import createStore from 'ee/roadmap/store';
import { getTimeframeForRangeType } from 'ee/roadmap/utils/roadmap_utils'; import { getTimeframeForRangeType } from 'ee/roadmap/utils/roadmap_utils';
import { mockTimeframeInitialDate, mockEpic } from 'ee_jest/roadmap/mock_data'; import { mockTimeframeInitialDate, mockEpic } from 'ee_jest/roadmap/mock_data';
Vue.use(Vuex);
const mockTimeframeMonths = getTimeframeForRangeType({ const mockTimeframeMonths = getTimeframeForRangeType({
timeframeRangeType: DATE_RANGES.CURRENT_YEAR, timeframeRangeType: DATE_RANGES.CURRENT_YEAR,
presetType: PRESET_TYPES.MONTHS, presetType: PRESET_TYPES.MONTHS,
...@@ -20,7 +25,14 @@ describe('MonthsPresetMixin', () => { ...@@ -20,7 +25,14 @@ describe('MonthsPresetMixin', () => {
timeframeItem = mockTimeframeMonths[0], timeframeItem = mockTimeframeMonths[0],
epic = mockEpic, epic = mockEpic,
} = {}) => { } = {}) => {
const store = createStore();
store.dispatch('setInitialData', {
progressTracking: PROGRESS_WEIGHT,
});
return shallowMount(EpicItemTimelineComponent, { return shallowMount(EpicItemTimelineComponent, {
store,
propsData: { propsData: {
presetType, presetType,
timeframe, timeframe,
......
import Vue from 'vue';
import Vuex from 'vuex';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import EpicItemTimelineComponent from 'ee/roadmap/components/epic_item_timeline.vue'; import EpicItemTimelineComponent from 'ee/roadmap/components/epic_item_timeline.vue';
import { DATE_RANGES, PRESET_TYPES } from 'ee/roadmap/constants'; import { DATE_RANGES, PRESET_TYPES, PROGRESS_WEIGHT } from 'ee/roadmap/constants';
import createStore from 'ee/roadmap/store';
import { getTimeframeForRangeType } from 'ee/roadmap/utils/roadmap_utils'; import { getTimeframeForRangeType } from 'ee/roadmap/utils/roadmap_utils';
import { mockTimeframeInitialDate, mockEpic } from 'ee_jest/roadmap/mock_data'; import { mockTimeframeInitialDate, mockEpic } from 'ee_jest/roadmap/mock_data';
Vue.use(Vuex);
const mockTimeframeQuarters = getTimeframeForRangeType({ const mockTimeframeQuarters = getTimeframeForRangeType({
timeframeRangeType: DATE_RANGES.THREE_YEARS, timeframeRangeType: DATE_RANGES.THREE_YEARS,
presetType: PRESET_TYPES.QUARTERS, presetType: PRESET_TYPES.QUARTERS,
...@@ -20,7 +25,14 @@ describe('QuartersPresetMixin', () => { ...@@ -20,7 +25,14 @@ describe('QuartersPresetMixin', () => {
timeframeItem = mockTimeframeQuarters[0], timeframeItem = mockTimeframeQuarters[0],
epic = mockEpic, epic = mockEpic,
} = {}) => { } = {}) => {
const store = createStore();
store.dispatch('setInitialData', {
progressTracking: PROGRESS_WEIGHT,
});
return shallowMount(EpicItemTimelineComponent, { return shallowMount(EpicItemTimelineComponent, {
store,
propsData: { propsData: {
presetType, presetType,
timeframe, timeframe,
......
import Vue from 'vue';
import Vuex from 'vuex';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import EpicItemTimelineComponent from 'ee/roadmap/components/epic_item_timeline.vue'; import EpicItemTimelineComponent from 'ee/roadmap/components/epic_item_timeline.vue';
import { DATE_RANGES, PRESET_TYPES } from 'ee/roadmap/constants'; import { DATE_RANGES, PRESET_TYPES, PROGRESS_WEIGHT } from 'ee/roadmap/constants';
import createStore from 'ee/roadmap/store';
import { getTimeframeForRangeType } from 'ee/roadmap/utils/roadmap_utils'; import { getTimeframeForRangeType } from 'ee/roadmap/utils/roadmap_utils';
import { mockTimeframeInitialDate, mockEpic } from 'ee_jest/roadmap/mock_data'; import { mockTimeframeInitialDate, mockEpic } from 'ee_jest/roadmap/mock_data';
Vue.use(Vuex);
const mockTimeframeWeeks = getTimeframeForRangeType({ const mockTimeframeWeeks = getTimeframeForRangeType({
timeframeRangeType: DATE_RANGES.CURRENT_QUARTER, timeframeRangeType: DATE_RANGES.CURRENT_QUARTER,
presetType: PRESET_TYPES.WEEKS, presetType: PRESET_TYPES.WEEKS,
...@@ -20,7 +25,14 @@ describe('WeeksPresetMixin', () => { ...@@ -20,7 +25,14 @@ describe('WeeksPresetMixin', () => {
timeframeItem = mockTimeframeWeeks[0], timeframeItem = mockTimeframeWeeks[0],
epic = mockEpic, epic = mockEpic,
} = {}) => { } = {}) => {
const store = createStore();
store.dispatch('setInitialData', {
progressTracking: PROGRESS_WEIGHT,
});
return shallowMount(EpicItemTimelineComponent, { return shallowMount(EpicItemTimelineComponent, {
store,
propsData: { propsData: {
presetType, presetType,
timeframe, timeframe,
......
...@@ -222,6 +222,8 @@ export const mockRawEpic = { ...@@ -222,6 +222,8 @@ export const mockRawEpic = {
descendantCounts: { descendantCounts: {
openedEpics: 3, openedEpics: 3,
closedEpics: 2, closedEpics: 2,
closedIssues: 3,
openedIssues: 2,
__typename: 'EpicDescendantCount', __typename: 'EpicDescendantCount',
}, },
group: mockGroup1, group: mockGroup1,
......
...@@ -657,6 +657,17 @@ describe('Roadmap Vuex Actions', () => { ...@@ -657,6 +657,17 @@ describe('Roadmap Vuex Actions', () => {
payload: { timeframeRangeType: 'CURRENT_YEAR', presetType: 'MONTHS' }, payload: { timeframeRangeType: 'CURRENT_YEAR', presetType: 'MONTHS' },
}, },
], ],
);
});
});
describe('setProgressTracking', () => {
it('should set progressTracking in store state', () => {
return testAction(
actions.setProgressTracking,
'COUNT',
state,
[{ type: types.SET_PROGRESS_TRACKING, payload: 'COUNT' }],
[], [],
); );
}); });
......
import * as types from 'ee/roadmap/store/mutation_types'; import * as types from 'ee/roadmap/store/mutation_types';
import mutations from 'ee/roadmap/store/mutations'; import mutations from 'ee/roadmap/store/mutations';
import { PROGRESS_COUNT } from 'ee/roadmap/constants';
import defaultState from 'ee/roadmap/store/state'; import defaultState from 'ee/roadmap/store/state';
import { getTimeframeForRangeType } from 'ee/roadmap/utils/roadmap_utils'; import { getTimeframeForRangeType } from 'ee/roadmap/utils/roadmap_utils';
...@@ -342,4 +343,17 @@ describe('Roadmap Store Mutations', () => { ...@@ -342,4 +343,17 @@ describe('Roadmap Store Mutations', () => {
}); });
}); });
}); });
describe('SET_PROGRESS_TRACKING', () => {
it('Should set `progressTracking` to the state', () => {
const progressTracking = PROGRESS_COUNT;
setEpicMockData(state);
mutations[types.SET_PROGRESS_TRACKING](state, progressTracking);
expect(state).toMatchObject({
progressTracking,
});
});
});
}); });
...@@ -510,6 +510,12 @@ msgstr[1] "" ...@@ -510,6 +510,12 @@ msgstr[1] ""
msgid "%{completedWeight} of %{totalWeight} weight completed" msgid "%{completedWeight} of %{totalWeight} weight completed"
msgstr "" msgstr ""
msgid "%{completed} of %{total} issues closed"
msgstr ""
msgid "%{completed} of %{total} weight completed"
msgstr ""
msgid "%{cores} cores" msgid "%{cores} cores"
msgstr "" msgstr ""
...@@ -858,6 +864,9 @@ msgstr "" ...@@ -858,6 +864,9 @@ msgstr ""
msgid "%{openedIssues} open, %{closedIssues} closed" msgid "%{openedIssues} open, %{closedIssues} closed"
msgstr "" msgstr ""
msgid "%{percentage}%% issues closed"
msgstr ""
msgid "%{percentage}%% weight completed" msgid "%{percentage}%% weight completed"
msgstr "" msgstr ""
...@@ -1287,6 +1296,9 @@ msgid_plural "- Users" ...@@ -1287,6 +1296,9 @@ msgid_plural "- Users"
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
msgid "- of - issues closed"
msgstr ""
msgid "- of - weight completed" msgid "- of - weight completed"
msgstr "" msgstr ""
...@@ -27709,6 +27721,9 @@ msgstr "" ...@@ -27709,6 +27721,9 @@ msgstr ""
msgid "Progress" msgid "Progress"
msgstr "" msgstr ""
msgid "Progress tracking"
msgstr ""
msgid "Project" msgid "Project"
msgstr "" msgstr ""
...@@ -38998,6 +39013,12 @@ msgstr "" ...@@ -38998,6 +39013,12 @@ msgstr ""
msgid "Use hashed storage paths for newly created and renamed repositories. Always enabled since 13.0." msgid "Use hashed storage paths for newly created and renamed repositories. Always enabled since 13.0."
msgstr "" msgstr ""
msgid "Use issue count"
msgstr ""
msgid "Use issue weight"
msgstr ""
msgid "Use one line per URI" msgid "Use one line per URI"
msgstr "" msgstr ""
......
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