diff --git a/app/assets/javascripts/merge_requests/list/components/merge_requests_list_app.vue b/app/assets/javascripts/merge_requests/list/components/merge_requests_list_app.vue index 482578217a09485efc567d6d28511b2a1a1bb5f2..22688bcda0dacf7463f7ce5898c70b43d4e2947b 100644 --- a/app/assets/javascripts/merge_requests/list/components/merge_requests_list_app.vue +++ b/app/assets/javascripts/merge_requests/list/components/merge_requests_list_app.vue @@ -15,6 +15,7 @@ import IssuableList from '~/vue_shared/issuable/list/components/issuable_list_ro import { DEFAULT_PAGE_SIZE, mergeRequestListTabs } from '~/vue_shared/issuable/list/constants'; import { OPERATORS_IS, + OPERATORS_IS_NOT, TOKEN_TITLE_AUTHOR, TOKEN_TYPE_AUTHOR, TOKEN_TITLE_DRAFT, @@ -29,6 +30,8 @@ import { TOKEN_TYPE_REVIEWER, TOKEN_TITLE_MILESTONE, TOKEN_TYPE_MILESTONE, + TOKEN_TITLE_LABEL, + TOKEN_TYPE_LABEL, } from '~/vue_shared/components/filtered_search_bar/constants'; import { convertToApiParams, @@ -53,6 +56,7 @@ import setSortPreferenceMutation from '~/issues/list/queries/set_sort_preference import { i18n } from '../constants'; import getMergeRequestsQuery from '../queries/get_merge_requests.query.graphql'; import getMergeRequestsCountsQuery from '../queries/get_merge_requests_counts.query.graphql'; +import searchLabelsQuery from '../queries/search_labels.query.graphql'; import MergeRequestStatistics from './merge_request_statistics.vue'; import MergeRequestMoreActionsDropdown from './more_actions_dropdown.vue'; @@ -61,6 +65,8 @@ const BranchToken = () => import('~/vue_shared/components/filtered_search_bar/tokens/branch_token.vue'); const MilestoneToken = () => import('~/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue'); +const LabelToken = () => + import('~/vue_shared/components/filtered_search_bar/tokens/label_token.vue'); export default { i18n, @@ -75,6 +81,7 @@ export default { inject: [ 'fullPath', 'hasAnyMergeRequests', + 'hasScopedLabelsFeature', 'initialSort', 'isPublicVisibilityRestricted', 'isSignedIn', @@ -269,6 +276,15 @@ export default { isProject: true, fetchBranches: this.fetchBranches, }, + { + type: TOKEN_TYPE_LABEL, + title: TOKEN_TITLE_LABEL, + icon: 'labels', + token: LabelToken, + operators: OPERATORS_IS_NOT, + fetchLabels: this.fetchLabels, + recentSuggestionsStorageKey: `${this.fullPath}-merge_requests-recent-tokens-label`, + }, ]; }, showPaginationControls() { @@ -330,6 +346,23 @@ export default { }); }); }, + fetchLabelsWithFetchPolicy(search, fetchPolicy = fetchPolicies.CACHE_FIRST) { + return this.$apollo + .query({ + query: searchLabelsQuery, + variables: { fullPath: this.fullPath, search }, + fetchPolicy, + }) + .then(({ data }) => data.project.labels.nodes) + .then((labels) => + // TODO remove once we can search by title-only on the backend + // https://gitlab.com/gitlab-org/gitlab/-/issues/346353 + labels.filter((label) => label.title.toLowerCase().includes(search.toLowerCase())), + ); + }, + fetchLabels(search) { + return this.fetchLabelsWithFetchPolicy(search); + }, getStatus(mergeRequest) { if (mergeRequest.state === STATUS_CLOSED) { return this.$options.i18n.closed; @@ -428,6 +461,7 @@ export default { :namespace="fullPath" recent-searches-storage-key="merge_requests" :search-tokens="searchTokens" + :has-scoped-labels-feature="hasScopedLabelsFeature" :initial-filter-value="filterTokens" :sort-options="sortOptions" :initial-sort-by="sortKey" diff --git a/app/assets/javascripts/merge_requests/list/index.js b/app/assets/javascripts/merge_requests/list/index.js index 320d1f29fab99d00bc2698723ca89516d5bbf932..bccf220c8dda91ebfc9797fed543c0c7292360ca 100644 --- a/app/assets/javascripts/merge_requests/list/index.js +++ b/app/assets/javascripts/merge_requests/list/index.js @@ -19,6 +19,7 @@ export async function mountMergeRequestListsApp() { const { fullPath, hasAnyMergeRequests, + hasScopedLabelsFeature, initialSort, isPublicVisibilityRestricted, isSignedIn, @@ -45,6 +46,7 @@ export async function mountMergeRequestListsApp() { provide: { fullPath, hasAnyMergeRequests: parseBoolean(hasAnyMergeRequests), + hasScopedLabelsFeature: parseBoolean(hasScopedLabelsFeature), initialSort, isPublicVisibilityRestricted: parseBoolean(isPublicVisibilityRestricted), isSignedIn: parseBoolean(isSignedIn), diff --git a/app/assets/javascripts/merge_requests/list/queries/get_merge_requests.query.graphql b/app/assets/javascripts/merge_requests/list/queries/get_merge_requests.query.graphql index ea81f5ea2baa0d239c61992340ad323d008bc201..1bef7386a5d5fbf3e49d4fbf2d841bce09f2dd3e 100644 --- a/app/assets/javascripts/merge_requests/list/queries/get_merge_requests.query.graphql +++ b/app/assets/javascripts/merge_requests/list/queries/get_merge_requests.query.graphql @@ -13,6 +13,7 @@ query getMergeRequests( $reviewerWildcardId: ReviewerWildcardId $authorUsername: String $draft: Boolean + $labelName: [String!] $milestoneTitle: String $milestoneWildcardId: MilestoneWildcardId $sourceBranches: [String!] @@ -33,6 +34,7 @@ query getMergeRequests( reviewerWildcardId: $reviewerWildcardId authorUsername: $authorUsername draft: $draft + labelName: $labelName milestoneTitle: $milestoneTitle milestoneWildcardId: $milestoneWildcardId sourceBranches: $sourceBranches diff --git a/app/assets/javascripts/merge_requests/list/queries/search_labels.query.graphql b/app/assets/javascripts/merge_requests/list/queries/search_labels.query.graphql new file mode 100644 index 0000000000000000000000000000000000000000..ec4ff5119a5f0471fcdf00e0ba275a27eb367c2f --- /dev/null +++ b/app/assets/javascripts/merge_requests/list/queries/search_labels.query.graphql @@ -0,0 +1,15 @@ +query searchLabelsForMergeRequests($fullPath: ID!, $search: String) { + project(fullPath: $fullPath) @persist { + id + labels(searchTerm: $search, includeAncestorGroups: true) { + __persist + nodes { + __persist + id + color + textColor + title + } + } + } +} diff --git a/spec/frontend/merge_requests/list/components/merge_requests_list_app_spec.js b/spec/frontend/merge_requests/list/components/merge_requests_list_app_spec.js index 295631bfbcdd2353dfd50c53382f8dfe685edff2..8cd993169648f0c7324ac97eaf8fa4693ea58f2e 100644 --- a/spec/frontend/merge_requests/list/components/merge_requests_list_app_spec.js +++ b/spec/frontend/merge_requests/list/components/merge_requests_list_app_spec.js @@ -10,6 +10,7 @@ import { convertToGraphQLId } from '~/graphql_shared/utils'; import { TOKEN_TYPE_AUTHOR, TOKEN_TYPE_DRAFT, + TOKEN_TYPE_LABEL, TOKEN_TYPE_MILESTONE, TOKEN_TYPE_SOURCE_BRANCH, TOKEN_TYPE_TARGET_BRANCH, @@ -39,6 +40,7 @@ function createComponent({ provide = {} } = {}) { provide: { fullPath: 'gitlab-org/gitlab', hasAnyMergeRequests: true, + hasScopedLabelsFeature: false, initialSort: '', isPublicVisibilityRestricted: false, isSignedIn: true, @@ -117,6 +119,7 @@ describe('Merge requests list app', () => { { type: TOKEN_TYPE_MILESTONE }, { type: TOKEN_TYPE_TARGET_BRANCH }, { type: TOKEN_TYPE_SOURCE_BRANCH }, + { type: TOKEN_TYPE_LABEL }, ]); }); }); @@ -126,6 +129,7 @@ describe('Merge requests list app', () => { assignee_username: 'bob', reviewer_username: 'bill', draft: 'yes', + 'label_name[]': 'fluff', milestone_title: 'milestone', 'target_branches[]': 'branch-a', 'source_branches[]': 'branch-b', @@ -163,6 +167,7 @@ describe('Merge requests list app', () => { { type: TOKEN_TYPE_MILESTONE }, { type: TOKEN_TYPE_TARGET_BRANCH }, { type: TOKEN_TYPE_SOURCE_BRANCH }, + { type: TOKEN_TYPE_LABEL }, ]); }); @@ -171,6 +176,7 @@ describe('Merge requests list app', () => { { type: TOKEN_TYPE_ASSIGNEE }, { type: TOKEN_TYPE_REVIEWER }, { type: TOKEN_TYPE_DRAFT }, + { type: TOKEN_TYPE_LABEL }, { type: TOKEN_TYPE_MILESTONE }, { type: TOKEN_TYPE_TARGET_BRANCH }, { type: TOKEN_TYPE_SOURCE_BRANCH },