From 79f759cc144c7020942f09762154c6758ee1d275 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Tue, 21 Sep 2021 18:11:18 +0000 Subject: Add latest changes from gitlab-org/gitlab@master --- .../javascripts/header_search/components/app.vue | 15 ++++- .../header_search_autocomplete_items.vue | 74 ++++++++++++++++++++++ app/assets/javascripts/header_search/constants.js | 8 +++ app/assets/javascripts/header_search/index.js | 4 +- .../javascripts/header_search/store/actions.js | 14 ++++ .../javascripts/header_search/store/getters.js | 32 ++++++++++ .../javascripts/header_search/store/index.js | 10 ++- .../header_search/store/mutation_types.js | 4 ++ .../javascripts/header_search/store/mutations.js | 12 ++++ .../javascripts/header_search/store/state.js | 5 +- 10 files changed, 171 insertions(+), 7 deletions(-) create mode 100644 app/assets/javascripts/header_search/components/header_search_autocomplete_items.vue (limited to 'app/assets/javascripts/header_search') diff --git a/app/assets/javascripts/header_search/components/app.vue b/app/assets/javascripts/header_search/components/app.vue index 580c27f6c61..c6590fd8eb3 100644 --- a/app/assets/javascripts/header_search/components/app.vue +++ b/app/assets/javascripts/header_search/components/app.vue @@ -3,6 +3,7 @@ import { GlSearchBoxByType, GlOutsideDirective as Outside } from '@gitlab/ui'; import { mapState, mapActions, mapGetters } from 'vuex'; import { visitUrl } from '~/lib/utils/url_utility'; import { __ } from '~/locale'; +import HeaderSearchAutocompleteItems from './header_search_autocomplete_items.vue'; import HeaderSearchDefaultItems from './header_search_default_items.vue'; import HeaderSearchScopedItems from './header_search_scoped_items.vue'; @@ -16,6 +17,7 @@ export default { GlSearchBoxByType, HeaderSearchDefaultItems, HeaderSearchScopedItems, + HeaderSearchAutocompleteItems, }, data() { return { @@ -41,7 +43,7 @@ export default { }, }, methods: { - ...mapActions(['setSearch']), + ...mapActions(['setSearch', 'fetchAutocompleteOptions']), openDropdown() { this.showDropdown = true; }, @@ -51,6 +53,13 @@ export default { submitSearch() { return visitUrl(this.searchQuery); }, + getAutocompleteOptions(searchTerm) { + if (!searchTerm) { + return; + } + + this.fetchAutocompleteOptions(); + }, }, }; @@ -64,18 +73,20 @@ export default { :placeholder="$options.i18n.searchPlaceholder" @focus="openDropdown" @click="openDropdown" + @input="getAutocompleteOptions" @keydown.enter="submitSearch" @keydown.esc="closeDropdown" />
diff --git a/app/assets/javascripts/header_search/components/header_search_autocomplete_items.vue b/app/assets/javascripts/header_search/components/header_search_autocomplete_items.vue new file mode 100644 index 00000000000..9bea2b280f7 --- /dev/null +++ b/app/assets/javascripts/header_search/components/header_search_autocomplete_items.vue @@ -0,0 +1,74 @@ + + + diff --git a/app/assets/javascripts/header_search/constants.js b/app/assets/javascripts/header_search/constants.js index fffed7bcbdb..2fadb1bd1ee 100644 --- a/app/assets/javascripts/header_search/constants.js +++ b/app/assets/javascripts/header_search/constants.js @@ -15,3 +15,11 @@ export const MSG_IN_ALL_GITLAB = __('in all GitLab'); export const MSG_IN_GROUP = __('in group'); export const MSG_IN_PROJECT = __('in project'); + +export const GROUPS_CATEGORY = 'Groups'; + +export const PROJECTS_CATEGORY = 'Projects'; + +export const LARGE_AVATAR_PX = 32; + +export const SMALL_AVATAR_PX = 16; diff --git a/app/assets/javascripts/header_search/index.js b/app/assets/javascripts/header_search/index.js index 2d37ee137fc..d7e21f55ea5 100644 --- a/app/assets/javascripts/header_search/index.js +++ b/app/assets/javascripts/header_search/index.js @@ -12,13 +12,13 @@ export const initHeaderSearchApp = () => { return false; } - const { searchPath, issuesPath, mrPath } = el.dataset; + const { searchPath, issuesPath, mrPath, autocompletePath } = el.dataset; let { searchContext } = el.dataset; searchContext = JSON.parse(searchContext); return new Vue({ el, - store: createStore({ searchPath, issuesPath, mrPath, searchContext }), + store: createStore({ searchPath, issuesPath, mrPath, autocompletePath, searchContext }), render(createElement) { return createElement(HeaderSearchApp); }, diff --git a/app/assets/javascripts/header_search/store/actions.js b/app/assets/javascripts/header_search/store/actions.js index 841aee04029..2c3b1bd4c0f 100644 --- a/app/assets/javascripts/header_search/store/actions.js +++ b/app/assets/javascripts/header_search/store/actions.js @@ -1,5 +1,19 @@ +import createFlash from '~/flash'; +import axios from '~/lib/utils/axios_utils'; +import { __ } from '~/locale'; import * as types from './mutation_types'; +export const fetchAutocompleteOptions = ({ commit, getters }) => { + commit(types.REQUEST_AUTOCOMPLETE); + return axios + .get(getters.autocompleteQuery) + .then(({ data }) => commit(types.RECEIVE_AUTOCOMPLETE_SUCCESS, data)) + .catch(() => { + commit(types.RECEIVE_AUTOCOMPLETE_ERROR); + createFlash({ message: __('There was an error fetching search autocomplete suggestions') }); + }); +}; + export const setSearch = ({ commit }, value) => { commit(types.SET_SEARCH, value); }; diff --git a/app/assets/javascripts/header_search/store/getters.js b/app/assets/javascripts/header_search/store/getters.js index d1e1fc8ad73..3f4e231ca55 100644 --- a/app/assets/javascripts/header_search/store/getters.js +++ b/app/assets/javascripts/header_search/store/getters.js @@ -23,6 +23,16 @@ export const searchQuery = (state) => { return `${state.searchPath}?${objectToQuery(query)}`; }; +export const autocompleteQuery = (state) => { + const query = { + term: state.search, + project_id: state.searchContext.project?.id, + project_ref: state.searchContext.ref, + }; + + return `${state.autocompletePath}?${objectToQuery(query)}`; +}; + export const scopedIssuesPath = (state) => { return ( state.searchContext.project_metadata?.issues_path || @@ -133,3 +143,25 @@ export const scopedSearchOptions = (state, getters) => { return options; }; + +export const autocompleteGroupedSearchOptions = (state) => { + const groupedOptions = {}; + const results = []; + + state.autocompleteOptions.forEach((option) => { + const category = groupedOptions[option.category]; + + if (category) { + category.data.push(option); + } else { + groupedOptions[option.category] = { + category: option.category, + data: [option], + }; + + results.push(groupedOptions[option.category]); + } + }); + + return results; +}; diff --git a/app/assets/javascripts/header_search/store/index.js b/app/assets/javascripts/header_search/store/index.js index 8b74f8662a5..06cca4be8a7 100644 --- a/app/assets/javascripts/header_search/store/index.js +++ b/app/assets/javascripts/header_search/store/index.js @@ -7,11 +7,17 @@ import createState from './state'; Vue.use(Vuex); -export const getStoreConfig = ({ searchPath, issuesPath, mrPath, searchContext }) => ({ +export const getStoreConfig = ({ + searchPath, + issuesPath, + mrPath, + autocompletePath, + searchContext, +}) => ({ actions, getters, mutations, - state: createState({ searchPath, issuesPath, mrPath, searchContext }), + state: createState({ searchPath, issuesPath, mrPath, autocompletePath, searchContext }), }); const createStore = (config) => new Vuex.Store(getStoreConfig(config)); diff --git a/app/assets/javascripts/header_search/store/mutation_types.js b/app/assets/javascripts/header_search/store/mutation_types.js index 0bc94ae055f..a2358621ce6 100644 --- a/app/assets/javascripts/header_search/store/mutation_types.js +++ b/app/assets/javascripts/header_search/store/mutation_types.js @@ -1 +1,5 @@ +export const REQUEST_AUTOCOMPLETE = 'REQUEST_AUTOCOMPLETE'; +export const RECEIVE_AUTOCOMPLETE_SUCCESS = 'RECEIVE_AUTOCOMPLETE_SUCCESS'; +export const RECEIVE_AUTOCOMPLETE_ERROR = 'RECEIVE_AUTOCOMPLETE_ERROR'; + export const SET_SEARCH = 'SET_SEARCH'; diff --git a/app/assets/javascripts/header_search/store/mutations.js b/app/assets/javascripts/header_search/store/mutations.js index 5b1438929d4..175b5406540 100644 --- a/app/assets/javascripts/header_search/store/mutations.js +++ b/app/assets/javascripts/header_search/store/mutations.js @@ -1,6 +1,18 @@ import * as types from './mutation_types'; export default { + [types.REQUEST_AUTOCOMPLETE](state) { + state.loading = true; + state.autocompleteOptions = []; + }, + [types.RECEIVE_AUTOCOMPLETE_SUCCESS](state, data) { + state.loading = false; + state.autocompleteOptions = data; + }, + [types.RECEIVE_AUTOCOMPLETE_ERROR](state) { + state.loading = false; + state.autocompleteOptions = []; + }, [types.SET_SEARCH](state, value) { state.search = value; }, diff --git a/app/assets/javascripts/header_search/store/state.js b/app/assets/javascripts/header_search/store/state.js index fb2c83dbbe3..3d4073f0583 100644 --- a/app/assets/javascripts/header_search/store/state.js +++ b/app/assets/javascripts/header_search/store/state.js @@ -1,8 +1,11 @@ -const createState = ({ searchPath, issuesPath, mrPath, searchContext }) => ({ +const createState = ({ searchPath, issuesPath, mrPath, autocompletePath, searchContext }) => ({ searchPath, issuesPath, mrPath, + autocompletePath, searchContext, search: '', + autocompleteOptions: [], + loading: false, }); export default createState; -- cgit v1.2.1