Commit 94e71a8e authored by Florie Guibert's avatar Florie Guibert

Introduce Roadmap settings sidebar

Introduce roadmap_settings feature flag
parent c200c4f8
---
name: roadmap_settings
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/78626
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/350830
milestone: '14.8'
type: development
group: group::product planning
default_enabled: false
......@@ -2,9 +2,12 @@
import { GlLoadingIcon } from '@gitlab/ui';
import { mapState, mapActions } from 'vuex';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { DATE_RANGES } from '../constants';
import EpicsListEmpty from './epics_list_empty.vue';
import RoadmapFilters from './roadmap_filters.vue';
import RoadmapSettings from './roadmap_settings.vue';
import RoadmapShell from './roadmap_shell.vue';
export default {
......@@ -12,8 +15,10 @@ export default {
EpicsListEmpty,
GlLoadingIcon,
RoadmapFilters,
RoadmapSettings,
RoadmapShell,
},
mixins: [glFeatureFlagMixin()],
props: {
timeframeRangeType: {
type: String,
......@@ -29,6 +34,11 @@ export default {
required: true,
},
},
data() {
return {
isSettingsSidebarOpen: false,
};
},
computed: {
...mapState([
'currentGroupId',
......@@ -66,6 +76,9 @@ export default {
},
methods: {
...mapActions(['fetchEpics', 'fetchMilestones']),
toggleSettings() {
this.isSettingsSidebarOpen = !this.isSettingsSidebarOpen;
},
},
};
</script>
......@@ -75,6 +88,7 @@ export default {
<roadmap-filters
v-if="showFilteredSearchbar && !epicIid"
:timeframe-range-type="timeframeRangeType"
@toggleSettings="toggleSettings"
/>
<div :class="{ 'overflow-reset': epicsFetchResultEmpty }" class="roadmap-container">
<gl-loading-icon v-if="epicsFetchInProgress" class="gl-mt-5" size="md" />
......@@ -96,6 +110,13 @@ export default {
:timeframe="timeframe"
:current-group-id="currentGroupId"
:has-filters-applied="hasFiltersApplied"
:is-settings-sidebar-open="isSettingsSidebarOpen"
/>
<roadmap-settings
v-if="glFeatures.roadmapSettings"
:is-open="isSettingsSidebarOpen"
data-testid="roadmap-settings"
@toggleSettings="toggleSettings"
/>
</div>
</div>
......
<script>
import {
GlButton,
GlFormGroup,
GlSegmentedControl,
GlDropdown,
......@@ -11,6 +12,7 @@ import { mapState, mapActions } from 'vuex';
import { visitUrl, mergeUrlParams, updateHistory, setUrlParams } from '~/lib/utils/url_utility';
import { __, s__ } from '~/locale';
import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { EPICS_STATES, PRESET_TYPES, DATE_RANGES } from '../constants';
import EpicsFilteredSearchMixin from '../mixins/filtered_search_mixin';
......@@ -48,6 +50,7 @@ export default {
},
],
components: {
GlButton,
GlFormGroup,
GlSegmentedControl,
GlDropdown,
......@@ -55,7 +58,7 @@ export default {
GlDropdownDivider,
FilteredSearchBar,
},
mixins: [EpicsFilteredSearchMixin],
mixins: [EpicsFilteredSearchMixin, glFeatureFlagMixin()],
props: {
timeframeRangeType: {
type: String,
......@@ -161,6 +164,9 @@ export default {
this.fetchEpics();
},
},
i18n: {
settings: __('Settings'),
},
};
</script>
......@@ -232,6 +238,16 @@ export default {
@onFilter="handleFilterEpics"
@onSort="handleSortEpics"
/>
<gl-button
v-if="glFeatures.roadmapSettings"
icon="settings"
class="gl-mb-3 gl-lg-ml-3 gl-sm-mt-3"
:aria-label="$options.i18n.settings"
data-testid="settings-button"
@click="$emit('toggleSettings', $event)"
>
{{ $options.i18n.settings }}
</gl-button>
</div>
</div>
</template>
<script>
import { GlDrawer } from '@gitlab/ui';
export default {
components: {
GlDrawer,
},
props: {
isOpen: {
type: Boolean,
required: true,
},
},
methods: {
getDrawerHeaderHeight() {
const wrapperEl = document.querySelector('.roadmap-container');
if (wrapperEl) {
return `${wrapperEl.offsetTop}px`;
}
return '';
},
},
};
</script>
<template>
<gl-drawer
v-bind="$attrs"
:open="isOpen"
:header-height="getDrawerHeaderHeight()"
@close="$emit('toggleSettings', $event)"
>
<template #title>
<h2 class="gl-my-0 gl-font-size-h2 gl-line-height-24">{{ __('Roadmap settings') }}</h2>
</template>
</gl-drawer>
</template>
......@@ -135,7 +135,7 @@ html.group-epics-roadmap-html {
position: sticky;
position: -webkit-sticky;
top: 0;
z-index: 20;
z-index: 9;
.timeline-header-blank,
.timeline-header-item {
......
......@@ -8,6 +8,10 @@ module Groups
before_action :check_epics_available!
before_action :persist_roadmap_layout, only: [:show]
before_action do
push_frontend_feature_flag(:roadmap_settings, @group, default_enabled: :yaml)
end
feature_category :portfolio_management
# show roadmap for a group
......
......@@ -119,6 +119,19 @@ RSpec.describe 'group epic roadmap', :js do
expect(page).to have_selector('.epics-list-item .epic-title', count: 3)
end
end
it 'toggles settings sidebar on click settings button' do
page.within('.content-wrapper .content') do
expect(page).not_to have_selector('[data-testid="roadmap-sidebar"]')
expect(page).to have_selector('[data-testid="settings-button"]')
click_button 'Settings'
expect(page).to have_selector('[data-testid="roadmap-settings"]')
click_button 'Settings'
expect(page).not_to have_selector('[data-testid="roadmap-settings"]')
end
end
end
describe 'roadmap page with epics state filter' do
......
import { GlLoadingIcon } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vue from 'vue';
import Vuex from 'vuex';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import EpicsListEmpty from 'ee/roadmap/components/epics_list_empty.vue';
import RoadmapApp from 'ee/roadmap/components/roadmap_app.vue';
import RoadmapFilters from 'ee/roadmap/components/roadmap_filters.vue';
......@@ -18,13 +19,12 @@ import {
mockTimeframeInitialDate,
} from 'ee_jest/roadmap/mock_data';
Vue.use(Vuex);
describe('RoadmapApp', () => {
const localVue = createLocalVue();
let store;
let wrapper;
localVue.use(Vuex);
const currentGroupId = mockGroupId;
const emptyStateIllustrationPath = mockSvgPath;
const epics = [mockFormattedEpic];
......@@ -36,9 +36,8 @@ describe('RoadmapApp', () => {
initialDate: mockTimeframeInitialDate,
});
const createComponent = (mountFunction = shallowMount) => {
return mountFunction(RoadmapApp, {
localVue,
const createComponent = ({ roadmapSettings = false } = {}) => {
return shallowMountExtended(RoadmapApp, {
propsData: {
emptyStateIllustrationPath,
presetType,
......@@ -47,11 +46,14 @@ describe('RoadmapApp', () => {
groupFullPath: 'gitlab-org',
groupMilestonesPath: '/groups/gitlab-org/-/milestones.json',
listEpicsPath: '/groups/gitlab-org/-/epics',
glFeatures: { roadmapSettings },
},
store,
});
};
const findSettingsSidebar = () => wrapper.findByTestId('roadmap-settings');
beforeEach(() => {
store = createStore();
store.dispatch('setInitialData', {
......@@ -67,7 +69,6 @@ describe('RoadmapApp', () => {
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
describe.each`
......@@ -152,5 +153,19 @@ describe('RoadmapApp', () => {
milestones: [],
});
});
it('does not render settings sidebar', () => {
expect(findSettingsSidebar().exists()).toBe(false);
});
describe('when roadmapSettings feature flag is on', () => {
beforeEach(() => {
wrapper = createComponent({ roadmapSettings: true });
});
it('renders settings button', () => {
expect(findSettingsSidebar().exists()).toBe(true);
});
});
});
});
import { GlSegmentedControl, GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { createLocalVue } from '@vue/test-utils';
import Vue from 'vue';
import Vuex from 'vuex';
import RoadmapFilters from 'ee/roadmap/components/roadmap_filters.vue';
......@@ -29,6 +29,8 @@ jest.mock('~/lib/utils/url_utility', () => ({
updateHistory: jest.requireActual('~/lib/utils/url_utility').updateHistory,
}));
Vue.use(Vuex);
const createComponent = ({
presetType = PRESET_TYPES.MONTHS,
epicsState = EPICS_STATES.ALL,
......@@ -43,12 +45,10 @@ const createComponent = ({
}),
filterParams = {},
timeframeRangeType = DATE_RANGES.THREE_YEARS,
roadmapSettings = false,
} = {}) => {
const localVue = createLocalVue();
const store = createStore();
localVue.use(Vuex);
store.dispatch('setInitialData', {
presetType,
epicsState,
......@@ -58,12 +58,12 @@ const createComponent = ({
});
return shallowMountExtended(RoadmapFilters, {
localVue,
store,
provide: {
groupFullPath,
groupMilestonesPath,
listEpicsPath,
glFeatures: { roadmapSettings },
},
props: {
timeframeRangeType,
......@@ -73,6 +73,7 @@ const createComponent = ({
describe('RoadmapFilters', () => {
let wrapper;
const findSettingsButton = () => wrapper.findByTestId('settings-button');
beforeEach(() => {
wrapper = createComponent();
......@@ -153,6 +154,10 @@ describe('RoadmapFilters', () => {
expect(epicsStateDropdown.findAll(GlDropdownItem)).toHaveLength(3);
});
it('does not render settings button', () => {
expect(findSettingsButton().exists()).toBe(false);
});
describe('FilteredSearchBar', () => {
const mockInitialFilterValue = [
{
......@@ -383,4 +388,20 @@ describe('RoadmapFilters', () => {
);
});
});
describe('when roadmapSettings feature flag is on', () => {
beforeEach(() => {
wrapper = createComponent({ roadmapSettings: true });
});
it('renders settings button', () => {
expect(findSettingsButton().exists()).toBe(true);
});
it('emits toggleSettings event on click settings button', () => {
findSettingsButton().vm.$emit('click');
expect(wrapper.emitted('toggleSettings')).toBeTruthy();
});
});
});
import { GlDrawer } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import RoadmapSettings from 'ee/roadmap/components/roadmap_settings.vue';
describe('RoadmapSettings', () => {
let wrapper;
const createComponent = ({ isOpen = false } = {}) => {
wrapper = shallowMountExtended(RoadmapSettings, {
propsData: { isOpen },
});
};
const findSettingsDrawer = () => wrapper.findComponent(GlDrawer);
beforeEach(() => {
createComponent();
});
afterEach(() => {
wrapper.destroy();
});
describe('template', () => {
it('render drawer and title', () => {
expect(findSettingsDrawer().exists()).toBe(true);
expect(findSettingsDrawer().text()).toContain('Roadmap settings');
});
});
});
......@@ -30633,6 +30633,9 @@ msgstr ""
msgid "Roadmap"
msgstr ""
msgid "Roadmap settings"
msgstr ""
msgid "Role"
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