debian-mirror-gitlab/app/assets/javascripts/boards/stores/boards_store.js

544 lines
15 KiB
JavaScript
Raw Normal View History

2018-12-13 13:39:08 +05:30
/* eslint-disable no-shadow */
2017-08-17 22:00:37 +05:30
/* global List */
2018-05-09 12:01:36 +05:30
import $ from 'jquery';
2017-09-10 17:25:29 +05:30
import _ from 'underscore';
2018-12-13 13:39:08 +05:30
import Vue from 'vue';
2017-08-17 22:00:37 +05:30
import Cookies from 'js-cookie';
2019-07-31 22:56:46 +05:30
import BoardsStoreEE from 'ee_else_ce/boards/stores/boards_store_ee';
2019-02-15 15:39:39 +05:30
import { getUrlParamsArray, parseBoolean } from '~/lib/utils/common_utils';
2019-07-31 22:56:46 +05:30
import { __ } from '~/locale';
2019-09-30 21:07:59 +05:30
import axios from '~/lib/utils/axios_utils';
import { mergeUrlParams } from '~/lib/utils/url_utility';
2019-09-04 21:01:54 +05:30
import eventHub from '../eventhub';
2019-12-21 20:55:43 +05:30
import { ListType } from '../constants';
2017-08-17 22:00:37 +05:30
2018-12-13 13:39:08 +05:30
const boardsStore = {
2017-08-17 22:00:37 +05:30
disabled: false,
2019-09-30 21:07:59 +05:30
timeTracking: {
limitToHours: false,
},
2019-07-31 22:56:46 +05:30
scopedLabels: {
helpLink: '',
enabled: false,
},
2017-08-17 22:00:37 +05:30
filter: {
path: '',
},
2019-07-31 22:56:46 +05:30
state: {
currentBoard: {
labels: [],
},
currentPage: '',
reload: false,
2019-09-30 21:07:59 +05:30
endpoints: {},
2019-07-31 22:56:46 +05:30
},
2017-08-17 22:00:37 +05:30
detail: {
2018-03-17 18:26:18 +05:30
issue: {},
2017-08-17 22:00:37 +05:30
},
moving: {
issue: {},
2018-03-17 18:26:18 +05:30
list: {},
2017-08-17 22:00:37 +05:30
},
2019-12-21 20:55:43 +05:30
multiSelect: { list: [] },
2019-09-30 21:07:59 +05:30
setEndpoints({ boardsEndpoint, listsEndpoint, bulkUpdatePath, boardId, recentBoardsEndpoint }) {
const listsEndpointGenerate = `${listsEndpoint}/generate.json`;
this.state.endpoints = {
boardsEndpoint,
boardId,
listsEndpoint,
listsEndpointGenerate,
bulkUpdatePath,
recentBoardsEndpoint: `${recentBoardsEndpoint}.json`,
};
},
2018-12-13 13:39:08 +05:30
create() {
2017-08-17 22:00:37 +05:30
this.state.lists = [];
2018-03-17 18:26:18 +05:30
this.filter.path = getUrlParamsArray().join('&');
this.detail = {
issue: {},
};
2017-08-17 22:00:37 +05:30
},
2019-07-31 22:56:46 +05:30
showPage(page) {
this.state.reload = false;
this.state.currentPage = page;
},
2018-12-13 13:39:08 +05:30
addList(listObj, defaultAvatar) {
2017-08-17 22:00:37 +05:30
const list = new List(listObj, defaultAvatar);
2019-09-04 21:01:54 +05:30
this.state.lists = _.sortBy([...this.state.lists, list], 'position');
2017-08-17 22:00:37 +05:30
return list;
},
2018-12-13 13:39:08 +05:30
new(listObj) {
2017-08-17 22:00:37 +05:30
const list = this.addList(listObj);
2017-09-10 17:25:29 +05:30
const backlogList = this.findList('type', 'backlog', 'backlog');
2017-08-17 22:00:37 +05:30
list
.save()
.then(() => {
2017-09-10 17:25:29 +05:30
// Remove any new issues from the backlog
// as they will be visible in the new list
list.issues.forEach(backlogList.removeIssue.bind(backlogList));
2017-08-17 22:00:37 +05:30
this.state.lists = _.sortBy(this.state.lists, 'position');
})
.catch(() => {
2019-12-04 20:38:33 +05:30
// https://gitlab.com/gitlab-org/gitlab-foss/issues/30821
2017-08-17 22:00:37 +05:30
});
this.removeBlankState();
},
2018-12-13 13:39:08 +05:30
updateNewListDropdown(listId) {
2017-08-17 22:00:37 +05:30
$(`.js-board-list-${listId}`).removeClass('is-active');
},
2018-12-13 13:39:08 +05:30
shouldAddBlankState() {
2017-08-17 22:00:37 +05:30
// Decide whether to add the blank state
2018-12-13 13:39:08 +05:30
return !this.state.lists.filter(list => list.type !== 'backlog' && list.type !== 'closed')[0];
2017-08-17 22:00:37 +05:30
},
2018-12-13 13:39:08 +05:30
addBlankState() {
2017-08-17 22:00:37 +05:30
if (!this.shouldAddBlankState() || this.welcomeIsHidden() || this.disabled) return;
this.addList({
id: 'blank',
list_type: 'blank',
2019-07-31 22:56:46 +05:30
title: __('Welcome to your Issue Board!'),
2018-12-13 13:39:08 +05:30
position: 0,
2017-08-17 22:00:37 +05:30
});
},
2018-12-13 13:39:08 +05:30
removeBlankState() {
2017-08-17 22:00:37 +05:30
this.removeList('blank');
Cookies.set('issue_board_welcome_hidden', 'true', {
expires: 365 * 10,
2018-12-13 13:39:08 +05:30
path: '',
2017-08-17 22:00:37 +05:30
});
},
2018-12-13 13:39:08 +05:30
welcomeIsHidden() {
2019-02-15 15:39:39 +05:30
return parseBoolean(Cookies.get('issue_board_welcome_hidden'));
2017-08-17 22:00:37 +05:30
},
2018-12-13 13:39:08 +05:30
removeList(id, type = 'blank') {
2017-08-17 22:00:37 +05:30
const list = this.findList('id', id, type);
if (!list) return;
this.state.lists = this.state.lists.filter(list => list.id !== id);
},
2018-12-13 13:39:08 +05:30
moveList(listFrom, orderLists) {
2017-08-17 22:00:37 +05:30
orderLists.forEach((id, i) => {
const list = this.findList('id', parseInt(id, 10));
list.position = i;
});
listFrom.update();
},
2019-09-04 21:01:54 +05:30
startMoving(list, issue) {
Object.assign(this.moving, { list, issue });
},
2019-12-21 20:55:43 +05:30
moveMultipleIssuesToList({ listFrom, listTo, issues, newIndex }) {
const issueTo = issues.map(issue => listTo.findIssue(issue.id));
const issueLists = _.flatten(issues.map(issue => issue.getLists()));
const listLabels = issueLists.map(list => list.label);
const hasMoveableIssues = _.compact(issueTo).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('|'));
},
2018-12-13 13:39:08 +05:30
moveIssueToList(listFrom, listTo, issue, newIndex) {
2017-08-17 22:00:37 +05:30
const issueTo = listTo.findIssue(issue.id);
const issueLists = issue.getLists();
const listLabels = issueLists.map(listIssue => listIssue.label);
if (!issueTo) {
2018-11-08 19:23:39 +05:30
// Check if target list assignee is already present in this issue
2018-12-13 13:39:08 +05:30
if (
listTo.type === 'assignee' &&
listFrom.type === 'assignee' &&
issue.findAssignee(listTo.assignee)
) {
2018-11-08 19:23:39 +05:30
const targetIssue = listTo.findIssue(issue.id);
targetIssue.removeAssignee(listFrom.assignee);
2018-11-18 11:00:15 +05:30
} else if (listTo.type === 'milestone') {
const currentMilestone = issue.milestone;
const currentLists = this.state.lists
2018-12-13 13:39:08 +05:30
.filter(list => list.type === 'milestone' && list.id !== listTo.id)
.filter(list => list.issues.some(listIssue => issue.id === listIssue.id));
2018-11-18 11:00:15 +05:30
issue.removeMilestone(currentMilestone);
issue.addMilestone(listTo.milestone);
currentLists.forEach(currentList => currentList.removeIssue(issue));
listTo.addIssue(issue, listFrom, newIndex);
2018-11-08 19:23:39 +05:30
} else {
// Add to new lists issues if it doesn't already exist
listTo.addIssue(issue, listFrom, newIndex);
}
2017-08-17 22:00:37 +05:30
} else {
listTo.updateIssueLabel(issue, listFrom);
issueTo.removeLabel(listFrom.label);
}
2017-09-10 17:25:29 +05:30
if (listTo.type === 'closed' && listFrom.type !== 'backlog') {
2018-12-13 13:39:08 +05:30
issueLists.forEach(list => {
2017-08-17 22:00:37 +05:30
list.removeIssue(issue);
});
issue.removeLabels(listLabels);
2018-11-08 19:23:39 +05:30
} else if (listTo.type === 'backlog' && listFrom.type === 'assignee') {
issue.removeAssignee(listFrom.assignee);
listFrom.removeIssue(issue);
2018-11-18 11:00:15 +05:30
} else if (listTo.type === 'backlog' && listFrom.type === 'milestone') {
issue.removeMilestone(listFrom.milestone);
listFrom.removeIssue(issue);
} else if (this.shouldRemoveIssue(listFrom, listTo)) {
2017-08-17 22:00:37 +05:30
listFrom.removeIssue(issue);
}
},
2018-11-18 11:00:15 +05:30
shouldRemoveIssue(listFrom, listTo) {
return (
(listTo.type !== 'label' && listFrom.type === 'assignee') ||
(listTo.type !== 'assignee' && listFrom.type === 'label') ||
2018-12-13 13:39:08 +05:30
listFrom.type === 'backlog'
2018-11-18 11:00:15 +05:30
);
},
2018-12-13 13:39:08 +05:30
moveIssueInList(list, issue, oldIndex, newIndex, idArray) {
2017-08-17 22:00:37 +05:30
const beforeId = parseInt(idArray[newIndex - 1], 10) || null;
const afterId = parseInt(idArray[newIndex + 1], 10) || null;
list.moveIssue(issue, oldIndex, newIndex, beforeId, afterId);
},
2019-12-21 20:55:43 +05:30
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,
});
},
2018-12-13 13:39:08 +05:30
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;
2017-08-17 22:00:37 +05:30
return list[key] === val && byType;
2018-11-08 19:23:39 +05:30
});
return filteredList[0];
2017-08-17 22:00:37 +05:30
},
2019-02-15 15:39:39 +05:30
findListByLabelId(id) {
return this.state.lists.find(list => list.type === 'label' && list.label.id === id);
},
2019-09-04 21:01:54 +05:30
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;
},
2018-12-13 13:39:08 +05:30
updateFiltersUrl() {
2018-11-08 19:23:39 +05:30
window.history.pushState(null, null, `?${this.filter.path}`);
2018-12-13 13:39:08 +05:30
},
2019-09-04 21:01:54 +05:30
clearDetailIssue() {
this.setIssueDetail({});
},
setIssueDetail(issueDetail) {
this.detail.issue = issueDetail;
},
2019-09-30 21:07:59 +05:30
setTimeTrackingLimitToHours(limitToHours) {
this.timeTracking.limitToHours = parseBoolean(limitToHours);
},
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}` : ''
}`;
},
2019-12-21 20:55:43 +05:30
generateMultiDragPath(boardId) {
return `${gon.relative_url_root}/-/boards/${boardId ? `${boardId}` : ''}/issues/bulk_move`;
},
2019-09-30 21:07:59 +05:30
all() {
return axios.get(this.state.endpoints.listsEndpoint);
},
generateDefaultLists() {
return axios.post(this.state.endpoints.listsEndpointGenerate, {});
},
createList(entityId, entityType) {
const list = {
[entityType]: entityId,
};
return axios.post(this.state.endpoints.listsEndpoint, {
list,
});
},
2019-12-04 20:38:33 +05:30
updateList(id, position, collapsed) {
2019-09-30 21:07:59 +05:30
return axios.put(`${this.state.endpoints.listsEndpoint}/${id}`, {
list: {
position,
2019-12-04 20:38:33 +05:30
collapsed,
2019-09-30 21:07:59 +05:30
},
});
},
destroyList(id) {
return axios.delete(`${this.state.endpoints.listsEndpoint}/${id}`);
},
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,
});
},
2019-12-21 20:55:43 +05:30
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,
});
},
2019-09-30 21:07:59 +05:30
newIssue(id, issue) {
return axios.post(this.generateIssuesPath(id), {
issue,
});
},
getBacklog(data) {
return axios.get(
mergeUrlParams(
data,
`${gon.relative_url_root}/-/boards/${this.state.endpoints.boardId}/issues.json`,
),
);
},
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);
},
allBoards() {
return axios.get(this.generateBoardsPath());
},
recentBoards() {
return axios.get(this.state.endpoints.recentBoardsEndpoint);
},
createBoard(board) {
const boardPayload = { ...board };
boardPayload.label_ids = (board.labels || []).map(b => b.id);
if (boardPayload.label_ids.length === 0) {
boardPayload.label_ids = [''];
}
if (boardPayload.assignee) {
boardPayload.assignee_id = boardPayload.assignee.id;
}
if (boardPayload.milestone) {
boardPayload.milestone_id = boardPayload.milestone.id;
}
if (boardPayload.id) {
return axios.put(this.generateBoardsPath(boardPayload.id), { board: boardPayload });
}
return axios.post(this.generateBoardsPath(), { board: boardPayload });
},
deleteBoard({ id }) {
return axios.delete(this.generateBoardsPath(id));
},
setCurrentBoard(board) {
this.state.currentBoard = board;
},
2019-12-21 20:55:43 +05:30
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),
];
},
clearMultiSelect() {
this.multiSelect.list = [];
},
2017-08-17 22:00:37 +05:30
};
2018-12-13 13:39:08 +05:30
2019-07-31 22:56:46 +05:30
BoardsStoreEE.initEESpecific(boardsStore);
2018-12-13 13:39:08 +05:30
// 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;