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 c5949890d57022ddb82e338c591bf21a84716d0f..5d9136718719d422e56911fa63155e4e0020056e 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 @@ -6,8 +6,15 @@ import { fetchPolicies } from '~/lib/graphql'; import { isPositiveInteger } from '~/lib/utils/number_utils'; import { scrollUp } from '~/lib/utils/scroll_utils'; import { getParameterByName } from '~/lib/utils/url_utility'; +import { TYPENAME_USER } from '~/graphql_shared/constants'; +import { convertToGraphQLId } from '~/graphql_shared/utils'; import IssuableList from '~/vue_shared/issuable/list/components/issuable_list_root.vue'; import { DEFAULT_PAGE_SIZE, mergeRequestListTabs } from '~/vue_shared/issuable/list/constants'; +import { + OPERATORS_IS, + TOKEN_TITLE_AUTHOR, + TOKEN_TYPE_AUTHOR, +} from '~/vue_shared/components/filtered_search_bar/constants'; import { convertToApiParams, convertToSearchQuery, @@ -33,6 +40,8 @@ import getMergeRequestsQuery from '../queries/get_merge_requests.query.graphql'; import getMergeRequestsCountsQuery from '../queries/get_merge_requests_counts.query.graphql'; import MergeRequestStatistics from './merge_request_statistics.vue'; +const UserToken = () => import('~/vue_shared/components/filtered_search_bar/tokens/user_token.vue'); + export default { i18n, mergeRequestListTabs, @@ -68,7 +77,7 @@ export default { return this.queryVariables; }, update(data) { - return data.project.mergeRequests.nodes ?? []; + return data.project.mergeRequests?.nodes ?? []; }, fetchPolicy: fetchPolicies.CACHE_AND_NETWORK, nextFetchPolicy: fetchPolicies.CACHE_FIRST, @@ -77,7 +86,7 @@ export default { if (!data) { return; } - this.pageInfo = data.project.mergeRequests.pageInfo ?? {}; + this.pageInfo = data.project.mergeRequests?.pageInfo ?? {}; }, error(error) { this.mergeRequestsError = this.$options.i18n.errorFetchingMergeRequests; @@ -135,7 +144,33 @@ export default { return convertToSearchQuery(this.filterTokens); }, searchTokens() { - return []; + const preloadedUsers = []; + + if (gon.current_user_id) { + preloadedUsers.push({ + id: convertToGraphQLId(TYPENAME_USER, gon.current_user_id), + name: gon.current_user_fullname, + username: gon.current_username, + avatar_url: gon.current_user_avatar_url, + }); + } + + return [ + { + type: TOKEN_TYPE_AUTHOR, + title: TOKEN_TITLE_AUTHOR, + icon: 'pencil', + token: UserToken, + dataType: 'user', + defaultUsers: [], + operators: OPERATORS_IS, + fullPath: this.fullPath, + isProject: true, + recentSuggestionsStorageKey: `${this.fullPath}-merge-requests-recent-tokens-author`, + preloadedUsers, + multiselect: false, + }, + ]; }, showPaginationControls() { return ( @@ -222,6 +257,12 @@ export default { this.$router.push({ query: this.urlParams }); }, + handleFilter(tokens) { + this.filterTokens = tokens; + this.pageParams = getInitialPageParams(this.pageSize); + + this.$router.push({ query: this.urlParams }); + }, handleSort(sortKey) { if (this.sortKey === sortKey) { return; @@ -251,14 +292,6 @@ export default { Sentry.captureException(error); }); }, - handlePageSizeChange(newPageSize) { - const pageParam = getParameterByName(PARAM_LAST_PAGE_SIZE) ? 'lastPageSize' : 'firstPageSize'; - this.pageParams[pageParam] = newPageSize; - this.pageSize = newPageSize; - scrollUp(); - - this.$router.push({ query: this.urlParams }); - }, updateData(sort) { const firstPageSize = getParameterByName(PARAM_FIRST_PAGE_SIZE); const lastPageSize = getParameterByName(PARAM_LAST_PAGE_SIZE); @@ -304,7 +337,7 @@ export default { @next-page="handleNextPage" @previous-page="handlePreviousPage" @sort="handleSort" - @page-size-change="handlePageSizeChange" + @filter="handleFilter" > <template #status="{ issuable = {} }"> {{ getStatus(issuable) }} 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 46359dcdb113eb69d78ae60b00c10650bf83d1d4..eac3f870e7bf2a010cfe03ca0618ed6a31f1eebb 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 @@ -7,6 +7,7 @@ query getMergeRequests( $fullPath: ID! $sort: MergeRequestSort $state: MergeRequestState + $authorUsername: String $beforeCursor: String $afterCursor: String $firstPageSize: Int @@ -17,6 +18,7 @@ query getMergeRequests( mergeRequests( sort: $sort state: $state + authorUsername: $authorUsername before: $beforeCursor after: $afterCursor first: $firstPageSize 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 945fa273f162ea11daca45f192e771f6ce1e173d..e8787b5b4eadf70027ecfabfbec1659124c725b8 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 @@ -4,6 +4,9 @@ import { shallowMount } from '@vue/test-utils'; import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; import { getCountsQueryResponse, getQueryResponse } from 'jest/merge_requests/list/mock_data'; +import { TYPENAME_USER } from '~/graphql_shared/constants'; +import { convertToGraphQLId } from '~/graphql_shared/utils'; +import { TOKEN_TYPE_AUTHOR } from '~/vue_shared/components/filtered_search_bar/constants'; import { mergeRequestListTabs } from '~/vue_shared/issuable/list/constants'; import { getSortOptions } from '~/issues/list/utils'; import MergeRequestsListApp from '~/merge_requests/list/components/merge_requests_list_app.vue'; @@ -72,4 +75,56 @@ describe('Merge requests list app', () => { hasNextPage: true, }); }); + + describe('tokens', () => { + const mockCurrentUser = { + id: 1, + name: 'Administrator', + username: 'root', + avatar_url: 'avatar/url', + }; + + describe('when user is signed out', () => { + beforeEach(async () => { + createComponent(); + + await waitForPromises(); + }); + + it('does not have preloaded users when gon.current_user_id does not exist', () => { + expect(findIssuableList().props('searchTokens')).toMatchObject([ + { type: TOKEN_TYPE_AUTHOR, preloadedUsers: [] }, + ]); + }); + }); + + describe('when all tokens are available', () => { + beforeEach(async () => { + window.gon = { + current_user_id: mockCurrentUser.id, + current_user_fullname: mockCurrentUser.name, + current_username: mockCurrentUser.username, + current_user_avatar_url: mockCurrentUser.avatar_url, + }; + + createComponent(); + + await waitForPromises(); + }); + + afterEach(() => { + window.gon = {}; + }); + + it('renders all tokens', () => { + const preloadedUsers = [ + { ...mockCurrentUser, id: convertToGraphQLId(TYPENAME_USER, mockCurrentUser.id) }, + ]; + + expect(findIssuableList().props('searchTokens')).toMatchObject([ + { type: TOKEN_TYPE_AUTHOR, preloadedUsers }, + ]); + }); + }); + }); });