Commit d3c09d84 authored by Kushal Pandya's avatar Kushal Pandya

Merge branch '218040-swimlanes-fetch-issues-graphql' into 'master'

Swimlanes - Fetch issues using GraphQL endpoint

See merge request gitlab-org/gitlab!39054
parents 42f472da afe8d6ae
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import ListIssue from 'ee_else_ce/boards/models/issue';
export function getMilestone() { export function getMilestone() {
return null; return null;
} }
export function formatListIssues(listIssues) {
return listIssues.nodes.reduce((map, list) => {
return {
...map,
[list.id]: list.issues.nodes.map(
i =>
new ListIssue({
...i,
id: getIdFromGraphQLId(i.id),
labels: i.labels?.nodes || [],
assignees: i.assignees?.nodes || [],
}),
),
};
}, {});
}
export default { export default {
getMilestone, getMilestone,
formatListIssues,
}; };
...@@ -42,7 +42,7 @@ export default { ...@@ -42,7 +42,7 @@ export default {
}, },
}, },
computed: { computed: {
...mapState(['isShowingEpicsSwimlanes']), ...mapState(['isShowingEpicsSwimlanes', 'boardLists']),
isSwimlanesOn() { isSwimlanesOn() {
return this.glFeatures.boardsWithSwimlanes && this.isShowingEpicsSwimlanes; return this.glFeatures.boardsWithSwimlanes && this.isShowingEpicsSwimlanes;
}, },
...@@ -73,11 +73,12 @@ export default { ...@@ -73,11 +73,12 @@ export default {
<epics-swimlanes <epics-swimlanes
v-else v-else
ref="swimlanes" ref="swimlanes"
:lists="lists" :lists="boardLists"
:can-admin-list="canAdminList" :can-admin-list="canAdminList"
:disabled="disabled" :disabled="disabled"
:board-id="boardId" :board-id="boardId"
:group-id="groupId" :group-id="groupId"
:root-path="rootPath"
/> />
</div> </div>
</template> </template>
...@@ -117,7 +117,7 @@ export default () => { ...@@ -117,7 +117,7 @@ export default () => {
boardId: this.boardId, boardId: this.boardId,
fullPath: $boardApp.dataset.fullPath, fullPath: $boardApp.dataset.fullPath,
}; };
this.setEndpoints(endpoints); this.setInitialBoardData({ ...endpoints, boardType: this.parent });
boardsStore.setEndpoints(endpoints); boardsStore.setEndpoints(endpoints);
boardsStore.rootPath = this.boardsEndpoint; boardsStore.rootPath = this.boardsEndpoint;
...@@ -189,7 +189,7 @@ export default () => { ...@@ -189,7 +189,7 @@ export default () => {
} }
}, },
methods: { methods: {
...mapActions(['setEndpoints']), ...mapActions(['setInitialBoardData']),
updateTokens() { updateTokens() {
this.filterManager.updateTokens(); this.filterManager.updateTokens();
}, },
......
...@@ -60,7 +60,9 @@ class List { ...@@ -60,7 +60,9 @@ class List {
this.title = this.milestone.title; this.title = this.milestone.title;
} }
if (!typeInfo.isBlank && this.id) { // doNotFetchIssues is a temporary workaround until issues are fetched using GraphQL on issue boards
// Issue: https://gitlab.com/gitlab-org/gitlab/-/issues/229416
if (!typeInfo.isBlank && this.id && !obj.doNotFetchIssues) {
this.getIssues().catch(() => { this.getIssues().catch(() => {
// TODO: handle request error // TODO: handle request error
}); });
......
...@@ -4,6 +4,7 @@ fragment BoardListShared on BoardList { ...@@ -4,6 +4,7 @@ fragment BoardListShared on BoardList {
position position
listType listType
collapsed collapsed
maxIssueCount
label { label {
id id
title title
......
#import "./issue.fragment.graphql"
query GroupListIssues($fullPath: ID!, $boardId: ID!) {
group(fullPath: $fullPath) {
board(id: $boardId) {
lists {
nodes {
id
issues {
nodes {
...IssueNode
}
}
}
}
}
}
}
#import "~/graphql_shared/fragments/user.fragment.graphql"
fragment IssueNode on Issue {
id
iid
title
referencePath: reference(full: true)
dueDate
timeEstimate
weight
confidential
webUrl
subscribed
blocked
epic {
id
}
assignees {
nodes {
...User
}
}
labels {
nodes {
id
title
color
description
}
}
}
#import "./issue.fragment.graphql"
query ProjectListIssues($fullPath: ID!, $boardId: ID!) {
project(fullPath: $fullPath) {
board(id: $boardId) {
lists {
nodes {
id
issues {
nodes {
...IssueNode
}
}
}
}
}
}
}
import * as types from './mutation_types'; import * as types from './mutation_types';
import createDefaultClient from '~/lib/graphql';
import { BoardType } from '~/boards/constants';
import { formatListIssues } from '../boards_util';
import groupListsIssuesQuery from '../queries/group_lists_issues.query.graphql';
import projectListsIssuesQuery from '../queries/project_lists_issues.query.graphql';
const gqlClient = createDefaultClient();
const notImplemented = () => { const notImplemented = () => {
/* eslint-disable-next-line @gitlab/require-i18n-strings */ /* eslint-disable-next-line @gitlab/require-i18n-strings */
...@@ -6,8 +13,8 @@ const notImplemented = () => { ...@@ -6,8 +13,8 @@ const notImplemented = () => {
}; };
export default { export default {
setEndpoints: ({ commit }, endpoints) => { setInitialBoardData: ({ commit }, data) => {
commit(types.SET_ENDPOINTS, endpoints); commit(types.SET_INITIAL_BOARD_DATA, data);
}, },
setActiveId({ commit }, id) { setActiveId({ commit }, id) {
...@@ -38,6 +45,32 @@ export default { ...@@ -38,6 +45,32 @@ export default {
notImplemented(); notImplemented();
}, },
fetchIssuesForAllLists: ({ state, commit }) => {
commit(types.REQUEST_ISSUES_FOR_ALL_LISTS);
const { endpoints, boardType } = state;
const { fullPath, boardId } = endpoints;
const query = boardType === BoardType.group ? groupListsIssuesQuery : projectListsIssuesQuery;
const variables = {
fullPath,
boardId: `gid://gitlab/Board/${boardId}`,
};
return gqlClient
.query({
query,
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: () => {
notImplemented(); notImplemented();
}, },
......
...@@ -81,7 +81,7 @@ const boardsStore = { ...@@ -81,7 +81,7 @@ const boardsStore = {
showPage(page) { showPage(page) {
this.state.currentPage = page; this.state.currentPage = page;
}, },
addList(listObj) { updateListPosition(listObj) {
const listType = listObj.listType || listObj.list_type; const listType = listObj.listType || listObj.list_type;
let { position } = listObj; let { position } = listObj;
if (listType === ListType.closed) { if (listType === ListType.closed) {
...@@ -91,6 +91,10 @@ const boardsStore = { ...@@ -91,6 +91,10 @@ const boardsStore = {
} }
const list = new List({ ...listObj, position }); const list = new List({ ...listObj, position });
return list;
},
addList(listObj) {
const list = this.updateListPosition(listObj);
this.state.lists = sortBy([...this.state.lists, list], 'position'); this.state.lists = sortBy([...this.state.lists, list], 'position');
return list; return list;
}, },
...@@ -850,19 +854,28 @@ const boardsStore = { ...@@ -850,19 +854,28 @@ const boardsStore = {
}, },
refreshIssueData(issue, obj) { refreshIssueData(issue, obj) {
issue.id = obj.id; // issue.id = obj.id;
issue.iid = obj.iid; // issue.iid = obj.iid;
issue.title = obj.title; // issue.title = obj.title;
issue.confidential = obj.confidential; // issue.confidential = obj.confidential;
issue.dueDate = obj.due_date; // issue.dueDate = obj.due_date || obj.dueDate;
issue.sidebarInfoEndpoint = obj.issue_sidebar_endpoint; // issue.sidebarInfoEndpoint = obj.issue_sidebar_endpoint;
issue.referencePath = obj.reference_path; // issue.referencePath = obj.reference_path || obj.referencePath;
issue.path = obj.real_path; // issue.path = obj.real_path || obj.webUrl;
issue.toggleSubscriptionEndpoint = obj.toggle_subscription_endpoint; // issue.toggleSubscriptionEndpoint = obj.toggle_subscription_endpoint;
// issue.project_id = obj.project_id;
// issue.timeEstimate = obj.time_estimate || obj.timeEstimate;
// issue.assignableLabelsEndpoint = obj.assignable_labels_endpoint;
// issue.blocked = obj.blocked;
// issue.epic = obj.epic;
const convertedObj = convertObjectPropsToCamelCase(obj, {
dropKeys: ['issue_sidebar_endpoint', 'real_path', 'webUrl'],
});
convertedObj.sidebarInfoEndpoint = obj.issue_sidebar_endpoint;
issue.path = obj.real_path || obj.webUrl;
issue.project_id = obj.project_id; issue.project_id = obj.project_id;
issue.timeEstimate = obj.time_estimate; Object.assign(issue, convertedObj);
issue.assignableLabelsEndpoint = obj.assignable_labels_endpoint;
issue.blocked = obj.blocked;
if (obj.project) { if (obj.project) {
issue.project = new IssueProject(obj.project); issue.project = new IssueProject(obj.project);
......
export const SET_ENDPOINTS = 'SET_ENDPOINTS'; export const SET_INITIAL_BOARD_DATA = 'SET_INITIAL_BOARD_DATA';
export const REQUEST_ADD_LIST = 'REQUEST_ADD_LIST'; export const REQUEST_ADD_LIST = 'REQUEST_ADD_LIST';
export const RECEIVE_ADD_LIST_SUCCESS = 'RECEIVE_ADD_LIST_SUCCESS'; export const RECEIVE_ADD_LIST_SUCCESS = 'RECEIVE_ADD_LIST_SUCCESS';
export const RECEIVE_ADD_LIST_ERROR = 'RECEIVE_ADD_LIST_ERROR'; export const RECEIVE_ADD_LIST_ERROR = 'RECEIVE_ADD_LIST_ERROR';
...@@ -8,6 +8,9 @@ export const RECEIVE_UPDATE_LIST_ERROR = 'RECEIVE_UPDATE_LIST_ERROR'; ...@@ -8,6 +8,9 @@ export const RECEIVE_UPDATE_LIST_ERROR = 'RECEIVE_UPDATE_LIST_ERROR';
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_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';
......
...@@ -6,8 +6,10 @@ const notImplemented = () => { ...@@ -6,8 +6,10 @@ const notImplemented = () => {
}; };
export default { export default {
[mutationTypes.SET_ENDPOINTS]: (state, endpoints) => { [mutationTypes.SET_INITIAL_BOARD_DATA]: (state, data) => {
const { boardType, ...endpoints } = data;
state.endpoints = endpoints; state.endpoints = endpoints;
state.boardType = boardType;
}, },
[mutationTypes.SET_ACTIVE_ID](state, id) { [mutationTypes.SET_ACTIVE_ID](state, id) {
...@@ -50,6 +52,20 @@ export default { ...@@ -50,6 +52,20 @@ export default {
notImplemented(); notImplemented();
}, },
[mutationTypes.REQUEST_ISSUES_FOR_ALL_LISTS]: state => {
state.isLoadingIssues = true;
},
[mutationTypes.RECEIVE_ISSUES_FOR_ALL_LISTS_SUCCESS]: (state, listIssues) => {
state.issuesByListId = listIssues;
state.isLoadingIssues = false;
},
[mutationTypes.RECEIVE_ISSUES_FOR_ALL_LISTS_FAILURE]: state => {
state.listIssueFetchFailure = true;
state.isLoadingIssues = false;
},
[mutationTypes.REQUEST_ADD_ISSUE]: () => { [mutationTypes.REQUEST_ADD_ISSUE]: () => {
notImplemented(); notImplemented();
}, },
......
...@@ -2,6 +2,10 @@ import { inactiveId } from '~/boards/constants'; ...@@ -2,6 +2,10 @@ import { inactiveId } from '~/boards/constants';
export default () => ({ export default () => ({
endpoints: {}, endpoints: {},
boardType: null,
isShowingLabels: true, isShowingLabels: true,
activeId: inactiveId, activeId: inactiveId,
issuesByListId: {},
isLoadingIssues: false,
listIssueFetchFailure: false,
}); });
...@@ -598,17 +598,17 @@ ...@@ -598,17 +598,17 @@
$epic-icons-spacing: 40px; $epic-icons-spacing: 40px;
.board-epic-lane { .board-epic-lane {
max-width: calc(100vw - #{$contextual-sidebar-width} - $epic-icons-spacing); max-width: calc(100vw - #{$contextual-sidebar-width} - #{$epic-icons-spacing});
.page-with-icon-sidebar & { .page-with-icon-sidebar & {
max-width: calc(100vw - #{$contextual-sidebar-collapsed-width} - $epic-icons-spacing); max-width: calc(100vw - #{$contextual-sidebar-collapsed-width} - #{$epic-icons-spacing});
} }
.page-with-icon-sidebar .is-compact & { .page-with-icon-sidebar .is-compact & {
max-width: calc(100vw - #{$contextual-sidebar-collapsed-width} - #{$gutter-width} - $epic-icons-spacing); max-width: calc(100vw - #{$contextual-sidebar-collapsed-width} - #{$gutter-width} - #{$epic-icons-spacing});
} }
.is-compact & { .is-compact & {
max-width: calc(100vw - #{$contextual-sidebar-width} - #{$gutter-width} - $epic-icons-spacing); max-width: calc(100vw - #{$contextual-sidebar-width} - #{$gutter-width} - #{$epic-icons-spacing});
} }
} }
...@@ -27,6 +27,23 @@ export default { ...@@ -27,6 +27,23 @@ export default {
type: Array, type: Array,
required: true, required: true,
}, },
issues: {
type: Object,
required: true,
},
isLoadingIssues: {
type: Boolean,
required: false,
default: false,
},
disabled: {
type: Boolean,
required: true,
},
rootPath: {
type: String,
required: true,
},
}, },
data() { data() {
return { return {
...@@ -73,10 +90,12 @@ export default { ...@@ -73,10 +90,12 @@ export default {
}, },
}, },
methods: { methods: {
epicIssuesForList(listIssues) { epicIssuesForList(listId) {
return this.epic.issues.filter(epicIssue => if (this.issues[listId]) {
Boolean(listIssues.find(listIssue => String(listIssue.iid) === epicIssue.iid)), return this.issues[listId].filter(issue => issue.epic && issue.epic.id === this.epic.id);
); }
return [];
}, },
toggleExpanded() { toggleExpanded() {
this.isExpanded = !this.isExpanded; this.isExpanded = !this.isExpanded;
...@@ -137,7 +156,10 @@ export default { ...@@ -137,7 +156,10 @@ export default {
v-for="list in lists" v-for="list in lists"
:key="`${list.id}-issues`" :key="`${list.id}-issues`"
:list="list" :list="list"
:issues="epicIssuesForList(list.issues)" :issues="epicIssuesForList(list.id)"
:is-loading="isLoadingIssues"
:disabled="disabled"
:root-path="rootPath"
/> />
</div> </div>
</div> </div>
......
<script> <script>
import { mapState } from 'vuex'; import { mapActions, mapState } from 'vuex';
import { n__ } from '~/locale'; import { n__ } from '~/locale';
import { GlIcon, GlTooltipDirective } from '@gitlab/ui'; import { GlIcon, GlTooltipDirective } from '@gitlab/ui';
import BoardListHeader from 'ee_else_ce/boards/components/board_list_header.vue'; import BoardListHeader from 'ee_else_ce/boards/components/board_list_header.vue';
...@@ -39,14 +39,30 @@ export default { ...@@ -39,14 +39,30 @@ export default {
required: false, required: false,
default: 0, default: 0,
}, },
rootPath: {
type: String,
required: true,
},
}, },
computed: { computed: {
...mapState(['epics']), ...mapState(['epics', 'issuesByListId', 'isLoadingIssues']),
issuesCount() { unassignedIssuesCount() {
return this.lists.reduce((total, list) => total + list.issues.length, 0); return this.lists.reduce((total, list) => total + this.unassignedIssues(list).length, 0);
}, },
issuesCountTooltipText() { unassignedIssuesCountTooltipText() {
return n__(`%d unassigned issue`, `%d unassigned issues`, this.issuesCount); return n__(`%d unassigned issue`, `%d unassigned issues`, this.unassignedIssuesCount);
},
},
mounted() {
this.fetchIssuesForAllLists();
},
methods: {
...mapActions(['fetchIssuesForAllLists']),
unassignedIssues(list) {
if (this.issuesByListId[list.id]) {
return this.issuesByListId[list.id].filter(i => i.epic === null);
}
return [];
}, },
}, },
}; };
...@@ -78,7 +94,16 @@ export default { ...@@ -78,7 +94,16 @@ export default {
</div> </div>
</div> </div>
<div class="board-epics-swimlanes gl-display-table"> <div class="board-epics-swimlanes gl-display-table">
<epic-lane v-for="epic in epics" :key="epic.id" :epic="epic" :lists="lists" /> <epic-lane
v-for="epic in epics"
:key="epic.id"
:epic="epic"
:lists="lists"
:issues="issuesByListId"
:is-loading-issues="isLoadingIssues"
:disabled="disabled"
:root-path="rootPath"
/>
<div class="board-lane-unassigned-issues gl-sticky gl-display-inline-block gl-left-0"> <div class="board-lane-unassigned-issues gl-sticky gl-display-inline-block gl-left-0">
<div class="gl-left-0 gl-py-5 gl-px-3 gl-display-flex gl-align-items-center"> <div class="gl-left-0 gl-py-5 gl-px-3 gl-display-flex gl-align-items-center">
<span <span
...@@ -88,14 +113,14 @@ export default { ...@@ -88,14 +113,14 @@ export default {
</span> </span>
<span <span
v-gl-tooltip.hover v-gl-tooltip.hover
:title="issuesCountTooltipText" :title="unassignedIssuesCountTooltipText"
class="gl-display-flex gl-align-items-center gl-text-gray-700" class="gl-display-flex gl-align-items-center gl-text-gray-700"
tabindex="0" tabindex="0"
:aria-label="issuesCountTooltipText" :aria-label="unassignedIssuesCountTooltipText"
data-testid="issues-lane-issue-count" data-testid="issues-lane-issue-count"
> >
<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">{{ unassignedIssuesCount }}</span>
</span> </span>
</div> </div>
</div> </div>
...@@ -104,9 +129,12 @@ export default { ...@@ -104,9 +129,12 @@ export default {
v-for="list in lists" v-for="list in lists"
:key="`${list.id}-issues`" :key="`${list.id}-issues`"
:list="list" :list="list"
:issues="list.issues" :issues="unassignedIssues(list)"
:group-id="groupId" :group-id="groupId"
:is-unassigned-issues-lane="true" :is-unassigned-issues-lane="true"
:is-loading="isLoadingIssues"
:disabled="disabled"
:root-path="rootPath"
/> />
</div> </div>
</div> </div>
......
<script> <script>
import { GlLoadingIcon } from '@gitlab/ui';
import eventHub from '~/boards/eventhub'; import eventHub from '~/boards/eventhub';
import BoardCard from '~/boards/components/board_card.vue'; import BoardCard from '~/boards/components/board_card.vue';
import BoardNewIssue from '~/boards/components/board_new_issue.vue'; import BoardNewIssue from '~/boards/components/board_new_issue.vue';
...@@ -7,12 +8,17 @@ export default { ...@@ -7,12 +8,17 @@ export default {
components: { components: {
BoardCard, BoardCard,
BoardNewIssue, BoardNewIssue,
GlLoadingIcon,
}, },
props: { props: {
list: { list: {
type: Object, type: Object,
required: true, required: true,
}, },
disabled: {
type: Boolean,
required: true,
},
issues: { issues: {
type: Array, type: Array,
required: true, required: true,
...@@ -27,6 +33,15 @@ export default { ...@@ -27,6 +33,15 @@ export default {
required: false, required: false,
default: false, default: false,
}, },
isLoading: {
type: Boolean,
required: false,
default: false,
},
rootPath: {
type: String,
required: true,
},
}, },
data() { data() {
return { return {
...@@ -56,6 +71,7 @@ export default { ...@@ -56,6 +71,7 @@ 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"
:group-id="groupId" :group-id="groupId"
...@@ -69,6 +85,7 @@ export default { ...@@ -69,6 +85,7 @@ export default {
:index="index" :index="index"
:list="list" :list="list"
:issue="issue" :issue="issue"
:root-path="rootPath"
/> />
</ul> </ul>
</div> </div>
......
import axios from 'axios'; import axios from 'axios';
import { sortBy } from 'lodash';
import boardsStore from '~/boards/stores/boards_store';
import actionsCE from '~/boards/stores/actions'; import actionsCE from '~/boards/stores/actions';
import boardsStoreEE from './boards_store_ee'; import boardsStoreEE from './boards_store_ee';
import * as types from './mutation_types'; import * as types from './mutation_types';
...@@ -14,7 +16,7 @@ const notImplemented = () => { ...@@ -14,7 +16,7 @@ const notImplemented = () => {
const gqlClient = createDefaultClient(); const gqlClient = createDefaultClient();
const fetchEpicsSwimlanes = ({ endpoints }) => { const fetchEpicsSwimlanes = ({ endpoints, boardType }) => {
const { fullPath, boardId } = endpoints; const { fullPath, boardId } = endpoints;
const query = epicsSwimlanes; const query = epicsSwimlanes;
...@@ -29,7 +31,8 @@ const fetchEpicsSwimlanes = ({ endpoints }) => { ...@@ -29,7 +31,8 @@ const fetchEpicsSwimlanes = ({ endpoints }) => {
variables, variables,
}) })
.then(({ data }) => { .then(({ data }) => {
return data; const { lists } = data[boardType]?.board;
return lists?.nodes;
}); });
}; };
...@@ -101,33 +104,25 @@ export default { ...@@ -101,33 +104,25 @@ export default {
notImplemented(); notImplemented();
}, },
toggleEpicSwimlanes: ({ state, commit, dispatch }) => { toggleEpicSwimlanes: ({ state, commit }) => {
commit(types.TOGGLE_EPICS_SWIMLANES); commit(types.TOGGLE_EPICS_SWIMLANES);
if (state.isShowingEpicsSwimlanes) { if (state.isShowingEpicsSwimlanes) {
Promise.all([fetchEpicsSwimlanes(state), fetchEpics(state)]) Promise.all([fetchEpicsSwimlanes(state), fetchEpics(state)])
.then(([swimlanes, epics]) => { .then(([lists, epics]) => {
if (swimlanes) { if (lists) {
dispatch('receiveSwimlanesSuccess', swimlanes); let boardLists = lists.map(list =>
boardsStore.updateListPosition({ ...list, doNotFetchIssues: true }),
);
boardLists = sortBy([...boardLists], 'position');
commit(types.RECEIVE_BOARD_LISTS_SUCCESS, boardLists);
} }
if (epics) { if (epics) {
dispatch('receiveEpicsSuccess', epics); commit(types.RECEIVE_EPICS_SUCCESS, epics);
} }
}) })
.catch(() => dispatch('receiveSwimlanesFailure')); .catch(() => commit(types.RECEIVE_SWIMLANES_FAILURE));
} }
}, },
receiveSwimlanesSuccess: ({ commit }, swimlanes) => {
commit(types.RECEIVE_SWIMLANES_SUCCESS, swimlanes);
},
receiveSwimlanesFailure: ({ commit }) => {
commit(types.RECEIVE_SWIMLANES_FAILURE);
},
receiveEpicsSuccess: ({ commit }, swimlanes) => {
commit(types.RECEIVE_EPICS_SUCCESS, swimlanes);
},
}; };
...@@ -12,7 +12,7 @@ export const RECEIVE_REMOVE_BOARD_SUCCESS = 'RECEIVE_REMOVE_BOARD_SUCCESS'; ...@@ -12,7 +12,7 @@ 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 TOGGLE_EPICS_SWIMLANES = 'TOGGLE_EPICS_SWIMLANES'; export const TOGGLE_EPICS_SWIMLANES = 'TOGGLE_EPICS_SWIMLANES';
export const RECEIVE_SWIMLANES_SUCCESS = 'RECEIVE_SWIMLANES_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';
export const RECEIVE_EPICS_SUCCESS = 'RECEIVE_EPICS_SUCCESS'; export const RECEIVE_EPICS_SUCCESS = 'RECEIVE_EPICS_SUCCESS';
export const SET_SHOW_LABELS = 'SET_SHOW_LABELS'; export const SET_SHOW_LABELS = 'SET_SHOW_LABELS';
...@@ -69,8 +69,8 @@ export default { ...@@ -69,8 +69,8 @@ export default {
state.epicsSwimlanesFetchInProgress = true; state.epicsSwimlanesFetchInProgress = true;
}, },
[mutationTypes.RECEIVE_SWIMLANES_SUCCESS]: (state, swimlanes) => { [mutationTypes.RECEIVE_BOARD_LISTS_SUCCESS]: (state, boardLists) => {
state.epicsSwimlanes = swimlanes; state.boardLists = boardLists;
state.epicsSwimlanesFetchInProgress = false; state.epicsSwimlanesFetchInProgress = false;
}, },
......
...@@ -8,4 +8,5 @@ export default () => ({ ...@@ -8,4 +8,5 @@ export default () => ({
epicsSwimlanesFetchFailure: false, epicsSwimlanesFetchFailure: false,
epicsSwimlanes: {}, epicsSwimlanes: {},
epics: {}, epics: {},
boardLists: [],
}); });
...@@ -10,6 +10,7 @@ describe('BoardList Component', () => { ...@@ -10,6 +10,7 @@ describe('BoardList Component', () => {
path: '/test', path: '/test',
}, },
real_path: '', real_path: '',
webUrl: '',
}; };
const componentProps = { const componentProps = {
......
...@@ -19,9 +19,19 @@ describe('EpicLane', () => { ...@@ -19,9 +19,19 @@ describe('EpicLane', () => {
}); });
const createComponent = (props = {}) => { const createComponent = (props = {}) => {
const issues = mockLists.reduce((map, list) => {
return {
...map,
[list.id]: mockIssues,
};
}, {});
const defaultProps = { const defaultProps = {
epic: mockEpic, epic: mockEpic,
lists: mockLists.map(listMock => Vue.observable(new List(listMock))), lists: mockLists.map(listMock => Vue.observable(new List(listMock))),
issues,
disabled: false,
rootPath: '/',
}; };
wrapper = shallowMount(EpicLane, { wrapper = shallowMount(EpicLane, {
......
...@@ -51,6 +51,8 @@ describe('IssuesLaneList', () => { ...@@ -51,6 +51,8 @@ describe('IssuesLaneList', () => {
propsData: { propsData: {
list, list,
issues: mockIssues, issues: mockIssues,
disabled: false,
rootPath: '/',
}, },
}); });
}; };
......
...@@ -99,29 +99,3 @@ describe('toggleEpicSwimlanes', () => { ...@@ -99,29 +99,3 @@ describe('toggleEpicSwimlanes', () => {
); );
}); });
}); });
describe('receiveSwimlanesSuccess', () => {
it('should commit mutation RECEIVE_SWIMLANES_SUCCESS', done => {
testAction(
actions.receiveSwimlanesSuccess,
{},
{},
[{ type: types.RECEIVE_SWIMLANES_SUCCESS, payload: {} }],
[],
done,
);
});
});
describe('receiveSwimlanesFailure', () => {
it('should commit mutation RECEIVE_SWIMLANES_SUCCESS', done => {
testAction(
actions.receiveSwimlanesFailure,
null,
{},
[{ type: types.RECEIVE_SWIMLANES_FAILURE }],
[],
done,
);
});
});
...@@ -103,17 +103,17 @@ describe('TOGGLE_EPICS_SWIMLANES', () => { ...@@ -103,17 +103,17 @@ describe('TOGGLE_EPICS_SWIMLANES', () => {
}); });
}); });
describe('RECEIVE_SWIMLANES_SUCCESS', () => { describe('RECEIVE_BOARD_LISTS_SUCCESS', () => {
it('sets epicsSwimlanesFetchInProgress to false and populates epicsSwimlanes with payload', () => { it('sets epicsSwimlanesFetchInProgress to false and populates boardLists with payload', () => {
const state = { const state = {
epicsSwimlanesFetchInProgress: true, epicsSwimlanesFetchInProgress: true,
epicsSwimlanes: {}, boardLists: {},
}; };
mutations.RECEIVE_SWIMLANES_SUCCESS(state, mockLists); mutations.RECEIVE_BOARD_LISTS_SUCCESS(state, mockLists);
expect(state.epicsSwimlanesFetchInProgress).toBe(false); expect(state.epicsSwimlanesFetchInProgress).toBe(false);
expect(state.epicsSwimlanes).toEqual(mockLists); expect(state.boardLists).toEqual(mockLists);
}); });
}); });
......
...@@ -5,7 +5,7 @@ import '~/boards/models/assignee'; ...@@ -5,7 +5,7 @@ import '~/boards/models/assignee';
import '~/boards/models/issue'; import '~/boards/models/issue';
import '~/boards/models/list'; import '~/boards/models/list';
import boardsStore from '~/boards/stores/boards_store'; import boardsStore from '~/boards/stores/boards_store';
import { setMockEndpoints } from './mock_data'; import { setMockEndpoints, mockIssue } from './mock_data';
describe('Issue model', () => { describe('Issue model', () => {
let issue; let issue;
...@@ -14,28 +14,7 @@ describe('Issue model', () => { ...@@ -14,28 +14,7 @@ describe('Issue model', () => {
setMockEndpoints(); setMockEndpoints();
boardsStore.create(); boardsStore.create();
issue = new ListIssue({ issue = new ListIssue(mockIssue);
title: 'Testing',
id: 1,
iid: 1,
confidential: false,
labels: [
{
id: 1,
title: 'test',
color: 'red',
description: 'testing',
},
],
assignees: [
{
id: 1,
name: 'name',
username: 'username',
avatar_url: 'http://avatar_url',
},
],
});
}); });
it('has label', () => { it('has label', () => {
......
...@@ -92,6 +92,29 @@ export const mockMilestone = { ...@@ -92,6 +92,29 @@ export const mockMilestone = {
due_date: '2019-12-31', due_date: '2019-12-31',
}; };
export const mockIssue = {
title: 'Testing',
id: 1,
iid: 1,
confidential: false,
labels: [
{
id: 1,
title: 'test',
color: 'red',
description: 'testing',
},
],
assignees: [
{
id: 1,
name: 'name',
username: 'username',
avatar_url: 'http://avatar_url',
},
],
};
export const BoardsMockData = { export const BoardsMockData = {
GET: { GET: {
'/test/-/boards/1/lists/300/issues?id=300&page=1': { '/test/-/boards/1/lists/300/issues?id=300&page=1': {
......
...@@ -9,18 +9,18 @@ const expectNotImplemented = action => { ...@@ -9,18 +9,18 @@ const expectNotImplemented = action => {
}); });
}; };
describe('setEndpoints', () => { describe('setInitialBoardData', () => {
it('sets endpoints object', () => { it('sets data object', () => {
const mockEndpoints = { const mockData = {
foo: 'bar', foo: 'bar',
bar: 'baz', bar: 'baz',
}; };
return testAction( return testAction(
actions.setEndpoints, actions.setInitialBoardData,
mockEndpoints, mockData,
{}, {},
[{ type: types.SET_ENDPOINTS, payload: mockEndpoints }], [{ type: types.SET_INITIAL_BOARD_DATA, payload: mockData }],
[], [],
); );
}); });
......
import mutations from '~/boards/stores/mutations'; import mutations from '~/boards/stores/mutations';
import * as types from '~/boards/stores/mutation_types';
import defaultState from '~/boards/stores/state'; import defaultState from '~/boards/stores/state';
import { mockIssue } from '../mock_data';
const expectNotImplemented = action => { const expectNotImplemented = action => {
it('is not implemented', () => { it('is not implemented', () => {
...@@ -15,7 +15,7 @@ describe('Board Store Mutations', () => { ...@@ -15,7 +15,7 @@ describe('Board Store Mutations', () => {
state = defaultState(); state = defaultState();
}); });
describe('SET_ENDPOINTS', () => { describe('SET_INITIAL_BOARD_DATA', () => {
it('Should set initial Boards data to state', () => { it('Should set initial Boards data to state', () => {
const endpoints = { const endpoints = {
boardsEndpoint: '/boards/', boardsEndpoint: '/boards/',
...@@ -25,15 +25,17 @@ describe('Board Store Mutations', () => { ...@@ -25,15 +25,17 @@ describe('Board Store Mutations', () => {
boardId: 1, boardId: 1,
fullPath: 'gitlab-org', fullPath: 'gitlab-org',
}; };
const boardType = 'group';
mutations[types.SET_ENDPOINTS](state, endpoints); mutations.SET_INITIAL_BOARD_DATA(state, { ...endpoints, boardType });
expect(state.endpoints).toEqual(endpoints); expect(state.endpoints).toEqual(endpoints);
expect(state.boardType).toEqual(boardType);
}); });
}); });
describe('SET_ACTIVE_ID', () => { describe('SET_ACTIVE_ID', () => {
it('updates aciveListId to be the value that is passed', () => { it('updates activeListId to be the value that is passed', () => {
const expectedId = 1; const expectedId = 1;
mutations.SET_ACTIVE_ID(state, expectedId); mutations.SET_ACTIVE_ID(state, expectedId);
...@@ -78,6 +80,35 @@ describe('Board Store Mutations', () => { ...@@ -78,6 +80,35 @@ describe('Board Store Mutations', () => {
expectNotImplemented(mutations.RECEIVE_REMOVE_LIST_ERROR); expectNotImplemented(mutations.RECEIVE_REMOVE_LIST_ERROR);
}); });
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_ALL_LISTS_SUCCESS', () => {
it('sets isLoadingIssues to false and updates issuesByListId object', () => {
const listIssues = {
'1': [mockIssue],
};
state = {
...state,
isLoadingIssues: true,
issuesByListId: {},
};
mutations.RECEIVE_ISSUES_FOR_ALL_LISTS_SUCCESS(state, listIssues);
expect(state.isLoadingIssues).toBe(false);
expect(state.issuesByListId).toEqual(listIssues);
});
});
describe('REQUEST_ADD_ISSUE', () => { describe('REQUEST_ADD_ISSUE', () => {
expectNotImplemented(mutations.REQUEST_ADD_ISSUE); expectNotImplemented(mutations.REQUEST_ADD_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