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