889 lines
24 KiB
JavaScript
889 lines
24 KiB
JavaScript
/* eslint-disable no-shadow, no-param-reassign,consistent-return */
|
|
/* global List */
|
|
/* global ListIssue */
|
|
import { sortBy } from 'lodash';
|
|
import Vue from 'vue';
|
|
import BoardsStoreEE from 'ee_else_ce/boards/stores/boards_store_ee';
|
|
import {
|
|
urlParamsToObject,
|
|
getUrlParamsArray,
|
|
parseBoolean,
|
|
convertObjectPropsToCamelCase,
|
|
} from '~/lib/utils/common_utils';
|
|
import createDefaultClient from '~/lib/graphql';
|
|
import axios from '~/lib/utils/axios_utils';
|
|
import { mergeUrlParams } from '~/lib/utils/url_utility';
|
|
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
|
import eventHub from '../eventhub';
|
|
import { ListType } from '../constants';
|
|
import IssueProject from '../models/project';
|
|
import ListLabel from '../models/label';
|
|
import ListAssignee from '../models/assignee';
|
|
import ListMilestone from '../models/milestone';
|
|
|
|
const PER_PAGE = 20;
|
|
export const gqlClient = createDefaultClient();
|
|
|
|
const boardsStore = {
|
|
disabled: false,
|
|
timeTracking: {
|
|
limitToHours: false,
|
|
},
|
|
scopedLabels: {
|
|
enabled: false,
|
|
},
|
|
filter: {
|
|
path: '',
|
|
},
|
|
state: {
|
|
currentBoard: {
|
|
labels: [],
|
|
},
|
|
currentPage: '',
|
|
endpoints: {},
|
|
},
|
|
detail: {
|
|
issue: {},
|
|
list: {},
|
|
},
|
|
moving: {
|
|
issue: {},
|
|
list: {},
|
|
},
|
|
multiSelect: { list: [] },
|
|
|
|
setEndpoints({
|
|
boardsEndpoint,
|
|
listsEndpoint,
|
|
bulkUpdatePath,
|
|
boardId,
|
|
recentBoardsEndpoint,
|
|
fullPath,
|
|
}) {
|
|
const listsEndpointGenerate = `${listsEndpoint}/generate.json`;
|
|
this.state.endpoints = {
|
|
boardsEndpoint,
|
|
boardId,
|
|
listsEndpoint,
|
|
listsEndpointGenerate,
|
|
bulkUpdatePath,
|
|
fullPath,
|
|
recentBoardsEndpoint: `${recentBoardsEndpoint}.json`,
|
|
};
|
|
},
|
|
create() {
|
|
this.state.lists = [];
|
|
this.filter.path = getUrlParamsArray().join('&');
|
|
this.detail = {
|
|
issue: {},
|
|
list: {},
|
|
};
|
|
},
|
|
showPage(page) {
|
|
this.state.currentPage = page;
|
|
},
|
|
updateListPosition(listObj) {
|
|
const listType = listObj.listType || listObj.list_type;
|
|
let { position } = listObj;
|
|
if (listType === ListType.closed) {
|
|
position = Infinity;
|
|
} else if (listType === ListType.backlog) {
|
|
position = -1;
|
|
}
|
|
|
|
const list = new List({ ...listObj, position });
|
|
return list;
|
|
},
|
|
addList(listObj) {
|
|
const list = this.updateListPosition(listObj);
|
|
this.state.lists = sortBy([...this.state.lists, list], 'position');
|
|
return list;
|
|
},
|
|
new(listObj) {
|
|
const list = this.addList(listObj);
|
|
const backlogList = this.findList('type', 'backlog', 'backlog');
|
|
|
|
list
|
|
.save()
|
|
.then(() => {
|
|
// Remove any new issues from the backlog
|
|
// as they will be visible in the new list
|
|
list.issues.forEach(backlogList.removeIssue.bind(backlogList));
|
|
this.state.lists = sortBy(this.state.lists, 'position');
|
|
})
|
|
.catch(() => {
|
|
// https://gitlab.com/gitlab-org/gitlab-foss/issues/30821
|
|
});
|
|
},
|
|
|
|
updateNewListDropdown(listId) {
|
|
// eslint-disable-next-line no-unused-expressions
|
|
document
|
|
.querySelector(`.js-board-list-${getIdFromGraphQLId(listId)}`)
|
|
?.classList.remove('is-active');
|
|
},
|
|
|
|
findIssueLabel(issue, findLabel) {
|
|
return issue.labels.find(label => label.id === findLabel.id);
|
|
},
|
|
|
|
goToNextPage(list) {
|
|
if (list.issuesSize > list.issues.length) {
|
|
if (list.issues.length / PER_PAGE >= 1) {
|
|
list.page += 1;
|
|
}
|
|
|
|
return list.getIssues(false);
|
|
}
|
|
},
|
|
|
|
addListIssue(list, issue, listFrom, newIndex) {
|
|
let moveBeforeId = null;
|
|
let moveAfterId = null;
|
|
|
|
if (!list.findIssue(issue.id)) {
|
|
if (newIndex !== undefined) {
|
|
list.issues.splice(newIndex, 0, issue);
|
|
|
|
if (list.issues[newIndex - 1]) {
|
|
moveBeforeId = list.issues[newIndex - 1].id;
|
|
}
|
|
|
|
if (list.issues[newIndex + 1]) {
|
|
moveAfterId = list.issues[newIndex + 1].id;
|
|
}
|
|
} else {
|
|
list.issues.push(issue);
|
|
}
|
|
|
|
if (list.label) {
|
|
issue.addLabel(list.label);
|
|
}
|
|
|
|
if (list.assignee) {
|
|
if (listFrom && listFrom.type === 'assignee') {
|
|
issue.removeAssignee(listFrom.assignee);
|
|
}
|
|
issue.addAssignee(list.assignee);
|
|
}
|
|
|
|
if (IS_EE && list.milestone) {
|
|
if (listFrom && listFrom.type === 'milestone') {
|
|
issue.removeMilestone(listFrom.milestone);
|
|
}
|
|
issue.addMilestone(list.milestone);
|
|
}
|
|
|
|
if (listFrom) {
|
|
list.issuesSize += 1;
|
|
|
|
list.updateIssueLabel(issue, listFrom, moveBeforeId, moveAfterId);
|
|
}
|
|
}
|
|
},
|
|
findListIssue(list, id) {
|
|
return list.issues.find(issue => issue.id === id);
|
|
},
|
|
|
|
removeList(id, type = 'blank') {
|
|
const list = this.findList('id', id, type);
|
|
|
|
if (!list) return;
|
|
|
|
this.state.lists = this.state.lists.filter(list => list.id !== id);
|
|
},
|
|
moveList(listFrom, orderLists) {
|
|
orderLists.forEach((id, i) => {
|
|
const list = this.findList('id', parseInt(id, 10));
|
|
|
|
list.position = i;
|
|
});
|
|
listFrom.update();
|
|
},
|
|
|
|
addMultipleListIssues(list, issues, listFrom, newIndex) {
|
|
let moveBeforeId = null;
|
|
let moveAfterId = null;
|
|
|
|
const listHasIssues = issues.every(issue => list.findIssue(issue.id));
|
|
|
|
if (!listHasIssues) {
|
|
if (newIndex !== undefined) {
|
|
if (list.issues[newIndex - 1]) {
|
|
moveBeforeId = list.issues[newIndex - 1].id;
|
|
}
|
|
|
|
if (list.issues[newIndex]) {
|
|
moveAfterId = list.issues[newIndex].id;
|
|
}
|
|
|
|
list.issues.splice(newIndex, 0, ...issues);
|
|
} else {
|
|
list.issues.push(...issues);
|
|
}
|
|
|
|
if (list.label) {
|
|
issues.forEach(issue => issue.addLabel(list.label));
|
|
}
|
|
|
|
if (list.assignee) {
|
|
if (listFrom && listFrom.type === 'assignee') {
|
|
issues.forEach(issue => issue.removeAssignee(listFrom.assignee));
|
|
}
|
|
issues.forEach(issue => issue.addAssignee(list.assignee));
|
|
}
|
|
|
|
if (IS_EE && list.milestone) {
|
|
if (listFrom && listFrom.type === 'milestone') {
|
|
issues.forEach(issue => issue.removeMilestone(listFrom.milestone));
|
|
}
|
|
issues.forEach(issue => issue.addMilestone(list.milestone));
|
|
}
|
|
|
|
if (listFrom) {
|
|
list.issuesSize += issues.length;
|
|
|
|
list.updateMultipleIssues(issues, listFrom, moveBeforeId, moveAfterId);
|
|
}
|
|
}
|
|
},
|
|
|
|
removeListIssues(list, removeIssue) {
|
|
list.issues = list.issues.filter(issue => {
|
|
const matchesRemove = removeIssue.id === issue.id;
|
|
|
|
if (matchesRemove) {
|
|
list.issuesSize -= 1;
|
|
issue.removeLabel(list.label);
|
|
}
|
|
|
|
return !matchesRemove;
|
|
});
|
|
},
|
|
removeListMultipleIssues(list, removeIssues) {
|
|
const ids = removeIssues.map(issue => issue.id);
|
|
|
|
list.issues = list.issues.filter(issue => {
|
|
const matchesRemove = ids.includes(issue.id);
|
|
|
|
if (matchesRemove) {
|
|
list.issuesSize -= 1;
|
|
issue.removeLabel(list.label);
|
|
}
|
|
|
|
return !matchesRemove;
|
|
});
|
|
},
|
|
|
|
startMoving(list, issue) {
|
|
Object.assign(this.moving, { list, issue });
|
|
},
|
|
|
|
onNewListIssueResponse(list, issue, data) {
|
|
issue.refreshData(data);
|
|
|
|
if (list.issues.length > 1) {
|
|
const moveBeforeId = list.issues[1].id;
|
|
this.moveIssue(issue.id, null, null, null, moveBeforeId);
|
|
}
|
|
},
|
|
|
|
moveMultipleIssuesToList({ listFrom, listTo, issues, newIndex }) {
|
|
const issueTo = issues.map(issue => listTo.findIssue(issue.id));
|
|
const issueLists = issues.map(issue => issue.getLists()).flat();
|
|
const listLabels = issueLists.map(list => list.label);
|
|
const hasMoveableIssues = issueTo.filter(Boolean).length > 0;
|
|
|
|
if (!hasMoveableIssues) {
|
|
// Check if target list assignee is already present in this issue
|
|
if (
|
|
listTo.type === ListType.assignee &&
|
|
listFrom.type === ListType.assignee &&
|
|
issues.some(issue => issue.findAssignee(listTo.assignee))
|
|
) {
|
|
const targetIssues = issues.map(issue => listTo.findIssue(issue.id));
|
|
targetIssues.forEach(targetIssue => targetIssue.removeAssignee(listFrom.assignee));
|
|
} else if (listTo.type === 'milestone') {
|
|
const currentMilestones = issues.map(issue => issue.milestone);
|
|
const currentLists = this.state.lists
|
|
.filter(list => list.type === 'milestone' && list.id !== listTo.id)
|
|
.filter(list =>
|
|
list.issues.some(listIssue => issues.some(issue => listIssue.id === issue.id)),
|
|
);
|
|
|
|
issues.forEach(issue => {
|
|
currentMilestones.forEach(milestone => {
|
|
issue.removeMilestone(milestone);
|
|
});
|
|
});
|
|
|
|
issues.forEach(issue => {
|
|
issue.addMilestone(listTo.milestone);
|
|
});
|
|
|
|
currentLists.forEach(currentList => {
|
|
issues.forEach(issue => {
|
|
currentList.removeIssue(issue);
|
|
});
|
|
});
|
|
|
|
listTo.addMultipleIssues(issues, listFrom, newIndex);
|
|
} else {
|
|
// Add to new lists issues if it doesn't already exist
|
|
listTo.addMultipleIssues(issues, listFrom, newIndex);
|
|
}
|
|
} else {
|
|
listTo.updateMultipleIssues(issues, listFrom);
|
|
issues.forEach(issue => {
|
|
issue.removeLabel(listFrom.label);
|
|
});
|
|
}
|
|
|
|
if (listTo.type === ListType.closed && listFrom.type !== ListType.backlog) {
|
|
issueLists.forEach(list => {
|
|
issues.forEach(issue => {
|
|
list.removeIssue(issue);
|
|
});
|
|
});
|
|
|
|
issues.forEach(issue => {
|
|
issue.removeLabels(listLabels);
|
|
});
|
|
} else if (listTo.type === ListType.backlog && listFrom.type === ListType.assignee) {
|
|
issues.forEach(issue => {
|
|
issue.removeAssignee(listFrom.assignee);
|
|
});
|
|
issueLists.forEach(list => {
|
|
issues.forEach(issue => {
|
|
list.removeIssue(issue);
|
|
});
|
|
});
|
|
} else if (listTo.type === ListType.backlog && listFrom.type === ListType.milestone) {
|
|
issues.forEach(issue => {
|
|
issue.removeMilestone(listFrom.milestone);
|
|
});
|
|
issueLists.forEach(list => {
|
|
issues.forEach(issue => {
|
|
list.removeIssue(issue);
|
|
});
|
|
});
|
|
} else if (
|
|
this.shouldRemoveIssue(listFrom, listTo) &&
|
|
this.issuesAreContiguous(listFrom, issues)
|
|
) {
|
|
listFrom.removeMultipleIssues(issues);
|
|
}
|
|
},
|
|
|
|
issuesAreContiguous(list, issues) {
|
|
// When there's only 1 issue selected, we can return early.
|
|
if (issues.length === 1) return true;
|
|
|
|
// Create list of ids for issues involved.
|
|
const listIssueIds = list.issues.map(issue => issue.id);
|
|
const movedIssueIds = issues.map(issue => issue.id);
|
|
|
|
// Check if moved issue IDs is sub-array
|
|
// of source list issue IDs (i.e. contiguous selection).
|
|
return listIssueIds.join('|').includes(movedIssueIds.join('|'));
|
|
},
|
|
|
|
moveIssueToList(listFrom, listTo, issue, newIndex) {
|
|
const issueTo = listTo.findIssue(issue.id);
|
|
const issueLists = issue.getLists();
|
|
const listLabels = issueLists.map(listIssue => listIssue.label);
|
|
|
|
if (!issueTo) {
|
|
// Check if target list assignee is already present in this issue
|
|
if (
|
|
listTo.type === 'assignee' &&
|
|
listFrom.type === 'assignee' &&
|
|
issue.findAssignee(listTo.assignee)
|
|
) {
|
|
const targetIssue = listTo.findIssue(issue.id);
|
|
targetIssue.removeAssignee(listFrom.assignee);
|
|
} else if (listTo.type === 'milestone') {
|
|
const currentMilestone = issue.milestone;
|
|
const currentLists = this.state.lists
|
|
.filter(list => list.type === 'milestone' && list.id !== listTo.id)
|
|
.filter(list => list.issues.some(listIssue => issue.id === listIssue.id));
|
|
|
|
issue.removeMilestone(currentMilestone);
|
|
issue.addMilestone(listTo.milestone);
|
|
currentLists.forEach(currentList => currentList.removeIssue(issue));
|
|
listTo.addIssue(issue, listFrom, newIndex);
|
|
} else {
|
|
// Add to new lists issues if it doesn't already exist
|
|
listTo.addIssue(issue, listFrom, newIndex);
|
|
}
|
|
} else {
|
|
listTo.updateIssueLabel(issue, listFrom);
|
|
issueTo.removeLabel(listFrom.label);
|
|
}
|
|
|
|
if (listTo.type === 'closed' && listFrom.type !== 'backlog') {
|
|
issueLists.forEach(list => {
|
|
list.removeIssue(issue);
|
|
});
|
|
issue.removeLabels(listLabels);
|
|
} else if (listTo.type === 'backlog' && listFrom.type === 'assignee') {
|
|
issue.removeAssignee(listFrom.assignee);
|
|
listFrom.removeIssue(issue);
|
|
} else if (listTo.type === 'backlog' && listFrom.type === 'milestone') {
|
|
issue.removeMilestone(listFrom.milestone);
|
|
listFrom.removeIssue(issue);
|
|
} else if (this.shouldRemoveIssue(listFrom, listTo)) {
|
|
listFrom.removeIssue(issue);
|
|
}
|
|
},
|
|
shouldRemoveIssue(listFrom, listTo) {
|
|
return (
|
|
(listTo.type !== 'label' && listFrom.type === 'assignee') ||
|
|
(listTo.type !== 'assignee' && listFrom.type === 'label') ||
|
|
listFrom.type === 'backlog' ||
|
|
listFrom.type === 'closed'
|
|
);
|
|
},
|
|
moveIssueInList(list, issue, oldIndex, newIndex, idArray) {
|
|
const beforeId = parseInt(idArray[newIndex - 1], 10) || null;
|
|
const afterId = parseInt(idArray[newIndex + 1], 10) || null;
|
|
|
|
list.moveIssue(issue, oldIndex, newIndex, beforeId, afterId);
|
|
},
|
|
moveMultipleIssuesInList({ list, issues, oldIndicies, newIndex, idArray }) {
|
|
const beforeId = parseInt(idArray[newIndex - 1], 10) || null;
|
|
const afterId = parseInt(idArray[newIndex + issues.length], 10) || null;
|
|
list.moveMultipleIssues({
|
|
issues,
|
|
oldIndicies,
|
|
newIndex,
|
|
moveBeforeId: beforeId,
|
|
moveAfterId: afterId,
|
|
});
|
|
},
|
|
findList(key, val, type = 'label') {
|
|
const filteredList = this.state.lists.filter(list => {
|
|
const byType = type
|
|
? list.type === type || list.type === 'assignee' || list.type === 'milestone'
|
|
: true;
|
|
|
|
return list[key] === val && byType;
|
|
});
|
|
return filteredList[0];
|
|
},
|
|
findListByLabelId(id) {
|
|
return this.state.lists.find(list => list.type === 'label' && list.label.id === id);
|
|
},
|
|
|
|
toggleFilter(filter) {
|
|
const filterPath = this.filter.path.split('&');
|
|
const filterIndex = filterPath.indexOf(filter);
|
|
|
|
if (filterIndex === -1) {
|
|
filterPath.push(filter);
|
|
} else {
|
|
filterPath.splice(filterIndex, 1);
|
|
}
|
|
|
|
this.filter.path = filterPath.join('&');
|
|
|
|
this.updateFiltersUrl();
|
|
|
|
eventHub.$emit('updateTokens');
|
|
},
|
|
|
|
setListDetail(newList) {
|
|
this.detail.list = newList;
|
|
},
|
|
|
|
updateFiltersUrl() {
|
|
window.history.pushState(null, null, `?${this.filter.path}`);
|
|
},
|
|
|
|
clearDetailIssue() {
|
|
this.setIssueDetail({});
|
|
},
|
|
|
|
setIssueDetail(issueDetail) {
|
|
this.detail.issue = issueDetail;
|
|
},
|
|
|
|
setTimeTrackingLimitToHours(limitToHours) {
|
|
this.timeTracking.limitToHours = parseBoolean(limitToHours);
|
|
},
|
|
|
|
generateBoardGid(boardId) {
|
|
return `gid://gitlab/Board/${boardId}`;
|
|
},
|
|
|
|
generateBoardsPath(id) {
|
|
return `${this.state.endpoints.boardsEndpoint}${id ? `/${id}` : ''}.json`;
|
|
},
|
|
|
|
generateIssuesPath(id) {
|
|
return `${this.state.endpoints.listsEndpoint}${id ? `/${id}` : ''}/issues`;
|
|
},
|
|
|
|
generateIssuePath(boardId, id) {
|
|
return `${gon.relative_url_root}/-/boards/${boardId ? `${boardId}` : ''}/issues${
|
|
id ? `/${id}` : ''
|
|
}`;
|
|
},
|
|
|
|
generateMultiDragPath(boardId) {
|
|
return `${gon.relative_url_root}/-/boards/${boardId ? `${boardId}` : ''}/issues/bulk_move`;
|
|
},
|
|
|
|
all() {
|
|
return axios.get(this.state.endpoints.listsEndpoint);
|
|
},
|
|
|
|
createList(entityId, entityType) {
|
|
const list = {
|
|
[entityType]: entityId,
|
|
};
|
|
|
|
return axios.post(this.state.endpoints.listsEndpoint, {
|
|
list,
|
|
});
|
|
},
|
|
|
|
updateList(id, position, collapsed) {
|
|
return axios.put(`${this.state.endpoints.listsEndpoint}/${id}`, {
|
|
list: {
|
|
position,
|
|
collapsed,
|
|
},
|
|
});
|
|
},
|
|
|
|
updateListFunc(list) {
|
|
const collapsed = !list.isExpanded;
|
|
return this.updateList(list.id, list.position, collapsed).catch(() => {
|
|
// TODO: handle request error
|
|
});
|
|
},
|
|
|
|
destroyList(id) {
|
|
return axios.delete(`${this.state.endpoints.listsEndpoint}/${id}`);
|
|
},
|
|
destroy(list) {
|
|
const index = this.state.lists.indexOf(list);
|
|
this.state.lists.splice(index, 1);
|
|
this.updateNewListDropdown(list.id);
|
|
|
|
this.destroyList(list.id).catch(() => {
|
|
// TODO: handle request error
|
|
});
|
|
},
|
|
|
|
saveList(list) {
|
|
const entity = list.label || list.assignee || list.milestone;
|
|
let entityType = '';
|
|
if (list.label) {
|
|
entityType = 'label_id';
|
|
} else if (list.assignee) {
|
|
entityType = 'assignee_id';
|
|
} else if (IS_EE && list.milestone) {
|
|
entityType = 'milestone_id';
|
|
}
|
|
|
|
return this.createList(entity.id, entityType)
|
|
.then(res => res.data)
|
|
.then(data => {
|
|
list.id = data.id;
|
|
list.type = data.list_type;
|
|
list.position = data.position;
|
|
list.label = data.label;
|
|
|
|
return list.getIssues();
|
|
});
|
|
},
|
|
|
|
getListIssues(list, emptyIssues = true) {
|
|
const data = {
|
|
...urlParamsToObject(this.filter.path),
|
|
page: list.page,
|
|
};
|
|
|
|
if (list.label && data.label_name) {
|
|
data.label_name = data.label_name.filter(label => label !== list.label.title);
|
|
}
|
|
|
|
if (emptyIssues) {
|
|
list.loading = true;
|
|
}
|
|
|
|
return this.getIssuesForList(list.id, data)
|
|
.then(res => res.data)
|
|
.then(data => {
|
|
list.loading = false;
|
|
list.issuesSize = data.size;
|
|
|
|
if (emptyIssues) {
|
|
list.issues = [];
|
|
}
|
|
|
|
data.issues.forEach(issueObj => {
|
|
list.addIssue(new ListIssue(issueObj));
|
|
});
|
|
|
|
return data;
|
|
});
|
|
},
|
|
|
|
getIssuesForList(id, filter = {}) {
|
|
const data = { id };
|
|
Object.keys(filter).forEach(key => {
|
|
data[key] = filter[key];
|
|
});
|
|
|
|
return axios.get(mergeUrlParams(data, this.generateIssuesPath(id)));
|
|
},
|
|
|
|
moveIssue(id, fromListId = null, toListId = null, moveBeforeId = null, moveAfterId = null) {
|
|
return axios.put(this.generateIssuePath(this.state.endpoints.boardId, id), {
|
|
from_list_id: fromListId,
|
|
to_list_id: toListId,
|
|
move_before_id: moveBeforeId,
|
|
move_after_id: moveAfterId,
|
|
});
|
|
},
|
|
|
|
moveListIssues(list, issue, oldIndex, newIndex, moveBeforeId, moveAfterId) {
|
|
list.issues.splice(oldIndex, 1);
|
|
list.issues.splice(newIndex, 0, issue);
|
|
|
|
this.moveIssue(issue.id, null, null, moveBeforeId, moveAfterId).catch(() => {
|
|
// TODO: handle request error
|
|
});
|
|
},
|
|
|
|
moveMultipleIssues({ ids, fromListId, toListId, moveBeforeId, moveAfterId }) {
|
|
return axios.put(this.generateMultiDragPath(this.state.endpoints.boardId), {
|
|
from_list_id: fromListId,
|
|
to_list_id: toListId,
|
|
move_before_id: moveBeforeId,
|
|
move_after_id: moveAfterId,
|
|
ids,
|
|
});
|
|
},
|
|
|
|
moveListMultipleIssues({ list, issues, oldIndicies, newIndex, moveBeforeId, moveAfterId }) {
|
|
oldIndicies.reverse().forEach(index => {
|
|
list.issues.splice(index, 1);
|
|
});
|
|
list.issues.splice(newIndex, 0, ...issues);
|
|
|
|
return this.moveMultipleIssues({
|
|
ids: issues.map(issue => issue.id),
|
|
fromListId: null,
|
|
toListId: null,
|
|
moveBeforeId,
|
|
moveAfterId,
|
|
});
|
|
},
|
|
|
|
newIssue(id, issue) {
|
|
if (typeof id === 'string') {
|
|
id = getIdFromGraphQLId(id);
|
|
}
|
|
|
|
return axios.post(this.generateIssuesPath(id), {
|
|
issue,
|
|
});
|
|
},
|
|
|
|
newListIssue(list, issue) {
|
|
list.addIssue(issue, null, 0);
|
|
list.issuesSize += 1;
|
|
let listId = list.id;
|
|
if (typeof listId === 'string') {
|
|
listId = getIdFromGraphQLId(listId);
|
|
}
|
|
|
|
return this.newIssue(list.id, issue)
|
|
.then(res => res.data)
|
|
.then(data => list.onNewIssueResponse(issue, data));
|
|
},
|
|
|
|
getBacklog(data) {
|
|
return axios.get(
|
|
mergeUrlParams(
|
|
data,
|
|
`${gon.relative_url_root}/-/boards/${this.state.endpoints.boardId}/issues.json`,
|
|
),
|
|
);
|
|
},
|
|
removeIssueLabel(issue, removeLabel) {
|
|
if (removeLabel) {
|
|
issue.labels = issue.labels.filter(label => removeLabel.id !== label.id);
|
|
}
|
|
},
|
|
|
|
addIssueAssignee(issue, assignee) {
|
|
if (!issue.findAssignee(assignee)) {
|
|
issue.assignees.push(new ListAssignee(assignee));
|
|
}
|
|
},
|
|
|
|
removeIssueLabels(issue, labels) {
|
|
labels.forEach(issue.removeLabel.bind(issue));
|
|
},
|
|
|
|
bulkUpdate(issueIds, extraData = {}) {
|
|
const data = {
|
|
update: Object.assign(extraData, {
|
|
issuable_ids: issueIds.join(','),
|
|
}),
|
|
};
|
|
|
|
return axios.post(this.state.endpoints.bulkUpdatePath, data);
|
|
},
|
|
|
|
getIssueInfo(endpoint) {
|
|
return axios.get(endpoint);
|
|
},
|
|
|
|
toggleIssueSubscription(endpoint) {
|
|
return axios.post(endpoint);
|
|
},
|
|
|
|
recentBoards() {
|
|
return axios.get(this.state.endpoints.recentBoardsEndpoint);
|
|
},
|
|
|
|
deleteBoard({ id }) {
|
|
return axios.delete(this.generateBoardsPath(id));
|
|
},
|
|
|
|
setCurrentBoard(board) {
|
|
this.state.currentBoard = board;
|
|
},
|
|
|
|
toggleMultiSelect(issue) {
|
|
const selectedIssueIds = this.multiSelect.list.map(issue => issue.id);
|
|
const index = selectedIssueIds.indexOf(issue.id);
|
|
|
|
if (index === -1) {
|
|
this.multiSelect.list.push(issue);
|
|
return;
|
|
}
|
|
|
|
this.multiSelect.list = [
|
|
...this.multiSelect.list.slice(0, index),
|
|
...this.multiSelect.list.slice(index + 1),
|
|
];
|
|
},
|
|
removeIssueAssignee(issue, removeAssignee) {
|
|
if (removeAssignee) {
|
|
issue.assignees = issue.assignees.filter(assignee => assignee.id !== removeAssignee.id);
|
|
}
|
|
},
|
|
|
|
findIssueAssignee(issue, findAssignee) {
|
|
return issue.assignees.find(assignee => assignee.id === findAssignee.id);
|
|
},
|
|
|
|
clearMultiSelect() {
|
|
this.multiSelect.list = [];
|
|
},
|
|
|
|
removeAllIssueAssignees(issue) {
|
|
issue.assignees = [];
|
|
},
|
|
|
|
addIssueMilestone(issue, milestone) {
|
|
const miletoneId = issue.milestone ? issue.milestone.id : null;
|
|
if (IS_EE && milestone.id !== miletoneId) {
|
|
issue.milestone = new ListMilestone(milestone);
|
|
}
|
|
},
|
|
|
|
setIssueLoadingState(issue, key, value) {
|
|
issue.isLoading[key] = value;
|
|
},
|
|
|
|
updateIssueData(issue, newData) {
|
|
Object.assign(issue, newData);
|
|
},
|
|
|
|
setIssueFetchingState(issue, key, value) {
|
|
issue.isFetching[key] = value;
|
|
},
|
|
|
|
removeIssueMilestone(issue, removeMilestone) {
|
|
if (IS_EE && removeMilestone && removeMilestone.id === issue.milestone.id) {
|
|
issue.milestone = {};
|
|
}
|
|
},
|
|
|
|
refreshIssueData(issue, obj) {
|
|
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;
|
|
Object.assign(issue, convertedObj);
|
|
|
|
if (obj.project) {
|
|
issue.project = new IssueProject(obj.project);
|
|
}
|
|
|
|
if (obj.milestone) {
|
|
issue.milestone = new ListMilestone(obj.milestone);
|
|
issue.milestone_id = obj.milestone.id;
|
|
}
|
|
|
|
if (obj.labels) {
|
|
issue.labels = obj.labels.map(label => new ListLabel(label));
|
|
}
|
|
|
|
if (obj.assignees) {
|
|
issue.assignees = obj.assignees.map(a => new ListAssignee(a));
|
|
}
|
|
},
|
|
addIssueLabel(issue, label) {
|
|
if (!issue.findLabel(label)) {
|
|
issue.labels.push(new ListLabel(label));
|
|
}
|
|
},
|
|
updateIssue(issue) {
|
|
const data = {
|
|
issue: {
|
|
milestone_id: issue.milestone ? issue.milestone.id : null,
|
|
due_date: issue.dueDate,
|
|
assignee_ids: issue.assignees.length > 0 ? issue.assignees.map(({ id }) => id) : [0],
|
|
label_ids: issue.labels.length > 0 ? issue.labels.map(({ id }) => id) : [''],
|
|
},
|
|
};
|
|
|
|
return axios.patch(`${issue.path}.json`, data).then(({ data: body = {} } = {}) => {
|
|
/**
|
|
* Since post implementation of Scoped labels, server can reject
|
|
* same key-ed labels. To keep the UI and server Model consistent,
|
|
* we're just assigning labels that server echo's back to us when we
|
|
* PATCH the said object.
|
|
*/
|
|
if (body) {
|
|
issue.labels = convertObjectPropsToCamelCase(body.labels, { deep: true });
|
|
}
|
|
});
|
|
},
|
|
};
|
|
|
|
BoardsStoreEE.initEESpecific(boardsStore);
|
|
|
|
// hacks added in order to allow milestone_select to function properly
|
|
// TODO: remove these
|
|
|
|
export function boardStoreIssueSet(...args) {
|
|
Vue.set(boardsStore.detail.issue, ...args);
|
|
}
|
|
|
|
export function boardStoreIssueDelete(...args) {
|
|
Vue.delete(boardsStore.detail.issue, ...args);
|
|
}
|
|
|
|
export default boardsStore;
|