diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-10-20 11:43:02 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-10-20 11:43:02 +0300 |
commit | d9ab72d6080f594d0b3cae15f14b3ef2c6c638cb (patch) | |
tree | 2341ef426af70ad1e289c38036737e04b0aa5007 /app/assets/javascripts/vue_shared/components/filtered_search_bar | |
parent | d6e514dd13db8947884cd58fe2a9c2a063400a9b (diff) |
Add latest changes from gitlab-org/gitlab@14-4-stable-eev14.4.0-rc42
Diffstat (limited to 'app/assets/javascripts/vue_shared/components/filtered_search_bar')
4 files changed, 110 insertions, 88 deletions
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/queries/epic.fragment.graphql b/app/assets/javascripts/vue_shared/components/filtered_search_bar/queries/epic.fragment.graphql new file mode 100644 index 00000000000..9e9bda8ad3e --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/queries/epic.fragment.graphql @@ -0,0 +1,15 @@ +fragment EpicNode on Epic { + id + iid + group { + fullPath + } + title + state + reference + referencePath: reference(full: true) + webPath + webUrl + createdAt + closedAt +} diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/queries/search_epics.query.graphql b/app/assets/javascripts/vue_shared/components/filtered_search_bar/queries/search_epics.query.graphql new file mode 100644 index 00000000000..4bb4b586fc9 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/queries/search_epics.query.graphql @@ -0,0 +1,16 @@ +#import "./epic.fragment.graphql" + +query searchEpics($fullPath: ID!, $search: String, $state: EpicState) { + group(fullPath: $fullPath) { + epics( + search: $search + state: $state + includeAncestorGroups: true + includeDescendantGroups: false + ) { + nodes { + ...EpicNode + } + } + } +} diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/base_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/base_token.vue index d1326e96794..cee7c40aa83 100644 --- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/base_token.vue +++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/base_token.vue @@ -67,6 +67,11 @@ export default { required: false, default: 'id', }, + searchBy: { + type: String, + required: false, + default: undefined, + }, }, data() { return { @@ -112,16 +117,18 @@ export default { ); }, showDefaultSuggestions() { - return this.availableDefaultSuggestions.length; + return this.availableDefaultSuggestions.length > 0; }, showRecentSuggestions() { - return this.isRecentSuggestionsEnabled && this.recentSuggestions.length && !this.searchKey; + return ( + this.isRecentSuggestionsEnabled && this.recentSuggestions.length > 0 && !this.searchKey + ); }, showPreloadedSuggestions() { - return this.preloadedSuggestions.length && !this.searchKey; + return this.preloadedSuggestions.length > 0 && !this.searchKey; }, showAvailableSuggestions() { - return this.availableSuggestions.length; + return this.availableSuggestions.length > 0; }, showSuggestions() { // These conditions must match the template under `#suggestions` slot @@ -134,13 +141,19 @@ export default { this.showAvailableSuggestions ); }, + searchTerm() { + return this.searchBy && this.activeTokenValue + ? this.activeTokenValue[this.searchBy] + : undefined; + }, }, watch: { active: { immediate: true, handler(newValue) { if (!newValue && !this.suggestions.length) { - this.$emit('fetch-suggestions', this.value.data); + const search = this.searchTerm ? this.searchTerm : this.value.data; + this.$emit('fetch-suggestions', search); } }, }, @@ -148,8 +161,10 @@ export default { methods: { handleInput: debounce(function debouncedSearch({ data }) { this.searchKey = data; - if (!this.suggestionsLoading) { - this.$emit('fetch-suggestions', data); + + if (!this.suggestionsLoading && !this.activeTokenValue) { + const search = this.searchTerm ? this.searchTerm : data; + this.$emit('fetch-suggestions', search); } }, DEBOUNCE_DELAY), handleTokenValueSelected(activeTokenValue) { diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/epic_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/epic_token.vue index 9f68308808e..9c2f5306654 100644 --- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/epic_token.vue +++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/epic_token.vue @@ -1,22 +1,19 @@ <script> -import { - GlDropdownDivider, - GlFilteredSearchSuggestion, - GlFilteredSearchToken, - GlLoadingIcon, -} from '@gitlab/ui'; -import { debounce } from 'lodash'; +import { GlFilteredSearchSuggestion } from '@gitlab/ui'; import createFlash from '~/flash'; +import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import { __ } from '~/locale'; -import { DEBOUNCE_DELAY, DEFAULT_NONE_ANY, FILTER_NONE_ANY, OPERATOR_IS_NOT } from '../constants'; +import { DEFAULT_NONE_ANY, FILTER_NONE_ANY, OPERATOR_IS_NOT } from '../constants'; +import searchEpicsQuery from '../queries/search_epics.query.graphql'; + +import BaseToken from './base_token.vue'; export default { - separator: '::&', + prefix: '&', + separator: '::', components: { - GlDropdownDivider, - GlFilteredSearchToken, + BaseToken, GlFilteredSearchSuggestion, - GlLoadingIcon, }, props: { config: { @@ -27,11 +24,15 @@ export default { type: Object, required: true, }, + active: { + type: Boolean, + required: true, + }, }, data() { return { epics: this.config.initialEpics || [], - loading: true, + loading: false, }; }, computed: { @@ -56,98 +57,73 @@ export default { } return this.defaultEpics; }, - activeEpic() { - if (this.currentValue && this.epics.length) { - // Check if current value is an epic ID. - if (typeof this.currentValue === 'number') { - return this.epics.find((epic) => epic[this.idProperty] === this.currentValue); - } - - // Current value is a string. - const [groupPath, idProperty] = this.currentValue?.split(this.$options.separator); - return this.epics.find( - (epic) => - epic.group_full_path === groupPath && - epic[this.idProperty] === parseInt(idProperty, 10), - ); - } - return null; - }, - displayText() { - return `${this.activeEpic?.title}${this.$options.separator}${this.activeEpic?.iid}`; - }, - }, - watch: { - active: { - immediate: true, - handler(newValue) { - if (!newValue && !this.epics.length) { - this.searchEpics({ data: this.currentValue }); - } - }, - }, }, methods: { - fetchEpicsBySearchTerm({ epicPath = '', search = '' }) { + fetchEpics(search = '') { + return this.$apollo + .query({ + query: searchEpicsQuery, + variables: { fullPath: this.config.fullPath, search }, + }) + .then(({ data }) => data.group?.epics.nodes); + }, + fetchEpicsBySearchTerm(search) { this.loading = true; - this.config - .fetchEpics({ epicPath, search }) + this.fetchEpics(search) .then((response) => { - this.epics = Array.isArray(response) ? response : response.data; + this.epics = Array.isArray(response) ? response : response?.data; }) .catch(() => createFlash({ message: __('There was a problem fetching epics.') })) .finally(() => { this.loading = false; }); }, - searchEpics: debounce(function debouncedSearch({ data }) { - let epicPath = this.activeEpic?.web_url; - - // When user visits the page with token value already included in filters - // We don't have any information about selected token except for its - // group path and iid joined by separator, so we need to manually - // compose epic path from it. - if (data.includes?.(this.$options.separator)) { - const [groupPath, epicIid] = data.split(this.$options.separator); - epicPath = `/groups/${groupPath}/-/epics/${epicIid}`; + getActiveEpic(epics, data) { + if (data && epics.length) { + return epics.find((epic) => this.getValue(epic) === data); } - this.fetchEpicsBySearchTerm({ epicPath, search: data }); - }, DEBOUNCE_DELAY), - + return undefined; + }, getValue(epic) { - return this.config.useIdValue - ? String(epic[this.idProperty]) - : `${epic.group_full_path}${this.$options.separator}${epic[this.idProperty]}`; + return this.getEpicIdProperty(epic).toString(); + }, + displayValue(epic) { + return `${this.$options.prefix}${this.getEpicIdProperty(epic)}${this.$options.separator}${ + epic?.title + }`; + }, + getEpicIdProperty(epic) { + return getIdFromGraphQLId(epic[this.idProperty]); }, }, }; </script> <template> - <gl-filtered-search-token + <base-token :config="config" - v-bind="{ ...$props, ...$attrs }" + :value="value" + :active="active" + :suggestions-loading="loading" + :suggestions="epics" + :get-active-token-value="getActiveEpic" + :default-suggestions="availableDefaultEpics" + :recent-suggestions-storage-key="config.recentSuggestionsStorageKey" + search-by="title" + @fetch-suggestions="fetchEpicsBySearchTerm" v-on="$listeners" - @input="searchEpics" > - <template #view="{ inputValue }"> - {{ activeEpic ? displayText : inputValue }} + <template #view="{ viewTokenProps: { inputValue, activeTokenValue } }"> + {{ activeTokenValue ? displayValue(activeTokenValue) : inputValue }} </template> - <template #suggestions> + <template #suggestions-list="{ suggestions }"> <gl-filtered-search-suggestion - v-for="epic in availableDefaultEpics" - :key="epic.value" - :value="epic.value" + v-for="epic in suggestions" + :key="epic.id" + :value="getValue(epic)" > - {{ epic.text }} + {{ epic.title }} </gl-filtered-search-suggestion> - <gl-dropdown-divider v-if="availableDefaultEpics.length" /> - <gl-loading-icon v-if="loading" size="sm" /> - <template v-else> - <gl-filtered-search-suggestion v-for="epic in epics" :key="epic.id" :value="getValue(epic)"> - {{ epic.title }} - </gl-filtered-search-suggestion> - </template> </template> - </gl-filtered-search-token> + </base-token> </template> |