Commit d86e7c7f authored by Kushal Pandya's avatar Kushal Pandya

Merge branch '238125-swimlanes-update-fetching-strategy' into 'master'

Swimlanes - Update fetching issues strategy

See merge request gitlab-org/gitlab!57661
parents ee43bdb6 7518d45f
<script>
import { GlSkeletonLoader } from '@gitlab/ui';
export default {
name: 'BoardCardLoading',
components: {
GlSkeletonLoader,
},
};
</script>
<template>
<div
class="board-card-skeleton gl-mb-3 gl-bg-white gl-rounded-base gl-p-5 gl-border-1 gl-border-solid gl-border-gray-50"
>
<div class="board-card-skeleton-inner">
<gl-skeleton-loader :width="340" :height="100">
<rect width="340" height="16" rx="4" />
<rect y="30" width="118" height="16" rx="8" />
<rect x="122" y="30" width="130" height="16" rx="8" />
<rect y="62" width="38" height="16" rx="4" />
<circle cx="320" cy="68" r="16" />
</gl-skeleton-loader>
</div>
</div>
</template>
...@@ -127,7 +127,7 @@ export default { ...@@ -127,7 +127,7 @@ export default {
</component> </component>
<epics-swimlanes <epics-swimlanes
v-else v-else-if="boardListsToUse.length"
ref="swimlanes" ref="swimlanes"
:lists="boardListsToUse" :lists="boardListsToUse"
:can-admin-list="canAdminList" :can-admin-list="canAdminList"
......
...@@ -471,3 +471,13 @@ ...@@ -471,3 +471,13 @@
.board-header-collapsed-info-icon:hover { .board-header-collapsed-info-icon:hover {
color: var(--gray-900, $gray-900); color: var(--gray-900, $gray-900);
} }
.board-card-skeleton {
height: 110px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
.board-card-skeleton-inner {
width: 340px;
height: 100px;
}
}
<script> <script>
import { GlButton, GlIcon, GlLink, GlLoadingIcon, GlPopover, GlTooltipDirective } from '@gitlab/ui'; import { GlButton, GlIcon, GlLink, GlPopover, GlTooltipDirective } from '@gitlab/ui';
import { mapActions, mapGetters, mapState } from 'vuex'; import { mapActions, mapGetters, mapState } from 'vuex';
import createFlash from '~/flash'; import createFlash from '~/flash';
import { formatDate } from '~/lib/utils/datetime_utility'; import { formatDate } from '~/lib/utils/datetime_utility';
...@@ -13,7 +13,6 @@ export default { ...@@ -13,7 +13,6 @@ export default {
GlButton, GlButton,
GlIcon, GlIcon,
GlLink, GlLink,
GlLoadingIcon,
GlPopover, GlPopover,
IssuesLaneList, IssuesLaneList,
}, },
...@@ -50,7 +49,7 @@ export default { ...@@ -50,7 +49,7 @@ export default {
}; };
}, },
computed: { computed: {
...mapState(['epicsFlags', 'filterParams']), ...mapState(['filterParams']),
...mapGetters(['getIssuesByEpic']), ...mapGetters(['getIssuesByEpic']),
isOpen() { isOpen() {
return this.epic.state === statusType.open; return this.epic.state === statusType.open;
...@@ -82,28 +81,12 @@ export default { ...@@ -82,28 +81,12 @@ export default {
epicDateString() { epicDateString() {
return formatDate(this.epic.createdAt); return formatDate(this.epic.createdAt);
}, },
isLoading() {
return Boolean(this.epicsFlags[this.epic.id]?.isLoading);
},
shouldDisplay() { shouldDisplay() {
return this.issuesCount > 0 || this.isLoading; return this.issuesCount > 0;
},
},
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', 'updateBoardEpicUserPreferences']), ...mapActions(['updateBoardEpicUserPreferences']),
toggleCollapsed() { toggleCollapsed() {
this.isCollapsed = !this.isCollapsed; this.isCollapsed = !this.isCollapsed;
...@@ -149,7 +132,6 @@ export default { ...@@ -149,7 +132,6 @@ 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"
...@@ -160,7 +142,6 @@ export default { ...@@ -160,7 +142,6 @@ export default {
<gl-icon class="gl-mr-2 gl-flex-shrink-0" name="issues" /> <gl-icon class="gl-mr-2 gl-flex-shrink-0" name="issues" />
<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="!isCollapsed" class="gl-display-flex gl-pb-5" data-testid="board-epic-lane-issues"> <div v-if="!isCollapsed" class="gl-display-flex gl-pb-5" data-testid="board-epic-lane-issues">
......
...@@ -13,6 +13,7 @@ import { calculateSwimlanesBufferSize } from '../boards_util'; ...@@ -13,6 +13,7 @@ import { calculateSwimlanesBufferSize } from '../boards_util';
import { DRAGGABLE_TAG, EPIC_LANE_BASE_HEIGHT } from '../constants'; import { DRAGGABLE_TAG, EPIC_LANE_BASE_HEIGHT } from '../constants';
import EpicLane from './epic_lane.vue'; import EpicLane from './epic_lane.vue';
import IssuesLaneList from './issues_lane_list.vue'; import IssuesLaneList from './issues_lane_list.vue';
import SwimlanesLoadingSkeleton from './swimlanes_loading_skeleton.vue';
export default { export default {
EpicLane, EpicLane,
...@@ -24,6 +25,7 @@ export default { ...@@ -24,6 +25,7 @@ export default {
IssuesLaneList, IssuesLaneList,
GlButton, GlButton,
GlIcon, GlIcon,
SwimlanesLoadingSkeleton,
VirtualList, VirtualList,
}, },
directives: { directives: {
...@@ -51,7 +53,14 @@ export default { ...@@ -51,7 +53,14 @@ export default {
}; };
}, },
computed: { computed: {
...mapState(['epics', 'pageInfoByListId', 'listsFlags', 'addColumnForm']), ...mapState([
'epics',
'pageInfoByListId',
'listsFlags',
'addColumnForm',
'filterParams',
'epicsSwimlanesFetchInProgress',
]),
...mapGetters(['getUnassignedIssues']), ...mapGetters(['getUnassignedIssues']),
addColumnFormVisible() { addColumnFormVisible() {
return this.addColumnForm?.visible; return this.addColumnForm?.visible;
...@@ -89,12 +98,34 @@ export default { ...@@ -89,12 +98,34 @@ export default {
this.lists.some((list) => this.pageInfoByListId[list.id]?.hasNextPage) this.lists.some((list) => this.pageInfoByListId[list.id]?.hasNextPage)
); );
}, },
isLoading() {
const {
epicLanesFetchInProgress,
listItemsFetchInProgress,
} = this.epicsSwimlanesFetchInProgress;
return epicLanesFetchInProgress && listItemsFetchInProgress;
},
},
watch: {
filterParams: {
handler() {
Promise.all(
this.lists.map((list) => {
return this.fetchItemsForList({ listId: list.id });
}),
)
.then(() => this.doneLoadingSwimlanesItems())
.catch(() => {});
},
deep: true,
immediate: true,
},
}, },
mounted() { mounted() {
this.bufferSize = calculateSwimlanesBufferSize(this.$el.offsetTop); this.bufferSize = calculateSwimlanesBufferSize(this.$el.offsetTop);
}, },
methods: { methods: {
...mapActions(['moveList', 'fetchItemsForList']), ...mapActions(['moveList', 'fetchItemsForList', 'doneLoadingSwimlanesItems']),
handleDragOnEnd(params) { handleDragOnEnd(params) {
const { newIndex, oldIndex, item, to } = params; const { newIndex, oldIndex, item, to } = params;
const { listId } = item.dataset; const { listId } = item.dataset;
...@@ -146,7 +177,8 @@ export default { ...@@ -146,7 +177,8 @@ export default {
data-testid="board-swimlanes" data-testid="board-swimlanes"
data_qa_selector="board_epics_swimlanes" data_qa_selector="board_epics_swimlanes"
> >
<div> <swimlanes-loading-skeleton v-if="isLoading" />
<div v-else>
<component <component
:is="treeRootWrapper" :is="treeRootWrapper"
v-bind="treeRootOptions" v-bind="treeRootOptions"
......
<script>
import BoardCardLoadingSkeleton from '~/boards/components/board_card_loading_skeleton.vue';
export default {
components: {
BoardCardLoadingSkeleton,
},
};
</script>
<template>
<div class="gl-px-3">
<div v-for="i in 3" :key="i" class="gl-mt-6 gl-bg-gray-100 gl-display-flex gl-rounded-base">
<div v-for="j in 4" :key="j" class="gl-px-3 gl-pt-3">
<board-card-loading-skeleton />
</div>
</div>
</div>
</template>
...@@ -346,22 +346,6 @@ export default { ...@@ -346,22 +346,6 @@ export default {
.catch(() => commit(types.RECEIVE_ITEMS_FOR_LIST_FAILURE, listId)); .catch(() => commit(types.RECEIVE_ITEMS_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(({ listItems }) => {
commit(types.RECEIVE_ISSUES_FOR_EPIC_SUCCESS, { ...listItems, 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);
...@@ -386,6 +370,10 @@ export default { ...@@ -386,6 +370,10 @@ export default {
commit(types.SET_EPICS_SWIMLANES); commit(types.SET_EPICS_SWIMLANES);
}, },
doneLoadingSwimlanesItems: ({ commit }) => {
commit(types.DONE_LOADING_SWIMLANES_ITEMS);
},
resetEpics: ({ commit }) => { resetEpics: ({ commit }) => {
commit(types.RESET_EPICS); commit(types.RESET_EPICS);
}, },
......
...@@ -11,11 +11,9 @@ export const TOGGLE_PROMOTION_STATE = 'TOGGLE_PROMOTION_STATE'; ...@@ -11,11 +11,9 @@ export const TOGGLE_PROMOTION_STATE = 'TOGGLE_PROMOTION_STATE';
export const REQUEST_ITEMS_FOR_LIST = 'REQUEST_ITEMS_FOR_LIST'; export const REQUEST_ITEMS_FOR_LIST = 'REQUEST_ITEMS_FOR_LIST';
export const RECEIVE_ITEMS_FOR_LIST_FAILURE = 'RECEIVE_ITEMS_FOR_LIST_FAILURE'; export const RECEIVE_ITEMS_FOR_LIST_FAILURE = 'RECEIVE_ITEMS_FOR_LIST_FAILURE';
export const RECEIVE_ITEMS_FOR_LIST_SUCCESS = 'RECEIVE_ITEMS_FOR_LIST_SUCCESS'; export const RECEIVE_ITEMS_FOR_LIST_SUCCESS = 'RECEIVE_ITEMS_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 SET_EPICS_SWIMLANES = 'SET_EPICS_SWIMLANES'; export const SET_EPICS_SWIMLANES = 'SET_EPICS_SWIMLANES';
export const DONE_LOADING_SWIMLANES_ITEMS = 'DONE_LOADING_SWIMLANES_ITEMS';
export const RECEIVE_BOARD_LISTS_SUCCESS = 'RECEIVE_BOARD_LISTS_SUCCESS'; export const RECEIVE_BOARD_LISTS_SUCCESS = 'RECEIVE_BOARD_LISTS_SUCCESS';
export const RECEIVE_BOARD_LISTS_FAILURE = 'RECEIVE_BOARD_LISTS_FAILURE'; export const RECEIVE_BOARD_LISTS_FAILURE = 'RECEIVE_BOARD_LISTS_FAILURE';
export const UPDATE_LIST_SUCCESS = 'UPDATE_LIST_SUCCESS'; export const UPDATE_LIST_SUCCESS = 'UPDATE_LIST_SUCCESS';
......
...@@ -77,11 +77,11 @@ export default { ...@@ -77,11 +77,11 @@ export default {
union(state.boardItemsByListId[listId] || [], listData[listId]), union(state.boardItemsByListId[listId] || [], listData[listId]),
); );
Vue.set(state.pageInfoByListId, listId, listPageInfo[listId]); Vue.set(state.pageInfoByListId, listId, listPageInfo[listId]);
Vue.set(state.listsFlags, listId, { Vue.set(state.listsFlags[listId], 'isLoading', false);
isLoading: false, Vue.set(state.listsFlags[listId], 'isLoadingMore', false);
isLoadingMore: false, if (noEpicIssues) {
unassignedIssuesCount: noEpicIssues ? listItemsCount : undefined, Vue.set(state.listsFlags[listId], 'unassignedIssuesCount', listItemsCount);
}); }
}, },
[mutationTypes.RECEIVE_ITEMS_FOR_LIST_FAILURE]: (state, listId) => { [mutationTypes.RECEIVE_ITEMS_FOR_LIST_FAILURE]: (state, listId) => {
...@@ -92,48 +92,41 @@ export default { ...@@ -92,48 +92,41 @@ export default {
Vue.set(state.listsFlags, listId, { isLoading: false, isLoadingMore: false }); Vue.set(state.listsFlags, listId, { isLoading: false, isLoadingMore: false });
}, },
[mutationTypes.REQUEST_ISSUES_FOR_EPIC]: (state, epicId) => {
Vue.set(state.epicsFlags, epicId, { isLoading: true });
},
[mutationTypes.RECEIVE_ISSUES_FOR_EPIC_SUCCESS]: (state, { listData, boardItems, epicId }) => {
Object.entries(listData).forEach(([listId, list]) => {
Vue.set(
state.boardItemsByListId,
listId,
union(state.boardItemsByListId[listId] || [], list),
);
});
Vue.set(state, 'boardItems', { ...state.boardItems, ...boardItems });
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; Vue.set(state, 'epicsSwimlanesFetchInProgress', {
epicLanesFetchInProgress: true,
listItemsFetchInProgress: true,
});
}, },
[mutationTypes.SET_EPICS_SWIMLANES]: (state) => { [mutationTypes.SET_EPICS_SWIMLANES]: (state) => {
state.isShowingEpicsSwimlanes = true; state.isShowingEpicsSwimlanes = true;
state.epicsSwimlanesFetchInProgress = true; Vue.set(state, 'epicsSwimlanesFetchInProgress', {
epicLanesFetchInProgress: true,
listItemsFetchInProgress: true,
});
},
[mutationTypes.DONE_LOADING_SWIMLANES_ITEMS]: (state) => {
Vue.set(state, 'epicsSwimlanesFetchInProgress', {
...state.epicsSwimlanesFetchInProgress,
listItemsFetchInProgress: false,
});
}, },
[mutationTypes.RECEIVE_BOARD_LISTS_SUCCESS]: (state, boardLists) => { [mutationTypes.RECEIVE_BOARD_LISTS_SUCCESS]: (state, boardLists) => {
state.boardLists = boardLists; state.boardLists = boardLists;
state.epicsSwimlanesFetchInProgress = false;
}, },
[mutationTypes.RECEIVE_SWIMLANES_FAILURE]: (state) => { [mutationTypes.RECEIVE_SWIMLANES_FAILURE]: (state) => {
state.error = s__( state.error = s__(
'Boards|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; Vue.set(state, 'epicsSwimlanesFetchInProgress', {
...state.epicsSwimlanesFetchInProgress,
epicLanesFetchInProgress: false,
});
}, },
[mutationTypes.RECEIVE_EPICS_SUCCESS]: (state, { epics, canAdminEpic }) => { [mutationTypes.RECEIVE_EPICS_SUCCESS]: (state, { epics, canAdminEpic }) => {
......
...@@ -5,13 +5,15 @@ export default () => ({ ...@@ -5,13 +5,15 @@ export default () => ({
canAdminEpic: false, canAdminEpic: false,
isShowingEpicsSwimlanes: false, isShowingEpicsSwimlanes: false,
epicsSwimlanesFetchInProgress: false, epicsSwimlanesFetchInProgress: {
epicLanesFetchInProgress: false,
listItemsFetchInProgress: false,
},
// The epic data stored in 'epics' do not always persist // The epic data stored in 'epics' do not always persist
// and will be cleared with changes to the filter. // and will be cleared with changes to the filter.
epics: [], epics: [],
epicsCacheById: {}, epicsCacheById: {},
epicFetchInProgress: false, epicFetchInProgress: false,
epicsFlags: {},
milestones: [], milestones: [],
milestonesLoading: false, milestonesLoading: false,
iterations: [], iterations: [],
......
---
title: Add Swimlanes loading skeleton
merge_request: 57661
author:
type: changed
...@@ -32,6 +32,8 @@ RSpec.describe 'epics swimlanes', :js do ...@@ -32,6 +32,8 @@ RSpec.describe 'epics swimlanes', :js do
it 'displays epics swimlanes when link to boards with group_by epic in URL' do it 'displays epics swimlanes when link to boards with group_by epic in URL' do
expect(page).to have_selector('[data-testid="board-swimlanes"]') expect(page).to have_selector('[data-testid="board-swimlanes"]')
wait_for_all_requests
epic_lanes = page.all(:css, '.board-epic-lane') epic_lanes = page.all(:css, '.board-epic-lane')
expect(epic_lanes.length).to eq(2) expect(epic_lanes.length).to eq(2)
end end
......
import { GlIcon, GlLoadingIcon } from '@gitlab/ui'; import { GlIcon } from '@gitlab/ui';
import { createLocalVue, shallowMount } from '@vue/test-utils'; import { createLocalVue, shallowMount } from '@vue/test-utils';
import Vuex from 'vuex'; import Vuex from 'vuex';
import EpicLane from 'ee/boards/components/epic_lane.vue'; import EpicLane from 'ee/boards/components/epic_lane.vue';
...@@ -16,29 +16,21 @@ describe('EpicLane', () => { ...@@ -16,29 +16,21 @@ describe('EpicLane', () => {
const updateBoardEpicUserPreferencesSpy = jest.fn(); const updateBoardEpicUserPreferencesSpy = jest.fn();
const createStore = ({ isLoading = false, boardItemsByListId = mockIssuesByListId }) => { const createStore = ({ boardItemsByListId = mockIssuesByListId }) => {
return new Vuex.Store({ return new Vuex.Store({
actions: { actions: {
fetchIssuesForEpic: jest.fn(),
updateBoardEpicUserPreferences: updateBoardEpicUserPreferencesSpy, updateBoardEpicUserPreferences: updateBoardEpicUserPreferencesSpy,
}, },
state: { state: {
boardItemsByListId, boardItemsByListId,
boardItems: issues, boardItems: issues,
epicsFlags: {
[mockEpic.id]: { isLoading },
},
}, },
getters, getters,
}); });
}; };
const createComponent = ({ const createComponent = ({ props = {}, boardItemsByListId = mockIssuesByListId } = {}) => {
props = {}, const store = createStore({ boardItemsByListId });
isLoading = false,
boardItemsByListId = mockIssuesByListId,
} = {}) => {
const store = createStore({ isLoading, boardItemsByListId });
const defaultProps = { const defaultProps = {
epic: mockEpic, epic: mockEpic,
...@@ -93,16 +85,6 @@ describe('EpicLane', () => { ...@@ -93,16 +85,6 @@ describe('EpicLane', () => {
}); });
}); });
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);
});
it('invokes `updateBoardEpicUserPreferences` method on collapse', () => { it('invokes `updateBoardEpicUserPreferences` method on collapse', () => {
const collapsedValue = false; const collapsedValue = false;
......
...@@ -8,6 +8,7 @@ import { calculateSwimlanesBufferSize } from 'ee/boards/boards_util'; ...@@ -8,6 +8,7 @@ import { calculateSwimlanesBufferSize } from 'ee/boards/boards_util';
import EpicLane from 'ee/boards/components/epic_lane.vue'; import EpicLane from 'ee/boards/components/epic_lane.vue';
import EpicsSwimlanes from 'ee/boards/components/epics_swimlanes.vue'; import EpicsSwimlanes from 'ee/boards/components/epics_swimlanes.vue';
import IssueLaneList from 'ee/boards/components/issues_lane_list.vue'; import IssueLaneList from 'ee/boards/components/issues_lane_list.vue';
import SwimlanesLoadingSkeleton from 'ee/boards/components/swimlanes_loading_skeleton.vue';
import { EPIC_LANE_BASE_HEIGHT } from 'ee/boards/constants'; import { EPIC_LANE_BASE_HEIGHT } from 'ee/boards/constants';
import getters from 'ee/boards/stores/getters'; import getters from 'ee/boards/stores/getters';
import BoardListHeader from 'ee_else_ce/boards/components/board_list_header.vue'; import BoardListHeader from 'ee_else_ce/boards/components/board_list_header.vue';
...@@ -19,7 +20,12 @@ jest.mock('ee/boards/boards_util'); ...@@ -19,7 +20,12 @@ jest.mock('ee/boards/boards_util');
describe('EpicsSwimlanes', () => { describe('EpicsSwimlanes', () => {
let wrapper; let wrapper;
const createStore = () => { const fetchItemsForListSpy = jest.fn();
const createStore = ({
epicLanesFetchInProgress = false,
listItemsFetchInProgress = false,
} = {}) => {
return new Vuex.Store({ return new Vuex.Store({
state: { state: {
epics: mockEpics, epics: mockEpics,
...@@ -37,13 +43,25 @@ describe('EpicsSwimlanes', () => { ...@@ -37,13 +43,25 @@ describe('EpicsSwimlanes', () => {
unassignedIssuesCount: 1, unassignedIssuesCount: 1,
}, },
}, },
epicsSwimlanesFetchInProgress: {
epicLanesFetchInProgress,
listItemsFetchInProgress,
},
}, },
getters, getters,
actions: {
fetchItemsForList: fetchItemsForListSpy,
},
}); });
}; };
const createComponent = ({ canAdminList = false, swimlanesBufferedRendering = false } = {}) => { const createComponent = ({
const store = createStore(); canAdminList = false,
swimlanesBufferedRendering = false,
epicLanesFetchInProgress = false,
listItemsFetchInProgress = false,
} = {}) => {
const store = createStore({ epicLanesFetchInProgress, listItemsFetchInProgress });
const defaultProps = { const defaultProps = {
lists: mockLists, lists: mockLists,
disabled: false, disabled: false,
...@@ -62,6 +80,11 @@ describe('EpicsSwimlanes', () => { ...@@ -62,6 +80,11 @@ describe('EpicsSwimlanes', () => {
wrapper.destroy(); wrapper.destroy();
}); });
it('calls fetchItemsForList on mounted', () => {
createComponent();
expect(fetchItemsForListSpy).toHaveBeenCalled();
});
describe('computed', () => { describe('computed', () => {
describe('treeRootWrapper', () => { describe('treeRootWrapper', () => {
describe('when canAdminList prop is true', () => { describe('when canAdminList prop is true', () => {
...@@ -121,6 +144,23 @@ describe('EpicsSwimlanes', () => { ...@@ -121,6 +144,23 @@ describe('EpicsSwimlanes', () => {
}); });
}); });
describe('Loading skeleton', () => {
it.each`
epicLanesFetchInProgress | listItemsFetchInProgress | expected
${true} | ${true} | ${true}
${false} | ${true} | ${false}
${true} | ${false} | ${false}
${false} | ${false} | ${false}
`(
'loading is $expected when epicLanesFetchInProgress is $epicLanesFetchInProgress and listItemsFetchInProgress is $listItemsFetchInProgress',
({ epicLanesFetchInProgress, listItemsFetchInProgress, expected }) => {
createComponent({ epicLanesFetchInProgress, listItemsFetchInProgress });
expect(wrapper.find(SwimlanesLoadingSkeleton).exists()).toBe(expected);
},
);
});
describe('when swimlanesBufferedRendering is true', () => { describe('when swimlanesBufferedRendering is true', () => {
const bufferSize = 100; const bufferSize = 100;
......
...@@ -9,7 +9,7 @@ import * as types from 'ee/boards/stores/mutation_types'; ...@@ -9,7 +9,7 @@ import * as types from 'ee/boards/stores/mutation_types';
import mutations from 'ee/boards/stores/mutations'; import mutations from 'ee/boards/stores/mutations';
import { TEST_HOST } from 'helpers/test_constants'; import { TEST_HOST } from 'helpers/test_constants';
import testAction from 'helpers/vuex_action_helper'; import testAction from 'helpers/vuex_action_helper';
import { formatListIssues, formatBoardLists } from '~/boards/boards_util'; import { formatBoardLists } from '~/boards/boards_util';
import { issuableTypes } from '~/boards/constants'; import { issuableTypes } from '~/boards/constants';
import * as typesCE from '~/boards/stores/mutation_types'; import * as typesCE from '~/boards/stores/mutation_types';
import * as commonUtils from '~/lib/utils/common_utils'; import * as commonUtils from '~/lib/utils/common_utils';
...@@ -480,71 +480,6 @@ describe('updateIssueWeight', () => { ...@@ -480,71 +480,6 @@ describe('updateIssueWeight', () => {
expectNotImplemented(actions.updateIssueWeight); expectNotImplemented(actions.updateIssueWeight);
}); });
describe('fetchIssuesForEpic', () => {
const listId = mockLists[0].id;
const epicId = mockEpic.id;
const state = {
fullPath: 'gitlab-org',
boardId: 1,
filterParams: {},
boardType: 'group',
};
const queryResponse = {
data: {
group: {
board: {
lists: {
nodes: [
{
id: listId,
issues: {
edges: [{ node: [mockIssue] }],
},
},
],
},
},
},
},
};
const formattedIssues = formatListIssues(queryResponse.data.group.board.lists);
it('should commit mutations REQUEST_ISSUES_FOR_EPIC and RECEIVE_ITEMS_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_ITEMS_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 startURl = `${TEST_HOST}/groups/gitlab-org/-/boards/1?group_by=epic`; const startURl = `${TEST_HOST}/groups/gitlab-org/-/boards/1?group_by=epic`;
...@@ -608,8 +543,6 @@ describe('toggleEpicSwimlanes', () => { ...@@ -608,8 +543,6 @@ describe('toggleEpicSwimlanes', () => {
describe('setEpicSwimlanes', () => { describe('setEpicSwimlanes', () => {
it('should commit mutation SET_EPICS_SWIMLANES', () => { it('should commit mutation SET_EPICS_SWIMLANES', () => {
jest.spyOn(gqlClient, 'query').mockResolvedValue({});
return testAction( return testAction(
actions.setEpicSwimlanes, actions.setEpicSwimlanes,
null, null,
...@@ -620,6 +553,18 @@ describe('setEpicSwimlanes', () => { ...@@ -620,6 +553,18 @@ describe('setEpicSwimlanes', () => {
}); });
}); });
describe('doneLoadingSwimlanesItems', () => {
it('should commit mutation DONE_LOADING_SWIMLANES_ITEMS', () => {
return testAction(
actions.doneLoadingSwimlanesItems,
null,
{},
[{ type: types.DONE_LOADING_SWIMLANES_ITEMS }],
[],
);
});
});
describe('resetEpics', () => { describe('resetEpics', () => {
it('commits RESET_EPICS mutation', () => { it('commits RESET_EPICS mutation', () => {
return testAction(actions.resetEpics, {}, {}, [{ type: types.RESET_EPICS }], []); return testAction(actions.resetEpics, {}, {}, [{ type: types.RESET_EPICS }], []);
......
...@@ -18,9 +18,6 @@ let state = { ...@@ -18,9 +18,6 @@ let state = {
boardItemsByListId: {}, boardItemsByListId: {},
boardItems: {}, boardItems: {},
boardLists: initialBoardListsState, boardLists: initialBoardListsState,
epicsFlags: {
[epicId]: { isLoading: true },
},
}; };
describe('SET_SHOW_LABELS', () => { describe('SET_SHOW_LABELS', () => {
...@@ -76,53 +73,6 @@ describe('TOGGLE_PROMOTION_STATE', () => { ...@@ -76,53 +73,6 @@ 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 boardItemsByListId 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,
boardItems: issues,
epicId,
});
expect(state.boardItemsByListId).toEqual(listIssues);
expect(state.boardItems).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', () => {
state = { state = {
...@@ -149,12 +99,18 @@ describe('TOGGLE_EPICS_SWIMLANES', () => { ...@@ -149,12 +99,18 @@ describe('TOGGLE_EPICS_SWIMLANES', () => {
it('sets epicsSwimlanesFetchInProgress to true', () => { it('sets epicsSwimlanesFetchInProgress to true', () => {
state = { state = {
...state, ...state,
epicsSwimlanesFetchInProgress: false, epicsSwimlanesFetchInProgress: {
epicLanesFetchInProgress: false,
listItemsFetchInProgress: false,
},
}; };
mutations.TOGGLE_EPICS_SWIMLANES(state); mutations.TOGGLE_EPICS_SWIMLANES(state);
expect(state.epicsSwimlanesFetchInProgress).toBe(true); expect(state.epicsSwimlanesFetchInProgress).toEqual({
epicLanesFetchInProgress: true,
listItemsFetchInProgress: true,
});
}); });
}); });
...@@ -163,42 +119,63 @@ describe('SET_EPICS_SWIMLANES', () => { ...@@ -163,42 +119,63 @@ describe('SET_EPICS_SWIMLANES', () => {
state = { state = {
...state, ...state,
isShowingEpicsSwimlanes: false, isShowingEpicsSwimlanes: false,
epicsSwimlanesFetchInProgress: false, epicsSwimlanesFetchInProgress: {
epicLanesFetchInProgress: false,
listItemsFetchInProgress: false,
},
}; };
mutations.SET_EPICS_SWIMLANES(state); mutations.SET_EPICS_SWIMLANES(state);
expect(state.isShowingEpicsSwimlanes).toBe(true); expect(state.isShowingEpicsSwimlanes).toBe(true);
expect(state.epicsSwimlanesFetchInProgress).toBe(true); expect(state.epicsSwimlanesFetchInProgress).toEqual({
epicLanesFetchInProgress: true,
listItemsFetchInProgress: true,
});
});
});
describe('DONE_LOADING_SWIMLANES_ITEMS', () => {
it('set listItemsFetchInProgress to false', () => {
state = {
...state,
epicsSwimlanesFetchInProgress: {
listItemsFetchInProgress: true,
},
};
mutations.DONE_LOADING_SWIMLANES_ITEMS(state);
expect(state.epicsSwimlanesFetchInProgress.listItemsFetchInProgress).toBe(false);
}); });
}); });
describe('RECEIVE_BOARD_LISTS_SUCCESS', () => { describe('RECEIVE_BOARD_LISTS_SUCCESS', () => {
it('sets epicsSwimlanesFetchInProgress to false and populates boardLists with payload', () => { it('populates boardLists with payload', () => {
state = { state = {
...state, ...state,
epicsSwimlanesFetchInProgress: true,
boardLists: {}, boardLists: {},
}; };
mutations.RECEIVE_BOARD_LISTS_SUCCESS(state, initialBoardListsState); mutations.RECEIVE_BOARD_LISTS_SUCCESS(state, initialBoardListsState);
expect(state.epicsSwimlanesFetchInProgress).toBe(false);
expect(state.boardLists).toEqual(initialBoardListsState); expect(state.boardLists).toEqual(initialBoardListsState);
}); });
}); });
describe('RECEIVE_SWIMLANES_FAILURE', () => { describe('RECEIVE_SWIMLANES_FAILURE', () => {
it('sets epicsSwimlanesFetchInProgress to false and sets error message', () => { it('sets epicLanesFetchInProgress to false and sets error message', () => {
state = { state = {
...state, ...state,
epicsSwimlanesFetchInProgress: true, epicsSwimlanesFetchInProgress: {
epicLanesFetchInProgress: true,
},
error: undefined, error: undefined,
}; };
mutations.RECEIVE_SWIMLANES_FAILURE(state); mutations.RECEIVE_SWIMLANES_FAILURE(state);
expect(state.epicsSwimlanesFetchInProgress).toBe(false); expect(state.epicsSwimlanesFetchInProgress.epicLanesFetchInProgress).toBe(false);
expect(state.error).toEqual( expect(state.error).toEqual(
'An error occurred while fetching the board swimlanes. Please reload the page.', 'An error occurred while fetching the board swimlanes. Please reload the page.',
); );
......
...@@ -4946,9 +4946,6 @@ msgstr "" ...@@ -4946,9 +4946,6 @@ msgstr ""
msgid "Boards|An error occurred while fetching group projects. Please try again." msgid "Boards|An error occurred while fetching group projects. Please try again."
msgstr "" msgstr ""
msgid "Boards|An error occurred while fetching issues. Please reload the page."
msgstr ""
msgid "Boards|An error occurred while fetching labels. Please reload the page." msgid "Boards|An error occurred while fetching labels. Please reload the page."
msgstr "" 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