diff options
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 |