Commit 9cba2ad9 authored by Simon Knox's avatar Simon Knox Committed by Kushal Pandya

Use vuex and draggable for moving board lists

Still needs some specs for start/end behaviour
and maybe some kind of integration ones would be worthwhile
rather than testing draggable internals
parent 99382092
<script> <script>
import Draggable from 'vuedraggable';
import { mapState, mapGetters, mapActions } from 'vuex'; import { mapState, mapGetters, mapActions } from 'vuex';
import { sortBy } from 'lodash'; import { sortBy } from 'lodash';
import { GlAlert } from '@gitlab/ui'; import { GlAlert } from '@gitlab/ui';
import BoardColumn from 'ee_else_ce/boards/components/board_column.vue'; import BoardColumn from 'ee_else_ce/boards/components/board_column.vue';
import BoardColumnNew from './board_column_new.vue'; import BoardColumnNew from './board_column_new.vue';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import defaultSortableConfig from '~/sortable/sortable_config';
import { sortableEnd, sortableStart } from '~/boards/mixins/sortable_default_options';
export default { export default {
components: { components: {
...@@ -36,6 +39,25 @@ export default { ...@@ -36,6 +39,25 @@ export default {
? sortBy([...Object.values(this.boardLists)], 'position') ? sortBy([...Object.values(this.boardLists)], 'position')
: this.lists; : this.lists;
}, },
canDragColumns() {
return this.glFeatures.graphqlBoardLists && this.canAdminList;
},
boardColumnWrapper() {
return this.canDragColumns ? Draggable : 'div';
},
draggableOptions() {
const options = {
...defaultSortableConfig,
disabled: this.disabled,
draggable: '.is-draggable',
fallbackOnBody: false,
group: 'boards-list',
tag: 'div',
value: this.lists,
};
return this.canDragColumns ? options : {};
},
}, },
mounted() { mounted() {
if (this.glFeatures.graphqlBoardLists) { if (this.glFeatures.graphqlBoardLists) {
...@@ -43,7 +65,26 @@ export default { ...@@ -43,7 +65,26 @@ export default {
} }
}, },
methods: { methods: {
...mapActions(['showPromotionList']), ...mapActions(['moveList', 'showPromotionList']),
handleDragOnStart() {
sortableStart();
},
handleDragOnEnd(params) {
sortableEnd();
const { item, newIndex, oldIndex, to } = params;
const listId = item.dataset.id;
const replacedListId = to.children[newIndex].dataset.id;
this.moveList({
listId,
replacedListId,
newIndex,
adjustmentValue: newIndex < oldIndex ? 1 : -1,
});
},
}, },
}; };
</script> </script>
...@@ -53,16 +94,28 @@ export default { ...@@ -53,16 +94,28 @@ export default {
<gl-alert v-if="error" variant="danger" :dismissible="false"> <gl-alert v-if="error" variant="danger" :dismissible="false">
{{ error }} {{ error }}
</gl-alert> </gl-alert>
<div v-if="!isSwimlanesOn" class="boards-list gl-w-full gl-py-5 gl-px-3 gl-white-space-nowrap"> <component
:is="boardColumnWrapper"
v-if="!isSwimlanesOn"
ref="list"
v-bind="draggableOptions"
class="boards-list gl-w-full gl-py-5 gl-px-3 gl-white-space-nowrap"
data-qa-selector="boards_list"
@start="handleDragOnStart"
@end="handleDragOnEnd"
>
<board-column <board-column
v-for="list in boardListsToUse" v-for="list in boardListsToUse"
:key="list.id" :key="list.id"
ref="board" ref="board"
:can-admin-list="canAdminList" :can-admin-list="canAdminList"
:class="{
'is-draggable': !list.preset,
}"
:list="list" :list="list"
:disabled="disabled" :disabled="disabled"
/> />
</div> </component>
<template v-else> <template v-else>
<epics-swimlanes <epics-swimlanes
......
...@@ -180,6 +180,10 @@ export default { ...@@ -180,6 +180,10 @@ export default {
{ state, commit, dispatch }, { state, commit, dispatch },
{ listId, replacedListId, newIndex, adjustmentValue }, { listId, replacedListId, newIndex, adjustmentValue },
) => { ) => {
if (listId === replacedListId) {
return;
}
const { boardLists } = state; const { boardLists } = state;
const backupList = { ...boardLists }; const backupList = { ...boardLists };
const movedList = boardLists[listId]; const movedList = boardLists[listId];
......
import Vuex from 'vuex'; import Vuex from 'vuex';
import { createLocalVue, shallowMount } from '@vue/test-utils'; import { createLocalVue, shallowMount } from '@vue/test-utils';
import { GlAlert } from '@gitlab/ui'; import { GlAlert } from '@gitlab/ui';
import Draggable from 'vuedraggable';
import EpicsSwimlanes from 'ee_component/boards/components/epics_swimlanes.vue'; import EpicsSwimlanes from 'ee_component/boards/components/epics_swimlanes.vue';
import BoardColumn from 'ee_else_ce/boards/components/board_column.vue'; import BoardColumn from 'ee_else_ce/boards/components/board_column.vue';
import getters from 'ee_else_ce/boards/stores/getters'; import getters from 'ee_else_ce/boards/stores/getters';
...@@ -10,6 +11,11 @@ import BoardContent from '~/boards/components/board_content.vue'; ...@@ -10,6 +11,11 @@ import BoardContent from '~/boards/components/board_content.vue';
const localVue = createLocalVue(); const localVue = createLocalVue();
localVue.use(Vuex); localVue.use(Vuex);
const actions = {
moveList: jest.fn(),
showPromotionList: jest.fn(),
};
describe('BoardContent', () => { describe('BoardContent', () => {
let wrapper; let wrapper;
...@@ -21,12 +27,13 @@ describe('BoardContent', () => { ...@@ -21,12 +27,13 @@ describe('BoardContent', () => {
const createStore = (state = defaultState) => { const createStore = (state = defaultState) => {
return new Vuex.Store({ return new Vuex.Store({
actions,
getters, getters,
state, state,
}); });
}; };
const createComponent = state => { const createComponent = ({ state, props = {}, graphqlBoardListsEnabled = false } = {}) => {
const store = createStore({ const store = createStore({
...defaultState, ...defaultState,
...state, ...state,
...@@ -37,25 +44,61 @@ describe('BoardContent', () => { ...@@ -37,25 +44,61 @@ describe('BoardContent', () => {
lists: mockListsWithModel, lists: mockListsWithModel,
canAdminList: true, canAdminList: true,
disabled: false, disabled: false,
...props,
},
provide: {
glFeatures: { graphqlBoardLists: graphqlBoardListsEnabled },
}, },
store, store,
}); });
}; };
beforeEach(() => {
createComponent();
});
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
}); });
it('renders a BoardColumn component per list', () => { it('renders a BoardColumn component per list', () => {
createComponent();
expect(wrapper.findAll(BoardColumn)).toHaveLength(mockListsWithModel.length); expect(wrapper.findAll(BoardColumn)).toHaveLength(mockListsWithModel.length);
}); });
it('does not display EpicsSwimlanes component', () => { it('does not display EpicsSwimlanes component', () => {
createComponent();
expect(wrapper.find(EpicsSwimlanes).exists()).toBe(false); expect(wrapper.find(EpicsSwimlanes).exists()).toBe(false);
expect(wrapper.find(GlAlert).exists()).toBe(false); expect(wrapper.find(GlAlert).exists()).toBe(false);
}); });
describe('graphqlBoardLists feature flag enabled', () => {
describe('can admin list', () => {
beforeEach(() => {
createComponent({ graphqlBoardListsEnabled: true, props: { canAdminList: true } });
});
it('renders draggable component', () => {
expect(wrapper.find(Draggable).exists()).toBe(true);
});
});
describe('can not admin list', () => {
beforeEach(() => {
createComponent({ graphqlBoardListsEnabled: true, props: { canAdminList: false } });
});
it('renders draggable component', () => {
expect(wrapper.find(Draggable).exists()).toBe(false);
});
});
});
describe('graphqlBoardLists feature flag disabled', () => {
beforeEach(() => {
createComponent({ graphqlBoardListsEnabled: false });
});
it('does not render draggable component', () => {
expect(wrapper.find(Draggable).exists()).toBe(false);
});
});
}); });
...@@ -290,6 +290,33 @@ describe('moveList', () => { ...@@ -290,6 +290,33 @@ describe('moveList', () => {
done, done,
); );
}); });
it('should not commit MOVE_LIST or dispatch updateList if listId and replacedListId are the same', () => {
const initialBoardListsState = {
'gid://gitlab/List/1': mockListsWithModel[0],
'gid://gitlab/List/2': mockListsWithModel[1],
};
const state = {
endpoints: { fullPath: 'gitlab-org', boardId: '1' },
boardType: 'group',
disabled: false,
boardLists: initialBoardListsState,
};
testAction(
actions.moveList,
{
listId: 'gid://gitlab/List/1',
replacedListId: 'gid://gitlab/List/1',
newIndex: 1,
adjustmentValue: 1,
},
state,
[],
[],
);
});
}); });
describe('updateList', () => { describe('updateList', () => {
......
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