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:
-rw-r--r--.gitlab/ci/rules.gitlab-ci.yml4
-rw-r--r--.rubocop_todo/layout/argument_alignment.yml1
-rw-r--r--app/assets/javascripts/search/sidebar/components/app.vue11
-rw-r--r--app/assets/javascripts/search/sidebar/components/confidentiality_filter.vue2
-rw-r--r--app/assets/javascripts/search/sidebar/components/issues_filters.vue73
-rw-r--r--app/assets/javascripts/search/sidebar/components/language_filter/checkbox_filter.vue91
-rw-r--r--app/assets/javascripts/search/sidebar/components/language_filter/index.vue119
-rw-r--r--app/assets/javascripts/search/sidebar/components/radio_filter.vue4
-rw-r--r--app/assets/javascripts/search/sidebar/components/scope_navigation.vue2
-rw-r--r--app/assets/javascripts/search/sidebar/components/status_filter.vue2
-rw-r--r--app/assets/javascripts/search/sidebar/constants/index.js7
-rw-r--r--app/assets/javascripts/search/store/actions.js2
-rw-r--r--app/assets/stylesheets/page_bundles/search.scss59
-rw-r--r--app/policies/group_policy.rb1
-rw-r--r--app/views/search/_results.html.haml2
-rw-r--r--app/views/search/_results_list.html.haml3
-rw-r--r--config/initializers/00_deprecations.rb5
-rw-r--r--qa/Gemfile2
-rw-r--r--qa/Gemfile.lock4
-rw-r--r--spec/frontend/search/sidebar/components/app_spec.js4
-rw-r--r--spec/frontend/search/sidebar/components/checkbox_filter_spec.js2
-rw-r--r--spec/frontend/search/sidebar/components/filters_spec.js4
-rw-r--r--spec/frontend/search/sidebar/components/language_filter_spec.js39
-rw-r--r--spec/frontend/search/store/actions_spec.js10
-rw-r--r--spec/support/shared_contexts/policies/group_policy_shared_context.rb1
25 files changed, 348 insertions, 106 deletions
diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml
index 4c473051ff5..f57662acac9 100644
--- a/.gitlab/ci/rules.gitlab-ci.yml
+++ b/.gitlab/ci/rules.gitlab-ci.yml
@@ -12,10 +12,10 @@
if: '$CI_PROJECT_NAME != "gitlab-foss" && $CI_PROJECT_NAME != "gitlab-ce" && $CI_PROJECT_NAME != "gitlabhq"'
.if-jh: &if-jh
- # Example of these projects:
+ # Matches these two projects:
# https://jihulab.com/gitlab-cn/gitlab
# https://gitlab.com/gitlab-org-sandbox/gitlab-jh-validation
- if: '$CI_PROJECT_PATH =~ /^gitlab-(jh|cn)\/.*/ || $CI_PROJECT_NAME =~ /^gitlab-jh/'
+ if: '$CI_PROJECT_PATH == "gitlab-cn/gitlab" || $CI_PROJECT_PATH == "gitlab-org-sandbox/gitlab-jh-validation"'
.if-force-ci: &if-force-ci
if: '$FORCE_GITLAB_CI'
diff --git a/.rubocop_todo/layout/argument_alignment.yml b/.rubocop_todo/layout/argument_alignment.yml
index 35b1c4e50ac..ddc6a703e24 100644
--- a/.rubocop_todo/layout/argument_alignment.yml
+++ b/.rubocop_todo/layout/argument_alignment.yml
@@ -971,7 +971,6 @@ Layout/ArgumentAlignment:
- 'ee/app/graphql/types/work_items/widgets/weight_input_type.rb'
- 'ee/app/models/ee/ci/secure_file.rb'
- 'ee/app/models/ee/namespace.rb'
- - 'ee/app/models/ee/namespace/storage/notification.rb'
- 'ee/app/models/gitlab_subscriptions/upcoming_reconciliation.rb'
- 'ee/app/models/group_merge_request_approval_setting.rb'
- 'ee/app/models/incident_management/escalation_rule.rb'
diff --git a/app/assets/javascripts/search/sidebar/components/app.vue b/app/assets/javascripts/search/sidebar/components/app.vue
index 317145d4cd1..af4d93dc033 100644
--- a/app/assets/javascripts/search/sidebar/components/app.vue
+++ b/app/assets/javascripts/search/sidebar/components/app.vue
@@ -4,13 +4,13 @@ import ScopeNavigation from '~/search/sidebar/components/scope_navigation.vue';
import ScopeNewNavigation from '~/search/sidebar/components/scope_new_navigation.vue';
import SidebarPortal from '~/super_sidebar/components/sidebar_portal.vue';
import { SCOPE_ISSUES, SCOPE_MERGE_REQUESTS, SCOPE_BLOB } from '../constants';
-import ResultsFilters from './results_filters.vue';
+import IssuesFilters from './issues_filters.vue';
import LanguageFilter from './language_filter/index.vue';
export default {
name: 'GlobalSearchSidebar',
components: {
- ResultsFilters,
+ IssuesFilters,
ScopeNavigation,
ScopeNewNavigation,
LanguageFilter,
@@ -25,6 +25,9 @@ export default {
showBlobFilter() {
return this.currentScope === SCOPE_BLOB;
},
+ showLabelFilter() {
+ return this.currentScope === SCOPE_ISSUES;
+ },
showOldNavigation() {
return Boolean(this.currentScope);
},
@@ -36,7 +39,7 @@ export default {
<section v-if="useNewNavigation">
<sidebar-portal>
<scope-new-navigation />
- <results-filters v-if="showIssueAndMergeFilters" />
+ <issues-filters v-if="showIssueAndMergeFilters" />
<language-filter v-if="showBlobFilter" />
</sidebar-portal>
</section>
@@ -45,7 +48,7 @@ export default {
class="search-sidebar gl-display-flex gl-flex-direction-column gl-md-mr-5 gl-mb-6 gl-mt-5"
>
<scope-navigation />
- <results-filters v-if="showIssueAndMergeFilters" />
+ <issues-filters v-if="showIssueAndMergeFilters" />
<language-filter v-if="showBlobFilter" />
</section>
</template>
diff --git a/app/assets/javascripts/search/sidebar/components/confidentiality_filter.vue b/app/assets/javascripts/search/sidebar/components/confidentiality_filter.vue
index 56e44d454a1..2a7988cd4c6 100644
--- a/app/assets/javascripts/search/sidebar/components/confidentiality_filter.vue
+++ b/app/assets/javascripts/search/sidebar/components/confidentiality_filter.vue
@@ -19,7 +19,7 @@ export default {
<template>
<div>
- <radio-filter class="gl-px-5" :filter-data="$options.confidentialFilterData" />
+ <radio-filter :filter-data="$options.confidentialFilterData" />
<hr v-if="!useNewNavigation" :class="$options.HR_DEFAULT_CLASSES" />
</div>
</template>
diff --git a/app/assets/javascripts/search/sidebar/components/issues_filters.vue b/app/assets/javascripts/search/sidebar/components/issues_filters.vue
new file mode 100644
index 00000000000..2ab5dfb8dea
--- /dev/null
+++ b/app/assets/javascripts/search/sidebar/components/issues_filters.vue
@@ -0,0 +1,73 @@
+<script>
+import { GlButton, GlLink } from '@gitlab/ui';
+import { mapActions, mapState, mapGetters } from 'vuex';
+import Tracking from '~/tracking';
+import {
+ HR_DEFAULT_CLASSES,
+ TRACKING_ACTION_CLICK,
+ TRACKING_LABEL_APPLY,
+ TRACKING_CATEGORY,
+ TRACKING_LABEL_RESET,
+} from '../constants/index';
+import { confidentialFilterData } from '../constants/confidential_filter_data';
+import { stateFilterData } from '../constants/state_filter_data';
+import ConfidentialityFilter from './confidentiality_filter.vue';
+import StatusFilter from './status_filter.vue';
+
+export default {
+ name: 'IssuesFilters',
+ components: {
+ GlButton,
+ GlLink,
+ StatusFilter,
+ ConfidentialityFilter,
+ },
+ computed: {
+ ...mapState(['urlQuery', 'sidebarDirty', 'useNewNavigation']),
+ ...mapGetters(['currentScope']),
+ showReset() {
+ return this.urlQuery.state || this.urlQuery.confidential || this.urlQuery.labels;
+ },
+ showConfidentialityFilter() {
+ return Object.values(confidentialFilterData.scopes).includes(this.currentScope);
+ },
+ showStatusFilter() {
+ return Object.values(stateFilterData.scopes).includes(this.currentScope);
+ },
+ hrClasses() {
+ return [...HR_DEFAULT_CLASSES, 'gl-display-none', 'gl-md-display-block'];
+ },
+ },
+ methods: {
+ ...mapActions(['applyQuery', 'resetQuery']),
+ applyQueryWithTracking() {
+ Tracking.event(TRACKING_ACTION_CLICK, TRACKING_LABEL_APPLY, {
+ label: TRACKING_CATEGORY,
+ });
+ this.applyQuery();
+ },
+ resetQueryWithTracking() {
+ Tracking.event(TRACKING_ACTION_CLICK, TRACKING_LABEL_RESET, {
+ label: TRACKING_CATEGORY,
+ });
+ this.resetQuery();
+ },
+ },
+};
+</script>
+
+<template>
+ <form class="issue-filters gl-px-5 gl-pt-0" @submit.prevent="applyQueryWithTracking">
+ <hr v-if="!useNewNavigation" :class="hrClasses" />
+ <status-filter v-if="showStatusFilter" class="gl-mb-5" />
+ <confidentiality-filter v-if="showConfidentialityFilter" class="gl-mb-5" />
+ <div class="gl-display-flex gl-align-items-center gl-mt-5">
+ <gl-button category="primary" variant="confirm" type="submit" :disabled="!sidebarDirty">
+ {{ __('Apply') }}
+ </gl-button>
+ <gl-link v-if="showReset" class="gl-ml-auto" @click="resetQueryWithTracking">{{
+ __('Reset filters')
+ }}</gl-link>
+ </div>
+ </form>
+</template>
diff --git a/app/assets/javascripts/search/sidebar/components/language_filter/checkbox_filter.vue b/app/assets/javascripts/search/sidebar/components/language_filter/checkbox_filter.vue
new file mode 100644
index 00000000000..b820ca837bc
--- /dev/null
+++ b/app/assets/javascripts/search/sidebar/components/language_filter/checkbox_filter.vue
@@ -0,0 +1,91 @@
+<script>
+import Vue from 'vue';
+import { GlFormCheckboxGroup, GlFormCheckbox } from '@gitlab/ui';
+import { mapState, mapActions, mapGetters } from 'vuex';
+import { intersection } from 'lodash';
+import Tracking from '~/tracking';
+import { NAV_LINK_COUNT_DEFAULT_CLASSES, LABEL_DEFAULT_CLASSES } from '../../constants';
+import { formatSearchResultCount } from '../../../store/utils';
+
+export const TRACKING_LABEL_SET = 'set';
+export const TRACKING_LABEL_CHECKBOX = 'checkbox';
+
+export default {
+ name: 'CheckboxFilter',
+ components: {
+ GlFormCheckboxGroup,
+ GlFormCheckbox,
+ },
+ props: {
+ filtersData: {
+ type: Object,
+ required: true,
+ },
+ trackingNamespace: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ ...mapState(['query', 'useNewNavigation']),
+ ...mapGetters(['queryLanguageFilters']),
+ dataFilters() {
+ return Object.values(this.filtersData?.filters || []);
+ },
+ flatDataFilterValues() {
+ return this.dataFilters.map(({ value }) => value);
+ },
+ selectedFilter: {
+ get() {
+ return intersection(this.flatDataFilterValues, this.queryLanguageFilters);
+ },
+ async set(value) {
+ this.setQuery({ key: this.filtersData?.filterParam, value });
+
+ await Vue.nextTick();
+ this.trackSelectCheckbox();
+ },
+ },
+ labelCountClasses() {
+ return [...NAV_LINK_COUNT_DEFAULT_CLASSES, 'gl-text-gray-500'];
+ },
+ },
+ methods: {
+ ...mapActions(['setQuery']),
+ getFormattedCount(count) {
+ return formatSearchResultCount(count);
+ },
+ trackSelectCheckbox() {
+ Tracking.event(this.trackingNamespace, TRACKING_LABEL_CHECKBOX, {
+ label: TRACKING_LABEL_SET,
+ property: this.selectedFilter,
+ });
+ },
+ },
+ NAV_LINK_COUNT_DEFAULT_CLASSES,
+ LABEL_DEFAULT_CLASSES,
+};
+</script>
+
+<template>
+ <gl-form-checkbox-group v-model="selectedFilter">
+ <gl-form-checkbox
+ v-for="f in dataFilters"
+ :key="f.label"
+ :value="f.label"
+ class="gl-flex-grow-1 gl-display-inline-flex gl-justify-content-space-between gl-w-full"
+ :class="$options.LABEL_DEFAULT_CLASSES"
+ >
+ <span
+ class="gl-flex-grow-1 gl-display-inline-flex gl-justify-content-space-between gl-w-full"
+ >
+ <span data-testid="label">
+ {{ f.label }}
+ </span>
+ <span v-if="f.count" :class="labelCountClasses" data-testid="labelCount">
+ {{ getFormattedCount(f.count) }}
+ </span>
+ </span>
+ </gl-form-checkbox>
+ </gl-form-checkbox-group>
+</template>
diff --git a/app/assets/javascripts/search/sidebar/components/language_filter/index.vue b/app/assets/javascripts/search/sidebar/components/language_filter/index.vue
index 40b50f657f0..e531abf523b 100644
--- a/app/assets/javascripts/search/sidebar/components/language_filter/index.vue
+++ b/app/assets/javascripts/search/sidebar/components/language_filter/index.vue
@@ -5,7 +5,7 @@ import { __, s__, sprintf } from '~/locale';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { HR_DEFAULT_CLASSES, ONLY_SHOW_MD } from '../../constants';
import { convertFiltersData } from '../../utils';
-import CheckboxFilter from '../checkbox_filter.vue';
+import CheckboxFilter from './checkbox_filter.vue';
import {
trackShowMore,
trackShowHasOverMax,
@@ -14,7 +14,7 @@ import {
TRACKING_ACTION_SELECT,
} from './tracking';
-import { DEFAULT_ITEM_LENGTH, MAX_ITEM_LENGTH } from './data';
+import { DEFAULT_ITEM_LENGTH, MAX_ITEM_LENGTH, languageFilterData } from './data';
export default {
name: 'LanguageFilter',
@@ -65,22 +65,25 @@ export default {
hasOverMax() {
return this.languageAggregationBuckets.length > MAX_ITEM_LENGTH;
},
- dividerClasses() {
+ dividerClassesTop() {
return [...HR_DEFAULT_CLASSES, ...ONLY_SHOW_MD];
},
+ dividerClassesBottom() {
+ return [...HR_DEFAULT_CLASSES, 'gl-mt-5'];
+ },
hasQueryFilters() {
return this.queryLanguageFilters.length > 0;
},
},
async created() {
- await this.fetchLanguageAggregation();
+ await this.fetchAllAggregation();
},
methods: {
...mapActions([
'applyQuery',
'resetLanguageQuery',
'resetLanguageQueryWithRedirect',
- 'fetchLanguageAggregation',
+ 'fetchAllAggregation',
]),
onShowMore() {
this.showAll = true;
@@ -108,69 +111,73 @@ export default {
},
HR_DEFAULT_CLASSES,
TRACKING_ACTION_SELECT,
+ languageFilterData,
};
</script>
<template>
- <gl-form
- v-if="hasBuckets"
- class="gl-pt-5 gl-md-pt-0 language-filter-checkbox"
- @submit.prevent="submitQuery"
- >
- <hr v-if="!useNewNavigation" :class="dividerClasses" />
- <div
- v-if="!aggregations.error"
- class="gl-overflow-x-hidden gl-overflow-y-auto"
- :class="{ 'language-filter-max-height': showAll }"
+ <div>
+ <gl-form
+ v-if="hasBuckets"
+ class="gl-m-5 gl-my-0 language-filter-checkbox"
+ @submit.prevent="submitQuery"
>
- <checkbox-filter
- :filters-data="filtersData"
- :tracking-namespace="$options.TRACKING_ACTION_SELECT"
- />
- <span v-if="showAll && hasOverMax" data-testid="has-over-max-text">{{
- $options.i18n.showingMax
- }}</span>
- </div>
- <gl-alert v-else class="gl-mx-5" variant="danger" :dismissible="false">{{
- $options.i18n.loadError
- }}</gl-alert>
- <div v-if="hasShowMore && !showAll" class="gl-px-5 language-filter-show-all">
- <gl-button
- data-testid="show-more-button"
- category="tertiary"
- variant="link"
- size="small"
- button-text-classes="gl-font-sm"
- @click="onShowMore"
- >
- {{ $options.i18n.showMore }}
- </gl-button>
- </div>
- <div v-if="!aggregations.error">
- <hr v-if="!useNewNavigation" :class="$options.HR_DEFAULT_CLASSES" />
+ <hr v-if="!useNewNavigation" :class="dividerClassesTop" />
+ <h5 class="gl-mt-0 gl-mb-5" :class="{ 'gl-font-sm': useNewNavigation }">
+ {{ $options.languageFilterData.header }}
+ </h5>
<div
- class="gl-display-flex gl-align-items-center gl-justify-content-space-between gl-mt-4 gl-mx-5"
+ v-if="!aggregations.error"
+ class="gl-overflow-x-hidden gl-overflow-y-auto"
+ :class="{ 'language-filter-max-height': showAll }"
>
+ <checkbox-filter
+ :filters-data="filtersData"
+ :tracking-namespace="$options.TRACKING_ACTION_SELECT"
+ />
+ <span v-if="showAll && hasOverMax" data-testid="has-over-max-text">{{
+ $options.i18n.showingMax
+ }}</span>
+ </div>
+ <gl-alert v-else class="gl-mx-5" variant="danger" :dismissible="false">{{
+ $options.i18n.loadError
+ }}</gl-alert>
+ <div v-if="hasShowMore && !showAll" class="gl-px-5 language-filter-show-all">
<gl-button
- category="primary"
- variant="confirm"
- type="submit"
- :disabled="!sidebarDirty"
- data-testid="apply-button"
- >
- {{ $options.i18n.apply }}
- </gl-button>
- <gl-button
+ data-testid="show-more-button"
category="tertiary"
variant="link"
size="small"
- :disabled="!hasQueryFilters && !sidebarDirty"
- data-testid="reset-button"
- @click="cleanResetFilters"
+ button-text-classes="gl-font-sm"
+ @click="onShowMore"
>
- {{ $options.i18n.reset }}
+ {{ $options.i18n.showMore }}
</gl-button>
</div>
- </div>
- </gl-form>
+ <div v-if="!aggregations.error">
+ <hr v-if="!useNewNavigation" :class="dividerClassesBottom" />
+ <div class="gl-display-flex gl-align-items-center gl-justify-content-space-between gl-mt-4">
+ <gl-button
+ category="primary"
+ variant="confirm"
+ type="submit"
+ :disabled="!sidebarDirty"
+ data-testid="apply-button"
+ >
+ {{ $options.i18n.apply }}
+ </gl-button>
+ <gl-button
+ v-if="hasQueryFilters && sidebarDirty"
+ category="tertiary"
+ variant="link"
+ size="small"
+ data-testid="reset-button"
+ @click="cleanResetFilters"
+ >
+ {{ $options.i18n.reset }}
+ </gl-button>
+ </div>
+ </div>
+ </gl-form>
+ </div>
</template>
diff --git a/app/assets/javascripts/search/sidebar/components/radio_filter.vue b/app/assets/javascripts/search/sidebar/components/radio_filter.vue
index 477ba37dab7..10ece1b82eb 100644
--- a/app/assets/javascripts/search/sidebar/components/radio_filter.vue
+++ b/app/assets/javascripts/search/sidebar/components/radio_filter.vue
@@ -56,7 +56,9 @@ export default {
<template>
<div>
- <h5 class="gl-mt-0" :class="{ 'gl-font-sm': useNewNavigation }">{{ filterData.header }}</h5>
+ <h5 class="gl-mt-0 gl-mb-5" :class="{ 'gl-font-sm': useNewNavigation }">
+ {{ filterData.header }}
+ </h5>
<gl-form-radio-group v-model="selectedFilter">
<gl-form-radio v-for="f in filtersArray" :key="f.value" :value="f.value">
{{ radioLabel(f) }}
diff --git a/app/assets/javascripts/search/sidebar/components/scope_navigation.vue b/app/assets/javascripts/search/sidebar/components/scope_navigation.vue
index fc41baee831..b29a8d13425 100644
--- a/app/assets/javascripts/search/sidebar/components/scope_navigation.vue
+++ b/app/assets/javascripts/search/sidebar/components/scope_navigation.vue
@@ -80,6 +80,6 @@ export default {
</span>
</gl-nav-item>
</gl-nav>
- <hr class="gl-mt-5 gl-mx-5 gl-mb-0 gl-border-gray-100 gl-md-display-none" />
+ <hr class="gl-mt-3 gl-mb-5 gl-border-gray-100 gl-md-display-none" />
</nav>
</template>
diff --git a/app/assets/javascripts/search/sidebar/components/status_filter.vue b/app/assets/javascripts/search/sidebar/components/status_filter.vue
index 44d6b537b7b..2a3d9ede982 100644
--- a/app/assets/javascripts/search/sidebar/components/status_filter.vue
+++ b/app/assets/javascripts/search/sidebar/components/status_filter.vue
@@ -19,7 +19,7 @@ export default {
<template>
<div>
- <radio-filter class="gl-px-5" :filter-data="$options.stateFilterData" />
+ <radio-filter :filter-data="$options.stateFilterData" />
<hr v-if="!useNewNavigation" :class="$options.HR_DEFAULT_CLASSES" />
</div>
</template>
diff --git a/app/assets/javascripts/search/sidebar/constants/index.js b/app/assets/javascripts/search/sidebar/constants/index.js
index 9519154a571..99d8821db61 100644
--- a/app/assets/javascripts/search/sidebar/constants/index.js
+++ b/app/assets/javascripts/search/sidebar/constants/index.js
@@ -12,7 +12,10 @@ export const NAV_LINK_DEFAULT_CLASSES = [
'gl-justify-content-space-between',
];
export const NAV_LINK_COUNT_DEFAULT_CLASSES = ['gl-font-sm', 'gl-font-weight-normal'];
-export const HR_DEFAULT_CLASSES = ['gl-my-5', 'gl-mx-5', 'gl-border-gray-100'];
+export const HR_DEFAULT_CLASSES = ['hr-x', 'gl-border-gray-100'];
export const ONLY_SHOW_MD = ['gl-display-none', 'gl-md-display-block'];
-export const TRACKING_LABEL_CHECKBOX = 'Checkbox';
+export const TRACKING_ACTION_CLICK = 'search:filters:click';
+export const TRACKING_LABEL_APPLY = 'Apply Filters';
+export const TRACKING_LABEL_RESET = 'Reset Filters';
+export const TRACKING_CATEGORY = 'Issue filters';
diff --git a/app/assets/javascripts/search/store/actions.js b/app/assets/javascripts/search/store/actions.js
index 3d6ca2a6eee..3fdc4165baf 100644
--- a/app/assets/javascripts/search/store/actions.js
+++ b/app/assets/javascripts/search/store/actions.js
@@ -136,7 +136,7 @@ export const fetchSidebarCount = ({ commit, state }) => {
return Promise.all(promises);
};
-export const fetchLanguageAggregation = ({ commit, state }) => {
+export const fetchAllAggregation = ({ commit, state }) => {
commit(types.REQUEST_AGGREGATIONS);
return axios
.get(getAggregationsUrl())
diff --git a/app/assets/stylesheets/page_bundles/search.scss b/app/assets/stylesheets/page_bundles/search.scss
index d37e87b5cd5..d1d14cbcddd 100644
--- a/app/assets/stylesheets/page_bundles/search.scss
+++ b/app/assets/stylesheets/page_bundles/search.scss
@@ -4,11 +4,8 @@ $search-dropdown-max-height: 400px;
$search-avatar-size: 16px;
$search-sidebar-min-width: 240px;
$search-sidebar-max-width: 300px;
-$search-keyboard-shortcut: '/';
$language-filter-max-height: 20rem;
-$border-radius-medium: 3px;
-
.search-results {
.search-result-row {
border-bottom: 1px solid var(--border-color, $border-color);
@@ -21,6 +18,13 @@ $border-radius-medium: 3px;
}
}
+.hr-x {
+ margin-left: -$gl-spacing-scale-5;
+ margin-right: -$gl-spacing-scale-5;
+ margin-top: $gl-spacing-scale-3;
+ margin-bottom: $gl-spacing-scale-5;
+}
+
.language-filter-checkbox {
.custom-control-label {
flex-grow: 1;
@@ -28,8 +32,17 @@ $border-radius-medium: 3px;
}
.search-sidebar {
- @include media-breakpoint-up(md) {
+ @include media-breakpoint-down(lg) {
+ max-width: 100%;
+ }
+
+ @include media-breakpoint-down(xl) {
min-width: $search-sidebar-min-width;
+ max-width: $search-sidebar-min-width;
+ }
+
+ @include media-breakpoint-up(xl) {
+ min-width: $search-sidebar-max-width;
max-width: $search-sidebar-max-width;
}
@@ -38,6 +51,44 @@ $border-radius-medium: 3px;
}
}
+.issue-filters {
+ .label-filter {
+ list-style: none;
+
+ .header-search-dropdown-menu {
+ max-height: $language-filter-max-height;
+
+ @include media-breakpoint-down(xl) {
+ min-width: calc(#{$search-sidebar-min-width} - (#{$gl-spacing-scale-5} + #{$gl-spacing-scale-5}));
+ max-width: calc(#{$search-sidebar-min-width} - (#{$gl-spacing-scale-5} + #{$gl-spacing-scale-5}));
+ }
+
+ @include media-breakpoint-up(xl) {
+ min-width: calc(#{$search-sidebar-max-width} - (#{$gl-spacing-scale-5} + #{$gl-spacing-scale-5}));
+ max-width: calc(#{$search-sidebar-max-width} - (#{$gl-spacing-scale-5} + #{$gl-spacing-scale-5}));
+ }
+
+ .label-with-color-checkbox {
+ max-height: $gl-spacing-scale-5;
+
+ .custom-control-label {
+ margin-bottom: 0;
+ max-height: $gl-spacing-scale-5;
+
+ .label-title {
+ margin-left: -$gl-spacing-scale-2;
+ }
+ }
+ }
+ }
+ }
+}
+
+.advanced-search-promote {
+ padding-left: 5px;
+ padding-right: 5px;
+}
+
.search-max-w-inherit {
max-width: inherit;
}
diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb
index 5b22851a98c..447251c6442 100644
--- a/app/policies/group_policy.rb
+++ b/app/policies/group_policy.rb
@@ -202,6 +202,7 @@ class GroupPolicy < Namespaces::GroupProjectNamespaceSharedPolicy
enable :read_package
enable :read_crm_organization
enable :read_crm_contact
+ enable :read_confidential_issues
end
rule { maintainer }.policy do
diff --git a/app/views/search/_results.html.haml b/app/views/search/_results.html.haml
index 99558f61b25..7399f51d7f8 100644
--- a/app/views/search/_results.html.haml
+++ b/app/views/search/_results.html.haml
@@ -1,5 +1,3 @@
-= render_if_exists 'shared/promotions/promote_advanced_search'
-
.gl-w-full.gl-flex-grow-1.gl-overflow-x-hidden
= render partial: 'search/results_status' unless @search_objects.to_a.empty?
= render partial: 'search/results_list'
diff --git a/app/views/search/_results_list.html.haml b/app/views/search/_results_list.html.haml
index fcbf0ba4452..ff79f003e7d 100644
--- a/app/views/search/_results_list.html.haml
+++ b/app/views/search/_results_list.html.haml
@@ -1,3 +1,6 @@
+.advanced-search-promote
+ = render_if_exists 'shared/promotions/promote_advanced_search'
+
- if @timeout
= render partial: "search/results/timeout"
- elsif @search_results.respond_to?(:failed?) && @search_results.failed?
diff --git a/config/initializers/00_deprecations.rb b/config/initializers/00_deprecations.rb
index 8aff095d88b..39398367c1e 100644
--- a/config/initializers/00_deprecations.rb
+++ b/config/initializers/00_deprecations.rb
@@ -35,7 +35,10 @@ else
/Using `return`, `break` or `throw` to exit a transaction block/
]
- ActiveSupport::Deprecation.disallowed_warnings = rails7_deprecation_warnings
+ view_component_3_warnings = [
+ /Setting a slot with `#\w+` is deprecated and will be removed from ViewComponent 3.0.0/
+ ]
+ ActiveSupport::Deprecation.disallowed_warnings = rails7_deprecation_warnings + view_component_3_warnings
end
unless ActiveSupport::Deprecation.silenced
diff --git a/qa/Gemfile b/qa/Gemfile
index 685e8970f78..9660c6b7b03 100644
--- a/qa/Gemfile
+++ b/qa/Gemfile
@@ -2,7 +2,7 @@
source 'https://rubygems.org'
-gem 'gitlab-qa', '~> 10', '>= 10.4.1', require: 'gitlab/qa'
+gem 'gitlab-qa', '~> 11', '>= 11.1.0', require: 'gitlab/qa'
gem 'gitlab_quality-test_tooling', '~> 0.6.1', require: false
gem 'activesupport', '~> 6.1.7.2' # This should stay in sync with the root's Gemfile
gem 'allure-rspec', '~> 2.20.0'
diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock
index ec654dbf5c8..edcaa3b9b91 100644
--- a/qa/Gemfile.lock
+++ b/qa/Gemfile.lock
@@ -102,7 +102,7 @@ GEM
gitlab (4.19.0)
httparty (~> 0.20)
terminal-table (>= 1.5.1)
- gitlab-qa (10.6.0)
+ gitlab-qa (11.1.0)
activesupport (~> 6.1)
gitlab (~> 4.19)
http (~> 5.0)
@@ -327,7 +327,7 @@ DEPENDENCIES
faraday-retry (~> 2.1)
fog-core (= 2.1.0)
fog-google (~> 1.19)
- gitlab-qa (~> 10, >= 10.4.1)
+ gitlab-qa (~> 11, >= 11.1.0)
gitlab_quality-test_tooling (~> 0.6.1)
influxdb-client (~> 2.9)
knapsack (~> 4.0)
diff --git a/spec/frontend/search/sidebar/components/app_spec.js b/spec/frontend/search/sidebar/components/app_spec.js
index 963b73aeae5..e1911534928 100644
--- a/spec/frontend/search/sidebar/components/app_spec.js
+++ b/spec/frontend/search/sidebar/components/app_spec.js
@@ -3,7 +3,7 @@ import Vue from 'vue';
import Vuex from 'vuex';
import { MOCK_QUERY } from 'jest/search/mock_data';
import GlobalSearchSidebar from '~/search/sidebar/components/app.vue';
-import ResultsFilters from '~/search/sidebar/components/results_filters.vue';
+import IssuesFilters from '~/search/sidebar/components/issues_filters.vue';
import ScopeNavigation from '~/search/sidebar/components/scope_navigation.vue';
import LanguageFilter from '~/search/sidebar/components/language_filter/index.vue';
@@ -42,7 +42,7 @@ describe('GlobalSearchSidebar', () => {
};
const findSidebarSection = () => wrapper.find('section');
- const findFilters = () => wrapper.findComponent(ResultsFilters);
+ const findFilters = () => wrapper.findComponent(IssuesFilters);
const findSidebarNavigation = () => wrapper.findComponent(ScopeNavigation);
const findLanguageAggregation = () => wrapper.findComponent(LanguageFilter);
diff --git a/spec/frontend/search/sidebar/components/checkbox_filter_spec.js b/spec/frontend/search/sidebar/components/checkbox_filter_spec.js
index 3907e199cae..54fdf6e869e 100644
--- a/spec/frontend/search/sidebar/components/checkbox_filter_spec.js
+++ b/spec/frontend/search/sidebar/components/checkbox_filter_spec.js
@@ -7,7 +7,7 @@ import { MOCK_QUERY, MOCK_LANGUAGE_AGGREGATIONS_BUCKETS } from 'jest/search/mock
import CheckboxFilter, {
TRACKING_LABEL_CHECKBOX,
TRACKING_LABEL_SET,
-} from '~/search/sidebar/components/checkbox_filter.vue';
+} from '~/search/sidebar/components/language_filter/checkbox_filter.vue';
import { languageFilterData } from '~/search/sidebar/components/language_filter/data';
import { convertFiltersData } from '~/search/sidebar/utils';
diff --git a/spec/frontend/search/sidebar/components/filters_spec.js b/spec/frontend/search/sidebar/components/filters_spec.js
index d189c695467..a92fafd3508 100644
--- a/spec/frontend/search/sidebar/components/filters_spec.js
+++ b/spec/frontend/search/sidebar/components/filters_spec.js
@@ -3,7 +3,7 @@ import { shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import Vuex from 'vuex';
import { MOCK_QUERY } from 'jest/search/mock_data';
-import ResultsFilters from '~/search/sidebar/components/results_filters.vue';
+import IssuesFilters from '~/search/sidebar/components/issues_filters.vue';
import ConfidentialityFilter from '~/search/sidebar/components/confidentiality_filter.vue';
import StatusFilter from '~/search/sidebar/components/status_filter.vue';
@@ -31,7 +31,7 @@ describe('GlobalSearchSidebarFilters', () => {
getters: defaultGetters,
});
- wrapper = shallowMount(ResultsFilters, {
+ wrapper = shallowMount(IssuesFilters, {
store,
});
};
diff --git a/spec/frontend/search/sidebar/components/language_filter_spec.js b/spec/frontend/search/sidebar/components/language_filter_spec.js
index 9ad9d095aca..817199d7cfe 100644
--- a/spec/frontend/search/sidebar/components/language_filter_spec.js
+++ b/spec/frontend/search/sidebar/components/language_filter_spec.js
@@ -9,7 +9,7 @@ import {
MOCK_LANGUAGE_AGGREGATIONS_BUCKETS,
} from 'jest/search/mock_data';
import LanguageFilter from '~/search/sidebar/components/language_filter/index.vue';
-import CheckboxFilter from '~/search/sidebar/components/checkbox_filter.vue';
+import CheckboxFilter from '~/search/sidebar/components/language_filter/checkbox_filter.vue';
import {
TRACKING_LABEL_SHOW_MORE,
@@ -32,7 +32,7 @@ describe('GlobalSearchSidebarLanguageFilter', () => {
let trackingSpy;
const actionSpies = {
- fetchLanguageAggregation: jest.fn(),
+ fetchAllAggregation: jest.fn(),
applyQuery: jest.fn(),
};
@@ -61,10 +61,6 @@ describe('GlobalSearchSidebarLanguageFilter', () => {
});
};
- afterEach(() => {
- unmockTracking();
- });
-
const findForm = () => wrapper.findComponent(GlForm);
const findCheckboxFilter = () => wrapper.findComponent(CheckboxFilter);
const findApplyButton = () => wrapper.findByTestId('apply-button');
@@ -80,6 +76,10 @@ describe('GlobalSearchSidebarLanguageFilter', () => {
trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
});
+ afterEach(() => {
+ unmockTracking();
+ });
+
it('renders form', () => {
expect(findForm().exists()).toBe(true);
});
@@ -108,19 +108,19 @@ describe('GlobalSearchSidebarLanguageFilter', () => {
describe('resetButton', () => {
describe.each`
- description | sidebarDirty | queryFilters | isDisabled
- ${'sidebar dirty only'} | ${true} | ${[]} | ${undefined}
- ${'query filters only'} | ${false} | ${['JSON', 'C']} | ${undefined}
- ${'sidebar dirty and query filters'} | ${true} | ${['JSON', 'C']} | ${undefined}
- ${'no sidebar and no query filters'} | ${false} | ${[]} | ${'true'}
- `('$description', ({ sidebarDirty, queryFilters, isDisabled }) => {
+ description | sidebarDirty | queryFilters | exists
+ ${'sidebar dirty only'} | ${true} | ${[]} | ${false}
+ ${'query filters only'} | ${false} | ${['JSON', 'C']} | ${false}
+ ${'sidebar dirty and query filters'} | ${true} | ${['JSON', 'C']} | ${true}
+ ${'no sidebar and no query filters'} | ${false} | ${[]} | ${false}
+ `('$description', ({ sidebarDirty, queryFilters, exists }) => {
beforeEach(() => {
getterSpies.queryLanguageFilters = jest.fn(() => queryFilters);
createComponent({ sidebarDirty, query: { ...MOCK_QUERY, language: queryFilters } });
});
- it(`button is ${isDisabled ? 'enabled' : 'disabled'}`, () => {
- expect(findResetButton().attributes('disabled')).toBe(isDisabled);
+ it(`button is ${exists ? 'shown' : 'hidden'}`, () => {
+ expect(findResetButton().exists()).toBe(exists);
});
});
});
@@ -153,6 +153,10 @@ describe('GlobalSearchSidebarLanguageFilter', () => {
trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
});
+ afterEach(() => {
+ unmockTracking();
+ });
+
it(`renders ${MAX_ITEM_LENGTH} amount of items`, async () => {
findShowMoreButton().vm.$emit('click');
@@ -196,13 +200,16 @@ describe('GlobalSearchSidebarLanguageFilter', () => {
createComponent({});
trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
});
+ afterEach(() => {
+ unmockTracking();
+ });
it('uses getter languageAggregationBuckets', () => {
expect(getterSpies.languageAggregationBuckets).toHaveBeenCalled();
});
- it('uses action fetchLanguageAggregation', () => {
- expect(actionSpies.fetchLanguageAggregation).toHaveBeenCalled();
+ it('uses action fetchAllAggregation', () => {
+ expect(actionSpies.fetchAllAggregation).toHaveBeenCalled();
});
it('clicking ApplyButton calls applyQuery', () => {
diff --git a/spec/frontend/search/store/actions_spec.js b/spec/frontend/search/store/actions_spec.js
index 0884411df0c..e3d06a50580 100644
--- a/spec/frontend/search/store/actions_spec.js
+++ b/spec/frontend/search/store/actions_spec.js
@@ -301,11 +301,11 @@ describe('Global Search Store Actions', () => {
});
describe.each`
- action | axiosMock | type | expectedMutations | errorLogs
- ${actions.fetchLanguageAggregation} | ${{ method: 'onGet', code: HTTP_STATUS_OK }} | ${'success'} | ${MOCK_RECEIVE_AGGREGATIONS_SUCCESS_MUTATION} | ${0}
- ${actions.fetchLanguageAggregation} | ${{ method: 'onPut', code: 0 }} | ${'error'} | ${MOCK_RECEIVE_AGGREGATIONS_ERROR_MUTATION} | ${1}
- ${actions.fetchLanguageAggregation} | ${{ method: 'onGet', code: HTTP_STATUS_INTERNAL_SERVER_ERROR }} | ${'error'} | ${MOCK_RECEIVE_AGGREGATIONS_ERROR_MUTATION} | ${1}
- `('fetchLanguageAggregation', ({ action, axiosMock, type, expectedMutations, errorLogs }) => {
+ action | axiosMock | type | expectedMutations | errorLogs
+ ${actions.fetchAllAggregation} | ${{ method: 'onGet', code: HTTP_STATUS_OK }} | ${'success'} | ${MOCK_RECEIVE_AGGREGATIONS_SUCCESS_MUTATION} | ${0}
+ ${actions.fetchAllAggregation} | ${{ method: 'onPut', code: 0 }} | ${'error'} | ${MOCK_RECEIVE_AGGREGATIONS_ERROR_MUTATION} | ${1}
+ ${actions.fetchAllAggregation} | ${{ method: 'onGet', code: HTTP_STATUS_INTERNAL_SERVER_ERROR }} | ${'error'} | ${MOCK_RECEIVE_AGGREGATIONS_ERROR_MUTATION} | ${1}
+ `('fetchAllAggregation', ({ action, axiosMock, type, expectedMutations, errorLogs }) => {
describe(`on ${type}`, () => {
beforeEach(() => {
if (axiosMock.method) {
diff --git a/spec/support/shared_contexts/policies/group_policy_shared_context.rb b/spec/support/shared_contexts/policies/group_policy_shared_context.rb
index 108acdfd4e5..22caf2b3530 100644
--- a/spec/support/shared_contexts/policies/group_policy_shared_context.rb
+++ b/spec/support/shared_contexts/policies/group_policy_shared_context.rb
@@ -37,6 +37,7 @@ RSpec.shared_context 'GroupPolicy context' do
read_crm_contact
read_crm_organization
read_internal_note
+ read_confidential_issues
]
end