Commit 72239bbb authored by Simon Knox's avatar Simon Knox

Merge branch '345206-initialise-boardconfig-state-from-graphql-data' into 'master'

Use GraphQL data to set boardConfig

See merge request gitlab-org/gitlab!74049
parents 721f11de 33db8694
import { sortBy, cloneDeep } from 'lodash';
import { isGid } from '~/graphql_shared/utils';
import { TYPE_BOARD, TYPE_ITERATION, TYPE_MILESTONE, TYPE_USER } from '~/graphql_shared/constants';
import { isGid, convertToGraphQLId } from '~/graphql_shared/utils';
import { ListType, MilestoneIDs, AssigneeFilterType, MilestoneFilterType } from './constants';
export function getMilestone() {
......@@ -80,19 +81,22 @@ export function formatListsPageInfo(lists) {
}
export function fullBoardId(boardId) {
return `gid://gitlab/Board/${boardId}`;
if (!boardId) {
return null;
}
return convertToGraphQLId(TYPE_BOARD, boardId);
}
export function fullIterationId(id) {
return `gid://gitlab/Iteration/${id}`;
return convertToGraphQLId(TYPE_ITERATION, id);
}
export function fullUserId(id) {
return `gid://gitlab/User/${id}`;
return convertToGraphQLId(TYPE_USER, id);
}
export function fullMilestoneId(id) {
return `gid://gitlab/Milestone/${id}`;
return convertToGraphQLId(TYPE_MILESTONE, id);
}
export function fullLabelId(label) {
......
<script>
import { GlModal, GlAlert } from '@gitlab/ui';
import { mapGetters, mapActions, mapState } from 'vuex';
import { TYPE_USER, TYPE_ITERATION, TYPE_MILESTONE } from '~/graphql_shared/constants';
import { convertToGraphQLId, getIdFromGraphQLId } from '~/graphql_shared/utils';
import { getParameterByName, visitUrl } from '~/lib/utils/url_utility';
import { __, s__ } from '~/locale';
import { fullLabelId } from '../boards_util';
import { formType } from '../constants';
import createBoardMutation from '../graphql/board_create.mutation.graphql';
......@@ -158,33 +155,8 @@ export default {
groupPath: this.isGroupBoard ? this.fullPath : undefined,
};
},
issueBoardScopeMutationVariables() {
return {
weight: this.board.weight,
assigneeId: this.board.assignee?.id
? convertToGraphQLId(TYPE_USER, this.board.assignee.id)
: null,
// Temporarily converting to milestone ID due to https://gitlab.com/gitlab-org/gitlab/-/issues/344779
milestoneId: this.board.milestone?.id
? convertToGraphQLId(TYPE_MILESTONE, getIdFromGraphQLId(this.board.milestone.id))
: null,
// Temporarily converting to iteration ID due to https://gitlab.com/gitlab-org/gitlab/-/issues/344779
iterationId: this.board.iteration?.id
? convertToGraphQLId(TYPE_ITERATION, getIdFromGraphQLId(this.board.iteration.id))
: null,
};
},
boardScopeMutationVariables() {
return {
labelIds: this.board.labels.map(fullLabelId),
...(this.isIssueBoard && this.issueBoardScopeMutationVariables),
};
},
mutationVariables() {
return {
...this.baseMutationVariables,
...(this.scopedIssueBoardFeatureEnabled ? this.boardScopeMutationVariables : {}),
};
return this.baseMutationVariables;
},
},
mounted() {
......
......@@ -101,6 +101,7 @@ export default {
},
update(data) {
const board = data.workspace?.board;
this.setBoardConfig(board);
return {
...board,
labels: board?.labels?.nodes,
......@@ -170,7 +171,7 @@ export default {
eventHub.$off('showBoardModal', this.showPage);
},
methods: {
...mapActions(['setError']),
...mapActions(['setError', 'setBoardConfig']),
showPage(page) {
this.currentPage = page;
},
......
......@@ -28,6 +28,12 @@ const apolloProvider = new VueApollo({
function mountBoardApp(el) {
const { boardId, groupId, fullPath, rootPath } = el.dataset;
store.dispatch('fetchBoard', {
fullPath,
fullBoardId: fullBoardId(boardId),
boardType: el.dataset.parent,
});
store.dispatch('setInitialBoardData', {
boardId,
fullBoardId: fullBoardId(boardId),
......@@ -35,17 +41,6 @@ function mountBoardApp(el) {
boardType: el.dataset.parent,
disabled: parseBoolean(el.dataset.disabled) || true,
issuableType: issuableTypes.issue,
boardConfig: {
milestoneId: parseInt(el.dataset.boardMilestoneId, 10),
milestoneTitle: el.dataset.boardMilestoneTitle || '',
iterationId: parseInt(el.dataset.boardIterationId, 10),
iterationTitle: el.dataset.boardIterationTitle || '',
assigneeId: el.dataset.boardAssigneeId,
assigneeUsername: el.dataset.boardAssigneeUsername,
labels: el.dataset.labels ? JSON.parse(el.dataset.labels) : [],
labelIds: el.dataset.labelIds ? JSON.parse(el.dataset.labelIds) : [],
weight: el.dataset.boardWeight ? parseInt(el.dataset.boardWeight, 10) : null,
},
});
// eslint-disable-next-line no-new
......
......@@ -36,6 +36,8 @@ import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { queryToObject } from '~/lib/utils/url_utility';
import { s__ } from '~/locale';
import { gqlClient } from '../graphql';
import projectBoardQuery from '../graphql/project_board.query.graphql';
import groupBoardQuery from '../graphql/group_board.query.graphql';
import boardLabelsQuery from '../graphql/board_labels.query.graphql';
import groupBoardMilestonesQuery from '../graphql/group_board_milestones.query.graphql';
import groupProjectsQuery from '../graphql/group_projects.query.graphql';
......@@ -46,10 +48,44 @@ import projectBoardMilestonesQuery from '../graphql/project_board_milestones.que
import * as types from './mutation_types';
export default {
fetchBoard: ({ commit, dispatch }, { fullPath, fullBoardId, boardType }) => {
const variables = {
fullPath,
boardId: fullBoardId,
};
return gqlClient
.query({
query: boardType === BoardType.group ? groupBoardQuery : projectBoardQuery,
variables,
})
.then(({ data }) => {
const board = data.workspace?.board;
commit(types.RECEIVE_BOARD_SUCCESS, board);
dispatch('setBoardConfig', board);
})
.catch(() => commit(types.RECEIVE_BOARD_FAILURE));
},
setInitialBoardData: ({ commit }, data) => {
commit(types.SET_INITIAL_BOARD_DATA, data);
},
setBoardConfig: ({ commit }, board) => {
const config = {
milestoneId: board.milestone?.id || null,
milestoneTitle: board.milestone?.title || null,
iterationId: board.iteration?.id || null,
iterationTitle: board.iteration?.title || null,
assigneeId: board.assignee?.id || null,
assigneeUsername: board.assignee?.username || null,
labels: board.labels?.nodes || [],
labelIds: board.labels?.nodes?.map((label) => label.id) || [],
weight: board.weight,
};
commit(types.SET_BOARD_CONFIG, config);
},
setActiveId({ commit }, { id, sidebarType }) {
commit(types.SET_ACTIVE_ID, { id, sidebarType });
},
......
export const RECEIVE_BOARD_SUCCESS = 'RECEIVE_BOARD_SUCCESS';
export const RECEIVE_BOARD_FAILURE = 'RECEIVE_BOARD_FAILURE';
export const SET_INITIAL_BOARD_DATA = 'SET_INITIAL_BOARD_DATA';
export const SET_BOARD_CONFIG = 'SET_BOARD_CONFIG';
export const SET_FILTERS = 'SET_FILTERS';
export const CREATE_LIST_SUCCESS = 'CREATE_LIST_SUCCESS';
export const CREATE_LIST_FAILURE = 'CREATE_LIST_FAILURE';
......
......@@ -33,10 +33,20 @@ export const addItemToList = ({ state, listId, itemId, moveBeforeId, moveAfterId
};
export default {
[mutationTypes.RECEIVE_BOARD_SUCCESS]: (state, board) => {
state.board = {
...board,
labels: board?.labels?.nodes || [],
};
},
[mutationTypes.RECEIVE_BOARD_FAILURE]: (state) => {
state.error = s__('Boards|An error occurred while fetching the board. Please reload the page.');
},
[mutationTypes.SET_INITIAL_BOARD_DATA](state, data) {
const {
allowSubEpics,
boardConfig,
boardId,
boardType,
disabled,
......@@ -45,7 +55,6 @@ export default {
issuableType,
} = data;
state.allowSubEpics = allowSubEpics;
state.boardConfig = boardConfig;
state.boardId = boardId;
state.boardType = boardType;
state.disabled = disabled;
......@@ -54,6 +63,10 @@ export default {
state.issuableType = issuableType;
},
[mutationTypes.SET_BOARD_CONFIG](state, boardConfig) {
state.boardConfig = boardConfig;
},
[mutationTypes.RECEIVE_BOARD_LISTS_SUCCESS]: (state, lists) => {
state.boardLists = lists;
},
......
import { inactiveId, ListType } from '~/boards/constants';
export default () => ({
board: {},
boardType: null,
issuableType: null,
fullPath: null,
......
export const MINIMUM_SEARCH_LENGTH = 3;
export const TYPE_BOARD = 'Board';
export const TYPE_CI_RUNNER = 'Ci::Runner';
export const TYPE_CRM_CONTACT = 'CustomerRelations::Contact';
export const TYPE_DISCUSSION = 'Discussion';
export const TYPE_EPIC = 'Epic';
export const TYPE_EPIC_BOARD = 'Boards::EpicBoard';
export const TYPE_GROUP = 'Group';
export const TYPE_ISSUE = 'Issue';
export const TYPE_ITERATION = 'Iteration';
......
......@@ -2,7 +2,14 @@ import {
FiltersInfo as FiltersInfoCE,
formatIssueInput as formatIssueInputCe,
} from '~/boards/boards_util';
import { getIdFromGraphQLId, isGid } from '~/graphql_shared/utils';
import {
TYPE_EPIC_BOARD,
TYPE_ITERATION,
TYPE_EPIC,
TYPE_MILESTONE,
TYPE_USER,
} from '~/graphql_shared/constants';
import { getIdFromGraphQLId, convertToGraphQLId } from '~/graphql_shared/utils';
import { objectToQuery, queryToObject } from '~/lib/utils/url_utility';
import {
EPIC_LANE_BASE_HEIGHT,
......@@ -31,11 +38,11 @@ export function getMilestone({ milestone }) {
}
export function fullEpicId(epicId) {
return `gid://gitlab/Epic/${epicId}`;
return convertToGraphQLId(TYPE_EPIC, epicId);
}
export function fullMilestoneId(milestoneId) {
return `gid://gitlab/Milestone/${milestoneId}`;
return convertToGraphQLId(TYPE_MILESTONE, milestoneId);
}
function fullIterationId(id) {
......@@ -43,10 +50,6 @@ function fullIterationId(id) {
return null;
}
if (isGid(id)) {
return id;
}
if (id === IterationIDs.CURRENT) {
return 'CURRENT';
}
......@@ -55,7 +58,7 @@ function fullIterationId(id) {
return 'UPCOMING';
}
return `gid://gitlab/Iteration/${id}`;
return convertToGraphQLId(TYPE_ITERATION, id);
}
function fullIterationCadenceId(id) {
......@@ -67,11 +70,11 @@ function fullIterationCadenceId(id) {
}
export function fullUserId(userId) {
return `gid://gitlab/User/${userId}`;
return convertToGraphQLId(TYPE_USER, userId);
}
export function fullEpicBoardId(epicBoardId) {
return `gid://gitlab/Boards::EpicBoard/${epicBoardId}`;
return convertToGraphQLId(TYPE_EPIC_BOARD, epicBoardId);
}
export function calculateSwimlanesBufferSize(listTopCoordinate) {
......@@ -209,7 +212,7 @@ export function transformBoardConfig(boardConfig) {
let updatedFilterPath = objectToQuery(updatedBoardConfig);
const filterPath = updatedFilterPath ? updatedFilterPath.split('&') : [];
boardConfig.labels.forEach((label) => {
boardConfig.labels?.forEach((label) => {
const labelTitle = encodeURIComponent(label.title);
const param = `label_name[]=${labelTitle}`;
......
......@@ -3,7 +3,11 @@
// extends a valid Vue single file component.
/* eslint-disable @gitlab/no-runtime-template-compiler */
import { mapGetters } from 'vuex';
import { fullLabelId } from '~/boards/boards_util';
import BoardFormFoss from '~/boards/components/board_form.vue';
import { TYPE_USER } from '~/graphql_shared/constants';
import { convertToGraphQLId } from '~/graphql_shared/utils';
import createEpicBoardMutation from '../graphql/epic_board_create.mutation.graphql';
import destroyEpicBoardMutation from '../graphql/epic_board_destroy.mutation.graphql';
import updateEpicBoardMutation from '../graphql/epic_board_update.mutation.graphql';
......@@ -15,6 +19,22 @@ export default {
currentEpicBoardMutation() {
return this.board.id ? updateEpicBoardMutation : createEpicBoardMutation;
},
issueBoardScopeMutationVariables() {
return {
weight: this.board.weight,
assigneeId: this.board.assignee?.id
? convertToGraphQLId(TYPE_USER, this.board.assignee.id)
: null,
milestoneId: this.board.milestone?.id || null,
iterationId: this.board.iteration?.id || null,
};
},
boardScopeMutationVariables() {
return {
labelIds: this.board.labels.map(fullLabelId),
...(this.isIssueBoard && this.issueBoardScopeMutationVariables),
};
},
mutationVariables() {
return {
...this.baseMutationVariables,
......
<script>
import { mapGetters } from 'vuex';
import { __ } from '~/locale';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import AssigneeSelect from './assignee_select.vue';
import BoardScopeCurrentIteration from './board_scope_current_iteration.vue';
import BoardLabelsSelect from './labels_select.vue';
......@@ -53,7 +52,7 @@ export default {
: __('Board scope affects which epics are displayed for anyone who visits this board');
},
iterationId() {
return getIdFromGraphQLId(this.board.iteration?.id) || null;
return this.board.iteration?.id;
},
},
......
......@@ -17,9 +17,9 @@ export default {
required: true,
},
iterationId: {
type: Number,
type: String,
required: false,
default: 0,
default: null,
},
},
data() {
......
......@@ -17,9 +17,6 @@ export default {
showCreate() {
return this.isEpicBoard || this.multipleIssueBoardsAvailable;
},
showDelete() {
return this.boards.length > 1;
},
currentBoardQuery() {
return this.isEpicBoard ? epicBoardQuery : this.currentBoardQueryCE;
},
......
......@@ -216,7 +216,7 @@ export default {
<template #header>
<dropdown-header
ref="header"
v-model="search"
:search-key="search"
:labels-create-title="footerCreateLabelTitle"
:labels-list-title="$options.i18n.dropdownTitleText"
:show-dropdown-contents-create-view="showDropdownContentsCreateView"
......
......@@ -86,7 +86,8 @@ export default {
selectMilestone(milestone) {
this.selected = milestone;
this.toggleEdit();
this.$emit('set-milestone', milestone?.id || null);
// TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/354264
this.$emit('set-milestone', milestone?.id !== ANY_MILESTONE.id ? milestone?.id : null);
},
toggleEdit() {
if (!this.isEditing && !this.isDropdownShowing) {
......
......@@ -42,8 +42,8 @@ export const IterationFilterType = {
};
export const IterationIDs = {
NONE: 0,
CURRENT: -4,
NONE: 'gid://gitlab/Iteration/0',
CURRENT: 'gid://gitlab/Iteration/-4',
};
export const MilestoneFilterType = {
......
......@@ -33,17 +33,6 @@ function mountBoardApp(el) {
boardId,
fullBoardId: fullEpicBoardId(boardId),
fullPath,
boardConfig: {
milestoneId: parseInt(el.dataset.boardMilestoneId, 10),
milestoneTitle: el.dataset.boardMilestoneTitle || '',
iterationId: parseInt(el.dataset.boardIterationId, 10),
iterationTitle: el.dataset.boardIterationTitle || '',
assigneeId: el.dataset.boardAssigneeId,
assigneeUsername: el.dataset.boardAssigneeUsername,
labels: el.dataset.labels ? JSON.parse(el.dataset.labels) : [],
labelIds: el.dataset.labelIds ? JSON.parse(el.dataset.labelIds) : [],
weight: el.dataset.boardWeight ? parseInt(el.dataset.boardWeight, 10) : null,
},
});
// eslint-disable-next-line no-new
......
......@@ -208,7 +208,7 @@ describe('BoardForm', () => {
id: 1,
},
milestone: {
id: 2,
id: 'gid://gitlab/Milestone/2',
},
iteration: {
id: 'gid://gitlab/Iteration/3',
......
......@@ -40,6 +40,7 @@ describe('BoardsSelector', () => {
...defaultStore,
actions: {
setError: jest.fn(),
setBoardConfig: jest.fn(),
},
getters: {
isEpicBoard: () => isEpicBoard,
......
......@@ -5885,6 +5885,9 @@ msgstr ""
msgid "Boards|An error occurred while fetching the board swimlanes. Please reload the page."
msgstr ""
msgid "Boards|An error occurred while fetching the board. Please reload the page."
msgstr ""
msgid "Boards|An error occurred while generating lists. Please reload the page."
msgstr ""
......
......@@ -41,6 +41,7 @@ describe('BoardsSelector', () => {
...defaultStore,
actions: {
setError: jest.fn(),
setBoardConfig: jest.fn(),
},
getters: {
isGroupBoard: () => isGroupBoard,
......
......@@ -8,6 +8,37 @@ import LabelToken from '~/vue_shared/components/filtered_search_bar/tokens/label
import MilestoneToken from '~/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue';
import ReleaseToken from '~/vue_shared/components/filtered_search_bar/tokens/release_token.vue';
export const mockBoard = {
milestone: {
id: 'gid://gitlab/Milestone/114',
title: '14.9',
},
iteration: {
id: 'gid://gitlab/Iteration/124',
title: 'Iteration 9',
},
assignee: {
id: 'gid://gitlab/User/1',
username: 'admin',
},
labels: {
nodes: [{ id: 'gid://gitlab/Label/32', title: 'Deliverable' }],
},
weight: 2,
};
export const mockBoardConfig = {
milestoneId: 'gid://gitlab/Milestone/114',
milestoneTitle: '14.9',
iterationId: 'gid://gitlab/Iteration/124',
iterationTitle: 'Iteration 9',
assigneeId: 'gid://gitlab/User/1',
assigneeUsername: 'admin',
labels: [{ id: 'gid://gitlab/Label/32', title: 'Deliverable' }],
labelIds: ['gid://gitlab/Label/32'],
weight: 2,
};
export const boardObj = {
id: 1,
name: 'test',
......
......@@ -32,6 +32,8 @@ import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import projectBoardMilestones from '~/boards/graphql/project_board_milestones.query.graphql';
import groupBoardMilestones from '~/boards/graphql/group_board_milestones.query.graphql';
import {
mockBoard,
mockBoardConfig,
mockLists,
mockListsById,
mockIssue,
......@@ -60,6 +62,52 @@ beforeEach(() => {
window.gon = { features: {} };
});
describe('fetchBoard', () => {
const payload = {
fullPath: 'gitlab-org',
fullBoardId: 'gid://gitlab/Board/1',
boardType: 'project',
};
const queryResponse = {
data: {
workspace: {
board: mockBoard,
},
},
};
it('should commit mutation RECEIVE_BOARD_SUCCESS and dispatch setBoardConfig on success', async () => {
jest.spyOn(gqlClient, 'query').mockResolvedValue(queryResponse);
await testAction({
action: actions.fetchBoard,
payload,
expectedMutations: [
{
type: types.RECEIVE_BOARD_SUCCESS,
payload: mockBoard,
},
],
expectedActions: [{ type: 'setBoardConfig', payload: mockBoard }],
});
});
it('should commit mutation RECEIVE_BOARD_FAILURE on failure', async () => {
jest.spyOn(gqlClient, 'query').mockResolvedValue(Promise.reject());
await testAction({
action: actions.fetchBoard,
payload,
expectedMutations: [
{
type: types.RECEIVE_BOARD_FAILURE,
},
],
});
});
});
describe('setInitialBoardData', () => {
it('sets data object', () => {
const mockData = {
......@@ -67,13 +115,21 @@ describe('setInitialBoardData', () => {
bar: 'baz',
};
return testAction(
actions.setInitialBoardData,
mockData,
{},
[{ type: types.SET_INITIAL_BOARD_DATA, payload: mockData }],
[],
);
return testAction({
action: actions.setInitialBoardData,
payload: mockData,
expectedMutations: [{ type: types.SET_INITIAL_BOARD_DATA, payload: mockData }],
});
});
});
describe('setBoardConfig', () => {
it('sets board config object from board object', () => {
return testAction({
action: actions.setBoardConfig,
payload: mockBoard,
expectedMutations: [{ type: types.SET_BOARD_CONFIG, payload: mockBoardConfig }],
});
});
});
......@@ -87,7 +143,7 @@ describe('setFilters', () => {
},
],
[
"and use 'assigneeWildcardId' as filter variable for 'assigneId' param",
"and use 'assigneeWildcardId' as filter variable for 'assigneeId' param",
{
filters: { assigneeId: 'None' },
filterVariables: { assigneeWildcardId: 'NONE', not: {} },
......
......@@ -4,6 +4,7 @@ import * as types from '~/boards/stores/mutation_types';
import mutations from '~/boards/stores/mutations';
import defaultState from '~/boards/stores/state';
import {
mockBoard,
mockLists,
rawIssue,
mockIssue,
......@@ -33,6 +34,27 @@ describe('Board Store Mutations', () => {
state = defaultState();
});
describe('RECEIVE_BOARD_SUCCESS', () => {
it('Should set board to state', () => {
mutations[types.RECEIVE_BOARD_SUCCESS](state, mockBoard);
expect(state.board).toEqual({
...mockBoard,
labels: mockBoard.labels.nodes,
});
});
});
describe('RECEIVE_BOARD_FAILURE', () => {
it('Should set error in state', () => {
mutations[types.RECEIVE_BOARD_FAILURE](state);
expect(state.error).toEqual(
'An error occurred while fetching the board. Please reload the page.',
);
});
});
describe('SET_INITIAL_BOARD_DATA', () => {
it('Should set initial Boards data to state', () => {
const allowSubEpics = true;
......@@ -40,9 +62,6 @@ describe('Board Store Mutations', () => {
const fullPath = 'gitlab-org';
const boardType = 'group';
const disabled = false;
const boardConfig = {
milestoneTitle: 'Milestone 1',
};
const issuableType = issuableTypes.issue;
mutations[types.SET_INITIAL_BOARD_DATA](state, {
......@@ -51,7 +70,6 @@ describe('Board Store Mutations', () => {
fullPath,
boardType,
disabled,
boardConfig,
issuableType,
});
......@@ -60,11 +78,23 @@ describe('Board Store Mutations', () => {
expect(state.fullPath).toEqual(fullPath);
expect(state.boardType).toEqual(boardType);
expect(state.disabled).toEqual(disabled);
expect(state.boardConfig).toEqual(boardConfig);
expect(state.issuableType).toEqual(issuableType);
});
});
describe('SET_BOARD_CONFIG', () => {
it('Should set board config data o state', () => {
const boardConfig = {
milestoneId: 1,
milestoneTitle: 'Milestone 1',
};
mutations[types.SET_BOARD_CONFIG](state, boardConfig);
expect(state.boardConfig).toEqual(boardConfig);
});
});
describe('RECEIVE_BOARD_LISTS_SUCCESS', () => {
it('Should set boardLists to state', () => {
mutations[types.RECEIVE_BOARD_LISTS_SUCCESS](state, initialBoardListsState);
......
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