2022-07-16 23:28:13 +05:30
|
|
|
import { createTerm } from '@gitlab/ui/src/components/base/filtered_search/filtered_search_utils';
|
2022-04-04 11:22:00 +05:30
|
|
|
import { isPositiveInteger } from '~/lib/utils/number_utils';
|
2022-06-21 17:19:12 +05:30
|
|
|
import { getParameterByName } from '~/lib/utils/url_utility';
|
2022-04-04 11:22:00 +05:30
|
|
|
import { __ } from '~/locale';
|
|
|
|
import {
|
|
|
|
FILTERED_SEARCH_TERM,
|
2023-03-04 22:38:38 +05:30
|
|
|
OPERATOR_NOT,
|
2023-01-13 00:05:48 +05:30
|
|
|
OPERATOR_OR,
|
|
|
|
TOKEN_TYPE_ASSIGNEE,
|
2023-03-04 22:38:38 +05:30
|
|
|
TOKEN_TYPE_AUTHOR,
|
2023-01-13 00:05:48 +05:30
|
|
|
TOKEN_TYPE_CONFIDENTIAL,
|
|
|
|
TOKEN_TYPE_ITERATION,
|
|
|
|
TOKEN_TYPE_MILESTONE,
|
|
|
|
TOKEN_TYPE_RELEASE,
|
|
|
|
TOKEN_TYPE_TYPE,
|
2023-03-17 16:20:25 +05:30
|
|
|
TOKEN_TYPE_HEALTH,
|
|
|
|
TOKEN_TYPE_LABEL,
|
2022-04-04 11:22:00 +05:30
|
|
|
} from '~/vue_shared/components/filtered_search_bar/constants';
|
2021-06-08 01:23:25 +05:30
|
|
|
import {
|
2023-03-04 22:38:38 +05:30
|
|
|
ALTERNATIVE_FILTER,
|
2021-09-30 23:02:18 +05:30
|
|
|
API_PARAM,
|
2022-01-26 12:08:38 +05:30
|
|
|
BLOCKING_ISSUES_ASC,
|
2021-06-08 01:23:25 +05:30
|
|
|
BLOCKING_ISSUES_DESC,
|
2023-03-04 22:38:38 +05:30
|
|
|
CLOSED_AT_ASC,
|
|
|
|
CLOSED_AT_DESC,
|
2021-06-08 01:23:25 +05:30
|
|
|
CREATED_ASC,
|
|
|
|
CREATED_DESC,
|
|
|
|
DUE_DATE_ASC,
|
|
|
|
DUE_DATE_DESC,
|
|
|
|
filters,
|
2023-03-04 22:38:38 +05:30
|
|
|
HEALTH_STATUS_ASC,
|
|
|
|
HEALTH_STATUS_DESC,
|
2021-09-30 23:02:18 +05:30
|
|
|
LABEL_PRIORITY_ASC,
|
2021-06-08 01:23:25 +05:30
|
|
|
LABEL_PRIORITY_DESC,
|
|
|
|
MILESTONE_DUE_ASC,
|
|
|
|
MILESTONE_DUE_DESC,
|
|
|
|
NORMAL_FILTER,
|
2022-05-07 20:08:51 +05:30
|
|
|
PAGE_SIZE,
|
2022-06-21 17:19:12 +05:30
|
|
|
PARAM_ASSIGNEE_ID,
|
2021-06-08 01:23:25 +05:30
|
|
|
POPULARITY_ASC,
|
|
|
|
POPULARITY_DESC,
|
2021-09-30 23:02:18 +05:30
|
|
|
PRIORITY_ASC,
|
2021-06-08 01:23:25 +05:30
|
|
|
PRIORITY_DESC,
|
2021-09-30 23:02:18 +05:30
|
|
|
RELATIVE_POSITION_ASC,
|
2021-06-08 01:23:25 +05:30
|
|
|
SPECIAL_FILTER,
|
2022-06-21 17:19:12 +05:30
|
|
|
specialFilterValues,
|
2021-12-11 22:18:48 +05:30
|
|
|
TITLE_ASC,
|
|
|
|
TITLE_DESC,
|
2021-06-08 01:23:25 +05:30
|
|
|
UPDATED_ASC,
|
|
|
|
UPDATED_DESC,
|
2021-09-30 23:02:18 +05:30
|
|
|
URL_PARAM,
|
2021-06-08 01:23:25 +05:30
|
|
|
urlSortParams,
|
|
|
|
WEIGHT_ASC,
|
|
|
|
WEIGHT_DESC,
|
2022-04-04 11:22:00 +05:30
|
|
|
} from './constants';
|
2021-06-08 01:23:25 +05:30
|
|
|
|
2022-07-23 23:45:48 +05:30
|
|
|
export const getInitialPageParams = (
|
2022-08-13 15:12:31 +05:30
|
|
|
pageSize,
|
|
|
|
firstPageSize = pageSize ?? PAGE_SIZE,
|
2022-07-23 23:45:48 +05:30
|
|
|
lastPageSize,
|
|
|
|
afterCursor,
|
|
|
|
beforeCursor,
|
|
|
|
) => ({
|
|
|
|
firstPageSize: lastPageSize ? undefined : firstPageSize,
|
|
|
|
lastPageSize,
|
2022-05-07 20:08:51 +05:30
|
|
|
afterCursor,
|
|
|
|
beforeCursor,
|
|
|
|
});
|
2021-10-27 15:23:28 +05:30
|
|
|
|
2021-06-08 01:23:25 +05:30
|
|
|
export const getSortKey = (sort) =>
|
2021-09-30 23:02:18 +05:30
|
|
|
Object.keys(urlSortParams).find((key) => urlSortParams[key] === sort);
|
2021-06-08 01:23:25 +05:30
|
|
|
|
2022-04-04 11:22:00 +05:30
|
|
|
export const isSortKey = (sort) => Object.keys(urlSortParams).includes(sort);
|
2021-06-08 01:23:25 +05:30
|
|
|
|
2023-03-04 22:38:38 +05:30
|
|
|
export const getSortOptions = ({
|
|
|
|
hasBlockedIssuesFeature,
|
|
|
|
hasIssuableHealthStatusFeature,
|
|
|
|
hasIssueWeightsFeature,
|
|
|
|
}) => {
|
2021-06-08 01:23:25 +05:30
|
|
|
const sortOptions = [
|
|
|
|
{
|
|
|
|
id: 1,
|
|
|
|
title: __('Priority'),
|
|
|
|
sortDirection: {
|
2021-09-30 23:02:18 +05:30
|
|
|
ascending: PRIORITY_ASC,
|
2021-06-08 01:23:25 +05:30
|
|
|
descending: PRIORITY_DESC,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: 2,
|
|
|
|
title: __('Created date'),
|
|
|
|
sortDirection: {
|
|
|
|
ascending: CREATED_ASC,
|
|
|
|
descending: CREATED_DESC,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: 3,
|
2022-03-02 08:16:31 +05:30
|
|
|
title: __('Updated date'),
|
2021-06-08 01:23:25 +05:30
|
|
|
sortDirection: {
|
|
|
|
ascending: UPDATED_ASC,
|
|
|
|
descending: UPDATED_DESC,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: 4,
|
2022-08-13 15:12:31 +05:30
|
|
|
title: __('Closed date'),
|
|
|
|
sortDirection: {
|
2023-03-04 22:38:38 +05:30
|
|
|
ascending: CLOSED_AT_ASC,
|
|
|
|
descending: CLOSED_AT_DESC,
|
2022-08-13 15:12:31 +05:30
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: 5,
|
2021-06-08 01:23:25 +05:30
|
|
|
title: __('Milestone due date'),
|
|
|
|
sortDirection: {
|
|
|
|
ascending: MILESTONE_DUE_ASC,
|
|
|
|
descending: MILESTONE_DUE_DESC,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
2022-08-13 15:12:31 +05:30
|
|
|
id: 6,
|
2021-06-08 01:23:25 +05:30
|
|
|
title: __('Due date'),
|
|
|
|
sortDirection: {
|
|
|
|
ascending: DUE_DATE_ASC,
|
|
|
|
descending: DUE_DATE_DESC,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
2022-08-13 15:12:31 +05:30
|
|
|
id: 7,
|
2021-06-08 01:23:25 +05:30
|
|
|
title: __('Popularity'),
|
|
|
|
sortDirection: {
|
|
|
|
ascending: POPULARITY_ASC,
|
|
|
|
descending: POPULARITY_DESC,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
2022-08-13 15:12:31 +05:30
|
|
|
id: 8,
|
2021-06-08 01:23:25 +05:30
|
|
|
title: __('Label priority'),
|
|
|
|
sortDirection: {
|
2021-09-30 23:02:18 +05:30
|
|
|
ascending: LABEL_PRIORITY_ASC,
|
2021-06-08 01:23:25 +05:30
|
|
|
descending: LABEL_PRIORITY_DESC,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
2022-08-13 15:12:31 +05:30
|
|
|
id: 9,
|
2021-06-08 01:23:25 +05:30
|
|
|
title: __('Manual'),
|
|
|
|
sortDirection: {
|
2021-09-30 23:02:18 +05:30
|
|
|
ascending: RELATIVE_POSITION_ASC,
|
|
|
|
descending: RELATIVE_POSITION_ASC,
|
2021-06-08 01:23:25 +05:30
|
|
|
},
|
|
|
|
},
|
2021-12-11 22:18:48 +05:30
|
|
|
{
|
2022-08-13 15:12:31 +05:30
|
|
|
id: 10,
|
2021-12-11 22:18:48 +05:30
|
|
|
title: __('Title'),
|
|
|
|
sortDirection: {
|
|
|
|
ascending: TITLE_ASC,
|
|
|
|
descending: TITLE_DESC,
|
|
|
|
},
|
|
|
|
},
|
2021-06-08 01:23:25 +05:30
|
|
|
];
|
|
|
|
|
2023-03-04 22:38:38 +05:30
|
|
|
if (hasIssuableHealthStatusFeature) {
|
|
|
|
sortOptions.push({
|
|
|
|
id: sortOptions.length + 1,
|
|
|
|
title: __('Health'),
|
|
|
|
sortDirection: {
|
|
|
|
ascending: HEALTH_STATUS_ASC,
|
|
|
|
descending: HEALTH_STATUS_DESC,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-06-08 01:23:25 +05:30
|
|
|
if (hasIssueWeightsFeature) {
|
|
|
|
sortOptions.push({
|
2021-12-11 22:18:48 +05:30
|
|
|
id: sortOptions.length + 1,
|
2021-06-08 01:23:25 +05:30
|
|
|
title: __('Weight'),
|
|
|
|
sortDirection: {
|
|
|
|
ascending: WEIGHT_ASC,
|
|
|
|
descending: WEIGHT_DESC,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (hasBlockedIssuesFeature) {
|
|
|
|
sortOptions.push({
|
2021-12-11 22:18:48 +05:30
|
|
|
id: sortOptions.length + 1,
|
2021-06-08 01:23:25 +05:30
|
|
|
title: __('Blocking'),
|
|
|
|
sortDirection: {
|
2022-01-26 12:08:38 +05:30
|
|
|
ascending: BLOCKING_ISSUES_ASC,
|
2021-06-08 01:23:25 +05:30
|
|
|
descending: BLOCKING_ISSUES_DESC,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return sortOptions;
|
|
|
|
};
|
|
|
|
|
|
|
|
const tokenTypes = Object.keys(filters);
|
|
|
|
|
|
|
|
const getUrlParams = (tokenType) =>
|
2021-09-30 23:02:18 +05:30
|
|
|
Object.values(filters[tokenType][URL_PARAM]).flatMap((filterObj) => Object.values(filterObj));
|
2021-06-08 01:23:25 +05:30
|
|
|
|
|
|
|
const urlParamKeys = tokenTypes.flatMap(getUrlParams);
|
|
|
|
|
|
|
|
const getTokenTypeFromUrlParamKey = (urlParamKey) =>
|
|
|
|
tokenTypes.find((tokenType) => getUrlParams(tokenType).includes(urlParamKey));
|
|
|
|
|
|
|
|
const getOperatorFromUrlParamKey = (tokenType, urlParamKey) =>
|
2021-09-30 23:02:18 +05:30
|
|
|
Object.entries(filters[tokenType][URL_PARAM]).find(([, filterObj]) =>
|
2021-06-08 01:23:25 +05:30
|
|
|
Object.values(filterObj).includes(urlParamKey),
|
|
|
|
)[0];
|
|
|
|
|
|
|
|
const convertToFilteredTokens = (locationSearch) =>
|
|
|
|
Array.from(new URLSearchParams(locationSearch).entries())
|
|
|
|
.filter(([key]) => urlParamKeys.includes(key))
|
|
|
|
.map(([key, data]) => {
|
|
|
|
const type = getTokenTypeFromUrlParamKey(key);
|
|
|
|
const operator = getOperatorFromUrlParamKey(type, key);
|
|
|
|
return {
|
|
|
|
type,
|
|
|
|
value: { data, operator },
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
|
|
|
const convertToFilteredSearchTerms = (locationSearch) =>
|
|
|
|
new URLSearchParams(locationSearch)
|
|
|
|
.get('search')
|
|
|
|
?.split(' ')
|
|
|
|
.map((word) => ({
|
|
|
|
type: FILTERED_SEARCH_TERM,
|
|
|
|
value: {
|
|
|
|
data: word,
|
|
|
|
},
|
|
|
|
})) || [];
|
|
|
|
|
|
|
|
export const getFilterTokens = (locationSearch) => {
|
|
|
|
if (!locationSearch) {
|
2022-07-16 23:28:13 +05:30
|
|
|
return [createTerm()];
|
2021-06-08 01:23:25 +05:30
|
|
|
}
|
|
|
|
const filterTokens = convertToFilteredTokens(locationSearch);
|
|
|
|
const searchTokens = convertToFilteredSearchTerms(locationSearch);
|
2022-07-16 23:28:13 +05:30
|
|
|
const tokens = filterTokens.concat(searchTokens);
|
|
|
|
return tokens.length ? tokens : [createTerm()];
|
2021-06-08 01:23:25 +05:30
|
|
|
};
|
|
|
|
|
2023-03-04 22:38:38 +05:30
|
|
|
const isSpecialFilter = (type, data) => {
|
2022-06-21 17:19:12 +05:30
|
|
|
const isAssigneeIdParam =
|
2023-03-04 22:38:38 +05:30
|
|
|
type === TOKEN_TYPE_ASSIGNEE &&
|
2022-06-21 17:19:12 +05:30
|
|
|
isPositiveInteger(data) &&
|
|
|
|
getParameterByName(PARAM_ASSIGNEE_ID) === data;
|
2023-03-04 22:38:38 +05:30
|
|
|
return specialFilterValues.includes(data) || isAssigneeIdParam;
|
|
|
|
};
|
|
|
|
|
|
|
|
const getFilterType = ({ type, value: { data, operator } }) => {
|
|
|
|
const isUnionedAuthor = type === TOKEN_TYPE_AUTHOR && operator === OPERATOR_OR;
|
2023-03-17 16:20:25 +05:30
|
|
|
const isUnionedLabel = type === TOKEN_TYPE_LABEL && operator === OPERATOR_OR;
|
2022-06-21 17:19:12 +05:30
|
|
|
|
2023-03-17 16:20:25 +05:30
|
|
|
if (isUnionedAuthor || isUnionedLabel) {
|
2023-03-04 22:38:38 +05:30
|
|
|
return ALTERNATIVE_FILTER;
|
|
|
|
}
|
|
|
|
if (isSpecialFilter(type, data)) {
|
|
|
|
return SPECIAL_FILTER;
|
|
|
|
}
|
|
|
|
return NORMAL_FILTER;
|
2022-06-21 17:19:12 +05:30
|
|
|
};
|
2021-06-08 01:23:25 +05:30
|
|
|
|
2021-12-11 22:18:48 +05:30
|
|
|
const wildcardTokens = [TOKEN_TYPE_ITERATION, TOKEN_TYPE_MILESTONE, TOKEN_TYPE_RELEASE];
|
|
|
|
|
2021-10-27 15:23:28 +05:30
|
|
|
const isWildcardValue = (tokenType, value) =>
|
2022-06-21 17:19:12 +05:30
|
|
|
wildcardTokens.includes(tokenType) && specialFilterValues.includes(value);
|
2021-10-27 15:23:28 +05:30
|
|
|
|
2023-03-17 16:20:25 +05:30
|
|
|
const isHealthStatusSpecialFilter = (tokenType, value) =>
|
|
|
|
tokenType === TOKEN_TYPE_HEALTH && specialFilterValues.includes(value);
|
|
|
|
|
2021-10-27 15:23:28 +05:30
|
|
|
const requiresUpperCaseValue = (tokenType, value) =>
|
2023-03-17 16:20:25 +05:30
|
|
|
tokenType === TOKEN_TYPE_TYPE ||
|
|
|
|
isWildcardValue(tokenType, value) ||
|
|
|
|
isHealthStatusSpecialFilter(tokenType, value);
|
2021-10-27 15:23:28 +05:30
|
|
|
|
2021-12-11 22:18:48 +05:30
|
|
|
const formatData = (token) => {
|
|
|
|
if (requiresUpperCaseValue(token.type, token.value.data)) {
|
|
|
|
return token.value.data.toUpperCase();
|
|
|
|
}
|
|
|
|
if (token.type === TOKEN_TYPE_CONFIDENTIAL) {
|
|
|
|
return token.value.data === 'yes';
|
|
|
|
}
|
|
|
|
return token.value.data;
|
|
|
|
};
|
2021-09-30 23:02:18 +05:30
|
|
|
|
|
|
|
export const convertToApiParams = (filterTokens) => {
|
|
|
|
const params = {};
|
|
|
|
const not = {};
|
2023-01-13 00:05:48 +05:30
|
|
|
const or = {};
|
2021-09-30 23:02:18 +05:30
|
|
|
|
|
|
|
filterTokens
|
|
|
|
.filter((token) => token.type !== FILTERED_SEARCH_TERM)
|
|
|
|
.forEach((token) => {
|
2023-03-04 22:38:38 +05:30
|
|
|
const filterType = getFilterType(token);
|
|
|
|
const apiField = filters[token.type][API_PARAM][filterType];
|
2023-01-13 00:05:48 +05:30
|
|
|
let obj;
|
2023-03-04 22:38:38 +05:30
|
|
|
if (token.value.operator === OPERATOR_NOT) {
|
2023-01-13 00:05:48 +05:30
|
|
|
obj = not;
|
|
|
|
} else if (token.value.operator === OPERATOR_OR) {
|
|
|
|
obj = or;
|
|
|
|
} else {
|
|
|
|
obj = params;
|
|
|
|
}
|
2021-10-27 15:23:28 +05:30
|
|
|
const data = formatData(token);
|
2021-09-30 23:02:18 +05:30
|
|
|
Object.assign(obj, {
|
2023-03-04 22:38:38 +05:30
|
|
|
[apiField]: obj[apiField] ? [obj[apiField], data].flat() : data,
|
2021-09-30 23:02:18 +05:30
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2023-01-13 00:05:48 +05:30
|
|
|
if (Object.keys(not).length) {
|
|
|
|
Object.assign(params, { not });
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Object.keys(or).length) {
|
|
|
|
Object.assign(params, { or });
|
|
|
|
}
|
|
|
|
|
|
|
|
return params;
|
2021-09-30 23:02:18 +05:30
|
|
|
};
|
|
|
|
|
|
|
|
export const convertToUrlParams = (filterTokens) =>
|
2021-06-08 01:23:25 +05:30
|
|
|
filterTokens
|
|
|
|
.filter((token) => token.type !== FILTERED_SEARCH_TERM)
|
|
|
|
.reduce((acc, token) => {
|
2023-03-04 22:38:38 +05:30
|
|
|
const filterType = getFilterType(token);
|
|
|
|
const urlParam = filters[token.type][URL_PARAM][token.value.operator]?.[filterType];
|
2021-06-08 01:23:25 +05:30
|
|
|
return Object.assign(acc, {
|
2023-03-04 22:38:38 +05:30
|
|
|
[urlParam]: acc[urlParam] ? [acc[urlParam], token.value.data].flat() : token.value.data,
|
2021-06-08 01:23:25 +05:30
|
|
|
});
|
|
|
|
}, {});
|
|
|
|
|
|
|
|
export const convertToSearchQuery = (filterTokens) =>
|
|
|
|
filterTokens
|
|
|
|
.filter((token) => token.type === FILTERED_SEARCH_TERM && token.value.data)
|
|
|
|
.map((token) => token.value.data)
|
2023-03-04 22:38:38 +05:30
|
|
|
.join(' ') || undefined;
|