Commit f372ec6f authored by Phil Hughes's avatar Phil Hughes

Merge branch '322755-add-releases-filtering' into 'master'

Add release filter and title sort to issues page refactor

See merge request gitlab-org/gitlab!72894
parents 45c31365 33597ad8
...@@ -36,6 +36,7 @@ import { ...@@ -36,6 +36,7 @@ import {
TOKEN_TYPE_LABEL, TOKEN_TYPE_LABEL,
TOKEN_TYPE_MILESTONE, TOKEN_TYPE_MILESTONE,
TOKEN_TYPE_MY_REACTION, TOKEN_TYPE_MY_REACTION,
TOKEN_TYPE_RELEASE,
TOKEN_TYPE_TYPE, TOKEN_TYPE_TYPE,
TOKEN_TYPE_WEIGHT, TOKEN_TYPE_WEIGHT,
UPDATED_DESC, UPDATED_DESC,
...@@ -65,6 +66,7 @@ import { ...@@ -65,6 +66,7 @@ import {
TOKEN_TITLE_LABEL, TOKEN_TITLE_LABEL,
TOKEN_TITLE_MILESTONE, TOKEN_TITLE_MILESTONE,
TOKEN_TITLE_MY_REACTION, TOKEN_TITLE_MY_REACTION,
TOKEN_TITLE_RELEASE,
TOKEN_TITLE_TYPE, TOKEN_TITLE_TYPE,
TOKEN_TITLE_WEIGHT, TOKEN_TITLE_WEIGHT,
} from '~/vue_shared/components/filtered_search_bar/constants'; } from '~/vue_shared/components/filtered_search_bar/constants';
...@@ -88,6 +90,8 @@ const LabelToken = () => ...@@ -88,6 +90,8 @@ const LabelToken = () =>
import('~/vue_shared/components/filtered_search_bar/tokens/label_token.vue'); import('~/vue_shared/components/filtered_search_bar/tokens/label_token.vue');
const MilestoneToken = () => const MilestoneToken = () =>
import('~/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue'); import('~/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue');
const ReleaseToken = () =>
import('~/vue_shared/components/filtered_search_bar/tokens/release_token.vue');
const WeightToken = () => const WeightToken = () =>
import('~/vue_shared/components/filtered_search_bar/tokens/weight_token.vue'); import('~/vue_shared/components/filtered_search_bar/tokens/weight_token.vue');
...@@ -165,6 +169,9 @@ export default { ...@@ -165,6 +169,9 @@ export default {
newIssuePath: { newIssuePath: {
default: '', default: '',
}, },
releasesPath: {
default: '',
},
rssPath: { rssPath: {
default: '', default: '',
}, },
...@@ -317,7 +324,6 @@ export default { ...@@ -317,7 +324,6 @@ export default {
title: TOKEN_TITLE_MILESTONE, title: TOKEN_TITLE_MILESTONE,
icon: 'clock', icon: 'clock',
token: MilestoneToken, token: MilestoneToken,
unique: true,
fetchMilestones: this.fetchMilestones, fetchMilestones: this.fetchMilestones,
}, },
{ {
...@@ -341,6 +347,16 @@ export default { ...@@ -341,6 +347,16 @@ export default {
}, },
]; ];
if (this.isProject) {
tokens.push({
type: TOKEN_TYPE_RELEASE,
title: TOKEN_TITLE_RELEASE,
icon: 'rocket',
token: ReleaseToken,
fetchReleases: this.fetchReleases,
});
}
if (this.isSignedIn) { if (this.isSignedIn) {
tokens.push({ tokens.push({
type: TOKEN_TYPE_MY_REACTION, type: TOKEN_TYPE_MY_REACTION,
...@@ -371,7 +387,6 @@ export default { ...@@ -371,7 +387,6 @@ export default {
title: TOKEN_TITLE_ITERATION, title: TOKEN_TITLE_ITERATION,
icon: 'iteration', icon: 'iteration',
token: IterationToken, token: IterationToken,
unique: true,
fetchIterations: this.fetchIterations, fetchIterations: this.fetchIterations,
}); });
} }
...@@ -457,6 +472,9 @@ export default { ...@@ -457,6 +472,9 @@ export default {
fetchEmojis(search) { fetchEmojis(search) {
return this.fetchWithCache(this.autocompleteAwardEmojisPath, 'emojis', 'name', search); return this.fetchWithCache(this.autocompleteAwardEmojisPath, 'emojis', 'name', search);
}, },
fetchReleases(search) {
return this.fetchWithCache(this.releasesPath, 'releases', 'tag', search);
},
fetchLabels(search) { fetchLabels(search) {
return this.$apollo return this.$apollo
.query({ .query({
......
...@@ -166,6 +166,7 @@ const LABEL_PRIORITY_ASC_SORT = 'label_priority_asc'; ...@@ -166,6 +166,7 @@ const LABEL_PRIORITY_ASC_SORT = 'label_priority_asc';
const POPULARITY_ASC_SORT = 'popularity_asc'; const POPULARITY_ASC_SORT = 'popularity_asc';
const WEIGHT_DESC_SORT = 'weight_desc'; const WEIGHT_DESC_SORT = 'weight_desc';
const BLOCKING_ISSUES_DESC_SORT = 'blocking_issues_desc'; const BLOCKING_ISSUES_DESC_SORT = 'blocking_issues_desc';
const TITLE_ASC_SORT = 'title_asc';
const TITLE_DESC_SORT = 'title_desc'; const TITLE_DESC_SORT = 'title_desc';
export const urlSortParams = { export const urlSortParams = {
...@@ -187,7 +188,7 @@ export const urlSortParams = { ...@@ -187,7 +188,7 @@ export const urlSortParams = {
[WEIGHT_ASC]: WEIGHT, [WEIGHT_ASC]: WEIGHT,
[WEIGHT_DESC]: WEIGHT_DESC_SORT, [WEIGHT_DESC]: WEIGHT_DESC_SORT,
[BLOCKING_ISSUES_DESC]: BLOCKING_ISSUES_DESC_SORT, [BLOCKING_ISSUES_DESC]: BLOCKING_ISSUES_DESC_SORT,
[TITLE_ASC]: TITLE, [TITLE_ASC]: TITLE_ASC_SORT,
[TITLE_DESC]: TITLE_DESC_SORT, [TITLE_DESC]: TITLE_DESC_SORT,
}; };
...@@ -211,6 +212,7 @@ export const TOKEN_TYPE_ASSIGNEE = 'assignee_username'; ...@@ -211,6 +212,7 @@ export const TOKEN_TYPE_ASSIGNEE = 'assignee_username';
export const TOKEN_TYPE_MILESTONE = 'milestone'; export const TOKEN_TYPE_MILESTONE = 'milestone';
export const TOKEN_TYPE_LABEL = 'labels'; export const TOKEN_TYPE_LABEL = 'labels';
export const TOKEN_TYPE_TYPE = 'type'; export const TOKEN_TYPE_TYPE = 'type';
export const TOKEN_TYPE_RELEASE = 'release';
export const TOKEN_TYPE_MY_REACTION = 'my_reaction_emoji'; export const TOKEN_TYPE_MY_REACTION = 'my_reaction_emoji';
export const TOKEN_TYPE_CONFIDENTIAL = 'confidential'; export const TOKEN_TYPE_CONFIDENTIAL = 'confidential';
export const TOKEN_TYPE_ITERATION = 'iteration'; export const TOKEN_TYPE_ITERATION = 'iteration';
...@@ -291,6 +293,21 @@ export const filters = { ...@@ -291,6 +293,21 @@ export const filters = {
}, },
}, },
}, },
[TOKEN_TYPE_RELEASE]: {
[API_PARAM]: {
[NORMAL_FILTER]: 'releaseTag',
[SPECIAL_FILTER]: 'releaseTagWildcardId',
},
[URL_PARAM]: {
[OPERATOR_IS]: {
[NORMAL_FILTER]: 'release_tag',
[SPECIAL_FILTER]: 'release_tag',
},
[OPERATOR_IS_NOT]: {
[NORMAL_FILTER]: 'not[release_tag]',
},
},
},
[TOKEN_TYPE_MY_REACTION]: { [TOKEN_TYPE_MY_REACTION]: {
[API_PARAM]: { [API_PARAM]: {
[NORMAL_FILTER]: 'myReactionEmoji', [NORMAL_FILTER]: 'myReactionEmoji',
......
...@@ -137,6 +137,7 @@ export function mountIssuesListApp() { ...@@ -137,6 +137,7 @@ export function mountIssuesListApp() {
newIssuePath, newIssuePath,
projectImportJiraPath, projectImportJiraPath,
quickActionsHelpPath, quickActionsHelpPath,
releasesPath,
resetPath, resetPath,
rssPath, rssPath,
showNewIssueLink, showNewIssueLink,
...@@ -164,6 +165,7 @@ export function mountIssuesListApp() { ...@@ -164,6 +165,7 @@ export function mountIssuesListApp() {
isSignedIn: parseBoolean(isSignedIn), isSignedIn: parseBoolean(isSignedIn),
jiraIntegrationPath, jiraIntegrationPath,
newIssuePath, newIssuePath,
releasesPath,
rssPath, rssPath,
showNewIssueLink: parseBoolean(showNewIssueLink), showNewIssueLink: parseBoolean(showNewIssueLink),
signInPath, signInPath,
......
...@@ -16,6 +16,8 @@ query getIssues( ...@@ -16,6 +16,8 @@ query getIssues(
$milestoneTitle: [String] $milestoneTitle: [String]
$milestoneWildcardId: MilestoneWildcardId $milestoneWildcardId: MilestoneWildcardId
$myReactionEmoji: String $myReactionEmoji: String
$releaseTag: [String!]
$releaseTagWildcardId: ReleaseTagWildcardId
$types: [IssueType!] $types: [IssueType!]
$not: NegatedIssueFilterInput $not: NegatedIssueFilterInput
$beforeCursor: String $beforeCursor: String
...@@ -66,6 +68,8 @@ query getIssues( ...@@ -66,6 +68,8 @@ query getIssues(
milestoneTitle: $milestoneTitle milestoneTitle: $milestoneTitle
milestoneWildcardId: $milestoneWildcardId milestoneWildcardId: $milestoneWildcardId
myReactionEmoji: $myReactionEmoji myReactionEmoji: $myReactionEmoji
releaseTag: $releaseTag
releaseTagWildcardId: $releaseTagWildcardId
types: $types types: $types
not: $not not: $not
before: $beforeCursor before: $beforeCursor
......
...@@ -10,6 +10,8 @@ query getIssuesCount( ...@@ -10,6 +10,8 @@ query getIssuesCount(
$milestoneTitle: [String] $milestoneTitle: [String]
$milestoneWildcardId: MilestoneWildcardId $milestoneWildcardId: MilestoneWildcardId
$myReactionEmoji: String $myReactionEmoji: String
$releaseTag: [String!]
$releaseTagWildcardId: ReleaseTagWildcardId
$types: [IssueType!] $types: [IssueType!]
$not: NegatedIssueFilterInput $not: NegatedIssueFilterInput
) { ) {
...@@ -78,6 +80,8 @@ query getIssuesCount( ...@@ -78,6 +80,8 @@ query getIssuesCount(
milestoneTitle: $milestoneTitle milestoneTitle: $milestoneTitle
milestoneWildcardId: $milestoneWildcardId milestoneWildcardId: $milestoneWildcardId
myReactionEmoji: $myReactionEmoji myReactionEmoji: $myReactionEmoji
releaseTag: $releaseTag
releaseTagWildcardId: $releaseTagWildcardId
types: $types types: $types
not: $not not: $not
) { ) {
...@@ -94,6 +98,8 @@ query getIssuesCount( ...@@ -94,6 +98,8 @@ query getIssuesCount(
milestoneTitle: $milestoneTitle milestoneTitle: $milestoneTitle
milestoneWildcardId: $milestoneWildcardId milestoneWildcardId: $milestoneWildcardId
myReactionEmoji: $myReactionEmoji myReactionEmoji: $myReactionEmoji
releaseTag: $releaseTag
releaseTagWildcardId: $releaseTagWildcardId
types: $types types: $types
not: $not not: $not
) { ) {
...@@ -110,6 +116,8 @@ query getIssuesCount( ...@@ -110,6 +116,8 @@ query getIssuesCount(
milestoneTitle: $milestoneTitle milestoneTitle: $milestoneTitle
milestoneWildcardId: $milestoneWildcardId milestoneWildcardId: $milestoneWildcardId
myReactionEmoji: $myReactionEmoji myReactionEmoji: $myReactionEmoji
releaseTag: $releaseTag
releaseTagWildcardId: $releaseTagWildcardId
types: $types types: $types
not: $not not: $not
) { ) {
......
...@@ -21,10 +21,13 @@ import { ...@@ -21,10 +21,13 @@ import {
RELATIVE_POSITION_ASC, RELATIVE_POSITION_ASC,
SPECIAL_FILTER, SPECIAL_FILTER,
SPECIAL_FILTER_VALUES, SPECIAL_FILTER_VALUES,
TITLE_ASC,
TITLE_DESC,
TOKEN_TYPE_ASSIGNEE, TOKEN_TYPE_ASSIGNEE,
TOKEN_TYPE_CONFIDENTIAL, TOKEN_TYPE_CONFIDENTIAL,
TOKEN_TYPE_ITERATION, TOKEN_TYPE_ITERATION,
TOKEN_TYPE_MILESTONE, TOKEN_TYPE_MILESTONE,
TOKEN_TYPE_RELEASE,
TOKEN_TYPE_TYPE, TOKEN_TYPE_TYPE,
UPDATED_ASC, UPDATED_ASC,
UPDATED_DESC, UPDATED_DESC,
...@@ -114,11 +117,19 @@ export const getSortOptions = (hasIssueWeightsFeature, hasBlockedIssuesFeature) ...@@ -114,11 +117,19 @@ export const getSortOptions = (hasIssueWeightsFeature, hasBlockedIssuesFeature)
descending: RELATIVE_POSITION_ASC, descending: RELATIVE_POSITION_ASC,
}, },
}, },
{
id: 9,
title: __('Title'),
sortDirection: {
ascending: TITLE_ASC,
descending: TITLE_DESC,
},
},
]; ];
if (hasIssueWeightsFeature) { if (hasIssueWeightsFeature) {
sortOptions.push({ sortOptions.push({
id: 9, id: sortOptions.length + 1,
title: __('Weight'), title: __('Weight'),
sortDirection: { sortDirection: {
ascending: WEIGHT_ASC, ascending: WEIGHT_ASC,
...@@ -129,7 +140,7 @@ export const getSortOptions = (hasIssueWeightsFeature, hasBlockedIssuesFeature) ...@@ -129,7 +140,7 @@ export const getSortOptions = (hasIssueWeightsFeature, hasBlockedIssuesFeature)
if (hasBlockedIssuesFeature) { if (hasBlockedIssuesFeature) {
sortOptions.push({ sortOptions.push({
id: 10, id: sortOptions.length + 1,
title: __('Blocking'), title: __('Blocking'),
sortDirection: { sortDirection: {
ascending: BLOCKING_ISSUES_DESC, ascending: BLOCKING_ISSUES_DESC,
...@@ -194,9 +205,10 @@ const getFilterType = (data, tokenType = '') => ...@@ -194,9 +205,10 @@ const getFilterType = (data, tokenType = '') =>
? SPECIAL_FILTER ? SPECIAL_FILTER
: NORMAL_FILTER; : NORMAL_FILTER;
const wildcardTokens = [TOKEN_TYPE_ITERATION, TOKEN_TYPE_MILESTONE, TOKEN_TYPE_RELEASE];
const isWildcardValue = (tokenType, value) => const isWildcardValue = (tokenType, value) =>
(tokenType === TOKEN_TYPE_ITERATION || tokenType === TOKEN_TYPE_MILESTONE) && wildcardTokens.includes(tokenType) && SPECIAL_FILTER_VALUES.includes(value);
SPECIAL_FILTER_VALUES.includes(value);
const requiresUpperCaseValue = (tokenType, value) => const requiresUpperCaseValue = (tokenType, value) =>
tokenType === TOKEN_TYPE_TYPE || isWildcardValue(tokenType, value); tokenType === TOKEN_TYPE_TYPE || isWildcardValue(tokenType, value);
......
...@@ -53,6 +53,7 @@ export const TOKEN_TITLE_ASSIGNEE = __('Assignee'); ...@@ -53,6 +53,7 @@ export const TOKEN_TITLE_ASSIGNEE = __('Assignee');
export const TOKEN_TITLE_MILESTONE = __('Milestone'); export const TOKEN_TITLE_MILESTONE = __('Milestone');
export const TOKEN_TITLE_LABEL = __('Label'); export const TOKEN_TITLE_LABEL = __('Label');
export const TOKEN_TITLE_TYPE = __('Type'); export const TOKEN_TITLE_TYPE = __('Type');
export const TOKEN_TITLE_RELEASE = __('Release');
export const TOKEN_TITLE_MY_REACTION = __('My-Reaction'); export const TOKEN_TITLE_MY_REACTION = __('My-Reaction');
export const TOKEN_TITLE_CONFIDENTIAL = __('Confidential'); export const TOKEN_TITLE_CONFIDENTIAL = __('Confidential');
export const TOKEN_TITLE_ITERATION = __('Iteration'); export const TOKEN_TITLE_ITERATION = __('Iteration');
......
<script>
import { GlFilteredSearchSuggestion } from '@gitlab/ui';
import createFlash from '~/flash';
import { __ } from '~/locale';
import BaseToken from '~/vue_shared/components/filtered_search_bar/tokens/base_token.vue';
import { DEFAULT_NONE_ANY } from '../constants';
export default {
components: {
BaseToken,
GlFilteredSearchSuggestion,
},
props: {
active: {
type: Boolean,
required: true,
},
config: {
type: Object,
required: true,
},
value: {
type: Object,
required: true,
},
},
data() {
return {
releases: this.config.initialReleases || [],
loading: false,
};
},
computed: {
defaultReleases() {
return this.config.defaultReleases || DEFAULT_NONE_ANY;
},
},
methods: {
getActiveRelease(releases, data) {
return releases.find((release) => release.tag.toLowerCase() === data.toLowerCase());
},
fetchReleases(searchTerm) {
this.loading = true;
this.config
.fetchReleases(searchTerm)
.then((response) => {
this.releases = response;
})
.catch(() => {
createFlash({ message: __('There was a problem fetching releases.') });
})
.finally(() => {
this.loading = false;
});
},
},
};
</script>
<template>
<base-token
:active="active"
:config="config"
:value="value"
:default-suggestions="defaultReleases"
:suggestions="releases"
:suggestions-loading="loading"
:get-active-token-value="getActiveRelease"
@fetch-suggestions="fetchReleases"
v-on="$listeners"
>
<template #view="{ viewTokenProps: { inputValue, activeTokenValue } }">
{{ activeTokenValue ? activeTokenValue.tag : inputValue }}
</template>
<template #suggestions-list="{ suggestions }">
<gl-filtered-search-suggestion
v-for="release in suggestions"
:key="release.id"
:value="release.tag"
>
{{ release.tag }}
</gl-filtered-search-suggestion>
</template>
</base-token>
</template>
...@@ -233,6 +233,7 @@ module IssuesHelper ...@@ -233,6 +233,7 @@ module IssuesHelper
new_issue_path: new_project_issue_path(project, issue: { milestone_id: finder.milestones.first.try(:id) }), new_issue_path: new_project_issue_path(project, issue: { milestone_id: finder.milestones.first.try(:id) }),
project_import_jira_path: project_import_jira_path(project), project_import_jira_path: project_import_jira_path(project),
quick_actions_help_path: help_page_path('user/project/quick_actions'), quick_actions_help_path: help_page_path('user/project/quick_actions'),
releases_path: project_releases_path(project, format: :json),
reset_path: new_issuable_address_project_path(project, issuable_type: 'issue'), reset_path: new_issuable_address_project_path(project, issuable_type: 'issue'),
show_new_issue_link: show_new_issue_link?(project).to_s show_new_issue_link: show_new_issue_link?(project).to_s
) )
......
...@@ -16,6 +16,8 @@ query getIssues( ...@@ -16,6 +16,8 @@ query getIssues(
$milestoneTitle: [String] $milestoneTitle: [String]
$milestoneWildcardId: MilestoneWildcardId $milestoneWildcardId: MilestoneWildcardId
$myReactionEmoji: String $myReactionEmoji: String
$releaseTag: [String!]
$releaseTagWildcardId: ReleaseTagWildcardId
$types: [IssueType!] $types: [IssueType!]
$epicId: String $epicId: String
$iterationId: [ID] $iterationId: [ID]
...@@ -79,6 +81,8 @@ query getIssues( ...@@ -79,6 +81,8 @@ query getIssues(
milestoneTitle: $milestoneTitle milestoneTitle: $milestoneTitle
milestoneWildcardId: $milestoneWildcardId milestoneWildcardId: $milestoneWildcardId
myReactionEmoji: $myReactionEmoji myReactionEmoji: $myReactionEmoji
releaseTag: $releaseTag
releaseTagWildcardId: $releaseTagWildcardId
types: $types types: $types
epicId: $epicId epicId: $epicId
iterationId: $iterationId iterationId: $iterationId
......
...@@ -10,6 +10,8 @@ query getIssuesCount( ...@@ -10,6 +10,8 @@ query getIssuesCount(
$milestoneTitle: [String] $milestoneTitle: [String]
$milestoneWildcardId: MilestoneWildcardId $milestoneWildcardId: MilestoneWildcardId
$myReactionEmoji: String $myReactionEmoji: String
$releaseTag: [String!]
$releaseTagWildcardId: ReleaseTagWildcardId
$types: [IssueType!] $types: [IssueType!]
$epicId: String $epicId: String
$iterationId: [ID] $iterationId: [ID]
...@@ -98,6 +100,8 @@ query getIssuesCount( ...@@ -98,6 +100,8 @@ query getIssuesCount(
milestoneTitle: $milestoneTitle milestoneTitle: $milestoneTitle
milestoneWildcardId: $milestoneWildcardId milestoneWildcardId: $milestoneWildcardId
myReactionEmoji: $myReactionEmoji myReactionEmoji: $myReactionEmoji
releaseTag: $releaseTag
releaseTagWildcardId: $releaseTagWildcardId
types: $types types: $types
epicId: $epicId epicId: $epicId
iterationId: $iterationId iterationId: $iterationId
...@@ -119,6 +123,8 @@ query getIssuesCount( ...@@ -119,6 +123,8 @@ query getIssuesCount(
milestoneTitle: $milestoneTitle milestoneTitle: $milestoneTitle
milestoneWildcardId: $milestoneWildcardId milestoneWildcardId: $milestoneWildcardId
myReactionEmoji: $myReactionEmoji myReactionEmoji: $myReactionEmoji
releaseTag: $releaseTag
releaseTagWildcardId: $releaseTagWildcardId
types: $types types: $types
epicId: $epicId epicId: $epicId
iterationId: $iterationId iterationId: $iterationId
...@@ -140,6 +146,8 @@ query getIssuesCount( ...@@ -140,6 +146,8 @@ query getIssuesCount(
milestoneTitle: $milestoneTitle milestoneTitle: $milestoneTitle
milestoneWildcardId: $milestoneWildcardId milestoneWildcardId: $milestoneWildcardId
myReactionEmoji: $myReactionEmoji myReactionEmoji: $myReactionEmoji
releaseTag: $releaseTag
releaseTagWildcardId: $releaseTagWildcardId
types: $types types: $types
epicId: $epicId epicId: $epicId
iterationId: $iterationId iterationId: $iterationId
......
...@@ -34507,6 +34507,9 @@ msgstr "" ...@@ -34507,6 +34507,9 @@ msgstr ""
msgid "There was a problem fetching recent projects." msgid "There was a problem fetching recent projects."
msgstr "" msgstr ""
msgid "There was a problem fetching releases."
msgstr ""
msgid "There was a problem fetching the job token scope value" msgid "There was a problem fetching the job token scope value"
msgstr "" msgstr ""
......
...@@ -37,6 +37,7 @@ import { ...@@ -37,6 +37,7 @@ import {
TOKEN_TYPE_LABEL, TOKEN_TYPE_LABEL,
TOKEN_TYPE_MILESTONE, TOKEN_TYPE_MILESTONE,
TOKEN_TYPE_MY_REACTION, TOKEN_TYPE_MY_REACTION,
TOKEN_TYPE_RELEASE,
TOKEN_TYPE_TYPE, TOKEN_TYPE_TYPE,
TOKEN_TYPE_WEIGHT, TOKEN_TYPE_WEIGHT,
urlSortParams, urlSortParams,
...@@ -581,6 +582,7 @@ describe('IssuesListApp component', () => { ...@@ -581,6 +582,7 @@ describe('IssuesListApp component', () => {
{ type: TOKEN_TYPE_MILESTONE }, { type: TOKEN_TYPE_MILESTONE },
{ type: TOKEN_TYPE_LABEL }, { type: TOKEN_TYPE_LABEL },
{ type: TOKEN_TYPE_TYPE }, { type: TOKEN_TYPE_TYPE },
{ type: TOKEN_TYPE_RELEASE },
{ type: TOKEN_TYPE_MY_REACTION }, { type: TOKEN_TYPE_MY_REACTION },
{ type: TOKEN_TYPE_CONFIDENTIAL }, { type: TOKEN_TYPE_CONFIDENTIAL },
{ type: TOKEN_TYPE_ITERATION }, { type: TOKEN_TYPE_ITERATION },
......
...@@ -95,12 +95,18 @@ export const locationSearch = [ ...@@ -95,12 +95,18 @@ export const locationSearch = [
'assignee_username[]=lisa', 'assignee_username[]=lisa',
'not[assignee_username][]=patty', 'not[assignee_username][]=patty',
'not[assignee_username][]=selma', 'not[assignee_username][]=selma',
'milestone_title=season+3',
'milestone_title=season+4', 'milestone_title=season+4',
'not[milestone_title]=season+20', 'not[milestone_title]=season+20',
'not[milestone_title]=season+30',
'label_name[]=cartoon', 'label_name[]=cartoon',
'label_name[]=tv', 'label_name[]=tv',
'not[label_name][]=live action', 'not[label_name][]=live action',
'not[label_name][]=drama', 'not[label_name][]=drama',
'release_tag=v3',
'release_tag=v4',
'not[release_tag]=v20',
'not[release_tag]=v30',
'type[]=issue', 'type[]=issue',
'type[]=feature', 'type[]=feature',
'not[type][]=bug', 'not[type][]=bug',
...@@ -109,7 +115,9 @@ export const locationSearch = [ ...@@ -109,7 +115,9 @@ export const locationSearch = [
'not[my_reaction_emoji]=thumbsdown', 'not[my_reaction_emoji]=thumbsdown',
'confidential=yes', 'confidential=yes',
'iteration_id=4', 'iteration_id=4',
'iteration_id=12',
'not[iteration_id]=20', 'not[iteration_id]=20',
'not[iteration_id]=42',
'epic_id=12', 'epic_id=12',
'not[epic_id]=34', 'not[epic_id]=34',
'weight=1', 'weight=1',
...@@ -122,6 +130,7 @@ export const locationSearchWithSpecialValues = [ ...@@ -122,6 +130,7 @@ export const locationSearchWithSpecialValues = [
'my_reaction_emoji=None', 'my_reaction_emoji=None',
'iteration_id=Current', 'iteration_id=Current',
'label_name[]=None', 'label_name[]=None',
'release_tag=None',
'milestone_title=Upcoming', 'milestone_title=Upcoming',
'epic_id=None', 'epic_id=None',
'weight=None', 'weight=None',
...@@ -134,12 +143,18 @@ export const filteredTokens = [ ...@@ -134,12 +143,18 @@ export const filteredTokens = [
{ type: 'assignee_username', value: { data: 'lisa', operator: OPERATOR_IS } }, { type: 'assignee_username', value: { data: 'lisa', operator: OPERATOR_IS } },
{ type: 'assignee_username', value: { data: 'patty', operator: OPERATOR_IS_NOT } }, { type: 'assignee_username', value: { data: 'patty', operator: OPERATOR_IS_NOT } },
{ type: 'assignee_username', value: { data: 'selma', operator: OPERATOR_IS_NOT } }, { type: 'assignee_username', value: { data: 'selma', operator: OPERATOR_IS_NOT } },
{ type: 'milestone', value: { data: 'season 3', operator: OPERATOR_IS } },
{ type: 'milestone', value: { data: 'season 4', operator: OPERATOR_IS } }, { type: 'milestone', value: { data: 'season 4', operator: OPERATOR_IS } },
{ type: 'milestone', value: { data: 'season 20', operator: OPERATOR_IS_NOT } }, { type: 'milestone', value: { data: 'season 20', operator: OPERATOR_IS_NOT } },
{ type: 'milestone', value: { data: 'season 30', operator: OPERATOR_IS_NOT } },
{ type: 'labels', value: { data: 'cartoon', operator: OPERATOR_IS } }, { type: 'labels', value: { data: 'cartoon', operator: OPERATOR_IS } },
{ type: 'labels', value: { data: 'tv', operator: OPERATOR_IS } }, { type: 'labels', value: { data: 'tv', operator: OPERATOR_IS } },
{ type: 'labels', value: { data: 'live action', operator: OPERATOR_IS_NOT } }, { type: 'labels', value: { data: 'live action', operator: OPERATOR_IS_NOT } },
{ type: 'labels', value: { data: 'drama', operator: OPERATOR_IS_NOT } }, { type: 'labels', value: { data: 'drama', operator: OPERATOR_IS_NOT } },
{ type: 'release', value: { data: 'v3', operator: OPERATOR_IS } },
{ type: 'release', value: { data: 'v4', operator: OPERATOR_IS } },
{ type: 'release', value: { data: 'v20', operator: OPERATOR_IS_NOT } },
{ type: 'release', value: { data: 'v30', operator: OPERATOR_IS_NOT } },
{ type: 'type', value: { data: 'issue', operator: OPERATOR_IS } }, { type: 'type', value: { data: 'issue', operator: OPERATOR_IS } },
{ type: 'type', value: { data: 'feature', operator: OPERATOR_IS } }, { type: 'type', value: { data: 'feature', operator: OPERATOR_IS } },
{ type: 'type', value: { data: 'bug', operator: OPERATOR_IS_NOT } }, { type: 'type', value: { data: 'bug', operator: OPERATOR_IS_NOT } },
...@@ -148,7 +163,9 @@ export const filteredTokens = [ ...@@ -148,7 +163,9 @@ export const filteredTokens = [
{ type: 'my_reaction_emoji', value: { data: 'thumbsdown', operator: OPERATOR_IS_NOT } }, { type: 'my_reaction_emoji', value: { data: 'thumbsdown', operator: OPERATOR_IS_NOT } },
{ type: 'confidential', value: { data: 'yes', operator: OPERATOR_IS } }, { type: 'confidential', value: { data: 'yes', operator: OPERATOR_IS } },
{ type: 'iteration', value: { data: '4', operator: OPERATOR_IS } }, { type: 'iteration', value: { data: '4', operator: OPERATOR_IS } },
{ type: 'iteration', value: { data: '12', operator: OPERATOR_IS } },
{ type: 'iteration', value: { data: '20', operator: OPERATOR_IS_NOT } }, { type: 'iteration', value: { data: '20', operator: OPERATOR_IS_NOT } },
{ type: 'iteration', value: { data: '42', operator: OPERATOR_IS_NOT } },
{ type: 'epic_id', value: { data: '12', operator: OPERATOR_IS } }, { type: 'epic_id', value: { data: '12', operator: OPERATOR_IS } },
{ type: 'epic_id', value: { data: '34', operator: OPERATOR_IS_NOT } }, { type: 'epic_id', value: { data: '34', operator: OPERATOR_IS_NOT } },
{ type: 'weight', value: { data: '1', operator: OPERATOR_IS } }, { type: 'weight', value: { data: '1', operator: OPERATOR_IS } },
...@@ -163,6 +180,7 @@ export const filteredTokensWithSpecialValues = [ ...@@ -163,6 +180,7 @@ export const filteredTokensWithSpecialValues = [
{ type: 'my_reaction_emoji', value: { data: 'None', operator: OPERATOR_IS } }, { type: 'my_reaction_emoji', value: { data: 'None', operator: OPERATOR_IS } },
{ type: 'iteration', value: { data: 'Current', operator: OPERATOR_IS } }, { type: 'iteration', value: { data: 'Current', operator: OPERATOR_IS } },
{ type: 'labels', value: { data: 'None', operator: OPERATOR_IS } }, { type: 'labels', value: { data: 'None', operator: OPERATOR_IS } },
{ type: 'release', value: { data: 'None', operator: OPERATOR_IS } },
{ type: 'milestone', value: { data: 'Upcoming', operator: OPERATOR_IS } }, { type: 'milestone', value: { data: 'Upcoming', operator: OPERATOR_IS } },
{ type: 'epic_id', value: { data: 'None', operator: OPERATOR_IS } }, { type: 'epic_id', value: { data: 'None', operator: OPERATOR_IS } },
{ type: 'weight', value: { data: 'None', operator: OPERATOR_IS } }, { type: 'weight', value: { data: 'None', operator: OPERATOR_IS } },
...@@ -171,22 +189,24 @@ export const filteredTokensWithSpecialValues = [ ...@@ -171,22 +189,24 @@ export const filteredTokensWithSpecialValues = [
export const apiParams = { export const apiParams = {
authorUsername: 'homer', authorUsername: 'homer',
assigneeUsernames: ['bart', 'lisa'], assigneeUsernames: ['bart', 'lisa'],
milestoneTitle: 'season 4', milestoneTitle: ['season 3', 'season 4'],
labelName: ['cartoon', 'tv'], labelName: ['cartoon', 'tv'],
releaseTag: ['v3', 'v4'],
types: ['ISSUE', 'FEATURE'], types: ['ISSUE', 'FEATURE'],
myReactionEmoji: 'thumbsup', myReactionEmoji: 'thumbsup',
confidential: true, confidential: true,
iterationId: '4', iterationId: ['4', '12'],
epicId: '12', epicId: '12',
weight: '1', weight: '1',
not: { not: {
authorUsername: 'marge', authorUsername: 'marge',
assigneeUsernames: ['patty', 'selma'], assigneeUsernames: ['patty', 'selma'],
milestoneTitle: 'season 20', milestoneTitle: ['season 20', 'season 30'],
labelName: ['live action', 'drama'], labelName: ['live action', 'drama'],
releaseTag: ['v20', 'v30'],
types: ['BUG', 'INCIDENT'], types: ['BUG', 'INCIDENT'],
myReactionEmoji: 'thumbsdown', myReactionEmoji: 'thumbsdown',
iterationId: '20', iterationId: ['20', '42'],
epicId: '34', epicId: '34',
weight: '3', weight: '3',
}, },
...@@ -197,6 +217,7 @@ export const apiParamsWithSpecialValues = { ...@@ -197,6 +217,7 @@ export const apiParamsWithSpecialValues = {
assigneeUsernames: 'bart', assigneeUsernames: 'bart',
labelName: 'None', labelName: 'None',
myReactionEmoji: 'None', myReactionEmoji: 'None',
releaseTagWildcardId: 'NONE',
iterationWildcardId: 'CURRENT', iterationWildcardId: 'CURRENT',
milestoneWildcardId: 'UPCOMING', milestoneWildcardId: 'UPCOMING',
epicId: 'None', epicId: 'None',
...@@ -208,17 +229,19 @@ export const urlParams = { ...@@ -208,17 +229,19 @@ export const urlParams = {
'not[author_username]': 'marge', 'not[author_username]': 'marge',
'assignee_username[]': ['bart', 'lisa'], 'assignee_username[]': ['bart', 'lisa'],
'not[assignee_username][]': ['patty', 'selma'], 'not[assignee_username][]': ['patty', 'selma'],
milestone_title: 'season 4', milestone_title: ['season 3', 'season 4'],
'not[milestone_title]': 'season 20', 'not[milestone_title]': ['season 20', 'season 30'],
'label_name[]': ['cartoon', 'tv'], 'label_name[]': ['cartoon', 'tv'],
'not[label_name][]': ['live action', 'drama'], 'not[label_name][]': ['live action', 'drama'],
release_tag: ['v3', 'v4'],
'not[release_tag]': ['v20', 'v30'],
'type[]': ['issue', 'feature'], 'type[]': ['issue', 'feature'],
'not[type][]': ['bug', 'incident'], 'not[type][]': ['bug', 'incident'],
my_reaction_emoji: 'thumbsup', my_reaction_emoji: 'thumbsup',
'not[my_reaction_emoji]': 'thumbsdown', 'not[my_reaction_emoji]': 'thumbsdown',
confidential: 'yes', confidential: 'yes',
iteration_id: '4', iteration_id: ['4', '12'],
'not[iteration_id]': '20', 'not[iteration_id]': ['20', '42'],
epic_id: '12', epic_id: '12',
'not[epic_id]': '34', 'not[epic_id]': '34',
weight: '1', weight: '1',
...@@ -229,6 +252,7 @@ export const urlParamsWithSpecialValues = { ...@@ -229,6 +252,7 @@ export const urlParamsWithSpecialValues = {
assignee_id: '123', assignee_id: '123',
'assignee_username[]': 'bart', 'assignee_username[]': 'bart',
'label_name[]': 'None', 'label_name[]': 'None',
release_tag: 'None',
my_reaction_emoji: 'None', my_reaction_emoji: 'None',
iteration_id: 'Current', iteration_id: 'Current',
milestone_title: 'Upcoming', milestone_title: 'Upcoming',
......
...@@ -58,10 +58,10 @@ describe('getDueDateValue', () => { ...@@ -58,10 +58,10 @@ describe('getDueDateValue', () => {
describe('getSortOptions', () => { describe('getSortOptions', () => {
describe.each` describe.each`
hasIssueWeightsFeature | hasBlockedIssuesFeature | length | containsWeight | containsBlocking hasIssueWeightsFeature | hasBlockedIssuesFeature | length | containsWeight | containsBlocking
${false} | ${false} | ${8} | ${false} | ${false} ${false} | ${false} | ${9} | ${false} | ${false}
${true} | ${false} | ${9} | ${true} | ${false} ${true} | ${false} | ${10} | ${true} | ${false}
${false} | ${true} | ${9} | ${false} | ${true} ${false} | ${true} | ${10} | ${false} | ${true}
${true} | ${true} | ${10} | ${true} | ${true} ${true} | ${true} | ${11} | ${true} | ${true}
`( `(
'when hasIssueWeightsFeature=$hasIssueWeightsFeature and hasBlockedIssuesFeature=$hasBlockedIssuesFeature', 'when hasIssueWeightsFeature=$hasIssueWeightsFeature and hasBlockedIssuesFeature=$hasBlockedIssuesFeature',
({ ({
......
...@@ -9,6 +9,7 @@ import EpicToken from '~/vue_shared/components/filtered_search_bar/tokens/epic_t ...@@ -9,6 +9,7 @@ import EpicToken from '~/vue_shared/components/filtered_search_bar/tokens/epic_t
import IterationToken from '~/vue_shared/components/filtered_search_bar/tokens/iteration_token.vue'; import IterationToken from '~/vue_shared/components/filtered_search_bar/tokens/iteration_token.vue';
import LabelToken from '~/vue_shared/components/filtered_search_bar/tokens/label_token.vue'; import LabelToken from '~/vue_shared/components/filtered_search_bar/tokens/label_token.vue';
import MilestoneToken from '~/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue'; import MilestoneToken from '~/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue';
import ReleaseToken from '~/vue_shared/components/filtered_search_bar/tokens/release_token.vue';
import WeightToken from '~/vue_shared/components/filtered_search_bar/tokens/weight_token.vue'; import WeightToken from '~/vue_shared/components/filtered_search_bar/tokens/weight_token.vue';
export const mockAuthor1 = { export const mockAuthor1 = {
...@@ -132,6 +133,14 @@ export const mockMilestoneToken = { ...@@ -132,6 +133,14 @@ export const mockMilestoneToken = {
fetchMilestones: () => Promise.resolve({ data: mockMilestones }), fetchMilestones: () => Promise.resolve({ data: mockMilestones }),
}; };
export const mockReleaseToken = {
type: 'release',
icon: 'rocket',
title: 'Release',
token: ReleaseToken,
fetchReleases: () => Promise.resolve(),
};
export const mockEpicToken = { export const mockEpicToken = {
type: 'epic_iid', type: 'epic_iid',
icon: 'clock', icon: 'clock',
......
import { GlFilteredSearchToken, GlFilteredSearchTokenSegment } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import waitForPromises from 'helpers/wait_for_promises';
import createFlash from '~/flash';
import ReleaseToken from '~/vue_shared/components/filtered_search_bar/tokens/release_token.vue';
import { mockReleaseToken } from '../mock_data';
jest.mock('~/flash');
describe('ReleaseToken', () => {
const id = 123;
let wrapper;
const createComponent = ({ config = mockReleaseToken, value = { data: '' } } = {}) =>
mount(ReleaseToken, {
propsData: {
active: false,
config,
value,
},
provide: {
portalName: 'fake target',
alignSuggestions: function fakeAlignSuggestions() {},
suggestionsListClass: () => 'custom-class',
},
});
afterEach(() => {
wrapper.destroy();
});
it('renders release value', async () => {
wrapper = createComponent({ value: { data: id } });
await wrapper.vm.$nextTick();
const tokenSegments = wrapper.findAllComponents(GlFilteredSearchTokenSegment);
expect(tokenSegments).toHaveLength(3); // `Release` `=` `v1`
expect(tokenSegments.at(2).text()).toBe(id.toString());
});
it('fetches initial values', () => {
const fetchReleasesSpy = jest.fn().mockResolvedValue();
wrapper = createComponent({
config: { ...mockReleaseToken, fetchReleases: fetchReleasesSpy },
value: { data: id },
});
expect(fetchReleasesSpy).toHaveBeenCalledWith(id);
});
it('fetches releases on user input', () => {
const search = 'hello';
const fetchReleasesSpy = jest.fn().mockResolvedValue();
wrapper = createComponent({
config: { ...mockReleaseToken, fetchReleases: fetchReleasesSpy },
});
wrapper.findComponent(GlFilteredSearchToken).vm.$emit('input', { data: search });
expect(fetchReleasesSpy).toHaveBeenCalledWith(search);
});
it('renders error message when request fails', async () => {
const fetchReleasesSpy = jest.fn().mockRejectedValue();
wrapper = createComponent({
config: { ...mockReleaseToken, fetchReleases: fetchReleasesSpy },
});
await waitForPromises();
expect(createFlash).toHaveBeenCalledWith({
message: 'There was a problem fetching releases.',
});
});
});
...@@ -326,6 +326,7 @@ RSpec.describe IssuesHelper do ...@@ -326,6 +326,7 @@ RSpec.describe IssuesHelper do
new_issue_path: new_project_issue_path(project, issue: { milestone_id: finder.milestones.first.id }), new_issue_path: new_project_issue_path(project, issue: { milestone_id: finder.milestones.first.id }),
project_import_jira_path: project_import_jira_path(project), project_import_jira_path: project_import_jira_path(project),
quick_actions_help_path: help_page_path('user/project/quick_actions'), quick_actions_help_path: help_page_path('user/project/quick_actions'),
releases_path: project_releases_path(project, format: :json),
reset_path: new_issuable_address_project_path(project, issuable_type: 'issue'), reset_path: new_issuable_address_project_path(project, issuable_type: 'issue'),
rss_path: '#', rss_path: '#',
show_new_issue_link: 'true', show_new_issue_link: 'true',
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment