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 372c15be931be1232dc4405658df3637c3bae8a9..142438bec216f15375d3b0d7cd9dd8e44ffa8e03 100644 --- a/app/assets/javascripts/issues_list/components/issues_list_app.vue +++ b/app/assets/javascripts/issues_list/components/issues_list_app.vue @@ -34,6 +34,7 @@ import { import axios from '~/lib/utils/axios_utils'; import { convertObjectPropsToCamelCase, getParameterByName } from '~/lib/utils/common_utils'; import { __ } from '~/locale'; +import { DEFAULT_NONE_ANY } from '~/vue_shared/components/filtered_search_bar/constants'; import AuthorToken from '~/vue_shared/components/filtered_search_bar/tokens/author_token.vue'; import EmojiToken from '~/vue_shared/components/filtered_search_bar/tokens/emoji_token.vue'; import IterationToken from '~/vue_shared/components/filtered_search_bar/tokens/iteration_token.vue'; @@ -186,7 +187,7 @@ export default { token: AuthorToken, dataType: 'user', unique: true, - defaultAuthors: [], + defaultAuthors: DEFAULT_NONE_ANY, fetchAuthors: this.fetchUsers, }, { @@ -213,7 +214,6 @@ export default { token: EmojiToken, unique: true, operators: [{ value: '=', description: __('is') }], - defaultEmojis: [], fetchEmojis: this.fetchEmojis, }, { @@ -237,7 +237,6 @@ export default { icon: 'iteration', token: IterationToken, unique: true, - defaultIterations: [], fetchIterations: this.fetchIterations, }); } diff --git a/app/assets/javascripts/issues_list/constants.js b/app/assets/javascripts/issues_list/constants.js index ee5150796680676642b0ed088b735beabeb9b614..3b01d0df523be6f0628f66f8eac170ad6a91779d 100644 --- a/app/assets/javascripts/issues_list/constants.js +++ b/app/assets/javascripts/issues_list/constants.js @@ -1,4 +1,9 @@ import { __, s__ } from '~/locale'; +import { + FILTER_ANY, + FILTER_CURRENT, + FILTER_NONE, +} from '~/vue_shared/components/filtered_search_bar/constants'; // Maps sort order as it appears in the URL query to API `order_by` and `sort` params. const PRIORITY = 'priority'; @@ -194,81 +199,149 @@ export const FILTERED_SEARCH_TERM = 'filtered-search-term'; export const OPERATOR_IS = '='; export const OPERATOR_IS_NOT = '!='; +export const NORMAL_FILTER = 'normalFilter'; +export const SPECIAL_FILTER = 'specialFilter'; +export const SPECIAL_FILTER_VALUES = [FILTER_NONE, FILTER_ANY, FILTER_CURRENT]; + export const filters = { author_username: { apiParam: { - [OPERATOR_IS]: 'author_username', - [OPERATOR_IS_NOT]: 'not[author_username]', + [OPERATOR_IS]: { + [NORMAL_FILTER]: 'author_username', + }, + [OPERATOR_IS_NOT]: { + [NORMAL_FILTER]: 'not[author_username]', + }, }, urlParam: { - [OPERATOR_IS]: 'author_username', - [OPERATOR_IS_NOT]: 'not[author_username]', + [OPERATOR_IS]: { + [NORMAL_FILTER]: 'author_username', + }, + [OPERATOR_IS_NOT]: { + [NORMAL_FILTER]: 'not[author_username]', + }, }, }, assignee_username: { apiParam: { - [OPERATOR_IS]: 'assignee_username', - [OPERATOR_IS_NOT]: 'not[assignee_username]', + [OPERATOR_IS]: { + [NORMAL_FILTER]: 'assignee_username', + [SPECIAL_FILTER]: 'assignee_id', + }, + [OPERATOR_IS_NOT]: { + [NORMAL_FILTER]: 'not[assignee_username]', + }, }, urlParam: { - [OPERATOR_IS]: 'assignee_username[]', - [OPERATOR_IS_NOT]: 'not[assignee_username][]', + [OPERATOR_IS]: { + [NORMAL_FILTER]: 'assignee_username[]', + [SPECIAL_FILTER]: 'assignee_id', + }, + [OPERATOR_IS_NOT]: { + [NORMAL_FILTER]: 'not[assignee_username][]', + }, }, }, milestone: { apiParam: { - [OPERATOR_IS]: 'milestone', - [OPERATOR_IS_NOT]: 'not[milestone]', + [OPERATOR_IS]: { + [NORMAL_FILTER]: 'milestone', + }, + [OPERATOR_IS_NOT]: { + [NORMAL_FILTER]: 'not[milestone]', + }, }, urlParam: { - [OPERATOR_IS]: 'milestone_title', - [OPERATOR_IS_NOT]: 'not[milestone_title]', + [OPERATOR_IS]: { + [NORMAL_FILTER]: 'milestone_title', + }, + [OPERATOR_IS_NOT]: { + [NORMAL_FILTER]: 'not[milestone_title]', + }, }, }, labels: { apiParam: { - [OPERATOR_IS]: 'labels', - [OPERATOR_IS_NOT]: 'not[labels]', + [OPERATOR_IS]: { + [NORMAL_FILTER]: 'labels', + }, + [OPERATOR_IS_NOT]: { + [NORMAL_FILTER]: 'not[labels]', + }, }, urlParam: { - [OPERATOR_IS]: 'label_name[]', - [OPERATOR_IS_NOT]: 'not[label_name][]', + [OPERATOR_IS]: { + [NORMAL_FILTER]: 'label_name[]', + }, + [OPERATOR_IS_NOT]: { + [NORMAL_FILTER]: 'not[label_name][]', + }, }, }, my_reaction_emoji: { apiParam: { - [OPERATOR_IS]: 'my_reaction_emoji', + [OPERATOR_IS]: { + [NORMAL_FILTER]: 'my_reaction_emoji', + [SPECIAL_FILTER]: 'my_reaction_emoji', + }, }, urlParam: { - [OPERATOR_IS]: 'my_reaction_emoji', + [OPERATOR_IS]: { + [NORMAL_FILTER]: 'my_reaction_emoji', + [SPECIAL_FILTER]: 'my_reaction_emoji', + }, }, }, confidential: { apiParam: { - [OPERATOR_IS]: 'confidential', + [OPERATOR_IS]: { + [NORMAL_FILTER]: 'confidential', + }, }, urlParam: { - [OPERATOR_IS]: 'confidential', + [OPERATOR_IS]: { + [NORMAL_FILTER]: 'confidential', + }, }, }, iteration: { apiParam: { - [OPERATOR_IS]: 'iteration_title', - [OPERATOR_IS_NOT]: 'not[iteration_title]', + [OPERATOR_IS]: { + [NORMAL_FILTER]: 'iteration_title', + [SPECIAL_FILTER]: 'iteration_id', + }, + [OPERATOR_IS_NOT]: { + [NORMAL_FILTER]: 'not[iteration_title]', + }, }, urlParam: { - [OPERATOR_IS]: 'iteration_title', - [OPERATOR_IS_NOT]: 'not[iteration_title]', + [OPERATOR_IS]: { + [NORMAL_FILTER]: 'iteration_title', + [SPECIAL_FILTER]: 'iteration_id', + }, + [OPERATOR_IS_NOT]: { + [NORMAL_FILTER]: 'not[iteration_title]', + }, }, }, weight: { apiParam: { - [OPERATOR_IS]: 'weight', - [OPERATOR_IS_NOT]: 'not[weight]', + [OPERATOR_IS]: { + [NORMAL_FILTER]: 'weight', + [SPECIAL_FILTER]: 'weight', + }, + [OPERATOR_IS_NOT]: { + [NORMAL_FILTER]: 'not[weight]', + }, }, urlParam: { - [OPERATOR_IS]: 'weight', - [OPERATOR_IS_NOT]: 'not[weight]', + [OPERATOR_IS]: { + [NORMAL_FILTER]: 'weight', + [SPECIAL_FILTER]: 'weight', + }, + [OPERATOR_IS_NOT]: { + [NORMAL_FILTER]: 'not[weight]', + }, }, }, }; diff --git a/app/assets/javascripts/issues_list/utils.js b/app/assets/javascripts/issues_list/utils.js index 30d122e7bbefafa20002443e30c808db87c37d03..f39f34894aaeff98e1c0abe281f17b5733e1f56d 100644 --- a/app/assets/javascripts/issues_list/utils.js +++ b/app/assets/javascripts/issues_list/utils.js @@ -11,12 +11,15 @@ import { LABEL_PRIORITY_DESC, MILESTONE_DUE_ASC, MILESTONE_DUE_DESC, + NORMAL_FILTER, POPULARITY_ASC, POPULARITY_DESC, PRIORITY_ASC, PRIORITY_DESC, RELATIVE_POSITION_ASC, sortParams, + SPECIAL_FILTER, + SPECIAL_FILTER_VALUES, UPDATED_ASC, UPDATED_DESC, WEIGHT_ASC, @@ -124,13 +127,18 @@ export const getSortOptions = (hasIssueWeightsFeature, hasBlockedIssuesFeature) const tokenTypes = Object.keys(filters); -const urlParamKeys = tokenTypes.flatMap((key) => Object.values(filters[key].urlParam)); +const getUrlParams = (tokenType) => + Object.values(filters[tokenType].urlParam).flatMap((filterObj) => Object.values(filterObj)); + +const urlParamKeys = tokenTypes.flatMap(getUrlParams); const getTokenTypeFromUrlParamKey = (urlParamKey) => - tokenTypes.find((key) => Object.values(filters[key].urlParam).includes(urlParamKey)); + tokenTypes.find((tokenType) => getUrlParams(tokenType).includes(urlParamKey)); const getOperatorFromUrlParamKey = (tokenType, urlParamKey) => - Object.entries(filters[tokenType].urlParam).find(([, urlParam]) => urlParam === urlParamKey)[0]; + Object.entries(filters[tokenType].urlParam).find(([, filterObj]) => + Object.values(filterObj).includes(urlParamKey), + )[0]; const convertToFilteredTokens = (locationSearch) => Array.from(new URLSearchParams(locationSearch).entries()) @@ -164,11 +172,15 @@ export const getFilterTokens = (locationSearch) => { return filterTokens.concat(searchTokens); }; +const getFilterType = (data) => + SPECIAL_FILTER_VALUES.includes(data) ? SPECIAL_FILTER : NORMAL_FILTER; + export const convertToApiParams = (filterTokens) => filterTokens .filter((token) => token.type !== FILTERED_SEARCH_TERM) .reduce((acc, token) => { - const apiParam = filters[token.type].apiParam[token.value.operator]; + const filterType = getFilterType(token.value.data); + const apiParam = filters[token.type].apiParam[token.value.operator][filterType]; return Object.assign(acc, { [apiParam]: acc[apiParam] ? `${acc[apiParam]},${token.value.data}` : token.value.data, }); @@ -178,7 +190,8 @@ export const convertToUrlParams = (filterTokens) => filterTokens .filter((token) => token.type !== FILTERED_SEARCH_TERM) .reduce((acc, token) => { - const urlParam = filters[token.type].urlParam[token.value.operator]; + const filterType = getFilterType(token.value.data); + const urlParam = filters[token.type].urlParam[token.value.operator]?.[filterType]; return Object.assign(acc, { [urlParam]: acc[urlParam] ? acc[urlParam].concat(token.value.data) : [token.value.data], }); diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/constants.js b/app/assets/javascripts/vue_shared/components/filtered_search_bar/constants.js index e2868879425195cd132cbb2c6bdd00df76e74048..519b461c0157d51840714718dafd646c80812187 100644 --- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/constants.js +++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/constants.js @@ -3,21 +3,24 @@ import { __ } from '~/locale'; export const DEBOUNCE_DELAY = 200; -const DEFAULT_LABEL_NO_LABEL = { value: 'No label', text: __('No label') }; -export const DEFAULT_LABEL_NONE = { value: 'None', text: __('None') }; -export const DEFAULT_LABEL_ANY = { value: 'Any', text: __('Any') }; -export const DEFAULT_LABEL_CURRENT = { value: 'Current', text: __('Current') }; +export const FILTER_NONE = 'None'; +export const FILTER_ANY = 'Any'; +export const FILTER_CURRENT = 'Current'; -export const DEFAULT_ITERATIONS = [DEFAULT_LABEL_NONE, DEFAULT_LABEL_ANY, DEFAULT_LABEL_CURRENT]; +export const DEFAULT_LABEL_NONE = { value: FILTER_NONE, text: __(FILTER_NONE) }; +export const DEFAULT_LABEL_ANY = { value: FILTER_ANY, text: __(FILTER_ANY) }; +export const DEFAULT_NONE_ANY = [DEFAULT_LABEL_NONE, DEFAULT_LABEL_ANY]; -export const DEFAULT_LABELS = [DEFAULT_LABEL_NO_LABEL]; +export const DEFAULT_ITERATIONS = DEFAULT_NONE_ANY.concat([ + { value: FILTER_CURRENT, text: __(FILTER_CURRENT) }, +]); -export const DEFAULT_MILESTONES = [ - DEFAULT_LABEL_NONE, - DEFAULT_LABEL_ANY, +export const DEFAULT_LABELS = [{ value: 'No label', text: __('No label') }]; + +export const DEFAULT_MILESTONES = DEFAULT_NONE_ANY.concat([ { value: 'Upcoming', text: __('Upcoming') }, { value: 'Started', text: __('Started') }, -]; +]); export const SortDirection = { descending: 'descending', diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/emoji_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/emoji_token.vue index 269e29a6dff07bd91bd276ecc0003c3f61f09ff6..f2f4787d80bfab681d45bce20a3f09ceda2a9cf6 100644 --- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/emoji_token.vue +++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/emoji_token.vue @@ -10,7 +10,7 @@ import { debounce } from 'lodash'; import { deprecatedCreateFlash as createFlash } from '~/flash'; import { __ } from '~/locale'; -import { DEFAULT_LABEL_NONE, DEFAULT_LABEL_ANY, DEBOUNCE_DELAY } from '../constants'; +import { DEBOUNCE_DELAY, DEFAULT_NONE_ANY } from '../constants'; import { stripQuotes } from '../filtered_search_utils'; export default { @@ -33,7 +33,7 @@ export default { data() { return { emojis: this.config.initialEmojis || [], - defaultEmojis: this.config.defaultEmojis || [DEFAULT_LABEL_NONE, DEFAULT_LABEL_ANY], + defaultEmojis: this.config.defaultEmojis || DEFAULT_NONE_ANY, loading: true, }; }, diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/weight_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/weight_token.vue index cfad79b9afaacf7bd0cc102ca0eabcfc1501a45f..72116f0e991f0fe9e210c55b27af8f11e0d9dbf0 100644 --- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/weight_token.vue +++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/weight_token.vue @@ -1,6 +1,6 @@