Commit 65db0ee1 authored by Kushal Pandya's avatar Kushal Pandya

Merge branch 'cngo-filtered-search-improvements' into 'master'

Add some filtered search token improvements and fixes

See merge request gitlab-org/gitlab!75103
parents c775de2d 55464d43
...@@ -302,6 +302,7 @@ export default { ...@@ -302,6 +302,7 @@ export default {
unique: true, unique: true,
defaultAuthors: [], defaultAuthors: [],
fetchAuthors: this.fetchUsers, fetchAuthors: this.fetchUsers,
recentSuggestionsStorageKey: `${this.fullPath}-issues-recent-tokens-author`,
preloadedAuthors, preloadedAuthors,
}, },
{ {
...@@ -313,6 +314,7 @@ export default { ...@@ -313,6 +314,7 @@ export default {
unique: !this.hasMultipleIssueAssigneesFeature, unique: !this.hasMultipleIssueAssigneesFeature,
defaultAuthors: DEFAULT_NONE_ANY, defaultAuthors: DEFAULT_NONE_ANY,
fetchAuthors: this.fetchUsers, fetchAuthors: this.fetchUsers,
recentSuggestionsStorageKey: `${this.fullPath}-issues-recent-tokens-assignee`,
preloadedAuthors, preloadedAuthors,
}, },
{ {
...@@ -321,6 +323,7 @@ export default { ...@@ -321,6 +323,7 @@ export default {
icon: 'clock', icon: 'clock',
token: MilestoneToken, token: MilestoneToken,
fetchMilestones: this.fetchMilestones, fetchMilestones: this.fetchMilestones,
recentSuggestionsStorageKey: `${this.fullPath}-issues-recent-tokens-milestone`,
}, },
{ {
type: TOKEN_TYPE_LABEL, type: TOKEN_TYPE_LABEL,
...@@ -329,6 +332,7 @@ export default { ...@@ -329,6 +332,7 @@ export default {
token: LabelToken, token: LabelToken,
defaultLabels: DEFAULT_NONE_ANY, defaultLabels: DEFAULT_NONE_ANY,
fetchLabels: this.fetchLabels, fetchLabels: this.fetchLabels,
recentSuggestionsStorageKey: `${this.fullPath}-issues-recent-tokens-label`,
}, },
{ {
type: TOKEN_TYPE_TYPE, type: TOKEN_TYPE_TYPE,
...@@ -350,6 +354,7 @@ export default { ...@@ -350,6 +354,7 @@ export default {
icon: 'rocket', icon: 'rocket',
token: ReleaseToken, token: ReleaseToken,
fetchReleases: this.fetchReleases, fetchReleases: this.fetchReleases,
recentSuggestionsStorageKey: `${this.fullPath}-issues-recent-tokens-release`,
}); });
} }
...@@ -361,6 +366,7 @@ export default { ...@@ -361,6 +366,7 @@ export default {
token: EmojiToken, token: EmojiToken,
unique: true, unique: true,
fetchEmojis: this.fetchEmojis, fetchEmojis: this.fetchEmojis,
recentSuggestionsStorageKey: `${this.fullPath}-issues-recent-tokens-my_reaction`,
}); });
tokens.push({ tokens.push({
...@@ -446,7 +452,12 @@ export default { ...@@ -446,7 +452,12 @@ export default {
query: searchLabelsQuery, query: searchLabelsQuery,
variables: { fullPath: this.fullPath, search, isProject: this.isProject }, variables: { fullPath: this.fullPath, search, isProject: this.isProject },
}) })
.then(({ data }) => data[this.namespace]?.labels.nodes); .then(({ data }) => data[this.namespace]?.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())),
);
}, },
fetchMilestones(search) { fetchMilestones(search) {
return this.$apollo return this.$apollo
......
...@@ -116,7 +116,7 @@ export default { ...@@ -116,7 +116,7 @@ export default {
statusTokenConfig, statusTokenConfig,
{ {
...tagTokenConfig, ...tagTokenConfig,
recentTokenValuesStorageKey: `${this.$options.filteredSearchNamespace}-recent-tags`, recentSuggestionsStorageKey: `${this.$options.filteredSearchNamespace}-recent-tags`,
}, },
]; ];
}, },
......
...@@ -68,7 +68,6 @@ export default { ...@@ -68,7 +68,6 @@ export default {
:config="config" :config="config"
:suggestions-loading="loading" :suggestions-loading="loading"
:suggestions="tags" :suggestions="tags"
:recent-suggestions-storage-key="config.recentTokenValuesStorageKey"
@fetch-suggestions="fetchTags" @fetch-suggestions="fetchTags"
v-on="$listeners" v-on="$listeners"
> >
......
...@@ -87,7 +87,6 @@ export default { ...@@ -87,7 +87,6 @@ export default {
:get-active-token-value="getActiveAuthor" :get-active-token-value="getActiveAuthor"
:default-suggestions="defaultAuthors" :default-suggestions="defaultAuthors"
:preloaded-suggestions="preloadedAuthors" :preloaded-suggestions="preloadedAuthors"
:recent-suggestions-storage-key="config.recentSuggestionsStorageKey"
@fetch-suggestions="fetchAuthors" @fetch-suggestions="fetchAuthors"
v-on="$listeners" v-on="$listeners"
> >
......
...@@ -4,6 +4,7 @@ import { ...@@ -4,6 +4,7 @@ import {
GlFilteredSearchSuggestion, GlFilteredSearchSuggestion,
GlDropdownDivider, GlDropdownDivider,
GlDropdownSectionHeader, GlDropdownSectionHeader,
GlDropdownText,
GlLoadingIcon, GlLoadingIcon,
} from '@gitlab/ui'; } from '@gitlab/ui';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
...@@ -17,6 +18,7 @@ export default { ...@@ -17,6 +18,7 @@ export default {
GlFilteredSearchSuggestion, GlFilteredSearchSuggestion,
GlDropdownDivider, GlDropdownDivider,
GlDropdownSectionHeader, GlDropdownSectionHeader,
GlDropdownText,
GlLoadingIcon, GlLoadingIcon,
}, },
props: { props: {
...@@ -57,11 +59,6 @@ export default { ...@@ -57,11 +59,6 @@ export default {
required: false, required: false,
default: () => [], default: () => [],
}, },
recentSuggestionsStorageKey: {
type: String,
required: false,
default: '',
},
valueIdentifier: { valueIdentifier: {
type: String, type: String,
required: false, required: false,
...@@ -76,14 +73,14 @@ export default { ...@@ -76,14 +73,14 @@ export default {
data() { data() {
return { return {
searchKey: '', searchKey: '',
recentSuggestions: this.recentSuggestionsStorageKey recentSuggestions: this.config.recentSuggestionsStorageKey
? getRecentlyUsedSuggestions(this.recentSuggestionsStorageKey) ? getRecentlyUsedSuggestions(this.config.recentSuggestionsStorageKey)
: [], : [],
}; };
}, },
computed: { computed: {
isRecentSuggestionsEnabled() { isRecentSuggestionsEnabled() {
return Boolean(this.recentSuggestionsStorageKey); return Boolean(this.config.recentSuggestionsStorageKey);
}, },
recentTokenIds() { recentTokenIds() {
return this.recentSuggestions.map((tokenValue) => tokenValue[this.valueIdentifier]); return this.recentSuggestions.map((tokenValue) => tokenValue[this.valueIdentifier]);
...@@ -119,6 +116,9 @@ export default { ...@@ -119,6 +116,9 @@ export default {
showDefaultSuggestions() { showDefaultSuggestions() {
return this.availableDefaultSuggestions.length > 0; return this.availableDefaultSuggestions.length > 0;
}, },
showNoMatchesText() {
return this.searchKey && !this.availableSuggestions.length;
},
showRecentSuggestions() { showRecentSuggestions() {
return ( return (
this.isRecentSuggestionsEnabled && this.recentSuggestions.length > 0 && !this.searchKey this.isRecentSuggestionsEnabled && this.recentSuggestions.length > 0 && !this.searchKey
...@@ -167,7 +167,9 @@ export default { ...@@ -167,7 +167,9 @@ export default {
this.$emit('fetch-suggestions', search); this.$emit('fetch-suggestions', search);
} }
}, DEBOUNCE_DELAY), }, DEBOUNCE_DELAY),
handleTokenValueSelected(activeTokenValue) { handleTokenValueSelected(selectedValue) {
const activeTokenValue = this.getActiveTokenValue(this.suggestions, selectedValue);
// Make sure that; // Make sure that;
// 1. Recently used values feature is enabled // 1. Recently used values feature is enabled
// 2. User has actually selected a value // 2. User has actually selected a value
...@@ -177,7 +179,7 @@ export default { ...@@ -177,7 +179,7 @@ export default {
activeTokenValue && activeTokenValue &&
!this.preloadedTokenIds.includes(activeTokenValue[this.valueIdentifier]) !this.preloadedTokenIds.includes(activeTokenValue[this.valueIdentifier])
) { ) {
setTokenValueToRecentlyUsed(this.recentSuggestionsStorageKey, activeTokenValue); setTokenValueToRecentlyUsed(this.config.recentSuggestionsStorageKey, activeTokenValue);
} }
}, },
}, },
...@@ -192,7 +194,7 @@ export default { ...@@ -192,7 +194,7 @@ export default {
v-bind="$attrs" v-bind="$attrs"
v-on="$listeners" v-on="$listeners"
@input="handleInput" @input="handleInput"
@select="handleTokenValueSelected(activeTokenValue)" @select="handleTokenValueSelected"
> >
<template #view-token="viewTokenProps"> <template #view-token="viewTokenProps">
<slot name="view-token" :view-token-props="{ ...viewTokenProps, activeTokenValue }"></slot> <slot name="view-token" :view-token-props="{ ...viewTokenProps, activeTokenValue }"></slot>
...@@ -222,6 +224,9 @@ export default { ...@@ -222,6 +224,9 @@ export default {
:suggestions="preloadedSuggestions" :suggestions="preloadedSuggestions"
></slot> ></slot>
<gl-loading-icon v-if="suggestionsLoading" size="sm" /> <gl-loading-icon v-if="suggestionsLoading" size="sm" />
<gl-dropdown-text v-else-if="showNoMatchesText">
{{ __('No matches found') }}
</gl-dropdown-text>
<template v-else> <template v-else>
<slot name="suggestions-list" :suggestions="availableSuggestions"></slot> <slot name="suggestions-list" :suggestions="availableSuggestions"></slot>
</template> </template>
......
...@@ -104,7 +104,6 @@ export default { ...@@ -104,7 +104,6 @@ export default {
:suggestions="labels" :suggestions="labels"
:get-active-token-value="getActiveLabel" :get-active-token-value="getActiveLabel"
:default-suggestions="defaultLabels" :default-suggestions="defaultLabels"
:recent-suggestions-storage-key="config.recentSuggestionsStorageKey"
@fetch-suggestions="fetchLabels" @fetch-suggestions="fetchLabels"
v-on="$listeners" v-on="$listeners"
> >
......
...@@ -107,7 +107,6 @@ export default { ...@@ -107,7 +107,6 @@ export default {
:suggestions="epics" :suggestions="epics"
:get-active-token-value="getActiveEpic" :get-active-token-value="getActiveEpic"
:default-suggestions="availableDefaultEpics" :default-suggestions="availableDefaultEpics"
:recent-suggestions-storage-key="config.recentSuggestionsStorageKey"
search-by="title" search-by="title"
@fetch-suggestions="fetchEpicsBySearchTerm" @fetch-suggestions="fetchEpicsBySearchTerm"
v-on="$listeners" v-on="$listeners"
......
...@@ -147,7 +147,7 @@ describe('AdminRunnersApp', () => { ...@@ -147,7 +147,7 @@ describe('AdminRunnersApp', () => {
}), }),
expect.objectContaining({ expect.objectContaining({
type: PARAM_KEY_TAG, type: PARAM_KEY_TAG,
recentTokenValuesStorageKey: `${ADMIN_FILTERED_SEARCH_NAMESPACE}-recent-tags`, recentSuggestionsStorageKey: `${ADMIN_FILTERED_SEARCH_NAMESPACE}-recent-tags`,
}), }),
]); ]);
}); });
......
...@@ -41,7 +41,7 @@ const mockTagTokenConfig = { ...@@ -41,7 +41,7 @@ const mockTagTokenConfig = {
title: 'Tags', title: 'Tags',
type: 'tag', type: 'tag',
token: TagToken, token: TagToken,
recentTokenValuesStorageKey: mockStorageKey, recentSuggestionsStorageKey: mockStorageKey,
operators: OPERATOR_IS_ONLY, operators: OPERATOR_IS_ONLY,
}; };
......
...@@ -46,13 +46,13 @@ const defaultSlots = { ...@@ -46,13 +46,13 @@ const defaultSlots = {
}; };
const mockProps = { const mockProps = {
config: mockLabelToken, config: { ...mockLabelToken, recentSuggestionsStorageKey: mockStorageKey },
value: { data: '' }, value: { data: '' },
active: false, active: false,
suggestions: [], suggestions: [],
suggestionsLoading: false, suggestionsLoading: false,
defaultSuggestions: DEFAULT_NONE_ANY, defaultSuggestions: DEFAULT_NONE_ANY,
recentSuggestionsStorageKey: mockStorageKey, getActiveTokenValue: (labels, data) => labels.find((label) => label.title === data),
}; };
function createComponent({ function createComponent({
...@@ -152,30 +152,22 @@ describe('BaseToken', () => { ...@@ -152,30 +152,22 @@ describe('BaseToken', () => {
describe('methods', () => { describe('methods', () => {
describe('handleTokenValueSelected', () => { describe('handleTokenValueSelected', () => {
it('calls `setTokenValueToRecentlyUsed` when `recentSuggestionsStorageKey` is defined', () => { const mockTokenValue = mockLabels[0];
const mockTokenValue = {
id: 1,
title: 'Foo',
};
wrapper.vm.handleTokenValueSelected(mockTokenValue); it('calls `setTokenValueToRecentlyUsed` when `recentSuggestionsStorageKey` is defined', () => {
wrapper.vm.handleTokenValueSelected(mockTokenValue.title);
expect(setTokenValueToRecentlyUsed).toHaveBeenCalledWith(mockStorageKey, mockTokenValue); expect(setTokenValueToRecentlyUsed).toHaveBeenCalledWith(mockStorageKey, mockTokenValue);
}); });
it('does not add token from preloadedSuggestions', async () => { it('does not add token from preloadedSuggestions', async () => {
const mockTokenValue = {
id: 1,
title: 'Foo',
};
wrapper.setProps({ wrapper.setProps({
preloadedSuggestions: [mockTokenValue], preloadedSuggestions: [mockTokenValue],
}); });
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
wrapper.vm.handleTokenValueSelected(mockTokenValue); wrapper.vm.handleTokenValueSelected(mockTokenValue.title);
expect(setTokenValueToRecentlyUsed).not.toHaveBeenCalled(); expect(setTokenValueToRecentlyUsed).not.toHaveBeenCalled();
}); });
...@@ -190,7 +182,7 @@ describe('BaseToken', () => { ...@@ -190,7 +182,7 @@ describe('BaseToken', () => {
const glFilteredSearchToken = wrapperWithNoStubs.find(GlFilteredSearchToken); const glFilteredSearchToken = wrapperWithNoStubs.find(GlFilteredSearchToken);
expect(glFilteredSearchToken.exists()).toBe(true); expect(glFilteredSearchToken.exists()).toBe(true);
expect(glFilteredSearchToken.props('config')).toBe(mockLabelToken); expect(glFilteredSearchToken.props('config')).toEqual(mockProps.config);
wrapperWithNoStubs.destroy(); wrapperWithNoStubs.destroy();
}); });
......
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