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--.gitpod.yml4
-rw-r--r--.rubocop_todo/layout/argument_alignment.yml25
-rw-r--r--CHANGELOG.md10
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.checksum2
-rw-r--r--Gemfile.lock6
-rw-r--r--app/assets/javascripts/search/sidebar/components/issues_filters.vue14
-rw-r--r--app/assets/javascripts/search/sidebar/components/label_filter/index.vue291
-rw-r--r--app/assets/javascripts/search/sidebar/components/label_filter/label_dropdown_items.vue43
-rw-r--r--app/assets/javascripts/search/sidebar/components/label_filter/tracking.js21
-rw-r--r--app/assets/javascripts/search/sidebar/components/language_filter/index.vue2
-rw-r--r--app/assets/javascripts/search/store/getters.js7
-rw-r--r--app/assets/javascripts/super_sidebar/components/global_search/command_palette/command_autocomplete_item.vue37
-rw-r--r--app/assets/javascripts/super_sidebar/components/global_search/command_palette/command_palette_items.vue70
-rw-r--r--app/assets/javascripts/super_sidebar/components/global_search/command_palette/constants.js29
-rw-r--r--app/assets/javascripts/super_sidebar/components/global_search/command_palette/fake_search_input.vue4
-rw-r--r--app/assets/javascripts/super_sidebar/components/global_search/command_palette/search_item.vue (renamed from app/assets/javascripts/super_sidebar/components/global_search/command_palette/user_autocomplete_item.vue)26
-rw-r--r--app/assets/javascripts/super_sidebar/components/global_search/command_palette/utils.js22
-rw-r--r--app/assets/javascripts/super_sidebar/super_sidebar_bundle.js2
-rw-r--r--app/assets/javascripts/vue_shared/components/timezone_dropdown/timezone_dropdown.vue6
-rw-r--r--app/assets/stylesheets/framework/markdown_area.scss1
-rw-r--r--app/controllers/search_controller.rb4
-rw-r--r--app/finders/namespaces/projects_finder.rb2
-rw-r--r--app/graphql/resolvers/namespace_projects_resolver.rb6
-rw-r--r--app/helpers/search_helper.rb20
-rw-r--r--app/models/merge_request.rb4
-rw-r--r--app/views/shared/runners/_runner_details.html.haml2
-rw-r--r--app/views/shared/runners/_runner_type_badge.html.haml14
-rw-r--r--config/metrics/counts_28d/20230531170613_ci_builds.yml22
-rw-r--r--db/post_migrate/20230614181637_add_idx_issues_on_work_item_type_project_closed_at_where_closed.rb15
-rw-r--r--db/schema_migrations/202306141816371
-rw-r--r--db/structure.sql2
-rw-r--r--doc/administration/geo/replication/configuration.md2
-rw-r--r--doc/api/graphql/reference/index.md2
-rw-r--r--doc/api/users.md16
-rw-r--r--doc/architecture/blueprints/permissions/index.md184
-rw-r--r--doc/ci/runners/new_creation_workflow.md4
-rw-r--r--doc/ci/runners/saas/macos_saas_runner.md2
-rw-r--r--doc/development/fe_guide/customizable_dashboards.md285
-rw-r--r--doc/development/internal_api/index.md24
-rw-r--r--doc/development/testing_guide/best_practices.md7
-rw-r--r--doc/user/admin_area/reporting/ip_addr_restrictions.md33
-rw-r--r--doc/user/admin_area/settings/index.md6
-rw-r--r--doc/user/analytics/analytics_dashboards.md26
-rw-r--r--doc/user/analytics/ci_cd_analytics.md20
-rw-r--r--doc/user/analytics/code_review_analytics.md4
-rw-r--r--doc/user/analytics/issue_analytics.md4
-rw-r--r--doc/user/analytics/merge_request_analytics.md12
-rw-r--r--doc/user/analytics/productivity_analytics.md12
-rw-r--r--doc/user/analytics/repository_analytics.md4
-rw-r--r--doc/user/analytics/value_streams_dashboard.md14
-rw-r--r--doc/user/group/manage.md16
-rw-r--r--doc/user/profile/account/create_accounts.md5
-rw-r--r--doc/user/profile/account/delete_account.md7
-rw-r--r--doc/user/profile/index.md8
-rw-r--r--doc/user/profile/notifications.md4
-rw-r--r--doc/user/profile/user_passwords.md2
-rw-r--r--doc/user/project/integrations/webhooks.md38
-rw-r--r--doc/user/project/issues/confidential_issues.md11
-rw-r--r--doc/user/project/merge_requests/changes.md4
-rw-r--r--doc/user/project/merge_requests/cherry_pick_changes.md16
-rw-r--r--doc/user/project/merge_requests/commit_templates.md4
-rw-r--r--doc/user/project/merge_requests/commits.md16
-rw-r--r--doc/user/project/merge_requests/conflicts.md8
-rw-r--r--doc/user/project/merge_requests/creating_merge_requests.md20
-rw-r--r--doc/user/project/merge_requests/csv_export.md4
-rw-r--r--doc/user/project/merge_requests/dependencies.md12
-rw-r--r--doc/user/project/merge_requests/index.md42
-rw-r--r--doc/user/project/merge_requests/merge_when_pipeline_succeeds.md17
-rw-r--r--doc/user/project/merge_requests/methods/index.md4
-rw-r--r--doc/user/project/merge_requests/revert_changes.md10
-rw-r--r--doc/user/project/merge_requests/reviews/index.md8
-rw-r--r--doc/user/project/merge_requests/reviews/suggestions.md16
-rw-r--r--doc/user/project/merge_requests/squash_and_merge.md4
-rw-r--r--doc/user/project/merge_requests/status_checks.md4
-rw-r--r--doc/user/project/milestones/index.md22
-rw-r--r--doc/user/project/repository/branches/default.md27
-rw-r--r--doc/user/project/repository/branches/index.md35
-rw-r--r--doc/user/project/repository/forking_workflow.md8
-rw-r--r--doc/user/project/repository/gpg_signed_commits/index.md14
-rw-r--r--doc/user/project/repository/mirror/bidirectional.md2
-rw-r--r--doc/user/project/repository/mirror/index.md20
-rw-r--r--doc/user/project/repository/mirror/pull.md4
-rw-r--r--doc/user/project/repository/mirror/push.md6
-rw-r--r--doc/user/project/repository/push_rules.md9
-rw-r--r--doc/user/project/repository/ssh_signed_commits/index.md6
-rw-r--r--doc/user/project/repository/tags/index.md12
-rw-r--r--doc/user/project/repository/web_editor.md16
-rw-r--r--doc/user/project/settings/import_export.md9
-rw-r--r--doc/user/project/settings/index.md14
-rw-r--r--doc/user/project/settings/project_access_tokens.md12
-rw-r--r--doc/user/project/web_ide/index.md2
-rw-r--r--doc/user/workspace/index.md5
-rw-r--r--lib/gitlab/usage/metrics/instrumentations/count_all_ci_builds_metric.rb15
-rw-r--r--locale/gitlab.pot40
-rw-r--r--qa/qa/service/praefect_manager.rb36
-rw-r--r--qa/qa/specs/features/api/12_systems/gitaly/praefect_connectivity_spec.rb45
-rw-r--r--spec/controllers/search_controller_spec.rb6
-rw-r--r--spec/finders/namespaces/projects_finder_spec.rb31
-rw-r--r--spec/frontend/search/mock_data.js1
-rw-r--r--spec/frontend/search/sidebar/components/label_dropdown_items_spec.js57
-rw-r--r--spec/frontend/search/sidebar/components/label_filter_spec.js322
-rw-r--r--spec/frontend/search/store/actions_spec.js2
-rw-r--r--spec/frontend/search/store/getters_spec.js4
-rw-r--r--spec/frontend/super_sidebar/components/global_search/command_palette/__snapshots__/command_autocomplete_item_spec.js.snap19
-rw-r--r--spec/frontend/super_sidebar/components/global_search/command_palette/__snapshots__/search_item_spec.js.snap122
-rw-r--r--spec/frontend/super_sidebar/components/global_search/command_palette/__snapshots__/user_autocomplete_item_spec.js.snap34
-rw-r--r--spec/frontend/super_sidebar/components/global_search/command_palette/command_autocomplete_item_spec.js25
-rw-r--r--spec/frontend/super_sidebar/components/global_search/command_palette/command_palette_items_spec.js38
-rw-r--r--spec/frontend/super_sidebar/components/global_search/command_palette/fake_search_input_spec.js14
-rw-r--r--spec/frontend/super_sidebar/components/global_search/command_palette/mock_data.js54
-rw-r--r--spec/frontend/super_sidebar/components/global_search/command_palette/search_item_spec.js33
-rw-r--r--spec/frontend/super_sidebar/components/global_search/command_palette/user_autocomplete_item_spec.js25
-rw-r--r--spec/frontend/super_sidebar/components/global_search/command_palette/utils_spec.js27
-rw-r--r--spec/frontend/super_sidebar/components/global_search/components/global_search_spec.js7
-rw-r--r--spec/graphql/resolvers/namespace_projects_resolver_spec.rb32
-rw-r--r--spec/helpers/search_helper_spec.rb62
-rw-r--r--spec/lib/gitlab/usage/metrics/instrumentations/count_all_ci_builds_metric_spec.rb18
-rw-r--r--spec/models/concerns/spammable_spec.rb33
-rw-r--r--spec/models/merge_request_spec.rb27
120 files changed, 2244 insertions, 765 deletions
diff --git a/.gitpod.yml b/.gitpod.yml
index ac20429c08e..19785aba125 100644
--- a/.gitpod.yml
+++ b/.gitpod.yml
@@ -53,6 +53,8 @@ tasks:
cd /workspace/gitlab-development-kit
# update GDK
echo "$(date) – Updating GDK" | tee -a /workspace/startup.log
+ export DEFAULT_BRANCH=$(git branch --show-current)
+ gdk config set gitlab.default_branch "$DEFAULT_BRANCH"
gdk update
# ensure gdk.yml has correct instance settings
gdk config set gitlab.rails.hostname $(gp url 3000 | sed -e 's+^http[s]*://++')
@@ -74,7 +76,7 @@ tasks:
make gitlab-db-migrate
fi
cd /workspace/gitlab-development-kit/gitlab
- echo "--- on branch: $(git branch --show-current)"
+ echo "--- on branch: $DEFAULT_BRANCH"
echo "--- installing lefthook"
bundle exec lefthook install
echo "--- resetting db/structure.sql"
diff --git a/.rubocop_todo/layout/argument_alignment.yml b/.rubocop_todo/layout/argument_alignment.yml
index 310f05e6bf9..81138c2d253 100644
--- a/.rubocop_todo/layout/argument_alignment.yml
+++ b/.rubocop_todo/layout/argument_alignment.yml
@@ -1181,31 +1181,6 @@ Layout/ArgumentAlignment:
- 'ee/spec/lib/gitlab/zoekt/search_results_spec.rb'
- 'ee/spec/lib/incident_management/oncall_shift_generator_spec.rb'
- 'ee/spec/lib/omni_auth/strategies/group_saml_spec.rb'
- - 'ee/spec/models/ee/event_spec.rb'
- - 'ee/spec/models/ee/group_spec.rb'
- - 'ee/spec/models/ee/namespace_spec.rb'
- - 'ee/spec/models/ee/project_spec.rb'
- - 'ee/spec/models/ee/user_spec.rb'
- - 'ee/spec/models/epic_spec.rb'
- - 'ee/spec/models/geo/project_registry_spec.rb'
- - 'ee/spec/models/ldap_group_link_spec.rb'
- - 'ee/spec/models/member_spec.rb'
- - 'ee/spec/models/merge_request_spec.rb'
- - 'ee/spec/models/merge_requests/external_status_check_spec.rb'
- - 'ee/spec/models/namespaces/free_user_cap/notification_spec.rb'
- - 'ee/spec/models/preloaders/environments/protected_environment_preloader_spec.rb'
- - 'ee/spec/models/product_analytics/funnel_step_spec.rb'
- - 'ee/spec/models/product_analytics/jitsu_authentication_spec.rb'
- - 'ee/spec/models/productivity_analytics_spec.rb'
- - 'ee/spec/models/project_import_state_spec.rb'
- - 'ee/spec/models/protected_environments/approval_rule_spec.rb'
- - 'ee/spec/models/remote_mirror_spec.rb'
- - 'ee/spec/models/requirements_management/test_report_spec.rb'
- - 'ee/spec/models/security/finding_spec.rb'
- - 'ee/spec/models/security/orchestration_policy_configuration_spec.rb'
- - 'ee/spec/models/upload_spec.rb'
- - 'ee/spec/models/vulnerabilities/finding_spec.rb'
- - 'ee/spec/models/vulnerabilities/state_transition_spec.rb'
- 'ee/spec/requests/admin/impersonation_tokens_controller_spec.rb'
- 'ee/spec/requests/api/analytics/product_analytics_spec.rb'
- 'ee/spec/requests/api/analytics/project_deployment_frequency_spec.rb'
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a53580ef55e..d543357ce4a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,16 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 16.0.5 (2023-06-16)
+
+### Fixed (1 change)
+
+- [Update gitlab-elasticsearch-indexer version](gitlab-org/gitlab@d080c6c9af405b24e65e269ccb7b723cd5510940) ([merge request](gitlab-org/gitlab!122335))
+
+### Changed (1 change)
+
+- [Make MigrateSharedVulnerabilityIdentifiers use slow iteration](gitlab-org/gitlab@252da7be42ec95a5d470c17f43209c27890a7e85) ([merge request](gitlab-org/gitlab!122859)) **GitLab Enterprise Edition**
+
## 16.0.4 (2023-06-08)
### Fixed (1 change)
diff --git a/Gemfile b/Gemfile
index 4e620da7fd7..583d096e494 100644
--- a/Gemfile
+++ b/Gemfile
@@ -474,7 +474,7 @@ group :test do
# Moved in `test` because https://gitlab.com/gitlab-org/gitlab/-/issues/217527
gem 'derailed_benchmarks', require: false
- gem 'gitlab_quality-test_tooling', '~> 0.8.0', require: false
+ gem 'gitlab_quality-test_tooling', '~> 0.8.1', require: false
end
gem 'octokit', '~> 4.15'
diff --git a/Gemfile.checksum b/Gemfile.checksum
index 8d17f6b040f..ccd03e7d598 100644
--- a/Gemfile.checksum
+++ b/Gemfile.checksum
@@ -218,7 +218,7 @@
{"name":"gitlab-styles","version":"10.0.0","platform":"ruby","checksum":"8a1b20f7b5f351605ff4ed4ec648ef37226f2774d1e1377ed99389448d6913f0"},
{"name":"gitlab_chronic_duration","version":"0.10.6.2","platform":"ruby","checksum":"6dda4cfe7dca9b958f163ac8835c3d9cc70cf8df8cbb89bb2fbf9ba4375105fb"},
{"name":"gitlab_omniauth-ldap","version":"2.2.0","platform":"ruby","checksum":"bb4d20acb3b123ed654a8f6a47d3fac673ece7ed0b6992edb92dca14bad2838c"},
-{"name":"gitlab_quality-test_tooling","version":"0.8.0","platform":"ruby","checksum":"e1972749dd23979abff5960628138c37ab35e40462c581d08c105c9ecf54edf0"},
+{"name":"gitlab_quality-test_tooling","version":"0.8.1","platform":"ruby","checksum":"01108f12c7b67aa0450ce76a4e669722f536250ff696abae9f126d072164bc0b"},
{"name":"globalid","version":"1.1.0","platform":"ruby","checksum":"b337e1746f0c8cb0a6c918234b03a1ddeb4966206ce288fbb57779f59b2d154f"},
{"name":"gon","version":"6.4.0","platform":"ruby","checksum":"e3a618d659392890f1aa7db420f17c75fd7d35aeb5f8fe003697d02c4b88d2f0"},
{"name":"google-apis-androidpublisher_v3","version":"0.34.0","platform":"ruby","checksum":"d7e1d7dd92f79c498fe2082222a1740d788e022e660c135564b3fd299cab5425"},
diff --git a/Gemfile.lock b/Gemfile.lock
index 77bc580669e..3635faf72a9 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -624,8 +624,8 @@ GEM
omniauth (>= 1.3, < 3)
pyu-ruby-sasl (>= 0.0.3.3, < 0.1)
rubyntlm (~> 0.5)
- gitlab_quality-test_tooling (0.8.0)
- activesupport (~> 6.1)
+ gitlab_quality-test_tooling (0.8.1)
+ activesupport (>= 6.1, < 7.1)
gitlab (~> 4.19)
http (~> 5.0)
nokogiri (~> 1.10)
@@ -1763,7 +1763,7 @@ DEPENDENCIES
gitlab-styles (~> 10.0.0)
gitlab_chronic_duration (~> 0.10.6.2)
gitlab_omniauth-ldap (~> 2.2.0)
- gitlab_quality-test_tooling (~> 0.8.0)
+ gitlab_quality-test_tooling (~> 0.8.1)
gon (~> 6.4.0)
google-apis-androidpublisher_v3 (~> 0.34.0)
google-apis-cloudbilling_v1 (~> 0.21.0)
diff --git a/app/assets/javascripts/search/sidebar/components/issues_filters.vue b/app/assets/javascripts/search/sidebar/components/issues_filters.vue
index 2ab5dfb8dea..8928f80d83a 100644
--- a/app/assets/javascripts/search/sidebar/components/issues_filters.vue
+++ b/app/assets/javascripts/search/sidebar/components/issues_filters.vue
@@ -2,6 +2,7 @@
import { GlButton, GlLink } from '@gitlab/ui';
import { mapActions, mapState, mapGetters } from 'vuex';
import Tracking from '~/tracking';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import {
HR_DEFAULT_CLASSES,
TRACKING_ACTION_CLICK,
@@ -12,6 +13,8 @@ import {
import { confidentialFilterData } from '../constants/confidential_filter_data';
import { stateFilterData } from '../constants/state_filter_data';
import ConfidentialityFilter from './confidentiality_filter.vue';
+import { labelFilterData } from './label_filter/data';
+import LabelFilter from './label_filter/index.vue';
import StatusFilter from './status_filter.vue';
export default {
@@ -21,7 +24,9 @@ export default {
GlLink,
StatusFilter,
ConfidentialityFilter,
+ LabelFilter,
},
+ mixins: [glFeatureFlagsMixin()],
computed: {
...mapState(['urlQuery', 'sidebarDirty', 'useNewNavigation']),
...mapGetters(['currentScope']),
@@ -34,6 +39,12 @@ export default {
showStatusFilter() {
return Object.values(stateFilterData.scopes).includes(this.currentScope);
},
+ showLabelFilter() {
+ return (
+ Object.values(labelFilterData.scopes).includes(this.currentScope) &&
+ this.glFeatures.searchIssueLabelAggregation
+ );
+ },
hrClasses() {
return [...HR_DEFAULT_CLASSES, 'gl-display-none', 'gl-md-display-block'];
},
@@ -61,7 +72,8 @@ export default {
<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">
+ <label-filter v-if="showLabelFilter" />
+ <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>
diff --git a/app/assets/javascripts/search/sidebar/components/label_filter/index.vue b/app/assets/javascripts/search/sidebar/components/label_filter/index.vue
new file mode 100644
index 00000000000..74855482b5d
--- /dev/null
+++ b/app/assets/javascripts/search/sidebar/components/label_filter/index.vue
@@ -0,0 +1,291 @@
+<script>
+import {
+ GlSearchBoxByType,
+ GlLabel,
+ GlLoadingIcon,
+ GlDropdownDivider,
+ GlDropdownSectionHeader,
+ GlFormCheckboxGroup,
+ GlDropdownForm,
+ GlAlert,
+ GlOutsideDirective as Outside,
+} from '@gitlab/ui';
+import { mapActions, mapState, mapGetters } from 'vuex';
+import { uniq } from 'lodash';
+import { rgbFromHex } from '@gitlab/ui/dist/utils/utils';
+import { slugify } from '~/lib/utils/text_utility';
+import { s__, sprintf } from '~/locale';
+
+import DropdownKeyboardNavigation from '~/vue_shared/components/dropdown_keyboard_navigation.vue';
+
+import {
+ SEARCH_INPUT_DESCRIBE_BY_NO_DROPDOWN,
+ SEARCH_INPUT_DESCRIBE_BY_WITH_DROPDOWN,
+ SEARCH_DESCRIBED_BY_DEFAULT,
+ SEARCH_DESCRIBED_BY_UPDATED,
+ SEARCH_RESULTS_LOADING,
+} from '~/vue_shared/global_search/constants';
+
+import { HR_DEFAULT_CLASSES, ONLY_SHOW_MD } from '../../constants';
+import LabelDropdownItems from './label_dropdown_items.vue';
+
+import {
+ FIRST_DROPDOWN_INDEX,
+ SEARCH_BOX_INDEX,
+ SEARCH_RESULTS_DESCRIPTION,
+ SEARCH_INPUT_DESCRIPTION,
+ labelFilterData,
+} from './data';
+
+import { trackSelectCheckbox, trackOpenDropdown } from './tracking';
+
+export default {
+ name: 'LabelFilter',
+ directives: { Outside },
+ components: {
+ DropdownKeyboardNavigation,
+ GlSearchBoxByType,
+ LabelDropdownItems,
+ GlLabel,
+ GlDropdownDivider,
+ GlDropdownSectionHeader,
+ GlFormCheckboxGroup,
+ GlDropdownForm,
+ GlLoadingIcon,
+ GlAlert,
+ },
+ data() {
+ return {
+ currentFocusIndex: SEARCH_BOX_INDEX,
+ isFocused: false,
+ };
+ },
+ i18n: {
+ SEARCH_LABELS: s__('GlobalSearch|Search labels'),
+ DROPDOWN_HEADER: s__('GlobalSearch|Label(s)'),
+ AGGREGATIONS_ERROR_MESSAGE: s__('GlobalSearch|Fetching aggregations error.'),
+ SEARCH_DESCRIBED_BY_DEFAULT,
+ SEARCH_RESULTS_LOADING,
+ SEARCH_DESCRIBED_BY_UPDATED,
+ SEARCH_INPUT_DESCRIBE_BY_WITH_DROPDOWN,
+ SEARCH_INPUT_DESCRIBE_BY_NO_DROPDOWN,
+ },
+ computed: {
+ ...mapState(['useSidebarNavigation', 'searchLabelString', 'query', 'aggregations']),
+ ...mapGetters([
+ 'filteredLabels',
+ 'filteredUnselectedLabels',
+ 'filteredAppliedSelectedLabels',
+ 'appliedSelectedLabels',
+ 'filteredUnappliedSelectedLabels',
+ ]),
+ searchInputDescribeBy() {
+ if (this.isLoggedIn) {
+ return this.$options.i18n.SEARCH_INPUT_DESCRIBE_BY_WITH_DROPDOWN;
+ }
+ return this.$options.i18n.SEARCH_INPUT_DESCRIBE_BY_NO_DROPDOWN;
+ },
+ dropdownResultsDescription() {
+ if (!this.showSearchDropdown) {
+ return ''; // This allows aria-live to see register an update when the dropdown is shown
+ }
+
+ if (this.showDefaultItems) {
+ return sprintf(this.$options.i18n.SEARCH_DESCRIBED_BY_DEFAULT, {
+ count: this.filteredLabels.length,
+ });
+ }
+
+ return this.loading
+ ? this.$options.i18n.SEARCH_RESULTS_LOADING
+ : sprintf(this.$options.i18n.SEARCH_DESCRIBED_BY_UPDATED, {
+ count: this.filteredLabels.length,
+ });
+ },
+ currentFocusedOption() {
+ return this.filteredLabels[this.currentFocusIndex] || null;
+ },
+ currentFocusedId() {
+ return `${slugify(this.currentFocusedOption?.parent_full_name || 'undefined-name')}_${slugify(
+ this.currentFocusedOption?.title || 'undefined-title',
+ )}`;
+ },
+ defaultIndex() {
+ if (this.showDefaultItems) {
+ return SEARCH_BOX_INDEX;
+ }
+ return FIRST_DROPDOWN_INDEX;
+ },
+ hasSelectedLabels() {
+ return this.filteredAppliedSelectedLabels.length > 0;
+ },
+ hasUnselectedLabels() {
+ return this.filteredUnselectedLabels.length > 0;
+ },
+ dividerClasses() {
+ return [...HR_DEFAULT_CLASSES, ...ONLY_SHOW_MD];
+ },
+ labelSearchBox() {
+ return this.$refs.searchLabelInputBox?.$el.querySelector('[role=searchbox]');
+ },
+ combinedSelectedFilters() {
+ const appliedSelectedLabelKeys = this.appliedSelectedLabels.map((label) => label.key);
+ const { labels = [] } = this.query;
+
+ return uniq([...appliedSelectedLabelKeys, ...labels]);
+ },
+ searchLabels: {
+ get() {
+ return this.searchLabelString;
+ },
+ set(value) {
+ this.setLabelFilterSearch({ value });
+ },
+ },
+ selectedFilters: {
+ get() {
+ return this.combinedSelectedFilters;
+ },
+ set(value) {
+ this.setQuery({ key: this.$options.labelFilterData?.filterParam, value });
+
+ trackSelectCheckbox(value);
+ },
+ },
+ },
+ async created() {
+ await this.fetchAllAggregation();
+ },
+ methods: {
+ ...mapActions(['fetchAllAggregation', 'setQuery', 'closeLabel', 'setLabelFilterSearch']),
+ openDropdown() {
+ this.isFocused = true;
+
+ trackOpenDropdown();
+ },
+ closeDropdown(event) {
+ const { target } = event;
+
+ if (this.labelSearchBox !== target) {
+ this.isFocused = false;
+ }
+ },
+ onLabelClose(event) {
+ if (!event?.target?.closest('.gl-label')?.dataset) {
+ return;
+ }
+
+ const { key } = event.target.closest('.gl-label').dataset;
+ this.closeLabel({ key });
+ },
+ reactiveLabelColor(label) {
+ const { color, key } = label;
+
+ return this.query?.labels?.some((labelKey) => labelKey === key)
+ ? color
+ : `rgba(${rgbFromHex(color)}, 0.3)`;
+ },
+ isLabelClosable(label) {
+ const { key } = label;
+ return this.query?.labels?.some((labelKey) => labelKey === key);
+ },
+ },
+ FIRST_DROPDOWN_INDEX,
+ SEARCH_RESULTS_DESCRIPTION,
+ SEARCH_INPUT_DESCRIPTION,
+ labelFilterData,
+};
+</script>
+
+<template>
+ <div class="gl-pb-0 gl-md-pt-0 label-filter gl-relative">
+ <h5
+ class="gl-my-0"
+ data-testid="label-filter-title"
+ :class="{ 'gl-font-sm': useSidebarNavigation }"
+ >
+ {{ $options.labelFilterData.header }}
+ </h5>
+ <div class="gl-my-5">
+ <gl-label
+ v-for="label in appliedSelectedLabels"
+ :key="label.key"
+ class="gl-mr-2 gl-mb-2 gl-bg-gray-10"
+ :data-key="label.key"
+ :background-color="reactiveLabelColor(label)"
+ :title="label.title"
+ :show-close-button="isLabelClosable(label)"
+ @close="onLabelClose"
+ />
+ </div>
+ <gl-search-box-by-type
+ ref="searchLabelInputBox"
+ v-model="searchLabels"
+ role="searchbox"
+ autocomplete="off"
+ :placeholder="$options.i18n.SEARCH_LABELS"
+ :aria-activedescendant="currentFocusedId"
+ :aria-describedby="$options.SEARCH_INPUT_DESCRIPTION"
+ @focusin="openDropdown"
+ @keydown.esc="closeDropdown"
+ />
+ <span :id="$options.SEARCH_INPUT_DESCRIPTION" role="region" class="gl-sr-only">{{
+ searchInputDescribeBy
+ }}</span>
+ <span
+ role="region"
+ :data-testid="$options.SEARCH_RESULTS_DESCRIPTION"
+ class="gl-sr-only"
+ aria-live="polite"
+ aria-atomic="true"
+ >
+ {{ dropdownResultsDescription }}
+ </span>
+ <div
+ v-if="isFocused"
+ v-outside="closeDropdown"
+ data-testid="header-search-dropdown-menu"
+ class="header-search-dropdown-menu gl-overflow-y-auto gl-absolute gl-w-full gl-bg-white gl-border-1 gl-rounded-base gl-border-solid gl-border-gray-200 gl-shadow-x0-y2-b4-s0 gl-mt-3 gl-z-index-1"
+ :class="{
+ 'gl-max-w-none!': useSidebarNavigation,
+ 'gl-min-w-full!': useSidebarNavigation,
+ 'gl-w-full!': useSidebarNavigation,
+ }"
+ >
+ <div class="header-search-dropdown-content gl-py-2">
+ <dropdown-keyboard-navigation
+ v-model="currentFocusIndex"
+ :max="filteredLabels.length - 1"
+ :min="$options.FIRST_DROPDOWN_INDEX"
+ :default-index="defaultIndex"
+ :enable-cycle="true"
+ />
+ <div v-if="!aggregations.error">
+ <gl-dropdown-section-header v-if="hasSelectedLabels || hasUnselectedLabels">{{
+ $options.i18n.DROPDOWN_HEADER
+ }}</gl-dropdown-section-header>
+ <gl-dropdown-form>
+ <gl-form-checkbox-group v-model="selectedFilters">
+ <label-dropdown-items
+ v-if="hasSelectedLabels"
+ data-testid="selected-lavel-items"
+ :labels="filteredAppliedSelectedLabels"
+ />
+ <gl-dropdown-divider v-if="hasSelectedLabels && hasUnselectedLabels" />
+ <label-dropdown-items
+ v-if="hasUnselectedLabels"
+ data-testid="unselected-lavel-items"
+ :labels="filteredUnselectedLabels"
+ />
+ </gl-form-checkbox-group>
+ </gl-dropdown-form>
+ </div>
+ <gl-alert v-else :dismissible="false" variant="danger">
+ {{ $options.i18n.AGGREGATIONS_ERROR_MESSAGE }}
+ </gl-alert>
+ <gl-loading-icon v-if="aggregations.fetching" size="lg" class="my-4" />
+ </div>
+ </div>
+ <hr v-if="!useSidebarNavigation" :class="dividerClasses" />
+ </div>
+</template>
diff --git a/app/assets/javascripts/search/sidebar/components/label_filter/label_dropdown_items.vue b/app/assets/javascripts/search/sidebar/components/label_filter/label_dropdown_items.vue
new file mode 100644
index 00000000000..7a9e6a2e4fc
--- /dev/null
+++ b/app/assets/javascripts/search/sidebar/components/label_filter/label_dropdown_items.vue
@@ -0,0 +1,43 @@
+<script>
+import { GlFormCheckbox } from '@gitlab/ui';
+
+export default {
+ name: 'LabelDropdownItems',
+ components: {
+ GlFormCheckbox,
+ },
+ props: {
+ labels: {
+ type: Array,
+ required: true,
+ },
+ },
+};
+</script>
+<template>
+ <ul class="gl-list-style-none gl-px-0">
+ <li
+ v-for="label in labels"
+ :id="label.key"
+ :ref="label.key"
+ :key="label.key"
+ :aria-label="label.title"
+ tabindex="-1"
+ class="gl-px-5 gl-py-3 label-filter-menu-item"
+ >
+ <gl-form-checkbox
+ class="label-with-color-checkbox gl-display-inline-flex gl-h-5 gl-min-h-5"
+ :value="label.key"
+ >
+ <span
+ data-testid="label-color-indicator"
+ class="gl-rounded-base gl-w-5 gl-h-5 gl-display-inline-block gl-vertical-align-bottom gl-mr-3"
+ :style="{ 'background-color': label.color }"
+ ></span>
+ <span class="gl-reset-text-align gl-m-0 gl-p-0 label-title">{{
+ label.title
+ }}</span></gl-form-checkbox
+ >
+ </li>
+ </ul>
+</template>
diff --git a/app/assets/javascripts/search/sidebar/components/label_filter/tracking.js b/app/assets/javascripts/search/sidebar/components/label_filter/tracking.js
new file mode 100644
index 00000000000..c38922a559c
--- /dev/null
+++ b/app/assets/javascripts/search/sidebar/components/label_filter/tracking.js
@@ -0,0 +1,21 @@
+import Tracking from '~/tracking';
+
+export const TRACKING_CATEGORY = 'Language filters';
+export const TRACKING_LABEL_FILTER = 'Label Key';
+
+export const TRACKING_LABEL_DROPDOWN = 'Dropdown';
+export const TRACKING_LABEL_CHECKBOX = 'Label Checkbox';
+
+export const TRACKING_ACTION_SELECT = 'search:agreggations:label:select';
+export const TRACKING_ACTION_SHOW = 'search:agreggations:label:show';
+
+export const trackSelectCheckbox = (value) =>
+ Tracking.event(TRACKING_ACTION_SELECT, TRACKING_LABEL_CHECKBOX, {
+ label: TRACKING_LABEL_FILTER,
+ property: value,
+ });
+
+export const trackOpenDropdown = () =>
+ Tracking.event(TRACKING_ACTION_SHOW, TRACKING_LABEL_DROPDOWN, {
+ label: TRACKING_LABEL_DROPDOWN,
+ });
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 e531abf523b..c10b14bd116 100644
--- a/app/assets/javascripts/search/sidebar/components/language_filter/index.vue
+++ b/app/assets/javascripts/search/sidebar/components/language_filter/index.vue
@@ -2,7 +2,6 @@
import { GlButton, GlAlert, GlForm } from '@gitlab/ui';
import { mapState, mapActions, mapGetters } from 'vuex';
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';
@@ -24,7 +23,6 @@ export default {
GlAlert,
GlForm,
},
- mixins: [glFeatureFlagsMixin()],
data() {
return {
showAll: false,
diff --git a/app/assets/javascripts/search/store/getters.js b/app/assets/javascripts/search/store/getters.js
index d31d2b5ae11..c7cb595f42f 100644
--- a/app/assets/javascripts/search/store/getters.js
+++ b/app/assets/javascripts/search/store/getters.js
@@ -41,8 +41,11 @@ export const filteredLabels = (state) => {
export const filteredAppliedSelectedLabels = (state) =>
filteredLabels(state)?.filter((label) => state?.urlQuery?.labels?.includes(label.key));
-export const appliedSelectedLabels = (state) =>
- labelAggregationBuckets(state)?.filter((label) => state?.urlQuery?.labels?.includes(label.key));
+export const appliedSelectedLabels = (state) => {
+ return labelAggregationBuckets(state)?.filter((label) =>
+ state?.urlQuery?.labels?.includes(label.key),
+ );
+};
export const filteredUnappliedSelectedLabels = (state) =>
filteredLabels(state)?.filter((label) => state?.query?.labels?.includes(label.key));
diff --git a/app/assets/javascripts/super_sidebar/components/global_search/command_palette/command_autocomplete_item.vue b/app/assets/javascripts/super_sidebar/components/global_search/command_palette/command_autocomplete_item.vue
deleted file mode 100644
index 6ca1e40f1f6..00000000000
--- a/app/assets/javascripts/super_sidebar/components/global_search/command_palette/command_autocomplete_item.vue
+++ /dev/null
@@ -1,37 +0,0 @@
-<script>
-import { GlIcon } from '@gitlab/ui';
-import SafeHtml from '~/vue_shared/directives/safe_html';
-import highlight from '~/lib/utils/highlight';
-
-export default {
- name: 'CommandPaletteCommandAutocompleteItem',
- components: {
- GlIcon,
- },
- directives: {
- SafeHtml,
- },
- props: {
- command: {
- type: Object,
- required: true,
- },
- searchQuery: {
- type: String,
- required: true,
- },
- },
- computed: {
- highlightedName() {
- return highlight(this.command.text, this.searchQuery);
- },
- },
-};
-</script>
-
-<template>
- <div class="gl-display-flex gl-align-items-center">
- <gl-icon v-if="command.icon" class="gl-mr-3" :name="command.icon" />
- <span v-safe-html="highlightedName" class="gl-text-gray-900"></span>
- </div>
-</template>
diff --git a/app/assets/javascripts/super_sidebar/components/global_search/command_palette/command_palette_items.vue b/app/assets/javascripts/super_sidebar/components/global_search/command_palette/command_palette_items.vue
index 4c1f3d3e0eb..96e6c9bab9e 100644
--- a/app/assets/javascripts/super_sidebar/components/global_search/command_palette/command_palette_items.vue
+++ b/app/assets/javascripts/super_sidebar/components/global_search/command_palette/command_palette_items.vue
@@ -3,30 +3,29 @@ import { debounce } from 'lodash';
import fuzzaldrinPlus from 'fuzzaldrin-plus';
import { GlDisclosureDropdownGroup, GlLoadingIcon } from '@gitlab/ui';
import axios from '~/lib/utils/axios_utils';
-import { joinPaths } from '~/lib/utils/url_utility';
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
+import { getFormattedItem } from '../utils';
import {
- USERS_ENDPOINT,
COMMON_HANDLES,
COMMAND_HANDLE,
USER_HANDLE,
+ PROJECT_HANDLE,
+ ISSUE_HANDLE,
GLOBAL_COMMANDS_GROUP_TITLE,
- USERS_GROUP_TITLE,
PAGES_GROUP_TITLE,
+ GROUP_TITLES,
} from './constants';
-import { userMapper, commandMapper, linksReducer } from './utils';
-import UserAutocompleteItem from './user_autocomplete_item.vue';
-import CommandAutocompleteItem from './command_autocomplete_item.vue';
+import SearchItem from './search_item.vue';
+import { commandMapper, linksReducer, autocompleteQuery } from './utils';
export default {
name: 'CommandPaletteItems',
components: {
GlDisclosureDropdownGroup,
GlLoadingIcon,
- UserAutocompleteItem,
- CommandAutocompleteItem,
+ SearchItem,
},
- inject: ['commandPaletteCommands', 'commandPaletteLinks'],
+ inject: ['commandPaletteCommands', 'commandPaletteLinks', 'autocompletePath', 'searchContext'],
props: {
searchQuery: {
type: String,
@@ -74,7 +73,16 @@ export default {
return this.groups?.length && this.groups.some((group) => group.items?.length);
},
hasSearchQuery() {
- return this.searchQuery?.length;
+ if (this.isCommandMode) {
+ return this.searchQuery?.length > 0;
+ }
+ return this.searchQuery?.length > 2;
+ },
+ searchTerm() {
+ if (this.handle === ISSUE_HANDLE) {
+ return `${ISSUE_HANDLE}${this.searchQuery}`;
+ }
+ return this.searchQuery;
},
},
watch: {
@@ -85,7 +93,9 @@ export default {
this.getCommandsAndPages();
break;
case USER_HANDLE:
- this.getUsers();
+ case PROJECT_HANDLE:
+ case ISSUE_HANDLE:
+ this.getScopedItems();
break;
default:
break;
@@ -103,7 +113,6 @@ export default {
this.groups = [...this.commands];
return;
}
-
const matchedLinks = this.filterBySearchQuery(this.links);
if (this.filteredCommands.length || matchedLinks.length) {
@@ -121,24 +130,22 @@ export default {
});
}
},
- getUsers: debounce(function debouncedUserSearch() {
+ getScopedItems: debounce(function debouncedSearch() {
if (this.searchQuery && this.searchQuery.length < 3) return null;
this.loading = true;
return axios
- .get(joinPaths(gon.relative_url_root || '', USERS_ENDPOINT), {
- params: {
- search: this.searchQuery,
- },
- })
+ .get(
+ autocompleteQuery({
+ path: this.autocompletePath,
+ searchTerm: this.searchTerm,
+ handle: this.handle,
+ projectId: this.searchContext.project?.id,
+ }),
+ )
.then(({ data }) => {
- this.groups = [
- {
- name: USERS_GROUP_TITLE,
- items: data.map(userMapper),
- },
- ];
+ this.groups = this.getGroups(data);
})
.catch((error) => {
this.error = error;
@@ -147,6 +154,14 @@ export default {
this.loading = false;
});
}, DEFAULT_DEBOUNCE_AND_THROTTLE_MS),
+ getGroups(data) {
+ return [
+ {
+ name: GROUP_TITLES[this.handle],
+ items: data.map(getFormattedItem),
+ },
+ ];
+ },
},
};
</script>
@@ -164,12 +179,7 @@ export default {
class="{'gl-mt-0!': index===0}"
>
<template #list-item="{ item }">
- <user-autocomplete-item v-if="isUserMode" :user="item" :search-query="searchQuery" />
- <command-autocomplete-item
- v-if="isCommandMode"
- :command="item"
- :search-query="searchQuery"
- />
+ <search-item :item="item" :search-query="searchQuery" />
</template>
</gl-disclosure-dropdown-group>
</template>
diff --git a/app/assets/javascripts/super_sidebar/components/global_search/command_palette/constants.js b/app/assets/javascripts/super_sidebar/components/global_search/command_palette/constants.js
index a3bb102f6f9..9dab16984f5 100644
--- a/app/assets/javascripts/super_sidebar/components/global_search/command_palette/constants.js
+++ b/app/assets/javascripts/super_sidebar/components/global_search/command_palette/constants.js
@@ -1,26 +1,45 @@
import { s__, sprintf } from '~/locale';
-export const USERS_ENDPOINT = '/-/autocomplete/users.json';
export const COMMAND_HANDLE = '>';
export const USER_HANDLE = '@';
+export const PROJECT_HANDLE = '&';
+export const ISSUE_HANDLE = '#';
-export const COMMON_HANDLES = [COMMAND_HANDLE, USER_HANDLE];
+export const COMMON_HANDLES = [COMMAND_HANDLE, USER_HANDLE, PROJECT_HANDLE, ISSUE_HANDLE];
export const SEARCH_OR_COMMAND_MODE_PLACEHOLDER = sprintf(
s__(
- 'CommandPalette|Type %{commandHandle} for command, %{userHandle} for user or perform generic search...',
+ 'CommandPalette|Type %{commandHandle} for command, %{userHandle} for user, %{projectHandle} for project, %{issueHandle} for issue or perform generic search...',
),
{
commandHandle: COMMAND_HANDLE,
userHandle: USER_HANDLE,
+ issueHandle: ISSUE_HANDLE,
+ projectHandle: PROJECT_HANDLE,
},
false,
);
-export const SEARCH_SCOPE = {
+export const SEARCH_SCOPE_PLACEHOLDER = {
[COMMAND_HANDLE]: s__('CommandPalette|command'),
[USER_HANDLE]: s__('CommandPalette|user (enter at least 3 chars)'),
+ [PROJECT_HANDLE]: s__('CommandPalette|project (enter at least 3 chars)'),
+ [ISSUE_HANDLE]: s__('CommandPalette|issue (enter at least 3 chars)'),
+};
+
+export const SEARCH_SCOPE = {
+ [USER_HANDLE]: 'user',
+ [PROJECT_HANDLE]: 'project',
+ [ISSUE_HANDLE]: 'issue',
};
export const GLOBAL_COMMANDS_GROUP_TITLE = s__('CommandPalette|Global Commands');
-export const USERS_GROUP_TITLE = s__('CommandPalette|Users');
+export const USERS_GROUP_TITLE = s__('GlobalSearch|Users');
export const PAGES_GROUP_TITLE = s__('CommandPalette|Pages');
+export const PROJECTS_GROUP_TITLE = s__('GlobalSearch|Projects');
+export const ISSUE_GROUP_TITLE = s__('GlobalSearch|Recent issues');
+
+export const GROUP_TITLES = {
+ [USER_HANDLE]: USERS_GROUP_TITLE,
+ [PROJECT_HANDLE]: PROJECTS_GROUP_TITLE,
+ [ISSUE_HANDLE]: ISSUE_GROUP_TITLE,
+};
diff --git a/app/assets/javascripts/super_sidebar/components/global_search/command_palette/fake_search_input.vue b/app/assets/javascripts/super_sidebar/components/global_search/command_palette/fake_search_input.vue
index 201d21f56fe..dce2b24f551 100644
--- a/app/assets/javascripts/super_sidebar/components/global_search/command_palette/fake_search_input.vue
+++ b/app/assets/javascripts/super_sidebar/components/global_search/command_palette/fake_search_input.vue
@@ -1,5 +1,5 @@
<script>
-import { COMMON_HANDLES, SEARCH_SCOPE } from './constants';
+import { COMMON_HANDLES, SEARCH_SCOPE_PLACEHOLDER } from './constants';
export default {
name: 'FakeSearchInput',
@@ -16,7 +16,7 @@ export default {
},
computed: {
placeholder() {
- return SEARCH_SCOPE[this.scope];
+ return SEARCH_SCOPE_PLACEHOLDER[this.scope];
},
},
};
diff --git a/app/assets/javascripts/super_sidebar/components/global_search/command_palette/user_autocomplete_item.vue b/app/assets/javascripts/super_sidebar/components/global_search/command_palette/search_item.vue
index 6a22df79c0f..b940c7c24c6 100644
--- a/app/assets/javascripts/super_sidebar/components/global_search/command_palette/user_autocomplete_item.vue
+++ b/app/assets/javascripts/super_sidebar/components/global_search/command_palette/search_item.vue
@@ -1,19 +1,20 @@
<script>
-import { GlAvatar } from '@gitlab/ui';
+import { GlAvatar, GlIcon } from '@gitlab/ui';
import SafeHtml from '~/vue_shared/directives/safe_html';
import highlight from '~/lib/utils/highlight';
import { AVATAR_SHAPE_OPTION_RECT } from '~/vue_shared/constants';
export default {
- name: 'CommandPaletteUserAutocompleteItem',
+ name: 'CommandPaletteSearchItem',
components: {
GlAvatar,
+ GlIcon,
},
directives: {
SafeHtml,
},
props: {
- user: {
+ item: {
type: Object,
required: true,
},
@@ -24,7 +25,7 @@ export default {
},
computed: {
highlightedName() {
- return highlight(this.user.text, this.searchQuery);
+ return highlight(this.item.text, this.searchQuery);
},
},
AVATAR_SHAPE_OPTION_RECT,
@@ -34,18 +35,23 @@ export default {
<template>
<div class="gl-display-flex gl-align-items-center">
<gl-avatar
- v-if="user.avatar_url"
+ v-if="item.avatar_url !== undefined"
class="gl-mr-3"
- :src="user.avatar_url"
- :entity-id="user.id"
- :entity-name="user.text"
- :size="16"
+ :src="item.avatar_url"
+ :entity-id="item.entity_id"
+ :entity-name="item.entity_name"
+ :size="item.avatar_size"
:shape="$options.AVATAR_SHAPE_OPTION_RECT"
aria-hidden="true"
/>
+ <gl-icon v-if="item.icon" class="gl-mr-3" :name="item.icon" />
<span class="gl-display-flex gl-flex-direction-column">
<span v-safe-html="highlightedName" class="gl-text-gray-900"></span>
- <span v-safe-html="user.username" class="gl-font-sm gl-text-gray-500"></span>
+ <span
+ v-if="item.namespace"
+ v-safe-html="item.namespace"
+ class="gl-font-sm gl-text-gray-500"
+ ></span>
</span>
</div>
</template>
diff --git a/app/assets/javascripts/super_sidebar/components/global_search/command_palette/utils.js b/app/assets/javascripts/super_sidebar/components/global_search/command_palette/utils.js
index be1edcc6e16..5c8c0e59eaf 100644
--- a/app/assets/javascripts/super_sidebar/components/global_search/command_palette/utils.js
+++ b/app/assets/javascripts/super_sidebar/components/global_search/command_palette/utils.js
@@ -1,8 +1,6 @@
-export const userMapper = ({ name: text, web_url: href, ...user } = {}) => ({
- text,
- href,
- ...user,
-});
+import { isNil, omitBy } from 'lodash';
+import { objectToQuery } from '~/lib/utils/url_utility';
+import { SEARCH_SCOPE } from './constants';
export const commandMapper = ({ name, items }) => {
// TODO: we filter out invite_members for now, because it is complicated to add the invite members modal here
@@ -33,3 +31,17 @@ export const linksReducer = (acc, menuItem) => {
}
return acc;
};
+
+export const autocompleteQuery = ({ path, searchTerm, handle, projectId }) => {
+ const query = omitBy(
+ {
+ term: searchTerm,
+ project_id: projectId,
+ filter: 'search',
+ scope: SEARCH_SCOPE[handle],
+ },
+ isNil,
+ );
+
+ return `${path}?${objectToQuery(query)}`;
+};
diff --git a/app/assets/javascripts/super_sidebar/super_sidebar_bundle.js b/app/assets/javascripts/super_sidebar/super_sidebar_bundle.js
index 7f49d261bcb..f6afde02fa5 100644
--- a/app/assets/javascripts/super_sidebar/super_sidebar_bundle.js
+++ b/app/assets/javascripts/super_sidebar/super_sidebar_bundle.js
@@ -89,6 +89,8 @@ export const initSuperSidebar = () => {
...getTrialStatusWidgetData(sidebarData),
commandPaletteCommands,
commandPaletteLinks,
+ autocompletePath,
+ searchContext,
},
store: createStore({
searchPath,
diff --git a/app/assets/javascripts/vue_shared/components/timezone_dropdown/timezone_dropdown.vue b/app/assets/javascripts/vue_shared/components/timezone_dropdown/timezone_dropdown.vue
index 04dc3b20dff..6764ad4ce73 100644
--- a/app/assets/javascripts/vue_shared/components/timezone_dropdown/timezone_dropdown.vue
+++ b/app/assets/javascripts/vue_shared/components/timezone_dropdown/timezone_dropdown.vue
@@ -9,6 +9,11 @@ export default {
GlCollapsibleListbox,
},
props: {
+ headerText: {
+ type: String,
+ required: false,
+ default: '',
+ },
value: {
type: String,
required: true,
@@ -98,6 +103,7 @@ export default {
type="hidden"
/>
<gl-collapsible-listbox
+ :header-text="headerText"
:items="filteredListboxItems"
:toggle-text="selectedTimezoneLabel"
:toggle-class="additionalClass"
diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss
index e57dad9e4cb..5fdab7891ec 100644
--- a/app/assets/stylesheets/framework/markdown_area.scss
+++ b/app/assets/stylesheets/framework/markdown_area.scss
@@ -105,6 +105,7 @@
padding: 5px;
box-shadow: none;
width: 100%;
+ resize: none !important;
}
.md-suggestion-diff {
diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb
index a3c6499bc54..45aefe48538 100644
--- a/app/controllers/search_controller.rb
+++ b/app/controllers/search_controller.rb
@@ -34,6 +34,7 @@ class SearchController < ApplicationController
before_action only: :show do
update_scope_for_code_search
end
+
rescue_from ActiveRecord::QueryCanceled, with: :render_timeout
layout 'search'
@@ -111,11 +112,12 @@ class SearchController < ApplicationController
@project = search_service.project
@ref = params[:project_ref] if params[:project_ref].present?
@filter = params[:filter]
+ @scope = params[:scope]
# Cache the response on the frontend
expires_in 1.minute
- render json: Gitlab::Json.dump(search_autocomplete_opts(term, filter: @filter))
+ render json: Gitlab::Json.dump(search_autocomplete_opts(term, filter: @filter, scope: @scope))
end
def opensearch
diff --git a/app/finders/namespaces/projects_finder.rb b/app/finders/namespaces/projects_finder.rb
index c96f9527dd8..0194ee40801 100644
--- a/app/finders/namespaces/projects_finder.rb
+++ b/app/finders/namespaces/projects_finder.rb
@@ -32,6 +32,8 @@ module Namespaces
namespace.projects.with_route
end
+ collection = collection.not_aimed_for_deletion if params[:not_aimed_for_deletion].present?
+
collection = filter_projects(collection)
sort(collection)
diff --git a/app/graphql/resolvers/namespace_projects_resolver.rb b/app/graphql/resolvers/namespace_projects_resolver.rb
index 726e78f9971..f0781058bea 100644
--- a/app/graphql/resolvers/namespace_projects_resolver.rb
+++ b/app/graphql/resolvers/namespace_projects_resolver.rb
@@ -7,6 +7,11 @@ module Resolvers
default_value: false,
description: 'Include also subgroup projects.'
+ argument :not_aimed_for_deletion, GraphQL::Types::Boolean,
+ required: false,
+ default_value: false,
+ description: 'Include projects that are not aimed for deletion.'
+
argument :search, GraphQL::Types::String,
required: false,
default_value: nil,
@@ -60,6 +65,7 @@ module Resolvers
def finder_params(args)
{
include_subgroups: args.dig(:include_subgroups),
+ not_aimed_for_deletion: args.dig(:not_aimed_for_deletion),
sort: args.dig(:sort),
search: args.dig(:search),
ids: parse_gids(args.dig(:ids)),
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
index 1d58239aea9..ecaa65dd129 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -14,12 +14,12 @@ module SearchHelper
:project_ids
].freeze
- def search_autocomplete_opts(term, filter: nil)
+ def search_autocomplete_opts(term, filter: nil, scope: nil)
return unless current_user
results = case filter&.to_sym
when :search
- resource_results(term)
+ resource_results(term, scope: scope)
when :generic
[
recent_items_autocomplete(term),
@@ -36,8 +36,9 @@ module SearchHelper
results.flatten { |item| item[:label] }
end
- def resource_results(term)
+ def resource_results(term, scope: nil)
return [] if term.length < Gitlab::Search::Params::MIN_TERM_LENGTH
+ return scope_specific_results(term, scope) if scope.present?
[
groups_autocomplete(term),
@@ -47,6 +48,19 @@ module SearchHelper
].flatten
end
+ def scope_specific_results(term, scope)
+ case scope&.to_sym
+ when :project
+ projects_autocomplete(term)
+ when :user
+ users_autocomplete(term)
+ when :issue
+ recent_issues_autocomplete(term)
+ else
+ []
+ end
+ end
+
def generic_results(term)
search_pattern = Regexp.new(Regexp.escape(term), "i")
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 2ce45b90330..116108ceaf9 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -2055,6 +2055,10 @@ class MergeRequest < ApplicationRecord
NewMergeRequestWorker.perform_async(id, author_id)
end
+ def check_for_spam?(*)
+ spammable_attribute_changed? && project.public?
+ end
+
private
attr_accessor :skip_fetch_ref
diff --git a/app/views/shared/runners/_runner_details.html.haml b/app/views/shared/runners/_runner_details.html.haml
index 686cd1a081b..30e5587c413 100644
--- a/app/views/shared/runners/_runner_details.html.haml
+++ b/app/views/shared/runners/_runner_details.html.haml
@@ -1,4 +1,4 @@
-%h1.page-title.gl-font-size-h-display
+%h1.page-title.gl-font-size-h-display.gl-display-flex.gl-align-items-center
= s_('Runners|Runner #%{runner_id}') % { runner_id: runner.id }
= render 'shared/runners/runner_type_badge', runner: runner
diff --git a/app/views/shared/runners/_runner_type_badge.html.haml b/app/views/shared/runners/_runner_type_badge.html.haml
index a8a93f3dd76..9930f60b755 100644
--- a/app/views/shared/runners/_runner_type_badge.html.haml
+++ b/app/views/shared/runners/_runner_type_badge.html.haml
@@ -1,7 +1,7 @@
-
-- if runner.instance_type?
- = gl_badge_tag s_('Runners|shared'), variant: :success
-- elsif runner.group_type?
- = gl_badge_tag s_('Runners|group'), variant: :success
-- else
- = gl_badge_tag s_('Runners|project'), variant: :info
+.gl-ml-2
+ - if runner.instance_type?
+ = gl_badge_tag s_('Runners|shared'), variant: :success
+ - elsif runner.group_type?
+ = gl_badge_tag s_('Runners|group'), variant: :success
+ - else
+ = gl_badge_tag s_('Runners|project'), variant: :info
diff --git a/config/metrics/counts_28d/20230531170613_ci_builds.yml b/config/metrics/counts_28d/20230531170613_ci_builds.yml
new file mode 100644
index 00000000000..2240f567633
--- /dev/null
+++ b/config/metrics/counts_28d/20230531170613_ci_builds.yml
@@ -0,0 +1,22 @@
+---
+key_path: counts_monthly.ci_builds
+description: Total monthly (28D) ci builds in Gitlab repositories for all project and project types
+product_section: ops
+product_stage: verify
+product_group: pipeline_execution
+value_type: number
+status: active
+milestone: "16.1"
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/122227
+time_frame: 28d
+data_source: database
+data_category: optional
+instrumentation_class: CountAllCiBuildsMetric
+performance_indicator_type: []
+distribution:
+- ce
+- ee
+tier:
+- free
+- premium
+- ultimate
diff --git a/db/post_migrate/20230614181637_add_idx_issues_on_work_item_type_project_closed_at_where_closed.rb b/db/post_migrate/20230614181637_add_idx_issues_on_work_item_type_project_closed_at_where_closed.rb
new file mode 100644
index 00000000000..dd260b868e5
--- /dev/null
+++ b/db/post_migrate/20230614181637_add_idx_issues_on_work_item_type_project_closed_at_where_closed.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class AddIdxIssuesOnWorkItemTypeProjectClosedAtWhereClosed < Gitlab::Database::Migration[2.1]
+ INDEX_NAME = 'idx_issues_on_project_work_item_type_closed_at_where_closed'
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :issues, [:project_id, :work_item_type_id, :closed_at], where: 'state_id = 2', name: INDEX_NAME
+ end
+
+ def down
+ remove_concurrent_index_by_name :issues, INDEX_NAME
+ end
+end
diff --git a/db/schema_migrations/20230614181637 b/db/schema_migrations/20230614181637
new file mode 100644
index 00000000000..0cd4d1d26d3
--- /dev/null
+++ b/db/schema_migrations/20230614181637
@@ -0,0 +1 @@
+a4587f858d87d2a79607a6449201524a62fff8edef613ad4b5d0b46da08742ab \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 167f27aef49..7215a1114a8 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -29790,6 +29790,8 @@ CREATE INDEX idx_issues_on_project_id_and_rel_position_and_id_and_state_id ON is
CREATE INDEX idx_issues_on_project_id_and_updated_at_and_id_and_state_id ON issues USING btree (project_id, updated_at, id, state_id);
+CREATE INDEX idx_issues_on_project_work_item_type_closed_at_where_closed ON issues USING btree (project_id, work_item_type_id, closed_at) WHERE (state_id = 2);
+
CREATE INDEX idx_issues_on_state_id ON issues USING btree (state_id);
CREATE INDEX idx_jira_connect_subscriptions_on_installation_id ON jira_connect_subscriptions USING btree (jira_connect_installation_id);
diff --git a/doc/administration/geo/replication/configuration.md b/doc/administration/geo/replication/configuration.md
index 0eba666979e..18d0440965e 100644
--- a/doc/administration/geo/replication/configuration.md
+++ b/doc/administration/geo/replication/configuration.md
@@ -318,7 +318,7 @@ On the **primary** site:
1. Expand **Visibility and access controls**.
1. If using Git over SSH, then:
1. Ensure "Enabled Git access protocols" is set to "Both SSH and HTTP(S)".
- 1. Follow [Fast lookup of authorized SSH keys in the database](../../operations/fast_ssh_key_lookup.md) on both primary and secondary sites.
+ 1. Follow [Fast lookup of authorized SSH keys in the database](../../operations/fast_ssh_key_lookup.md) on **all primary and secondary** sites.
1. If not using Git over SSH, then set "Enabled Git access protocols" to "Only HTTP(S)".
### Step 6. Verify proper functioning of the **secondary** site
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 1e67323279b..0fc857a4ebd 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -16428,6 +16428,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| <a id="groupprojectshasvulnerabilities"></a>`hasVulnerabilities` | [`Boolean`](#boolean) | Returns only the projects which have vulnerabilities. |
| <a id="groupprojectsids"></a>`ids` | [`[ID!]`](#id) | Filter projects by IDs. |
| <a id="groupprojectsincludesubgroups"></a>`includeSubgroups` | [`Boolean`](#boolean) | Include also subgroup projects. |
+| <a id="groupprojectsnotaimedfordeletion"></a>`notAimedForDeletion` | [`Boolean`](#boolean) | Include projects that are not aimed for deletion. |
| <a id="groupprojectssearch"></a>`search` | [`String`](#string) | Search project with most similar names or paths. |
| <a id="groupprojectssort"></a>`sort` | [`NamespaceProjectSort`](#namespaceprojectsort) | Sort projects by this criteria. |
| <a id="groupprojectswithissuesenabled"></a>`withIssuesEnabled` | [`Boolean`](#boolean) | Return only projects with issues enabled. |
@@ -19007,6 +19008,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| <a id="namespaceprojectshasvulnerabilities"></a>`hasVulnerabilities` | [`Boolean`](#boolean) | Returns only the projects which have vulnerabilities. |
| <a id="namespaceprojectsids"></a>`ids` | [`[ID!]`](#id) | Filter projects by IDs. |
| <a id="namespaceprojectsincludesubgroups"></a>`includeSubgroups` | [`Boolean`](#boolean) | Include also subgroup projects. |
+| <a id="namespaceprojectsnotaimedfordeletion"></a>`notAimedForDeletion` | [`Boolean`](#boolean) | Include projects that are not aimed for deletion. |
| <a id="namespaceprojectssearch"></a>`search` | [`String`](#string) | Search project with most similar names or paths. |
| <a id="namespaceprojectssort"></a>`sort` | [`NamespaceProjectSort`](#namespaceprojectsort) | Sort projects by this criteria. |
| <a id="namespaceprojectswithissuesenabled"></a>`withIssuesEnabled` | [`Boolean`](#boolean) | Return only projects with issues enabled. |
diff --git a/doc/api/users.md b/doc/api/users.md
index 501934887e2..7b418e7a08b 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -115,6 +115,7 @@ GET /users?without_project_bots=true
> - The `namespace_id` field in the response was [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/82045) in GitLab 14.10.
> - The `created_by` field in the response was [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/93092) in GitLab 15.6.
+> - The `scim_identities` field in the response [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/324247) in GitLab 16.1.
```plaintext
GET /users
@@ -448,6 +449,21 @@ see the `group_saml` option and `provisioned_by_group_id` parameter:
}
```
+Users on [GitLab.com Premium or Ultimate](https://about.gitlab.com/pricing/) also
+see the `scim_identities` parameter:
+
+```json
+{
+ ...
+ "extra_shared_runners_minutes_limit": null,
+ "scim_identities": [
+ {"extern_uid": "2435223452345", "group_id": "3", "active": true},
+ {"extern_uid": "john.smith", "group_id": "42", "active": false}
+ ]
+ ...
+}
+```
+
Administrators can use the `created_by` parameter to see if a user account was created:
- [Manually by an administrator](../user/profile/account/create_accounts.md#create-users-in-admin-area).
diff --git a/doc/architecture/blueprints/permissions/index.md b/doc/architecture/blueprints/permissions/index.md
new file mode 100644
index 00000000000..ab66733803d
--- /dev/null
+++ b/doc/architecture/blueprints/permissions/index.md
@@ -0,0 +1,184 @@
+---
+status: proposed
+creation-date: "2023-03-10"
+authors: [ "@jessieay", "@jarka" ]
+coach: "@grzesiek"
+approvers: [ "@hsutor", "@adil.farrukh" ]
+owning-stage: "~devops::manage"
+participating-stages: []
+---
+
+# Permissions Changes required to enable Custom Roles
+
+## Summary
+
+Today, the GitLab permissions system is a backend implementation detail of our
+static [role-based access control system](../../../user/permissions.md#roles).
+
+In %15.9, we [announced](https://about.gitlab.com/blog/2023/03/08/expanding-guest-capabilities-in-gitlab-ultimate/)
+a customer MVC of the custom roles feature. The MVC introduced the ability to
+add one single permission (`read_code`) to a custom role based on a standard
+GitLab Guest role. The MVC was [implemented](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/106256) by
+taking an existing permission from the GitLab authorization framework and
+enabling it if a custom role has it set to `true`.
+
+Post-MVC, the Auth group has started work on making more permissions
+customizable, with the ultimate goal of making *all* permissions customizable.
+
+As we've started planning this work, there are two large challenges:
+
+1. The GitLab permissions system is not a stable, backwards-compatible API.
+ But [the custom roles feature is built on top of the current permissions system](https://gitlab.com/gitlab-org/gitlab/-/issues/352891#note_993031741).
+ Which means that custom roles relies on permissions being a stable,
+ backwards-compatible API. So we must change how we approach our permissions
+ system if we plan to continue on with the current architecture.
+1. Refactoring our permissions system is difficult due to the sheer number of
+ permissions (over 700), duplication of permissions checks throughout the
+ codebase, and the importance of permissions for security (cost
+ of errors is very high).
+
+This blueprint goes into further detail on each of these challenges and suggests
+a path for addressing them.
+
+## What are custom roles?
+
+Our permissions system supports 6 pre-defined roles (Guest, Reporter, Developer, Maintainer, Owner) and users are assigned to per project or group, they can't be modified. Custom roles should solve the problem that our current system is static.
+
+With custom roles, customers can define their own roles and give them permissions they want to. For every role they create they can assign set of permissions. For example, a newly created role "Engineer" could have `read code` and `admin merge requests` enabled but abilities such as `admin issues` disabled.
+
+## Motivation
+
+This plan is important to define because the [custom roles project](https://gitlab.com/groups/gitlab-org/-/epics/4035)'s
+current architecture is built off of our current permissions system, [Declarative Policy](https://gitlab.com/gitlab-org/ruby/gems/declarative-policy).
+Declarative Policy makes it inexpensive to add new permissions, which has
+resulted in our current state of having [over 700 permissions](https://gitlab.com/gitlab-org/gitlab/-/issues/393454#more-context)
+in the `gitlab-org/gitlab` codebase. Even our [permissions documentation](../../../user/permissions.md)
+contains a table with over 200 rows, each row representing a unique
+"permission." Up until now, the proliferation of permissions in the code has
+been manageable because these checks are not part of a public API. With custom
+roles, however, that is changing.
+
+Our current authorization checks are [often duplicated and sprinkled throughout application code](https://gitlab.com/gitlab-org/gitlab/-/issues/352891#note_958192650). For a single web request, there might be several different
+permissions checked in the UI to determine if a user can see those page
+elements, another few permissions checks in the Rails controller to determine if
+the user can access the route at all, and maybe a few more permissions checks
+sprinkled into other Ruby service classes that run as part of the page load.
+This approach is [recommended in the GitLab developer documentation](../../../development/permissions/authorizations.md#where-should-permissions-be-checked)
+as a "defense-in-depth" measure.
+
+In the context of custom roles, however, this approach will not work. When a
+group admin wants to enable a user to take a single action via a custom role,
+that group admin should be able to toggle a single, well-named permission to
+enable the user with the custom role to view or update a resource. This means
+that, for a single web request, we must ensure that only one well-named
+permission is checked. And, the access granted for that permission must be
+relatively stable so that the admin is not giving users more access than they
+think they are. Otherwise, creating and managing custom roles will be overly
+complex and a security nightmare.
+
+While the Auth group owns permissions as a feature, each team owns a set of permissions related to their domain area.
+corner of the `gitlab-org/gitlab` codebase. As a result, all engineering teams that
+are contributing to the `gitlab-org/gitlab` codebase touch permissions. This
+means that it is even more important to provide clear guidelines on the future
+of permissions and automate the enforcement of these guidelines.
+
+### Goals
+
+- Make it possible to customize all permissions via custom roles.
+- Make the GitLab permissions system worthy of being a public API.
+- Improve the naming and consistency of permissions.
+- Reduce the overall number of permissions from 700+ to < 100.
+- Reduce risk of refactors related to permissions.
+- Make refactoring permissions easier by having a way to evaluate behavior other than unit tests and documentation.
+- Track ownership of individual permissions so that DRIs can be consulted on any changes related to a permission that they own
+- Create a SSoT for permissions behavior.
+- Automate generation of permissions documentation.
+
+### Non-Goals
+
+- Pause custom roles project indefinitely while we refactor our existing permissions system (there is high demand for this as an Ultimate feature).
+- Perform a total re-write or re-build of our permissions system (too much upfront investment without providing customer value).
+- Iteratively work on custom roles without ever getting to feature complete ("iterate to nowhere").
+
+## Proposal
+
+1. Introduce a linter that ensures all new permissions adhere to naming
+ conventions.
+1. Reduce the overall number of permissions from 700+ to < 100 by consolidating
+ our existing permissions.
+1. Introduce ownership tags for each permission that requires owning group to
+ review any MRs that update that permission.
+1. Create a Rake task for generating permissions documentation from code so that
+ we have a Single Source of Truth for permissions.
+
+## Alternative Solutions
+
+### Do nothing
+
+Pros:
+
+- No need to lengthy architecture conversation or plan
+- May discover methods for improving permissions system organically as we move
+ forward.
+
+Cons:
+
+- Slow progress in building custom role feature without blueprint for how to
+ think about permissions system as a whole
+- Permissions system can spiral into an unmaintainable code if we iterate on it without a strategically important vision.
+
+### Leave the current permissions system as-is and build a parallel Declarative Policy-based system alongside it to be used for custom roles
+
+Pros:
+
+- Faster to design and build a new system than to do a large-scale refactor of the existing system.
+- Auth team can own this new system entirely.
+
+Cons:
+
+- Maintaining 2 systems
+- Each new "regular" permission added needs a parallel addition to the
+ custom roles system. This makes it difficult to have feature parity between
+ custom roles and static roles.
+- Replacing our existing RBAC system with custom roles (an eventual goal of the
+ custom roles feature) is more difficult with this approach because it requires
+ retiring the legacy permissions system.
+
+### Bundle existing permissions into custom permissions; use "custom permissions" for the custom roles API
+
+Pros:
+
+- Faster to design and build a new system than to do a large-scale refactor of the existing system.
+- Auth team can own these new bundled permissions
+
+Cons:
+
+- Bundling permissions is less granular; the goal of custom permissions is to
+ enable granular access.
+- Each new "regular" permission added needs a parallel addition to the
+ bundled permissions for custom roles. This makes it difficult to have feature
+ parity between custom roles and static roles.
+
+## Glossary
+
+- **RBAC**: Role-based access control; "a method of restricting network access based
+ on the roles of individual users." RBAC is the method of access control that
+ GitLab uses.
+- **Static roles**: the 5 categories that GitLab users can be grouped into: Guest,
+ Reporter, Developer, Maintainer, Owner ([documentation](../../../user/permissions.md#roles)).
+ A static role can be thought of as a group of permissions.
+- **Declarative Policy**: [code library](https://gitlab.com/gitlab-org/ruby/gems/declarative-policy/)
+ used by GitLab to define our authorization logic.
+- **Permissions**: a specific ability that a user with a Role has. For example, a
+ Developer can create merge requests but a Guest cannot. Each row listed in
+ [the permissions documentation](../../../user/permissions.md#project-members-permissions)
+ represents a "permission" but these may not have a 1:1 mapping with a Declarative Policy
+ [ability](https://gitlab.com/gitlab-org/ruby/gems/declarative-policy/-/blob/main/doc/defining-policies.md#invocation).
+ An ability is how permissions are represented in the GitLab codebase.
+- **Access level**: integer value representing a static role, used for determining access and calculating inherited user access in group hierarchies ([documentation](../../../api/access_requests.md#valid-access-levels)).
+
+## Resources
+
+- [Custom Roles MVC announcement](https://github.blog/changelog/2021-10-27-enterprise-organizations-can-now-create-custom-repository-roles)
+- [Custom Roles lunch and learn notes](https://docs.google.com/document/d/1x2ExhGJl2-nEibTaQE_7e5w2sDCRRHiakrBYDspPRqw/edit#)
+- [Discovery on auto-generating documentation for permissions](https://gitlab.com/gitlab-org/gitlab/-/issues/352891#note_989392294).
diff --git a/doc/ci/runners/new_creation_workflow.md b/doc/ci/runners/new_creation_workflow.md
index b1bf033d076..95118cf4b8a 100644
--- a/doc/ci/runners/new_creation_workflow.md
+++ b/doc/ci/runners/new_creation_workflow.md
@@ -139,12 +139,12 @@ or [Personal Access Tokens](../../user/profile/personal_access_tokens.md):
# created with `owner` access and `api` scope.
#
# The output will be parsed by `jq` to extract the token of the newly created runner
-RUNNER_TOKEN=$(curl --silent --method POST "https://gitlab.com/api/v4/user/runners" \
+RUNNER_TOKEN=$(curl --silent --request POST "https://gitlab.com/api/v4/user/runners" \
--header "private-token: $GITLAB_TOKEN" \
--data runner_type=group_type --data group_id=$GROUP_ID --data 'description=My runner' --data 'tag_list=java,linux' \
| jq -r '.token')
-gitlab-runner register
+gitlab-runner register \
--non-interactive \
--executor "shell" \
--url "https://gitlab.com/" \
diff --git a/doc/ci/runners/saas/macos_saas_runner.md b/doc/ci/runners/saas/macos_saas_runner.md
index 1c856925f10..2d897e978ed 100644
--- a/doc/ci/runners/saas/macos_saas_runner.md
+++ b/doc/ci/runners/saas/macos_saas_runner.md
@@ -6,7 +6,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# SaaS runners on macOS (Beta) **(PREMIUM SAAS)**
-SaaS runners on macOS are in [Beta](../../../policy/experiment-beta-support.md#beta) for approved open source programs and customers in Premium and Ultimate plans.
+SaaS runners on macOS are in [Beta](../../../policy/experiment-beta-support.md#beta) for open source programs and customers in Premium and Ultimate plans.
SaaS runners on macOS provide an on-demand macOS build environment integrated with
GitLab SaaS [CI/CD](../../../ci/index.md).
diff --git a/doc/development/fe_guide/customizable_dashboards.md b/doc/development/fe_guide/customizable_dashboards.md
index e3a5e008fe6..ac8b0b8a1ab 100644
--- a/doc/development/fe_guide/customizable_dashboards.md
+++ b/doc/development/fe_guide/customizable_dashboards.md
@@ -8,25 +8,158 @@ info: To determine the technical writer assigned to the Stage/Group associated w
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/98610) in GitLab 15.5 as an [Experiment](../../policy/experiment-beta-support.md#experiment).
-Customizable dashboards provide a dashboard structure that allows users to create
-their own dashboards and commit the structure to a repository.
+Customizable dashboards provide a configuation-based [dashboard](https://design.gitlab.com/patterns/dashboards)
+structure, which is used to render and modify dashboard configurations created by GitLab or users.
-This feature is available for Premium and Ultimate subscriptions.
+The dashboard structure does not provide the means to save and version control
+user configuration files on a repository. This functionality is provided by [Analytics dashboards](../../user/analytics/analytics_dashboards.md)
+which uses the customizable dashboard component.
+
+NOTE:
+Customizable dashboards is intended for Premium and Ultimate subscriptions.
+
+## Overview
+
+Customizable dashboard can be broken down into 3 logical components:
+
+- Dashboard
+- Panels
+- Visualizations
+
+A dashboard consists of a list of panels to render, each panel references one
+visualization, and a single visualization can be shared by many panels.
+
+A typical dashboard structure looks like this:
+
+```plaintext
+dashboard
+├── panelA
+│ └── visualizationX
+├── panelB
+│ └── visualizationY
+├── panelC
+│ └── visualizationY
+```
## Usage
To use customizable dashboards:
-1. Create your dashboard component.
-1. Render an instance of `CustomizableDashboard`.
-1. Pass a list of panels to render.
+1. Create a new Vue component for your dashboard.
+1. Create a [visualization configuration](#visualization-configuration).
+1. Create your [dashboard configuration](#dashboard-configuration).
+1. Render an instance of `CustomizableDashboard` and pass it your [dashboard configuration](#using-the-component).
+
+### Visualization configuration
+
+Each visualization is a graphical representation of query results fetched from a data source.
+
+```javascript
+// visualizations.js
+
+export const pageViewsOverTime = {
+ // The name of the Vue component used to render the query.
+ type: 'LineChart',
+ // Chart options defined by the charting library being used by the panel.
+ options: {
+ xAxis: { name: __('Time'), type: 'time' },
+ yAxis: { name: __('Counts'), type: 'time' },
+ },
+ // The data to query
+ data: {
+ // The data source to query. Here it is Product Analytics.
+ type: 'cube_analytics',
+ // The query to run on the data source. Here in Cube.js format.
+ query: {
+ dimensions: [],
+ filters: [
+ {
+ member: 'SnowplowTrackedEvents.event',
+ operator: 'equals',
+ values: ['page_view']
+ }
+ ],
+ measures: ['SnowplowTrackedEvents.pageViewsCount'],
+ timeDimensions: [
+ {
+ dimension: 'SnowplowTrackedEvents.derivedTstamp',
+ granularity: 'day',
+ },
+ ],
+ limit: 100,
+ timezone: 'UTC',
+ },
+ },
+};
+```
+
+#### Adding a new visualization render type
+
+To add a new visualization render type:
+
+1. Create a new Vue component that accepts `data` and `options` properties.
+See [`line_chart.vue`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/app/assets/javascripts/analytics/analytics_dashboards/components/visualizations/line_chart.vue) as an example.
+1. Add your component to the list of conditional imports in [`panel_base.vue`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/app/assets/javascripts/vue_shared/components/customizable_dashboard/panels_base.vue#L13).
+
+#### Adding a new visualization data source
+
+To add a new data source:
+
+1. Create a new JavaScript module that exports a `fetch` method. See [`cube_analytics.js`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/app/assets/javascripts/analytics/analytics_dashboards/data_sources/cube_analytics.js#L122) as an example.
+1. Add your module to the list exports in [`data_sources/index.js`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/app/assets/javascripts/analytics/analytics_dashboards/data_sources/index.js).
+
+NOTE:
+Your data source must respect the filters so that all panels show data for
+the same date range.
+
+### Dashboard configuration
+
+Here is an example dashboard configuration:
-For example, a customizable dashboard for users over time:
+```javascript
+// constants.js
+import { pageViewsOverTime } from './visualizations';
+
+export const dashboard = {
+ slug: 'my_dashboard', // Used to set the URL path for the dashboard.
+ title: 'My dashboard title', // The title to display.
+ // Each dashboard consists of an array of panels to display.
+ panels: [
+ {
+ id: 1,
+ title: 'Page views over time', // The panel title to display.
+ // The visualization configuration. This can be shared by many panels.
+ visualization: pageViewsOverTime,
+ // Gridstack settings based upon https://github.com/gridstack/gridstack.js/tree/master/doc#item-options.
+ // All values are grid row/column numbers up to 12.
+ // We use the default 12 column grid https://github.com/gridstack/gridstack.js#change-grid-columns.
+ gridAttributes: {
+ yPos: 1,
+ xPos: 0,
+ width: 6,
+ height: 5,
+ },
+ // Optional overrides for the values in `visualization.data.query`.
+ // Here we override the Cube.js query to get page views per week instead of days.
+ queryOverrides: {
+ timeDimensions: {
+ dimension: 'SnowplowTrackedEvents.derivedTstamp',
+ granularity: 'week',
+ },
+ },
+ },
+ ],
+};
+```
+
+### Using the component
+
+Here is an example component that renders a customizable dashboard:
```vue
<script>
import CustomizableDashboard from 'ee/vue_shared/components/customizable_dashboard/customizable_dashboard.vue';
-import { s__ } from '~/locale';
+import { dashboard } from './constants';
export default {
name: 'AnalyticsDashboard',
@@ -35,63 +168,107 @@ export default {
},
data() {
return {
- panels: [
- {
- component: 'CubeLineChart', // The name of the panel component.
- title: s__('ProductAnalytics|Users / Time'), // The title shown on the panel component.
- // Gridstack settings based upon https://github.com/gridstack/gridstack.js/tree/master/doc#item-options.
- // All values are grid row/column numbers up to 12.
- // We use the default 12 column grid https://github.com/gridstack/gridstack.js#change-grid-columns.
- gridAttributes: {
- height: 4,
- width: 6,
- minHeight: 4,
- minWidth: 6,
- xPos: 0,
- yPos: 0,
- },
- // Options that are used to set bespoke values for each panel.
- // Available customizations are determined by the panel itself.
- customizations: {},
- // Chart options defined by the charting library being used by the panel.
- chartOptions: {
- xAxis: { name: __('Time'), type: 'time' },
- yAxis: { name: __('Counts') },
- },
- // The data for the panel.
- // This could be imported or in this case, a query passed to be used by the panels API.
- // Each panel type determines how it handles this property.
- data: {
- query: {
- users: {
- measures: ['TrackedEvents.count'],
- dimensions: ['TrackedEvents.eventType'],
- },
- },
- },
- },
- ]
+ // We keep the original (default) dashboard object to track changes.
+ dashboard: {
+ ...dashboard,
+ default: { ...dashboard, }
+ },
+ // Optional dashboard filters. Currently the only availble filter is date range.
+ defaultFilters: {
+ dateRangeOption: 'last_7_days' // 'custom', 'today', 'last_7_days', 'last_30_days'
+ endDate: new Date(2023, 06, 14),
+ startDate: new Date(2023, 06, 7),
+ },
+ // Set to true to sync the filter object with the URL query string.
+ syncUrlFilters: true,
+ // Set to true to show the date range filter.
+ showDateRangeFilter: true,
+ // The maximum size of the date range allowed in days. 0 for unlimited.
+ dateRangeLimit: 0,
};
},
};
</script>
<template>
- <h1>{{ s__('ProductAnalytics|Analytics dashboard') }}</h1>
- <customizable-dashboard :panels="panels" />
+ <customizable-dashboard
+ :initial-dashboard="dashboard"
+ :default-filters="defaultFilters"
+ :sync-url-filters="syncUrlFilters"
+ :show-date-range-filter="showDateRangeFilter"
+ :date-range-limit="dateRangeLimit"
+ />
</template>
```
-The panels data can be retrieved from a file or API request, or imported through HTML data attributes.
+## Dashboard designer
-For each panel, a `component` is defined. Each `component` is a component declaration and should be included in
-[`vue_shared/components/customizable_dashboard/panels_base.vue`](https://gitlab.com/gitlab-org/gitlab/blob/master/ee/app/assets/javascripts/vue_shared/components/customizable_dashboard/panels_base.vue)
-as a dynamic import, to keep the memory usage down until it is used.
+> Introduced in GitLab 16.1 [with a flag](../../administration/feature_flags.md) named `combined_analytics_dashboards_editor`. Disabled by default.
-For example:
+The dashboard designer provides a graphical interface for users to modify the
+panels and add new ones on user-defined dashboards. Is is not available on
+GitLab hardcoded dashboards.
-```javascript
-components: {
- CubeLineChart: () => import('ee/product_analytics/dashboards/components/panels/cube_line_chart.vue')
+NOTE:
+The dashboard designer is in the early experimental stage and subject to
+change.
+
+```vue
+<script>
+import { s__ } from '~/locale';
+
+export const I18N_MY_NEW_CATEGORY = s__('Namespace|My data source');
+
+export default {
+ name: 'AnalyticsDashboard',
+ data() {
+ return {
+ ...,
+ // Set to true to render the dashboard saving state.
+ isSaving: false,
+ // A list of availble visualizations categorized by feature.
+ availableVisualizations: {
+ // The visualization category title to display.
+ [I18N_MY_NEW_CATEGORY]: {
+ // Set to true when asynchronously loading the visualization IDs
+ loading: false,
+ // List of availble visualization IDs for the user to add to the dashboard.
+ visualizationIds: [
+ 'page_views_over_time',
+ 'events_over_time',
+ ],
+ },
+ }
+ };
+ },
+ methods: {
+ /**
+ * Event handler for when a user saves changes made to the current dashboard.
+ * @param {String} dashboardId The current dashboard ID.
+ * @param {String} newDashboardObject The newly modified dashboard object.
+ */
+ saveDashboard(dashboardId, newDashboardObject) {
+ // Save changes and modify `this.dashboard`.
+ },
+ /**
+ * Event handler for when a user adds a visualization in a new panel.
+ * @param {String} visualizationId The ID (usually filename) of the visualization.
+ * @param {String} visualizationSource The source to get the new visualization config.
+ */
+ addNewPanel(visualizationId, visualizationSource) {
+ // Load the visualization and push a new panel onto `this.dashboard.panels`.
+ },
+ },
}
+</script>
+
+<template>
+ <customizable-dashboard
+ ...
+ :available-visualizations="availableVisualizations"
+ :is-saving="isSaving"
+ @save="handleSave"
+ @add-panel="handleAddPanel"
+ />
+</template>
```
diff --git a/doc/development/internal_api/index.md b/doc/development/internal_api/index.md
index fc7cc3e21d3..98becd60742 100644
--- a/doc/development/internal_api/index.md
+++ b/doc/development/internal_api/index.md
@@ -14,7 +14,7 @@ working on the GitLab codebase.
This documentation does not yet include the internal API used by
GitLab Pages.
-## Adding new endpoints
+## Add new endpoints
API endpoints should be externally accessible by default, with proper authentication and authorization.
Before adding a new internal endpoint, consider if the API would potentially be
@@ -663,7 +663,7 @@ Example response:
The subscriptions endpoint is used by [CustomersDot](https://gitlab.com/gitlab-org/customers-gitlab-com) (`customers.gitlab.com`) to apply subscriptions including trials, and add-on purchases, for personal namespaces or top-level groups within GitLab.com.
-### Creating a subscription
+### Create a subscription
Use a POST command to create a subscription.
@@ -714,7 +714,7 @@ Example response:
}
```
-### Updating a subscription
+### Update a subscription
Use a PUT command to update an existing subscription.
@@ -765,7 +765,7 @@ Example response:
}
```
-### Retrieving a subscription
+### Retrieve a subscription
Use a GET command to view an existing subscription.
@@ -815,7 +815,7 @@ The subscription add-on purchase endpoint is used by [CustomersDot](https://gitl
### Create a subscription add-on purchase
-Use a POST to create a subscription add-on purchase.
+Use a POST command to create a subscription add-on purchase.
```plaintext
POST /namespaces/:id/subscription_add_on_purchase/:add_on_name
@@ -928,7 +928,7 @@ Example request:
```shell
curl --request GET \
--url "https://gitlab.com/v4/namespaces/storage/limit_exclusions" \
- --header 'PRIVATE-TOKEN: <admin access token>'
+ --header 'PRIVATE-TOKEN: <admin access token>'
```
Example response:
@@ -1018,9 +1018,9 @@ Example response:
The compute quota endpoints are used by [CustomersDot](https://gitlab.com/gitlab-org/customers-gitlab-com) (`customers.gitlab.com`)
to apply additional packs of units of compute, for personal namespaces or top-level groups in GitLab.com.
-### Creating an additional pack
+### Create an additional pack
-Use a POST to create additional packs.
+Use a POST command to create additional packs.
```plaintext
POST /namespaces/:id/minutes
@@ -1064,9 +1064,9 @@ Example response:
]
```
-### Moving additional packs
+### Move additional packs
-Use a `PATCH` to move additional packs from one namespace to another.
+Use a `PATCH` command to move additional packs from one namespace to another.
```plaintext
PATCH /namespaces/:id/minutes/move/:target_id
@@ -1102,7 +1102,7 @@ Example response:
The `upcoming_reconciliations` endpoint is used by [CustomersDot](https://gitlab.com/gitlab-org/customers-gitlab-com) (`customers.gitlab.com`)
to update upcoming reconciliations for namespaces.
-### Updating `upcoming_reconciliations`
+### Update `upcoming_reconciliations`
Use a PUT command to update `upcoming_reconciliations`.
@@ -1136,7 +1136,7 @@ Example response:
200
```
-### Deleting an `upcoming_reconciliation`
+### Delete an `upcoming_reconciliation`
Use a DELETE command to delete an `upcoming_reconciliation`.
diff --git a/doc/development/testing_guide/best_practices.md b/doc/development/testing_guide/best_practices.md
index 31cec20ffdd..90267a517b8 100644
--- a/doc/development/testing_guide/best_practices.md
+++ b/doc/development/testing_guide/best_practices.md
@@ -388,9 +388,10 @@ With [issue](https://gitlab.com/gitlab-org/gitlab/-/issues/375983) we defined th
For tests that are not meeting the thresholds it is recommended to create issues and improve the tests duration.
-| Date | Feature tests | Controllers and Requests tests | Other |
-| --- | --- | --- | --- |
-| 2023-02-15 | 67.42 seconds | 44.66 seconds | 76.86 seconds |
+| Date | Feature tests | Controllers and Requests tests | Unit | Other | Method |
+| :-: | :-: | :-: | :-: | :-: | :-: |
+| 2023-02-15 | 67.42 seconds | 44.66 seconds | - | 76.86 seconds | Top slow test eliminating the maximum |
+| 2023-06-15 | 50.13 seconds | 19.20 seconds | 27.12 | 45.40 seconds | Avg for top 100 slow tests|
#### Avoid repeating expensive actions
diff --git a/doc/user/admin_area/reporting/ip_addr_restrictions.md b/doc/user/admin_area/reporting/ip_addr_restrictions.md
new file mode 100644
index 00000000000..5b749c62c30
--- /dev/null
+++ b/doc/user/admin_area/reporting/ip_addr_restrictions.md
@@ -0,0 +1,33 @@
+---
+stage: Anti-Abuse
+group: Anti-Abuse
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
+---
+
+# IP address restrictions **(FREE SELF)**
+
+IP address restrictions help prevent malicious users hiding their activities behind multiple IP addresses.
+
+GitLab maintains a list of the unique IP addresses used by a user to make requests over a specified period. When the
+specified limit is reached, any requests made by the user from a new IP address are rejected with a `403 Forbidden` error.
+
+IP addresses are cleared from the list when no further requests have been made by the user from the IP address in the specified time period.
+
+NOTE:
+When a runner runs a CI/CD job as a particular user, the runner IP address is also stored against the user's list of
+unique IP addresses. Therefore, the IP addresses per user limit should take into account the number of configured active runners.
+
+## Configure IP address restrictions
+
+1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
+1. Select **Admin Area**.
+1. Select **Settings > Reporting**.
+1. Expand **Spam and Anti-bot Protection**.
+1. Update the IP address restrictions settings:
+ 1. Select the **Limit sign in from multiple IP addresses** checkbox to enable IP address restrictions.
+ 1. Enter a number in the **IP addresses per user** field, greater than or equal to `1`. This number specifies the
+ maximum number of unique IP addresses a user can access GitLab from in the specified time period before requests
+ from a new IP address are rejected.
+ 1. Enter a number in the **IP address expiration time** field, greater than or equal to `0`. This number specifies the
+ time in seconds an IP address counts towards the limit for a user, taken from the time the last request was made.
+1. Select **Save changes**.
diff --git a/doc/user/admin_area/settings/index.md b/doc/user/admin_area/settings/index.md
index 923181bfc5d..632adf273c4 100644
--- a/doc/user/admin_area/settings/index.md
+++ b/doc/user/admin_area/settings/index.md
@@ -162,8 +162,10 @@ The **Preferences** settings contain:
The **Reporting** settings contain:
-- [Spam and Anti-bot Protection](../../../integration/recaptcha.md) -
- Enable anti-spam services, like reCAPTCHA, Akismet, or [Spamcheck](../reporting/spamcheck.md), and set IP limits.
+- Spam and Anti-bot protection:
+ - Anti-spam services, such as [reCAPTCHA](../../../integration/recaptcha.md),
+ [Akismet](../../../integration/akismet.md), or [Spamcheck](../reporting/spamcheck.md).
+ - [IP address restrictions](../reporting/ip_addr_restrictions.md).
- [Abuse reports](../review_abuse_reports.md) - Set notification email for abuse reports.
- [Git abuse rate limit](../reporting/git_abuse_rate_limit.md) - Configure Git abuse rate limit settings. **(ULTIMATE SELF)**
diff --git a/doc/user/analytics/analytics_dashboards.md b/doc/user/analytics/analytics_dashboards.md
index f8f9059f99c..71a51dbf824 100644
--- a/doc/user/analytics/analytics_dashboards.md
+++ b/doc/user/analytics/analytics_dashboards.md
@@ -36,8 +36,8 @@ The following data sources are configured for analytics dashboards:
To view a list of dashboards for a project:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Analytics > Dashboards**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Analyze > Dashboards**.
1. From the list of available dashboards, select the dashboard you want to view.
### Change the location of group dashboards
@@ -47,10 +47,10 @@ This feature will be connected to group-level dashboards as part of [issue #4115
To change the location of a group's dashboards:
-1. On the top bar, select **Main menu > Projects** and find the project you want to store your dashboard files in.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find the project you want to store your dashboard files in.
The project must belong to the group for which you create the dashboards.
-1. On the top bar, select **Main menu > Groups** and find your group.
-1. On the left sidebar, select **Settings > General**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your group.
+1. Select **Settings > General**.
1. Expand **Analytics**.
1. In the **Analytics Dashboards** section, select your dashboard files project.
1. Select **Save changes**.
@@ -66,9 +66,11 @@ You can share dashboards only between projects that are located in the same grou
To change the location of project dashboards:
-1. On the top bar, select **Main menu > Projects** and find or create the project to store your dashboard files.
-1. On the top bar, select **Main menu > Projects** and find the analytics project.
-1. On the left sidebar, select **Settings > General**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project,
+ or select **Create new...** (**{plus}**) and **New project/repository**
+ to create the project to store your dashboard files.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) and find the analytics project.
+1. Select **Settings > General**.
1. Expand **Analytics**.
1. In the **Analytics Dashboards** section, select your dashboard files project.
1. Select **Save changes**.
@@ -150,8 +152,8 @@ To edit these dashboards you should create a new custom dashboard which uses the
To create a custom dashboard:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Analytics > Dashboards**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Analyze > Dashboards**.
1. Select **New dashboard**.
1. In the **New dashboard** input, enter the name of the dashboard.
1. From the **Add visualizations** list on the right, select the visualizations to add to the dashboard.
@@ -164,8 +166,8 @@ You can edit your custom dashboard's title and add or resize visualizations with
To edit an existing custom dashboard:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Analytics > Dashboards**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Analyze > Dashboards**.
1. From the list of available dashboards, select a custom dashboard (one without the `By GitLab` label) you want to edit.
1. Select **Edit**.
1. Optional. Change the title of the dashboard.
diff --git a/doc/user/analytics/ci_cd_analytics.md b/doc/user/analytics/ci_cd_analytics.md
index bde7fdc6c2d..daee21d2567 100644
--- a/doc/user/analytics/ci_cd_analytics.md
+++ b/doc/user/analytics/ci_cd_analytics.md
@@ -32,8 +32,8 @@ View pipeline duration history:
To view CI/CD analytics:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Analytics > CI/CD Analytics**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Analyze > CI/CD analytics**.
## View DORA deployment frequency chart **(ULTIMATE)**
@@ -50,8 +50,8 @@ The deployment frequency chart is available for groups and projects.
To view the deployment frequency chart:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Analytics > CI/CD Analytics**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Analyze > CI/CD analytics**.
1. Select the **Deployment frequency** tab.
![Deployment frequency](img/deployment_frequency_charts_v13_12.png)
@@ -72,8 +72,8 @@ merge requests to be deployed to a production environment. This chart is availab
To view the lead time for changes chart:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Analytics > CI/CD Analytics**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Analyze > CI/CD analytics**.
1. Select the **Lead time** tab.
![Lead time](img/lead_time_chart_v13_11.png)
@@ -88,8 +88,8 @@ Time to restore service is one of the four DORA metrics that DevOps teams use fo
To view the time to restore service chart:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Analytics > CI/CD Analytics**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Analyze > CI/CD analytics**.
1. Select the **Time to restore service** tab.
![Lead time](img/time_to_restore_service_charts_v15_1.png)
@@ -104,6 +104,6 @@ Change failure rate is one of the four DORA metrics that DevOps teams use for me
To view the change failure rate chart:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Analytics > CI/CD Analytics**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Analyze > CI/CD analytics**.
1. Select the **Change failure rate** tab.
diff --git a/doc/user/analytics/code_review_analytics.md b/doc/user/analytics/code_review_analytics.md
index 2e76252f7d3..646a85fc7a2 100644
--- a/doc/user/analytics/code_review_analytics.md
+++ b/doc/user/analytics/code_review_analytics.md
@@ -34,8 +34,8 @@ Prerequisite:
To view code review analytics:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Analytics > Code Review**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Analyze > Code review analytics**.
1. Optional. Filter results:
1. Select the filter bar.
1. Select a parameter. You can filter merge requests by milestone and label.
diff --git a/doc/user/analytics/issue_analytics.md b/doc/user/analytics/issue_analytics.md
index 71ce13a725e..aaf33f48df2 100644
--- a/doc/user/analytics/issue_analytics.md
+++ b/doc/user/analytics/issue_analytics.md
@@ -15,8 +15,8 @@ prior.
To access the chart:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Analytics > Issue**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Analyze > Issue analytics**.
Hover over each bar to see the total number of issues.
diff --git a/doc/user/analytics/merge_request_analytics.md b/doc/user/analytics/merge_request_analytics.md
index 69ba5a67197..eeaa8271749 100644
--- a/doc/user/analytics/merge_request_analytics.md
+++ b/doc/user/analytics/merge_request_analytics.md
@@ -29,8 +29,8 @@ Prerequisite:
To view merge request analytics:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Analytics > Merge request**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Analyze > Merge request analytics**.
## View the number of merge requests in a date range
@@ -39,8 +39,8 @@ To view merge request analytics:
To view the number of merge requests merged during a specific date range:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Analytics > Merge request**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Analyze > Merge request analytics**.
1. Optional. Filter results:
1. Select the filter bar.
1. Select a parameter.
@@ -73,6 +73,6 @@ created and when it's merged. Closed and not yet merged merge requests are not i
To view **Mean time to merge**:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Analytics > Merge request**. The **Mean time to merge** number
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Analyze > Merge request analytics**. The **Mean time to merge** number
is displayed on the dashboard.
diff --git a/doc/user/analytics/productivity_analytics.md b/doc/user/analytics/productivity_analytics.md
index 08bb579fd0a..f53b516dd0d 100644
--- a/doc/user/analytics/productivity_analytics.md
+++ b/doc/user/analytics/productivity_analytics.md
@@ -26,8 +26,8 @@ Prerequisite:
- You must have at least the Reporter role for the group.
-1. On the top bar, select **Main menu > Groups** and find your group.
-1. On the left sidebar, select **Analytics > Productivity**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your group.
+1. Select **Analyze > Productivity analytics**.
1. Optional. Filter results:
1. Select a project from the dropdown list.
1. To filter results by author, milestone, or label,
@@ -44,8 +44,8 @@ Use the following charts in productivity analytics to view the velocity of your
merge requests to merge after they were created.
- **Trendline**: number of merge requests that were merged in a specific time period.
-1. On the top bar, select **Main menu > Groups** and find your group.
-1. On the left sidebar, select **Analytics > Productivity**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your group.
+1. Select **Analyze > Productivity analytics**.
To filter time metrics:
@@ -56,8 +56,8 @@ To filter time metrics:
To view commit statistics for your group:
-1. On the top bar, select **Main menu > Groups** and find your group.
-1. On the left sidebar, select **Analytics > Productivity**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your group.
+1. Select **Analyze > Productivity analytics**.
1. Under the **Trendline** scatterplot, view the commit statistics:
- The left histogram shows the number of hours between commits, comments, and merges.
- The right histogram shows the number of commits and changes per merge request.
diff --git a/doc/user/analytics/repository_analytics.md b/doc/user/analytics/repository_analytics.md
index b434221b887..e686d76eda2 100644
--- a/doc/user/analytics/repository_analytics.md
+++ b/doc/user/analytics/repository_analytics.md
@@ -30,8 +30,8 @@ Commits in a project's [wiki](../project/wiki/index.md#track-wiki-events) are no
To review repository analytics for a project:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Analytics > Repository**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Analyze > Repository analytics**.
## How repository analytics chart data is updated
diff --git a/doc/user/analytics/value_streams_dashboard.md b/doc/user/analytics/value_streams_dashboard.md
index e99925e544f..9b332d78060 100644
--- a/doc/user/analytics/value_streams_dashboard.md
+++ b/doc/user/analytics/value_streams_dashboard.md
@@ -55,10 +55,8 @@ Prerequisite:
To view the value streams dashboard:
-1. On the top bar, select **Main menu**, and:
- - For a project, select **Projects** and find your project.
- - For a group, select **Groups** and find your group.
-1. On the left sidebar, select **Analytics > Value stream**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project or group.
+1. Select **Analyze > Value stream analytics**.
1. Below the **Filter results** text box, in the **Key metrics** row, select **Value Streams Dashboard / DORA**.
1. Optional. To open the new page, append this path `/analytics/dashboards/value_streams_dashboard` to the group URL
(for example, `https://gitlab.com/groups/gitlab-org/-/analytics/dashboards/value_streams_dashboard`).
@@ -89,15 +87,15 @@ Prerequisite:
- You must have at least the Maintainer role for the group.
-1. On the top bar, select **Main menu > Groups** and find your group.
-1. On the left sidebar, select **Settings > General**.
-1. Scroll to **Analytics Dashboards** and select **Expand**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your group.
+1. Select **Settings > General**.
+1. Expand **Analytics**.
1. Select the project where you would like to store your YAML configuration file.
1. Select **Save changes**.
After you have set up the project, set up the configuration file:
-1. On the top bar, select **Main menu > Projects** and find your project.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
1. In the default branch, create the configuration file: `.gitlab/analytics/dashboards/value_streams/value_streams.yaml`.
1. In the `value_streams.yaml` configuration file, fill in the configuration options:
diff --git a/doc/user/group/manage.md b/doc/user/group/manage.md
index b198fd82b80..284fb8b3d7c 100644
--- a/doc/user/group/manage.md
+++ b/doc/user/group/manage.md
@@ -131,6 +131,22 @@ After sharing the `Frontend` group with the `Engineering` group:
- The **Groups** tab lists the `Engineering` group.
- The **Groups** tab lists a group regardless of whether it is a public or private group.
- All direct members of the `Engineering` group have access to the `Frontend` group. Direct members of `Engineering` that gain access to the `Frontend` group keep their same access level as in `Engineering`, but up to the maximum access level selected when sharing the group. Inherited members of the `Engineering` group do not gain access to the `Frontend` group.
+- All direct members of the `Engineering` group count towards the billable members of the `Frontend` group.
+
+## Remove a shared group
+
+To unshare a group:
+
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your group.
+1. Select **Manage > Members**.
+1. Select the **Groups** tab.
+1. To the right of the account you want to remove, select **Remove group** (**{remove}**).
+
+For example, if the `Engineering` group is shared with the `Frontend` group, when
+you unshare the `Engineering` group:
+
+- All direct members of the `Engineering` group no longer have access to the `Frontend` group.
+- Members of the `Engineering` group no longer count towards the billable members of the `Frontend` group.
## Transfer a group
diff --git a/doc/user/profile/account/create_accounts.md b/doc/user/profile/account/create_accounts.md
index f405dc42f46..f2f1921f0bb 100644
--- a/doc/user/profile/account/create_accounts.md
+++ b/doc/user/profile/account/create_accounts.md
@@ -33,8 +33,9 @@ Prerequisite:
To create a user manually:
-1. On the top bar, select **Main menu > Admin**.
-1. On the left sidebar, select **Overview > Users** (`/admin/users`).
+1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
+1. Select **Admin Area**.
+1. Select **Overview > Users**.
1. Select **New user**.
1. Complete the required fields, such as name, username, and email.
1. Select **Create user**.
diff --git a/doc/user/profile/account/delete_account.md b/doc/user/profile/account/delete_account.md
index 1ffe01d6e00..c367498f66e 100644
--- a/doc/user/profile/account/delete_account.md
+++ b/doc/user/profile/account/delete_account.md
@@ -24,7 +24,7 @@ On self-managed GitLab, by default this feature is not available. To make it ava
As a user, to delete your own account:
-1. On the top bar, in the upper-right corner, select your avatar.
+1. On the left sidebar, select your avatar.
1. Select **Edit profile**.
1. On the left sidebar, select **Account**.
1. Select **Delete account**.
@@ -38,8 +38,9 @@ Unblocking the account does not undo the deletion because the account will still
As an administrator, to delete a user account:
-1. On the top bar, select **Main menu > Admin**.
-1. On the left sidebar, select **Overview > Users**.
+1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
+1. Select **Admin Area**.
+1. Select **Overview > Users**.
1. Select a user.
1. Under the **Account** tab, select:
- **Delete user** to delete only the user but maintain their [associated records](#associated-records). You can't use this option if
diff --git a/doc/user/profile/index.md b/doc/user/profile/index.md
index 69a5ba8df3d..958acaa61a9 100644
--- a/doc/user/profile/index.md
+++ b/doc/user/profile/index.md
@@ -237,7 +237,7 @@ You can set your local time zone to:
To set your time zone:
-1. On the top bar, in the upper-right corner, select your avatar.
+1. On the left sidebar, select your avatar.
1. Select **Edit profile**.
1. In the **Time settings** section, select your time zone from the dropdown list.
@@ -283,7 +283,7 @@ so you can keep your email information private.
To use a private commit email:
-1. On the top bar, in the upper-right corner, select your avatar.
+1. On the left sidebar, select your avatar.
1. Select **Edit profile**.
1. In the **Commit email** dropdown list, select **Use a private email**.
1. Select **Update profile settings**.
@@ -314,7 +314,7 @@ the maximum number of users you can follow is 300.
You can disable following and being followed by other users.
-1. On the top bar, in the upper-right corner, select your avatar.
+1. On the left sidebar, select your avatar.
1. Select **Edit profile**.
1. Select **Preferences**.
1. Clear the **Enable follow users** checkbox.
@@ -331,7 +331,7 @@ When this feature is being disabled, all current followed/following connections
You can disable searching with Zoekt and use Elasticsearch instead.
-1. On the top bar, in the upper-right corner, select your avatar.
+1. On the left sidebar, select your avatar.
1. Select **Edit profile**.
1. Select **Preferences**.
1. Clear the **Enable advanced code search** checkbox.
diff --git a/doc/user/profile/notifications.md b/doc/user/profile/notifications.md
index c94954e4dd2..cc0b67eed52 100644
--- a/doc/user/profile/notifications.md
+++ b/doc/user/profile/notifications.md
@@ -107,7 +107,7 @@ To select a notification level for a group, use either of these methods:
Or:
-1. On the top bar, select **Main menu > Groups** and find your group.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your group.
1. Select the notification dropdown list, next to the bell icon (**{notifications}**).
1. Select the desired [notification level](#notification-levels).
@@ -138,7 +138,7 @@ To select a notification level for a project, use either of these methods:
Or:
-1. On the top bar, select **Main menu > Projects** and find your project.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
1. Select the notification dropdown list, next to the bell icon (**{notifications}**).
1. Select the desired [notification level](#notification-levels).
diff --git a/doc/user/profile/user_passwords.md b/doc/user/profile/user_passwords.md
index f8cbdd54e77..50624a43893 100644
--- a/doc/user/profile/user_passwords.md
+++ b/doc/user/profile/user_passwords.md
@@ -28,7 +28,7 @@ authorization provider, you do not need to choose a password. GitLab
You can change your password. GitLab enforces [password requirements](#password-requirements) when you choose your new
password.
-1. On the top bar, in the upper-right corner, select your avatar.
+1. On the left sidebar, select your avatar.
1. Select **Edit profile**.
1. On the left sidebar, select **Password**.
1. In the **Current password** text box, enter your current password.
diff --git a/doc/user/project/integrations/webhooks.md b/doc/user/project/integrations/webhooks.md
index 513f2ae442c..5c8fc5703dd 100644
--- a/doc/user/project/integrations/webhooks.md
+++ b/doc/user/project/integrations/webhooks.md
@@ -136,10 +136,25 @@ intermittently and are temporarily disabled. These webhooks are initially disabl
for one minute, which is extended on each subsequent failure up to a maximum of 24 hours.
Project or group webhooks that return response codes in the `4xx` range are understood to be
-misconfigured and are permanently disabled until you manually re-enable
-them yourself.
+misconfigured and are permanently disabled until you [manually re-enable](#re-enable-disabled-webhooks)
+the webhooks yourself.
-For more information about disabled webhooks, see [troubleshooting](#troubleshooting).
+### Re-enable disabled webhooks
+
+> - Introduced in GitLab 15.2 [with a flag](../../../administration/feature_flags.md) named `webhooks_failed_callout`. Disabled by default.
+> - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/365535) in GitLab 15.7. Feature flag `webhooks_failed_callout` removed.
+
+If a webhook is failing, a banner displays at the top of the edit page explaining
+why the webhook is disabled and when it is automatically re-enabled. For example:
+
+![A banner for a failing webhook, warning it has failed to connect and is retrying in 60 minutes](img/failed_banner.png)
+
+In the case of a failed webhook, an error banner is displayed:
+
+![A banner for a failed webhook, showing an error state, and explaining how to re-enable it](img/failed_banner_error.png)
+
+To re-enable a failing or failed webhook, [send a test request](#test-a-webhook). If the test
+request succeeds, the webhook is re-enabled.
## Test a webhook
@@ -361,19 +376,4 @@ If you're receiving multiple webhook requests, the webhook might have timed out.
GitLab expects a response in [10 seconds](../../../user/gitlab_com/index.md#other-limits). On self-managed GitLab instances, you can [change the webhook timeout limit](../../../administration/instance_limits.md#webhook-timeout).
-### Re-enable disabled webhooks
-
-> - Introduced in GitLab 15.2 [with a flag](../../../administration/feature_flags.md) named `webhooks_failed_callout`. Disabled by default.
-> - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/365535) in GitLab 15.7. Feature flag `webhooks_failed_callout` removed.
-
-If a webhook is failing, a banner displays at the top of the edit page explaining
-why it is disabled, and when it is automatically re-enabled. For example:
-
-![A banner for a failing webhook, warning it has failed to connect and is retrying in 60 minutes](img/failed_banner.png)
-
-In the case of a failed webhook, an error banner is displayed:
-
-![A banner for a failed webhook, showing an error state, and explaining how to re-enable it](img/failed_banner_error.png)
-
-To re-enable a failing or failed webhook, [send a test request](#test-a-webhook). If the test
-request succeeds, the webhook is re-enabled.
+If a webhook is not triggered, the webhook might be [automatically disabled](#failing-webhooks).
diff --git a/doc/user/project/issues/confidential_issues.md b/doc/user/project/issues/confidential_issues.md
index 2f698b5424c..7e5f26d3526 100644
--- a/doc/user/project/issues/confidential_issues.md
+++ b/doc/user/project/issues/confidential_issues.md
@@ -25,9 +25,9 @@ When you create a confidential issue in a project, the project becomes listed in
To create a confidential issue:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the top bar, select the plus sign (**{plus-square}**) and then, under **This project**,
- select **New issue**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. On the left sidebar, at the top, select **Create new...** (**{plus}**).
+1. From the dropdown list, select **New issue**.
1. Complete the [fields](create_issues.md#fields-in-the-new-issue-form).
- Select the **This issue is confidential...** checkbox.
1. Select **Create issue**.
@@ -36,8 +36,9 @@ To create a confidential issue:
To change the confidentiality of an existing issue:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. Select **Issues**, then select the title of your issue to view it.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Plan > Issues**.
+1. Select the title of your issue to view it.
1. On the right sidebar, next to **Confidentiality**, select **Edit**.
1. Select **Turn on** (or **Turn off** to make the issue non-confidential).
diff --git a/doc/user/project/merge_requests/changes.md b/doc/user/project/merge_requests/changes.md
index 3c60d78f84f..94f506ba556 100644
--- a/doc/user/project/merge_requests/changes.md
+++ b/doc/user/project/merge_requests/changes.md
@@ -157,8 +157,8 @@ rebases and file changes.
To add a comment to a merge request file:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Settings > Merge requests** and find your merge request.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Code > Merge requests** and find your merge request.
1. Select **Changes**.
1. In the header for the file you want to comment on, select **Comment** (**{comment}**).
diff --git a/doc/user/project/merge_requests/cherry_pick_changes.md b/doc/user/project/merge_requests/cherry_pick_changes.md
index 004d24778b7..6669b2883a4 100644
--- a/doc/user/project/merge_requests/cherry_pick_changes.md
+++ b/doc/user/project/merge_requests/cherry_pick_changes.md
@@ -52,8 +52,8 @@ Commit `G` is added after the cherry-pick.
After a merge request is merged, you can cherry-pick all changes introduced
by the merge request:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Merge requests**, and find your merge request.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Code > Merge requests**, and find your merge request.
1. Scroll to the merge request reports section, and find the **Merged by** report.
1. In the upper-right corner, select **Cherry-pick**:
@@ -70,8 +70,8 @@ You can cherry-pick a single commit from multiple locations in your GitLab proje
To cherry-pick a commit from the list of all commits for a project:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Repository > Commits**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Code > Commits**.
1. Select the [title](https://git-scm.com/docs/git-commit#_discussion) of the commit you want to cherry-pick.
1. In the upper-right corner, select **Options > Cherry-pick** to show the cherry-pick modal.
1. In the modal window, select the project and branch to cherry-pick into.
@@ -84,8 +84,8 @@ You can cherry-pick commits from any merge request in your project, regardless o
whether the merge request is open or closed. To cherry-pick a commit from the
list of commits included in a merge request:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Merge requests**, and find your merge request.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Code > Merge requests**, and find your merge request.
1. In the merge request's secondary menu, select **Commits** to display the commit details page.
1. Select the [title](https://git-scm.com/docs/git-commit#_discussion) of the commit you want to cherry-pick.
1. In the upper-right corner, select **Options > Cherry-pick** to show the cherry-pick modal.
@@ -98,8 +98,8 @@ list of commits included in a merge request:
You can cherry-pick from the list of previous commits affecting an individual file
when you view that file in your project's Git repository:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Repository > Files** and go to the file
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Code > Repository** and go to the file
changed by the commit.
1. Select **History**, then select the [title](https://git-scm.com/docs/git-commit#_discussion)
of the commit you want to cherry-pick.
diff --git a/doc/user/project/merge_requests/commit_templates.md b/doc/user/project/merge_requests/commit_templates.md
index 3ebb3aa5dd3..c930c5c6f7f 100644
--- a/doc/user/project/merge_requests/commit_templates.md
+++ b/doc/user/project/merge_requests/commit_templates.md
@@ -29,8 +29,8 @@ Prerequisite:
To do this:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Settings > Merge requests**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Settings > Merge requests**.
1. Depending on the type of template you want to create, scroll to either
[**Merge commit message template**](#default-template-for-merge-commits) or
[**Squash commit message template**](#default-template-for-squash-commits).
diff --git a/doc/user/project/merge_requests/commits.md b/doc/user/project/merge_requests/commits.md
index 5814ed53a66..a36e45d159a 100644
--- a/doc/user/project/merge_requests/commits.md
+++ b/doc/user/project/merge_requests/commits.md
@@ -18,8 +18,8 @@ From this tab, you can review commit messages and copy a commit's SHA when you n
To see the commits included in a merge request:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Merge requests**, then select your merge request.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Code > Merge requests**, then select your merge request.
1. To show a list of the commits in the merge request, newest first, select **Commits** .
To read more about the commit, select **Toggle commit description** (**{ellipsis_h}**)
on any commit.
@@ -48,8 +48,8 @@ if another merge request:
To add previously merged commits to a merge request for more context:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Merge requests**, then select your merge request.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Code > Merge requests**, then select your merge request.
1. Select **Commits**.
1. Scroll to the end of the list of commits, and select **Add previously merged commits**.
1. Select the commits that you want to add.
@@ -63,8 +63,8 @@ force push.
To add discussion to a specific commit:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Repository > Commits**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Code > Commits**.
1. Below the commits, in the **Comment** field, enter a comment.
1. Save your comment as either a standalone comment, or a thread:
- To add a comment, select **Comment**.
@@ -74,8 +74,8 @@ To add discussion to a specific commit:
To view the changes between previously merged commits:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Merge requests**, then select your merge request.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Code > Merge requests**, then select your merge request.
1. Select **Changes**.
1. By **Compare** (**{file-tree}**), select the commits to compare:
diff --git a/doc/user/project/merge_requests/conflicts.md b/doc/user/project/merge_requests/conflicts.md
index cc8f8cb2fe6..44cef4d63e5 100644
--- a/doc/user/project/merge_requests/conflicts.md
+++ b/doc/user/project/merge_requests/conflicts.md
@@ -59,8 +59,8 @@ in the user interface, and you can also resolve conflicts locally through the co
To resolve less-complex conflicts from the GitLab user interface:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Merge requests** and find the merge request.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Code > Merge requests** and find the merge request.
1. Select **Overview**, and scroll to the merge request reports section.
1. Find the merge conflicts message, and select **Resolve conflicts**.
GitLab shows a list of files with merge conflicts. The conflicts are
@@ -84,8 +84,8 @@ Some merge conflicts are more complex, requiring you to manually modify lines to
resolve their conflicts. Use the merge conflict resolution editor to resolve complex
conflicts in the GitLab interface:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Merge requests** and find the merge request.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Code > Merge requests** and find the merge request.
1. Select **Overview**, and scroll to the merge request reports section.
1. Find the merge conflicts message, and select **Resolve conflicts**.
GitLab shows a list of files with merge conflicts.
diff --git a/doc/user/project/merge_requests/creating_merge_requests.md b/doc/user/project/merge_requests/creating_merge_requests.md
index a91d324016a..4eb4476422f 100644
--- a/doc/user/project/merge_requests/creating_merge_requests.md
+++ b/doc/user/project/merge_requests/creating_merge_requests.md
@@ -19,8 +19,8 @@ to streamline merge request creation.
You can create a merge request from the list of merge requests.
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left menu, select **Merge requests**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Code > Merge requests**.
1. In the upper-right corner, select **New merge request**.
1. Select a source and target branch and then **Compare branches and continue**.
1. Fill out the fields and select **Create merge request**.
@@ -95,8 +95,8 @@ You can create a merge request when you add, edit, or upload a file to a reposit
You can create a merge request when you create a branch.
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left menu, select **Repository > Branches**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Code > Branches**.
1. Type a branch name and select **New branch**.
1. Above the file list, on the right side, select **Create merge request**.
A merge request is created. The default branch is the target.
@@ -142,9 +142,9 @@ to reduce the need for editing merge requests manually through the UI.
You can create a merge request from your fork to contribute back to the main project.
-1. On the top bar, select **Main menu > Projects** and find your project.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
1. Select your fork of the repository.
-1. On the left menu, go to **Merge requests**, and select **New merge request**.
+1. On the left sidebar, select **Code > Merge requests**, and select **New merge request**.
1. In the **Source branch** dropdown list box, select the branch in your forked repository as the source branch.
1. In the **Target branch** dropdown list box, select the branch from the upstream repository as the target branch.
You can set a [default target project](#set-the-default-target-project) to
@@ -171,8 +171,8 @@ Prerequisites:
To create a merge request by sending an email:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left menu, select **Merge requests**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Code > Merge requests**.
1. In the upper-right corner, select **Email a new merge request to this project**.
An email address is displayed. Copy this address.
Ensure you keep this address private.
@@ -216,8 +216,8 @@ scenarios when you create a new merge request:
To have merge requests from a fork by default target your own fork
(instead of the upstream project), you can change the default.
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left menu, select **Settings > General > Merge requests**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Settings > Merge requests**.
1. In the **Target project** section, select the option you want to use for
your default target project.
1. Select **Save changes**.
diff --git a/doc/user/project/merge_requests/csv_export.md b/doc/user/project/merge_requests/csv_export.md
index f40b82a6280..f8988ae7bd7 100644
--- a/doc/user/project/merge_requests/csv_export.md
+++ b/doc/user/project/merge_requests/csv_export.md
@@ -12,8 +12,8 @@ Export all the data collected from a project's merge requests into a comma-separ
To export merge requests to a CSV file:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Merge requests** .
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Code > Merge requests**.
1. Add any searches or filters. This can help you keep the size of the CSV file under the 15 MB limit. The limit ensures
the file can be emailed to a variety of email providers.
1. Select **Actions** (**{ellipsis_v}**) **> Export as CSV**.
diff --git a/doc/user/project/merge_requests/dependencies.md b/doc/user/project/merge_requests/dependencies.md
index 3d92fc9a91e..1cd81e2aac2 100644
--- a/doc/user/project/merge_requests/dependencies.md
+++ b/doc/user/project/merge_requests/dependencies.md
@@ -57,8 +57,8 @@ information about the dependency:
To view dependency information on a merge request:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Merge requests** and identify your merge request.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Code > Merge requests** and identify your merge request.
1. Scroll to the merge request reports area. Dependent merge requests display information
about the total number of dependencies set, such as
**(status-warning)** **Depends on 1 merge request being merged**.
@@ -105,8 +105,8 @@ Prerequisite:
To do this:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Merge requests** and identify your merge request.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Code > Merge requests** and identify your merge request.
1. Select **Edit**.
1. In **Merge request dependencies**, paste either the reference or the full URL
to the merge requests that should merge before this work merges. References
@@ -120,8 +120,8 @@ Prerequisite:
- You must have a role in the project that allows you to edit merge requests.
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Merge requests** and identify your merge request.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Code > Merge requests** and identify your merge request.
1. Select **Edit**.
1. Scroll to **Merge request dependencies** and select **Remove** next to the reference
for each dependency you want to remove.
diff --git a/doc/user/project/merge_requests/index.md b/doc/user/project/merge_requests/index.md
index abd54d1ebb8..38125f623eb 100644
--- a/doc/user/project/merge_requests/index.md
+++ b/doc/user/project/merge_requests/index.md
@@ -50,8 +50,8 @@ You can view merge requests for your project, group, or yourself.
To view all merge requests for a project:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Merge requests**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Code > Merge requests**.
Or, to use a [keyboard shortcut](../../shortcuts.md), press <kbd>g</kbd> + <kbd>m</kbd>.
@@ -59,8 +59,8 @@ Or, to use a [keyboard shortcut](../../shortcuts.md), press <kbd>g</kbd> + <kbd>
To view merge requests for all projects in a group:
-1. On the top bar, select **Main menu > Groups** and find your group.
-1. On the left sidebar, select **Merge requests**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your group.
+1. Select **Code > Merge requests**.
If your group contains subgroups, this view also displays merge requests from the subgroup projects.
@@ -68,21 +68,17 @@ If your group contains subgroups, this view also displays merge requests from th
To view all merge requests assigned to you:
-<!-- vale gitlab.FirstPerson = NO -->
-
-1. On the top bar, put your cursor in the **Search** box.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**).
1. From the dropdown list, select **Merge requests assigned to me**.
-<!-- vale gitlab.FirstPerson = YES -->
-
or:
- To use a [keyboard shortcut](../../shortcuts.md), press <kbd>Shift</kbd> + <kbd>m</kbd>.
or:
-1. On the top bar, in the upper-right corner, select **Merge requests** (**{merge-request-open}**).
-1. From the dropdown list, select **Assigned to you**.
+1. On the left sidebar, at the top, select **Merge requests** (**{merge-request}**).
+1. From the dropdown list, select **Assigned**.
## Filter the list of merge requests
@@ -93,8 +89,8 @@ or:
To filter the list of merge requests:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Merge requests**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Code > Merge requests**.
1. Above the list of merge requests, select **Search or filter results...**.
1. From the dropdown list, select the attribute you wish to filter by. Some examples:
- [**By environment or deployment date**](#by-environment-or-deployment-date).
@@ -167,8 +163,8 @@ To assign the merge request to a user, use the `/assign @user`
[quick action](../quick_actions.md#issues-merge-requests-and-epics) in a text area in
a merge request, or:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Merge requests** and find your merge request.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Code > Merge requests** and find your merge request.
1. On the right sidebar, expand the right sidebar and locate the **Assignees** section.
1. Select **Edit**.
1. Search for the user you want to assign, and select the user.
@@ -187,8 +183,8 @@ accountable for it:
To assign multiple assignees to a merge request, use the `/assign @user`
[quick action](../quick_actions.md#issues-merge-requests-and-epics) in a text area, or:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Merge requests** and find your merge request.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Code > Merge requests** and find your merge request.
1. On the right sidebar, expand the right sidebar and locate the **Assignees** section.
1. Select **Edit** and, from the dropdown list, select all users you want
to assign the merge request to.
@@ -327,8 +323,8 @@ On GitLab.com, this feature is enabled for GitLab team members only.
To understand the history of a merge request, filter its activity feed to show you
only the items that are relevant to you.
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Merge requests**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Code > Merge requests**.
1. Select a merge request.
1. Scroll to **Activity**.
1. On the right side of the page, select **Activity filter** to show the filter options.
@@ -405,8 +401,8 @@ You can prevent merge requests from being merged until all threads are
resolved. When this setting is enabled, the **Unresolved threads** counter in a merge request
is shown in orange when at least one thread remains unresolved.
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Settings > Merge requests**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Settings > Merge requests**.
1. In the **Merge checks** section, select the **All threads must be resolved** checkbox.
1. Select **Save changes**.
@@ -415,8 +411,8 @@ is shown in orange when at least one thread remains unresolved.
You can set merge requests to automatically resolve threads when lines are modified
with a new push.
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Settings > Merge requests**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Settings > Merge requests**.
1. In the **Merge options** section, select
**Automatically resolve merge request diff threads when they become outdated**.
1. Select **Save changes**.
diff --git a/doc/user/project/merge_requests/merge_when_pipeline_succeeds.md b/doc/user/project/merge_requests/merge_when_pipeline_succeeds.md
index f614a412937..66c3b1fda74 100644
--- a/doc/user/project/merge_requests/merge_when_pipeline_succeeds.md
+++ b/doc/user/project/merge_requests/merge_when_pipeline_succeeds.md
@@ -39,8 +39,8 @@ To do this when pushing from the command line, use the `merge_request.merge_when
To do this from the GitLab user interface:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Merge requests**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Code > Merge requests**.
1. Scroll to the merge request reports section.
1. Optional. Select your desired merge options, such as **Delete source branch**,
**Squash commits**, or **Edit commit message**.
@@ -62,8 +62,8 @@ Prerequisites:
To do this:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Merge requests**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Code > Merge requests**.
1. Scroll to the merge request reports section.
1. Select **Cancel auto-merge**.
@@ -88,8 +88,8 @@ Prerequisites:
To enable this setting:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Settings > Merge requests**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Settings > Merge requests**.
1. Scroll to **Merge checks**, and select **Pipelines must succeed**.
This setting also prevents merge requests from being merged if there is no pipeline,
which can [conflict with some rules](#merge-requests-dont-merge-when-successful-pipeline-is-required).
@@ -109,9 +109,8 @@ Prerequisite:
To change this behavior:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Settings > General**.
-1. Expand **Merge requests**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Settings > Merge requests**.
1. Under **Merge checks**:
- Select **Pipelines must succeed**.
- Select **Skipped pipelines are considered successful**.
diff --git a/doc/user/project/merge_requests/methods/index.md b/doc/user/project/merge_requests/methods/index.md
index 02bd4ed0502..7bb10303d7e 100644
--- a/doc/user/project/merge_requests/methods/index.md
+++ b/doc/user/project/merge_requests/methods/index.md
@@ -26,8 +26,8 @@ gitGraph
## Configure a project's merge method
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Settings > Merge requests**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Settings > Merge requests**.
1. Select your desired **Merge method** from these options:
- Merge commit
- Merge commit with semi-linear history
diff --git a/doc/user/project/merge_requests/revert_changes.md b/doc/user/project/merge_requests/revert_changes.md
index 77fd78ee0d0..c4288a7793c 100644
--- a/doc/user/project/merge_requests/revert_changes.md
+++ b/doc/user/project/merge_requests/revert_changes.md
@@ -30,8 +30,8 @@ Prerequisites:
To do this:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Merge requests** and identify your merge request.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Code > Merge requests** and identify your merge request.
1. Scroll to the merge request reports area, and find the report showing when the
merge request was merged.
1. Select **Revert**.
@@ -55,13 +55,13 @@ Prerequisites:
To do this:
-1. On the top bar, select **Main menu > Projects** and find your project.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
1. If you know the merge request that contains the commit:
- 1. On the left sidebar, select **Merge requests** and identify your merge request.
+ 1. Select **Code > Merge requests**, then identify and select your merge request.
1. Select **Commits**, then select the title of the commit you want to revert. This displays the commit in the **Changes** tab of your merge request.
1. Select the commit hash you want to revert. GitLab displays the contents of the commit.
1. If you don't know the merge request the commit originated from:
- 1. On the left sidebar, select **Repository > Commits**.
+ 1. Select **Code > Commits**.
1. Select the title of the commit to display full information about the commit.
1. In the upper-right corner, select **Options**, then select **Revert**.
1. In **Revert in branch**, select the branch to revert your changes into.
diff --git a/doc/user/project/merge_requests/reviews/index.md b/doc/user/project/merge_requests/reviews/index.md
index 66b1d792391..86468af06a2 100644
--- a/doc/user/project/merge_requests/reviews/index.md
+++ b/doc/user/project/merge_requests/reviews/index.md
@@ -77,8 +77,8 @@ displays next to your name.
To download the changes included in a merge request as a diff:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Merge requests**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Code > Merge requests**.
1. Select your merge request.
1. In the upper-right corner, select **Code > Plain diff**.
@@ -100,8 +100,8 @@ curl "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/000000.diff" | git a
To download the changes included in a merge request as a patch file:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Merge requests**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Code > Merge requests**.
1. Select your merge request.
1. In the upper-right corner, select **Code > Patches**.
diff --git a/doc/user/project/merge_requests/reviews/suggestions.md b/doc/user/project/merge_requests/reviews/suggestions.md
index 9461ac658c7..24197c5c313 100644
--- a/doc/user/project/merge_requests/reviews/suggestions.md
+++ b/doc/user/project/merge_requests/reviews/suggestions.md
@@ -17,8 +17,8 @@ merge request, authored by the user who suggested the changes.
## Create suggestions
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Merge requests** and find your merge request.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Code > Merge requests** and find your merge request.
1. On the secondary menu, select **Changes**.
1. Find the lines of code you want to change.
- To select a single line:
@@ -80,8 +80,8 @@ Prerequisites:
To apply suggested changes directly from the merge request:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Merge requests** and find your merge request.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Code > Merge requests** and find your merge request.
1. Find the comment containing the suggestion you want to apply.
- To apply suggestions individually, select **Apply suggestion**.
- To apply multiple suggestions in a single commit, select **Add suggestion to batch**.
@@ -128,8 +128,8 @@ Merge requests created from forks use the template defined in the target project
To meet your project's needs, you can customize these messages and include other
placeholder variables:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Settings > Merge requests**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Settings > Merge requests**.
1. Scroll to **Merge suggestions**, and alter the text to meet your needs.
See [Supported variables](#supported-variables) for a list of placeholders
you can use in this message.
@@ -163,8 +163,8 @@ For example, to customize the commit message to output
To reduce the number of commits added to your branch, you can apply multiple
suggestions in a single commit.
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Merge requests** and find your merge request.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Code > Merge requests** and find your merge request.
1. For each suggestion you want to apply, and select **Add suggestion to batch**.
1. Optional. To remove a suggestion, select **Remove from batch**.
1. After you add your desired suggestions, select **Apply suggestions**.
diff --git a/doc/user/project/merge_requests/squash_and_merge.md b/doc/user/project/merge_requests/squash_and_merge.md
index 075716e90c8..b9b8485021b 100644
--- a/doc/user/project/merge_requests/squash_and_merge.md
+++ b/doc/user/project/merge_requests/squash_and_merge.md
@@ -71,8 +71,8 @@ Prerequisites:
To configure the default squashing behavior for all merge requests in your project:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Settings > Merge requests**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Settings > Merge requests**.
1. In the **Squash commits when merging** section, select your desired behavior:
- **Do not allow**: Squashing is never performed, and the option is not displayed.
- **Allow**: Squashing is allowed, but cleared by default.
diff --git a/doc/user/project/merge_requests/status_checks.md b/doc/user/project/merge_requests/status_checks.md
index 0e339c65ed5..a151a7cbf1b 100644
--- a/doc/user/project/merge_requests/status_checks.md
+++ b/doc/user/project/merge_requests/status_checks.md
@@ -36,8 +36,8 @@ see [epic 3869](https://gitlab.com/groups/gitlab-org/-/epics/3869).
By default, merge requests in projects can be merged even if external status checks fail. To block the merging of merge requests when external checks fail:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Settings > Merge requests**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Settings > Merge requests**.
1. Select the **Status checks must succeed** checkbox.
1. Select **Save changes**.
diff --git a/doc/user/project/milestones/index.md b/doc/user/project/milestones/index.md
index 4641af262ca..47325ee7c90 100644
--- a/doc/user/project/milestones/index.md
+++ b/doc/user/project/milestones/index.md
@@ -37,9 +37,8 @@ For information about project and group milestones API, see:
To view the milestone list:
-1. On the top bar, select **Main menu > Projects** and find your project or
- **Main menu > Groups** and find your group.
-1. Select **Issues > Milestones**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project or group.
+1. Select **Plan > Milestones**.
In a project, GitLab displays milestones that belong to the project.
In a group, GitLab displays milestones that belong to the group and all projects in the group.
@@ -67,7 +66,8 @@ You might not see some milestones because they're in projects or groups you're n
To do so:
-1. On the top bar select **Main menu > Your work**.
+1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
+1. Select **Your work**.
1. On the left sidebar, select **Milestones**.
### View milestone details
@@ -121,8 +121,8 @@ Prerequisites:
To create a milestone:
-1. On the top bar, select **Main menu > Projects** and find your project or **Main menu > Groups** and find your group.
-1. On the left sidebar, select **Issues > Milestones**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project or group.
+1. Select **Plan > Milestones**.
1. Select **New milestone**.
1. Enter the title.
1. Optional. Enter description, start date, and due date.
@@ -140,7 +140,8 @@ Prerequisites:
To edit a milestone:
-1. On the top bar, select **Main menu > Projects** and find your project or **Main menu > Groups** and find your group.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project or group.
+1. Select **Plan > Milestones**.
1. Select a milestone's title.
1. In the top right corner, select **Milestone actions** (**{ellipsis_v}**) and then select **Edit**.
1. Edit the title, start date, due date, or description.
@@ -156,7 +157,8 @@ Prerequisites:
To edit a milestone:
-1. On the top bar, select **Main menu > Projects** and find your project or **Main menu > Groups** and find your group.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project or group.
+1. Select **Plan > Milestones**.
1. Select a milestone's title.
1. In the top right corner, select **Milestone actions** (**{ellipsis_v}**) and then select **Delete**.
1. Select **Delete milestone**.
@@ -181,8 +183,8 @@ Prerequisites:
To promote a project milestone:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Issues > Milestones**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Plan > Milestones**.
1. Either:
- Select **Promote to Group Milestone** (**{level-up}**) next to the milestone you want to promote.
- Select the milestone title, and then select **Milestone actions** (**{ellipsis_v}**) > **Promote**.
diff --git a/doc/user/project/repository/branches/default.md b/doc/user/project/repository/branches/default.md
index 96f5f6887d9..100450aefe7 100644
--- a/doc/user/project/repository/branches/default.md
+++ b/doc/user/project/repository/branches/default.md
@@ -42,8 +42,8 @@ Prerequisites:
To update the default branch for an individual [project](../../index.md):
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. In the left navigation menu, go to **Settings > Repository**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Settings > Repository**.
1. Expand **Branch defaults**. For **Default branch**, select a new default branch.
1. Optional. Select the **Auto-close referenced issues on default branch** checkbox to close
issues when a merge request
@@ -68,8 +68,9 @@ GitLab [administrators](../../../permissions.md) of self-managed instances can
customize the initial branch for projects hosted on that instance. Individual
groups and subgroups can override this instance-wide setting for their projects.
-1. On the top bar, select **Main menu > Admin**.
-1. On the left sidebar, select **Settings > Repository**.
+1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
+1. Select **Admin Area**.
+1. Select **Settings > Repository**.
1. Expand **Default branch**.
1. For **Initial default branch name**, select a new default branch.
1. Select **Save changes**.
@@ -84,8 +85,8 @@ overrides it.
Users with the Owner role of groups and subgroups can configure the default branch name for a group:
-1. On the top bar, select **Main menu > Group** and find your group.
-1. On the left sidebar, select **Settings > Repository**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your group.
+1. Select **Settings > Repository**.
1. Expand **Default branch**.
1. For **Initial default branch name**, select a new default branch.
1. Select **Save changes**.
@@ -124,8 +125,9 @@ you must either:
Administrators of self-managed instances can customize the initial default branch protection for projects hosted on that instance. Individual
groups and subgroups can override this instance-wide setting for their projects.
-1. On the top bar, select **Main menu > Admin**.
-1. On the left sidebar, select **Settings > Repository**.
+1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
+1. Select **Admin Area**.
+1. Select **Settings > Repository**.
1. Expand **Default branch**.
1. Select [**Initial default branch protection**](#protect-initial-default-branches).
1. To allow group owners to override the instance's default branch protection, select
@@ -141,8 +143,9 @@ can be overridden on a per-group basis by the group's owner. In
[GitLab Premium or Ultimate](https://about.gitlab.com/pricing/), GitLab administrators can
disable this privilege for group owners, enforcing the instance-level protection rule:
-1. On the top bar, select **Main menu > Admin**.
-1. On the left sidebar, select **Settings > Repository**.
+1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
+1. Select **Admin Area**.
+1. Select **Settings > Repository**.
1. Expand the **Default branch** section.
1. Clear the **Allow owners to manage default branch protection per group** checkbox.
1. Select **Save changes**.
@@ -161,8 +164,8 @@ can be overridden on a per-group basis by the group's owner. In
[enforce protection of initial default branches](#prevent-overrides-of-default-branch-protection)
which locks this setting for group owners.
-1. On the top bar, select **Main menu > Groups** and find your group.
-1. On the left sidebar, select **Settings > Repository**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your group.
+1. Select **Settings > Repository**.
1. Expand **Default branch**.
1. Select [**Initial default branch protection**](#protect-initial-default-branches).
1. Select **Save changes**.
diff --git a/doc/user/project/repository/branches/index.md b/doc/user/project/repository/branches/index.md
index 6375a562219..e43efca600a 100644
--- a/doc/user/project/repository/branches/index.md
+++ b/doc/user/project/repository/branches/index.md
@@ -29,8 +29,8 @@ Branches are the foundation of development in a project:
To create a new branch from the GitLab UI:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Repository > Branches**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Code > Branches**.
1. On the top right, select **New branch**.
1. Enter a **Branch name**.
1. In **Create from**, select the base of your branch: an existing branch, an existing
@@ -52,7 +52,7 @@ Prerequisites:
To add a [default branch](default.md) to an empty project:
-1. On the top bar, select **Main menu > Projects** and find your project.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
1. Scroll to **The repository for this project is empty** and select the type of
file you want to add.
1. In the Web IDE, make any desired changes to this file, then select **Create commit**.
@@ -77,8 +77,8 @@ Prerequisites:
To create a branch from an issue:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Issues** (**{issues}**) and find your issue.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Plan > Issues** and find your issue.
1. Below the issue description, find the **Create merge request** dropdown list, and select
**{chevron-down}** to display the dropdown list.
1. Select **Create branch**. A default **Branch name** is provided, based on the
@@ -111,8 +111,8 @@ You can manage your branches:
To view and manage your branches in the GitLab user interface:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Repository > Branches**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Code > Branches**.
On this page, you can:
@@ -149,8 +149,8 @@ Prerequisites:
To view the **Branch rules overview** list:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Settings > Repository**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Settings > Repository**.
1. Expand **Branch Rules** to view all branches with protections.
- To add protections to a new branch:
1. Select **Add branch rule**.
@@ -200,8 +200,9 @@ Prerequisites:
To change the default pattern for branches created from issues:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Settings > Repository** and expand **Branch defaults**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Settings > Repository**.
+1. Expand **Branch defaults**.
1. Scroll to **Branch name template** and enter a value. The field supports these variables:
- `%{id}`: The numeric ID of the issue.
- `%{title}`: The title of the issue, modified to use only characters acceptable in Git branch names.
@@ -237,8 +238,8 @@ new changes. It uses the merge base, not the actual commit content, to compare b
To compare branches in a repository:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Repository > Compare revisions**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Code > Compare revisions**.
1. Select the **Source** branch to search for your desired branch. Exact matches are
shown first. You can refine your search with operators:
- `^` matches the beginning of the branch name: `^feat` matches `feat/user-authentication`.
@@ -262,8 +263,8 @@ Prerequisites:
To do this:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Repository > Branches**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Code > Branches**.
1. On the upper right corner of the page, select **More** **{ellipsis_v}**.
1. Select **Delete merged branches**.
1. In the modal window, enter the word `delete` to confirm, then select **Delete merged branches**.
@@ -322,8 +323,8 @@ Error: Could not set the default branch. Do you have a branch named 'HEAD' in yo
To fix this problem:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Repository > Branches**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Code > Branches**.
1. Search for a branch named `HEAD`.
1. Make sure the branch has no uncommitted changes.
1. Select **Delete branch**, then **Yes, delete branch**.
diff --git a/doc/user/project/repository/forking_workflow.md b/doc/user/project/repository/forking_workflow.md
index 91f6d0a0bf9..a6bb02989a3 100644
--- a/doc/user/project/repository/forking_workflow.md
+++ b/doc/user/project/repository/forking_workflow.md
@@ -59,8 +59,8 @@ or the command line. GitLab Premium and Ultimate tiers can also automate updates
To update your fork from the GitLab UI:
-1. On the top bar, select **Main menu > Projects > View all projects**.
-1. On the secondary menu, select **Personal**.
+1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
+1. Select **View all your projects**.
1. Select the fork you want to update.
1. Below the dropdown list for branch name, find the **Forked from** (**{fork}**)
information box to determine if your fork is ahead, behind, or both. In this example,
@@ -181,8 +181,8 @@ To restore the fork relationship, [use the API](../../../api/projects.md#create-
To remove a fork relationship:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Settings > General**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Settings > General**.
1. Expand **Advanced**.
1. In the **Remove fork relationship** section, select **Remove fork relationship**.
1. To confirm, enter the project path and select **Confirm**.
diff --git a/doc/user/project/repository/gpg_signed_commits/index.md b/doc/user/project/repository/gpg_signed_commits/index.md
index a2dd2488961..839ab33808b 100644
--- a/doc/user/project/repository/gpg_signed_commits/index.md
+++ b/doc/user/project/repository/gpg_signed_commits/index.md
@@ -121,7 +121,7 @@ To add a GPG key to your user settings:
1. Sign in to GitLab.
1. In the upper-right corner, select your avatar.
1. Select **Edit profile**.
-1. On the left sidebar, select **GPG Keys** (**{key}**).
+1. Select **GPG Keys** (**{key}**).
1. In **Key**, paste your _public_ key.
1. To add the key to your account, select **Add key**. GitLab shows the key's
fingerprint, email address, and creation date:
@@ -226,11 +226,11 @@ Prerequisites:
You can review commits for a merge request, or for an entire project:
1. To review commits for a project:
- 1. On the top bar, select **Main menu > Projects** and find your project.
- 1. On the left sidebar, select **Repository > Commits**.
+ 1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+ 1. Select **Code > Commits**.
1. To review commits for a merge request:
- 1. On the top bar, select **Main menu > Projects** and find your project.
- 1. On the left sidebar, select **Merge requests**, then select your merge request.
+ 1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+ 1. Select **Merge requests**, then select your merge request.
1. Select **Commits**.
1. Identify the commit you want to review. Signed commits show either a **Verified**
or **Unverified** badge, depending on the verification status of the GPG
@@ -255,7 +255,7 @@ To revoke a GPG key:
1. In the upper-right corner, select your avatar.
1. Select **Edit profile**.
-1. On the left sidebar, select **GPG Keys** (**{key}**).
+1. Select **GPG Keys** (**{key}**).
1. Select **Revoke** next to the GPG key you want to delete.
## Remove a GPG key
@@ -270,7 +270,7 @@ To remove a GPG key from your account:
1. In the upper-right corner, select your avatar.
1. Select **Edit profile**.
-1. On the left sidebar, select **GPG Keys** (**{key}**).
+1. Select **GPG Keys** (**{key}**).
1. Select **Remove** (**{remove}**) next to the GPG key you want to delete.
If you must unverify both future and past commits,
diff --git a/doc/user/project/repository/mirror/bidirectional.md b/doc/user/project/repository/mirror/bidirectional.md
index 0563f747e8d..550d4535adb 100644
--- a/doc/user/project/repository/mirror/bidirectional.md
+++ b/doc/user/project/repository/mirror/bidirectional.md
@@ -44,7 +44,7 @@ and [pull](pull.md#pull-from-a-remote-repository) mirrors in the upstream GitLab
To create the webhook in the downstream instance:
1. Create a [personal access token](../../../profile/personal_access_tokens.md) with `API` scope.
-1. On the top bar, select **Main menu > Projects** and find your project.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
1. On the left sidebar, select **Settings > Webhooks**.
1. Add the webhook **URL**, which (in this case) uses the
[Pull Mirror API](../../../../api/projects.md#start-the-pull-mirroring-process-for-a-project)
diff --git a/doc/user/project/repository/mirror/index.md b/doc/user/project/repository/mirror/index.md
index 5b531c85ac9..733310a0b4d 100644
--- a/doc/user/project/repository/mirror/index.md
+++ b/doc/user/project/repository/mirror/index.md
@@ -40,8 +40,8 @@ Prerequisites:
- If your mirror connects with `ssh://`, the host key must be detectable on the server,
or you must have a local copy of the key.
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Settings > Repository**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Settings > Repository**.
1. Expand **Mirroring repositories**.
1. Enter a **Git repository URL**. For security reasons, the URL to the original
repository is only displayed to users with the Maintainer role
@@ -113,8 +113,8 @@ Prerequisite:
- You must have at least the Maintainer role for the project.
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Settings > Repository**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Settings > Repository**.
1. Expand **Mirroring repositories**.
1. Scroll to **Mirrored repositories** and identify the mirror to update.
1. Select **Update now** (**{retry}**):
@@ -156,8 +156,8 @@ When you mirror a repository and select the **SSH public key** as your
authentication method, GitLab generates a public key for you. The non-GitLab server
needs this key to establish trust with your GitLab repository. To copy your SSH public key:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Settings > Repository**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Settings > Repository**.
1. Expand **Mirroring repositories**.
1. Scroll to **Mirrored repositories**.
1. Identify the correct repository, and select **Copy SSH public key** (**{copy-to-clipboard}**).
@@ -265,8 +265,8 @@ If you receive this error after creating a new project using
Check if the repository owner is specified in the URL of your mirrored repository:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Settings > Repository**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Settings > Repository**.
1. Expand **Mirroring repositories**.
1. If no repository owner is specified, delete and add the URL again in this format,
replacing `OWNER`, `ACCOUNTNAME`, `PATH_TO_REPO`, and `REPONAME` with your values:
@@ -346,8 +346,8 @@ Prerequisites:
To resolve the issue:
1. [Verify the host key](#verify-a-host-key).
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Settings > Repository**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Settings > Repository**.
1. Expand **Mirroring repositories**.
1. To refresh the keys, either:
diff --git a/doc/user/project/repository/mirror/pull.md b/doc/user/project/repository/mirror/pull.md
index 2b8470b9e3d..1463e0de0f1 100644
--- a/doc/user/project/repository/mirror/pull.md
+++ b/doc/user/project/repository/mirror/pull.md
@@ -60,8 +60,8 @@ Prerequisite:
with the `repo` scope. If 2FA is enabled, this personal access
token serves as your GitHub password.
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Settings > Repository**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Settings > Repository**.
1. Expand **Mirroring repositories**.
1. Enter the **Git repository URL**. Include the username
in the URL, if required: `https://MYUSERNAME@gitlab.com/GROUPNAME/PROJECTNAME.git`
diff --git a/doc/user/project/repository/mirror/push.md b/doc/user/project/repository/mirror/push.md
index dfd9911effc..9da8ce7acc5 100644
--- a/doc/user/project/repository/mirror/push.md
+++ b/doc/user/project/repository/mirror/push.md
@@ -34,8 +34,8 @@ displays an error.
To set up push mirroring for an existing project:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Settings > Repository**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Settings > Repository**.
1. Expand **Mirroring repositories**.
1. Enter a repository URL.
1. In the **Mirror direction** dropdown list, select **Push**.
@@ -156,7 +156,7 @@ To set up a mirror from GitLab to AWS CodeCommit:
1. In the AWS CodeCommit console, create a new repository to mirror from your GitLab repository.
1. Open your new repository, and then select **Clone URL > Clone HTTPS** (not **Clone HTTPS (GRC)**).
1. In GitLab, open the repository to be push-mirrored.
-1. On the left sidebar, select **Settings > Repository**, and then expand **Mirroring repositories**.
+1. Select **Settings > Repository**, and then expand **Mirroring repositories**.
1. Fill in the **Git repository URL** field using this format:
```plaintext
diff --git a/doc/user/project/repository/push_rules.md b/doc/user/project/repository/push_rules.md
index 4d05ea701b7..fbadc9b84a3 100644
--- a/doc/user/project/repository/push_rules.md
+++ b/doc/user/project/repository/push_rules.md
@@ -37,8 +37,9 @@ Prerequisite:
To create global push rules:
-1. On the top bar, select **Main menu > Admin**.
-1. On the left sidebar, select **Push Rules**.
+1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
+1. Select **Admin Area**.
+1. Select **Push Rules**.
1. Expand **Push rules**.
1. Set the rule you want.
1. Select **Save push rules**.
@@ -49,8 +50,8 @@ The push rule of an individual project overrides the global push rule.
To override global push rules for a specific project, or to update the rules
for an existing project to match new global push rules:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Settings > Repository**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Settings > Repository**.
1. Expand **Push rules**.
1. Set the rule you want.
1. Select **Save push rules**.
diff --git a/doc/user/project/repository/ssh_signed_commits/index.md b/doc/user/project/repository/ssh_signed_commits/index.md
index f2ce80263c8..8f29845fd9b 100644
--- a/doc/user/project/repository/ssh_signed_commits/index.md
+++ b/doc/user/project/repository/ssh_signed_commits/index.md
@@ -93,10 +93,10 @@ You can review commits for a merge request, or for an entire project, to confirm
they are signed:
1. To review commits for a project:
- 1. On the top bar, select **Main menu > Projects** and find your project.
- 1. On the left sidebar, select **Repository > Commits**.
+ 1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+ 1. Select **Code > Commits**.
1. To review commits for a merge request:
- 1. On the top bar, select **Main menu > Projects** and find your project.
+ 1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
1. On the left sidebar, select **Merge requests**, then select your merge request.
1. Select **Commits**.
1. Identify the commit you want to review. Signed commits show either a **Verified**
diff --git a/doc/user/project/repository/tags/index.md b/doc/user/project/repository/tags/index.md
index e252a9a433b..8c6774408e6 100644
--- a/doc/user/project/repository/tags/index.md
+++ b/doc/user/project/repository/tags/index.md
@@ -44,15 +44,15 @@ In the GitLab UI, each tag displays:
To view all existing tags for a project:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Repository > Tags**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Code > Tags**.
## View tagged commits in the commits list
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/18795) in GitLab 15.10.
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Repository > Commits**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Code > Commits**.
1. Commits with a tag are labeled with a tag icon (**{tag}**) and the name of the tag.
This example shows a commit tagged `v1.26.0`:
@@ -88,8 +88,8 @@ To create either a lightweight or annotated tag from the command line, and push
To create a tag from the GitLab UI:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Repository > Tags**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Code > Tags**.
1. Select **New tag**.
1. Provide a **Tag name**.
1. For **Create from**, select an existing branch name, tag, or commit SHA.
diff --git a/doc/user/project/repository/web_editor.md b/doc/user/project/repository/web_editor.md
index c81f4e93166..7b2dcd04982 100644
--- a/doc/user/project/repository/web_editor.md
+++ b/doc/user/project/repository/web_editor.md
@@ -25,7 +25,7 @@ for any change you commit through the Web Editor.
To create a text file in the Web Editor:
-1. On the top bar, select **Main menu > Projects** and find your project.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
1. From the project dashboard or repository, next to the branch name,
select the plus icon (**{plus}**).
1. From the dropdown list, select **New file**.
@@ -35,8 +35,8 @@ To create a text file in the Web Editor:
### Create a file from a template
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Repository > Files**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Code > Repository**.
1. Next to the project name, select the plus icon (**{plus}**) to display a
dropdown list, then select **New file** from the list.
1. For **Filename**, provide one of the filenames that GitLab provides a template for:
@@ -56,7 +56,7 @@ To create a text file in the Web Editor:
To edit a text file in the Web Editor:
-1. On the top bar, select **Main menu > Projects** and find your project.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
1. Go to your file.
1. In the upper right, select **Edit > Edit single file**.
@@ -105,7 +105,7 @@ To upload a binary file in the Web Editor:
<!-- This list is duplicated at doc/gitlab-basics/add-file.md#from-the-ui -->
<!-- For why we duplicated the info, see https://gitlab.com/gitlab-org/gitlab/-/merge_requests/111072#note_1267429478 -->
-1. On the top bar, select **Main menu > Projects** and find your project.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
1. From the project dashboard or repository, next to the branch name, select the plus icon (**{plus}**).
1. From the dropdown list, select **Upload file**.
1. Complete the fields. To create a merge request with the uploaded file, ensure the **Start a new merge request with these changes** toggle is turned on.
@@ -115,7 +115,7 @@ To upload a binary file in the Web Editor:
To create a directory in the Web Editor:
-1. On the top bar, select **Main menu > Projects** and find your project.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
1. From the project dashboard or repository, next to the branch name, select the plus icon (**{plus}**).
1. From the dropdown list, select **New directory**.
1. Complete the fields. To create a merge request with the new directory, ensure the **Start a new merge request with these changes** toggle is turned on.
@@ -125,7 +125,7 @@ To create a directory in the Web Editor:
To create a [branch](branches/index.md) in the Web Editor:
-1. On the top bar, select **Main menu > Projects** and find your project.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
1. From the project dashboard or repository, next to the branch name, select the plus icon (**{plus}**).
1. From the dropdown list, select **New branch**.
1. Complete the fields.
@@ -136,7 +136,7 @@ To create a [branch](branches/index.md) in the Web Editor:
You can create [tags](tags/index.md) to mark milestones such as
production releases and release candidates. To create a tag in the Web Editor:
-1. On the top bar, select **Main menu > Projects** and find your project.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
1. From the project dashboard or repository, next to the branch name, select the plus icon (**{plus}**).
1. From the dropdown list, select **New tag**.
1. Complete the fields.
diff --git a/doc/user/project/settings/import_export.md b/doc/user/project/settings/import_export.md
index 35af3679185..6cc2b51f077 100644
--- a/doc/user/project/settings/import_export.md
+++ b/doc/user/project/settings/import_export.md
@@ -86,8 +86,9 @@ Before you can migrate projects on a self-managed GitLab instance using file exp
To enable file exports as an import source for the destination instance:
-1. On the top bar, select **Main menu > Admin**.
-1. On the left sidebar, select **Settings > General**.
+1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
+1. Select **Admin Area**.
+1. Select **Settings > General**.
1. Expand **Visibility and access controls**.
1. Scroll to **Import sources**.
1. Select the **GitLab export** checkbox.
@@ -112,8 +113,8 @@ Prerequisites:
To export a project and its data, follow these steps:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Settings > General**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Settings > General**.
1. Expand **Advanced**.
1. Select **Export project**.
1. After the export is generated, you should receive an email with a link to download the file.
diff --git a/doc/user/project/settings/index.md b/doc/user/project/settings/index.md
index f0b4ca1dc58..8deb05c45ef 100644
--- a/doc/user/project/settings/index.md
+++ b/doc/user/project/settings/index.md
@@ -51,7 +51,7 @@ compliance framework using either:
- The GitLab UI:
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
1. Select **Settings** > **General**.
- 1. Expand the **Compliance frameworks** section.
+ 1. Expand **Compliance frameworks**.
1. Select a compliance framework.
1. Select **Save changes**.
- In [GitLab 14.2](https://gitlab.com/gitlab-org/gitlab/-/issues/333249) and later, using the
@@ -68,7 +68,7 @@ To configure visibility, features, and permissions for a project:
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
1. Select **Settings > General**.
-1. Expand the **Visibility, project features, permissions** section.
+1. Expand **Visibility, project features, permissions**.
1. To change the project visibility, select the dropdown list. If you select to **Public**, you limit access to some features to **Only Project Members**.
1. To allow users to request access to the project, select the **Users can request access** checkbox.
1. Use the [toggles](#project-feature-settings) to enable or disable features in the project.
@@ -135,7 +135,7 @@ To disable the CVE identifier request option in issues in your project:
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
1. Select **Settings > General**.
-1. Expand the **Visibility, project features, permissions** section.
+1. Expand **Visibility, project features, permissions**.
1. Under **Issues**, turn off the **CVE ID requests in the issue sidebar** toggle.
1. Select **Save changes**.
@@ -147,7 +147,7 @@ Prerequisites:
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
1. Select **Settings > General**.
-1. Expand the **Visibility, project features, permissions** section.
+1. Expand **Visibility, project features, permissions**.
1. Clear the **Disable email notifications** checkbox.
## Configure merge request settings for a project
@@ -231,7 +231,7 @@ To rename a repository:
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
1. Select **Settings > General**.
-1. Expand the **Advanced** section.
+1. Expand **Advanced**.
1. In the **Change path** text box, edit the path.
1. Select **Change path**.
@@ -300,7 +300,7 @@ To delete a project:
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
1. Select **Settings > General**.
-1. Expand the **Advanced** section.
+1. Expand **Advanced**.
1. In the **Delete this project** section, select **Delete project**.
1. In the confirmation message text field, enter the name of the project as instructed, and select **Yes, delete project**.
@@ -336,7 +336,7 @@ To immediately delete a project marked for deletion:
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
1. Select **Settings > General**.
-1. Expand the **Advanced** section.
+1. Expand **Advanced**.
1. In the **Delete this project** section, select **Delete project**.
1. In the confirmation message text field, enter the name of the project as instructed, as select **Yes, delete project**.
diff --git a/doc/user/project/settings/project_access_tokens.md b/doc/user/project/settings/project_access_tokens.md
index 8082ff3d76a..7fd8fdf3a00 100644
--- a/doc/user/project/settings/project_access_tokens.md
+++ b/doc/user/project/settings/project_access_tokens.md
@@ -55,8 +55,8 @@ all projects that have visibility level set to [Internal](../../public_access.md
To create a project access token:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Settings > Access Tokens**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Settings > Access Tokens**.
1. Enter a name. The token name is visible to any user with permissions to view the project.
1. Enter an expiry date for the token.
- The token expires on that date at midnight UTC.
@@ -73,8 +73,8 @@ A project access token is displayed. Save the project access token somewhere saf
To revoke a project access token:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Settings > Access Tokens**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Settings > Access Tokens**.
1. Next to the project access token to revoke, select **Revoke**.
## Scopes for a project access token
@@ -96,8 +96,8 @@ The scope determines the actions you can perform when you authenticate with a pr
To enable or disable project access token creation for all projects in a top-level group:
-1. On the top bar, select **Main menu > Groups** and find your group.
-1. On the left sidebar, select **Settings > General**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your group.
+1. Select **Settings > General**.
1. Expand **Permissions and group features**.
1. Under **Permissions**, turn on or off **Allow project and group access token creation**.
diff --git a/doc/user/project/web_ide/index.md b/doc/user/project/web_ide/index.md
index 1e54566ab3f..481eca5a890 100644
--- a/doc/user/project/web_ide/index.md
+++ b/doc/user/project/web_ide/index.md
@@ -23,7 +23,7 @@ To pair the Web IDE with a remote development environment, see [remote developme
To open the Web IDE from the GitLab UI:
-1. On the top bar, select **Main menu > Projects** and find your project.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
1. Use the <kbd>.</kbd> keyboard shortcut.
You can also open the Web IDE from:
diff --git a/doc/user/workspace/index.md b/doc/user/workspace/index.md
index 74ac6753056..70beed20786 100644
--- a/doc/user/workspace/index.md
+++ b/doc/user/workspace/index.md
@@ -157,3 +157,8 @@ You can provide your own container image, which can run as any Linux user ID. It
GitLab uses the Linux root group ID permission to create, update, or delete files in a container. The container runtime used by the Kubernetes cluster must ensure all containers have a default Linux group ID of `0`.
If you have a container image that does not support arbitrary user IDs, you cannot create, update, or delete files in a workspace. To create a container image that supports arbitrary user IDs, see the [OpenShift documentation](https://docs.openshift.com/container-platform/4.12/openshift_images/create-images.html#use-uid_create-images).
+
+## Selecting an agent for your workspace
+
+A project can use any agent defined under the root group of the project,
+provided that remote development is properly configured for that agent.
diff --git a/lib/gitlab/usage/metrics/instrumentations/count_all_ci_builds_metric.rb b/lib/gitlab/usage/metrics/instrumentations/count_all_ci_builds_metric.rb
new file mode 100644
index 00000000000..2a3cbaf5d03
--- /dev/null
+++ b/lib/gitlab/usage/metrics/instrumentations/count_all_ci_builds_metric.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Usage
+ module Metrics
+ module Instrumentations
+ class CountAllCiBuildsMetric < DatabaseMetric
+ operation :count
+
+ relation { ::Ci::Build }
+ end
+ end
+ end
+ end
+end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 88fe8fed849..7f2995d2bb4 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -11250,13 +11250,16 @@ msgstr ""
msgid "CommandPalette|Pages"
msgstr ""
-msgid "CommandPalette|Type %{commandHandle} for command, %{userHandle} for user or perform generic search..."
+msgid "CommandPalette|Type %{commandHandle} for command, %{userHandle} for user, %{projectHandle} for project, %{issueHandle} for issue or perform generic search..."
msgstr ""
-msgid "CommandPalette|Users"
+msgid "CommandPalette|command"
msgstr ""
-msgid "CommandPalette|command"
+msgid "CommandPalette|issue (enter at least 3 chars)"
+msgstr ""
+
+msgid "CommandPalette|project (enter at least 3 chars)"
msgstr ""
msgid "CommandPalette|user (enter at least 3 chars)"
@@ -20872,6 +20875,9 @@ msgstr ""
msgid "GlobalSearch|Close"
msgstr ""
+msgid "GlobalSearch|Fetching aggregations error."
+msgstr ""
+
msgid "GlobalSearch|Group"
msgstr ""
@@ -20890,6 +20896,9 @@ msgstr ""
msgid "GlobalSearch|Issues assigned to me"
msgstr ""
+msgid "GlobalSearch|Label(s)"
+msgstr ""
+
msgid "GlobalSearch|Language"
msgstr ""
@@ -20938,6 +20947,9 @@ msgstr ""
msgid "GlobalSearch|Search for projects, issues, etc."
msgstr ""
+msgid "GlobalSearch|Search labels"
+msgstr ""
+
msgid "GlobalSearch|Search results are loading"
msgstr ""
@@ -40333,10 +40345,13 @@ msgstr ""
msgid "Saving project."
msgstr ""
-msgid "ScanExecutionPolicy|%{period} %{days} at %{time}"
+msgid "ScanExecutionPolicy|%{hostname}'s timezone"
+msgstr ""
+
+msgid "ScanExecutionPolicy|%{period} %{days} at %{time} %{timezoneLabel} %{timezone}"
msgstr ""
-msgid "ScanExecutionPolicy|%{rules} %{period} for %{scopes} %{branches} %{agents} %{namespaces}"
+msgid "ScanExecutionPolicy|%{rules} actions for %{scopes} %{branches} %{agents} %{namespaces} %{period}"
msgstr ""
msgid "ScanExecutionPolicy|%{rules} every time a pipeline runs for %{scopes} %{branches} %{agents} %{namespaces}"
@@ -40351,6 +40366,9 @@ msgstr ""
msgid "ScanExecutionPolicy|If the field is empty, the runner will be automatically selected"
msgstr ""
+msgid "ScanExecutionPolicy|Kubernetes agent's timezone"
+msgstr ""
+
msgid "ScanExecutionPolicy|Run a %{scan} scan on runner that %{tags}"
msgstr ""
@@ -40384,6 +40402,9 @@ msgstr ""
msgid "ScanExecutionPolicy|Select site profile"
msgstr ""
+msgid "ScanExecutionPolicy|Select timezone"
+msgstr ""
+
msgid "ScanExecutionPolicy|Site profile"
msgstr ""
@@ -40405,6 +40426,12 @@ msgstr ""
msgid "ScanExecutionPolicy|in namespaces"
msgstr ""
+msgid "ScanExecutionPolicy|on %{hostname}"
+msgstr ""
+
+msgid "ScanExecutionPolicy|on the Kubernetes agent pod"
+msgstr ""
+
msgid "ScanExecutionPolicy|scanner profile %{scannerProfile} and site profile %{siteProfile}"
msgstr ""
@@ -53213,9 +53240,6 @@ msgstr ""
msgid "Your top-level group %{namespace_name} will move to a read-only state soon"
msgstr ""
-msgid "Your top-level group is over the user and storage limits and has been placed in a read-only state."
-msgstr ""
-
msgid "Your top-level group is over the user limit and has been placed in a read-only state."
msgstr ""
diff --git a/qa/qa/service/praefect_manager.rb b/qa/qa/service/praefect_manager.rb
index 57f5310901b..684b18a97a7 100644
--- a/qa/qa/service/praefect_manager.rb
+++ b/qa/qa/service/praefect_manager.rb
@@ -198,42 +198,6 @@ module QA
destination_storage[:type] == :praefect ? verify_storage_move_to_praefect(repo_path, destination_storage[:name]) : verify_storage_move_to_gitaly(repo_path, destination_storage[:name])
end
- def praefect_sql_ping_healthy?
- cmd = "docker exec #{@praefect} bash -c '/opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml sql-ping'"
- wait_until_shell_command(cmd) do |line|
- QA::Runtime::Logger.debug(line.chomp)
- break line.include?('praefect sql-ping: OK')
- end
- end
-
- def wait_for_dial_nodes_successful
- Support::Waiter.repeat_until(max_attempts: 3, max_duration: 120, sleep_interval: 1) do
- nodes_confirmed = {
- @primary_node => false,
- @secondary_node => false,
- @tertiary_node => false
- }
-
- nodes_confirmed.each_key do |node|
- nodes_confirmed[node] = true if praefect_dial_nodes_status?(node)
- end
-
- nodes_confirmed.values.all?
- end
- end
-
- def praefect_dial_nodes_status?(node, expect_healthy = true)
- cmd = "docker exec #{@praefect} bash -c '/opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml dial-nodes -timeout 1s'"
- if expect_healthy
- wait_until_shell_command_matches(cmd, /SUCCESS: confirmed Gitaly storage "#{node}" in virtual storages \[#{@virtual_storage}\] is served/)
- else
- wait_until_shell_command(cmd, raise_on_failure: false) do |line|
- QA::Runtime::Logger.debug(line.chomp)
- break true if line.include?('the following nodes are not healthy') && line.include?(node)
- end
- end
- end
-
def praefect_dataloss_information(project_id)
dataloss_info = []
cmd = "docker exec #{@praefect} praefect -config /var/opt/gitlab/praefect/config.toml dataloss --partially-unavailable=true"
diff --git a/qa/qa/specs/features/api/12_systems/gitaly/praefect_connectivity_spec.rb b/qa/qa/specs/features/api/12_systems/gitaly/praefect_connectivity_spec.rb
deleted file mode 100644
index 7692e0f1d01..00000000000
--- a/qa/qa/specs/features/api/12_systems/gitaly/praefect_connectivity_spec.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-# frozen_string_literal: true
-
-module QA
- RSpec.describe 'Systems' do
- describe 'Praefect connectivity commands', :orchestrated, :gitaly_cluster, product_group: :gitaly do
- praefect_manager = Service::PraefectManager.new
-
- before do
- praefect_manager.start_all_nodes
- end
-
- context 'in a healthy environment' do
- it 'confirms healthy connection to database',
- testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/349937' do
- expect(praefect_manager.praefect_sql_ping_healthy?).to be true
- end
-
- it 'confirms healthy connection to gitaly nodes',
- testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/349938' do
- expect(praefect_manager.wait_for_dial_nodes_successful).to be true
- end
- end
-
- context 'in an unhealthy environment' do
- it 'diagnoses unhealthy connection to database',
- testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/349939' do
- praefect_manager.stop_node(praefect_manager.postgres)
- expect(praefect_manager.praefect_sql_ping_healthy?).to be false
- end
-
- it 'diagnoses connection issues to gitaly nodes',
- testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/349940' do
- praefect_manager.stop_node(praefect_manager.primary_node)
- praefect_manager.stop_node(praefect_manager.tertiary_node)
- expect(praefect_manager.praefect_dial_nodes_status?(praefect_manager.primary_node, false)).to be true
- expect(praefect_manager.praefect_dial_nodes_status?(praefect_manager.secondary_node)).to be true
- expect(praefect_manager.praefect_dial_nodes_status?(praefect_manager.tertiary_node, false)).to be true
-
- praefect_manager.stop_node(praefect_manager.secondary_node)
- expect(praefect_manager.praefect_dial_nodes_status?(praefect_manager.secondary_node, false)).to be true
- end
- end
- end
- end
-end
diff --git a/spec/controllers/search_controller_spec.rb b/spec/controllers/search_controller_spec.rb
index 7fce39950e5..9e69566d18f 100644
--- a/spec/controllers/search_controller_spec.rb
+++ b/spec/controllers/search_controller_spec.rb
@@ -477,6 +477,12 @@ RSpec.describe SearchController, feature_category: :global_search do
expect(json_response.first['label']).to match(/User settings/)
end
+ it 'can be scoped with params[:scope]' do
+ expect(controller).to receive(:search_autocomplete_opts).with('setting', filter: nil, scope: 'project')
+
+ get :autocomplete, params: { term: 'setting', scope: 'project' }
+ end
+
it 'makes a call to search_autocomplete_opts' do
expect(controller).to receive(:search_autocomplete_opts).once
diff --git a/spec/finders/namespaces/projects_finder_spec.rb b/spec/finders/namespaces/projects_finder_spec.rb
index 040cdf33b87..9291572d8d1 100644
--- a/spec/finders/namespaces/projects_finder_spec.rb
+++ b/spec/finders/namespaces/projects_finder_spec.rb
@@ -10,6 +10,8 @@ RSpec.describe Namespaces::ProjectsFinder do
let_it_be(:project_2) { create(:project, :public, group: namespace, path: 'test-project', name: 'Test Project') }
let_it_be(:project_3) { create(:project, :public, :issues_disabled, path: 'sub-test-project', group: subgroup, name: 'Sub Test Project') }
let_it_be(:project_4) { create(:project, :public, :merge_requests_disabled, path: 'test-project-2', group: namespace, name: 'Test Project 2') }
+ let_it_be(:project_5) { create(:project, group: subgroup, marked_for_deletion_at: 1.day.ago, pending_delete: true) }
+ let_it_be(:project_6) { create(:project, group: namespace, marked_for_deletion_at: 1.day.ago, pending_delete: true) }
let(:params) { {} }
@@ -28,14 +30,22 @@ RSpec.describe Namespaces::ProjectsFinder do
context 'with a namespace' do
it 'returns the project for the namespace' do
- expect(projects).to contain_exactly(project_1, project_2, project_4)
+ expect(projects).to contain_exactly(project_1, project_2, project_4, project_6)
+ end
+
+ context 'when not_aimed_for_deletion is provided' do
+ let(:params) { { not_aimed_for_deletion: true } }
+
+ it 'returns all projects not aimed for deletion for the namespace' do
+ expect(projects).to contain_exactly(project_1, project_2, project_4)
+ end
end
context 'when include_subgroups is provided' do
let(:params) { { include_subgroups: true } }
it 'returns all projects for the namespace' do
- expect(projects).to contain_exactly(project_1, project_2, project_3, project_4)
+ expect(projects).to contain_exactly(project_1, project_2, project_3, project_4, project_5, project_6)
end
context 'when ids are provided' do
@@ -45,6 +55,14 @@ RSpec.describe Namespaces::ProjectsFinder do
expect(projects).to contain_exactly(project_3)
end
end
+
+ context 'when not_aimed_for_deletion is provided' do
+ let(:params) { { not_aimed_for_deletion: true, include_subgroups: true } }
+
+ it 'returns all projects not aimed for deletion for the namespace' do
+ expect(projects).to contain_exactly(project_1, project_2, project_3, project_4)
+ end
+ end
end
context 'when ids are provided' do
@@ -59,7 +77,7 @@ RSpec.describe Namespaces::ProjectsFinder do
let(:params) { { with_issues_enabled: true, include_subgroups: true } }
it 'returns the projects that have issues enabled' do
- expect(projects).to contain_exactly(project_1, project_2, project_4)
+ expect(projects).to contain_exactly(project_1, project_2, project_4, project_5, project_6)
end
end
@@ -67,7 +85,7 @@ RSpec.describe Namespaces::ProjectsFinder do
let(:params) { { with_merge_requests_enabled: true } }
it 'returns the projects that have merge requests enabled' do
- expect(projects).to contain_exactly(project_1, project_2)
+ expect(projects).to contain_exactly(project_1, project_2, project_6)
end
end
@@ -83,7 +101,7 @@ RSpec.describe Namespaces::ProjectsFinder do
let(:params) { { sort: :similarity } }
it 'returns all projects' do
- expect(projects).to contain_exactly(project_1, project_2, project_4)
+ expect(projects).to contain_exactly(project_1, project_2, project_4, project_6)
end
end
@@ -99,13 +117,14 @@ RSpec.describe Namespaces::ProjectsFinder do
let(:params) { { sort: :latest_activity_desc } }
before do
+ project_6.update!(last_activity_at: 15.minutes.ago)
project_2.update!(last_activity_at: 10.minutes.ago)
project_1.update!(last_activity_at: 5.minutes.ago)
project_4.update!(last_activity_at: 1.minute.ago)
end
it 'returns projects sorted by latest activity' do
- expect(projects).to eq([project_4, project_1, project_2])
+ expect(projects).to eq([project_4, project_1, project_2, project_6])
end
end
end
diff --git a/spec/frontend/search/mock_data.js b/spec/frontend/search/mock_data.js
index 58824f8023d..7cf8633d749 100644
--- a/spec/frontend/search/mock_data.js
+++ b/spec/frontend/search/mock_data.js
@@ -8,6 +8,7 @@ export const MOCK_QUERY = {
group_id: 1,
language: ['C', 'JavaScript'],
labels: ['60', '37'],
+ search: '*',
};
export const MOCK_GROUP = {
diff --git a/spec/frontend/search/sidebar/components/label_dropdown_items_spec.js b/spec/frontend/search/sidebar/components/label_dropdown_items_spec.js
new file mode 100644
index 00000000000..135b12956b2
--- /dev/null
+++ b/spec/frontend/search/sidebar/components/label_dropdown_items_spec.js
@@ -0,0 +1,57 @@
+import { GlFormCheckbox } from '@gitlab/ui';
+import Vue from 'vue';
+import Vuex from 'vuex';
+import { shallowMount } from '@vue/test-utils';
+import { PROCESS_LABELS_DATA } from 'jest/search/mock_data';
+import LabelDropdownItems from '~/search/sidebar/components/label_filter/label_dropdown_items.vue';
+
+Vue.use(Vuex);
+
+describe('LabelDropdownItems', () => {
+ let wrapper;
+
+ const defaultProps = {
+ labels: PROCESS_LABELS_DATA,
+ };
+
+ const createComponent = (Props = defaultProps) => {
+ wrapper = shallowMount(LabelDropdownItems, {
+ propsData: {
+ ...Props,
+ },
+ });
+ };
+
+ const findAllLabelItems = () => wrapper.findAll('.label-filter-menu-item');
+ const findFirstLabelCheckbox = () => findAllLabelItems().at(0).findComponent(GlFormCheckbox);
+ const findFirstLabelTitle = () => findAllLabelItems().at(0).findComponent('.label-title');
+ const findFirstLabelColor = () =>
+ findAllLabelItems().at(0).findComponent('[data-testid="label-color-indicator"]');
+
+ describe('Renders correctly', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders items', () => {
+ expect(findAllLabelItems().exists()).toBe(true);
+ expect(findAllLabelItems()).toHaveLength(defaultProps.labels.length);
+ });
+
+ it('renders items checkbox', () => {
+ expect(findFirstLabelCheckbox().exists()).toBe(true);
+ });
+
+ it('renders label title', () => {
+ expect(findFirstLabelTitle().exists()).toBe(true);
+ expect(findFirstLabelTitle().text()).toBe(defaultProps.labels[0].title);
+ });
+
+ it('renders label color', () => {
+ expect(findFirstLabelColor().exists()).toBe(true);
+ expect(findFirstLabelColor().attributes('style')).toBe(
+ `background-color: ${defaultProps.labels[0].color};`,
+ );
+ });
+ });
+});
diff --git a/spec/frontend/search/sidebar/components/label_filter_spec.js b/spec/frontend/search/sidebar/components/label_filter_spec.js
new file mode 100644
index 00000000000..c5df374d4ef
--- /dev/null
+++ b/spec/frontend/search/sidebar/components/label_filter_spec.js
@@ -0,0 +1,322 @@
+import {
+ GlAlert,
+ GlLoadingIcon,
+ GlSearchBoxByType,
+ GlLabel,
+ GlDropdownForm,
+ GlFormCheckboxGroup,
+ GlDropdownSectionHeader,
+ GlDropdownDivider,
+} from '@gitlab/ui';
+import Vue from 'vue';
+import Vuex from 'vuex';
+import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
+import { MOCK_QUERY, MOCK_LABEL_AGGREGATIONS } from 'jest/search/mock_data';
+import LabelFilter from '~/search/sidebar/components/label_filter/index.vue';
+import LabelDropdownItems from '~/search/sidebar/components/label_filter/label_dropdown_items.vue';
+
+import * as actions from '~/search/store/actions';
+import * as getters from '~/search/store/getters';
+import mutations from '~/search/store/mutations';
+import createState from '~/search/store/state';
+
+import {
+ TRACKING_LABEL_FILTER,
+ TRACKING_LABEL_DROPDOWN,
+ TRACKING_LABEL_CHECKBOX,
+ TRACKING_ACTION_SELECT,
+ TRACKING_ACTION_SHOW,
+} from '~/search/sidebar/components/label_filter/tracking';
+
+import { labelFilterData } from '~/search/sidebar/components/label_filter/data';
+
+import {
+ RECEIVE_AGGREGATIONS_SUCCESS,
+ REQUEST_AGGREGATIONS,
+ RECEIVE_AGGREGATIONS_ERROR,
+} from '~/search/store/mutation_types';
+
+Vue.use(Vuex);
+
+const actionSpies = {
+ fetchAllAggregation: jest.fn(),
+ setQuery: jest.fn(),
+ closeLabel: jest.fn(),
+ setLabelFilterSearch: jest.fn(),
+};
+
+describe('GlobalSearchSidebarLabelFilter', () => {
+ let wrapper;
+ let trackingSpy;
+ let config;
+ let store;
+
+ const createComponent = (initialState) => {
+ config = {
+ actions: {
+ ...actions,
+ fetchAllAggregation: actionSpies.fetchAllAggregation,
+ closeLabel: actionSpies.closeLabel,
+ setLabelFilterSearch: actionSpies.setLabelFilterSearch,
+ setQuery: actionSpies.setQuery,
+ },
+ getters,
+ mutations,
+ state: createState({
+ query: MOCK_QUERY,
+ aggregations: MOCK_LABEL_AGGREGATIONS,
+ ...initialState,
+ }),
+ };
+
+ store = new Vuex.Store(config);
+
+ wrapper = mountExtended(LabelFilter, {
+ store,
+ provide: {
+ glFeatures: {
+ searchIssueLabelAggregation: true,
+ },
+ },
+ });
+ };
+
+ const findComponentTitle = () => wrapper.findComponentByTestId('label-filter-title');
+ const findAllSelectedLabelsAbove = () => wrapper.findAllComponents(GlLabel);
+ const findSearchBox = () => wrapper.findComponent(GlSearchBoxByType);
+ const findDropdownForm = () => wrapper.findComponent(GlDropdownForm);
+ const findCheckboxGroup = () => wrapper.findComponent(GlFormCheckboxGroup);
+ const findDropdownSectionHeader = () => wrapper.findComponent(GlDropdownSectionHeader);
+ const findDivider = () => wrapper.findComponent(GlDropdownDivider);
+ const findCheckboxFilter = () => wrapper.findAllComponents(LabelDropdownItems);
+ const findAlert = () => wrapper.findComponent(GlAlert);
+ const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
+
+ describe('Renders correctly closed', () => {
+ beforeEach(async () => {
+ createComponent();
+ store.commit(RECEIVE_AGGREGATIONS_SUCCESS, MOCK_LABEL_AGGREGATIONS.data);
+
+ await Vue.nextTick();
+ });
+
+ it('renders component title', () => {
+ expect(findComponentTitle().exists()).toBe(true);
+ });
+
+ it('renders selected labels above search box', () => {
+ expect(findAllSelectedLabelsAbove().exists()).toBe(true);
+ expect(findAllSelectedLabelsAbove()).toHaveLength(2);
+ });
+
+ it('renders search box', () => {
+ expect(findSearchBox().exists()).toBe(true);
+ });
+
+ it("doesn't render dropdown form", () => {
+ expect(findDropdownForm().exists()).toBe(false);
+ });
+
+ it("doesn't render checkbox group", () => {
+ expect(findCheckboxGroup().exists()).toBe(false);
+ });
+
+ it("doesn't render dropdown section header", () => {
+ expect(findDropdownSectionHeader().exists()).toBe(false);
+ });
+
+ it("doesn't render divider", () => {
+ expect(findDivider().exists()).toBe(false);
+ });
+
+ it("doesn't render checkbox filter", () => {
+ expect(findCheckboxFilter().exists()).toBe(false);
+ });
+
+ it("doesn't render alert", () => {
+ expect(findAlert().exists()).toBe(false);
+ });
+
+ it("doesn't render loading icon", () => {
+ expect(findLoadingIcon().exists()).toBe(false);
+ });
+ });
+
+ describe('Renders correctly opened', () => {
+ beforeEach(async () => {
+ createComponent();
+ store.commit(RECEIVE_AGGREGATIONS_SUCCESS, MOCK_LABEL_AGGREGATIONS.data);
+
+ await Vue.nextTick();
+ trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
+ findSearchBox().vm.$emit('focusin');
+ });
+
+ afterEach(() => {
+ unmockTracking();
+ });
+
+ it('renders component title', () => {
+ expect(findComponentTitle().exists()).toBe(true);
+ });
+
+ it('renders selected labels above search box', () => {
+ // default data need to provide at least two selected labels
+ expect(findAllSelectedLabelsAbove().exists()).toBe(true);
+ expect(findAllSelectedLabelsAbove()).toHaveLength(2);
+ });
+
+ it('renders search box', () => {
+ expect(findSearchBox().exists()).toBe(true);
+ });
+
+ it('renders dropdown form', () => {
+ expect(findDropdownForm().exists()).toBe(true);
+ });
+
+ it('renders checkbox group', () => {
+ expect(findCheckboxGroup().exists()).toBe(true);
+ });
+
+ it('renders dropdown section header', () => {
+ expect(findDropdownSectionHeader().exists()).toBe(true);
+ });
+
+ it('renders divider', () => {
+ expect(findDivider().exists()).toBe(true);
+ });
+
+ it('renders checkbox filter', () => {
+ expect(findCheckboxFilter().exists()).toBe(true);
+ });
+
+ it("doesn't render alert", () => {
+ expect(findAlert().exists()).toBe(false);
+ });
+
+ it("doesn't render loading icon", () => {
+ expect(findLoadingIcon().exists()).toBe(false);
+ });
+
+ it('sends tracking information when dropdown is opened', () => {
+ expect(trackingSpy).toHaveBeenCalledWith(TRACKING_ACTION_SHOW, TRACKING_LABEL_DROPDOWN, {
+ label: TRACKING_LABEL_DROPDOWN,
+ });
+ });
+ });
+
+ describe('Renders loading state correctly', () => {
+ beforeEach(async () => {
+ createComponent();
+ store.commit(REQUEST_AGGREGATIONS);
+ await Vue.nextTick();
+
+ findSearchBox().vm.$emit('focusin');
+ });
+
+ it('renders checkbox filter', () => {
+ expect(findCheckboxFilter().exists()).toBe(false);
+ });
+
+ it("doesn't render alert", () => {
+ expect(findAlert().exists()).toBe(false);
+ });
+
+ it('renders loading icon', () => {
+ expect(findLoadingIcon().exists()).toBe(true);
+ });
+ });
+
+ describe('Renders error state correctly', () => {
+ beforeEach(async () => {
+ createComponent();
+ store.commit(RECEIVE_AGGREGATIONS_ERROR);
+ await Vue.nextTick();
+
+ findSearchBox().vm.$emit('focusin');
+ });
+
+ it("doesn't render checkbox filter", () => {
+ expect(findCheckboxFilter().exists()).toBe(false);
+ });
+
+ it('renders alert', () => {
+ expect(findAlert().exists()).toBe(true);
+ });
+
+ it("doesn't render loading icon", () => {
+ expect(findLoadingIcon().exists()).toBe(false);
+ });
+ });
+
+ describe('Actions', () => {
+ describe('dispatch action when component is created', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders checkbox filter', async () => {
+ await Vue.nextTick();
+ expect(actionSpies.fetchAllAggregation).toHaveBeenCalled();
+ });
+ });
+
+ describe('Closing label works correctly', () => {
+ beforeEach(async () => {
+ createComponent();
+ store.commit(RECEIVE_AGGREGATIONS_SUCCESS, MOCK_LABEL_AGGREGATIONS.data);
+ await Vue.nextTick();
+ });
+
+ it('renders checkbox filter', async () => {
+ await findAllSelectedLabelsAbove().at(0).find('.btn-reset').trigger('click');
+ expect(actionSpies.closeLabel).toHaveBeenCalled();
+ });
+ });
+
+ describe('label search input box works properly', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders checkbox filter', () => {
+ findSearchBox().find('input').setValue('test');
+ expect(actionSpies.setLabelFilterSearch).toHaveBeenCalledWith(
+ expect.anything(),
+ expect.objectContaining({
+ value: 'test',
+ }),
+ );
+ });
+ });
+
+ describe('dropdown checkboxes work', () => {
+ beforeEach(async () => {
+ createComponent();
+
+ await findSearchBox().vm.$emit('focusin');
+ await Vue.nextTick();
+
+ trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
+
+ await findCheckboxGroup().vm.$emit('input', 6);
+ await Vue.nextTick();
+ });
+
+ it('trigger event', () => {
+ expect(actionSpies.setQuery).toHaveBeenCalledWith(
+ expect.anything(),
+ expect.objectContaining({ key: labelFilterData?.filterParam, value: 6 }),
+ );
+ });
+
+ it('sends tracking information when checkbox is selected', () => {
+ expect(trackingSpy).toHaveBeenCalledWith(TRACKING_ACTION_SELECT, TRACKING_LABEL_CHECKBOX, {
+ label: TRACKING_LABEL_FILTER,
+ property: 6,
+ });
+ });
+ });
+ });
+});
diff --git a/spec/frontend/search/store/actions_spec.js b/spec/frontend/search/store/actions_spec.js
index 18f7e1ce21c..2051e731647 100644
--- a/spec/frontend/search/store/actions_spec.js
+++ b/spec/frontend/search/store/actions_spec.js
@@ -133,7 +133,7 @@ describe('Global Search Store Actions', () => {
describe('when groupId is set', () => {
it('calls Api.groupProjects with expected parameters', () => {
- actions.fetchProjects({ commit: mockCommit, state }, undefined);
+ actions.fetchProjects({ commit: mockCommit, state }, MOCK_QUERY.search);
expect(Api.groupProjects).toHaveBeenCalledWith(state.query.group_id, state.query.search, {
order_by: 'similarity',
include_subgroups: true,
diff --git a/spec/frontend/search/store/getters_spec.js b/spec/frontend/search/store/getters_spec.js
index 51692cb1ab4..772acb39a57 100644
--- a/spec/frontend/search/store/getters_spec.js
+++ b/spec/frontend/search/store/getters_spec.js
@@ -33,10 +33,6 @@ describe('Global Search Store Getters', () => {
useMockLocationHelper();
});
- afterEach(() => {
- state = cloneDeep(defaultState);
- });
-
describe('frequentGroups', () => {
it('returns the correct data', () => {
state.frequentItems[GROUPS_LOCAL_STORAGE_KEY] = MOCK_GROUPS;
diff --git a/spec/frontend/super_sidebar/components/global_search/command_palette/__snapshots__/command_autocomplete_item_spec.js.snap b/spec/frontend/super_sidebar/components/global_search/command_palette/__snapshots__/command_autocomplete_item_spec.js.snap
deleted file mode 100644
index 3c52cc195db..00000000000
--- a/spec/frontend/super_sidebar/components/global_search/command_palette/__snapshots__/command_autocomplete_item_spec.js.snap
+++ /dev/null
@@ -1,19 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`CommandAutocompleteItem should render user item 1`] = `
-<div
- class="gl-display-flex gl-align-items-center"
->
- <gl-icon-stub
- class="gl-mr-3"
- name="users"
- size="16"
- />
-
- <span
- class="gl-text-gray-900"
- >
- Manage &gt; Activity
- </span>
-</div>
-`;
diff --git a/spec/frontend/super_sidebar/components/global_search/command_palette/__snapshots__/search_item_spec.js.snap b/spec/frontend/super_sidebar/components/global_search/command_palette/__snapshots__/search_item_spec.js.snap
new file mode 100644
index 00000000000..d16d137db2f
--- /dev/null
+++ b/spec/frontend/super_sidebar/components/global_search/command_palette/__snapshots__/search_item_spec.js.snap
@@ -0,0 +1,122 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`SearchItem should render the item 1`] = `
+<div
+ class="gl-display-flex gl-align-items-center"
+>
+ <gl-avatar-stub
+ alt="avatar"
+ aria-hidden="true"
+ class="gl-mr-3"
+ entityid="37"
+ entityname=""
+ shape="rect"
+ size="16"
+ src="https://www.gravatar.com/avatar/a9638f4ec70148d51e56bf05ad41e993?s=80&d=identicon"
+ />
+
+ <!---->
+
+ <span
+ class="gl-display-flex gl-flex-direction-column"
+ >
+ <span
+ class="gl-text-gray-900"
+ />
+
+ <!---->
+ </span>
+</div>
+`;
+
+exports[`SearchItem should render the item 2`] = `
+<div
+ class="gl-display-flex gl-align-items-center"
+>
+ <!---->
+
+ <gl-icon-stub
+ class="gl-mr-3"
+ name="users"
+ size="16"
+ />
+
+ <span
+ class="gl-display-flex gl-flex-direction-column"
+ >
+ <span
+ class="gl-text-gray-900"
+ >
+ Manage &gt; Activity
+ </span>
+
+ <!---->
+ </span>
+</div>
+`;
+
+exports[`SearchItem should render the item 3`] = `
+<div
+ class="gl-display-flex gl-align-items-center"
+>
+ <gl-avatar-stub
+ alt="avatar"
+ aria-hidden="true"
+ class="gl-mr-3"
+ entityid="1"
+ entityname="MockProject1"
+ shape="rect"
+ size="32"
+ src="/project/avatar/1/avatar.png"
+ />
+
+ <!---->
+
+ <span
+ class="gl-display-flex gl-flex-direction-column"
+ >
+ <span
+ class="gl-text-gray-900"
+ >
+ MockProject1
+ </span>
+
+ <span
+ class="gl-font-sm gl-text-gray-500"
+ >
+ Gitlab Org / MockProject1
+ </span>
+ </span>
+</div>
+`;
+
+exports[`SearchItem should render the item 4`] = `
+<div
+ class="gl-display-flex gl-align-items-center"
+>
+ <gl-avatar-stub
+ alt="avatar"
+ aria-hidden="true"
+ class="gl-mr-3"
+ entityid="7"
+ entityname="Flight"
+ shape="rect"
+ size="16"
+ src=""
+ />
+
+ <!---->
+
+ <span
+ class="gl-display-flex gl-flex-direction-column"
+ >
+ <span
+ class="gl-text-gray-900"
+ >
+ Dismiss Cipher with no integrity
+ </span>
+
+ <!---->
+ </span>
+</div>
+`;
diff --git a/spec/frontend/super_sidebar/components/global_search/command_palette/__snapshots__/user_autocomplete_item_spec.js.snap b/spec/frontend/super_sidebar/components/global_search/command_palette/__snapshots__/user_autocomplete_item_spec.js.snap
deleted file mode 100644
index 431cdce2955..00000000000
--- a/spec/frontend/super_sidebar/components/global_search/command_palette/__snapshots__/user_autocomplete_item_spec.js.snap
+++ /dev/null
@@ -1,34 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`UserAutocompleteItem should render user item 1`] = `
-<div
- class="gl-display-flex gl-align-items-center"
->
- <gl-avatar-stub
- alt="avatar"
- aria-hidden="true"
- class="gl-mr-3"
- entityid="37"
- entityname="Cole Dickinson"
- shape="rect"
- size="16"
- src="https://www.gravatar.com/avatar/a9638f4ec70148d51e56bf05ad41e993?s=80&d=identicon"
- />
-
- <span
- class="gl-display-flex gl-flex-direction-column"
- >
- <span
- class="gl-text-gray-900"
- >
- Cole Dickinson
- </span>
-
- <span
- class="gl-font-sm gl-text-gray-500"
- >
- reported_user_14
- </span>
- </span>
-</div>
-`;
diff --git a/spec/frontend/super_sidebar/components/global_search/command_palette/command_autocomplete_item_spec.js b/spec/frontend/super_sidebar/components/global_search/command_palette/command_autocomplete_item_spec.js
deleted file mode 100644
index 2597812acaf..00000000000
--- a/spec/frontend/super_sidebar/components/global_search/command_palette/command_autocomplete_item_spec.js
+++ /dev/null
@@ -1,25 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import CommandAutocompleteItem from '~/super_sidebar/components/global_search/command_palette/command_autocomplete_item.vue';
-import { linksReducer } from '~/super_sidebar/components/global_search/command_palette/utils';
-import { LINKS } from './mock_data';
-
-describe('CommandAutocompleteItem', () => {
- let wrapper;
-
- const createComponent = () => {
- wrapper = shallowMount(CommandAutocompleteItem, {
- propsData: {
- command: LINKS.reduce(linksReducer, [])[1],
- searchQuery: 'root',
- },
- });
- };
-
- beforeEach(() => {
- createComponent();
- });
-
- it('should render user item', () => {
- expect(wrapper.element).toMatchSnapshot();
- });
-});
diff --git a/spec/frontend/super_sidebar/components/global_search/command_palette/command_palette_items_spec.js b/spec/frontend/super_sidebar/components/global_search/command_palette/command_palette_items_spec.js
index bd1e0dbc15d..21d085dc0fb 100644
--- a/spec/frontend/super_sidebar/components/global_search/command_palette/command_palette_items_spec.js
+++ b/spec/frontend/super_sidebar/components/global_search/command_palette/command_palette_items_spec.js
@@ -7,11 +7,13 @@ import {
COMMAND_HANDLE,
USERS_GROUP_TITLE,
USER_HANDLE,
+ SEARCH_SCOPE,
} from '~/super_sidebar/components/global_search/command_palette/constants';
import {
- userMapper,
+ commandMapper,
linksReducer,
} from '~/super_sidebar/components/global_search/command_palette/utils';
+import { getFormattedItem } from '~/super_sidebar/components/global_search/utils';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
import waitForPromises from 'helpers/wait_for_promises';
@@ -21,6 +23,8 @@ const links = LINKS.reduce(linksReducer, []);
describe('CommandPaletteItems', () => {
let wrapper;
+ const autocompletePath = '/autocomplete';
+ const searchContext = { project: { id: 1 }, group: { id: 2 } };
const createComponent = (props) => {
wrapper = shallowMount(CommandPaletteItems, {
@@ -36,32 +40,34 @@ describe('CommandPaletteItems', () => {
provide: {
commandPaletteCommands: COMMANDS,
commandPaletteLinks: LINKS,
+ autocompletePath,
+ searchContext,
},
});
};
const findItems = () => wrapper.findAllComponents(GlDisclosureDropdownItem);
- const findGroup = () => wrapper.findComponent(GlDisclosureDropdownGroup);
+ const findGroups = () => wrapper.findAllComponents(GlDisclosureDropdownGroup);
const findLoader = () => wrapper.findComponent(GlLoadingIcon);
describe('COMMANDS & LINKS', () => {
it('renders all commands initially', () => {
createComponent();
- const commandGroup = COMMANDS[0];
+ const commandGroup = COMMANDS.map(commandMapper)[0];
expect(findItems()).toHaveLength(commandGroup.items.length);
- expect(findGroup().props('group')).toEqual({
+ expect(findGroups().at(0).props('group')).toEqual({
name: commandGroup.name,
items: commandGroup.items,
});
});
describe('with search query', () => {
- it('should filter comamnds and links by the search query', async () => {
+ it('should filter commands and links by the search query', async () => {
jest.spyOn(fuzzaldrinPlus, 'filter');
createComponent({ searchQuery: 'mr' });
const searchQuery = 'todo';
await wrapper.setProps({ searchQuery });
- const commandGroup = COMMANDS[0];
+ const commandGroup = COMMANDS.map(commandMapper)[0];
expect(fuzzaldrinPlus.filter).toHaveBeenCalledWith(
commandGroup.items,
searchQuery,
@@ -84,14 +90,14 @@ describe('CommandPaletteItems', () => {
});
});
- describe('USERS', () => {
+ describe('USERS, ISSUES, PROJECTS', () => {
let mockAxios;
beforeEach(() => {
mockAxios = new MockAdapter(axios);
});
- it('should NOT start search for users by the search query which is less than 3 chars', () => {
+ it('should NOT start search by the search query which is less than 3 chars', () => {
jest.spyOn(axios, 'get');
const searchQuery = 'us';
createComponent({ handle: USER_HANDLE, searchQuery });
@@ -101,24 +107,18 @@ describe('CommandPaletteItems', () => {
expect(findLoader().exists()).toBe(false);
});
- it('should start search for users by the search query with 3+ chars and display a loader', () => {
+ it('should start scoped search with 3+ chars and display a loader', () => {
jest.spyOn(axios, 'get');
const searchQuery = 'user';
createComponent({ handle: USER_HANDLE, searchQuery });
expect(axios.get).toHaveBeenCalledWith(
- expect.any(String),
- expect.objectContaining({
- params: {
- search: searchQuery,
- },
- }),
+ `${autocompletePath}?term=${searchQuery}&project_id=${searchContext.project.id}&filter=search&scope=${SEARCH_SCOPE[USER_HANDLE]}`,
);
-
expect(findLoader().exists()).toBe(true);
});
- it('should render returned users', async () => {
+ it('should render returned items', async () => {
mockAxios.onGet().replyOnce(HTTP_STATUS_OK, USERS);
const searchQuery = 'user';
@@ -126,9 +126,9 @@ describe('CommandPaletteItems', () => {
await waitForPromises();
expect(findItems()).toHaveLength(USERS.length);
- expect(findGroup().props('group')).toEqual({
+ expect(findGroups().at(0).props('group')).toMatchObject({
name: USERS_GROUP_TITLE,
- items: USERS.map(userMapper),
+ items: USERS.map(getFormattedItem),
});
});
diff --git a/spec/frontend/super_sidebar/components/global_search/command_palette/fake_search_input_spec.js b/spec/frontend/super_sidebar/components/global_search/command_palette/fake_search_input_spec.js
index 0aeb4c89d06..a8e91395303 100644
--- a/spec/frontend/super_sidebar/components/global_search/command_palette/fake_search_input_spec.js
+++ b/spec/frontend/super_sidebar/components/global_search/command_palette/fake_search_input_spec.js
@@ -1,8 +1,9 @@
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import FakeSearchInput from '~/super_sidebar/components/global_search/command_palette/fake_search_input.vue';
import {
+ SEARCH_SCOPE_PLACEHOLDER,
+ COMMON_HANDLES,
COMMAND_HANDLE,
- SEARCH_SCOPE,
} from '~/super_sidebar/components/global_search/command_palette/constants';
describe('FakeSearchInput', () => {
@@ -27,10 +28,13 @@ describe('FakeSearchInput', () => {
});
describe('placeholder', () => {
- it('should render the placeholder for its search scope when there is no user input', () => {
- createComponent();
- expect(findSearchScopePlaceholder().text()).toBe(SEARCH_SCOPE[COMMAND_HANDLE]);
- });
+ it.each(COMMON_HANDLES)(
+ 'should render the placeholder for the %s scope when there is no user input',
+ (scope) => {
+ createComponent({ scope });
+ expect(findSearchScopePlaceholder().text()).toBe(SEARCH_SCOPE_PLACEHOLDER[scope]);
+ },
+ );
it('should NOT render the placeholder when there is user input', () => {
createComponent({ userInput: 'todo' });
diff --git a/spec/frontend/super_sidebar/components/global_search/command_palette/mock_data.js b/spec/frontend/super_sidebar/components/global_search/command_palette/mock_data.js
index 726339f56a1..ec65a43d549 100644
--- a/spec/frontend/super_sidebar/components/global_search/command_palette/mock_data.js
+++ b/spec/frontend/super_sidebar/components/global_search/command_palette/mock_data.js
@@ -5,17 +5,19 @@ export const COMMANDS = [
{
text: 'New project/repository',
href: '/projects/new',
- keywords: ['new', 'project', 'repository'],
},
{
text: 'New group',
href: '/groups/new',
- keywords: ['new', 'group'],
},
{
text: 'New snippet',
href: '/-/snippets/new',
- keywords: ['new', 'snippet'],
+ },
+ {
+ text: 'Invite members',
+ href: '/-/snippets/new',
+ component: 'invite_members',
},
],
},
@@ -61,6 +63,33 @@ export const LINKS = [
},
];
+export const TRANSFORMED_LINKS = [
+ {
+ href: '/flightjs/Flight/activity',
+ icon: 'users',
+ keywords: 'Manage',
+ text: 'Manage',
+ },
+ {
+ href: '/flightjs/Flight/activity',
+ icon: 'users',
+ keywords: 'Activity',
+ text: 'Manage > Activity',
+ },
+ {
+ href: '/flightjs/Flight/-/project_members',
+ icon: 'users',
+ keywords: 'Members',
+ text: 'Manage > Members',
+ },
+ {
+ href: '/flightjs/Flight/-/labels',
+ icon: 'users',
+ keywords: 'Labels',
+ text: 'Manage > Labels',
+ },
+];
+
export const USERS = [
{
id: 37,
@@ -83,3 +112,22 @@ export const USERS = [
web_url: 'http://127.0.0.1:3000/reported_user_7',
},
];
+
+export const PROJECT = {
+ category: 'Projects',
+ id: 1,
+ label: 'Gitlab Org / MockProject1',
+ value: 'MockProject1',
+ url: 'project/1',
+ avatar_url: '/project/avatar/1/avatar.png',
+};
+
+export const ISSUE = {
+ avatar_url: '',
+ category: 'Recent issues',
+ id: 516,
+ label: 'Dismiss Cipher with no integrity',
+ project_id: 7,
+ project_name: 'Flight',
+ url: '/flightjs/Flight/-/issues/37',
+};
diff --git a/spec/frontend/super_sidebar/components/global_search/command_palette/search_item_spec.js b/spec/frontend/super_sidebar/components/global_search/command_palette/search_item_spec.js
new file mode 100644
index 00000000000..c7e49310588
--- /dev/null
+++ b/spec/frontend/super_sidebar/components/global_search/command_palette/search_item_spec.js
@@ -0,0 +1,33 @@
+import { shallowMount } from '@vue/test-utils';
+import SearchItem from '~/super_sidebar/components/global_search/command_palette/search_item.vue';
+import { getFormattedItem } from '~/super_sidebar/components/global_search/utils';
+import { linksReducer } from '~/super_sidebar/components/global_search/command_palette/utils';
+import { USERS, LINKS, PROJECT, ISSUE } from './mock_data';
+
+jest.mock('~/lib/utils/highlight', () => ({
+ __esModule: true,
+ default: (text) => text,
+}));
+const mockUser = getFormattedItem(USERS[0]);
+const mockCommand = LINKS.reduce(linksReducer, [])[1];
+const mockProject = getFormattedItem(PROJECT);
+const mockIssue = getFormattedItem(ISSUE);
+
+describe('SearchItem', () => {
+ let wrapper;
+
+ const createComponent = (item) => {
+ wrapper = shallowMount(SearchItem, {
+ propsData: {
+ item,
+ searchQuery: 'root',
+ },
+ });
+ };
+
+ it.each([mockUser, mockCommand, mockProject, mockIssue])('should render the item', (item) => {
+ createComponent(item);
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+});
diff --git a/spec/frontend/super_sidebar/components/global_search/command_palette/user_autocomplete_item_spec.js b/spec/frontend/super_sidebar/components/global_search/command_palette/user_autocomplete_item_spec.js
deleted file mode 100644
index 5abb56228a4..00000000000
--- a/spec/frontend/super_sidebar/components/global_search/command_palette/user_autocomplete_item_spec.js
+++ /dev/null
@@ -1,25 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import UserAutocompleteItem from '~/super_sidebar/components/global_search/command_palette/user_autocomplete_item.vue';
-import { userMapper } from '~/super_sidebar/components/global_search/command_palette/utils';
-import { USERS } from './mock_data';
-
-describe('UserAutocompleteItem', () => {
- let wrapper;
-
- const createComponent = () => {
- wrapper = shallowMount(UserAutocompleteItem, {
- propsData: {
- user: USERS.map(userMapper)[0],
- searchQuery: 'root',
- },
- });
- };
-
- beforeEach(() => {
- createComponent();
- });
-
- it('should render user item', () => {
- expect(wrapper.element).toMatchSnapshot();
- });
-});
diff --git a/spec/frontend/super_sidebar/components/global_search/command_palette/utils_spec.js b/spec/frontend/super_sidebar/components/global_search/command_palette/utils_spec.js
index 74a5247add5..0b75787723e 100644
--- a/spec/frontend/super_sidebar/components/global_search/command_palette/utils_spec.js
+++ b/spec/frontend/super_sidebar/components/global_search/command_palette/utils_spec.js
@@ -1,15 +1,18 @@
-import { userMapper } from '~/super_sidebar/components/global_search/command_palette/utils';
-import { USERS } from './mock_data';
+import {
+ commandMapper,
+ linksReducer,
+} from '~/super_sidebar/components/global_search/command_palette/utils';
+import { COMMANDS, LINKS, TRANSFORMED_LINKS } from './mock_data';
-describe('userMapper', () => {
- it('should transform users response', () => {
- const user = USERS[0];
- expect(userMapper(user)).toEqual({
- id: user.id,
- username: user.username,
- text: user.name,
- href: user.web_url,
- avatar_url: user.avatar_url,
- });
+describe('linksReducer', () => {
+ it('should transform links', () => {
+ expect(LINKS.reduce(linksReducer, [])).toEqual(TRANSFORMED_LINKS);
+ });
+});
+
+describe('commandMapper', () => {
+ it('should temporarily remove the `invite_members` item', () => {
+ const initialCommandsLength = COMMANDS[0].items.length;
+ expect(COMMANDS.map(commandMapper)[0].items).toHaveLength(initialCommandsLength - 1);
});
});
diff --git a/spec/frontend/super_sidebar/components/global_search/components/global_search_spec.js b/spec/frontend/super_sidebar/components/global_search/components/global_search_spec.js
index 0a6d5919207..9b7b9e288df 100644
--- a/spec/frontend/super_sidebar/components/global_search/components/global_search_spec.js
+++ b/spec/frontend/super_sidebar/components/global_search/components/global_search_spec.js
@@ -11,8 +11,7 @@ import FakeSearchInput from '~/super_sidebar/components/global_search/command_pa
import CommandPaletteItems from '~/super_sidebar/components/global_search/command_palette/command_palette_items.vue';
import {
SEARCH_OR_COMMAND_MODE_PLACEHOLDER,
- COMMAND_HANDLE,
- USER_HANDLE,
+ COMMON_HANDLES,
} from '~/super_sidebar/components/global_search/command_palette/constants';
import {
SEARCH_INPUT_DESCRIPTION,
@@ -320,8 +319,8 @@ describe('GlobalSearchModal', () => {
});
});
- describe.each([COMMAND_HANDLE, USER_HANDLE])(
- 'when FF `command_palette` is enabled',
+ describe.each(COMMON_HANDLES)(
+ 'when FF `command_palette` is enabled and search handle is %s',
(handle) => {
beforeEach(() => {
createComponent({ search: handle }, undefined, undefined, {
diff --git a/spec/graphql/resolvers/namespace_projects_resolver_spec.rb b/spec/graphql/resolvers/namespace_projects_resolver_spec.rb
index 5974bf2a7b4..3ae19078c30 100644
--- a/spec/graphql/resolvers/namespace_projects_resolver_spec.rb
+++ b/spec/graphql/resolvers/namespace_projects_resolver_spec.rb
@@ -7,12 +7,14 @@ RSpec.describe Resolvers::NamespaceProjectsResolver, feature_category: :groups_a
let(:current_user) { create(:user) }
let(:include_subgroups) { true }
+ let(:not_aimed_for_deletion) { false }
let(:sort) { nil }
let(:search) { nil }
let(:ids) { nil }
let(:args) do
{
include_subgroups: include_subgroups,
+ not_aimed_for_deletion: not_aimed_for_deletion,
sort: sort,
search: search,
ids: ids
@@ -24,21 +26,37 @@ RSpec.describe Resolvers::NamespaceProjectsResolver, feature_category: :groups_a
let(:namespace) { group }
let(:project1) { create(:project, namespace: namespace) }
let(:project2) { create(:project, namespace: namespace) }
+ let(:project3) { create(:project, namespace: namespace, marked_for_deletion_at: 1.day.ago, pending_delete: true) }
let(:nested_group) { create(:group, parent: group) }
let(:nested_project) { create(:project, group: nested_group) }
+ let(:nested_project2) { create(:project, group: nested_group, marked_for_deletion_at: 1.day.ago, pending_delete: true) }
before do
project1.add_developer(current_user)
project2.add_developer(current_user)
+ project3.add_developer(current_user)
nested_project.add_developer(current_user)
+ nested_project2.add_developer(current_user)
end
describe '#resolve' do
it 'finds all projects' do
- expect(resolve_projects).to contain_exactly(project1, project2)
+ expect(resolve_projects).to contain_exactly(project1, project2, project3)
end
it 'finds all projects including the subgroups' do
+ expect(resolve_projects(args)).to contain_exactly(project1, project2, project3, nested_project, nested_project2)
+ end
+
+ it 'finds all projects not aimed for deletion' do
+ arg = { not_aimed_for_deletion: true }
+
+ expect(resolve_projects(arg)).to contain_exactly(project1, project2)
+ end
+
+ it 'finds all projects not aimed for deletion including the subgroups' do
+ args[:not_aimed_for_deletion] = true
+
expect(resolve_projects(args)).to contain_exactly(project1, project2, nested_project)
end
@@ -46,11 +64,11 @@ RSpec.describe Resolvers::NamespaceProjectsResolver, feature_category: :groups_a
let(:namespace) { current_user.namespace }
it 'finds all projects' do
- expect(resolve_projects).to contain_exactly(project1, project2)
+ expect(resolve_projects).to contain_exactly(project1, project2, project3)
end
it 'finds all projects including the subgroups' do
- expect(resolve_projects(args)).to contain_exactly(project1, project2)
+ expect(resolve_projects(args)).to contain_exactly(project1, project2, project3)
end
end
end
@@ -112,13 +130,13 @@ RSpec.describe Resolvers::NamespaceProjectsResolver, feature_category: :groups_a
subject(:projects) { resolve_projects(args) }
let(:include_subgroups) { false }
- let!(:project_3) { create(:project, name: 'Project', path: 'project', namespace: namespace) }
+ let!(:project_4) { create(:project, name: 'Project', path: 'project', namespace: namespace) }
context 'when ids is provided' do
- let(:ids) { [project_3.to_global_id.to_s] }
+ let(:ids) { [project_4.to_global_id.to_s] }
it 'returns matching project' do
- expect(projects).to contain_exactly(project_3)
+ expect(projects).to contain_exactly(project_4)
end
end
@@ -126,7 +144,7 @@ RSpec.describe Resolvers::NamespaceProjectsResolver, feature_category: :groups_a
let(:ids) { nil }
it 'returns all projects' do
- expect(projects).to contain_exactly(project1, project2, project_3)
+ expect(projects).to contain_exactly(project1, project2, project3, project_4)
end
end
end
diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb
index 99ef8a8de08..c168572ab4c 100644
--- a/spec/helpers/search_helper_spec.rb
+++ b/spec/helpers/search_helper_spec.rb
@@ -339,6 +339,17 @@ RSpec.describe SearchHelper, feature_category: :global_search do
})
end
end
+
+ context 'with a search scope' do
+ let(:term) { 'bla' }
+ let(:scope) { 'project' }
+
+ it 'returns scoped resource results' do
+ expect(self).to receive(:resource_results).with(term, scope: scope).and_return([])
+
+ search_autocomplete_opts(term, filter: :search, scope: scope)
+ end
+ end
end
end
@@ -408,6 +419,57 @@ RSpec.describe SearchHelper, feature_category: :global_search do
expect(results.first[:category]).to eq(category) if size == 1
end
end
+
+ context 'with a search scope' do
+ let(:term) { 'bla' }
+ let(:scope) { 'project' }
+
+ it 'returns only scope-specific results' do
+ expect(self).to receive(:scope_specific_results).with(term, scope).and_return([])
+ expect(self).not_to receive(:groups_autocomplete)
+ expect(self).not_to receive(:projects_autocomplete)
+ expect(self).not_to receive(:users_autocomplete)
+ expect(self).not_to receive(:issue_autocomplete)
+
+ resource_results(term, scope: scope)
+ end
+ end
+ end
+
+ describe 'scope_specific_results' do
+ using RSpec::Parameterized::TableSyntax
+
+ let_it_be(:user) { create(:user, name: 'Searched') }
+ let_it_be(:project) { create(:project, name: 'Searched') }
+ let_it_be(:issue) { create(:issue, title: 'Searched', project: project) }
+
+ before do
+ allow(self).to receive(:current_user).and_return(user)
+ allow_next_instance_of(Gitlab::Search::RecentIssues) do |recent_issues|
+ allow(recent_issues).to receive(:search).and_return([issue])
+ end
+ project.add_developer(user)
+ end
+
+ where(:scope, :category) do
+ 'user' | 'Users'
+ 'project' | 'Projects'
+ 'issue' | 'Recent issues'
+ end
+
+ with_them do
+ it 'returns results only for the specific scope' do
+ results = scope_specific_results('sea', scope)
+ expect(results.size).to eq(1)
+ expect(results.first[:category]).to eq(category)
+ end
+ end
+
+ context 'when scope is unknown' do
+ it 'does not return any results' do
+ expect(scope_specific_results('sea', 'other')).to eq([])
+ end
+ end
end
describe 'projects_autocomplete' do
diff --git a/spec/lib/gitlab/usage/metrics/instrumentations/count_all_ci_builds_metric_spec.rb b/spec/lib/gitlab/usage/metrics/instrumentations/count_all_ci_builds_metric_spec.rb
new file mode 100644
index 00000000000..93814436395
--- /dev/null
+++ b/spec/lib/gitlab/usage/metrics/instrumentations/count_all_ci_builds_metric_spec.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Usage::Metrics::Instrumentations::CountAllCiBuildsMetric, feature_category: :continuous_integration do
+ before do
+ create(:ci_build, created_at: 5.days.ago)
+ create(:ci_build, created_at: 1.year.ago)
+ end
+
+ it_behaves_like 'a correct instrumented metric value', { time_frame: 'all', data_source: 'database' } do
+ let(:expected_value) { 2 }
+ end
+
+ it_behaves_like 'a correct instrumented metric value', { time_frame: '28d', data_source: 'database' } do
+ let(:expected_value) { 1 }
+ end
+end
diff --git a/spec/models/concerns/spammable_spec.rb b/spec/models/concerns/spammable_spec.rb
index e80b202f3c9..a18a4b067dd 100644
--- a/spec/models/concerns/spammable_spec.rb
+++ b/spec/models/concerns/spammable_spec.rb
@@ -3,6 +3,22 @@
require 'spec_helper'
RSpec.describe Spammable, feature_category: :instance_resiliency do
+ before do
+ stub_const('SpammableModel', Class.new(ActiveRecord::Base))
+
+ SpammableModel.class_eval do
+ self.table_name = 'issues'
+
+ include Spammable
+
+ attr_accessor :other_attr
+
+ attr_spammable :title, spam_title: true
+ attr_spammable :description, spam_description: true
+ end
+ end
+
+ let(:spammable_model) { SpammableModel.new }
let(:issue) { create(:issue, description: 'Test Desc.') }
describe 'Associations' do
@@ -26,16 +42,14 @@ RSpec.describe Spammable, feature_category: :instance_resiliency do
describe '#check_for_spam?' do
context 'when not overriden' do
- let(:merge_request) { create(:merge_request) }
-
- subject { merge_request.check_for_spam? }
+ subject { spammable_model.check_for_spam? }
context 'when spammable attributes have changed' do
where(attr: [:title, :description])
with_them do
before do
- merge_request.assign_attributes(attr => 'x')
+ spammable_model.assign_attributes(attr => 'x')
end
it { is_expected.to eq(true) }
@@ -44,7 +58,7 @@ RSpec.describe Spammable, feature_category: :instance_resiliency do
context 'when other attributes have changed' do
before do
- merge_request.draft = true
+ spammable_model.other_attr = true
end
it { is_expected.to eq(false) }
@@ -68,15 +82,6 @@ RSpec.describe Spammable, feature_category: :instance_resiliency do
end
context 'when the model is spam' do
- before do
- stub_const('SpammableModel', Class.new(ApplicationRecord))
-
- SpammableModel.class_eval do
- include Spammable
- self.table_name = 'issues'
- end
- end
-
where(model: [:issue, :merge_request, :snippet, :spammable_model])
with_them do
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index f0ea8db9e81..2a35efed8e0 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -5673,4 +5673,31 @@ RSpec.describe MergeRequest, factory_default: :keep, feature_category: :code_rev
subject.prepare
end
end
+
+ describe '#check_for_spam?' do
+ let_it_be(:project) { create(:project, :public) }
+ let(:merge_request) { build_stubbed(:merge_request, source_project: project) }
+
+ subject { merge_request.check_for_spam? }
+
+ before do
+ merge_request.title = 'New title'
+ end
+
+ it { is_expected.to eq(true) }
+
+ context 'when project is private' do
+ let_it_be(:project) { create(:project, :private) }
+
+ it { is_expected.to eq(false) }
+ end
+
+ context 'when no spammable attribute has changed' do
+ before do
+ merge_request.title = merge_request.title_was
+ end
+
+ it { is_expected.to eq(false) }
+ end
+ end
end