Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-08-19 12:08:42 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-08-19 12:08:42 +0300
commitb76ae638462ab0f673e5915986070518dd3f9ad3 (patch)
treebdab0533383b52873be0ec0eb4d3c66598ff8b91 /app/assets/javascripts/vue_shared/components/filtered_search_bar
parent434373eabe7b4be9593d18a585fb763f1e5f1a6f (diff)
Add latest changes from gitlab-org/gitlab@14-2-stable-eev14.2.0-rc42
Diffstat (limited to 'app/assets/javascripts/vue_shared/components/filtered_search_bar')
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/constants.js12
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue9
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_utils.js9
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/author_token.vue22
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/base_token.vue63
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/branch_token.vue100
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/emoji_token.vue109
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/epic_token.vue16
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/iteration_token.vue99
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/label_token.vue18
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue97
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/weight_token.vue56
12 files changed, 279 insertions, 331 deletions
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/constants.js b/app/assets/javascripts/vue_shared/components/filtered_search_bar/constants.js
index 994ce6a762a..2e9634819a0 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/constants.js
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/constants.js
@@ -2,10 +2,14 @@ import { __ } from '~/locale';
export const DEBOUNCE_DELAY = 200;
export const MAX_RECENT_TOKENS_SIZE = 3;
+export const WEIGHT_TOKEN_SUGGESTIONS_SIZE = 21;
export const FILTER_NONE = 'None';
export const FILTER_ANY = 'Any';
export const FILTER_CURRENT = 'Current';
+export const FILTER_UPCOMING = 'Upcoming';
+export const FILTER_STARTED = 'Started';
+export const FILTER_NONE_ANY = [FILTER_NONE, FILTER_ANY];
export const OPERATOR_IS = '=';
export const OPERATOR_IS_TEXT = __('is');
@@ -24,11 +28,9 @@ export const DEFAULT_ITERATIONS = DEFAULT_NONE_ANY.concat([
{ value: FILTER_CURRENT, text: __(FILTER_CURRENT) },
]);
-export const DEFAULT_LABELS = [DEFAULT_LABEL_NONE, DEFAULT_LABEL_ANY];
-
export const DEFAULT_MILESTONES = DEFAULT_NONE_ANY.concat([
- { value: 'Upcoming', text: __('Upcoming') }, // eslint-disable-line @gitlab/require-i18n-strings
- { value: 'Started', text: __('Started') }, // eslint-disable-line @gitlab/require-i18n-strings
+ { value: FILTER_UPCOMING, text: __(FILTER_UPCOMING) },
+ { value: FILTER_STARTED, text: __(FILTER_STARTED) },
]);
export const SortDirection = {
@@ -36,12 +38,14 @@ export const SortDirection = {
ascending: 'ascending',
};
+export const FILTERED_SEARCH_LABELS = 'labels';
export const FILTERED_SEARCH_TERM = 'filtered-search-term';
export const TOKEN_TITLE_AUTHOR = __('Author');
export const TOKEN_TITLE_ASSIGNEE = __('Assignee');
export const TOKEN_TITLE_MILESTONE = __('Milestone');
export const TOKEN_TITLE_LABEL = __('Label');
+export const TOKEN_TITLE_TYPE = __('Type');
export const TOKEN_TITLE_MY_REACTION = __('My-Reaction');
export const TOKEN_TITLE_CONFIDENTIAL = __('Confidential');
export const TOKEN_TITLE_ITERATION = __('Iteration');
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue
index 5ab287150f2..9dc5c5db276 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue
@@ -16,7 +16,7 @@ import createFlash from '~/flash';
import { __ } from '~/locale';
import { SortDirection } from './constants';
-import { stripQuotes, uniqueTokens } from './filtered_search_utils';
+import { filterEmptySearchTerm, stripQuotes, uniqueTokens } from './filtered_search_utils';
export default {
components: {
@@ -223,9 +223,14 @@ export default {
// Put any searches that may have come in before
// we fetched the saved searches ahead of the already saved ones
- const resultantSearches = this.recentSearchesStore.setRecentSearches(
+ let resultantSearches = this.recentSearchesStore.setRecentSearches(
this.recentSearchesStore.state.recentSearches.concat(searches),
);
+ // If visited URL has search params, add them to recent search store
+ if (filterEmptySearchTerm(this.filterValue).length) {
+ resultantSearches = this.recentSearchesStore.addRecentSearch(this.filterValue);
+ }
+
this.recentSearchesService.save(resultantSearches);
this.recentSearches = resultantSearches;
});
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_utils.js b/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_utils.js
index 571d24b50cf..6573f366b52 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_utils.js
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_utils.js
@@ -247,3 +247,12 @@ export function setTokenValueToRecentlyUsed(recentSuggestionsStorageKey, tokenVa
);
}
}
+
+/**
+ * Removes `FILTERED_SEARCH_TERM` tokens with empty data
+ *
+ * @param filterTokens array of filtered search tokens
+ * @return {Array} array of filtered search tokens
+ */
+export const filterEmptySearchTerm = (filterTokens = []) =>
+ filterTokens.filter((token) => token.type === FILTERED_SEARCH_TERM && token.value.data);
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/author_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/author_token.vue
index a25a19a006c..ae5d3965de1 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/author_token.vue
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/author_token.vue
@@ -31,19 +31,25 @@ export default {
data() {
return {
authors: this.config.initialAuthors || [],
- defaultAuthors: this.config.defaultAuthors || [DEFAULT_LABEL_ANY],
- preloadedAuthors: this.config.preloadedAuthors || [],
loading: false,
};
},
+ computed: {
+ defaultAuthors() {
+ return this.config.defaultAuthors || [DEFAULT_LABEL_ANY];
+ },
+ preloadedAuthors() {
+ return this.config.preloadedAuthors || [];
+ },
+ },
methods: {
- getActiveAuthor(authors, currentValue) {
- return authors.find((author) => author.username.toLowerCase() === currentValue);
+ getActiveAuthor(authors, data) {
+ return authors.find((author) => author.username.toLowerCase() === data.toLowerCase());
},
getAvatarUrl(author) {
return author.avatarUrl || author.avatar_url;
},
- fetchAuthorBySearchTerm(searchTerm) {
+ fetchAuthors(searchTerm) {
this.loading = true;
const fetchPromise = this.config.fetchPath
? this.config.fetchAuthors(this.config.fetchPath, searchTerm)
@@ -76,11 +82,11 @@ export default {
:active="active"
:suggestions-loading="loading"
:suggestions="authors"
- :fn-active-token-value="getActiveAuthor"
+ :get-active-token-value="getActiveAuthor"
:default-suggestions="defaultAuthors"
:preloaded-suggestions="preloadedAuthors"
:recent-suggestions-storage-key="config.recentSuggestionsStorageKey"
- @fetch-suggestions="fetchAuthorBySearchTerm"
+ @fetch-suggestions="fetchAuthors"
v-on="$listeners"
>
<template #view="{ viewTokenProps: { inputValue, activeTokenValue } }">
@@ -91,7 +97,7 @@ export default {
shape="circle"
class="gl-mr-2"
/>
- <span>{{ activeTokenValue ? activeTokenValue.name : inputValue }}</span>
+ {{ activeTokenValue ? activeTokenValue.name : inputValue }}
</template>
<template #suggestions-list="{ suggestions }">
<gl-filtered-search-suggestion
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 a4804525a53..d1326e96794 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
@@ -8,7 +8,7 @@ import {
} from '@gitlab/ui';
import { debounce } from 'lodash';
-import { DEBOUNCE_DELAY } from '../constants';
+import { DEBOUNCE_DELAY, FILTER_NONE_ANY, OPERATOR_IS_NOT } from '../constants';
import { getRecentlyUsedSuggestions, setTokenValueToRecentlyUsed } from '../filtered_search_utils';
export default {
@@ -42,12 +42,10 @@ export default {
required: false,
default: () => [],
},
- fnActiveTokenValue: {
+ getActiveTokenValue: {
type: Function,
required: false,
- default: (suggestions, currentTokenValue) => {
- return suggestions.find(({ value }) => value === currentTokenValue);
- },
+ default: (suggestions, data) => suggestions.find(({ value }) => value === data),
},
defaultSuggestions: {
type: Array,
@@ -69,11 +67,6 @@ export default {
required: false,
default: 'id',
},
- fnCurrentTokenValue: {
- type: Function,
- required: false,
- default: null,
- },
},
data() {
return {
@@ -81,7 +74,6 @@ export default {
recentSuggestions: this.recentSuggestionsStorageKey
? getRecentlyUsedSuggestions(this.recentSuggestionsStorageKey)
: [],
- loading: false,
};
},
computed: {
@@ -94,14 +86,16 @@ export default {
preloadedTokenIds() {
return this.preloadedSuggestions.map((tokenValue) => tokenValue[this.valueIdentifier]);
},
- currentTokenValue() {
- if (this.fnCurrentTokenValue) {
- return this.fnCurrentTokenValue(this.value.data);
- }
- return this.value.data.toLowerCase();
- },
activeTokenValue() {
- return this.fnActiveTokenValue(this.suggestions, this.currentTokenValue);
+ return this.getActiveTokenValue(this.suggestions, this.value.data);
+ },
+ availableDefaultSuggestions() {
+ if (this.value.operator === OPERATOR_IS_NOT) {
+ return this.defaultSuggestions.filter(
+ (suggestion) => !FILTER_NONE_ANY.includes(suggestion.value),
+ );
+ }
+ return this.defaultSuggestions;
},
/**
* Return all the suggestions when searchKey is present
@@ -117,6 +111,29 @@ export default {
!this.preloadedTokenIds.includes(tokenValue[this.valueIdentifier]),
);
},
+ showDefaultSuggestions() {
+ return this.availableDefaultSuggestions.length;
+ },
+ showRecentSuggestions() {
+ return this.isRecentSuggestionsEnabled && this.recentSuggestions.length && !this.searchKey;
+ },
+ showPreloadedSuggestions() {
+ return this.preloadedSuggestions.length && !this.searchKey;
+ },
+ showAvailableSuggestions() {
+ return this.availableSuggestions.length;
+ },
+ showSuggestions() {
+ // These conditions must match the template under `#suggestions` slot
+ // See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/65817#note_632619411
+ return (
+ this.showDefaultSuggestions ||
+ this.showRecentSuggestions ||
+ this.showPreloadedSuggestions ||
+ this.suggestionsLoading ||
+ this.showAvailableSuggestions
+ );
+ },
},
watch: {
active: {
@@ -168,10 +185,10 @@ export default {
<template #view="viewTokenProps">
<slot name="view" :view-token-props="{ ...viewTokenProps, activeTokenValue }"></slot>
</template>
- <template #suggestions>
- <template v-if="defaultSuggestions.length">
+ <template v-if="showSuggestions" #suggestions>
+ <template v-if="showDefaultSuggestions">
<gl-filtered-search-suggestion
- v-for="token in defaultSuggestions"
+ v-for="token in availableDefaultSuggestions"
:key="token.value"
:value="token.value"
>
@@ -179,13 +196,13 @@ export default {
</gl-filtered-search-suggestion>
<gl-dropdown-divider />
</template>
- <template v-if="isRecentSuggestionsEnabled && recentSuggestions.length && !searchKey">
+ <template v-if="showRecentSuggestions">
<gl-dropdown-section-header>{{ __('Recently used') }}</gl-dropdown-section-header>
<slot name="suggestions-list" :suggestions="recentSuggestions"></slot>
<gl-dropdown-divider />
</template>
<slot
- v-if="preloadedSuggestions.length && !searchKey"
+ v-if="showPreloadedSuggestions"
name="suggestions-list"
:suggestions="preloadedSuggestions"
></slot>
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/branch_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/branch_token.vue
index 5859fd10688..4ecfc1cf40c 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/branch_token.vue
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/branch_token.vue
@@ -1,27 +1,19 @@
<script>
-import {
- GlToken,
- GlFilteredSearchToken,
- GlFilteredSearchSuggestion,
- GlDropdownDivider,
- GlLoadingIcon,
-} from '@gitlab/ui';
-import { debounce } from 'lodash';
-
+import { GlFilteredSearchSuggestion } from '@gitlab/ui';
import createFlash from '~/flash';
import { __ } from '~/locale';
-
-import { DEBOUNCE_DELAY } from '../constants';
+import BaseToken from '~/vue_shared/components/filtered_search_bar/tokens/base_token.vue';
export default {
components: {
- GlToken,
- GlFilteredSearchToken,
+ BaseToken,
GlFilteredSearchSuggestion,
- GlDropdownDivider,
- GlLoadingIcon,
},
props: {
+ active: {
+ type: Boolean,
+ required: true,
+ },
config: {
type: Object,
required: true,
@@ -34,82 +26,62 @@ export default {
data() {
return {
branches: this.config.initialBranches || [],
- defaultBranches: this.config.defaultBranches || [],
- loading: true,
+ loading: false,
};
},
computed: {
- currentValue() {
- return this.value.data.toLowerCase();
- },
- activeBranch() {
- return this.branches.find((branch) => branch.name.toLowerCase() === this.currentValue);
- },
- },
- watch: {
- active: {
- immediate: true,
- handler(newValue) {
- if (!newValue && !this.branches.length) {
- this.fetchBranchBySearchTerm(this.value.data);
- }
- },
+ defaultBranches() {
+ return this.config.defaultBranches || [];
},
},
methods: {
- fetchBranchBySearchTerm(searchTerm) {
+ getActiveBranch(branches, data) {
+ return branches.find((branch) => branch.name.toLowerCase() === data.toLowerCase());
+ },
+ fetchBranches(searchTerm) {
this.loading = true;
this.config
.fetchBranches(searchTerm)
.then(({ data }) => {
this.branches = data;
})
- .catch(() => createFlash({ message: __('There was a problem fetching branches.') }))
+ .catch(() => {
+ createFlash({ message: __('There was a problem fetching branches.') });
+ })
.finally(() => {
this.loading = false;
});
},
- searchBranches: debounce(function debouncedSearch({ data }) {
- this.fetchBranchBySearchTerm(data);
- }, DEBOUNCE_DELAY),
},
};
</script>
<template>
- <gl-filtered-search-token
+ <base-token
+ :active="active"
:config="config"
- v-bind="{ ...$props, ...$attrs }"
+ :value="value"
+ :default-suggestions="defaultBranches"
+ :suggestions="branches"
+ :suggestions-loading="loading"
+ :get-active-token-value="getActiveBranch"
+ @fetch-suggestions="fetchBranches"
v-on="$listeners"
- @input="searchBranches"
>
- <template #view-token="{ inputValue }">
- <gl-token variant="search-value">{{
- activeBranch ? activeBranch.name : inputValue
- }}</gl-token>
+ <template #view="{ viewTokenProps: { inputValue, activeTokenValue } }">
+ {{ activeTokenValue ? activeTokenValue.name : inputValue }}
</template>
- <template #suggestions>
+ <template #suggestions-list="{ suggestions }">
<gl-filtered-search-suggestion
- v-for="branch in defaultBranches"
- :key="branch.value"
- :value="branch.value"
+ v-for="branch in suggestions"
+ :key="branch.id"
+ :value="branch.name"
>
- {{ branch.text }}
+ <div class="gl-display-flex">
+ <span class="gl-display-inline-block gl-mr-3 gl-p-3"></span>
+ {{ branch.name }}
+ </div>
</gl-filtered-search-suggestion>
- <gl-dropdown-divider v-if="defaultBranches.length" />
- <gl-loading-icon v-if="loading" size="sm" />
- <template v-else>
- <gl-filtered-search-suggestion
- v-for="branch in branches"
- :key="branch.id"
- :value="branch.name"
- >
- <div class="gl-display-flex">
- <span class="gl-display-inline-block gl-mr-3 gl-p-3"></span>
- <div>{{ branch.name }}</div>
- </div>
- </gl-filtered-search-suggestion>
- </template>
</template>
- </gl-filtered-search-token>
+ </base-token>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/emoji_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/emoji_token.vue
index d186f46866c..5a69751a2cc 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/emoji_token.vue
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/emoji_token.vue
@@ -1,26 +1,21 @@
<script>
-import {
- GlFilteredSearchToken,
- GlFilteredSearchSuggestion,
- GlDropdownDivider,
- GlLoadingIcon,
-} from '@gitlab/ui';
-import { debounce } from 'lodash';
-
+import { GlFilteredSearchSuggestion } from '@gitlab/ui';
import createFlash from '~/flash';
import { __ } from '~/locale';
-
-import { DEBOUNCE_DELAY, DEFAULT_NONE_ANY } from '../constants';
+import BaseToken from '~/vue_shared/components/filtered_search_bar/tokens/base_token.vue';
+import { DEFAULT_NONE_ANY } from '../constants';
import { stripQuotes } from '../filtered_search_utils';
export default {
components: {
- GlFilteredSearchToken,
+ BaseToken,
GlFilteredSearchSuggestion,
- GlDropdownDivider,
- GlLoadingIcon,
},
props: {
+ active: {
+ type: Boolean,
+ required: true,
+ },
config: {
type: Object,
required: true,
@@ -33,87 +28,63 @@ export default {
data() {
return {
emojis: this.config.initialEmojis || [],
- defaultEmojis: this.config.defaultEmojis || DEFAULT_NONE_ANY,
- loading: true,
+ loading: false,
};
},
computed: {
- currentValue() {
- return this.value.data.toLowerCase();
- },
- activeEmoji() {
- return this.emojis.find(
- (emoji) => emoji.name.toLowerCase() === stripQuotes(this.currentValue),
- );
- },
- },
- watch: {
- active: {
- immediate: true,
- handler(newValue) {
- if (!newValue && !this.emojis.length) {
- this.fetchEmojiBySearchTerm(this.value.data);
- }
- },
+ defaultEmojis() {
+ return this.config.defaultEmojis || DEFAULT_NONE_ANY;
},
},
methods: {
- fetchEmojiBySearchTerm(searchTerm) {
+ getActiveEmoji(emojis, data) {
+ return emojis.find((emoji) => emoji.name.toLowerCase() === stripQuotes(data).toLowerCase());
+ },
+ fetchEmojis(searchTerm) {
this.loading = true;
this.config
.fetchEmojis(searchTerm)
- .then((res) => {
- this.emojis = Array.isArray(res) ? res : res.data;
+ .then((response) => {
+ this.emojis = Array.isArray(response) ? response : response.data;
+ })
+ .catch(() => {
+ createFlash({ message: __('There was a problem fetching emojis.') });
})
- .catch(() =>
- createFlash({
- message: __('There was a problem fetching emojis.'),
- }),
- )
.finally(() => {
this.loading = false;
});
},
- searchEmojis: debounce(function debouncedSearch({ data }) {
- this.fetchEmojiBySearchTerm(data);
- }, DEBOUNCE_DELAY),
},
};
</script>
<template>
- <gl-filtered-search-token
+ <base-token
+ :active="active"
:config="config"
- v-bind="{ ...$props, ...$attrs }"
+ :value="value"
+ :default-suggestions="defaultEmojis"
+ :suggestions="emojis"
+ :suggestions-loading="loading"
+ :get-active-token-value="getActiveEmoji"
+ @fetch-suggestions="fetchEmojis"
v-on="$listeners"
- @input="searchEmojis"
>
- <template #view="{ inputValue }">
- <gl-emoji v-if="activeEmoji" :data-name="activeEmoji.name" />
- <span v-else>{{ inputValue }}</span>
+ <template #view="{ viewTokenProps: { inputValue, activeTokenValue } }">
+ <gl-emoji v-if="activeTokenValue" :data-name="activeTokenValue.name" />
+ <template v-else>{{ inputValue }}</template>
</template>
- <template #suggestions>
+ <template #suggestions-list="{ suggestions }">
<gl-filtered-search-suggestion
- v-for="emoji in defaultEmojis"
- :key="emoji.value"
- :value="emoji.value"
+ v-for="emoji in suggestions"
+ :key="emoji.name"
+ :value="emoji.name"
>
- {{ emoji.value }}
+ <div class="gl-display-flex">
+ <gl-emoji class="gl-mr-3" :data-name="emoji.name" />
+ {{ emoji.name }}
+ </div>
</gl-filtered-search-suggestion>
- <gl-dropdown-divider v-if="defaultEmojis.length" />
- <gl-loading-icon v-if="loading" size="sm" />
- <template v-else>
- <gl-filtered-search-suggestion
- v-for="emoji in emojis"
- :key="emoji.name"
- :value="emoji.name"
- >
- <div class="gl-display-flex">
- <gl-emoji :data-name="emoji.name" />
- <span class="gl-ml-3">{{ emoji.name }}</span>
- </div>
- </gl-filtered-search-suggestion>
- </template>
</template>
- </gl-filtered-search-token>
+ </base-token>
</template>
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 aa234cf86d9..9f68308808e 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
@@ -8,7 +8,7 @@ import {
import { debounce } from 'lodash';
import createFlash from '~/flash';
import { __ } from '~/locale';
-import { DEBOUNCE_DELAY, DEFAULT_NONE_ANY } from '../constants';
+import { DEBOUNCE_DELAY, DEFAULT_NONE_ANY, FILTER_NONE_ANY, OPERATOR_IS_NOT } from '../constants';
export default {
separator: '::&',
@@ -48,6 +48,14 @@ export default {
defaultEpics() {
return this.config.defaultEpics || DEFAULT_NONE_ANY;
},
+ availableDefaultEpics() {
+ if (this.value.operator === OPERATOR_IS_NOT) {
+ return this.defaultEpics.filter(
+ (suggestion) => !FILTER_NONE_ANY.includes(suggestion.value),
+ );
+ }
+ return this.defaultEpics;
+ },
activeEpic() {
if (this.currentValue && this.epics.length) {
// Check if current value is an epic ID.
@@ -99,7 +107,7 @@ export default {
// 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)) {
+ if (data.includes?.(this.$options.separator)) {
const [groupPath, epicIid] = data.split(this.$options.separator);
epicPath = `/groups/${groupPath}/-/epics/${epicIid}`;
}
@@ -127,13 +135,13 @@ export default {
</template>
<template #suggestions>
<gl-filtered-search-suggestion
- v-for="epic in defaultEpics"
+ v-for="epic in availableDefaultEpics"
:key="epic.value"
:value="epic.value"
>
{{ epic.text }}
</gl-filtered-search-suggestion>
- <gl-dropdown-divider v-if="defaultEpics.length" />
+ <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)">
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/iteration_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/iteration_token.vue
index ba8b2421726..c1d1bc7da91 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/iteration_token.vue
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/iteration_token.vue
@@ -1,24 +1,21 @@
<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_ITERATIONS } from '../constants';
+import BaseToken from '~/vue_shared/components/filtered_search_bar/tokens/base_token.vue';
+import { DEFAULT_ITERATIONS } from '../constants';
export default {
components: {
- GlDropdownDivider,
+ BaseToken,
GlFilteredSearchSuggestion,
- GlFilteredSearchToken,
- GlLoadingIcon,
},
props: {
+ active: {
+ type: Boolean,
+ required: true,
+ },
config: {
type: Object,
required: true,
@@ -35,84 +32,58 @@ export default {
};
},
computed: {
- currentValue() {
- return this.value.data;
- },
- activeIteration() {
- return this.iterations.find(
- (iteration) => getIdFromGraphQLId(iteration.id) === Number(this.currentValue),
- );
- },
defaultIterations() {
return this.config.defaultIterations || DEFAULT_ITERATIONS;
},
},
- watch: {
- active: {
- immediate: true,
- handler(newValue) {
- if (!newValue && !this.iterations.length) {
- this.fetchIterationBySearchTerm(this.currentValue);
- }
- },
- },
- },
methods: {
- getValue(iteration) {
- return String(getIdFromGraphQLId(iteration.id));
+ getActiveIteration(iterations, data) {
+ return iterations.find((iteration) => this.getValue(iteration) === data);
},
- fetchIterationBySearchTerm(searchTerm) {
- const fetchPromise = this.config.fetchPath
- ? this.config.fetchIterations(this.config.fetchPath, searchTerm)
- : this.config.fetchIterations(searchTerm);
-
+ fetchIterations(searchTerm) {
this.loading = true;
-
- fetchPromise
+ this.config
+ .fetchIterations(searchTerm)
.then((response) => {
this.iterations = Array.isArray(response) ? response : response.data;
})
- .catch(() => createFlash({ message: __('There was a problem fetching iterations.') }))
+ .catch(() => {
+ createFlash({ message: __('There was a problem fetching iterations.') });
+ })
.finally(() => {
this.loading = false;
});
},
- searchIterations: debounce(function debouncedSearch({ data }) {
- this.fetchIterationBySearchTerm(data);
- }, DEBOUNCE_DELAY),
+ getValue(iteration) {
+ return String(getIdFromGraphQLId(iteration.id));
+ },
},
};
</script>
<template>
- <gl-filtered-search-token
+ <base-token
+ :active="active"
:config="config"
- v-bind="{ ...$props, ...$attrs }"
+ :value="value"
+ :default-suggestions="defaultIterations"
+ :suggestions="iterations"
+ :suggestions-loading="loading"
+ :get-active-token-value="getActiveIteration"
+ @fetch-suggestions="fetchIterations"
v-on="$listeners"
- @input="searchIterations"
>
- <template #view="{ inputValue }">
- {{ activeIteration ? activeIteration.title : inputValue }}
+ <template #view="{ viewTokenProps: { inputValue, activeTokenValue } }">
+ {{ activeTokenValue ? activeTokenValue.title : inputValue }}
</template>
- <template #suggestions>
+ <template #suggestions-list="{ suggestions }">
<gl-filtered-search-suggestion
- v-for="iteration in defaultIterations"
- :key="iteration.value"
- :value="iteration.value"
+ v-for="iteration in suggestions"
+ :key="iteration.id"
+ :value="getValue(iteration)"
>
- {{ iteration.text }}
+ {{ iteration.title }}
</gl-filtered-search-suggestion>
- <gl-dropdown-divider v-if="defaultIterations.length" />
- <gl-loading-icon v-if="loading" size="sm" />
- <template v-else>
- <gl-filtered-search-suggestion
- v-for="iteration in iterations"
- :key="iteration.id"
- :value="getValue(iteration)"
- >
- {{ iteration.title }}
- </gl-filtered-search-suggestion>
- </template>
</template>
- </gl-filtered-search-token>
+ </base-token>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/label_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/label_token.vue
index 4d08f81fee9..c31f3a25fb1 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/label_token.vue
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/label_token.vue
@@ -5,7 +5,7 @@ import createFlash from '~/flash';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { __ } from '~/locale';
-import { DEFAULT_LABELS } from '../constants';
+import { DEFAULT_NONE_ANY } from '../constants';
import { stripQuotes } from '../filtered_search_utils';
import BaseToken from './base_token.vue';
@@ -33,14 +33,18 @@ export default {
data() {
return {
labels: this.config.initialLabels || [],
- defaultLabels: this.config.defaultLabels || DEFAULT_LABELS,
loading: false,
};
},
+ computed: {
+ defaultLabels() {
+ return this.config.defaultLabels || DEFAULT_NONE_ANY;
+ },
+ },
methods: {
- getActiveLabel(labels, currentValue) {
+ getActiveLabel(labels, data) {
return labels.find(
- (label) => this.getLabelName(label).toLowerCase() === stripQuotes(currentValue),
+ (label) => this.getLabelName(label).toLowerCase() === stripQuotes(data).toLowerCase(),
);
},
/**
@@ -68,7 +72,7 @@ export default {
}
return {};
},
- fetchLabelBySearchTerm(searchTerm) {
+ fetchLabels(searchTerm) {
this.loading = true;
this.config
.fetchLabels(searchTerm)
@@ -98,10 +102,10 @@ export default {
:active="active"
:suggestions-loading="loading"
:suggestions="labels"
- :fn-active-token-value="getActiveLabel"
+ :get-active-token-value="getActiveLabel"
:default-suggestions="defaultLabels"
:recent-suggestions-storage-key="config.recentSuggestionsStorageKey"
- @fetch-suggestions="fetchLabelBySearchTerm"
+ @fetch-suggestions="fetchLabels"
v-on="$listeners"
>
<template
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue
index 66ad5ef5b4e..4b9ad6d8f91 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue
@@ -1,27 +1,22 @@
<script>
-import {
- GlFilteredSearchToken,
- GlFilteredSearchSuggestion,
- GlDropdownDivider,
- GlLoadingIcon,
-} from '@gitlab/ui';
-import { debounce } from 'lodash';
-
+import { GlFilteredSearchSuggestion } from '@gitlab/ui';
import createFlash from '~/flash';
import { __ } from '~/locale';
import { sortMilestonesByDueDate } from '~/milestones/milestone_utils';
-
-import { DEFAULT_MILESTONES, DEBOUNCE_DELAY } from '../constants';
+import BaseToken from '~/vue_shared/components/filtered_search_bar/tokens/base_token.vue';
+import { DEFAULT_MILESTONES } from '../constants';
import { stripQuotes } from '../filtered_search_utils';
export default {
components: {
- GlFilteredSearchToken,
+ BaseToken,
GlFilteredSearchSuggestion,
- GlDropdownDivider,
- GlLoadingIcon,
},
props: {
+ active: {
+ type: Boolean,
+ required: true,
+ },
config: {
type: Object,
required: true,
@@ -34,36 +29,21 @@ export default {
data() {
return {
milestones: this.config.initialMilestones || [],
- defaultMilestones: this.config.defaultMilestones || DEFAULT_MILESTONES,
loading: false,
};
},
computed: {
- currentValue() {
- return this.value.data.toLowerCase();
- },
- activeMilestone() {
- return this.milestones.find(
- (milestone) => milestone.title.toLowerCase() === stripQuotes(this.currentValue),
- );
- },
- },
- watch: {
- active: {
- immediate: true,
- handler(newValue) {
- if (!newValue && !this.milestones.length) {
- this.fetchMilestoneBySearchTerm(this.value.data);
- }
- },
+ defaultMilestones() {
+ return this.config.defaultMilestones || DEFAULT_MILESTONES;
},
},
methods: {
- fetchMilestoneBySearchTerm(searchTerm = '') {
- if (this.loading) {
- return;
- }
-
+ getActiveMilestone(milestones, data) {
+ return milestones.find(
+ (milestone) => milestone.title.toLowerCase() === stripQuotes(data).toLowerCase(),
+ );
+ },
+ fetchMilestones(searchTerm) {
this.loading = true;
this.config
.fetchMilestones(searchTerm)
@@ -71,47 +51,40 @@ export default {
const data = Array.isArray(response) ? response : response.data;
this.milestones = data.slice().sort(sortMilestonesByDueDate);
})
- .catch(() => createFlash({ message: __('There was a problem fetching milestones.') }))
+ .catch(() => {
+ createFlash({ message: __('There was a problem fetching milestones.') });
+ })
.finally(() => {
this.loading = false;
});
},
- searchMilestones: debounce(function debouncedSearch({ data }) {
- this.fetchMilestoneBySearchTerm(data);
- }, DEBOUNCE_DELAY),
},
};
</script>
<template>
- <gl-filtered-search-token
+ <base-token
+ :active="active"
:config="config"
- v-bind="{ ...$props, ...$attrs }"
+ :value="value"
+ :default-suggestions="defaultMilestones"
+ :suggestions="milestones"
+ :suggestions-loading="loading"
+ :get-active-token-value="getActiveMilestone"
+ @fetch-suggestions="fetchMilestones"
v-on="$listeners"
- @input="searchMilestones"
>
- <template #view="{ inputValue }">
- <span>%{{ activeMilestone ? activeMilestone.title : inputValue }}</span>
+ <template #view="{ viewTokenProps: { inputValue, activeTokenValue } }">
+ %{{ activeTokenValue ? activeTokenValue.title : inputValue }}
</template>
- <template #suggestions>
+ <template #suggestions-list="{ suggestions }">
<gl-filtered-search-suggestion
- v-for="milestone in defaultMilestones"
- :key="milestone.value"
- :value="milestone.value"
+ v-for="milestone in suggestions"
+ :key="milestone.id"
+ :value="milestone.title"
>
- {{ milestone.text }}
+ {{ milestone.title }}
</gl-filtered-search-suggestion>
- <gl-dropdown-divider v-if="defaultMilestones.length" />
- <gl-loading-icon v-if="loading" size="sm" />
- <template v-else>
- <gl-filtered-search-suggestion
- v-for="milestone in milestones"
- :key="milestone.id"
- :value="milestone.title"
- >
- <div>{{ milestone.title }}</div>
- </gl-filtered-search-suggestion>
- </template>
</template>
- </gl-filtered-search-token>
+ </base-token>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/weight_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/weight_token.vue
index 72116f0e991..280fb234576 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/weight_token.vue
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/weight_token.vue
@@ -1,15 +1,20 @@
<script>
-import { GlDropdownDivider, GlFilteredSearchSuggestion, GlFilteredSearchToken } from '@gitlab/ui';
-import { DEFAULT_NONE_ANY } from '../constants';
+import { GlFilteredSearchSuggestion } from '@gitlab/ui';
+import BaseToken from '~/vue_shared/components/filtered_search_bar/tokens/base_token.vue';
+import { DEFAULT_NONE_ANY, WEIGHT_TOKEN_SUGGESTIONS_SIZE } from '../constants';
+
+const weights = Array.from(Array(WEIGHT_TOKEN_SUGGESTIONS_SIZE), (_, index) => index.toString());
export default {
- baseWeights: ['0', '1', '2', '3', '4', '5'],
components: {
- GlDropdownDivider,
+ BaseToken,
GlFilteredSearchSuggestion,
- GlFilteredSearchToken,
},
props: {
+ active: {
+ type: Boolean,
+ required: true,
+ },
config: {
type: Object,
required: true,
@@ -21,38 +26,41 @@ export default {
},
data() {
return {
- weights: this.$options.baseWeights,
- defaultWeights: this.config.defaultWeights || DEFAULT_NONE_ANY,
+ weights,
};
},
+ computed: {
+ defaultWeights() {
+ return this.config.defaultWeights || DEFAULT_NONE_ANY;
+ },
+ },
methods: {
- updateWeights({ data }) {
- const weight = parseInt(data, 10);
- this.weights = Number.isNaN(weight) ? this.$options.baseWeights : [String(weight)];
+ getActiveWeight(weightSuggestions, data) {
+ return weightSuggestions.find((weight) => weight === data);
+ },
+ updateWeights(searchTerm) {
+ const weight = parseInt(searchTerm, 10);
+ this.weights = Number.isNaN(weight) ? weights : [String(weight)];
},
},
};
</script>
<template>
- <gl-filtered-search-token
+ <base-token
+ :active="active"
:config="config"
- v-bind="{ ...$props, ...$attrs }"
+ :value="value"
+ :default-suggestions="defaultWeights"
+ :suggestions="weights"
+ :get-active-token-value="getActiveWeight"
+ @fetch-suggestions="updateWeights"
v-on="$listeners"
- @input="updateWeights"
>
- <template #suggestions>
- <gl-filtered-search-suggestion
- v-for="weight in defaultWeights"
- :key="weight.value"
- :value="weight.value"
- >
- {{ weight.text }}
- </gl-filtered-search-suggestion>
- <gl-dropdown-divider v-if="defaultWeights.length" />
- <gl-filtered-search-suggestion v-for="weight of weights" :key="weight" :value="weight">
+ <template #suggestions-list="{ suggestions }">
+ <gl-filtered-search-suggestion v-for="weight of suggestions" :key="weight" :value="weight">
{{ weight }}
</gl-filtered-search-suggestion>
</template>
- </gl-filtered-search-token>
+ </base-token>
</template>