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';
import initNewListDropdown from 'ee_else_ce/boards/components/new_list_dropdown';
import boardConfigToggle from 'ee_else_ce/boards/config_toggle';
import toggleLabels from 'ee_else_ce/boards/toggle_labels';
import toggleEpicsSwimlanes from 'ee_else_ce/boards/toggle_epics_swimlanes';
import {
setPromotionState,
setWeigthFetchingState,
......@@ -371,5 +372,6 @@ export default () => {
toggleFocusMode(ModalStore, boardsStore);
toggleLabels();
toggleEpicsSwimlanes();
mountMultipleBoardsSwitcher();
};
......@@ -541,7 +541,8 @@
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
* filtered-search-box input element increases in size on Linux smaller breakpoints
......
......@@ -173,6 +173,8 @@
= render 'shared/issuable/board_create_list_dropdown', board: board
- if @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
- elsif is_not_boards_modal_or_productivity_analytics && show_sorting_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 {
togglePromotionState: () => {
notImplemented();
},
toggleEpicSwimlanes: ({ commit }) => {
commit(types.TOGGLE_EPICS_SWIMLANES);
},
};
......@@ -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 TOGGLE_PROMOTION_STATE = 'TOGGLE_PROMOTION_STATE';
export const TOGGLE_LABELS = 'TOGGLE_LABELS';
export const TOGGLE_EPICS_SWIMLANES = 'TOGGLE_EPICS_SWIMLANES';
export const SET_ACTIVE_LIST_ID = 'SET_ACTIVE_LIST_ID';
......@@ -64,4 +64,8 @@ export default {
[mutationTypes.TOGGLE_PROMOTION_STATE]: () => {
notImplemented();
},
[mutationTypes.TOGGLE_EPICS_SWIMLANES]: state => {
state.isShowingEpicsSwimlanes = !state.isShowingEpicsSwimlanes;
},
};
......@@ -3,5 +3,5 @@ import createStateCE from '~/boards/stores/state';
export default () => ({
...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
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
let!(:label) { create(:label, project: project, name: 'Label 1') }
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', () => {
describe('togglePromotionState', () => {
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', () => {
describe('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 ""
msgid "Group avatar"
msgstr ""
msgid "Group by:"
msgstr ""
msgid "Group could not be imported: %{errors}"
msgstr ""
......@@ -14531,6 +14534,9 @@ msgstr ""
msgid "No forks are available to you."
msgstr ""
msgid "No grouping"
msgstr ""
msgid "No job log"
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