diff --git a/app/assets/stylesheets/utilities.scss b/app/assets/stylesheets/utilities.scss index d6aa63be996a93976683bcc8ecf1f2ba6f90b49e..57c9bcde024cd25771b98dc14beb7d7717d35136 100644 --- a/app/assets/stylesheets/utilities.scss +++ b/app/assets/stylesheets/utilities.scss @@ -200,6 +200,12 @@ } } +.gl-xl-ml-3 { + @include media-breakpoint-up(lg) { + margin-left: $gl-spacing-scale-3; + } +} + .gl-mb-n3 { margin-bottom: -$gl-spacing-scale-3; } diff --git a/ee/app/assets/javascripts/roadmap/components/epic_item_timeline.vue b/ee/app/assets/javascripts/roadmap/components/epic_item_timeline.vue index 0493bf223363a510175af3f3544ef59dcca23ea9..f8968a147e7074a5f19d009fa07cf51652ab2e84 100644 --- a/ee/app/assets/javascripts/roadmap/components/epic_item_timeline.vue +++ b/ee/app/assets/javascripts/roadmap/components/epic_item_timeline.vue @@ -57,7 +57,7 @@ export default { }, }, computed: { - ...mapState(['progressTracking']), + ...mapState(['progressTracking', 'isProgressTrackingActive']), timelineBarInnerStyle() { return { maxWidth: `${this.clientWidth - EPIC_DETAILS_CELL_WIDTH}px`, @@ -141,7 +141,10 @@ export default { <div class="epic-bar-inner gl-px-3 gl-py-2" :style="timelineBarInnerStyle"> <p class="epic-bar-title gl-text-truncate gl-m-0">{{ timelineBarTitle }}</p> - <div v-if="!isTimelineBarSmall" class="gl-display-flex gl-align-items-center"> + <div + v-if="!isTimelineBarSmall && isProgressTrackingActive" + class="gl-display-flex gl-align-items-center" + > <gl-progress-bar class="epic-bar-progress gl-flex-grow-1 gl-mr-2" :value="epicPercentage" diff --git a/ee/app/assets/javascripts/roadmap/components/roadmap_app.vue b/ee/app/assets/javascripts/roadmap/components/roadmap_app.vue index 1f20de196a6cc79888a859bc503eb15fceb33d25..c400fad0e7628e31ef172dbe9892dd871a063536 100644 --- a/ee/app/assets/javascripts/roadmap/components/roadmap_app.vue +++ b/ee/app/assets/javascripts/roadmap/components/roadmap_app.vue @@ -82,7 +82,7 @@ export default { :timeframe-range-type="timeframeRangeType" @toggleSettings="toggleSettings" /> - <div :class="{ 'overflow-reset': epicsFetchResultEmpty }" class="roadmap-container"> + <div :class="{ 'overflow-reset': epicsFetchResultEmpty }" class="roadmap-container gl-relative"> <gl-loading-icon v-if="epicsFetchInProgress" class="gl-mt-5" size="md" /> <epics-list-empty v-else-if="epicsFetchResultEmpty" diff --git a/ee/app/assets/javascripts/roadmap/components/roadmap_daterange.vue b/ee/app/assets/javascripts/roadmap/components/roadmap_daterange.vue index a1c26950d02cd50ea74fc11fcb7780962777fa28..7727d7fa977a2d635cd48e1e62bc8365b28c4650 100644 --- a/ee/app/assets/javascripts/roadmap/components/roadmap_daterange.vue +++ b/ee/app/assets/javascripts/roadmap/components/roadmap_daterange.vue @@ -94,7 +94,7 @@ export default { <gl-dropdown id="roadmap-daterange" icon="calendar" - class="gl-mb-3 roadmap-daterange-dropdown" + class="roadmap-daterange-dropdown" toggle-class="gl-rounded-base!" :text="daterangeDropdownText" data-testid="daterange-dropdown" @@ -110,7 +110,7 @@ export default { {{ dateRange.text }} </gl-dropdown-item> </gl-dropdown> - <gl-form-group v-if="availablePresets.length" class="gl-mb-0"> + <gl-form-group v-if="availablePresets.length" class="gl-mb-0 gl-mt-3"> <gl-form-radio-group data-testid="daterange-presets" :checked="presetType" diff --git a/ee/app/assets/javascripts/roadmap/components/roadmap_filters.vue b/ee/app/assets/javascripts/roadmap/components/roadmap_filters.vue index 53a3acbf08b608b60c2d83455e0cf35d2a102817..16a7e078027ee4520fe7cb133c5b8b37abd045cb 100644 --- a/ee/app/assets/javascripts/roadmap/components/roadmap_filters.vue +++ b/ee/app/assets/javascripts/roadmap/components/roadmap_filters.vue @@ -76,6 +76,7 @@ export default { 'epicsState', 'sortedBy', 'filterParams', + 'isProgressTrackingActive', 'progressTracking', 'isShowingMilestones', 'milestonesType', @@ -254,7 +255,7 @@ export default { <gl-button v-if="glFeatures.roadmapSettings" icon="settings" - class="gl-mb-3 gl-lg-ml-3 gl-sm-mt-3" + class="gl-mb-3 gl-xl-ml-3 gl-inset-border-1-gray-400!" :aria-label="$options.i18n.settings" data-testid="settings-button" @click="$emit('toggleSettings', $event)" diff --git a/ee/app/assets/javascripts/roadmap/components/roadmap_progress_tracking.vue b/ee/app/assets/javascripts/roadmap/components/roadmap_progress_tracking.vue index 218849831925624d1b9dd393170b410362b72da8..c47f503d8131bea40bfc6463409428dfa4c222a0 100644 --- a/ee/app/assets/javascripts/roadmap/components/roadmap_progress_tracking.vue +++ b/ee/app/assets/javascripts/roadmap/components/roadmap_progress_tracking.vue @@ -1,5 +1,5 @@ <script> -import { GlFormGroup, GlFormRadioGroup } from '@gitlab/ui'; +import { GlFormGroup, GlFormRadioGroup, GlToggle } from '@gitlab/ui'; import { mapActions, mapState } from 'vuex'; import { __ } from '~/locale'; @@ -9,12 +9,13 @@ export default { components: { GlFormGroup, GlFormRadioGroup, + GlToggle, }, computed: { - ...mapState(['progressTracking']), + ...mapState(['progressTracking', 'isProgressTrackingActive']), }, methods: { - ...mapActions(['setProgressTracking']), + ...mapActions(['setProgressTracking', 'toggleProgressTrackingActive']), handleProgressTrackingChange(option) { if (option !== this.progressTracking) { this.setProgressTracking(option); @@ -23,6 +24,7 @@ export default { }, i18n: { header: __('Progress tracking'), + toggleLabel: __('Display progress of child issues'), }, PROGRESS_TRACKING_OPTIONS, }; @@ -33,12 +35,24 @@ export default { <gl-form-group class="gl-mb-0" :label="$options.i18n.header" + label-class="gl-pb-2!" data-testid="roadmap-progress-tracking" > + <gl-toggle + :value="isProgressTrackingActive" + :label="$options.i18n.toggleLabel" + @change="toggleProgressTrackingActive" + > + <template #label> + <span class="gl-font-weight-normal">{{ $options.i18n.toggleLabel }}</span> + </template> + </gl-toggle> <gl-form-radio-group + v-if="isProgressTrackingActive" :checked="progressTracking" stacked :options="$options.PROGRESS_TRACKING_OPTIONS" + class="gl-mt-3" @change="handleProgressTrackingChange" /> </gl-form-group> diff --git a/ee/app/assets/javascripts/roadmap/components/roadmap_settings.vue b/ee/app/assets/javascripts/roadmap/components/roadmap_settings.vue index 30062040302becc90b6ddc42fcfed846283f3c0d..c98dc1a680e37701f8a5f86bf3969ffdd4385dc7 100644 --- a/ee/app/assets/javascripts/roadmap/components/roadmap_settings.vue +++ b/ee/app/assets/javascripts/roadmap/components/roadmap_settings.vue @@ -23,18 +23,6 @@ export default { required: true, }, }, - methods: { - getDrawerHeaderHeight() { - const wrapperEl = document.querySelector('.roadmap-container'); - - if (wrapperEl) { - const topPosition = wrapperEl.getBoundingClientRect().top + window.pageYOffset; - return `${topPosition}px`; - } - - return ''; - }, - }, }; </script> @@ -42,7 +30,7 @@ export default { <gl-drawer v-bind="$attrs" :open="isOpen" - :header-height="getDrawerHeaderHeight()" + class="gl-absolute" @close="$emit('toggleSettings', $event)" > <template #title> diff --git a/ee/app/assets/javascripts/roadmap/mixins/filtered_search_mixin.js b/ee/app/assets/javascripts/roadmap/mixins/filtered_search_mixin.js index 6f48855409cc0bdc07f41d596eb0ecf1f7dbc438..ab4134af26d458297cce8c9aa0021da70eef574c 100644 --- a/ee/app/assets/javascripts/roadmap/mixins/filtered_search_mixin.js +++ b/ee/app/assets/javascripts/roadmap/mixins/filtered_search_mixin.js @@ -54,6 +54,7 @@ export default { 'not[my_reaction_emoji]': notMyReactionEmoji, 'not[label_name][]': notLabelName, progress: this.progressTracking, + show_progress: this.isProgressTrackingActive, show_milestones: this.isShowingMilestones, milestones_type: this.milestonesType, }; diff --git a/ee/app/assets/javascripts/roadmap/roadmap_bundle.js b/ee/app/assets/javascripts/roadmap/roadmap_bundle.js index 90fb6c4e435d56498a62016b45f3b5ff969f2f43..30aaf6b89e9495c06c591a75c19bb001c2bd4d5a 100644 --- a/ee/app/assets/javascripts/roadmap/roadmap_bundle.js +++ b/ee/app/assets/javascripts/roadmap/roadmap_bundle.js @@ -109,6 +109,10 @@ export default () => { presetType, timeframe, progressTracking: rawFilterParams.progress || PROGRESS_WEIGHT, + isProgressTrackingActive: + rawFilterParams.show_progress === undefined + ? true + : parseBoolean(rawFilterParams.show_progress), isShowingMilestones: rawFilterParams.show_milestones === undefined ? true @@ -132,6 +136,7 @@ export default () => { isChildEpics: this.isChildEpics, hasFiltersApplied: this.hasFiltersApplied, allowSubEpics: this.allowSubEpics, + isProgressTrackingActive: this.isProgressTrackingActive, progressTracking: this.progressTracking, isShowingMilestones: this.isShowingMilestones, milestonesType: this.milestonesType, diff --git a/ee/app/assets/javascripts/roadmap/store/actions.js b/ee/app/assets/javascripts/roadmap/store/actions.js index ca3819e4a5c2c42eb5a4d16a6fbf1a3a6794b2c2..65c6c016a3c1ac596308834637045aff57c16439 100644 --- a/ee/app/assets/javascripts/roadmap/store/actions.js +++ b/ee/app/assets/javascripts/roadmap/store/actions.js @@ -330,6 +330,9 @@ export const setSortedBy = ({ commit }, sortedBy) => commit(types.SET_SORTED_BY, export const setProgressTracking = ({ commit }, progressTracking) => commit(types.SET_PROGRESS_TRACKING, progressTracking); +export const toggleProgressTrackingActive = ({ commit }) => + commit(types.TOGGLE_PROGRESS_TRACKING_ACTIVE); + export const setMilestonesType = ({ commit }, milestonesType) => commit(types.SET_MILESTONES_TYPE, milestonesType); diff --git a/ee/app/assets/javascripts/roadmap/store/mutation_types.js b/ee/app/assets/javascripts/roadmap/store/mutation_types.js index 8d31cc5a01f7a97deda4862fa60e6a832b1eff4f..f1ed1aabe3b7ffb93f493c99061afe813b0cba38 100644 --- a/ee/app/assets/javascripts/roadmap/store/mutation_types.js +++ b/ee/app/assets/javascripts/roadmap/store/mutation_types.js @@ -33,5 +33,6 @@ export const SET_DATERANGE = 'SET_DATERANGE'; export const SET_FILTER_PARAMS = 'SET_FILTER_PARAMS'; export const SET_SORTED_BY = 'SET_SORTED_BY'; export const SET_PROGRESS_TRACKING = 'SET_PROGRESS_TRACKING'; +export const TOGGLE_PROGRESS_TRACKING_ACTIVE = 'TOGGLE_PROGRESS_TRACKING_ACTIVE'; export const SET_MILESTONES_TYPE = 'SET_MILESTONES_TYPE'; export const TOGGLE_MILESTONES = 'TOGGLE_MILESTONES'; diff --git a/ee/app/assets/javascripts/roadmap/store/mutations.js b/ee/app/assets/javascripts/roadmap/store/mutations.js index c5f00571ca0d31f62b848ab532995a93d73a8036..7a0a68cc90b1e9efc11a671c8dcd57817e13b76d 100644 --- a/ee/app/assets/javascripts/roadmap/store/mutations.js +++ b/ee/app/assets/javascripts/roadmap/store/mutations.js @@ -145,6 +145,10 @@ export default { state.progressTracking = progressTracking; }, + [types.TOGGLE_PROGRESS_TRACKING_ACTIVE](state) { + state.isProgressTrackingActive = !state.isProgressTrackingActive; + }, + [types.SET_MILESTONES_TYPE](state, milestonesType) { state.milestonesType = milestonesType; }, diff --git a/ee/app/assets/javascripts/roadmap/store/state.js b/ee/app/assets/javascripts/roadmap/store/state.js index 5d7c288a8922101cee43153e203898fcab6decf1..ecf4beafeda9b9e34d3fdbe98cc1fc22d008ab1e 100644 --- a/ee/app/assets/javascripts/roadmap/store/state.js +++ b/ee/app/assets/javascripts/roadmap/store/state.js @@ -3,6 +3,7 @@ export default () => ({ basePath: '', epicsState: '', progressTracking: '', + isProgressTrackingActive: true, filterParams: null, isShowingMilestones: true, milestonesType: '', diff --git a/ee/app/assets/stylesheets/page_bundles/roadmap.scss b/ee/app/assets/stylesheets/page_bundles/roadmap.scss index 7e4741b6fd2a816051feeafbe0e8951200d5b849..405f72c12ab3e92209a3786400fefc897c0d1bdc 100644 --- a/ee/app/assets/stylesheets/page_bundles/roadmap.scss +++ b/ee/app/assets/stylesheets/page_bundles/roadmap.scss @@ -525,11 +525,9 @@ html.group-epics-roadmap-html { .sort-dropdown-container { // This override is needed to make sort-dropdown have same height // as filtered search bar. - @include media-breakpoint-up(sm) { - .dropdown, - > button { - margin-bottom: $gl-padding-8; - } + .dropdown, + > button { + margin-bottom: $gl-padding-8; } } } diff --git a/ee/spec/features/groups/group_roadmap_spec.rb b/ee/spec/features/groups/group_roadmap_spec.rb index 4e2555b00c30122352c8bfb05e6574fd3ffe5a10..302e0a259da288e39878e92a4c513f5462734ab5 100644 --- a/ee/spec/features/groups/group_roadmap_spec.rb +++ b/ee/spec/features/groups/group_roadmap_spec.rb @@ -247,6 +247,43 @@ def select_state(state) end end + describe 'roadmap with epics progress tracking' do + def wait_for_epics(count, icon) + page.within('.roadmap-container .epics-list-section') do + expect(page).to have_selector('.epic-bar-progress', count: count) + expect(page).to have_selector("[data-testid='#{icon}']", count: count) + end + end + + before do + open_settings_sidebar + end + + it 'renders progress bar using weight', :aggregate_failures do + choose 'Use issue weight' + + wait_for_epics(3, "weight-icon") + end + + it 'renders progress bar issue count', :aggregate_failures do + choose 'Use issue count' + + wait_for_epics(3, "issue-closed-icon") + end + + it 'turns off progress tracking', :aggregate_failures do + page.within('[data-testid="roadmap-progress-tracking"]') do + click_button class: 'gl-toggle' + end + + page.within('.roadmap-container .epics-list-section') do + expect(page).not_to have_selector('.epic-bar-progress') + expect(page).not_to have_selector('[data-testid="issue-closed-icon"]') + expect(page).not_to have_selector('[data-testid="weight-icon"]') + end + end + end + describe 'roadmap milestones settings' do def select_milestones(milestones) page.within('[data-testid="roadmap-milestones-settings"]') do diff --git a/ee/spec/frontend/roadmap/components/epic_item_timeline_spec.js b/ee/spec/frontend/roadmap/components/epic_item_timeline_spec.js index f0852367ea13b468eb5bc2312c85a3761f33d628..d66ad0f0ac3a2da28287cdad60ab23f66f09119f 100644 --- a/ee/spec/frontend/roadmap/components/epic_item_timeline_spec.js +++ b/ee/spec/frontend/roadmap/components/epic_item_timeline_spec.js @@ -23,11 +23,13 @@ const createComponent = ({ timeframeItem = mockTimeframeMonths[0], timeframeString = '', progressTracking = PROGRESS_WEIGHT, + isProgressTrackingActive = true, } = {}) => { const store = createStore(); store.dispatch('setInitialData', { progressTracking, + isProgressTrackingActive, }); return shallowMount(EpicItemTimeline, { @@ -75,6 +77,16 @@ describe('EpicItemTimelineComponent', () => { expect(getEpicBar(wrapper).attributes('href')).toBe(mockFormattedEpic.webUrl); }); + it.each` + isProgressTrackingActive + ${true} + ${false} + `('displays tracking depending on isProgressTrackingActive', ({ isProgressTrackingActive }) => { + wrapper = createComponent({ isProgressTrackingActive }); + + expect(wrapper.findComponent(GlProgressBar).exists()).toBe(isProgressTrackingActive); + }); + it.each` progressTracking | icon ${PROGRESS_WEIGHT} | ${'weight'} diff --git a/ee/spec/frontend/roadmap/components/roadmap_filters_spec.js b/ee/spec/frontend/roadmap/components/roadmap_filters_spec.js index b76aaa474eae7d5aad8fb4067b59d7734c0c44d1..67e7e13526489048130578670deed130a69e21d1 100644 --- a/ee/spec/frontend/roadmap/components/roadmap_filters_spec.js +++ b/ee/spec/frontend/roadmap/components/roadmap_filters_spec.js @@ -61,6 +61,7 @@ const createComponent = ({ sortedBy, filterParams, timeframe, + isProgressTrackingActive: true, progressTracking: PROGRESS_WEIGHT, milestonesType: MILESTONES_ALL, }); @@ -124,7 +125,7 @@ describe('RoadmapFilters', () => { await nextTick(); 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&progress=WEIGHT&show_milestones=true&milestones_type=ALL`, + `${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&show_progress=true&show_milestones=true&milestones_type=ALL`, ); }); }); diff --git a/ee/spec/frontend/roadmap/components/roadmap_progress_tracking_spec.js b/ee/spec/frontend/roadmap/components/roadmap_progress_tracking_spec.js index d45548b33eba515e78d858cde6eeb5a478658571..bb55a5313ebdc52cea40032baed1a0651bd8c249 100644 --- a/ee/spec/frontend/roadmap/components/roadmap_progress_tracking_spec.js +++ b/ee/spec/frontend/roadmap/components/roadmap_progress_tracking_spec.js @@ -8,11 +8,12 @@ import { PROGRESS_WEIGHT, PROGRESS_TRACKING_OPTIONS } from 'ee/roadmap/constants describe('RoadmapProgressTracking', () => { let wrapper; - const createComponent = () => { + const createComponent = ({ isProgressTrackingActive = true } = {}) => { const store = createStore(); store.dispatch('setInitialData', { progressTracking: PROGRESS_WEIGHT, + isProgressTrackingActive, }); wrapper = shallowMountExtended(RoadmapProgressTracking, { @@ -37,9 +38,20 @@ describe('RoadmapProgressTracking', () => { 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); - }); + it.each` + isProgressTrackingActive + ${true} + ${false} + `( + 'displays radio form group depending on isProgressTrackingActive', + ({ isProgressTrackingActive }) => { + createComponent({ isProgressTrackingActive }); + + expect(findFormRadioGroup().exists()).toBe(isProgressTrackingActive); + if (isProgressTrackingActive) { + expect(findFormRadioGroup().props('options')).toEqual(PROGRESS_TRACKING_OPTIONS); + } + }, + ); }); }); diff --git a/ee/spec/frontend/roadmap/store/actions_spec.js b/ee/spec/frontend/roadmap/store/actions_spec.js index 01cc8fe272891d371ee3f4f6caaa848df2b2f73a..c51cb8acbd47c85a693f4402e27217b5064d4c8a 100644 --- a/ee/spec/frontend/roadmap/store/actions_spec.js +++ b/ee/spec/frontend/roadmap/store/actions_spec.js @@ -673,6 +673,18 @@ describe('Roadmap Vuex Actions', () => { }); }); + describe('toggleProgressTrackingActive', () => { + it('commit TOGGLE_PROGRESS_TRACKING_ACTIVE mutation', () => { + return testAction( + actions.toggleProgressTrackingActive, + undefined, + state, + [{ type: types.TOGGLE_PROGRESS_TRACKING_ACTIVE }], + [], + ); + }); + }); + describe('setMilestonesType', () => { it('should set milestonesType in store state', () => { return testAction( diff --git a/ee/spec/frontend/roadmap/store/mutations_spec.js b/ee/spec/frontend/roadmap/store/mutations_spec.js index 5a0fbe685904f402db470e4a6238f3f71d45ec55..2d9be0ed5b41c17ed2408be46bf07bfa9485e1e6 100644 --- a/ee/spec/frontend/roadmap/store/mutations_spec.js +++ b/ee/spec/frontend/roadmap/store/mutations_spec.js @@ -347,7 +347,6 @@ 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); @@ -357,6 +356,20 @@ describe('Roadmap Store Mutations', () => { }); }); + describe('TOGGLE_PROGRESS_TRACKING_ACTIVE', () => { + it('Should toggle `progressTracking` on state', () => { + expect(state).toMatchObject({ + isProgressTrackingActive: true, + }); + + mutations[types.TOGGLE_PROGRESS_TRACKING_ACTIVE](state); + + expect(state).toMatchObject({ + isProgressTrackingActive: false, + }); + }); + }); + describe('SET_MILESTONES_TYPE', () => { it('Should set `milestonesType` to the state', () => { const milestonesType = MILESTONES_GROUP; diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 2f3fad4e70339e39751e4a9f21d7c43b9f68f866..8769f0fcede5bea0353b3326aa7b91a5a7acc8e8 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -12869,6 +12869,9 @@ msgstr "" msgid "Display name" msgstr "" +msgid "Display progress of child issues" +msgstr "" + msgid "Display rendered file" msgstr ""