diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-11-08 18:09:34 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-11-08 18:09:34 +0300 |
commit | b5bdf6e5219b3b57107aee49ba7c103affb65dd9 (patch) | |
tree | 54c1ea8b3140d60af9a6c64867edc0a484ef7735 /app/assets/javascripts/search | |
parent | 81f062b841f6062601662061850934a51e77ceea (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/search')
9 files changed, 166 insertions, 37 deletions
diff --git a/app/assets/javascripts/search/sidebar/components/app.vue b/app/assets/javascripts/search/sidebar/components/app.vue index 789efc8f09d..927ae6f6b81 100644 --- a/app/assets/javascripts/search/sidebar/components/app.vue +++ b/app/assets/javascripts/search/sidebar/components/app.vue @@ -1,48 +1,27 @@ <script> -import { GlButton, GlLink } from '@gitlab/ui'; -import { mapActions, mapState } from 'vuex'; -import ConfidentialityFilter from './confidentiality_filter.vue'; -import StatusFilter from './status_filter.vue'; +import { mapState } from 'vuex'; +import ScopeNavigation from '~/search/sidebar/components/scope_navigation.vue'; +import { SCOPE_ISSUES, SCOPE_MERGE_REQUESTS } from '../constants'; +import ResultsFilters from './results_filters.vue'; export default { name: 'GlobalSearchSidebar', components: { - GlButton, - GlLink, - StatusFilter, - ConfidentialityFilter, + ResultsFilters, + ScopeNavigation, }, computed: { - ...mapState(['urlQuery', 'sidebarDirty']), - showReset() { - return this.urlQuery.state || this.urlQuery.confidential; + ...mapState(['urlQuery']), + showFilters() { + return this.urlQuery.scope === SCOPE_ISSUES || this.urlQuery.scope === SCOPE_MERGE_REQUESTS; }, - showSidebar() { - return this.urlQuery.scope === 'issues' || this.urlQuery.scope === 'merge_requests'; - }, - }, - methods: { - ...mapActions(['applyQuery', 'resetQuery']), }, }; </script> <template> - <form - class="search-sidebar gl-display-flex gl-flex-direction-column gl-mr-4 gl-mb-6 gl-mt-5" - @submit.prevent="applyQuery" - > - <template v-if="showSidebar"> - <status-filter /> - <confidentiality-filter /> - <div class="gl-display-flex gl-align-items-center gl-mt-3"> - <gl-button category="primary" variant="confirm" type="submit" :disabled="!sidebarDirty"> - {{ __('Apply') }} - </gl-button> - <gl-link v-if="showReset" class="gl-ml-auto" @click="resetQuery">{{ - __('Reset filters') - }}</gl-link> - </div> - </template> - </form> + <section class="search-sidebar gl-display-flex gl-flex-direction-column gl-mr-4 gl-mb-6 gl-mt-5"> + <scope-navigation /> + <results-filters v-if="showFilters" /> + </section> </template> diff --git a/app/assets/javascripts/search/sidebar/components/results_filters.vue b/app/assets/javascripts/search/sidebar/components/results_filters.vue new file mode 100644 index 00000000000..5b53f94bb53 --- /dev/null +++ b/app/assets/javascripts/search/sidebar/components/results_filters.vue @@ -0,0 +1,49 @@ +<script> +import { GlButton, GlLink } from '@gitlab/ui'; +import { mapActions, mapState } from 'vuex'; +import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; +import ConfidentialityFilter from './confidentiality_filter.vue'; +import StatusFilter from './status_filter.vue'; + +export default { + name: 'ResultsFilters', + components: { + GlButton, + GlLink, + StatusFilter, + ConfidentialityFilter, + }, + mixins: [glFeatureFlagsMixin()], + computed: { + ...mapState(['urlQuery', 'sidebarDirty']), + showReset() { + return this.urlQuery.state || this.urlQuery.confidential; + }, + searchPageVerticalNavFeatureFlag() { + return this.glFeatures.searchPageVerticalNav; + }, + }, + methods: { + ...mapActions(['applyQuery', 'resetQuery']), + }, +}; +</script> + +<template> + <form + :class="searchPageVerticalNavFeatureFlag ? 'gl-px-5' : 'gl-px-0'" + @submit.prevent="applyQuery" + > + <hr v-if="searchPageVerticalNavFeatureFlag" class="gl-my-5 gl-border-gray-100" /> + <status-filter /> + <confidentiality-filter /> + <div class="gl-display-flex gl-align-items-center gl-mt-4"> + <gl-button category="primary" variant="confirm" type="submit" :disabled="!sidebarDirty"> + {{ __('Apply') }} + </gl-button> + <gl-link v-if="showReset" class="gl-ml-auto" @click="resetQuery">{{ + __('Reset filters') + }}</gl-link> + </div> + </form> +</template> diff --git a/app/assets/javascripts/search/sidebar/components/scope_navigation.vue b/app/assets/javascripts/search/sidebar/components/scope_navigation.vue new file mode 100644 index 00000000000..37138955415 --- /dev/null +++ b/app/assets/javascripts/search/sidebar/components/scope_navigation.vue @@ -0,0 +1,66 @@ +<script> +import { GlNav, GlNavItem } from '@gitlab/ui'; +import { mapActions, mapState } from 'vuex'; +import { formatNumber } from '~/locale'; +import Tracking from '~/tracking'; +import { NAV_LINK_DEFAULT_CLASSES, NUMBER_FORMATING_OPTIONS } from '../constants'; + +export default { + name: 'ScopeNavigation', + components: { + GlNav, + GlNavItem, + }, + mixins: [Tracking.mixin()], + computed: { + ...mapState(['navigation', 'urlQuery']), + }, + created() { + this.fetchSidebarCount(); + }, + methods: { + ...mapActions(['fetchSidebarCount']), + activeClasses(currentScope) { + return currentScope === this.urlQuery.scope ? 'gl-font-weight-bold' : ''; + }, + showFormatedCount(count) { + if (!count) { + return '0'; + } + const countNumber = parseInt(count.replace(/,/g, ''), 10); + return formatNumber(countNumber, NUMBER_FORMATING_OPTIONS); + }, + handleClick(scope) { + this.track('click_menu_item', { label: `vertical_navigation_${scope}` }); + }, + linkClasses(scope) { + return [ + { 'gl-font-weight-bold': scope === this.urlQuery.scope }, + ...this.$options.NAV_LINK_DEFAULT_CLASSES, + ]; + }, + }, + NAV_LINK_DEFAULT_CLASSES, +}; +</script> + +<template> + <nav> + <gl-nav vertical pills> + <gl-nav-item + v-for="(item, scope, index) in navigation" + :key="scope" + :link-classes="linkClasses(scope)" + class="gl-mb-1" + :href="item.link" + :active="urlQuery.scope ? urlQuery.scope === scope : index === 0" + @click="handleClick(scope)" + ><span>{{ item.label }}</span + ><span v-if="item.count" class="gl-font-sm gl-font-weight-normal"> + {{ showFormatedCount(item.count) }} + </span> + </gl-nav-item> + </gl-nav> + <hr class="gl-mt-5 gl-mb-0 gl-border-gray-100 gl-md-display-none" /> + </nav> +</template> diff --git a/app/assets/javascripts/search/sidebar/constants/index.js b/app/assets/javascripts/search/sidebar/constants/index.js new file mode 100644 index 00000000000..3621138afe4 --- /dev/null +++ b/app/assets/javascripts/search/sidebar/constants/index.js @@ -0,0 +1,11 @@ +export const SCOPE_ISSUES = 'issues'; +export const SCOPE_MERGE_REQUESTS = 'merge_requests'; + +export const NUMBER_FORMATING_OPTIONS = { notation: 'compact', compactDisplay: 'short' }; +export const NAV_LINK_DEFAULT_CLASSES = [ + 'gl-display-flex', + 'gl-flex-direction-row', + 'gl-flex-wrap-nowrap', + 'gl-justify-content-space-between', + 'gl-text-gray-900', +]; diff --git a/app/assets/javascripts/search/store/actions.js b/app/assets/javascripts/search/store/actions.js index be5742e5949..2a1b744561d 100644 --- a/app/assets/javascripts/search/store/actions.js +++ b/app/assets/javascripts/search/store/actions.js @@ -1,6 +1,8 @@ import Api from '~/api'; import { createAlert } from '~/flash'; +import axios from '~/lib/utils/axios_utils'; import { visitUrl, setUrlParams } from '~/lib/utils/url_utility'; +import { logError } from '~/lib/logger'; import { __ } from '~/locale'; import { GROUPS_LOCAL_STORAGE_KEY, PROJECTS_LOCAL_STORAGE_KEY, SIDEBAR_PARAMS } from './constants'; import * as types from './mutation_types'; @@ -99,3 +101,19 @@ export const applyQuery = ({ state }) => { export const resetQuery = ({ state }) => { visitUrl(setUrlParams({ ...state.query, page: null, state: null, confidential: null })); }; + +export const fetchSidebarCount = ({ commit, state }) => { + const promises = Object.keys(state.navigation).map((scope) => { + // active nav item has count already so we skip it + if (scope !== state.urlQuery.scope) { + return axios + .get(state.navigation[scope].count_link) + .then(({ data: { count } }) => { + commit(types.RECEIVE_NAVIGATION_COUNT, { key: scope, count }); + }) + .catch((e) => logError(e)); + } + return Promise.resolve(); + }); + return Promise.all(promises); +}; diff --git a/app/assets/javascripts/search/store/index.js b/app/assets/javascripts/search/store/index.js index 4fa88822722..e20a43808cf 100644 --- a/app/assets/javascripts/search/store/index.js +++ b/app/assets/javascripts/search/store/index.js @@ -7,11 +7,11 @@ import createState from './state'; Vue.use(Vuex); -export const getStoreConfig = ({ query }) => ({ +export const getStoreConfig = ({ query, navigation }) => ({ actions, getters, mutations, - state: createState({ query }), + state: createState({ query, navigation }), }); const createStore = (config) => new Vuex.Store(getStoreConfig(config)); diff --git a/app/assets/javascripts/search/store/mutation_types.js b/app/assets/javascripts/search/store/mutation_types.js index bf1e3e79cba..511b93cad2b 100644 --- a/app/assets/javascripts/search/store/mutation_types.js +++ b/app/assets/javascripts/search/store/mutation_types.js @@ -10,3 +10,4 @@ export const SET_QUERY = 'SET_QUERY'; export const SET_SIDEBAR_DIRTY = 'SET_SIDEBAR_DIRTY'; export const LOAD_FREQUENT_ITEMS = 'LOAD_FREQUENT_ITEMS'; +export const RECEIVE_NAVIGATION_COUNT = 'RECEIVE_NAVIGATION_COUNT'; diff --git a/app/assets/javascripts/search/store/mutations.js b/app/assets/javascripts/search/store/mutations.js index 5d154fe3aa0..c1339845272 100644 --- a/app/assets/javascripts/search/store/mutations.js +++ b/app/assets/javascripts/search/store/mutations.js @@ -32,4 +32,8 @@ export default { [types.LOAD_FREQUENT_ITEMS](state, { key, data }) { state.frequentItems[key] = data; }, + [types.RECEIVE_NAVIGATION_COUNT](state, { key, count }) { + const item = { ...state.navigation[key], count }; + state.navigation = { ...state.navigation, [key]: item }; + }, }; diff --git a/app/assets/javascripts/search/store/state.js b/app/assets/javascripts/search/store/state.js index d4005697f35..b64231a8688 100644 --- a/app/assets/javascripts/search/store/state.js +++ b/app/assets/javascripts/search/store/state.js @@ -1,7 +1,7 @@ import { cloneDeep } from 'lodash'; import { GROUPS_LOCAL_STORAGE_KEY, PROJECTS_LOCAL_STORAGE_KEY } from './constants'; -const createState = ({ query }) => ({ +const createState = ({ query, navigation }) => ({ urlQuery: cloneDeep(query), query, groups: [], @@ -13,5 +13,6 @@ const createState = ({ query }) => ({ [PROJECTS_LOCAL_STORAGE_KEY]: [], }, sidebarDirty: false, + navigation, }); export default createState; |