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-02-09 15:09:48 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-02-09 15:09:48 +0300
commit3c53fbc50bf8d084f1184836468850d2a83ef920 (patch)
tree85c451a4082e7b5e8dc3ecb6265edb1aef0b14f0 /app/assets/javascripts/search
parente7462f7b49a60b2ee7be14682c23190f7f7c5ba7 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/search')
-rw-r--r--app/assets/javascripts/search/index.js2
-rw-r--r--app/assets/javascripts/search/store/actions.js56
-rw-r--r--app/assets/javascripts/search/store/mutation_types.js3
-rw-r--r--app/assets/javascripts/search/store/mutations.js11
-rw-r--r--app/assets/javascripts/search/store/state.js1
-rw-r--r--app/assets/javascripts/search/topbar/components/app.vue66
-rw-r--r--app/assets/javascripts/search/topbar/components/scope_tabs.vue73
-rw-r--r--app/assets/javascripts/search/topbar/constants.js14
-rw-r--r--app/assets/javascripts/search/topbar/index.js6
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,
},
});
},