Commit d31d6150 authored by Florie Guibert's avatar Florie Guibert

Swimlanes - Display switch to group by epics

Introduced feature flag :boards_with_swimlanes
parent 7809246b
...@@ -7,6 +7,7 @@ import BoardSidebar from 'ee_else_ce/boards/components/board_sidebar'; ...@@ -7,6 +7,7 @@ import BoardSidebar from 'ee_else_ce/boards/components/board_sidebar';
import initNewListDropdown from 'ee_else_ce/boards/components/new_list_dropdown'; import initNewListDropdown from 'ee_else_ce/boards/components/new_list_dropdown';
import boardConfigToggle from 'ee_else_ce/boards/config_toggle'; import boardConfigToggle from 'ee_else_ce/boards/config_toggle';
import toggleLabels from 'ee_else_ce/boards/toggle_labels'; import toggleLabels from 'ee_else_ce/boards/toggle_labels';
import toggleEpicsSwimlanes from 'ee_else_ce/boards/toggle_epics_swimlanes';
import { import {
setPromotionState, setPromotionState,
setWeigthFetchingState, setWeigthFetchingState,
...@@ -371,5 +372,6 @@ export default () => { ...@@ -371,5 +372,6 @@ export default () => {
toggleFocusMode(ModalStore, boardsStore); toggleFocusMode(ModalStore, boardsStore);
toggleLabels(); toggleLabels();
toggleEpicsSwimlanes();
mountMultipleBoardsSwitcher(); mountMultipleBoardsSwitcher();
}; };
...@@ -541,7 +541,8 @@ ...@@ -541,7 +541,8 @@
cursor: help; cursor: help;
} }
.board-labels-toggle-wrapper { .board-labels-toggle-wrapper,
.board-swimlanes-toggle-wrapper {
/** /**
* Make the wrapper the same height as a button so it aligns properly when the * Make the wrapper the same height as a button so it aligns properly when the
* filtered-search-box input element increases in size on Linux smaller breakpoints * filtered-search-box input element increases in size on Linux smaller breakpoints
......
...@@ -173,6 +173,8 @@ ...@@ -173,6 +173,8 @@
= render 'shared/issuable/board_create_list_dropdown', board: board = render 'shared/issuable/board_create_list_dropdown', board: board
- if @project - if @project
#js-add-issues-btn.prepend-left-10{ data: { can_admin_list: can?(current_user, :admin_list, @project) } } #js-add-issues-btn.prepend-left-10{ data: { can_admin_list: can?(current_user, :admin_list, @project) } }
- if Feature.enabled?(:boards_with_swimlanes)
#js-board-epics-swimlanes-toggle
#js-toggle-focus-btn #js-toggle-focus-btn
- elsif is_not_boards_modal_or_productivity_analytics && show_sorting_dropdown - elsif is_not_boards_modal_or_productivity_analytics && show_sorting_dropdown
= render 'shared/issuable/sort_dropdown' = render 'shared/issuable/sort_dropdown'
<script>
import { mapState, mapActions } from 'vuex';
import { GlNewDropdown as GlDropdown, GlNewDropdownItem as GlDropdownItem } from '@gitlab/ui';
import { __ } from '~/locale';
export default {
components: {
GlDropdown,
GlDropdownItem,
},
computed: {
...mapState(['isShowingEpicsSwimlanes']),
groupByEpicLabel() {
return __('Epic');
},
groupByNoneLabel() {
return __('No grouping');
},
dropdownLabel() {
return this.isShowingEpicsSwimlanes ? this.groupByEpicLabel : __('None');
},
},
methods: {
...mapActions(['toggleEpicSwimlanes']),
onToggle() {
this.toggleEpicSwimlanes();
},
},
};
</script>
<template>
<div
class="board-swimlanes-toggle-wrapper gl-display-flex gl-align-items-center prepend-left-10"
data-testid="toggle-swimlanes"
>
<span
class="board-swimlanes-toggle-text gl-white-space-nowrap"
data-testid="toggle-swimlanes-label"
>
{{ __('Group by:') }}
</span>
<gl-dropdown
right
:text="dropdownLabel"
toggle-class="gl-ml-2 gl-border-none gl-inset-border-1-gray-400! border-radius-default"
>
<gl-dropdown-item
:is-check-item="true"
:is-checked="!isShowingEpicsSwimlanes"
@click="toggleEpicSwimlanes()"
>{{ groupByNoneLabel }}</gl-dropdown-item
>
<gl-dropdown-item
:is-check-item="true"
:is-checked="isShowingEpicsSwimlanes"
@click="toggleEpicSwimlanes()"
>{{ groupByEpicLabel }}</gl-dropdown-item
>
</gl-dropdown>
</div>
</template>
...@@ -51,4 +51,8 @@ export default { ...@@ -51,4 +51,8 @@ export default {
togglePromotionState: () => { togglePromotionState: () => {
notImplemented(); notImplemented();
}, },
toggleEpicSwimlanes: ({ commit }) => {
commit(types.TOGGLE_EPICS_SWIMLANES);
},
}; };
...@@ -12,4 +12,5 @@ export const RECEIVE_REMOVE_BOARD_SUCCESS = 'RECEIVE_REMOVE_BOARD_SUCCESS'; ...@@ -12,4 +12,5 @@ export const RECEIVE_REMOVE_BOARD_SUCCESS = 'RECEIVE_REMOVE_BOARD_SUCCESS';
export const RECEIVE_REMOVE_BOARD_ERROR = 'RECEIVE_REMOVE_BOARD_ERROR'; export const RECEIVE_REMOVE_BOARD_ERROR = 'RECEIVE_REMOVE_BOARD_ERROR';
export const TOGGLE_PROMOTION_STATE = 'TOGGLE_PROMOTION_STATE'; export const TOGGLE_PROMOTION_STATE = 'TOGGLE_PROMOTION_STATE';
export const TOGGLE_LABELS = 'TOGGLE_LABELS'; export const TOGGLE_LABELS = 'TOGGLE_LABELS';
export const TOGGLE_EPICS_SWIMLANES = 'TOGGLE_EPICS_SWIMLANES';
export const SET_ACTIVE_LIST_ID = 'SET_ACTIVE_LIST_ID'; export const SET_ACTIVE_LIST_ID = 'SET_ACTIVE_LIST_ID';
...@@ -64,4 +64,8 @@ export default { ...@@ -64,4 +64,8 @@ export default {
[mutationTypes.TOGGLE_PROMOTION_STATE]: () => { [mutationTypes.TOGGLE_PROMOTION_STATE]: () => {
notImplemented(); notImplemented();
}, },
[mutationTypes.TOGGLE_EPICS_SWIMLANES]: state => {
state.isShowingEpicsSwimlanes = !state.isShowingEpicsSwimlanes;
},
}; };
...@@ -3,5 +3,5 @@ import createStateCE from '~/boards/stores/state'; ...@@ -3,5 +3,5 @@ import createStateCE from '~/boards/stores/state';
export default () => ({ export default () => ({
...createStateCE(), ...createStateCE(),
// ... isShowingEpicsSwimlanes: false,
}); });
import Vue from 'vue';
import ToggleEpicsSwimlanes from './components/toggle_epics_swimlanes.vue';
import store from '~/boards/stores';
export default () =>
new Vue({
el: document.getElementById('js-board-epics-swimlanes-toggle'),
components: {
ToggleEpicsSwimlanes,
},
store,
render(createElement) {
return createElement(ToggleEpicsSwimlanes);
},
});
...@@ -74,6 +74,32 @@ describe 'issue boards', :js do ...@@ -74,6 +74,32 @@ describe 'issue boards', :js do
end end
end end
context 'swimlanes dropdown' do
context 'feature flag on' do
before do
stub_feature_flags(boards_with_swimlanes: true)
end
it 'shows Group by dropdown' do
visit_board_page
expect(page).to have_css('.board-swimlanes-toggle-wrapper')
end
end
context 'feature flag off' do
before do
stub_feature_flags(boards_with_swimlanes: false)
end
it 'does not show Group by dropdown' do
visit_board_page
expect(page).not_to have_css('.board-swimlanes-toggle-wrapper')
end
end
end
context 'total weight' do context 'total weight' do
let!(:label) { create(:label, project: project, name: 'Label 1') } let!(:label) { create(:label, project: project, name: 'Label 1') }
let!(:list) { create(:list, board: board, label: label, position: 0) } let!(:list) { create(:list, board: board, label: label, position: 0) }
......
import Vue from 'vue';
import Vuex from 'vuex';
import { mount } from '@vue/test-utils';
import { GlNewDropdown as GlDropdown, GlNewDropdownItem as GlDropdownItem } from '@gitlab/ui';
import ToggleEpicsSwimlanes from 'ee/boards/components/toggle_epics_swimlanes.vue';
Vue.use(Vuex);
describe('ToggleEpicsSwimlanes', () => {
let store;
let wrapper;
const createComponent = () => {
wrapper = mount(ToggleEpicsSwimlanes, { store });
};
afterEach(() => {
wrapper.destroy();
});
beforeEach(() => {
store = new Vuex.Store({ state: { isShowingEpicsSwimlanes: false } });
createComponent();
});
describe('dropdownLabel', () => {
it('displays "None" when isShowingEpicsSwimlanes is false', () => {
expect(wrapper.find(GlDropdown).props('text')).toEqual('None');
});
it('returns "Epics" when isShowingEpicsSwimlanes is true', () => {
store = new Vuex.Store({ state: { isShowingEpicsSwimlanes: true } });
createComponent();
expect(wrapper.find(GlDropdown).props('text')).toEqual('Epic');
});
});
describe('template', () => {
it('renders .board-swimlanes-toggle-wrapper container', () => {
expect(wrapper.find('[data-testid="toggle-swimlanes"]').exists()).toBe(true);
});
it('renders "Group by" label', () => {
expect(wrapper.find('[data-testid="toggle-swimlanes-label"]').text()).toEqual('Group by:');
});
it('renders dropdown with 2 options', () => {
expect(wrapper.find(GlDropdown).exists()).toBe(true);
expect(wrapper.findAll(GlDropdownItem).length).toEqual(2);
});
});
});
...@@ -90,3 +90,20 @@ describe('updateIssueWeight', () => { ...@@ -90,3 +90,20 @@ describe('updateIssueWeight', () => {
describe('togglePromotionState', () => { describe('togglePromotionState', () => {
expectNotImplemented(actions.updateIssueWeight); expectNotImplemented(actions.updateIssueWeight);
}); });
describe('toggleEpicSwimlanes', () => {
it('should commit mutation TOGGLE_EPICS_SWIMLANES', done => {
const state = {
isShowingEpicsSwimlanes: true,
};
testAction(
actions.toggleEpicSwimlanes,
null,
state,
[{ type: types.TOGGLE_EPICS_SWIMLANES }],
[],
done,
);
});
});
...@@ -93,3 +93,25 @@ describe('RECEIVE_REMOVE_BOARD_ERROR', () => { ...@@ -93,3 +93,25 @@ describe('RECEIVE_REMOVE_BOARD_ERROR', () => {
describe('TOGGLE_PROMOTION_STATE', () => { describe('TOGGLE_PROMOTION_STATE', () => {
expectNotImplemented(mutations.TOGGLE_PROMOTION_STATE); expectNotImplemented(mutations.TOGGLE_PROMOTION_STATE);
}); });
describe('TOGGLE_EPICS_SWIMLANES', () => {
it('toggles isShowingEpicsSwimlanes from true to false', () => {
const state = {
isShowingEpicsSwimlanes: true,
};
mutations.TOGGLE_EPICS_SWIMLANES(state);
expect(state.isShowingEpicsSwimlanes).toBe(false);
});
it('toggles isShowingEpicsSwimlanes from false to true', () => {
const state = {
isShowingEpicsSwimlanes: false,
};
mutations.TOGGLE_EPICS_SWIMLANES(state);
expect(state.isShowingEpicsSwimlanes).toBe(true);
});
});
...@@ -10796,6 +10796,9 @@ msgstr "" ...@@ -10796,6 +10796,9 @@ msgstr ""
msgid "Group avatar" msgid "Group avatar"
msgstr "" msgstr ""
msgid "Group by:"
msgstr ""
msgid "Group could not be imported: %{errors}" msgid "Group could not be imported: %{errors}"
msgstr "" msgstr ""
...@@ -14531,6 +14534,9 @@ msgstr "" ...@@ -14531,6 +14534,9 @@ msgstr ""
msgid "No forks are available to you." msgid "No forks are available to you."
msgstr "" msgstr ""
msgid "No grouping"
msgstr ""
msgid "No job log" msgid "No job log"
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