diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml index e5b278f20b3..fa4a5d3e3d8 100644 --- a/.gitlab/ci/rules.gitlab-ci.yml +++ b/.gitlab/ci/rules.gitlab-ci.yml @@ -1057,6 +1057,8 @@ .reports:rules:package_hunter: rules: + - if: "$PACKAGE_HUNTER_USER == null || $PACKAGE_HUNTER_USER == ''" + when: never - <<: *if-default-branch-schedule-2-hourly - <<: *if-merge-request changes: ["yarn.lock"] diff --git a/app/assets/javascripts/environments/components/environments_app.vue b/app/assets/javascripts/environments/components/environments_app.vue index 1ceb3cb9828..e4cf5760987 100644 --- a/app/assets/javascripts/environments/components/environments_app.vue +++ b/app/assets/javascripts/environments/components/environments_app.vue @@ -135,7 +135,7 @@ export default { >{{ $options.i18n.newEnvironmentButtonLabel }} - + isActive) ?? 0; + }, }, /** diff --git a/app/assets/javascripts/issues_list/components/issues_list_app.vue b/app/assets/javascripts/issues_list/components/issues_list_app.vue index 921af766796..051512e9a66 100644 --- a/app/assets/javascripts/issues_list/components/issues_list_app.vue +++ b/app/assets/javascripts/issues_list/components/issues_list_app.vue @@ -16,7 +16,6 @@ import IssuableByEmail from '~/issuable/components/issuable_by_email.vue'; import IssuableList from '~/issuable_list/components/issuable_list_root.vue'; import { IssuableListTabs, IssuableStates } from '~/issuable_list/constants'; import { - API_PARAM, CREATED_DESC, i18n, initialPageParams, @@ -25,7 +24,7 @@ import { PARAM_DUE_DATE, PARAM_SORT, PARAM_STATE, - RELATIVE_POSITION_DESC, + RELATIVE_POSITION_ASC, TOKEN_TYPE_ASSIGNEE, TOKEN_TYPE_AUTHOR, TOKEN_TYPE_CONFIDENTIAL, @@ -36,12 +35,12 @@ import { TOKEN_TYPE_MILESTONE, TOKEN_TYPE_WEIGHT, UPDATED_DESC, - URL_PARAM, urlSortParams, } from '~/issues_list/constants'; import { - convertToParams, + convertToApiParams, convertToSearchQuery, + convertToUrlParams, getDueDateValue, getFilterTokens, getSortKey, @@ -192,10 +191,10 @@ export default { ...this.apiFilterParams, }; }, - update: ({ project }) => project.issues.nodes, + update: ({ project }) => project?.issues.nodes ?? [], result({ data }) { - this.pageInfo = data.project.issues.pageInfo; - this.totalIssues = data.project.issues.count; + this.pageInfo = data.project?.issues.pageInfo ?? {}; + this.totalIssues = data.project?.issues.count ?? 0; this.exportCsvPathWithQuery = this.getExportCsvPathWithQuery(); }, error(error) { @@ -215,32 +214,30 @@ export default { return this.showBulkEditSidebar || !this.issues.length; }, isManualOrdering() { - return this.sortKey === RELATIVE_POSITION_DESC; + return this.sortKey === RELATIVE_POSITION_ASC; }, isOpenTab() { return this.state === IssuableStates.Opened; }, apiFilterParams() { - return convertToParams(this.filterTokens, API_PARAM); + return convertToApiParams(this.filterTokens); }, urlFilterParams() { - return convertToParams(this.filterTokens, URL_PARAM); + return convertToUrlParams(this.filterTokens); }, searchQuery() { return convertToSearchQuery(this.filterTokens) || undefined; }, searchTokens() { - let preloadedAuthors = []; + const preloadedAuthors = []; if (gon.current_user_id) { - preloadedAuthors = [ - { - id: gon.current_user_id, - name: gon.current_user_fullname, - username: gon.current_username, - avatar_url: gon.current_user_avatar_url, - }, - ]; + preloadedAuthors.push({ + id: gon.current_user_id, + name: gon.current_user_fullname, + username: gon.current_username, + avatar_url: gon.current_user_avatar_url, + }); } const tokens = [ @@ -252,6 +249,7 @@ export default { dataType: 'user', unique: true, defaultAuthors: [], + operators: OPERATOR_IS_ONLY, fetchAuthors: this.fetchUsers, preloadedAuthors, }, @@ -280,7 +278,7 @@ export default { title: TOKEN_TITLE_LABEL, icon: 'labels', token: LabelToken, - defaultLabels: [], + defaultLabels: DEFAULT_NONE_ANY, fetchLabels: this.fetchLabels, }, ]; @@ -329,6 +327,7 @@ export default { token: EpicToken, unique: true, idProperty: 'id', + useIdValue: true, fetchEpics: this.fetchEpics, }); } @@ -346,7 +345,7 @@ export default { return tokens; }, showPaginationControls() { - return this.issues.length > 0; + return this.issues.length > 0 && (this.pageInfo.hasNextPage || this.pageInfo.hasPreviousPage); }, sortOptions() { return getSortOptions(this.hasIssueWeightsFeature, this.hasBlockedIssuesFeature); @@ -361,22 +360,12 @@ export default { ); }, urlParams() { - const filterParams = { - ...this.urlFilterParams, - }; - - if (filterParams.epic_id) { - filterParams.epic_id = encodeURIComponent(filterParams.epic_id); - } else if (filterParams['not[epic_id]']) { - filterParams['not[epic_id]'] = encodeURIComponent(filterParams['not[epic_id]']); - } - return { due_date: this.dueDateFilter, search: this.searchQuery, + sort: urlSortParams[this.sortKey], state: this.state, - ...urlSortParams[this.sortKey], - ...filterParams, + ...this.urlFilterParams, }; }, }, @@ -424,7 +413,10 @@ export default { return this.fetchWithCache(this.projectMilestonesPath, 'milestones', 'title', search, true); }, fetchIterations(search) { - return axios.get(this.projectIterationsPath, { params: { search } }); + const id = Number(search); + return !search || Number.isNaN(id) + ? axios.get(this.projectIterationsPath, { params: { search } }) + : axios.get(this.projectIterationsPath, { params: { id } }); }, fetchUsers(search) { return axios.get(this.autocompleteUsersPath, { params: { search } }); @@ -471,6 +463,7 @@ export default { this.state = state; }, handleFilter(filter) { + this.pageParams = initialPageParams; this.filterTokens = filter; }, handleNextPage() { diff --git a/app/assets/javascripts/issues_list/constants.js b/app/assets/javascripts/issues_list/constants.js index 76006f9081d..6337ccc4926 100644 --- a/app/assets/javascripts/issues_list/constants.js +++ b/app/assets/javascripts/issues_list/constants.js @@ -128,21 +128,21 @@ export const CREATED_ASC = 'CREATED_ASC'; export const CREATED_DESC = 'CREATED_DESC'; export const DUE_DATE_ASC = 'DUE_DATE_ASC'; export const DUE_DATE_DESC = 'DUE_DATE_DESC'; +export const LABEL_PRIORITY_ASC = 'LABEL_PRIORITY_ASC'; export const LABEL_PRIORITY_DESC = 'LABEL_PRIORITY_DESC'; export const MILESTONE_DUE_ASC = 'MILESTONE_DUE_ASC'; export const MILESTONE_DUE_DESC = 'MILESTONE_DUE_DESC'; export const POPULARITY_ASC = 'POPULARITY_ASC'; export const POPULARITY_DESC = 'POPULARITY_DESC'; +export const PRIORITY_ASC = 'PRIORITY_ASC'; export const PRIORITY_DESC = 'PRIORITY_DESC'; -export const RELATIVE_POSITION_DESC = 'RELATIVE_POSITION_DESC'; +export const RELATIVE_POSITION_ASC = 'RELATIVE_POSITION_ASC'; export const UPDATED_ASC = 'UPDATED_ASC'; export const UPDATED_DESC = 'UPDATED_DESC'; export const WEIGHT_ASC = 'WEIGHT_ASC'; export const WEIGHT_DESC = 'WEIGHT_DESC'; -const SORT_ASC = 'asc'; -const SORT_DESC = 'desc'; - +const PRIORITY_ASC_SORT = 'priority_asc'; const CREATED_DATE_SORT = 'created_date'; const CREATED_ASC_SORT = 'created_asc'; const UPDATED_DESC_SORT = 'updated_desc'; @@ -150,129 +150,30 @@ const UPDATED_ASC_SORT = 'updated_asc'; const MILESTONE_SORT = 'milestone'; const MILESTONE_DUE_DESC_SORT = 'milestone_due_desc'; const DUE_DATE_DESC_SORT = 'due_date_desc'; +const LABEL_PRIORITY_ASC_SORT = 'label_priority_asc'; const POPULARITY_ASC_SORT = 'popularity_asc'; const WEIGHT_DESC_SORT = 'weight_desc'; const BLOCKING_ISSUES_DESC_SORT = 'blocking_issues_desc'; -const BLOCKING_ISSUES = 'blocking_issues'; - -export const apiSortParams = { - [PRIORITY_DESC]: { - order_by: PRIORITY, - sort: SORT_DESC, - }, - [CREATED_ASC]: { - order_by: CREATED_AT, - sort: SORT_ASC, - }, - [CREATED_DESC]: { - order_by: CREATED_AT, - sort: SORT_DESC, - }, - [UPDATED_ASC]: { - order_by: UPDATED_AT, - sort: SORT_ASC, - }, - [UPDATED_DESC]: { - order_by: UPDATED_AT, - sort: SORT_DESC, - }, - [MILESTONE_DUE_ASC]: { - order_by: MILESTONE_DUE, - sort: SORT_ASC, - }, - [MILESTONE_DUE_DESC]: { - order_by: MILESTONE_DUE, - sort: SORT_DESC, - }, - [DUE_DATE_ASC]: { - order_by: DUE_DATE, - sort: SORT_ASC, - }, - [DUE_DATE_DESC]: { - order_by: DUE_DATE, - sort: SORT_DESC, - }, - [POPULARITY_ASC]: { - order_by: POPULARITY, - sort: SORT_ASC, - }, - [POPULARITY_DESC]: { - order_by: POPULARITY, - sort: SORT_DESC, - }, - [LABEL_PRIORITY_DESC]: { - order_by: LABEL_PRIORITY, - sort: SORT_DESC, - }, - [RELATIVE_POSITION_DESC]: { - order_by: RELATIVE_POSITION, - per_page: 100, - sort: SORT_ASC, - }, - [WEIGHT_ASC]: { - order_by: WEIGHT, - sort: SORT_ASC, - }, - [WEIGHT_DESC]: { - order_by: WEIGHT, - sort: SORT_DESC, - }, - [BLOCKING_ISSUES_DESC]: { - order_by: BLOCKING_ISSUES, - sort: SORT_DESC, - }, -}; export const urlSortParams = { - [PRIORITY_DESC]: { - sort: PRIORITY, - }, - [CREATED_ASC]: { - sort: CREATED_ASC_SORT, - }, - [CREATED_DESC]: { - sort: CREATED_DATE_SORT, - }, - [UPDATED_ASC]: { - sort: UPDATED_ASC_SORT, - }, - [UPDATED_DESC]: { - sort: UPDATED_DESC_SORT, - }, - [MILESTONE_DUE_ASC]: { - sort: MILESTONE_SORT, - }, - [MILESTONE_DUE_DESC]: { - sort: MILESTONE_DUE_DESC_SORT, - }, - [DUE_DATE_ASC]: { - sort: DUE_DATE, - }, - [DUE_DATE_DESC]: { - sort: DUE_DATE_DESC_SORT, - }, - [POPULARITY_ASC]: { - sort: POPULARITY_ASC_SORT, - }, - [POPULARITY_DESC]: { - sort: POPULARITY, - }, - [LABEL_PRIORITY_DESC]: { - sort: LABEL_PRIORITY, - }, - [RELATIVE_POSITION_DESC]: { - sort: RELATIVE_POSITION, - per_page: 100, - }, - [WEIGHT_ASC]: { - sort: WEIGHT, - }, - [WEIGHT_DESC]: { - sort: WEIGHT_DESC_SORT, - }, - [BLOCKING_ISSUES_DESC]: { - sort: BLOCKING_ISSUES_DESC_SORT, - }, + [PRIORITY_ASC]: PRIORITY_ASC_SORT, + [PRIORITY_DESC]: PRIORITY, + [CREATED_ASC]: CREATED_ASC_SORT, + [CREATED_DESC]: CREATED_DATE_SORT, + [UPDATED_ASC]: UPDATED_ASC_SORT, + [UPDATED_DESC]: UPDATED_DESC_SORT, + [MILESTONE_DUE_ASC]: MILESTONE_SORT, + [MILESTONE_DUE_DESC]: MILESTONE_DUE_DESC_SORT, + [DUE_DATE_ASC]: DUE_DATE, + [DUE_DATE_DESC]: DUE_DATE_DESC_SORT, + [POPULARITY_ASC]: POPULARITY_ASC_SORT, + [POPULARITY_DESC]: POPULARITY, + [LABEL_PRIORITY_ASC]: LABEL_PRIORITY_ASC_SORT, + [LABEL_PRIORITY_DESC]: LABEL_PRIORITY, + [RELATIVE_POSITION_ASC]: RELATIVE_POSITION, + [WEIGHT_ASC]: WEIGHT, + [WEIGHT_DESC]: WEIGHT_DESC_SORT, + [BLOCKING_ISSUES_DESC]: BLOCKING_ISSUES_DESC_SORT, }; export const MAX_LIST_SIZE = 10; @@ -297,12 +198,7 @@ export const TOKEN_TYPE_WEIGHT = 'weight'; export const filters = { [TOKEN_TYPE_AUTHOR]: { [API_PARAM]: { - [OPERATOR_IS]: { - [NORMAL_FILTER]: 'author_username', - }, - [OPERATOR_IS_NOT]: { - [NORMAL_FILTER]: 'not[author_username]', - }, + [NORMAL_FILTER]: 'authorUsername', }, [URL_PARAM]: { [OPERATOR_IS]: { @@ -315,13 +211,8 @@ export const filters = { }, [TOKEN_TYPE_ASSIGNEE]: { [API_PARAM]: { - [OPERATOR_IS]: { - [NORMAL_FILTER]: 'assignee_username', - [SPECIAL_FILTER]: 'assignee_id', - }, - [OPERATOR_IS_NOT]: { - [NORMAL_FILTER]: 'not[assignee_username]', - }, + [NORMAL_FILTER]: 'assigneeUsernames', + [SPECIAL_FILTER]: 'assigneeId', }, [URL_PARAM]: { [OPERATOR_IS]: { @@ -336,12 +227,7 @@ export const filters = { }, [TOKEN_TYPE_MILESTONE]: { [API_PARAM]: { - [OPERATOR_IS]: { - [NORMAL_FILTER]: 'milestone', - }, - [OPERATOR_IS_NOT]: { - [NORMAL_FILTER]: 'not[milestone]', - }, + [NORMAL_FILTER]: 'milestoneTitle', }, [URL_PARAM]: { [OPERATOR_IS]: { @@ -354,16 +240,13 @@ export const filters = { }, [TOKEN_TYPE_LABEL]: { [API_PARAM]: { - [OPERATOR_IS]: { - [NORMAL_FILTER]: 'labels', - }, - [OPERATOR_IS_NOT]: { - [NORMAL_FILTER]: 'not[labels]', - }, + [NORMAL_FILTER]: 'labelName', + [SPECIAL_FILTER]: 'labelName', }, [URL_PARAM]: { [OPERATOR_IS]: { [NORMAL_FILTER]: 'label_name[]', + [SPECIAL_FILTER]: 'label_name[]', }, [OPERATOR_IS_NOT]: { [NORMAL_FILTER]: 'not[label_name][]', @@ -372,10 +255,8 @@ export const filters = { }, [TOKEN_TYPE_MY_REACTION]: { [API_PARAM]: { - [OPERATOR_IS]: { - [NORMAL_FILTER]: 'my_reaction_emoji', - [SPECIAL_FILTER]: 'my_reaction_emoji', - }, + [NORMAL_FILTER]: 'myReactionEmoji', + [SPECIAL_FILTER]: 'myReactionEmoji', }, [URL_PARAM]: { [OPERATOR_IS]: { @@ -386,9 +267,7 @@ export const filters = { }, [TOKEN_TYPE_CONFIDENTIAL]: { [API_PARAM]: { - [OPERATOR_IS]: { - [NORMAL_FILTER]: 'confidential', - }, + [NORMAL_FILTER]: 'confidential', }, [URL_PARAM]: { [OPERATOR_IS]: { @@ -398,33 +277,23 @@ export const filters = { }, [TOKEN_TYPE_ITERATION]: { [API_PARAM]: { - [OPERATOR_IS]: { - [NORMAL_FILTER]: 'iteration_title', - [SPECIAL_FILTER]: 'iteration_id', - }, - [OPERATOR_IS_NOT]: { - [NORMAL_FILTER]: 'not[iteration_title]', - }, + [NORMAL_FILTER]: 'iterationId', + [SPECIAL_FILTER]: 'iterationWildcardId', }, [URL_PARAM]: { [OPERATOR_IS]: { - [NORMAL_FILTER]: 'iteration_title', + [NORMAL_FILTER]: 'iteration_id', [SPECIAL_FILTER]: 'iteration_id', }, [OPERATOR_IS_NOT]: { - [NORMAL_FILTER]: 'not[iteration_title]', + [NORMAL_FILTER]: 'not[iteration_id]', }, }, }, [TOKEN_TYPE_EPIC]: { [API_PARAM]: { - [OPERATOR_IS]: { - [NORMAL_FILTER]: 'epic_id', - [SPECIAL_FILTER]: 'epic_id', - }, - [OPERATOR_IS_NOT]: { - [NORMAL_FILTER]: 'not[epic_id]', - }, + [NORMAL_FILTER]: 'epicId', + [SPECIAL_FILTER]: 'epicId', }, [URL_PARAM]: { [OPERATOR_IS]: { @@ -438,13 +307,8 @@ export const filters = { }, [TOKEN_TYPE_WEIGHT]: { [API_PARAM]: { - [OPERATOR_IS]: { - [NORMAL_FILTER]: 'weight', - [SPECIAL_FILTER]: 'weight', - }, - [OPERATOR_IS_NOT]: { - [NORMAL_FILTER]: 'not[weight]', - }, + [NORMAL_FILTER]: 'weight', + [SPECIAL_FILTER]: 'weight', }, [URL_PARAM]: { [OPERATOR_IS]: { diff --git a/app/assets/javascripts/issues_list/utils.js b/app/assets/javascripts/issues_list/utils.js index b5ec44198da..49f937cc453 100644 --- a/app/assets/javascripts/issues_list/utils.js +++ b/app/assets/javascripts/issues_list/utils.js @@ -1,4 +1,5 @@ import { + API_PARAM, BLOCKING_ISSUES_DESC, CREATED_ASC, CREATED_DESC, @@ -6,29 +7,36 @@ import { DUE_DATE_DESC, DUE_DATE_VALUES, filters, + LABEL_PRIORITY_ASC, LABEL_PRIORITY_DESC, MILESTONE_DUE_ASC, MILESTONE_DUE_DESC, NORMAL_FILTER, POPULARITY_ASC, POPULARITY_DESC, + PRIORITY_ASC, PRIORITY_DESC, - RELATIVE_POSITION_DESC, + RELATIVE_POSITION_ASC, SPECIAL_FILTER, SPECIAL_FILTER_VALUES, TOKEN_TYPE_ASSIGNEE, + TOKEN_TYPE_ITERATION, UPDATED_ASC, UPDATED_DESC, + URL_PARAM, urlSortParams, WEIGHT_ASC, WEIGHT_DESC, } from '~/issues_list/constants'; import { isPositiveInteger } from '~/lib/utils/number_utils'; import { __ } from '~/locale'; -import { FILTERED_SEARCH_TERM } from '~/vue_shared/components/filtered_search_bar/constants'; +import { + FILTERED_SEARCH_TERM, + OPERATOR_IS_NOT, +} from '~/vue_shared/components/filtered_search_bar/constants'; export const getSortKey = (sort) => - Object.keys(urlSortParams).find((key) => urlSortParams[key].sort === sort); + Object.keys(urlSortParams).find((key) => urlSortParams[key] === sort); export const getDueDateValue = (value) => (DUE_DATE_VALUES.includes(value) ? value : undefined); @@ -38,7 +46,7 @@ export const getSortOptions = (hasIssueWeightsFeature, hasBlockedIssuesFeature) id: 1, title: __('Priority'), sortDirection: { - ascending: PRIORITY_DESC, + ascending: PRIORITY_ASC, descending: PRIORITY_DESC, }, }, @@ -86,7 +94,7 @@ export const getSortOptions = (hasIssueWeightsFeature, hasBlockedIssuesFeature) id: 7, title: __('Label priority'), sortDirection: { - ascending: LABEL_PRIORITY_DESC, + ascending: LABEL_PRIORITY_ASC, descending: LABEL_PRIORITY_DESC, }, }, @@ -94,8 +102,8 @@ export const getSortOptions = (hasIssueWeightsFeature, hasBlockedIssuesFeature) id: 8, title: __('Manual'), sortDirection: { - ascending: RELATIVE_POSITION_DESC, - descending: RELATIVE_POSITION_DESC, + ascending: RELATIVE_POSITION_ASC, + descending: RELATIVE_POSITION_ASC, }, }, ]; @@ -128,7 +136,7 @@ export const getSortOptions = (hasIssueWeightsFeature, hasBlockedIssuesFeature) const tokenTypes = Object.keys(filters); const getUrlParams = (tokenType) => - Object.values(filters[tokenType].urlParam).flatMap((filterObj) => Object.values(filterObj)); + Object.values(filters[tokenType][URL_PARAM]).flatMap((filterObj) => Object.values(filterObj)); const urlParamKeys = tokenTypes.flatMap(getUrlParams); @@ -136,7 +144,7 @@ const getTokenTypeFromUrlParamKey = (urlParamKey) => tokenTypes.find((tokenType) => getUrlParams(tokenType).includes(urlParamKey)); const getOperatorFromUrlParamKey = (tokenType, urlParamKey) => - Object.entries(filters[tokenType].urlParam).find(([, filterObj]) => + Object.entries(filters[tokenType][URL_PARAM]).find(([, filterObj]) => Object.values(filterObj).includes(urlParamKey), )[0]; @@ -178,12 +186,36 @@ const getFilterType = (data, tokenType = '') => ? SPECIAL_FILTER : NORMAL_FILTER; -export const convertToParams = (filterTokens, paramType) => +const isIterationSpecialValue = (tokenType, value) => + tokenType === TOKEN_TYPE_ITERATION && SPECIAL_FILTER_VALUES.includes(value); + +export const convertToApiParams = (filterTokens) => { + const params = {}; + const not = {}; + + filterTokens + .filter((token) => token.type !== FILTERED_SEARCH_TERM) + .forEach((token) => { + const filterType = getFilterType(token.value.data, token.type); + const field = filters[token.type][API_PARAM][filterType]; + const obj = token.value.operator === OPERATOR_IS_NOT ? not : params; + const data = isIterationSpecialValue(token.type, token.value.data) + ? token.value.data.toUpperCase() + : token.value.data; + Object.assign(obj, { + [field]: obj[field] ? [obj[field], data].flat() : data, + }); + }); + + return Object.keys(not).length ? Object.assign(params, { not }) : params; +}; + +export const convertToUrlParams = (filterTokens) => filterTokens .filter((token) => token.type !== FILTERED_SEARCH_TERM) .reduce((acc, token) => { const filterType = getFilterType(token.value.data, token.type); - const param = filters[token.type][paramType][token.value.operator]?.[filterType]; + const param = filters[token.type][URL_PARAM][token.value.operator]?.[filterType]; return Object.assign(acc, { [param]: acc[param] ? [acc[param], token.value.data].flat() : token.value.data, }); diff --git a/app/assets/javascripts/nav/components/top_nav_dropdown_menu.vue b/app/assets/javascripts/nav/components/top_nav_dropdown_menu.vue index cac8fecb6b1..97856eaf256 100644 --- a/app/assets/javascripts/nav/components/top_nav_dropdown_menu.vue +++ b/app/assets/javascripts/nav/components/top_nav_dropdown_menu.vue @@ -72,7 +72,7 @@ export default {