diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-02-09 15:09:48 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-02-09 15:09:48 +0300 |
commit | 3c53fbc50bf8d084f1184836468850d2a83ef920 (patch) | |
tree | 85c451a4082e7b5e8dc3ecb6265edb1aef0b14f0 /app/assets/javascripts/search | |
parent | e7462f7b49a60b2ee7be14682c23190f7f7c5ba7 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/search')
-rw-r--r-- | app/assets/javascripts/search/index.js | 2 | ||||
-rw-r--r-- | app/assets/javascripts/search/store/actions.js | 56 | ||||
-rw-r--r-- | app/assets/javascripts/search/store/mutation_types.js | 3 | ||||
-rw-r--r-- | app/assets/javascripts/search/store/mutations.js | 11 | ||||
-rw-r--r-- | app/assets/javascripts/search/store/state.js | 1 | ||||
-rw-r--r-- | app/assets/javascripts/search/topbar/components/app.vue | 66 | ||||
-rw-r--r-- | app/assets/javascripts/search/topbar/components/scope_tabs.vue | 73 | ||||
-rw-r--r-- | app/assets/javascripts/search/topbar/constants.js | 14 | ||||
-rw-r--r-- | app/assets/javascripts/search/topbar/index.js | 6 |
9 files changed, 203 insertions, 29 deletions
diff --git a/app/assets/javascripts/search/index.js b/app/assets/javascripts/search/index.js index 3050b628cd5..0c5d68ab96b 100644 --- a/app/assets/javascripts/search/index.js +++ b/app/assets/javascripts/search/index.js @@ -1,6 +1,5 @@ import setHighlightClass from 'ee_else_ce/search/highlight_blob_search_result'; import Project from '~/pages/projects/project'; -import refreshCounts from '~/pages/search/show/refresh_counts'; import { queryToObject } from '~/lib/utils/url_utility'; import createStore from './store'; import { initTopbar } from './topbar'; @@ -20,6 +19,5 @@ export const initSearchApp = () => { initSearchSort(store); setHighlightClass(query.search); // Code Highlighting - refreshCounts(); // Other Scope Tab Counts Project.initRefSwitcher(); // Code Search Branch Picker }; diff --git a/app/assets/javascripts/search/store/actions.js b/app/assets/javascripts/search/store/actions.js index bdfe966d990..e1722d8dee3 100644 --- a/app/assets/javascripts/search/store/actions.js +++ b/app/assets/javascripts/search/store/actions.js @@ -1,9 +1,30 @@ +import axios from '~/lib/utils/axios_utils'; import Api from '~/api'; import createFlash from '~/flash'; import { __ } from '~/locale'; import { visitUrl, setUrlParams } from '~/lib/utils/url_utility'; import * as types from './mutation_types'; +/* private */ +const getCount = ({ params, state, activeCount }) => { + const globalSearchCountsPath = '/search/count'; + const url = Api.buildUrl(globalSearchCountsPath); + + // count is known for active tab, so return it and skip the Api call + if (params.scope === state.query?.scope) { + return { scope: params.scope, count: activeCount }; + } + + return axios + .get(url, { params }) + .then(({ data }) => { + return { scope: params.scope, count: data.count }; + }) + .catch((e) => { + throw e; + }); +}; + export const fetchGroups = ({ commit }, search) => { commit(types.REQUEST_GROUPS); Api.groups(search) @@ -38,6 +59,21 @@ export const fetchProjects = ({ commit, state }, search) => { } }; +export const fetchSearchCounts = ({ commit, state }, { scopeTabs, activeCount }) => { + commit(types.REQUEST_SEARCH_COUNTS, { scopeTabs, activeCount }); + const promises = scopeTabs.map((scope) => + getCount({ params: { ...state.query, scope }, state, activeCount }), + ); + + Promise.all(promises) + .then((data) => { + commit(types.RECEIVE_SEARCH_COUNTS_SUCCESS, data); + }) + .catch(() => { + createFlash({ message: __('There was an error fetching the Search Counts') }); + }); +}; + export const setQuery = ({ commit }, { key, value }) => { commit(types.SET_QUERY, { key, value }); }; @@ -46,6 +82,22 @@ export const applyQuery = ({ state }) => { visitUrl(setUrlParams({ ...state.query, page: null })); }; -export const resetQuery = ({ state }) => { - visitUrl(setUrlParams({ ...state.query, page: null, state: null, confidential: null })); +export const resetQuery = ({ state }, snippets = false) => { + let defaultQuery = { + page: null, + state: null, + confidential: null, + nav_source: null, + }; + + if (snippets) { + defaultQuery = { + snippets: true, + group_id: null, + project_id: null, + ...defaultQuery, + }; + } + + visitUrl(setUrlParams({ ...state.query, ...defaultQuery })); }; diff --git a/app/assets/javascripts/search/store/mutation_types.js b/app/assets/javascripts/search/store/mutation_types.js index a6430b53c4f..5ba3845bfcc 100644 --- a/app/assets/javascripts/search/store/mutation_types.js +++ b/app/assets/javascripts/search/store/mutation_types.js @@ -6,4 +6,7 @@ export const REQUEST_PROJECTS = 'REQUEST_PROJECTS'; export const RECEIVE_PROJECTS_SUCCESS = 'RECEIVE_PROJECTS_SUCCESS'; export const RECEIVE_PROJECTS_ERROR = 'RECEIVE_PROJECTS_ERROR'; +export const REQUEST_SEARCH_COUNTS = 'REQUEST_SEARCH_COUNTS'; +export const RECEIVE_SEARCH_COUNTS_SUCCESS = 'RECEIVE_SEARCH_COUNTS_SUCCESS'; + export const SET_QUERY = 'SET_QUERY'; diff --git a/app/assets/javascripts/search/store/mutations.js b/app/assets/javascripts/search/store/mutations.js index 91d7cf66c8f..57a577060b3 100644 --- a/app/assets/javascripts/search/store/mutations.js +++ b/app/assets/javascripts/search/store/mutations.js @@ -1,3 +1,4 @@ +import { ALL_SCOPE_TABS } from '~/search/topbar/constants'; import * as types from './mutation_types'; export default { @@ -23,6 +24,16 @@ export default { state.fetchingProjects = false; state.projects = []; }, + [types.REQUEST_SEARCH_COUNTS](state, { scopeTabs, activeCount }) { + state.inflatedScopeTabs = scopeTabs.map((tab) => { + return { ...ALL_SCOPE_TABS[tab], count: tab === state.query?.scope ? activeCount : '' }; + }); + }, + [types.RECEIVE_SEARCH_COUNTS_SUCCESS](state, data) { + state.inflatedScopeTabs = data.map((tab) => { + return { ...ALL_SCOPE_TABS[tab.scope], count: tab.count }; + }); + }, [types.SET_QUERY](state, { key, value }) { state.query[key] = value; }, diff --git a/app/assets/javascripts/search/store/state.js b/app/assets/javascripts/search/store/state.js index 9a0d61d0b93..9528b0400a4 100644 --- a/app/assets/javascripts/search/store/state.js +++ b/app/assets/javascripts/search/store/state.js @@ -4,5 +4,6 @@ const createState = ({ query }) => ({ fetchingGroups: false, projects: [], fetchingProjects: false, + inflatedScopeTabs: [], }); export default createState; diff --git a/app/assets/javascripts/search/topbar/components/app.vue b/app/assets/javascripts/search/topbar/components/app.vue index 639cff591c3..c858f1fcc42 100644 --- a/app/assets/javascripts/search/topbar/components/app.vue +++ b/app/assets/javascripts/search/topbar/components/app.vue @@ -3,6 +3,7 @@ import { mapState, mapActions } from 'vuex'; import { GlForm, GlSearchBoxByType, GlButton } from '@gitlab/ui'; import GroupFilter from './group_filter.vue'; import ProjectFilter from './project_filter.vue'; +import ScopeTabs from './scope_tabs.vue'; export default { name: 'GlobalSearchTopbar', @@ -12,6 +13,7 @@ export default { GroupFilter, ProjectFilter, GlButton, + ScopeTabs, }, props: { groupInitialData: { @@ -24,6 +26,16 @@ export default { required: false, default: () => ({}), }, + scopeTabs: { + type: Array, + required: false, + default: () => [], + }, + count: { + type: String, + required: false, + default: '', + }, }, computed: { ...mapState(['query']), @@ -38,6 +50,9 @@ export default { showFilters() { return !this.query.snippets || this.query.snippets === 'false'; }, + showScopeTabs() { + return this.query.search; + }, }, methods: { ...mapActions(['applyQuery', 'setQuery']), @@ -46,28 +61,31 @@ export default { </script> <template> - <gl-form class="search-page-form" @submit.prevent="applyQuery"> - <section class="gl-lg-display-flex gl-align-items-flex-end"> - <div class="gl-flex-fill-1 gl-mb-4 gl-lg-mb-0 gl-lg-mr-2"> - <label>{{ __('What are you searching for?') }}</label> - <gl-search-box-by-type - id="dashboard_search" - v-model="search" - name="search" - :placeholder="__(`Search for projects, issues, etc.`)" - /> - </div> - <div v-if="showFilters" class="gl-mb-4 gl-lg-mb-0 gl-lg-mx-2"> - <label class="gl-display-block">{{ __('Group') }}</label> - <group-filter :initial-data="groupInitialData" /> - </div> - <div v-if="showFilters" class="gl-mb-4 gl-lg-mb-0 gl-lg-mx-2"> - <label class="gl-display-block">{{ __('Project') }}</label> - <project-filter :initial-data="projectInitialData" /> - </div> - <gl-button class="btn-search gl-lg-ml-2" variant="success" type="submit">{{ - __('Search') - }}</gl-button> - </section> - </gl-form> + <section> + <gl-form class="search-page-form" @submit.prevent="applyQuery"> + <section class="gl-lg-display-flex gl-align-items-flex-end"> + <div class="gl-flex-fill-1 gl-mb-4 gl-lg-mb-0 gl-lg-mr-2"> + <label>{{ __('What are you searching for?') }}</label> + <gl-search-box-by-type + id="dashboard_search" + v-model="search" + name="search" + :placeholder="__(`Search for projects, issues, etc.`)" + /> + </div> + <div v-if="showFilters" class="gl-mb-4 gl-lg-mb-0 gl-lg-mx-2"> + <label class="gl-display-block">{{ __('Group') }}</label> + <group-filter :initial-data="groupInitialData" /> + </div> + <div v-if="showFilters" class="gl-mb-4 gl-lg-mb-0 gl-lg-mx-2"> + <label class="gl-display-block">{{ __('Project') }}</label> + <project-filter :initial-data="projectInitialData" /> + </div> + <gl-button class="btn-search gl-lg-ml-2" variant="success" type="submit">{{ + __('Search') + }}</gl-button> + </section> + </gl-form> + <scope-tabs v-if="showScopeTabs" :scope-tabs="scopeTabs" :count="count" /> + </section> </template> diff --git a/app/assets/javascripts/search/topbar/components/scope_tabs.vue b/app/assets/javascripts/search/topbar/components/scope_tabs.vue new file mode 100644 index 00000000000..83264a1b411 --- /dev/null +++ b/app/assets/javascripts/search/topbar/components/scope_tabs.vue @@ -0,0 +1,73 @@ +<script> +import { GlTabs, GlTab, GlBadge } from '@gitlab/ui'; +import { mapState, mapActions } from 'vuex'; + +export default { + name: 'ScopeTabs', + components: { + GlTabs, + GlTab, + GlBadge, + }, + props: { + scopeTabs: { + type: Array, + required: true, + }, + count: { + type: String, + required: false, + default: '', + }, + }, + computed: { + ...mapState(['query', 'inflatedScopeTabs']), + }, + created() { + this.fetchSearchCounts({ scopeTabs: this.scopeTabs, activeCount: this.count }); + }, + methods: { + ...mapActions(['fetchSearchCounts', 'setQuery', 'resetQuery']), + handleTabChange(scope) { + this.setQuery({ key: 'scope', value: scope }); + this.resetQuery(scope === 'snippet_titles'); + }, + isTabActive(scope) { + return scope === this.query.scope; + }, + }, +}; +</script> + +<template> + <div> + <gl-tabs + content-class="gl-p-0" + nav-class="search-filter search-nav-tabs gl-display-flex gl-overflow-x-auto" + > + <gl-tab + v-for="tab in inflatedScopeTabs" + :key="tab.scope" + class="gl-display-flex" + :active="isTabActive(tab.scope)" + :data-testid="`tab-${tab.scope}`" + :title-link-attributes="{ 'data-qa-selector': tab.qaSelector }" + title-link-class="gl-white-space-nowrap" + @click="handleTabChange(tab.scope)" + > + <template #title> + <span data-testid="tab-title"> {{ tab.title }} </span> + <gl-badge + v-show="tab.count" + :data-scope="tab.scope" + :data-testid="`badge-${tab.scope}`" + :variant="isTabActive(tab.scope) ? 'neutral' : 'muted'" + size="sm" + > + {{ tab.count }} + </gl-badge> + </template> + </gl-tab> + </gl-tabs> + </div> +</template> diff --git a/app/assets/javascripts/search/topbar/constants.js b/app/assets/javascripts/search/topbar/constants.js index 3944b2c8374..ec65667cde3 100644 --- a/app/assets/javascripts/search/topbar/constants.js +++ b/app/assets/javascripts/search/topbar/constants.js @@ -19,3 +19,17 @@ export const PROJECT_DATA = { selectedDisplayValue: 'name_with_namespace', itemsDisplayValue: 'name_with_namespace', }; + +export const ALL_SCOPE_TABS = { + blobs: { scope: 'blobs', title: __('Code'), qaSelector: 'code_tab' }, + issues: { scope: 'issues', title: __('Issues') }, + merge_requests: { scope: 'merge_requests', title: __('Merge requests') }, + milestones: { scope: 'milestones', title: __('Milestones') }, + notes: { scope: 'notes', title: __('Comments') }, + wiki_blobs: { scope: 'wiki_blobs', title: __('Wiki') }, + commits: { scope: 'commits', title: __('Commits') }, + epics: { scope: 'epics', title: __('Epics') }, + users: { scope: 'users', title: __('Users') }, + snippet_titles: { scope: 'snippet_titles', title: __('Titles and Descriptions') }, + projects: { scope: 'projects', title: __('Projects'), qaSelector: 'projects_tab' }, +}; diff --git a/app/assets/javascripts/search/topbar/index.js b/app/assets/javascripts/search/topbar/index.js index 87316e10e8d..f9564b5cfbb 100644 --- a/app/assets/javascripts/search/topbar/index.js +++ b/app/assets/javascripts/search/topbar/index.js @@ -11,10 +11,12 @@ export const initTopbar = (store) => { return false; } - let { groupInitialData, projectInitialData } = el.dataset; + let { groupInitialData, projectInitialData, scopeTabs } = el.dataset; + const { count } = el.dataset; groupInitialData = JSON.parse(groupInitialData); projectInitialData = JSON.parse(projectInitialData); + scopeTabs = JSON.parse(scopeTabs); return new Vue({ el, @@ -24,6 +26,8 @@ export const initTopbar = (store) => { props: { groupInitialData, projectInitialData, + scopeTabs, + count, }, }); }, |