Commit 33e992a2 authored by Kushal Pandya's avatar Kushal Pandya

Merge branch '218040-swimlanes-pagination' into 'master'

Swimlanes Pagination - Fetch issues per epic

See merge request gitlab-org/gitlab!42320
parents 06debdcf 5f1f6c53
export const setPromotionState = () => {}; export const setPromotionState = () => {};
export const setWeigthFetchingState = () => {}; export const setWeightFetchingState = () => {};
export const setEpicFetchingState = () => {}; export const setEpicFetchingState = () => {};
export const getMilestoneTitle = () => ({}); export const getMilestoneTitle = () => ({});
......
...@@ -11,7 +11,7 @@ import toggleLabels from 'ee_else_ce/boards/toggle_labels'; ...@@ -11,7 +11,7 @@ import toggleLabels from 'ee_else_ce/boards/toggle_labels';
import toggleEpicsSwimlanes from 'ee_else_ce/boards/toggle_epics_swimlanes'; import toggleEpicsSwimlanes from 'ee_else_ce/boards/toggle_epics_swimlanes';
import { import {
setPromotionState, setPromotionState,
setWeigthFetchingState, setWeightFetchingState,
setEpicFetchingState, setEpicFetchingState,
getMilestoneTitle, getMilestoneTitle,
getBoardsModalData, getBoardsModalData,
...@@ -163,12 +163,7 @@ export default () => { ...@@ -163,12 +163,7 @@ export default () => {
} }
}, },
methods: { methods: {
...mapActions([ ...mapActions(['setInitialBoardData', 'setFilters', 'fetchEpicsSwimlanes', 'resetIssues']),
'setInitialBoardData',
'setFilters',
'fetchEpicsSwimlanes',
'fetchIssuesForAllLists',
]),
updateTokens() { updateTokens() {
this.filterManager.updateTokens(); this.filterManager.updateTokens();
}, },
...@@ -176,14 +171,14 @@ export default () => { ...@@ -176,14 +171,14 @@ export default () => {
this.setFilters(convertObjectPropsToCamelCase(urlParamsToObject(window.location.search))); this.setFilters(convertObjectPropsToCamelCase(urlParamsToObject(window.location.search)));
if (gon.features.boardsWithSwimlanes && this.isShowingEpicsSwimlanes) { if (gon.features.boardsWithSwimlanes && this.isShowingEpicsSwimlanes) {
this.fetchEpicsSwimlanes(false); this.fetchEpicsSwimlanes(false);
this.fetchIssuesForAllLists(); this.resetIssues();
} }
}, },
updateDetailIssue(newIssue, multiSelect = false) { updateDetailIssue(newIssue, multiSelect = false) {
const { sidebarInfoEndpoint } = newIssue; const { sidebarInfoEndpoint } = newIssue;
if (sidebarInfoEndpoint && newIssue.subscribed === undefined) { if (sidebarInfoEndpoint && newIssue.subscribed === undefined) {
newIssue.setFetchingState('subscriptions', true); newIssue.setFetchingState('subscriptions', true);
setWeigthFetchingState(newIssue, true); setWeightFetchingState(newIssue, true);
setEpicFetchingState(newIssue, true); setEpicFetchingState(newIssue, true);
boardsStore boardsStore
.getIssueInfo(sidebarInfoEndpoint) .getIssueInfo(sidebarInfoEndpoint)
...@@ -201,7 +196,7 @@ export default () => { ...@@ -201,7 +196,7 @@ export default () => {
} = convertObjectPropsToCamelCase(data); } = convertObjectPropsToCamelCase(data);
newIssue.setFetchingState('subscriptions', false); newIssue.setFetchingState('subscriptions', false);
setWeigthFetchingState(newIssue, false); setWeightFetchingState(newIssue, false);
setEpicFetchingState(newIssue, false); setEpicFetchingState(newIssue, false);
newIssue.updateData({ newIssue.updateData({
humanTimeSpent: humanTotalTimeSpent, humanTimeSpent: humanTotalTimeSpent,
...@@ -216,7 +211,7 @@ export default () => { ...@@ -216,7 +211,7 @@ export default () => {
}) })
.catch(() => { .catch(() => {
newIssue.setFetchingState('subscriptions', false); newIssue.setFetchingState('subscriptions', false);
setWeigthFetchingState(newIssue, false); setWeightFetchingState(newIssue, false);
Flash(__('An error occurred while fetching sidebar data')); Flash(__('An error occurred while fetching sidebar data'));
}); });
} }
......
...@@ -226,31 +226,8 @@ export default { ...@@ -226,31 +226,8 @@ export default {
.catch(() => commit(types.RECEIVE_ISSUES_FOR_LIST_FAILURE, listId)); .catch(() => commit(types.RECEIVE_ISSUES_FOR_LIST_FAILURE, listId));
}, },
fetchIssuesForAllLists: ({ state, commit }) => { resetIssues: ({ commit }) => {
commit(types.REQUEST_ISSUES_FOR_ALL_LISTS); commit(types.RESET_ISSUES);
const { endpoints, boardType, filterParams } = state;
const { fullPath, boardId } = endpoints;
const variables = {
fullPath,
boardId: fullBoardId(boardId),
filters: filterParams,
isGroup: boardType === BoardType.group,
isProject: boardType === BoardType.project,
};
return gqlClient
.query({
query: listsIssuesQuery,
variables,
})
.then(({ data }) => {
const { lists } = data[boardType]?.board;
const listIssues = formatListIssues(lists);
commit(types.RECEIVE_ISSUES_FOR_ALL_LISTS_SUCCESS, listIssues);
})
.catch(() => commit(types.RECEIVE_ISSUES_FOR_ALL_LISTS_FAILURE));
}, },
moveIssue: ( moveIssue: (
......
...@@ -12,11 +12,8 @@ export const UPDATE_LIST_FAILURE = 'UPDATE_LIST_FAILURE'; ...@@ -12,11 +12,8 @@ export const UPDATE_LIST_FAILURE = 'UPDATE_LIST_FAILURE';
export const REQUEST_REMOVE_LIST = 'REQUEST_REMOVE_LIST'; export const REQUEST_REMOVE_LIST = 'REQUEST_REMOVE_LIST';
export const RECEIVE_REMOVE_LIST_SUCCESS = 'RECEIVE_REMOVE_LIST_SUCCESS'; export const RECEIVE_REMOVE_LIST_SUCCESS = 'RECEIVE_REMOVE_LIST_SUCCESS';
export const RECEIVE_REMOVE_LIST_ERROR = 'RECEIVE_REMOVE_LIST_ERROR'; export const RECEIVE_REMOVE_LIST_ERROR = 'RECEIVE_REMOVE_LIST_ERROR';
export const REQUEST_ISSUES_FOR_ALL_LISTS = 'REQUEST_ISSUES_FOR_ALL_LISTS';
export const RECEIVE_ISSUES_FOR_LIST_FAILURE = 'RECEIVE_ISSUES_FOR_LIST_FAILURE'; export const RECEIVE_ISSUES_FOR_LIST_FAILURE = 'RECEIVE_ISSUES_FOR_LIST_FAILURE';
export const RECEIVE_ISSUES_FOR_LIST_SUCCESS = 'RECEIVE_ISSUES_FOR_LIST_SUCCESS'; export const RECEIVE_ISSUES_FOR_LIST_SUCCESS = 'RECEIVE_ISSUES_FOR_LIST_SUCCESS';
export const RECEIVE_ISSUES_FOR_ALL_LISTS_SUCCESS = 'RECEIVE_ISSUES_FOR_ALL_LISTS_SUCCESS';
export const RECEIVE_ISSUES_FOR_ALL_LISTS_FAILURE = 'RECEIVE_ISSUES_FOR_ALL_LISTS_FAILURE';
export const REQUEST_ADD_ISSUE = 'REQUEST_ADD_ISSUE'; export const REQUEST_ADD_ISSUE = 'REQUEST_ADD_ISSUE';
export const RECEIVE_ADD_ISSUE_SUCCESS = 'RECEIVE_ADD_ISSUE_SUCCESS'; export const RECEIVE_ADD_ISSUE_SUCCESS = 'RECEIVE_ADD_ISSUE_SUCCESS';
export const RECEIVE_ADD_ISSUE_ERROR = 'RECEIVE_ADD_ISSUE_ERROR'; export const RECEIVE_ADD_ISSUE_ERROR = 'RECEIVE_ADD_ISSUE_ERROR';
...@@ -32,3 +29,4 @@ export const SET_CURRENT_PAGE = 'SET_CURRENT_PAGE'; ...@@ -32,3 +29,4 @@ export const SET_CURRENT_PAGE = 'SET_CURRENT_PAGE';
export const TOGGLE_EMPTY_STATE = 'TOGGLE_EMPTY_STATE'; export const TOGGLE_EMPTY_STATE = 'TOGGLE_EMPTY_STATE';
export const SET_ACTIVE_ID = 'SET_ACTIVE_ID'; export const SET_ACTIVE_ID = 'SET_ACTIVE_ID';
export const UPDATE_ISSUE_BY_ID = 'UPDATE_ISSUE_BY_ID'; export const UPDATE_ISSUE_BY_ID = 'UPDATE_ISSUE_BY_ID';
export const RESET_ISSUES = 'RESET_ISSUES';
...@@ -2,7 +2,7 @@ import Vue from 'vue'; ...@@ -2,7 +2,7 @@ import Vue from 'vue';
import { sortBy, pull } from 'lodash'; import { sortBy, pull } from 'lodash';
import { formatIssue, moveIssueListHelper } from '../boards_util'; import { formatIssue, moveIssueListHelper } from '../boards_util';
import * as mutationTypes from './mutation_types'; import * as mutationTypes from './mutation_types';
import { __ } from '~/locale'; import { s__ } from '~/locale';
import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import { getIdFromGraphQLId } from '~/graphql_shared/utils';
const notImplemented = () => { const notImplemented = () => {
...@@ -49,7 +49,7 @@ export default { ...@@ -49,7 +49,7 @@ export default {
}, },
[mutationTypes.CREATE_LIST_FAILURE]: state => { [mutationTypes.CREATE_LIST_FAILURE]: state => {
state.error = __('An error occurred while creating the list. Please try again.'); state.error = s__('Boards|An error occurred while creating the list. Please try again.');
}, },
[mutationTypes.REQUEST_ADD_LIST]: () => { [mutationTypes.REQUEST_ADD_LIST]: () => {
...@@ -73,7 +73,7 @@ export default { ...@@ -73,7 +73,7 @@ export default {
}, },
[mutationTypes.UPDATE_LIST_FAILURE]: (state, backupList) => { [mutationTypes.UPDATE_LIST_FAILURE]: (state, backupList) => {
state.error = __('An error occurred while updating the list. Please try again.'); state.error = s__('Boards|An error occurred while updating the list. Please try again.');
Vue.set(state, 'boardLists', backupList); Vue.set(state, 'boardLists', backupList);
}, },
...@@ -98,19 +98,17 @@ export default { ...@@ -98,19 +98,17 @@ export default {
}, },
[mutationTypes.RECEIVE_ISSUES_FOR_LIST_FAILURE]: (state, listId) => { [mutationTypes.RECEIVE_ISSUES_FOR_LIST_FAILURE]: (state, listId) => {
state.error = __('An error occurred while fetching the board issues. Please reload the page.'); state.error = s__(
'Boards|An error occurred while fetching the board issues. Please reload the page.',
);
const listIndex = state.boardLists.findIndex(l => l.id === listId); const listIndex = state.boardLists.findIndex(l => l.id === listId);
Vue.set(state.boardLists[listIndex], 'loading', false); Vue.set(state.boardLists[listIndex], 'loading', false);
}, },
[mutationTypes.REQUEST_ISSUES_FOR_ALL_LISTS]: state => { [mutationTypes.RESET_ISSUES]: state => {
state.isLoadingIssues = true; Object.keys(state.issuesByListId).forEach(listId => {
}, Vue.set(state.issuesByListId, listId, []);
});
[mutationTypes.RECEIVE_ISSUES_FOR_ALL_LISTS_SUCCESS]: (state, { listData, issues }) => {
state.issuesByListId = listData;
state.issues = issues;
state.isLoadingIssues = false;
}, },
[mutationTypes.UPDATE_ISSUE_BY_ID]: (state, { issueId, prop, value }) => { [mutationTypes.UPDATE_ISSUE_BY_ID]: (state, { issueId, prop, value }) => {
...@@ -122,11 +120,6 @@ export default { ...@@ -122,11 +120,6 @@ export default {
Vue.set(state.issues[issueId], prop, value); Vue.set(state.issues[issueId], prop, value);
}, },
[mutationTypes.RECEIVE_ISSUES_FOR_ALL_LISTS_FAILURE]: state => {
state.error = __('An error occurred while fetching the board issues. Please reload the page.');
state.isLoadingIssues = false;
},
[mutationTypes.REQUEST_ADD_ISSUE]: () => { [mutationTypes.REQUEST_ADD_ISSUE]: () => {
notImplemented(); notImplemented();
}, },
...@@ -162,7 +155,7 @@ export default { ...@@ -162,7 +155,7 @@ export default {
state, state,
{ originalIssue, fromListId, toListId, originalIndex }, { originalIssue, fromListId, toListId, originalIndex },
) => { ) => {
state.error = __('An error occurred while moving the issue. Please try again.'); state.error = s__('Boards|An error occurred while moving the issue. Please try again.');
Vue.set(state.issues, originalIssue.id, originalIssue); Vue.set(state.issues, originalIssue.id, originalIssue);
removeIssueFromList(state, toListId, originalIssue.id); removeIssueFromList(state, toListId, originalIssue.id);
addIssueToList({ addIssueToList({
...@@ -193,7 +186,7 @@ export default { ...@@ -193,7 +186,7 @@ export default {
}, },
[mutationTypes.ADD_ISSUE_TO_LIST_FAILURE]: (state, { list, issue }) => { [mutationTypes.ADD_ISSUE_TO_LIST_FAILURE]: (state, { list, issue }) => {
state.error = __('An error occurred while creating the issue. Please try again.'); state.error = s__('Boards|An error occurred while creating the issue. Please try again.');
removeIssueFromList(state, list.id, issue.id); removeIssueFromList(state, list.id, issue.id);
}, },
......
...@@ -11,7 +11,6 @@ export default () => ({ ...@@ -11,7 +11,6 @@ export default () => ({
boardLists: [], boardLists: [],
issuesByListId: {}, issuesByListId: {},
issues: {}, issues: {},
isLoadingIssues: false,
filterParams: {}, filterParams: {},
error: undefined, error: undefined,
// TODO: remove after ce/ee split of board_content.vue // TODO: remove after ce/ee split of board_content.vue
......
<script> <script>
import { GlButton, GlIcon, GlLink, GlPopover, GlTooltipDirective } from '@gitlab/ui'; import { GlButton, GlIcon, GlLink, GlLoadingIcon, GlPopover, GlTooltipDirective } from '@gitlab/ui';
import { mapGetters } from 'vuex'; import { mapActions, mapGetters, mapState } from 'vuex';
import { __, n__, sprintf } from '~/locale'; import { __, n__, sprintf } from '~/locale';
import timeagoMixin from '~/vue_shared/mixins/timeago'; import timeagoMixin from '~/vue_shared/mixins/timeago';
import { formatDate } from '~/lib/utils/datetime_utility'; import { formatDate } from '~/lib/utils/datetime_utility';
...@@ -12,6 +12,7 @@ export default { ...@@ -12,6 +12,7 @@ export default {
GlButton, GlButton,
GlIcon, GlIcon,
GlLink, GlLink,
GlLoadingIcon,
GlPopover, GlPopover,
IssuesLaneList, IssuesLaneList,
}, },
...@@ -28,11 +29,6 @@ export default { ...@@ -28,11 +29,6 @@ export default {
type: Array, type: Array,
required: true, required: true,
}, },
isLoadingIssues: {
type: Boolean,
required: false,
default: false,
},
disabled: { disabled: {
type: Boolean, type: Boolean,
required: true, required: true,
...@@ -49,6 +45,7 @@ export default { ...@@ -49,6 +45,7 @@ export default {
}; };
}, },
computed: { computed: {
...mapState(['epicsFlags', 'filterParams']),
...mapGetters(['getIssuesByEpic']), ...mapGetters(['getIssuesByEpic']),
isOpen() { isOpen() {
return this.epic.state === statusType.open; return this.epic.state === statusType.open;
...@@ -89,8 +86,25 @@ export default { ...@@ -89,8 +86,25 @@ export default {
epicDateString() { epicDateString() {
return formatDate(this.epic.createdAt); return formatDate(this.epic.createdAt);
}, },
isLoading() {
return Boolean(this.epicsFlags[this.epic.id]?.isLoading);
},
},
watch: {
filterParams: {
handler() {
if (!this.filterParams.epicId || this.filterParams.epicId === this.epic.id) {
this.fetchIssuesForEpic(this.epic.id);
}
},
deep: true,
},
},
mounted() {
this.fetchIssuesForEpic(this.epic.id);
}, },
methods: { methods: {
...mapActions(['fetchIssuesForEpic']),
toggleExpanded() { toggleExpanded() {
this.isExpanded = !this.isExpanded; this.isExpanded = !this.isExpanded;
}, },
...@@ -133,6 +147,7 @@ export default { ...@@ -133,6 +147,7 @@ export default {
<gl-link :href="epic.webUrl" class="gl-font-sm">{{ __('Go to epic') }}</gl-link> <gl-link :href="epic.webUrl" class="gl-font-sm">{{ __('Go to epic') }}</gl-link>
</gl-popover> </gl-popover>
<span <span
v-if="!isLoading"
v-gl-tooltip.hover v-gl-tooltip.hover
:title="issuesCountTooltipText" :title="issuesCountTooltipText"
class="gl-display-flex gl-align-items-center gl-text-gray-500" class="gl-display-flex gl-align-items-center gl-text-gray-500"
...@@ -143,6 +158,7 @@ export default { ...@@ -143,6 +158,7 @@ export default {
<gl-icon class="gl-mr-2 gl-flex-shrink-0" name="issues" aria-hidden="true" /> <gl-icon class="gl-mr-2 gl-flex-shrink-0" name="issues" aria-hidden="true" />
<span aria-hidden="true">{{ issuesCount }}</span> <span aria-hidden="true">{{ issuesCount }}</span>
</span> </span>
<gl-loading-icon v-if="isLoading" class="gl-p-2" />
</div> </div>
</div> </div>
<div v-if="isExpanded" class="gl-display-flex"> <div v-if="isExpanded" class="gl-display-flex">
...@@ -151,7 +167,6 @@ export default { ...@@ -151,7 +167,6 @@ export default {
:key="`${list.id}-issues`" :key="`${list.id}-issues`"
:list="list" :list="list"
:issues="getIssuesByEpic(list.id, epic.id)" :issues="getIssuesByEpic(list.id, epic.id)"
:is-loading="isLoadingIssues"
:disabled="disabled" :disabled="disabled"
:epic-id="epic.id" :epic-id="epic.id"
:epic-is-confidential="epic.confidential" :epic-is-confidential="epic.confidential"
......
...@@ -35,7 +35,7 @@ export default { ...@@ -35,7 +35,7 @@ export default {
}, },
}, },
computed: { computed: {
...mapState(['epics', 'isLoadingIssues']), ...mapState(['epics']),
...mapGetters(['getUnassignedIssues']), ...mapGetters(['getUnassignedIssues']),
unassignedIssues() { unassignedIssues() {
return listId => this.getUnassignedIssues(listId); return listId => this.getUnassignedIssues(listId);
...@@ -66,11 +66,8 @@ export default { ...@@ -66,11 +66,8 @@ export default {
return this.canAdminList ? options : {}; return this.canAdminList ? options : {};
}, },
}, },
mounted() {
this.fetchIssuesForAllLists();
},
methods: { methods: {
...mapActions(['fetchIssuesForAllLists', 'moveList']), ...mapActions(['moveList']),
handleDragOnEnd(params) { handleDragOnEnd(params) {
const { newIndex, oldIndex, item } = params; const { newIndex, oldIndex, item } = params;
const { listId } = item.dataset; const { listId } = item.dataset;
...@@ -122,7 +119,6 @@ export default { ...@@ -122,7 +119,6 @@ export default {
:key="epic.id" :key="epic.id"
:epic="epic" :epic="epic"
:lists="lists" :lists="lists"
:is-loading-issues="isLoadingIssues"
:disabled="disabled" :disabled="disabled"
:can-admin-list="canAdminList" :can-admin-list="canAdminList"
/> />
...@@ -153,7 +149,6 @@ export default { ...@@ -153,7 +149,6 @@ export default {
:list="list" :list="list"
:issues="unassignedIssues(list.id)" :issues="unassignedIssues(list.id)"
:is-unassigned-issues-lane="true" :is-unassigned-issues-lane="true"
:is-loading="isLoadingIssues"
:disabled="disabled" :disabled="disabled"
:can-admin-list="canAdminList" :can-admin-list="canAdminList"
/> />
......
...@@ -33,11 +33,6 @@ export default { ...@@ -33,11 +33,6 @@ export default {
required: false, required: false,
default: false, default: false,
}, },
isLoading: {
type: Boolean,
required: false,
default: false,
},
canAdminList: { canAdminList: {
type: Boolean, type: Boolean,
required: false, required: false,
...@@ -50,7 +45,7 @@ export default { ...@@ -50,7 +45,7 @@ export default {
}; };
}, },
computed: { computed: {
...mapState(['activeId']), ...mapState(['activeId', 'filterParams']),
treeRootWrapper() { treeRootWrapper() {
return this.canAdminList ? Draggable : 'ul'; return this.canAdminList ? Draggable : 'ul';
}, },
...@@ -68,6 +63,17 @@ export default { ...@@ -68,6 +63,17 @@ export default {
return this.canAdminList ? options : {}; return this.canAdminList ? options : {};
}, },
}, },
watch: {
filterParams: {
handler() {
if (this.isUnassignedIssuesLane) {
this.fetchIssuesForList(this.list.id);
}
},
deep: true,
immediate: true,
},
},
created() { created() {
eventHub.$on(`toggle-issue-form-${this.list.id}`, this.toggleForm); eventHub.$on(`toggle-issue-form-${this.list.id}`, this.toggleForm);
}, },
...@@ -75,7 +81,7 @@ export default { ...@@ -75,7 +81,7 @@ export default {
eventHub.$off(`toggle-issue-form-${this.list.id}`, this.toggleForm); eventHub.$off(`toggle-issue-form-${this.list.id}`, this.toggleForm);
}, },
methods: { methods: {
...mapActions(['setActiveId', 'moveIssue']), ...mapActions(['setActiveId', 'moveIssue', 'fetchIssuesForList']),
toggleForm() { toggleForm() {
this.showIssueForm = !this.showIssueForm; this.showIssueForm = !this.showIssueForm;
if (this.showIssueForm && this.isUnassignedIssuesLane) { if (this.showIssueForm && this.isUnassignedIssuesLane) {
...@@ -138,7 +144,6 @@ export default { ...@@ -138,7 +144,6 @@ export default {
:class="{ 'is-collapsed': !list.isExpanded }" :class="{ 'is-collapsed': !list.isExpanded }"
> >
<div class="board-inner gl-rounded-base gl-relative gl-w-full"> <div class="board-inner gl-rounded-base gl-relative gl-w-full">
<gl-loading-icon v-if="isLoading" class="gl-p-2" />
<board-new-issue <board-new-issue
v-if="list.type !== 'closed' && showIssueForm && isUnassignedIssuesLane" v-if="list.type !== 'closed' && showIssueForm && isUnassignedIssuesLane"
:list="list" :list="list"
......
...@@ -2,7 +2,7 @@ export const setPromotionState = store => { ...@@ -2,7 +2,7 @@ export const setPromotionState = store => {
store.addPromotionState(); store.addPromotionState();
}; };
export const setWeigthFetchingState = (issue, value) => { export const setWeightFetchingState = (issue, value) => {
issue.setFetchingState('weight', value); issue.setFetchingState('weight', value);
}; };
export const setEpicFetchingState = (issue, value) => { export const setEpicFetchingState = (issue, value) => {
......
...@@ -10,16 +10,45 @@ import { EpicFilterType } from '../constants'; ...@@ -10,16 +10,45 @@ import { EpicFilterType } from '../constants';
import boardsStoreEE from './boards_store_ee'; import boardsStoreEE from './boards_store_ee';
import * as types from './mutation_types'; import * as types from './mutation_types';
import { fullEpicId } from '../boards_util'; import { fullEpicId } from '../boards_util';
import { formatListIssues, fullBoardId } from '~/boards/boards_util';
import createDefaultClient from '~/lib/graphql'; import createDefaultClient from '~/lib/graphql';
import epicsSwimlanesQuery from '../queries/epics_swimlanes.query.graphql'; import epicsSwimlanesQuery from '../queries/epics_swimlanes.query.graphql';
import listsIssuesQuery from '~/boards/queries/lists_issues.query.graphql';
const notImplemented = () => { const notImplemented = () => {
/* eslint-disable-next-line @gitlab/require-i18n-strings */ /* eslint-disable-next-line @gitlab/require-i18n-strings */
throw new Error('Not implemented!'); throw new Error('Not implemented!');
}; };
const gqlClient = createDefaultClient(); export const gqlClient = createDefaultClient();
const fetchAndFormatListIssues = (state, extraVariables) => {
const { endpoints, boardType, filterParams } = state;
const { fullPath, boardId } = endpoints;
const variables = {
...extraVariables,
fullPath,
boardId: fullBoardId(boardId),
filters: { ...filterParams },
isGroup: boardType === BoardType.group,
isProject: boardType === BoardType.project,
};
return gqlClient
.query({
query: listsIssuesQuery,
context: {
isSingleRequest: true,
},
variables,
})
.then(({ data }) => {
const { lists } = data[boardType]?.board;
return formatListIssues(lists);
});
};
export default { export default {
...actionsCE, ...actionsCE,
...@@ -139,6 +168,39 @@ export default { ...@@ -139,6 +168,39 @@ export default {
notImplemented(); notImplemented();
}, },
fetchIssuesForList: ({ state, commit }, listId, noEpicIssues = false) => {
const { filterParams } = state;
const variables = {
id: listId,
filters: noEpicIssues
? { ...filterParams, epicWildcardId: EpicFilterType.none }
: filterParams,
};
return fetchAndFormatListIssues(state, variables)
.then(listIssues => {
commit(types.RECEIVE_ISSUES_FOR_LIST_SUCCESS, { listIssues, listId });
})
.catch(() => commit(types.RECEIVE_ISSUES_FOR_LIST_FAILURE, listId));
},
fetchIssuesForEpic: ({ state, commit }, epicId) => {
commit(types.REQUEST_ISSUES_FOR_EPIC, epicId);
const { filterParams } = state;
const variables = {
filters: { ...filterParams, epicId },
};
return fetchAndFormatListIssues(state, variables)
.then(listIssues => {
commit(types.RECEIVE_ISSUES_FOR_EPIC_SUCCESS, { ...listIssues, epicId });
})
.catch(() => commit(types.RECEIVE_ISSUES_FOR_EPIC_FAILURE, epicId));
},
toggleEpicSwimlanes: ({ state, commit, dispatch }) => { toggleEpicSwimlanes: ({ state, commit, dispatch }) => {
commit(types.TOGGLE_EPICS_SWIMLANES); commit(types.TOGGLE_EPICS_SWIMLANES);
......
...@@ -11,6 +11,11 @@ export const REQUEST_REMOVE_BOARD = 'REQUEST_REMOVE_BOARD'; ...@@ -11,6 +11,11 @@ export const REQUEST_REMOVE_BOARD = 'REQUEST_REMOVE_BOARD';
export const RECEIVE_REMOVE_BOARD_SUCCESS = 'RECEIVE_REMOVE_BOARD_SUCCESS'; 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 RECEIVE_ISSUES_FOR_LIST_FAILURE = 'RECEIVE_ISSUES_FOR_LIST_FAILURE';
export const RECEIVE_ISSUES_FOR_LIST_SUCCESS = 'RECEIVE_ISSUES_FOR_LIST_SUCCESS';
export const REQUEST_ISSUES_FOR_EPIC = 'REQUEST_ISSUES_FOR_EPIC';
export const RECEIVE_ISSUES_FOR_EPIC_SUCCESS = 'RECEIVE_ISSUES_FOR_EPIC_SUCCESS';
export const RECEIVE_ISSUES_FOR_EPIC_FAILURE = 'RECEIVE_ISSUES_FOR_EPIC_FAILURE';
export const TOGGLE_EPICS_SWIMLANES = 'TOGGLE_EPICS_SWIMLANES'; export const TOGGLE_EPICS_SWIMLANES = 'TOGGLE_EPICS_SWIMLANES';
export const RECEIVE_BOARD_LISTS_SUCCESS = 'RECEIVE_BOARD_LISTS_SUCCESS'; export const RECEIVE_BOARD_LISTS_SUCCESS = 'RECEIVE_BOARD_LISTS_SUCCESS';
export const RECEIVE_SWIMLANES_FAILURE = 'RECEIVE_SWIMLANES_FAILURE'; export const RECEIVE_SWIMLANES_FAILURE = 'RECEIVE_SWIMLANES_FAILURE';
......
import Vue from 'vue';
import { union } from 'lodash';
import mutationsCE from '~/boards/stores/mutations'; import mutationsCE from '~/boards/stores/mutations';
import { __ } from '~/locale'; import { s__ } from '~/locale';
import * as mutationTypes from './mutation_types'; import * as mutationTypes from './mutation_types';
const notImplemented = () => { const notImplemented = () => {
...@@ -65,6 +67,24 @@ export default { ...@@ -65,6 +67,24 @@ export default {
notImplemented(); notImplemented();
}, },
[mutationTypes.REQUEST_ISSUES_FOR_EPIC]: (state, epicId) => {
Vue.set(state.epicsFlags, epicId, { isLoading: true });
},
[mutationTypes.RECEIVE_ISSUES_FOR_EPIC_SUCCESS]: (state, { listData, issues, epicId }) => {
Object.entries(listData).forEach(([listId, list]) => {
Vue.set(state.issuesByListId, listId, union(state.issuesByListId[listId] || [], list));
});
Vue.set(state, 'issues', { ...state.issues, ...issues });
Vue.set(state.epicsFlags, epicId, { isLoading: false });
},
[mutationTypes.RECEIVE_ISSUES_FOR_EPIC_FAILURE]: (state, epicId) => {
state.error = s__('Boards|An error occurred while fetching issues. Please reload the page.');
Vue.set(state.epicsFlags, epicId, { isLoading: false });
},
[mutationTypes.TOGGLE_EPICS_SWIMLANES]: state => { [mutationTypes.TOGGLE_EPICS_SWIMLANES]: state => {
state.isShowingEpicsSwimlanes = !state.isShowingEpicsSwimlanes; state.isShowingEpicsSwimlanes = !state.isShowingEpicsSwimlanes;
state.epicsSwimlanesFetchInProgress = true; state.epicsSwimlanesFetchInProgress = true;
...@@ -76,8 +96,8 @@ export default { ...@@ -76,8 +96,8 @@ export default {
}, },
[mutationTypes.RECEIVE_SWIMLANES_FAILURE]: state => { [mutationTypes.RECEIVE_SWIMLANES_FAILURE]: state => {
state.error = __( state.error = s__(
'An error occurred while fetching the board swimlanes. Please reload the page.', 'Boards|An error occurred while fetching the board swimlanes. Please reload the page.',
); );
state.epicsSwimlanesFetchInProgress = false; state.epicsSwimlanesFetchInProgress = false;
}, },
......
...@@ -6,5 +6,5 @@ export default () => ({ ...@@ -6,5 +6,5 @@ export default () => ({
isShowingEpicsSwimlanes: false, isShowingEpicsSwimlanes: false,
epicsSwimlanesFetchInProgress: false, epicsSwimlanesFetchInProgress: false,
epics: {}, epics: {},
issuesByEpicId: {}, epicsFlags: {},
}); });
...@@ -2,7 +2,7 @@ import Vuex from 'vuex'; ...@@ -2,7 +2,7 @@ import Vuex from 'vuex';
import { createLocalVue, shallowMount } from '@vue/test-utils'; import { createLocalVue, shallowMount } from '@vue/test-utils';
import EpicLane from 'ee/boards/components/epic_lane.vue'; import EpicLane from 'ee/boards/components/epic_lane.vue';
import IssuesLaneList from 'ee/boards/components/issues_lane_list.vue'; import IssuesLaneList from 'ee/boards/components/issues_lane_list.vue';
import { GlIcon } from '@gitlab/ui'; import { GlIcon, GlLoadingIcon } from '@gitlab/ui';
import getters from 'ee/boards/stores/getters'; import getters from 'ee/boards/stores/getters';
import { mockEpic, mockListsWithModel, mockIssuesByListId, issues } from '../mock_data'; import { mockEpic, mockListsWithModel, mockIssuesByListId, issues } from '../mock_data';
...@@ -12,18 +12,26 @@ localVue.use(Vuex); ...@@ -12,18 +12,26 @@ localVue.use(Vuex);
describe('EpicLane', () => { describe('EpicLane', () => {
let wrapper; let wrapper;
const createStore = () => { const findByTestId = testId => wrapper.find(`[data-testid="${testId}"]`);
const createStore = isLoading => {
return new Vuex.Store({ return new Vuex.Store({
actions: {
fetchIssuesForEpic: jest.fn(),
},
state: { state: {
issuesByListId: mockIssuesByListId, issuesByListId: mockIssuesByListId,
issues, issues,
epicsFlags: {
[mockEpic.id]: { isLoading },
},
}, },
getters, getters,
}); });
}; };
const createComponent = (props = {}) => { const createComponent = ({ props = {}, isLoading = false } = {}) => {
const store = createStore(); const store = createStore(isLoading);
const defaultProps = { const defaultProps = {
epic: mockEpic, epic: mockEpic,
...@@ -55,12 +63,12 @@ describe('EpicLane', () => { ...@@ -55,12 +63,12 @@ describe('EpicLane', () => {
}); });
it('icon aria label is Closed when epic is closed', () => { it('icon aria label is Closed when epic is closed', () => {
createComponent({ epic: { ...mockEpic, state: 'closed' } }); createComponent({ props: { epic: { ...mockEpic, state: 'closed' } } });
expect(wrapper.find(GlIcon).attributes('aria-label')).toEqual('Closed'); expect(wrapper.find(GlIcon).attributes('aria-label')).toEqual('Closed');
}); });
it('displays count of issues in epic which belong to board', () => { it('displays count of issues in epic which belong to board', () => {
expect(wrapper.find('[data-testid="epic-lane-issue-count"]').text()).toContain(2); expect(findByTestId('epic-lane-issue-count').text()).toContain(2);
}); });
it('displays 2 icons', () => { it('displays 2 icons', () => {
...@@ -79,12 +87,22 @@ describe('EpicLane', () => { ...@@ -79,12 +87,22 @@ describe('EpicLane', () => {
expect(wrapper.findAll(IssuesLaneList)).toHaveLength(wrapper.props('lists').length); expect(wrapper.findAll(IssuesLaneList)).toHaveLength(wrapper.props('lists').length);
expect(wrapper.vm.isExpanded).toBe(true); expect(wrapper.vm.isExpanded).toBe(true);
wrapper.find('[data-testid="epic-lane-chevron"]').vm.$emit('click'); findByTestId('epic-lane-chevron').vm.$emit('click');
return wrapper.vm.$nextTick().then(() => { return wrapper.vm.$nextTick().then(() => {
expect(wrapper.findAll(IssuesLaneList)).toHaveLength(0); expect(wrapper.findAll(IssuesLaneList)).toHaveLength(0);
expect(wrapper.vm.isExpanded).toBe(false); expect(wrapper.vm.isExpanded).toBe(false);
}); });
}); });
it('does not display loading icon when issues are not loading', () => {
expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
});
it('displays loading icon and hides issues count when issues are loading', () => {
createComponent({ isLoading: true });
expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
expect(findByTestId('epic-lane-issue-count').exists()).toBe(false);
});
}); });
}); });
...@@ -17,12 +17,8 @@ describe('EpicsSwimlanes', () => { ...@@ -17,12 +17,8 @@ describe('EpicsSwimlanes', () => {
const createStore = () => { const createStore = () => {
return new Vuex.Store({ return new Vuex.Store({
actions: {
fetchIssuesForAllLists: jest.fn(),
},
state: { state: {
epics: mockEpics, epics: mockEpics,
isLoadingIssues: false,
issuesByListId: mockIssuesByListId, issuesByListId: mockIssuesByListId,
issues, issues,
}, },
......
import axios from 'axios'; import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import boardsStoreEE from 'ee/boards/stores/boards_store_ee'; import boardsStoreEE from 'ee/boards/stores/boards_store_ee';
import actions from 'ee/boards/stores/actions'; import actions, { gqlClient } from 'ee/boards/stores/actions';
import * as types from 'ee/boards/stores/mutation_types'; import * as types from 'ee/boards/stores/mutation_types';
import testAction from 'helpers/vuex_action_helper'; import testAction from 'helpers/vuex_action_helper';
import { ListType } from '~/boards/constants'; import { ListType } from '~/boards/constants';
import { formatListIssues } from '~/boards/boards_util';
jest.mock('axios'); import { mockLists, mockIssue, mockEpic } from '../mock_data';
const expectNotImplemented = action => { const expectNotImplemented = action => {
it('is not implemented', () => { it('is not implemented', () => {
...@@ -13,6 +14,16 @@ const expectNotImplemented = action => { ...@@ -13,6 +14,16 @@ const expectNotImplemented = action => {
}); });
}; };
let mock;
beforeEach(() => {
mock = new MockAdapter(axios);
});
afterEach(() => {
mock.restore();
});
describe('setFilters', () => { describe('setFilters', () => {
it('should commit mutation SET_FILTERS, updates epicId with global id', done => { it('should commit mutation SET_FILTERS, updates epicId with global id', done => {
const state = { const state = {
...@@ -79,10 +90,16 @@ describe('updateListWipLimit', () => { ...@@ -79,10 +90,16 @@ describe('updateListWipLimit', () => {
}; };
boardsStoreEE.initEESpecific(storeMock); boardsStoreEE.initEESpecific(storeMock);
jest.mock('axios');
axios.put = jest.fn();
axios.put.mockResolvedValue({ data: {} });
});
afterEach(() => {
jest.restoreAllMocks();
}); });
it('should call the correct url', () => { it('should call the correct url', () => {
axios.put.mockResolvedValue({ data: {} });
const maxIssueCount = 0; const maxIssueCount = 0;
const activeId = 1; const activeId = 1;
...@@ -147,6 +164,73 @@ describe('togglePromotionState', () => { ...@@ -147,6 +164,73 @@ describe('togglePromotionState', () => {
expectNotImplemented(actions.updateIssueWeight); expectNotImplemented(actions.updateIssueWeight);
}); });
describe('fetchIssuesForEpic', () => {
const listId = mockLists[0].id;
const epicId = mockEpic.id;
const state = {
endpoints: {
fullPath: 'gitlab-org',
boardId: 1,
},
filterParams: {},
boardType: 'group',
};
const queryResponse = {
data: {
group: {
board: {
lists: {
nodes: [
{
id: listId,
issues: {
nodes: [mockIssue],
},
},
],
},
},
},
},
};
const formattedIssues = formatListIssues(queryResponse.data.group.board.lists);
it('should commit mutations REQUEST_ISSUES_FOR_EPIC and RECEIVE_ISSUES_FOR_LIST_SUCCESS on success', done => {
jest.spyOn(gqlClient, 'query').mockResolvedValue(queryResponse);
testAction(
actions.fetchIssuesForEpic,
epicId,
state,
[
{ type: types.REQUEST_ISSUES_FOR_EPIC, payload: epicId },
{ type: types.RECEIVE_ISSUES_FOR_EPIC_SUCCESS, payload: { ...formattedIssues, epicId } },
],
[],
done,
);
});
it('should commit mutations REQUEST_ISSUES_FOR_EPIC and RECEIVE_ISSUES_FOR_LIST_FAILURE on failure', done => {
jest.spyOn(gqlClient, 'query').mockResolvedValue(Promise.reject());
testAction(
actions.fetchIssuesForEpic,
epicId,
state,
[
{ type: types.REQUEST_ISSUES_FOR_EPIC, payload: epicId },
{ type: types.RECEIVE_ISSUES_FOR_EPIC_FAILURE, payload: epicId },
],
[],
done,
);
});
});
describe('toggleEpicSwimlanes', () => { describe('toggleEpicSwimlanes', () => {
it('should commit mutation TOGGLE_EPICS_SWIMLANES', () => { it('should commit mutation TOGGLE_EPICS_SWIMLANES', () => {
const state = { const state = {
......
import mutations from 'ee/boards/stores/mutations'; import mutations from 'ee/boards/stores/mutations';
import { mockLists, mockEpics } from '../mock_data'; import {
mockLists,
mockIssue,
mockIssue2,
mockEpics,
mockEpic,
mockListsWithModel,
} from '../mock_data';
const expectNotImplemented = action => { const expectNotImplemented = action => {
it('is not implemented', () => { it('is not implemented', () => {
...@@ -7,9 +14,21 @@ const expectNotImplemented = action => { ...@@ -7,9 +14,21 @@ const expectNotImplemented = action => {
}); });
}; };
const epicId = mockEpic.id;
let state = {
issuesByListId: {},
issues: {},
boardLists: mockListsWithModel,
epicsFlags: {
[epicId]: { isLoading: true },
},
};
describe('SET_SHOW_LABELS', () => { describe('SET_SHOW_LABELS', () => {
it('updates isShowingLabels', () => { it('updates isShowingLabels', () => {
const state = { state = {
...state,
isShowingLabels: true, isShowingLabels: true,
}; };
...@@ -71,9 +90,57 @@ describe('TOGGLE_PROMOTION_STATE', () => { ...@@ -71,9 +90,57 @@ describe('TOGGLE_PROMOTION_STATE', () => {
expectNotImplemented(mutations.TOGGLE_PROMOTION_STATE); expectNotImplemented(mutations.TOGGLE_PROMOTION_STATE);
}); });
describe('REQUEST_ISSUES_FOR_EPIC', () => {
it('sets isLoading epicsFlags in state for epicId to true', () => {
state = {
...state,
epicsFlags: {
[epicId]: { isLoading: false },
},
};
mutations.REQUEST_ISSUES_FOR_EPIC(state, epicId);
expect(state.epicsFlags[epicId].isLoading).toBe(true);
});
});
describe('RECEIVE_ISSUES_FOR_EPIC_SUCCESS', () => {
it('sets issuesByListId and issues state for epic issues and loading state to false', () => {
const listIssues = {
'gid://gitlab/List/1': [mockIssue.id],
'gid://gitlab/List/2': [mockIssue2.id],
};
const issues = {
'436': mockIssue,
'437': mockIssue2,
};
mutations.RECEIVE_ISSUES_FOR_EPIC_SUCCESS(state, {
listData: listIssues,
issues,
epicId,
});
expect(state.issuesByListId).toEqual(listIssues);
expect(state.issues).toEqual(issues);
expect(state.epicsFlags[epicId].isLoading).toBe(false);
});
});
describe('RECEIVE_ISSUES_FOR_EPIC_FAILURE', () => {
it('sets loading state to false for epic and error message', () => {
mutations.RECEIVE_ISSUES_FOR_EPIC_FAILURE(state, epicId);
expect(state.error).toEqual('An error occurred while fetching issues. Please reload the page.');
expect(state.epicsFlags[epicId].isLoading).toBe(false);
});
});
describe('TOGGLE_EPICS_SWIMLANES', () => { describe('TOGGLE_EPICS_SWIMLANES', () => {
it('toggles isShowingEpicsSwimlanes from true to false', () => { it('toggles isShowingEpicsSwimlanes from true to false', () => {
const state = { state = {
...state,
isShowingEpicsSwimlanes: true, isShowingEpicsSwimlanes: true,
}; };
...@@ -83,7 +150,8 @@ describe('TOGGLE_EPICS_SWIMLANES', () => { ...@@ -83,7 +150,8 @@ describe('TOGGLE_EPICS_SWIMLANES', () => {
}); });
it('toggles isShowingEpicsSwimlanes from false to true', () => { it('toggles isShowingEpicsSwimlanes from false to true', () => {
const state = { state = {
...state,
isShowingEpicsSwimlanes: false, isShowingEpicsSwimlanes: false,
}; };
...@@ -93,7 +161,8 @@ describe('TOGGLE_EPICS_SWIMLANES', () => { ...@@ -93,7 +161,8 @@ describe('TOGGLE_EPICS_SWIMLANES', () => {
}); });
it('sets epicsSwimlanesFetchInProgress to true', () => { it('sets epicsSwimlanesFetchInProgress to true', () => {
const state = { state = {
...state,
epicsSwimlanesFetchInProgress: false, epicsSwimlanesFetchInProgress: false,
}; };
...@@ -105,7 +174,8 @@ describe('TOGGLE_EPICS_SWIMLANES', () => { ...@@ -105,7 +174,8 @@ describe('TOGGLE_EPICS_SWIMLANES', () => {
describe('RECEIVE_BOARD_LISTS_SUCCESS', () => { describe('RECEIVE_BOARD_LISTS_SUCCESS', () => {
it('sets epicsSwimlanesFetchInProgress to false and populates boardLists with payload', () => { it('sets epicsSwimlanesFetchInProgress to false and populates boardLists with payload', () => {
const state = { state = {
...state,
epicsSwimlanesFetchInProgress: true, epicsSwimlanesFetchInProgress: true,
boardLists: {}, boardLists: {},
}; };
...@@ -119,7 +189,8 @@ describe('RECEIVE_BOARD_LISTS_SUCCESS', () => { ...@@ -119,7 +189,8 @@ describe('RECEIVE_BOARD_LISTS_SUCCESS', () => {
describe('RECEIVE_SWIMLANES_FAILURE', () => { describe('RECEIVE_SWIMLANES_FAILURE', () => {
it('sets epicsSwimlanesFetchInProgress to false and sets error message', () => { it('sets epicsSwimlanesFetchInProgress to false and sets error message', () => {
const state = { state = {
...state,
epicsSwimlanesFetchInProgress: true, epicsSwimlanesFetchInProgress: true,
error: undefined, error: undefined,
}; };
...@@ -134,8 +205,9 @@ describe('RECEIVE_SWIMLANES_FAILURE', () => { ...@@ -134,8 +205,9 @@ describe('RECEIVE_SWIMLANES_FAILURE', () => {
}); });
describe('RECEIVE_EPICS_SUCCESS', () => { describe('RECEIVE_EPICS_SUCCESS', () => {
it('populates epics with payload', () => { it('populates epics with payload and set epicsFlags loading to true', () => {
const state = { state = {
...state,
epics: {}, epics: {},
}; };
......
...@@ -2678,12 +2678,6 @@ msgstr "" ...@@ -2678,12 +2678,6 @@ msgstr ""
msgid "An error occurred while checking group path. Please refresh and try again." msgid "An error occurred while checking group path. Please refresh and try again."
msgstr "" msgstr ""
msgid "An error occurred while creating the issue. Please try again."
msgstr ""
msgid "An error occurred while creating the list. Please try again."
msgstr ""
msgid "An error occurred while decoding the file." msgid "An error occurred while decoding the file."
msgstr "" msgstr ""
...@@ -2756,18 +2750,12 @@ msgstr "" ...@@ -2756,18 +2750,12 @@ msgstr ""
msgid "An error occurred while fetching the Service Desk address." msgid "An error occurred while fetching the Service Desk address."
msgstr "" msgstr ""
msgid "An error occurred while fetching the board issues. Please reload the page."
msgstr ""
msgid "An error occurred while fetching the board lists. Please reload the page." msgid "An error occurred while fetching the board lists. Please reload the page."
msgstr "" msgstr ""
msgid "An error occurred while fetching the board lists. Please try again." msgid "An error occurred while fetching the board lists. Please try again."
msgstr "" msgstr ""
msgid "An error occurred while fetching the board swimlanes. Please reload the page."
msgstr ""
msgid "An error occurred while fetching the builds." msgid "An error occurred while fetching the builds."
msgstr "" msgstr ""
...@@ -2879,9 +2867,6 @@ msgstr "" ...@@ -2879,9 +2867,6 @@ msgstr ""
msgid "An error occurred while moving the issue." msgid "An error occurred while moving the issue."
msgstr "" msgstr ""
msgid "An error occurred while moving the issue. Please try again."
msgstr ""
msgid "An error occurred while parsing recent searches" msgid "An error occurred while parsing recent searches"
msgstr "" msgstr ""
...@@ -2951,9 +2936,6 @@ msgstr "" ...@@ -2951,9 +2936,6 @@ msgstr ""
msgid "An error occurred while updating the comment" msgid "An error occurred while updating the comment"
msgstr "" msgstr ""
msgid "An error occurred while updating the list. Please try again."
msgstr ""
msgid "An error occurred while validating group path" msgid "An error occurred while validating group path"
msgstr "" msgstr ""
...@@ -4090,6 +4072,27 @@ msgstr "" ...@@ -4090,6 +4072,27 @@ msgstr ""
msgid "Boards and Board Lists" msgid "Boards and Board Lists"
msgstr "" msgstr ""
msgid "Boards|An error occurred while creating the issue. Please try again."
msgstr ""
msgid "Boards|An error occurred while creating the list. Please try again."
msgstr ""
msgid "Boards|An error occurred while fetching issues. Please reload the page."
msgstr ""
msgid "Boards|An error occurred while fetching the board issues. Please reload the page."
msgstr ""
msgid "Boards|An error occurred while fetching the board swimlanes. Please reload the page."
msgstr ""
msgid "Boards|An error occurred while moving the issue. Please try again."
msgstr ""
msgid "Boards|An error occurred while updating the list. Please try again."
msgstr ""
msgid "Boards|Collapse" msgid "Boards|Collapse"
msgstr "" msgstr ""
......
...@@ -23,9 +23,6 @@ describe('BoardContent', () => { ...@@ -23,9 +23,6 @@ describe('BoardContent', () => {
return new Vuex.Store({ return new Vuex.Store({
getters, getters,
state, state,
actions: {
fetchIssuesForAllLists: () => {},
},
}); });
}; };
......
...@@ -6,12 +6,13 @@ import { ...@@ -6,12 +6,13 @@ import {
mockIssueWithModel, mockIssueWithModel,
mockIssue2WithModel, mockIssue2WithModel,
rawIssue, rawIssue,
mockIssues,
} from '../mock_data'; } from '../mock_data';
import actions, { gqlClient } from '~/boards/stores/actions'; import actions, { gqlClient } from '~/boards/stores/actions';
import * as types from '~/boards/stores/mutation_types'; import * as types from '~/boards/stores/mutation_types';
import { inactiveId, ListType } from '~/boards/constants'; import { inactiveId, ListType } from '~/boards/constants';
import issueMoveListMutation from '~/boards/queries/issue_move_list.mutation.graphql'; import issueMoveListMutation from '~/boards/queries/issue_move_list.mutation.graphql';
import { fullBoardId } from '~/boards/boards_util'; import { fullBoardId, formatListIssues } from '~/boards/boards_util';
const expectNotImplemented = action => { const expectNotImplemented = action => {
it('is not implemented', () => { it('is not implemented', () => {
...@@ -237,6 +238,77 @@ describe('deleteList', () => { ...@@ -237,6 +238,77 @@ describe('deleteList', () => {
expectNotImplemented(actions.deleteList); expectNotImplemented(actions.deleteList);
}); });
describe('fetchIssuesForList', () => {
const listId = mockLists[0].id;
const state = {
endpoints: {
fullPath: 'gitlab-org',
boardId: 1,
},
filterParams: {},
boardType: 'group',
};
const queryResponse = {
data: {
group: {
board: {
lists: {
nodes: [
{
id: listId,
issues: {
nodes: mockIssues,
},
},
],
},
},
},
},
};
const formattedIssues = formatListIssues(queryResponse.data.group.board.lists);
it('should commit mutation RECEIVE_ISSUES_FOR_LIST_SUCCESS on success', done => {
jest.spyOn(gqlClient, 'query').mockResolvedValue(queryResponse);
testAction(
actions.fetchIssuesForList,
listId,
state,
[
{
type: types.RECEIVE_ISSUES_FOR_LIST_SUCCESS,
payload: { listIssues: formattedIssues, listId },
},
],
[],
done,
);
});
it('should commit mutation RECEIVE_ISSUES_FOR_LIST_FAILURE on failure', done => {
jest.spyOn(gqlClient, 'query').mockResolvedValue(Promise.reject());
testAction(
actions.fetchIssuesForList,
listId,
state,
[{ type: types.RECEIVE_ISSUES_FOR_LIST_FAILURE, payload: listId }],
[],
done,
);
});
});
describe('resetIssues', () => {
it('commits RESET_ISSUES mutation', () => {
return testAction(actions.resetIssues, {}, {}, [{ type: types.RESET_ISSUES }], []);
});
});
describe('moveIssue', () => { describe('moveIssue', () => {
const listIssues = { const listIssues = {
'gid://gitlab/List/1': [436, 437], 'gid://gitlab/List/1': [436, 437],
......
...@@ -145,6 +145,23 @@ describe('Board Store Mutations', () => { ...@@ -145,6 +145,23 @@ describe('Board Store Mutations', () => {
expectNotImplemented(mutations.RECEIVE_REMOVE_LIST_ERROR); expectNotImplemented(mutations.RECEIVE_REMOVE_LIST_ERROR);
}); });
describe('RESET_ISSUES', () => {
it('should remove issues from issuesByListId state', () => {
const issuesByListId = {
'gid://gitlab/List/1': [mockIssue.id],
};
state = {
...state,
issuesByListId,
};
mutations[types.RESET_ISSUES](state);
expect(state.issuesByListId).toEqual({ 'gid://gitlab/List/1': [] });
});
});
describe('RECEIVE_ISSUES_FOR_LIST_SUCCESS', () => { describe('RECEIVE_ISSUES_FOR_LIST_SUCCESS', () => {
it('updates issuesByListId and issues on state', () => { it('updates issuesByListId and issues on state', () => {
const listIssues = { const listIssues = {
...@@ -156,7 +173,6 @@ describe('Board Store Mutations', () => { ...@@ -156,7 +173,6 @@ describe('Board Store Mutations', () => {
state = { state = {
...state, ...state,
isLoadingIssues: true,
issuesByListId: {}, issuesByListId: {},
issues: {}, issues: {},
boardLists: mockListsWithModel, boardLists: mockListsWithModel,
...@@ -172,16 +188,6 @@ describe('Board Store Mutations', () => { ...@@ -172,16 +188,6 @@ describe('Board Store Mutations', () => {
}); });
}); });
describe('REQUEST_ISSUES_FOR_ALL_LISTS', () => {
it('sets isLoadingIssues to true', () => {
expect(state.isLoadingIssues).toBe(false);
mutations.REQUEST_ISSUES_FOR_ALL_LISTS(state);
expect(state.isLoadingIssues).toBe(true);
});
});
describe('RECEIVE_ISSUES_FOR_LIST_FAILURE', () => { describe('RECEIVE_ISSUES_FOR_LIST_FAILURE', () => {
it('sets error message', () => { it('sets error message', () => {
state = { state = {
...@@ -200,51 +206,10 @@ describe('Board Store Mutations', () => { ...@@ -200,51 +206,10 @@ describe('Board Store Mutations', () => {
}); });
}); });
describe('RECEIVE_ISSUES_FOR_ALL_LISTS_SUCCESS', () => {
it('sets isLoadingIssues to false and updates issuesByListId object', () => {
const listIssues = {
'gid://gitlab/List/1': [mockIssue.id],
};
const issues = {
'1': mockIssue,
};
state = {
...state,
isLoadingIssues: true,
issuesByListId: {},
issues: {},
};
mutations.RECEIVE_ISSUES_FOR_ALL_LISTS_SUCCESS(state, { listData: listIssues, issues });
expect(state.isLoadingIssues).toBe(false);
expect(state.issuesByListId).toEqual(listIssues);
expect(state.issues).toEqual(issues);
});
});
describe('REQUEST_ADD_ISSUE', () => { describe('REQUEST_ADD_ISSUE', () => {
expectNotImplemented(mutations.REQUEST_ADD_ISSUE); expectNotImplemented(mutations.REQUEST_ADD_ISSUE);
}); });
describe('RECEIVE_ISSUES_FOR_ALL_LISTS_FAILURE', () => {
it('sets isLoadingIssues to false and sets error message', () => {
state = {
...state,
isLoadingIssues: true,
error: undefined,
};
mutations.RECEIVE_ISSUES_FOR_ALL_LISTS_FAILURE(state);
expect(state.isLoadingIssues).toBe(false);
expect(state.error).toEqual(
'An error occurred while fetching the board issues. Please reload the page.',
);
});
});
describe('UPDATE_ISSUE_BY_ID', () => { describe('UPDATE_ISSUE_BY_ID', () => {
const issueId = '1'; const issueId = '1';
const prop = 'id'; const prop = 'id';
...@@ -254,7 +219,6 @@ describe('Board Store Mutations', () => { ...@@ -254,7 +219,6 @@ describe('Board Store Mutations', () => {
beforeEach(() => { beforeEach(() => {
state = { state = {
...state, ...state,
isLoadingIssues: true,
error: undefined, error: undefined,
issues: { issues: {
...issue, ...issue,
......
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