2021-04-29 21:17:54 +05:30
|
|
|
import { sortBy, cloneDeep } from 'lodash';
|
2020-10-24 23:57:45 +05:30
|
|
|
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
2021-09-04 01:27:46 +05:30
|
|
|
import { ListType } from './constants';
|
2020-10-24 23:57:45 +05:30
|
|
|
|
2019-07-31 22:56:46 +05:30
|
|
|
export function getMilestone() {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2021-02-22 17:27:13 +05:30
|
|
|
export function updateListPosition(listObj) {
|
|
|
|
const { listType } = listObj;
|
|
|
|
let { position } = listObj;
|
|
|
|
if (listType === ListType.closed) {
|
|
|
|
position = Infinity;
|
|
|
|
} else if (listType === ListType.backlog) {
|
|
|
|
position = -Infinity;
|
|
|
|
}
|
|
|
|
|
|
|
|
return { ...listObj, position };
|
|
|
|
}
|
|
|
|
|
2021-01-03 14:25:43 +05:30
|
|
|
export function formatBoardLists(lists) {
|
2021-02-22 17:27:13 +05:30
|
|
|
return lists.nodes.reduce((map, list) => {
|
2021-01-03 14:25:43 +05:30
|
|
|
return {
|
|
|
|
...map,
|
2021-02-22 17:27:13 +05:30
|
|
|
[list.id]: updateListPosition(list),
|
2021-01-03 14:25:43 +05:30
|
|
|
};
|
|
|
|
}, {});
|
|
|
|
}
|
|
|
|
|
2020-11-24 15:15:51 +05:30
|
|
|
export function formatIssue(issue) {
|
2021-02-22 17:27:13 +05:30
|
|
|
return {
|
2020-11-24 15:15:51 +05:30
|
|
|
...issue,
|
|
|
|
labels: issue.labels?.nodes || [],
|
|
|
|
assignees: issue.assignees?.nodes || [],
|
2021-02-22 17:27:13 +05:30
|
|
|
};
|
2020-11-24 15:15:51 +05:30
|
|
|
}
|
|
|
|
|
2020-10-24 23:57:45 +05:30
|
|
|
export function formatListIssues(listIssues) {
|
2021-04-17 20:07:23 +05:30
|
|
|
const boardItems = {};
|
|
|
|
let listItemsCount;
|
2020-11-24 15:15:51 +05:30
|
|
|
|
|
|
|
const listData = listIssues.nodes.reduce((map, list) => {
|
2021-09-04 01:27:46 +05:30
|
|
|
listItemsCount = list.issuesCount;
|
2021-03-08 18:12:59 +05:30
|
|
|
let sortedIssues = list.issues.edges.map((issueNode) => ({
|
2021-01-03 14:25:43 +05:30
|
|
|
...issueNode.node,
|
|
|
|
}));
|
|
|
|
sortedIssues = sortBy(sortedIssues, 'relativePosition');
|
|
|
|
|
2020-10-24 23:57:45 +05:30
|
|
|
return {
|
|
|
|
...map,
|
2021-03-08 18:12:59 +05:30
|
|
|
[list.id]: sortedIssues.map((i) => {
|
2020-11-24 15:15:51 +05:30
|
|
|
const id = getIdFromGraphQLId(i.id);
|
|
|
|
|
2021-02-22 17:27:13 +05:30
|
|
|
const listIssue = {
|
2020-11-24 15:15:51 +05:30
|
|
|
...i,
|
|
|
|
id,
|
2021-09-30 23:02:18 +05:30
|
|
|
fullId: i.id,
|
2020-11-24 15:15:51 +05:30
|
|
|
labels: i.labels?.nodes || [],
|
|
|
|
assignees: i.assignees?.nodes || [],
|
2021-02-22 17:27:13 +05:30
|
|
|
};
|
2020-11-24 15:15:51 +05:30
|
|
|
|
2021-04-17 20:07:23 +05:30
|
|
|
boardItems[id] = listIssue;
|
2020-11-24 15:15:51 +05:30
|
|
|
|
|
|
|
return id;
|
|
|
|
}),
|
2020-10-24 23:57:45 +05:30
|
|
|
};
|
|
|
|
}, {});
|
2020-11-24 15:15:51 +05:30
|
|
|
|
2021-04-17 20:07:23 +05:30
|
|
|
return { listData, boardItems, listItemsCount };
|
2021-01-03 14:25:43 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
export function formatListsPageInfo(lists) {
|
|
|
|
const listData = lists.nodes.reduce((map, list) => {
|
|
|
|
return {
|
|
|
|
...map,
|
|
|
|
[list.id]: list.issues.pageInfo,
|
|
|
|
};
|
|
|
|
}, {});
|
|
|
|
return listData;
|
2020-11-24 15:15:51 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
export function fullBoardId(boardId) {
|
|
|
|
return `gid://gitlab/Board/${boardId}`;
|
|
|
|
}
|
|
|
|
|
2021-03-08 18:12:59 +05:30
|
|
|
export function fullIterationId(id) {
|
|
|
|
return `gid://gitlab/Iteration/${id}`;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function fullUserId(id) {
|
|
|
|
return `gid://gitlab/User/${id}`;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function fullMilestoneId(id) {
|
|
|
|
return `gid://gitlab/Milestone/${id}`;
|
|
|
|
}
|
|
|
|
|
2021-01-03 14:25:43 +05:30
|
|
|
export function fullLabelId(label) {
|
2021-03-08 18:12:59 +05:30
|
|
|
if (label.project_id && label.project_id !== null) {
|
2021-01-03 14:25:43 +05:30
|
|
|
return `gid://gitlab/ProjectLabel/${label.id}`;
|
|
|
|
}
|
|
|
|
return `gid://gitlab/GroupLabel/${label.id}`;
|
|
|
|
}
|
|
|
|
|
2021-03-08 18:12:59 +05:30
|
|
|
export function formatIssueInput(issueInput, boardConfig) {
|
|
|
|
const { labelIds = [], assigneeIds = [] } = issueInput;
|
|
|
|
const { labels, assigneeId, milestoneId } = boardConfig;
|
|
|
|
|
|
|
|
return {
|
|
|
|
...issueInput,
|
2021-09-30 23:02:18 +05:30
|
|
|
milestoneId: milestoneId ? fullMilestoneId(milestoneId) : null,
|
2021-03-08 18:12:59 +05:30
|
|
|
labelIds: [...labelIds, ...(labels?.map((l) => fullLabelId(l)) || [])],
|
|
|
|
assigneeIds: [...assigneeIds, ...(assigneeId ? [fullUserId(assigneeId)] : [])],
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2021-04-29 21:17:54 +05:30
|
|
|
export function shouldCloneCard(fromListType, toListType) {
|
|
|
|
const involvesClosed = fromListType === ListType.closed || toListType === ListType.closed;
|
|
|
|
const involvesBacklog = fromListType === ListType.backlog || toListType === ListType.backlog;
|
|
|
|
|
|
|
|
if (involvesClosed || involvesBacklog) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fromListType !== toListType) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function getMoveData(state, params) {
|
|
|
|
const { boardItems, boardItemsByListId, boardLists } = state;
|
|
|
|
const { itemId, fromListId, toListId } = params;
|
|
|
|
const fromListType = boardLists[fromListId].listType;
|
|
|
|
const toListType = boardLists[toListId].listType;
|
|
|
|
|
|
|
|
return {
|
|
|
|
reordering: fromListId === toListId,
|
|
|
|
shouldClone: shouldCloneCard(fromListType, toListType),
|
|
|
|
itemNotInToList: !boardItemsByListId[toListId].includes(itemId),
|
|
|
|
originalIssue: cloneDeep(boardItems[itemId]),
|
|
|
|
originalIndex: boardItemsByListId[fromListId].indexOf(itemId),
|
|
|
|
...params,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2021-04-17 20:07:23 +05:30
|
|
|
export function moveItemListHelper(item, fromList, toList) {
|
|
|
|
const updatedItem = item;
|
2021-02-22 17:27:13 +05:30
|
|
|
if (
|
|
|
|
toList.listType === ListType.label &&
|
2021-04-17 20:07:23 +05:30
|
|
|
!updatedItem.labels.find((label) => label.id === toList.label.id)
|
2021-02-22 17:27:13 +05:30
|
|
|
) {
|
2021-04-17 20:07:23 +05:30
|
|
|
updatedItem.labels.push(toList.label);
|
2020-11-24 15:15:51 +05:30
|
|
|
}
|
2021-02-22 17:27:13 +05:30
|
|
|
if (fromList?.label && fromList.listType === ListType.label) {
|
2021-04-17 20:07:23 +05:30
|
|
|
updatedItem.labels = updatedItem.labels.filter((label) => fromList.label.id !== label.id);
|
2020-11-24 15:15:51 +05:30
|
|
|
}
|
|
|
|
|
2021-02-22 17:27:13 +05:30
|
|
|
if (
|
|
|
|
toList.listType === ListType.assignee &&
|
2021-04-17 20:07:23 +05:30
|
|
|
!updatedItem.assignees.find((assignee) => assignee.id === toList.assignee.id)
|
2021-02-22 17:27:13 +05:30
|
|
|
) {
|
2021-04-17 20:07:23 +05:30
|
|
|
updatedItem.assignees.push(toList.assignee);
|
2021-02-22 17:27:13 +05:30
|
|
|
}
|
|
|
|
if (fromList?.assignee && fromList.listType === ListType.assignee) {
|
2021-04-17 20:07:23 +05:30
|
|
|
updatedItem.assignees = updatedItem.assignees.filter(
|
2021-03-08 18:12:59 +05:30
|
|
|
(assignee) => assignee.id !== fromList.assignee.id,
|
2021-02-22 17:27:13 +05:30
|
|
|
);
|
2020-11-24 15:15:51 +05:30
|
|
|
}
|
2021-02-22 17:27:13 +05:30
|
|
|
|
2021-04-17 20:07:23 +05:30
|
|
|
return updatedItem;
|
2021-02-22 17:27:13 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
export function isListDraggable(list) {
|
|
|
|
return list.listType !== ListType.backlog && list.listType !== ListType.closed;
|
|
|
|
}
|
2020-11-24 15:15:51 +05:30
|
|
|
|
2021-09-04 01:27:46 +05:30
|
|
|
export const FiltersInfo = {
|
|
|
|
assigneeUsername: {
|
|
|
|
negatedSupport: true,
|
|
|
|
},
|
|
|
|
assigneeId: {
|
|
|
|
// assigneeId should be renamed to assigneeWildcardId.
|
|
|
|
// Classic boards used 'assigneeId'
|
|
|
|
remap: () => 'assigneeWildcardId',
|
|
|
|
},
|
|
|
|
assigneeWildcardId: {
|
|
|
|
negatedSupport: false,
|
|
|
|
transform: (val) => val.toUpperCase(),
|
|
|
|
},
|
|
|
|
authorUsername: {
|
|
|
|
negatedSupport: true,
|
|
|
|
},
|
|
|
|
labelName: {
|
|
|
|
negatedSupport: true,
|
|
|
|
},
|
|
|
|
milestoneTitle: {
|
|
|
|
negatedSupport: true,
|
|
|
|
},
|
|
|
|
myReactionEmoji: {
|
|
|
|
negatedSupport: true,
|
|
|
|
},
|
|
|
|
releaseTag: {
|
|
|
|
negatedSupport: true,
|
|
|
|
},
|
|
|
|
search: {
|
|
|
|
negatedSupport: false,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {Object} filters - ex. { search: "foobar", "not[authorUsername]": "root", }
|
|
|
|
* @returns {Object} - ex. [ ["search", "foobar", false], ["authorUsername", "root", true], ]
|
|
|
|
*/
|
|
|
|
const parseFilters = (filters) => {
|
|
|
|
/* eslint-disable-next-line @gitlab/require-i18n-strings */
|
|
|
|
const isNegated = (x) => x.startsWith('not[') && x.endsWith(']');
|
|
|
|
|
|
|
|
return Object.entries(filters).map(([k, v]) => {
|
|
|
|
const isNot = isNegated(k);
|
|
|
|
const filterKey = isNot ? k.slice(4, -1) : k;
|
|
|
|
|
|
|
|
return [filterKey, v, isNot];
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns an object of filter key/value pairs used as variables in GraphQL requests.
|
|
|
|
* (warning: filter values are not validated)
|
|
|
|
*
|
|
|
|
* @param {Object} objParam.filters - filters extracted from url params. ex. { search: "foobar", "not[authorUsername]": "root", }
|
|
|
|
* @param {string} objParam.issuableType - issuable type e.g., issue.
|
|
|
|
* @param {Object} objParam.filterInfo - data on filters such as how to transform filter value, if filter can be negated, etc.
|
|
|
|
* @param {Object} objParam.filterFields - data on what filters are available for given issuableType (based on GraphQL schema)
|
|
|
|
*/
|
|
|
|
export const filterVariables = ({ filters, issuableType, filterInfo, filterFields }) =>
|
|
|
|
parseFilters(filters)
|
|
|
|
.map(([k, v, negated]) => {
|
|
|
|
// for legacy reasons, some filters need to be renamed to correct GraphQL fields.
|
|
|
|
const remapAvailable = filterInfo[k]?.remap;
|
|
|
|
const remappedKey = remapAvailable ? filterInfo[k].remap(k, v) : k;
|
|
|
|
|
|
|
|
return [remappedKey, v, negated];
|
|
|
|
})
|
|
|
|
.filter(([k, , negated]) => {
|
|
|
|
// remove unsupported filters (+ check if the filters support negation)
|
|
|
|
const supported = filterFields[issuableType].includes(k);
|
|
|
|
if (supported) {
|
|
|
|
return negated ? filterInfo[k].negatedSupport : true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
})
|
|
|
|
.map(([k, v, negated]) => {
|
|
|
|
// if the filter value needs a special transformation, apply it (e.g., capitalization)
|
|
|
|
const transform = filterInfo[k]?.transform;
|
|
|
|
const newVal = transform ? transform(v) : v;
|
|
|
|
|
|
|
|
return [k, newVal, negated];
|
|
|
|
})
|
|
|
|
.reduce(
|
|
|
|
(acc, [k, v, negated]) => {
|
|
|
|
return negated
|
|
|
|
? {
|
|
|
|
...acc,
|
|
|
|
not: {
|
|
|
|
...acc.not,
|
|
|
|
[k]: v,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
: {
|
|
|
|
...acc,
|
|
|
|
[k]: v,
|
|
|
|
};
|
|
|
|
},
|
|
|
|
{ not: {} },
|
|
|
|
);
|
2021-06-08 01:23:25 +05:30
|
|
|
|
2021-02-22 17:27:13 +05:30
|
|
|
// EE-specific feature. Find the implementation in the `ee/`-folder
|
|
|
|
export function transformBoardConfig() {
|
|
|
|
return '';
|
2020-10-24 23:57:45 +05:30
|
|
|
}
|
|
|
|
|
2019-07-31 22:56:46 +05:30
|
|
|
export default {
|
|
|
|
getMilestone,
|
2020-11-24 15:15:51 +05:30
|
|
|
formatIssue,
|
2020-10-24 23:57:45 +05:30
|
|
|
formatListIssues,
|
2020-11-24 15:15:51 +05:30
|
|
|
fullBoardId,
|
2021-01-03 14:25:43 +05:30
|
|
|
fullLabelId,
|
2021-03-08 18:12:59 +05:30
|
|
|
fullIterationId,
|
2021-02-22 17:27:13 +05:30
|
|
|
isListDraggable,
|
2019-07-31 22:56:46 +05:30
|
|
|
};
|