Commit 72fbaf67 authored by Florie Guibert's avatar Florie Guibert

Swimlanes - Reorder lists

Feedback
parent a249542e
...@@ -139,24 +139,28 @@ export default { ...@@ -139,24 +139,28 @@ export default {
eventHub.$emit(`toggle-issue-form-${this.list.id}`); eventHub.$emit(`toggle-issue-form-${this.list.id}`);
}, },
toggleExpanded() { toggleExpanded() {
if (this.list.isExpandable) { this.list.isExpanded = !this.list.isExpanded;
this.list.isExpanded = !this.list.isExpanded;
if (AccessorUtilities.isLocalStorageAccessSafe() && !this.isLoggedIn) { if (!this.isLoggedIn) {
localStorage.setItem(`${this.uniqueKey}.expanded`, this.list.isExpanded); this.addToLocalStorage();
} } else {
this.updateListFunction();
if (this.isLoggedIn) { }
if (this.glFeatures.boardsWithSwimlanes && this.isSwimlanesHeader) {
this.updateList({ listId: this.list.id, collapsed: !this.list.isExpanded });
} else {
this.list.update();
}
}
// When expanding/collapsing, the tooltip on the caret button sometimes stays open. // When expanding/collapsing, the tooltip on the caret button sometimes stays open.
// Close all tooltips manually to prevent dangling tooltips. // Close all tooltips manually to prevent dangling tooltips.
this.$root.$emit('bv::hide::tooltip'); this.$root.$emit('bv::hide::tooltip');
},
addToLocalStorage() {
if (AccessorUtilities.isLocalStorageAccessSafe()) {
localStorage.setItem(`${this.uniqueKey}.expanded`, this.list.isExpanded);
}
},
updateListFunction() {
if (this.glFeatures.boardsWithSwimlanes && this.isSwimlanesHeader) {
this.updateList({ listId: this.list.id, collapsed: !this.list.isExpanded });
} else {
this.list.update();
} }
}, },
}, },
......
...@@ -148,23 +148,25 @@ export default { ...@@ -148,23 +148,25 @@ export default {
notImplemented(); notImplemented();
}, },
moveList: ({ state, commit, dispatch }, { listId, position, moveNewIndexListUp }) => { moveList: ({ state, commit, dispatch }, { listId, newIndex, adjustmentValue }) => {
const { boardLists } = state; const { boardLists } = state;
const backupList = [...boardLists];
const movedList = boardLists.find(({ id }) => id === listId); const movedList = boardLists.find(({ id }) => id === listId);
const listAtNewIndex = boardLists[position + 1];
movedList.position = position; const newPosition = newIndex - 1;
listAtNewIndex.position = moveNewIndexListUp const listAtNewIndex = boardLists[newIndex];
? listAtNewIndex.position + 1
: listAtNewIndex.position - 1; movedList.position = newPosition;
listAtNewIndex.position += adjustmentValue;
commit(types.MOVE_LIST, { commit(types.MOVE_LIST, {
movedList, movedList,
listAtNewIndex, listAtNewIndex,
}); });
dispatch('updateList', { listId, position }); dispatch('updateList', { listId, position: newPosition, backupList });
}, },
updateList: ({ commit }, { listId, position, collapsed }) => { updateList: ({ commit }, { listId, position, collapsed, backupList }) => {
gqlClient gqlClient
.mutate({ .mutate({
mutation: updateBoardListMutation, mutation: updateBoardListMutation,
...@@ -176,11 +178,11 @@ export default { ...@@ -176,11 +178,11 @@ export default {
}) })
.then(({ data }) => { .then(({ data }) => {
if (data?.updateBoardList?.errors.length) { if (data?.updateBoardList?.errors.length) {
commit(types.UPDATE_LIST_FAILURE); commit(types.UPDATE_LIST_FAILURE, backupList);
} }
}) })
.catch(() => { .catch(() => {
commit(types.UPDATE_LIST_FAILURE); commit(types.UPDATE_LIST_FAILURE, backupList);
}); });
}, },
......
...@@ -48,16 +48,15 @@ export default { ...@@ -48,16 +48,15 @@ export default {
[mutationTypes.MOVE_LIST]: (state, { movedList, listAtNewIndex }) => { [mutationTypes.MOVE_LIST]: (state, { movedList, listAtNewIndex }) => {
const { boardLists } = state; const { boardLists } = state;
state.boardListsPreviousState = boardLists;
const movedListIndex = state.boardLists.findIndex(l => l.id === movedList.id); const movedListIndex = state.boardLists.findIndex(l => l.id === movedList.id);
Vue.set(boardLists, movedListIndex, movedList); Vue.set(boardLists, movedListIndex, movedList);
Vue.set(boardLists, movedListIndex.position + 1, listAtNewIndex); Vue.set(boardLists, movedListIndex.position + 1, listAtNewIndex);
state.boardLists = sortBy(boardLists, 'position'); Vue.set(state, 'boardLists', sortBy(boardLists, 'position'));
}, },
[mutationTypes.UPDATE_LIST_FAILURE]: state => { [mutationTypes.UPDATE_LIST_FAILURE]: (state, backupList) => {
state.boardLists = state.boardListsPreviousState;
state.error = __('An error occurred while updating the list. Please try again.'); state.error = __('An error occurred while updating the list. Please try again.');
Vue.set(state, 'boardLists', backupList);
}, },
[mutationTypes.REQUEST_REMOVE_LIST]: () => { [mutationTypes.REQUEST_REMOVE_LIST]: () => {
......
...@@ -9,7 +9,6 @@ export default () => ({ ...@@ -9,7 +9,6 @@ export default () => ({
activeId: inactiveId, activeId: inactiveId,
sidebarType: '', sidebarType: '',
boardLists: [], boardLists: [],
boardListsPreviousState: [],
issuesByListId: {}, issuesByListId: {},
issues: {}, issues: {},
isLoadingIssues: false, isLoadingIssues: false,
......
...@@ -116,7 +116,6 @@ ...@@ -116,7 +116,6 @@
.board-title { .board-title {
flex-direction: column; flex-direction: column;
height: 100%;
} }
.board-title-caret { .board-title-caret {
......
...@@ -3,7 +3,7 @@ import { mapActions, mapGetters, mapState } from 'vuex'; ...@@ -3,7 +3,7 @@ import { mapActions, mapGetters, mapState } from 'vuex';
import { GlIcon, GlTooltipDirective } from '@gitlab/ui'; import { GlIcon, GlTooltipDirective } from '@gitlab/ui';
import Draggable from 'vuedraggable'; import Draggable from 'vuedraggable';
import BoardListHeader from 'ee_else_ce/boards/components/board_list_header.vue'; import BoardListHeader from 'ee_else_ce/boards/components/board_list_header.vue';
import { draggableTag } from '../constants'; import { DRAGGABLE_TAG } from '../constants';
import defaultSortableConfig from '~/sortable/sortable_config'; import defaultSortableConfig from '~/sortable/sortable_config';
import { n__ } from '~/locale'; import { n__ } from '~/locale';
import EpicLane from './epic_lane.vue'; import EpicLane from './epic_lane.vue';
...@@ -57,14 +57,14 @@ export default { ...@@ -57,14 +57,14 @@ export default {
return n__(`%d unassigned issue`, `%d unassigned issues`, this.unassignedIssuesCount); return n__(`%d unassigned issue`, `%d unassigned issues`, this.unassignedIssuesCount);
}, },
treeRootWrapper() { treeRootWrapper() {
return this.canAdminList ? Draggable : draggableTag; return this.canAdminList ? Draggable : DRAGGABLE_TAG;
}, },
treeRootOptions() { treeRootOptions() {
const options = { const options = {
...defaultSortableConfig, ...defaultSortableConfig,
fallbackOnBody: false, fallbackOnBody: false,
group: 'board-swimlanes', group: 'board-swimlanes',
tag: draggableTag, tag: DRAGGABLE_TAG,
draggable: '.is-draggable', draggable: '.is-draggable',
'ghost-class': 'swimlane-header-drag-active', 'ghost-class': 'swimlane-header-drag-active',
value: this.lists, value: this.lists,
...@@ -81,17 +81,11 @@ export default { ...@@ -81,17 +81,11 @@ export default {
handleDragOnEnd(params) { handleDragOnEnd(params) {
const { newIndex, oldIndex, item } = params; const { newIndex, oldIndex, item } = params;
const { listId } = item.dataset; const { listId } = item.dataset;
const newPosition = newIndex - 1;
let moveNewIndexListUp = false;
if (newIndex < oldIndex) {
moveNewIndexListUp = true;
}
this.moveList({ this.moveList({
listId, listId,
position: newPosition, newIndex,
moveNewIndexListUp, adjustmentValue: newIndex < oldIndex ? 1 : -1,
}); });
}, },
}, },
...@@ -107,6 +101,7 @@ export default { ...@@ -107,6 +101,7 @@ export default {
:is="treeRootWrapper" :is="treeRootWrapper"
v-bind="treeRootOptions" v-bind="treeRootOptions"
class="board-swimlanes-headers gl-display-table gl-sticky gl-pt-5 gl-bg-white gl-top-0 gl-z-index-3" class="board-swimlanes-headers gl-display-table gl-sticky gl-pt-5 gl-bg-white gl-top-0 gl-z-index-3"
data-testid="board-swimlanes-headers"
@end="handleDragOnEnd" @end="handleDragOnEnd"
> >
<div <div
......
export const draggableTag = 'div'; export const DRAGGABLE_TAG = 'div';
export default { export default {
draggableTag, DRAGGABLE_TAG,
}; };
...@@ -6,7 +6,6 @@ import BoardListHeader from 'ee_else_ce/boards/components/board_list_header.vue' ...@@ -6,7 +6,6 @@ import BoardListHeader from 'ee_else_ce/boards/components/board_list_header.vue'
import EpicLane from 'ee/boards/components/epic_lane.vue'; import EpicLane from 'ee/boards/components/epic_lane.vue';
import IssueLaneList from 'ee/boards/components/issues_lane_list.vue'; import IssueLaneList from 'ee/boards/components/issues_lane_list.vue';
import getters from 'ee/boards/stores/getters'; import getters from 'ee/boards/stores/getters';
import { draggableTag } from 'ee/boards/constants';
import { GlIcon } from '@gitlab/ui'; import { GlIcon } from '@gitlab/ui';
import { mockListsWithModel, mockEpics, mockIssuesByListId, issues } from '../mock_data'; import { mockListsWithModel, mockEpics, mockIssuesByListId, issues } from '../mock_data';
...@@ -52,44 +51,25 @@ describe('EpicsSwimlanes', () => { ...@@ -52,44 +51,25 @@ describe('EpicsSwimlanes', () => {
}); });
describe('computed', () => { describe('computed', () => {
beforeEach(() => {
createComponent();
});
describe('treeRootWrapper', () => { describe('treeRootWrapper', () => {
it('should return Draggable reference when canAdminList prop is true', () => { describe('when canAdminList prop is true', () => {
wrapper.setProps({ canAdminList: true }); beforeEach(() => {
createComponent({ canAdminList: true });
expect(wrapper.vm.treeRootWrapper).toBe(Draggable); });
it('should return Draggable reference when canAdminList prop is true', () => {
expect(wrapper.find(Draggable).exists()).toBe(true);
});
}); });
it('should return string "div" when canAdminList prop is false', () => { describe('when canAdminList prop is false', () => {
expect(wrapper.vm.treeRootWrapper).toBe(draggableTag); beforeEach(() => {
}); createComponent();
}); });
describe('treeRootOptions', () => {
it('should return object containing Vue.Draggable config extended from `defaultSortableConfig` when canAdminList prop is true', () => {
wrapper.setProps({ canAdminList: true });
expect(wrapper.vm.treeRootOptions).toEqual(
expect.objectContaining({
animation: 200,
forceFallback: true,
fallbackClass: 'is-dragging',
fallbackOnBody: false,
ghostClass: 'is-ghost',
group: 'board-swimlanes',
tag: draggableTag,
draggable: '.is-draggable',
'ghost-class': 'swimlane-header-drag-active',
value: mockListsWithModel,
}),
);
});
it('should return an empty object when canAdminList prop is false', () => { it('should not return Draggable reference when canAdminList prop is false', () => {
expect(wrapper.vm.treeRootOptions).toEqual(expect.objectContaining({})); expect(wrapper.find(Draggable).exists()).toBe(false);
});
}); });
}); });
}); });
......
...@@ -172,7 +172,7 @@ describe('moveList', () => { ...@@ -172,7 +172,7 @@ describe('moveList', () => {
testAction( testAction(
actions.moveList, actions.moveList,
{ listId: 'gid://gitlab/List/1', position: 0, moveNewIndexListUp: true }, { listId: 'gid://gitlab/List/1', newIndex: 1, adjustmentValue: 1 },
state, state,
[ [
{ {
...@@ -180,7 +180,12 @@ describe('moveList', () => { ...@@ -180,7 +180,12 @@ describe('moveList', () => {
payload: { movedList: mockListsWithModel[0], listAtNewIndex: mockListsWithModel[1] }, payload: { movedList: mockListsWithModel[0], listAtNewIndex: mockListsWithModel[1] },
}, },
], ],
[{ type: 'updateList', payload: { listId: 'gid://gitlab/List/1', position: 0 } }], [
{
type: 'updateList',
payload: { listId: 'gid://gitlab/List/1', position: 0, backupList: mockListsWithModel },
},
],
done, done,
); );
}); });
......
...@@ -93,7 +93,7 @@ describe('Board Store Mutations', () => { ...@@ -93,7 +93,7 @@ describe('Board Store Mutations', () => {
}); });
describe('MOVE_LIST', () => { describe('MOVE_LIST', () => {
it('updates boardLists state with reordered lists and saves previous order', () => { it('updates boardLists state with reordered lists', () => {
state = { state = {
...state, ...state,
boardLists: mockListsWithModel, boardLists: mockListsWithModel,
...@@ -105,7 +105,6 @@ describe('Board Store Mutations', () => { ...@@ -105,7 +105,6 @@ describe('Board Store Mutations', () => {
}); });
expect(state.boardLists).toEqual([mockListsWithModel[1], mockListsWithModel[0]]); expect(state.boardLists).toEqual([mockListsWithModel[1], mockListsWithModel[0]]);
expect(state.boardListsPreviousState).toEqual(mockListsWithModel);
}); });
}); });
...@@ -114,11 +113,10 @@ describe('Board Store Mutations', () => { ...@@ -114,11 +113,10 @@ describe('Board Store Mutations', () => {
state = { state = {
...state, ...state,
boardLists: [mockListsWithModel[1], mockListsWithModel[0]], boardLists: [mockListsWithModel[1], mockListsWithModel[0]],
boardListsPreviousState: mockListsWithModel,
error: undefined, error: undefined,
}; };
mutations.UPDATE_LIST_FAILURE(state); mutations.UPDATE_LIST_FAILURE(state, mockListsWithModel);
expect(state.boardLists).toEqual(mockListsWithModel); expect(state.boardLists).toEqual(mockListsWithModel);
expect(state.error).toEqual('An error occurred while updating the list. Please try again.'); expect(state.error).toEqual('An error occurred while updating the list. Please try again.');
......
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