Commit 90b5a6c8 authored by Kushal Pandya's avatar Kushal Pandya

Merge branch 'persist-collapsed-swimlanes' into 'master'

Persist collapsed state to DB

See merge request gitlab-org/gitlab!43681
parents 47a3b9ab 969eb2dc
......@@ -2,6 +2,7 @@
import { GlButton, GlIcon, GlLink, GlLoadingIcon, GlPopover, GlTooltipDirective } from '@gitlab/ui';
import { mapActions, mapGetters, mapState } from 'vuex';
import { __, n__, sprintf } from '~/locale';
import createFlash from '~/flash';
import timeagoMixin from '~/vue_shared/mixins/timeago';
import { formatDate } from '~/lib/utils/datetime_utility';
import { statusType } from '../../epic/constants';
......@@ -40,8 +41,12 @@ export default {
},
},
data() {
const { userPreferences } = this.epic;
const { collapsed = false } = userPreferences || {};
return {
isExpanded: true,
isCollapsed: collapsed,
};
},
computed: {
......@@ -51,10 +56,10 @@ export default {
return this.epic.state === statusType.open;
},
chevronTooltip() {
return this.isExpanded ? __('Collapse') : __('Expand');
return this.isCollapsed ? __('Expand') : __('Collapse');
},
chevronIcon() {
return this.isExpanded ? 'chevron-down' : 'chevron-right';
return this.isCollapsed ? 'chevron-right' : 'chevron-down';
},
issuesCount() {
return this.lists.reduce(
......@@ -95,9 +100,16 @@ export default {
this.fetchIssuesForEpic(this.epic.id);
},
methods: {
...mapActions(['fetchIssuesForEpic']),
toggleExpanded() {
this.isExpanded = !this.isExpanded;
...mapActions(['fetchIssuesForEpic', 'updateBoardEpicUserPreferences']),
toggleCollapsed() {
this.isCollapsed = !this.isCollapsed;
this.updateBoardEpicUserPreferences({
collapsed: this.isCollapsed,
epicId: this.epic.id,
}).catch(() => {
createFlash({ message: __('Unable to save your preference'), captureError: true });
});
},
},
};
......@@ -115,7 +127,7 @@ export default {
class="gl-mr-2 gl-cursor-pointer"
variant="link"
data-testid="epic-lane-chevron"
@click="toggleExpanded"
@click="toggleCollapsed"
/>
<h4
ref="epicTitle"
......@@ -146,7 +158,7 @@ export default {
<gl-loading-icon v-if="isLoading" class="gl-p-2" />
</div>
</div>
<div v-if="isExpanded" class="gl-display-flex" data-testid="board-epic-lane-issues">
<div v-if="!isCollapsed" class="gl-display-flex" data-testid="board-epic-lane-issues">
<issues-lane-list
v-for="list in lists"
:key="`${list.id}-issues`"
......
......@@ -21,6 +21,9 @@ query BoardEE(
edges {
node {
...BoardEpicNode
userPreferences {
collapsed
}
}
}
pageInfo {
......@@ -41,6 +44,9 @@ query BoardEE(
edges {
node {
...BoardEpicNode
userPreferences {
collapsed
}
}
}
pageInfo {
......
mutation updateBoardEpicUserPreferences(
$boardId: BoardID!
$epicId: EpicID!
$collapsed: Boolean!
) {
updateBoardEpicUserPreferences(
input: { boardId: $boardId, epicId: $epicId, collapsed: $collapsed }
) {
errors
epicUserPreferences {
collapsed
}
}
}
......@@ -26,6 +26,7 @@ import issueSetEpic from '../queries/issue_set_epic.mutation.graphql';
import issueSetWeight from '../queries/issue_set_weight.mutation.graphql';
import listsIssuesQuery from '~/boards/queries/lists_issues.query.graphql';
import issueMoveListMutation from '../queries/issue_move_list.mutation.graphql';
import updateBoardEpicUserPreferencesMutation from '../queries/updateBoardEpicUserPreferences.mutation.graphql';
const notImplemented = () => {
/* eslint-disable-next-line @gitlab/require-i18n-strings */
......@@ -140,6 +141,40 @@ export default {
.catch(() => commit(types.RECEIVE_SWIMLANES_FAILURE));
},
updateBoardEpicUserPreferences({ commit, state }, { epicId, collapsed }) {
const {
endpoints: { boardId },
} = state;
const variables = {
boardId: fullBoardId(boardId),
epicId,
collapsed,
};
return gqlClient
.mutate({
mutation: updateBoardEpicUserPreferencesMutation,
variables,
})
.then(({ data }) => {
if (data?.updateBoardEpicUserPreferences?.errors.length) {
throw new Error();
}
const { epicUserPreferences: userPreferences } = data?.updateBoardEpicUserPreferences;
commit(types.SET_BOARD_EPIC_USER_PREFERENCES, { epicId, userPreferences });
})
.catch(() => {
commit(types.SET_BOARD_EPIC_USER_PREFERENCES, {
epicId,
userPreferences: {
collapsed: !collapsed,
},
});
});
},
setShowLabels({ commit }, val) {
commit(types.SET_SHOW_LABELS, val);
},
......
......@@ -28,3 +28,4 @@ export const SET_FILTERS = 'SET_FILTERS';
export const MOVE_ISSUE = 'MOVE_ISSUE';
export const MOVE_ISSUE_SUCCESS = 'MOVE_ISSUE_SUCCESS';
export const MOVE_ISSUE_FAILURE = 'MOVE_ISSUE_FAILURE';
export const SET_BOARD_EPIC_USER_PREFERENCES = 'SET_BOARD_EPIC_USER_PREFERENCES';
......@@ -155,4 +155,14 @@ export default {
removeIssueFromList({ state, listId: fromListId, issueId: issue.id });
addIssueToList({ state, listId: toListId, issueId: issue.id, moveBeforeId, moveAfterId });
},
[mutationTypes.SET_BOARD_EPIC_USER_PREFERENCES]: (state, val) => {
const { userPreferences, epicId } = val;
const epic = state.epics.filter(currentEpic => currentEpic.id === epicId)[0];
if (epic) {
Vue.set(epic, 'userPreferences', userPreferences);
}
},
};
---
title: Persist collapsed state to DB
merge_request: 43681
author:
type: added
......@@ -14,10 +14,13 @@ describe('EpicLane', () => {
const findByTestId = testId => wrapper.find(`[data-testid="${testId}"]`);
const updateBoardEpicUserPreferencesSpy = jest.fn();
const createStore = isLoading => {
return new Vuex.Store({
actions: {
fetchIssuesForEpic: jest.fn(),
updateBoardEpicUserPreferences: updateBoardEpicUserPreferencesSpy,
},
state: {
issuesByListId: mockIssuesByListId,
......@@ -76,13 +79,13 @@ describe('EpicLane', () => {
it('hides issues when collapsing', () => {
expect(wrapper.findAll(IssuesLaneList)).toHaveLength(wrapper.props('lists').length);
expect(wrapper.vm.isExpanded).toBe(true);
expect(wrapper.vm.isCollapsed).toBe(false);
findByTestId('epic-lane-chevron').vm.$emit('click');
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.findAll(IssuesLaneList)).toHaveLength(0);
expect(wrapper.vm.isExpanded).toBe(false);
expect(wrapper.vm.isCollapsed).toBe(true);
});
});
......@@ -95,5 +98,26 @@ describe('EpicLane', () => {
expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
expect(findByTestId('epic-lane-issue-count').exists()).toBe(false);
});
it('invokes `updateBoardEpicUserPreferences` method on collapse', () => {
const collapsedValue = false;
expect(wrapper.vm.isCollapsed).toBe(collapsedValue);
findByTestId('epic-lane-chevron').vm.$emit('click');
return wrapper.vm.$nextTick().then(() => {
expect(updateBoardEpicUserPreferencesSpy).toHaveBeenCalled();
const payload = updateBoardEpicUserPreferencesSpy.mock.calls[0][1];
expect(payload).toEqual({
collapsed: !collapsedValue,
epicId: mockEpic.id,
});
expect(wrapper.vm.isCollapsed).toBe(true);
});
});
});
});
......@@ -165,6 +165,47 @@ describe('fetchEpicsSwimlanes', () => {
});
});
describe('updateBoardEpicUserPreferences', () => {
const state = {
endpoints: {
boardId: 1,
},
};
const queryResponse = (collapsed = false) => ({
data: {
updateBoardEpicUserPreferences: {
errors: [],
epicUserPreferences: { collapsed },
},
},
});
it('should send mutation', done => {
const collapsed = true;
jest.spyOn(gqlClient, 'mutate').mockResolvedValue(queryResponse(collapsed));
testAction(
actions.updateBoardEpicUserPreferences,
{ epicId: mockEpic.id, collapsed },
state,
[
{
payload: {
epicId: mockEpic.id,
userPreferences: {
collapsed,
},
},
type: types.SET_BOARD_EPIC_USER_PREFERENCES,
},
],
[],
done,
);
});
});
describe('setShowLabels', () => {
it('should commit mutation SET_SHOW_LABELS', done => {
const state = {
......
......@@ -319,3 +319,19 @@ describe('MOVE_ISSUE', () => {
expect(state.issues['437'].epic).toEqual(null);
});
});
describe('SET_BOARD_EPIC_USER_PREFERENCES', () => {
it('should replace userPreferences on the given epic', () => {
state = {
...state,
epics: mockEpics,
};
const epic = mockEpics[0];
const userPreferences = { collapsed: false };
mutations.SET_BOARD_EPIC_USER_PREFERENCES(state, { epicId: epic.id, userPreferences });
expect(state.epics[0].userPreferences).toEqual(userPreferences);
});
});
......@@ -27967,6 +27967,9 @@ msgstr ""
msgid "Unable to save your changes. Please try again."
msgstr ""
msgid "Unable to save your preference"
msgstr ""
msgid "Unable to schedule a pipeline to run immediately"
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