diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-02-10 18:10:50 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-02-10 18:10:50 +0000 |
commit | f27a1b0faf16a83ba9c3f71f660262e368f4509a (patch) | |
tree | f87277526b25da417b30a425c51bbd81eff6639b | |
parent | ae1b3d982482280f22a907faba2c9ba02f4d1db1 (diff) | |
download | gitlab-ce-f27a1b0faf16a83ba9c3f71f660262e368f4509a.tar.gz |
Add latest changes from gitlab-org/gitlab@master
34 files changed, 401 insertions, 175 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9972d0f166b..9fa296be455 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -124,7 +124,7 @@ workflow: variables: PG_VERSION: "12" - DEFAULT_CI_IMAGE: "${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/debian-${DEBIAN_VERSION}-ruby-${RUBY_VERSION}.patched-golang-${GO_VERSION}-node-16.14-postgresql-${PG_VERSION}:rubygems-3.2-git-2.36-lfs-2.9-chrome-${CHROME_VERSION}-yarn-1.22-graphicsmagick-1.3.36" + DEFAULT_CI_IMAGE: "${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/debian-${DEBIAN_VERSION}-ruby-${RUBY_VERSION}.patched-golang-${GO_VERSION}-rust-${RUST_VERSION}-node-16.14-postgresql-${PG_VERSION}:rubygems-3.2-git-2.36-lfs-2.9-chrome-${CHROME_VERSION}-yarn-1.22-graphicsmagick-1.3.36" # We set $GITLAB_DEPENDENCY_PROXY to another variable (since it's set at the group level and has higher precedence than .gitlab-ci.yml) # so that we can override $GITLAB_DEPENDENCY_PROXY_ADDRESS in workflow rules. GITLAB_DEPENDENCY_PROXY_ADDRESS: "${GITLAB_DEPENDENCY_PROXY}" @@ -141,10 +141,11 @@ variables: GIT_SUBMODULE_STRATEGY: "none" GET_SOURCES_ATTEMPTS: "3" DEBIAN_VERSION: "bullseye" - CHROME_VERSION: "106" + CHROME_VERSION: "109" DOCKER_VERSION: "20.10.14" RUBY_VERSION: "2.7" GO_VERSION: "1.18" + RUST_VERSION: "1.65" FLAKY_RSPEC_SUITE_REPORT_PATH: rspec/flaky/report-suite.json FRONTEND_FIXTURES_MAPPING_PATH: crystalball/frontend_fixtures_mapping.json diff --git a/.rubocop_todo/layout/argument_alignment.yml b/.rubocop_todo/layout/argument_alignment.yml index 4e22c8a85a4..e658ac751e7 100644 --- a/.rubocop_todo/layout/argument_alignment.yml +++ b/.rubocop_todo/layout/argument_alignment.yml @@ -1318,7 +1318,6 @@ Layout/ArgumentAlignment: - 'ee/app/services/group_saml/saml_group_links/create_service.rb' - 'ee/app/services/iterations/create_service.rb' - 'ee/app/services/merge_trains/create_pipeline_service.rb' - - 'ee/app/services/projects/register_suggested_reviewers_project_service.rb' - 'ee/app/services/registrations/standard_namespace_create_service.rb' - 'ee/app/services/resource_events/change_weight_service.rb' - 'ee/app/services/security/findings/dismiss_service.rb' @@ -1585,7 +1584,6 @@ Layout/ArgumentAlignment: - 'ee/spec/lib/gitlab/analytics/cycle_analytics/data_collector_spec.rb' - 'ee/spec/lib/gitlab/analytics/cycle_analytics/distinct_stage_loader_spec.rb' - 'ee/spec/lib/gitlab/analytics/cycle_analytics/summary/stage_time_summary_spec.rb' - - 'ee/spec/lib/gitlab/applied_ml/suggested_reviewers/client_spec.rb' - 'ee/spec/lib/gitlab/audit/auditor_spec.rb' - 'ee/spec/lib/gitlab/auth/group_saml/identity_linker_spec.rb' - 'ee/spec/lib/gitlab/auth/ldap/access_spec.rb' @@ -2237,7 +2235,6 @@ Layout/ArgumentAlignment: - 'qa/qa/specs/features/ee/browser_ui/12_systems/geo/wiki_ssh_push_spec.rb' - 'qa/qa/specs/features/ee/browser_ui/12_systems/geo/wiki_ssh_push_to_secondary_spec.rb' - 'qa/qa/specs/features/ee/browser_ui/13_secure/merge_request_license_widget_spec.rb' - - 'qa/qa/specs/features/ee/browser_ui/14_model_ops/suggested_reviewer_spec.rb' - 'qa/qa/specs/features/ee/browser_ui/15_growth/free_trial_spec.rb' - 'qa/qa/specs/features/ee/browser_ui/1_manage/group/group_saml_enforced_sso_git_access_spec.rb' - 'qa/qa/specs/features/ee/browser_ui/1_manage/group/group_saml_non_enforced_sso_spec.rb' diff --git a/.rubocop_todo/rspec/missing_feature_category.yml b/.rubocop_todo/rspec/missing_feature_category.yml index 91b2fb06070..77004d41167 100644 --- a/.rubocop_todo/rspec/missing_feature_category.yml +++ b/.rubocop_todo/rspec/missing_feature_category.yml @@ -1489,7 +1489,6 @@ RSpec/MissingFeatureCategory: - 'ee/spec/models/license_spec.rb' - 'ee/spec/models/member_spec.rb' - 'ee/spec/models/merge_request/blocking_spec.rb' - - 'ee/spec/models/merge_request/suggested_reviewers_merge_request_spec.rb' - 'ee/spec/models/merge_request_block_spec.rb' - 'ee/spec/models/merge_request_spec.rb' - 'ee/spec/models/merge_requests/compliance_violation_spec.rb' @@ -2153,8 +2152,6 @@ RSpec/MissingFeatureCategory: - 'ee/spec/services/merge_request_approval_settings/update_service_spec.rb' - 'ee/spec/services/merge_requests/approval_service_spec.rb' - 'ee/spec/services/merge_requests/build_service_spec.rb' - - 'ee/spec/services/merge_requests/capture_suggested_reviewers_accepted_service_spec.rb' - - 'ee/spec/services/merge_requests/fetch_suggested_reviewers_service_spec.rb' - 'ee/spec/services/merge_requests/merge_service_spec.rb' - 'ee/spec/services/merge_requests/merge_to_ref_service_spec.rb' - 'ee/spec/services/merge_requests/mergeability/check_approved_service_spec.rb' @@ -2214,7 +2211,6 @@ RSpec/MissingFeatureCategory: - 'ee/spec/services/projects/operations/update_service_spec.rb' - 'ee/spec/services/projects/prometheus/alerts/notify_service_spec.rb' - 'ee/spec/services/projects/protect_default_branch_service_spec.rb' - - 'ee/spec/services/projects/register_suggested_reviewers_project_service_spec.rb' - 'ee/spec/services/projects/restore_service_spec.rb' - 'ee/spec/services/projects/setup_ci_cd_spec.rb' - 'ee/spec/services/projects/transfer_service_spec.rb' @@ -2416,7 +2412,6 @@ RSpec/MissingFeatureCategory: - 'ee/spec/views/projects/security/policies/index.html.haml_spec.rb' - 'ee/spec/views/projects/security/sast_configuration/show.html.haml_spec.rb' - 'ee/spec/views/projects/settings/merge_requests/_merge_request_approvals.html.haml_spec.rb' - - 'ee/spec/views/projects/settings/merge_requests/_suggested_reviewers_settings.html.haml_spec.rb' - 'ee/spec/views/projects/settings/subscriptions/_index.html.haml_spec.rb' - 'ee/spec/views/registrations/company/new.html.haml_spec.rb' - 'ee/spec/views/registrations/groups_projects/new.html.haml_spec.rb' @@ -2571,7 +2566,6 @@ RSpec/MissingFeatureCategory: - 'ee/spec/workers/ldap_sync_worker_spec.rb' - 'ee/spec/workers/licenses/reset_submit_license_usage_data_banner_worker_spec.rb' - 'ee/spec/workers/merge_request_reset_approvals_worker_spec.rb' - - 'ee/spec/workers/merge_requests/capture_suggested_reviewers_accepted_worker_spec.rb' - 'ee/spec/workers/merge_requests/stream_approval_audit_event_worker_spec.rb' - 'ee/spec/workers/merge_requests/sync_code_owner_approval_rules_worker_spec.rb' - 'ee/spec/workers/merge_trains/refresh_worker_spec.rb' @@ -2585,7 +2579,6 @@ RSpec/MissingFeatureCategory: - 'ee/spec/workers/project_import_schedule_worker_spec.rb' - 'ee/spec/workers/project_template_export_worker_spec.rb' - 'ee/spec/workers/projects/disable_legacy_open_source_license_for_inactive_projects_worker_spec.rb' - - 'ee/spec/workers/projects/register_suggested_reviewers_project_worker_spec.rb' - 'ee/spec/workers/refresh_license_compliance_checks_worker_spec.rb' - 'ee/spec/workers/repository_update_mirror_worker_spec.rb' - 'ee/spec/workers/requirements_management/import_requirements_csv_worker_spec.rb' diff --git a/app/assets/javascripts/confidential_merge_request/components/dropdown.vue b/app/assets/javascripts/confidential_merge_request/components/dropdown.vue index 9cb7cd9607f..c937e65abe3 100644 --- a/app/assets/javascripts/confidential_merge_request/components/dropdown.vue +++ b/app/assets/javascripts/confidential_merge_request/components/dropdown.vue @@ -1,11 +1,10 @@ <script> -import { GlDropdown, GlDropdownItem } from '@gitlab/ui'; +import { GlCollapsibleListbox } from '@gitlab/ui'; import { __ } from '~/locale'; export default { components: { - GlDropdown, - GlDropdownItem, + GlCollapsibleListbox, }, props: { projects: { @@ -19,32 +18,37 @@ export default { }, }, computed: { - dropdownText() { - if (Object.keys(this.selectedProject).length) { - return this.selectedProject.name; - } - - return __('Select private project'); + selectedProjectValue() { + return this.selectedProject?.id && String(this.selectedProject.id); + }, + toggleText() { + return this.selectedProject?.name || __('Select private project'); + }, + listboxItems() { + return this.projects.map(({ id, name }) => { + return { + value: String(id), + text: name, + }; + }); }, }, methods: { - selectProject(project) { - this.$emit('click', project); + selectProject(projectId) { + const project = this.projects.find(({ id }) => String(id) === projectId); + this.$emit('select', project); }, }, }; </script> <template> - <gl-dropdown block icon="lock" :text="dropdownText"> - <gl-dropdown-item - v-for="project in projects" - :key="project.id" - is-check-item - :is-checked="project.id === selectedProject.id" - @click="selectProject(project)" - > - {{ project.name }} - </gl-dropdown-item> - </gl-dropdown> + <gl-collapsible-listbox + icon="lock" + :items="listboxItems" + :selected="selectedProjectValue" + :toggle-text="toggleText" + block + @select="selectProject" + /> </template> diff --git a/app/assets/javascripts/confidential_merge_request/components/project_form_group.vue b/app/assets/javascripts/confidential_merge_request/components/project_form_group.vue index e95424eef4d..196f5537a90 100644 --- a/app/assets/javascripts/confidential_merge_request/components/project_form_group.vue +++ b/app/assets/javascripts/confidential_merge_request/components/project_form_group.vue @@ -114,7 +114,7 @@ export default { v-if="projects.length" :projects="projects" :selected-project="selectedProject" - @click="selectProject" + @select="selectProject" /> <p class="gl-text-gray-600 gl-mt-1 gl-mb-0"> <template v-if="projects.length"> diff --git a/app/assets/javascripts/issues/show/components/incidents/timeline_events_item.vue b/app/assets/javascripts/issues/show/components/incidents/timeline_events_item.vue index 9819fb459b3..d33f3146d64 100644 --- a/app/assets/javascripts/issues/show/components/incidents/timeline_events_item.vue +++ b/app/assets/javascripts/issues/show/components/incidents/timeline_events_item.vue @@ -42,6 +42,9 @@ export default { time() { return formatDate(this.occurredAt, 'HH:MM', true); }, + canEditEvent() { + return this.action === 'comment'; + }, }, methods: { getEventIcon, @@ -83,7 +86,7 @@ export default { category="tertiary" no-caret > - <gl-dropdown-item @click="$emit('edit')"> + <gl-dropdown-item v-if="canEditEvent" @click="$emit('edit')"> {{ $options.i18n.edit }} </gl-dropdown-item> <gl-dropdown-item @click="$emit('delete')"> diff --git a/app/finders/packages/tags_finder.rb b/app/finders/packages/tags_finder.rb index 020b3d8072a..dd104ea6f91 100644 --- a/app/finders/packages/tags_finder.rb +++ b/app/finders/packages/tags_finder.rb @@ -15,7 +15,7 @@ class Packages::TagsFinder .with_name(package_name) packages = packages.with_package_type(package_type) if package_type.present? - Packages::Tag.for_packages(packages) + Packages::Tag.for_package_ids(packages.select(:id)) end private diff --git a/app/models/packages/package.rb b/app/models/packages/package.rb index c5da647eea5..970538b45e7 100644 --- a/app/models/packages/package.rb +++ b/app/models/packages/package.rb @@ -162,7 +162,8 @@ class Packages::Package < ApplicationRecord scope :preload_files, -> { preload(:installable_package_files) } scope :preload_nuget_files, -> { preload(:installable_nuget_package_files) } scope :preload_pipelines, -> { preload(pipelines: :user) } - scope :last_of_each_version, -> { where(id: all.select('MAX(id) AS id').group(:version)) } + scope :last_of_each_version, -> { where(id: all.last_of_each_version_ids) } + scope :last_of_each_version_ids, -> { select('MAX(id) AS id').unscope(where: :id).group(:version) } scope :limit_recent, ->(limit) { order_created_desc.limit(limit) } scope :select_distinct_name, -> { select(:name).distinct } diff --git a/app/models/packages/tag.rb b/app/models/packages/tag.rb index 14a1ae98ed4..9c17a147bf4 100644 --- a/app/models/packages/tag.rb +++ b/app/models/packages/tag.rb @@ -10,8 +10,8 @@ class Packages::Tag < ApplicationRecord scope :preload_package, -> { preload(:package) } scope :with_name, -> (name) { where(name: name) } - def self.for_packages(packages) - where(package_id: packages.select(:id)) + def self.for_package_ids(package_ids) + where(package_id: package_ids) .order(updated_at: :desc) .limit(FOR_PACKAGES_TAGS_LIMIT) end diff --git a/app/models/system_note_metadata.rb b/app/models/system_note_metadata.rb index 36166bdbc9a..bb8527d8c01 100644 --- a/app/models/system_note_metadata.rb +++ b/app/models/system_note_metadata.rb @@ -2,6 +2,9 @@ class SystemNoteMetadata < ApplicationRecord include Importable + include IgnorableColumns + + ignore_column :note_id_convert_to_bigint, remove_with: '16.0', remove_after: '2023-05-22' # These notes's action text might contain a reference that is external. # We should always force a deep validation upon references that are found diff --git a/app/presenters/packages/npm/package_presenter.rb b/app/presenters/packages/npm/package_presenter.rb index fabb0a36746..57bdd373309 100644 --- a/app/presenters/packages/npm/package_presenter.rb +++ b/app/presenters/packages/npm/package_presenter.rb @@ -83,7 +83,7 @@ module Packages end def package_tags - Packages::Tag.for_packages(packages) + Packages::Tag.for_package_ids(packages.last_of_each_version_ids) .preload_package end diff --git a/db/migrate/20230201014223_initialize_conversion_of_system_note_metadata_note_id_to_bigint.rb b/db/migrate/20230201014223_initialize_conversion_of_system_note_metadata_note_id_to_bigint.rb new file mode 100644 index 00000000000..ec84f1cb946 --- /dev/null +++ b/db/migrate/20230201014223_initialize_conversion_of_system_note_metadata_note_id_to_bigint.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class InitializeConversionOfSystemNoteMetadataNoteIdToBigint < Gitlab::Database::Migration[2.1] + TABLE = :system_note_metadata + COLUMNS = %i[note_id] + + enable_lock_retries! + + def up + initialize_conversion_of_integer_to_bigint(TABLE, COLUMNS) + end + + def down + revert_initialize_conversion_of_integer_to_bigint(TABLE, COLUMNS) + end +end diff --git a/db/post_migrate/20230201014238_backfill_system_note_metadata_note_id_for_bigint_conversion.rb b/db/post_migrate/20230201014238_backfill_system_note_metadata_note_id_for_bigint_conversion.rb new file mode 100644 index 00000000000..df24b755b7f --- /dev/null +++ b/db/post_migrate/20230201014238_backfill_system_note_metadata_note_id_for_bigint_conversion.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class BackfillSystemNoteMetadataNoteIdForBigintConversion < Gitlab::Database::Migration[2.1] + TABLE = :system_note_metadata + COLUMNS = %i[note_id] + + restrict_gitlab_migration gitlab_schema: :gitlab_main + + def up + backfill_conversion_of_integer_to_bigint(TABLE, COLUMNS) + end + + def down + revert_backfill_conversion_of_integer_to_bigint(TABLE, COLUMNS) + end +end diff --git a/db/schema_migrations/20230201014223 b/db/schema_migrations/20230201014223 new file mode 100644 index 00000000000..323fe555df5 --- /dev/null +++ b/db/schema_migrations/20230201014223 @@ -0,0 +1 @@ +42100a86045f084c3b74e404a2f95d4d76751ad92102edb271dc628279060ce5
\ No newline at end of file diff --git a/db/schema_migrations/20230201014238 b/db/schema_migrations/20230201014238 new file mode 100644 index 00000000000..8a2d6effa5e --- /dev/null +++ b/db/schema_migrations/20230201014238 @@ -0,0 +1 @@ +00bbfdf3e45248b72aac115e44a95c23b71344dcc9e35ad3be6bf1f5eda33561
\ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 5a937446086..de5030325f9 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -297,6 +297,15 @@ BEGIN END; $$; +CREATE FUNCTION trigger_482bac5ec48a() RETURNS trigger + LANGUAGE plpgsql + AS $$ +BEGIN + NEW."note_id_convert_to_bigint" := NEW."note_id"; + RETURN NEW; +END; +$$; + CREATE FUNCTION trigger_c5a5f48f12b0() RETURNS trigger LANGUAGE plpgsql AS $$ @@ -22359,7 +22368,8 @@ CREATE TABLE system_note_metadata ( action character varying, created_at timestamp without time zone NOT NULL, updated_at timestamp without time zone NOT NULL, - description_version_id bigint + description_version_id bigint, + note_id_convert_to_bigint bigint DEFAULT 0 NOT NULL ); CREATE SEQUENCE system_note_metadata_id_seq @@ -33581,6 +33591,8 @@ CREATE TRIGGER trigger_bfc6e47be8cc BEFORE INSERT OR UPDATE ON snippet_user_ment CREATE TRIGGER trigger_c2051020aa8b BEFORE INSERT OR UPDATE ON issue_user_mentions FOR EACH ROW EXECUTE FUNCTION trigger_c2051020aa8b(); +CREATE TRIGGER trigger_482bac5ec48a BEFORE INSERT OR UPDATE ON system_note_metadata FOR EACH ROW EXECUTE FUNCTION trigger_482bac5ec48a(); + CREATE TRIGGER trigger_c5a5f48f12b0 BEFORE INSERT OR UPDATE ON epic_user_mentions FOR EACH ROW EXECUTE FUNCTION trigger_c5a5f48f12b0(); CREATE TRIGGER trigger_c7107f30d69d BEFORE INSERT OR UPDATE ON merge_request_metrics FOR EACH ROW EXECUTE FUNCTION trigger_c7107f30d69d(); diff --git a/doc/development/logging.md b/doc/development/logging.md index 6282f0f6677..538fc7ccfe1 100644 --- a/doc/development/logging.md +++ b/doc/development/logging.md @@ -390,14 +390,32 @@ end On GitLab.com, that setting is only 6 compressed files. These settings should suffice for most users, but you may need to tweak them in [Omnibus GitLab](https://gitlab.com/gitlab-org/omnibus-gitlab). -1. If you add a new file, submit an issue to the - [production tracker](https://gitlab.com/gitlab-com/gl-infra/production/-/issues) or - a merge request to the [`gitlab_fluentd`](https://gitlab.com/gitlab-cookbooks/gitlab_fluentd) - project. See [this example](https://gitlab.com/gitlab-cookbooks/gitlab_fluentd/-/merge_requests/51/diffs). +1. On GitLab.com all new JSON log files generated by GitLab Rails are + automatically shipped to Elasticsearch (and available in Kibana) on GitLab + Rails Kubernetes pods. If you need the file forwarded from Gitaly nodes then + submit an issue to the + [production tracker](https://gitlab.com/gitlab-com/gl-infra/production/-/issues) + or a merge request to the + [`gitlab_fluentd`](https://gitlab.com/gitlab-cookbooks/gitlab_fluentd) + project. See + [this example](https://gitlab.com/gitlab-cookbooks/gitlab_fluentd/-/merge_requests/51/diffs). 1. Be sure to update the [GitLab CE/EE documentation](../administration/logs/index.md) and the [GitLab.com runbooks](https://gitlab.com/gitlab-com/runbooks/blob/master/docs/logging/README.md). +## Finding new log files in Kibana (GitLab.com only) + +On GitLab.com all new JSON log files generated by GitLab Rails are +automatically shipped to Elasticsearch (and available in Kibana) on GitLab +Rails Kubernetes pods. The `json.subcomponent` field in Kibana will allow you +to filter by the different kinds of log files. For example the +`json.subcomponent` will be `production_json` for entries forwarded from +`production_json.log`. + +It's also worth noting that log files from Web/API pods go to a different +index than log files from Sidekiq pods. Depending on where you log from you +will find the logs in a different index pattern. + ## Control logging visibility An increase in the logs can cause a growing backlog of unacknowledged messages. When adding new log messages, make sure they don't increase the overall volume of logging by more than 10%. diff --git a/doc/user/group/epics/manage_epics.md b/doc/user/group/epics/manage_epics.md index 25f875ccdfd..74cfa2bd6ed 100644 --- a/doc/user/group/epics/manage_epics.md +++ b/doc/user/group/epics/manage_epics.md @@ -264,7 +264,7 @@ To filter: ### Filter with the OR operator -> OR filtering for labels was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/382969) in GitLab 15.9 [with a flag](../../../administration/feature_flags.md) named `or_issuable_queries`. Disabled by default. +> OR filtering for labels and authors was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/382969) in GitLab 15.9 [with a flag](../../../administration/feature_flags.md) named `or_issuable_queries`. Disabled by default. FLAG: On self-managed GitLab, by default this feature is not available. @@ -273,7 +273,10 @@ On GitLab.com, this feature is not available. The feature is not ready for production use. When this feature is enabled, you can use the OR operator (**is one of: `||`**) -to [filter the list of epics](#filter-the-list-of-epics) by labels. +when you [filter the list of epics](#filter-the-list-of-epics) by: + +- Authors +- Labels `is one of` represents an inclusive OR. For example, if you filter by `Label is one of Deliverable` and `Label is one of UX`, GitLab shows epics with either `Deliverable`, `UX`, or both labels. @@ -518,10 +521,12 @@ The maximum number of direct child epics is 100. > - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/8502) in GitLab 15.6 [with a flag](../../../administration/feature_flags.md) named `child_epics_from_different_hierarchies`. Disabled by default. > - Minimum required role for the group [changed](https://gitlab.com/gitlab-org/gitlab/-/issues/382503) from Reporter to Guest in GitLab 15.7. +> - Cross-group child epics [enabled on GitLab.com and self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/375622) in GitLab 15.9. Enabled by default. FLAG: -On self-managed GitLab, by default this feature is not available. To make it available per group, ask an administrator to [enable the feature flag](../../../administration/feature_flags.md) named `child_epics_from_different_hierarchies`. -On GitLab.com, this feature is not available. The feature is not ready for production use. +On self-managed GitLab, by default this feature is available. To disable it, +ask an administrator to [disable the feature flag](../../../administration/feature_flags.md) named `child_epics_from_different_hierarchies`. +On GitLab.com, this feature is available. You can add a child epic that belongs to a group that is different from the parent epic's group. diff --git a/doc/user/group/saml_sso/index.md b/doc/user/group/saml_sso/index.md index 3d1ef3bad3e..810d680ec59 100644 --- a/doc/user/group/saml_sso/index.md +++ b/doc/user/group/saml_sso/index.md @@ -214,9 +214,9 @@ for additional guidance on information your identity provider may require. GitLab provides the following information for guidance only. If you have any questions on configuring the SAML app, contact your provider's support. -### Azure setup notes +### Set up Azure -Follow the Azure documentation on [configuring single sign-on to applications](https://learn.microsoft.com/en-us/azure/active-directory/manage-apps/view-applications-portal) with the notes below for consideration. +Follow the Azure documentation on [configuring single sign-on to applications](https://learn.microsoft.com/en-us/azure/active-directory/manage-apps/add-application-portal-setup-sso), and use the following notes when needed. <i class="fa fa-youtube-play youtube" aria-hidden="true"></i> For a demo of the Azure SAML setup including SCIM, see [SCIM Provisioning on Azure Using SAML SSO for Groups Demo](https://youtu.be/24-ZxmTeEBU). @@ -230,11 +230,11 @@ The video is outdated in regard to objectID mapping and you should follow the [S | Identity provider single sign-on URL | Login URL | | Certificate fingerprint | Thumbprint | -The recommended attributes are: +You should set the following attributes: -- **Unique User Identifier (Name identifier)** set to `user.objectID`. -- **nameid-format** set to persistent. -- Additional claims set to [supported attributes](#user-attributes). +- **Unique User Identifier (Name identifier)** to `user.objectID`. +- **nameid-format** to persistent. +- Additional claims to [supported attributes](#user-attributes). If using [Group Sync](#group-sync), customize the name of the group claim to match the required attribute. diff --git a/doc/user/group/saml_sso/scim_setup.md b/doc/user/group/saml_sso/scim_setup.md index f02a232ea99..74dc7e067dc 100644 --- a/doc/user/group/saml_sso/scim_setup.md +++ b/doc/user/group/saml_sso/scim_setup.md @@ -116,7 +116,7 @@ For each attribute: 1. Select the required settings. 1. Select **Ok**. -If your SAML configuration differs from [the recommended SAML settings](index.md#azure-setup-notes), select the mapping +If your SAML configuration differs from [the recommended SAML settings](index.md#set-up-azure), select the mapping attributes and modify them accordingly. In particular, the `objectId` source attribute must map to the `externalId` target attribute. diff --git a/doc/user/search/index.md b/doc/user/search/index.md index c86fa136044..dc07b4c9215 100644 --- a/doc/user/search/index.md +++ b/doc/user/search/index.md @@ -84,6 +84,23 @@ where the results were found. ![code search results](img/code_search_git_blame_v15_1.png) +## Search for projects by full path + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/108906) in GitLab 15.9 [with a flag](../../administration/feature_flags.md) named `full_path_project_search`. Disabled by default. + +FLAG: +On self-managed GitLab, by default this feature is not available. +To make it available, ask an administrator to [enable the feature flag](../../administration/feature_flags.md) named `full_path_project_search`. +On GitLab.com, this feature is not available. + +You can search for a project by entering its full path (including the namespace it belongs to) in the search box. +As you type the project path, [autocomplete suggestions](#autocomplete-suggestions) are displayed. + +For example, the search query: + +- `gitlab-org/gitlab` searches for the `gitlab` project in the `gitlab-org` namespace. +- `gitlab-org/` displays autocomplete suggestions for projects that belong to the `gitlab-org` namespace. + ## Search for a SHA You can search for a commit SHA. @@ -149,7 +166,7 @@ To delete filter tokens one at a time, the <kbd>⌥</kbd> (Mac) / <kbd>Control</ In the search bar, you can view autocomplete suggestions for: -- Projects and groups +- [Projects](#search-for-projects-by-full-path) and groups - Users - Various help pages (try and type **API help**) - Project feature pages (try and type **milestones**) diff --git a/locale/gitlab.pot b/locale/gitlab.pot index a6c4917abf7..7cbfcaea1fb 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -34568,6 +34568,9 @@ msgstr "" msgid "ProtectedBranch|default" msgstr "" +msgid "ProtectedEnvironments|Add deployment rules" +msgstr "" + msgid "ProtectedEnvironments|Allowed to deploy" msgstr "" @@ -34577,6 +34580,9 @@ msgstr "" msgid "ProtectedEnvironments|Approval rules" msgstr "" +msgid "ProtectedEnvironments|Create deployment rule" +msgstr "" + msgid "ProtectedEnvironments|Delete deployment rule" msgstr "" @@ -39293,6 +39299,9 @@ msgstr "" msgid "Set a default description template to be used for new issues. %{link_start}What are description templates?%{link_end}" msgstr "" +msgid "Set a group, access level or users who are required to deploy." +msgstr "" + msgid "Set a password on your account to pull or push via %{protocol}." msgstr "" diff --git a/qa/qa/page/merge_request/new.rb b/qa/qa/page/merge_request/new.rb index ffd40fabf05..a8ea937112b 100644 --- a/qa/qa/page/merge_request/new.rb +++ b/qa/qa/page/merge_request/new.rb @@ -49,8 +49,7 @@ module QA def select_source_branch(branch) click_element(:source_branch_dropdown) - fill_element(:dropdown_input_field, branch) - click_via_capybara(:click_on, branch) + search_and_select(branch) end end end diff --git a/qa/qa/specs/features/browser_ui/5_package/package_registry/maven/maven_group_level_spec.rb b/qa/qa/specs/features/browser_ui/5_package/package_registry/maven/maven_group_level_spec.rb index cda45efd828..3fb5c921187 100644 --- a/qa/qa/specs/features/browser_ui/5_package/package_registry/maven/maven_group_level_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/package_registry/maven/maven_group_level_spec.rb @@ -41,7 +41,11 @@ module QA 'using a ci job token' => { authentication_token_type: :ci_job_token, maven_header_name: 'Job-Token', - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347579' + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347579', + quarantine: { + issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/373189', + type: :stale + } } } end @@ -60,7 +64,7 @@ module QA end end - it 'pushes and pulls a maven package', testcase: params[:testcase] do + it 'pushes and pulls a maven package', testcase: params[:testcase], quarantine: params[:quarantine] do Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do Resource::Repository::Commit.fabricate_via_api! do |commit| gitlab_ci_yaml = ERB.new(read_fixture('package_managers/maven/group/producer', 'gitlab_ci.yaml.erb')).result(binding) diff --git a/spec/frontend/confidential_merge_request/components/dropdown_spec.js b/spec/frontend/confidential_merge_request/components/dropdown_spec.js index 770f2636648..4d577fe1132 100644 --- a/spec/frontend/confidential_merge_request/components/dropdown_spec.js +++ b/spec/frontend/confidential_merge_request/components/dropdown_spec.js @@ -1,47 +1,79 @@ -import { GlDropdown, GlDropdownItem } from '@gitlab/ui'; -import { mount } from '@vue/test-utils'; +import { GlCollapsibleListbox } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; import Dropdown from '~/confidential_merge_request/components/dropdown.vue'; -let vm; +const TEST_PROJECTS = [ + { + id: 7, + name: 'test', + }, + { + id: 9, + name: 'lorem ipsum', + }, + { + id: 11, + name: 'dolar sit', + }, +]; -function factory(projects = []) { - vm = mount(Dropdown, { - propsData: { - projects, - selectedProject: projects[0], - }, - }); -} - -describe('Confidential merge request project dropdown component', () => { - afterEach(() => { - vm.destroy(); - }); +describe('~/confidential_merge_request/components/dropdown.vue', () => { + let wrapper; - it('renders dropdown items', () => { - factory([ - { - id: 1, - name: 'test', - }, - { - id: 2, - name: 'test', + function factory(props = {}) { + wrapper = shallowMount(Dropdown, { + propsData: { + projects: TEST_PROJECTS, + ...props, }, - ]); + }); + } - expect(vm.findAllComponents(GlDropdownItem).length).toBe(2); - }); + const findListbox = () => wrapper.findComponent(GlCollapsibleListbox); + + describe('default', () => { + beforeEach(() => { + factory(); + }); + + it('renders collapsible listbox', () => { + expect(findListbox().props()).toMatchObject({ + icon: 'lock', + selected: [], + toggleText: 'Select private project', + block: true, + items: TEST_PROJECTS.map(({ id, name }) => ({ + value: String(id), + text: name, + })), + }); + }); + + it('does not emit anything', () => { + expect(wrapper.emitted()).toEqual({}); + }); - it('shows lock icon', () => { - factory(); + describe('when listbox emits selected', () => { + beforeEach(() => { + findListbox().vm.$emit('select', String(TEST_PROJECTS[1].id)); + }); - expect(vm.findComponent(GlDropdown).props('icon')).toBe('lock'); + it('emits selected project', () => { + expect(wrapper.emitted('select')).toEqual([[TEST_PROJECTS[1]]]); + }); + }); }); - it('has dropdown text', () => { - factory(); + describe('with selected', () => { + beforeEach(() => { + factory({ selectedProject: TEST_PROJECTS[1] }); + }); - expect(vm.findComponent(GlDropdown).props('text')).toBe('Select private project'); + it('shows selected project', () => { + expect(findListbox().props()).toMatchObject({ + selected: String(TEST_PROJECTS[1].id), + toggleText: TEST_PROJECTS[1].name, + }); + }); }); }); diff --git a/spec/frontend/issues/show/components/incidents/timeline_events_item_spec.js b/spec/frontend/issues/show/components/incidents/timeline_events_item_spec.js index acff5d9ed43..24653a23036 100644 --- a/spec/frontend/issues/show/components/incidents/timeline_events_item_spec.js +++ b/spec/frontend/issues/show/components/incidents/timeline_events_item_spec.js @@ -9,8 +9,8 @@ import { mockEvents } from './mock_data'; describe('IncidentTimelineEventList', () => { let wrapper; - const mountComponent = ({ propsData, provide } = {}) => { - const { action, noteHtml, occurredAt } = mockEvents[0]; + const mountComponent = ({ propsData, provide, mockEvent = mockEvents[0] } = {}) => { + const { action, noteHtml, occurredAt } = mockEvent; wrapper = mountExtended(IncidentTimelineEventItem, { propsData: { action, @@ -30,6 +30,7 @@ describe('IncidentTimelineEventList', () => { const findEventTags = () => wrapper.findAllComponents(GlBadge); const findDropdown = () => wrapper.findComponent(GlDropdown); const findDeleteButton = () => wrapper.findByText(timelineItemI18n.delete); + const findEditButton = () => wrapper.findByText(timelineItemI18n.edit); describe('template', () => { beforeEach(() => { @@ -88,6 +89,21 @@ describe('IncidentTimelineEventList', () => { expect(findDeleteButton().exists()).toBe(false); }); + it('does not show edit item when event was system generated', () => { + const systemGeneratedMockEvent = { + ...mockEvents[0], + action: 'status', + }; + + mountComponent({ + provide: { canUpdateTimelineEvent: true }, + mockEvent: systemGeneratedMockEvent, + }); + + expect(findDropdown().exists()).toBe(true); + expect(findEditButton().exists()).toBe(false); + }); + it('shows dropdown and delete item when user has update permission', () => { mountComponent({ provide: { canUpdateTimelineEvent: true } }); diff --git a/spec/frontend/issues/show/components/incidents/timeline_events_tab_spec.js b/spec/frontend/issues/show/components/incidents/timeline_events_tab_spec.js index 5bac1d6e7ad..63474070701 100644 --- a/spec/frontend/issues/show/components/incidents/timeline_events_tab_spec.js +++ b/spec/frontend/issues/show/components/incidents/timeline_events_tab_spec.js @@ -112,7 +112,9 @@ describe('TimelineEventsTab', () => { await waitForPromises(); expect(findEmptyState().exists()).toBe(false); - expect(findTimelineEventsList().props('timelineEvents')).toHaveLength(3); + expect(findTimelineEventsList().props('timelineEvents')).toHaveLength( + timelineEventsQueryListResponse.data.project.incidentManagementTimelineEvents.nodes.length, + ); }); }); diff --git a/spec/lib/feature_groups/gitlab_team_members_spec.rb b/spec/lib/feature_groups/gitlab_team_members_spec.rb index 103cb62af1c..f4db02e6c58 100644 --- a/spec/lib/feature_groups/gitlab_team_members_spec.rb +++ b/spec/lib/feature_groups/gitlab_team_members_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe FeatureGroups::GitlabTeamMembers do # rubocop:disable RSpec/MissingFeatureCategory +RSpec.describe FeatureGroups::GitlabTeamMembers, feature_category: :shared do let_it_be(:gitlab_com) { create(:group) } let_it_be_with_reload(:member) { create(:user).tap { |user| gitlab_com.add_developer(user) } } let_it_be_with_reload(:non_member) { create(:user) } diff --git a/spec/lib/feature_spec.rb b/spec/lib/feature_spec.rb index 82501cbd1aa..c86bc36057a 100644 --- a/spec/lib/feature_spec.rb +++ b/spec/lib/feature_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Feature, stub_feature_flags: false do # rubocop:disable RSpec/MissingFeatureCategory +RSpec.describe Feature, stub_feature_flags: false, feature_category: :shared do include StubVersion before do diff --git a/spec/models/packages/tag_spec.rb b/spec/models/packages/tag_spec.rb index 842ba7ad518..bc03c34f56b 100644 --- a/spec/models/packages/tag_spec.rb +++ b/spec/models/packages/tag_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Packages::Tag, type: :model do +RSpec.describe Packages::Tag, type: :model, feature_category: :package_registry do let!(:project) { create(:project) } let!(:package) { create(:npm_package, version: '1.0.2', project: project, updated_at: 3.days.ago) } @@ -16,14 +16,14 @@ RSpec.describe Packages::Tag, type: :model do it { is_expected.to validate_presence_of(:name) } end - describe '.for_packages' do + describe '.for_package_ids' do let(:package2) { create(:package, project: project, updated_at: 2.days.ago) } let(:package3) { create(:package, project: project, updated_at: 1.day.ago) } let!(:tag1) { create(:packages_tag, package: package) } let!(:tag2) { create(:packages_tag, package: package2) } let!(:tag3) { create(:packages_tag, package: package3) } - subject { described_class.for_packages(project.packages) } + subject { described_class.for_package_ids(project.packages) } it { is_expected.to match_array([tag1, tag2, tag3]) } @@ -34,6 +34,12 @@ RSpec.describe Packages::Tag, type: :model do it { is_expected.to match_array([tag2, tag3]) } end + + context 'with package ids' do + subject { described_class.for_package_ids(project.packages.select(:id)) } + + it { is_expected.to match_array([tag1, tag2, tag3]) } + end end describe '.with_name' do diff --git a/spec/requests/api/appearance_spec.rb b/spec/requests/api/appearance_spec.rb index 1b54fcc94f4..c08ecae28e8 100644 --- a/spec/requests/api/appearance_spec.rb +++ b/spec/requests/api/appearance_spec.rb @@ -5,21 +5,15 @@ require 'spec_helper' RSpec.describe API::Appearance, 'Appearance', feature_category: :navigation do let_it_be(:user) { create(:user) } let_it_be(:admin) { create(:admin) } + let_it_be(:path) { "/application/appearance" } describe "GET /application/appearance" do - context 'as a non-admin user' do - it "returns 403" do - get api("/application/appearance", user) - - expect(response).to have_gitlab_http_status(:forbidden) - end - end + it_behaves_like 'GET request permissions for admin mode' context 'as an admin user' do it "returns appearance" do - get api("/application/appearance", admin) + get api("/application/appearance", admin, admin_mode: true) - expect(response).to have_gitlab_http_status(:ok) expect(json_response).to be_an Hash expect(json_response['description']).to eq('') expect(json_response['email_header_and_footer_enabled']).to be(false) @@ -42,18 +36,12 @@ RSpec.describe API::Appearance, 'Appearance', feature_category: :navigation do end describe "PUT /application/appearance" do - context 'as a non-admin user' do - it "returns 403" do - put api("/application/appearance", user), params: { title: "Test" } - - expect(response).to have_gitlab_http_status(:forbidden) - end - end + it_behaves_like 'PUT request permissions for admin mode', { title: "Test" } context 'as an admin user' do context "instance basics" do it "allows updating the settings" do - put api("/application/appearance", admin), params: { + put api("/application/appearance", admin, admin_mode: true), params: { title: "GitLab Test Instance", description: "gitlab-test.example.com", pwa_name: "GitLab PWA Test", @@ -63,7 +51,6 @@ RSpec.describe API::Appearance, 'Appearance', feature_category: :navigation do profile_image_guidelines: "Custom profile image guidelines" } - expect(response).to have_gitlab_http_status(:ok) expect(json_response).to be_an Hash expect(json_response['description']).to eq('gitlab-test.example.com') expect(json_response['email_header_and_footer_enabled']).to be(false) @@ -94,7 +81,7 @@ RSpec.describe API::Appearance, 'Appearance', feature_category: :navigation do email_header_and_footer_enabled: true } - put api("/application/appearance", admin), params: settings + put api("/application/appearance", admin, admin_mode: true), params: settings expect(response).to have_gitlab_http_status(:ok) settings.each do |attribute, value| @@ -104,14 +91,14 @@ RSpec.describe API::Appearance, 'Appearance', feature_category: :navigation do context "fails on invalid color values" do it "with message_font_color" do - put api("/application/appearance", admin), params: { message_font_color: "No Color" } + put api("/application/appearance", admin, admin_mode: true), params: { message_font_color: "No Color" } expect(response).to have_gitlab_http_status(:bad_request) expect(json_response['message']['message_font_color']).to contain_exactly('must be a valid color code') end it "with message_background_color" do - put api("/application/appearance", admin), params: { message_background_color: "#1" } + put api("/application/appearance", admin, admin_mode: true), params: { message_background_color: "#1" } expect(response).to have_gitlab_http_status(:bad_request) expect(json_response['message']['message_background_color']).to contain_exactly('must be a valid color code') @@ -123,7 +110,7 @@ RSpec.describe API::Appearance, 'Appearance', feature_category: :navigation do let_it_be(:appearance) { create(:appearance) } it "allows updating the image files" do - put api("/application/appearance", admin), params: { + put api("/application/appearance", admin, admin_mode: true), params: { logo: fixture_file_upload("spec/fixtures/dk.png", "image/png"), header_logo: fixture_file_upload("spec/fixtures/dk.png", "image/png"), pwa_icon: fixture_file_upload("spec/fixtures/dk.png", "image/png"), @@ -139,14 +126,14 @@ RSpec.describe API::Appearance, 'Appearance', feature_category: :navigation do context "fails on invalid color images" do it "with string instead of file" do - put api("/application/appearance", admin), params: { logo: 'not-a-file.png' } + put api("/application/appearance", admin, admin_mode: true), params: { logo: 'not-a-file.png' } expect(response).to have_gitlab_http_status(:bad_request) expect(json_response['error']).to eq("logo is invalid") end it "with .svg file instead of .png" do - put api("/application/appearance", admin), params: { favicon: fixture_file_upload("spec/fixtures/logo_sample.svg", "image/svg") } + put api("/application/appearance", admin, admin_mode: true), params: { favicon: fixture_file_upload("spec/fixtures/logo_sample.svg", "image/svg") } expect(response).to have_gitlab_http_status(:bad_request) expect(json_response['message']['favicon']).to contain_exactly("You are not allowed to upload \"svg\" files, allowed types: png, ico") diff --git a/spec/requests/api/applications_spec.rb b/spec/requests/api/applications_spec.rb index e238a1fb554..b81cdcfea8e 100644 --- a/spec/requests/api/applications_spec.rb +++ b/spec/requests/api/applications_spec.rb @@ -3,21 +3,23 @@ require 'spec_helper' RSpec.describe API::Applications, :api, feature_category: :authentication_and_authorization do - let(:admin_user) { create(:user, admin: true) } - let(:user) { create(:user, admin: false) } - let(:scopes) { 'api' } + let_it_be(:admin) { create(:admin) } + let_it_be(:user) { create(:user) } + let_it_be(:scopes) { 'api' } + let_it_be(:path) { "/applications" } let!(:application) { create(:application, name: 'another_application', owner: nil, redirect_uri: 'http://other_application.url', scopes: scopes) } describe 'POST /applications' do + it_behaves_like 'POST request permissions for admin mode', { name: 'application_name', redirect_uri: 'http://application.url', scopes: 'api' } + context 'authenticated and authorized user' do it 'creates and returns an OAuth application' do expect do - post api('/applications', admin_user), params: { name: 'application_name', redirect_uri: 'http://application.url', scopes: scopes } + post api(path, admin, admin_mode: true), params: { name: 'application_name', redirect_uri: 'http://application.url', scopes: scopes } end.to change { Doorkeeper::Application.count }.by 1 application = Doorkeeper::Application.find_by(name: 'application_name', redirect_uri: 'http://application.url') - expect(response).to have_gitlab_http_status(:created) expect(json_response).to be_a Hash expect(json_response['application_id']).to eq application.uid expect(json_response['secret']).to eq application.secret @@ -28,7 +30,7 @@ RSpec.describe API::Applications, :api, feature_category: :authentication_and_au it 'does not allow creating an application with the wrong redirect_uri format' do expect do - post api('/applications', admin_user), params: { name: 'application_name', redirect_uri: 'http://', scopes: scopes } + post api(path, admin, admin_mode: true), params: { name: 'application_name', redirect_uri: 'http://', scopes: scopes } end.not_to change { Doorkeeper::Application.count } expect(response).to have_gitlab_http_status(:bad_request) @@ -38,7 +40,7 @@ RSpec.describe API::Applications, :api, feature_category: :authentication_and_au it 'does not allow creating an application with a forbidden URI format' do expect do - post api('/applications', admin_user), params: { name: 'application_name', redirect_uri: 'javascript://alert()', scopes: scopes } + post api(path, admin, admin_mode: true), params: { name: 'application_name', redirect_uri: 'javascript://alert()', scopes: scopes } end.not_to change { Doorkeeper::Application.count } expect(response).to have_gitlab_http_status(:bad_request) @@ -48,7 +50,7 @@ RSpec.describe API::Applications, :api, feature_category: :authentication_and_au it 'does not allow creating an application without a name' do expect do - post api('/applications', admin_user), params: { redirect_uri: 'http://application.url', scopes: scopes } + post api(path, admin, admin_mode: true), params: { redirect_uri: 'http://application.url', scopes: scopes } end.not_to change { Doorkeeper::Application.count } expect(response).to have_gitlab_http_status(:bad_request) @@ -58,7 +60,7 @@ RSpec.describe API::Applications, :api, feature_category: :authentication_and_au it 'does not allow creating an application without a redirect_uri' do expect do - post api('/applications', admin_user), params: { name: 'application_name', scopes: scopes } + post api(path, admin, admin_mode: true), params: { name: 'application_name', scopes: scopes } end.not_to change { Doorkeeper::Application.count } expect(response).to have_gitlab_http_status(:bad_request) @@ -68,7 +70,7 @@ RSpec.describe API::Applications, :api, feature_category: :authentication_and_au it 'does not allow creating an application without specifying `scopes`' do expect do - post api('/applications', admin_user), params: { name: 'application_name', redirect_uri: 'http://application.url' } + post api(path, admin, admin_mode: true), params: { name: 'application_name', redirect_uri: 'http://application.url' } end.not_to change { Doorkeeper::Application.count } expect(response).to have_gitlab_http_status(:bad_request) @@ -78,7 +80,7 @@ RSpec.describe API::Applications, :api, feature_category: :authentication_and_au it 'does not allow creating an application with blank `scopes`' do expect do - post api('/applications', admin_user), params: { name: 'application_name', redirect_uri: 'http://application.url', scopes: '' } + post api(path, admin, admin_mode: true), params: { name: 'application_name', redirect_uri: 'http://application.url', scopes: '' } end.not_to change { Doorkeeper::Application.count } expect(response).to have_gitlab_http_status(:bad_request) @@ -87,7 +89,7 @@ RSpec.describe API::Applications, :api, feature_category: :authentication_and_au it 'does not allow creating an application with invalid `scopes`' do expect do - post api('/applications', admin_user), params: { name: 'application_name', redirect_uri: 'http://application.url', scopes: 'non_existent_scope' } + post api(path, admin, admin_mode: true), params: { name: 'application_name', redirect_uri: 'http://application.url', scopes: 'non_existent_scope' } end.not_to change { Doorkeeper::Application.count } expect(response).to have_gitlab_http_status(:bad_request) @@ -97,7 +99,7 @@ RSpec.describe API::Applications, :api, feature_category: :authentication_and_au context 'multiple scopes' do it 'creates an application with multiple `scopes` when each scope specified is seperated by a space' do expect do - post api('/applications', admin_user), params: { name: 'application_name', redirect_uri: 'http://application.url', scopes: 'api read_user' } + post api(path, admin, admin_mode: true), params: { name: 'application_name', redirect_uri: 'http://application.url', scopes: 'api read_user' } end.to change { Doorkeeper::Application.count }.by 1 application = Doorkeeper::Application.last @@ -108,7 +110,7 @@ RSpec.describe API::Applications, :api, feature_category: :authentication_and_au it 'does not allow creating an application with multiple `scopes` when one of the scopes is invalid' do expect do - post api('/applications', admin_user), params: { name: 'application_name', redirect_uri: 'http://application.url', scopes: 'api non_existent_scope' } + post api(path, admin, admin_mode: true), params: { name: 'application_name', redirect_uri: 'http://application.url', scopes: 'api non_existent_scope' } end.not_to change { Doorkeeper::Application.count } expect(response).to have_gitlab_http_status(:bad_request) @@ -118,7 +120,7 @@ RSpec.describe API::Applications, :api, feature_category: :authentication_and_au it 'defaults to creating an application with confidential' do expect do - post api('/applications', admin_user), params: { name: 'application_name', redirect_uri: 'http://application.url', scopes: scopes, confidential: nil } + post api(path, admin, admin_mode: true), params: { name: 'application_name', redirect_uri: 'http://application.url', scopes: scopes, confidential: nil } end.to change { Doorkeeper::Application.count }.by(1) expect(response).to have_gitlab_http_status(:created) @@ -133,15 +135,13 @@ RSpec.describe API::Applications, :api, feature_category: :authentication_and_au expect do post api('/applications', user), params: { name: 'application_name', redirect_uri: 'http://application.url', scopes: scopes } end.not_to change { Doorkeeper::Application.count } - - expect(response).to have_gitlab_http_status(:forbidden) end end context 'non-authenticated user' do it 'does not create application' do expect do - post api('/applications'), params: { name: 'application_name', redirect_uri: 'http://application.url' } + post api(path), params: { name: 'application_name', redirect_uri: 'http://application.url' } end.not_to change { Doorkeeper::Application.count } expect(response).to have_gitlab_http_status(:unauthorized) @@ -150,26 +150,17 @@ RSpec.describe API::Applications, :api, feature_category: :authentication_and_au end describe 'GET /applications' do - context 'authenticated and authorized user' do - it 'can list application' do - get api('/applications', admin_user) - - expect(response).to have_gitlab_http_status(:ok) - expect(json_response).to be_a(Array) - end - end + it_behaves_like 'GET request permissions for admin mode' - context 'authorized user without authorization' do - it 'cannot list application' do - get api('/applications', user) + it 'can list application' do + get api(path, admin, admin_mode: true) - expect(response).to have_gitlab_http_status(:forbidden) - end + expect(json_response).to be_a(Array) end context 'non-authenticated user' do it 'cannot list application' do - get api('/applications') + get api(path) expect(response).to have_gitlab_http_status(:unauthorized) end @@ -177,33 +168,29 @@ RSpec.describe API::Applications, :api, feature_category: :authentication_and_au end describe 'DELETE /applications/:id' do + context 'user authorization' do + let!(:path) { "/applications/#{application.id}" } + + it_behaves_like 'DELETE request permissions for admin mode' + end + context 'authenticated and authorized user' do it 'can delete an application' do expect do - delete api("/applications/#{application.id}", admin_user) + delete api("#{path}/#{application.id}", admin, admin_mode: true) end.to change { Doorkeeper::Application.count }.by(-1) - - expect(response).to have_gitlab_http_status(:no_content) end it 'cannot delete non-existing application' do - delete api("/applications/#{non_existing_record_id}", admin_user) + delete api("#{path}/#{non_existing_record_id}", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:not_found) end end - context 'authorized user without authorization' do - it 'cannot delete an application' do - delete api("/applications/#{application.id}", user) - - expect(response).to have_gitlab_http_status(:forbidden) - end - end - context 'non-authenticated user' do it 'cannot delete an application' do - delete api("/applications/#{application.id}") + delete api("#{path}/#{application.id}") expect(response).to have_gitlab_http_status(:unauthorized) end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 2d3afb489df..4e8f990fc10 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -362,8 +362,6 @@ RSpec.configure do |config| ./spec/requests/api/admin/instance_clusters_spec.rb ./spec/requests/api/admin/plan_limits_spec.rb ./spec/requests/api/admin/sidekiq_spec.rb - ./spec/requests/api/appearance_spec.rb - ./spec/requests/api/applications_spec.rb ./spec/requests/api/broadcast_messages_spec.rb ./spec/requests/api/ci/pipelines_spec.rb ./spec/requests/api/ci/runners_reset_registration_token_spec.rb diff --git a/spec/support/shared_examples/requests/admin_mode_shared_examples.rb b/spec/support/shared_examples/requests/admin_mode_shared_examples.rb new file mode 100644 index 00000000000..07fde7d3f35 --- /dev/null +++ b/spec/support/shared_examples/requests/admin_mode_shared_examples.rb @@ -0,0 +1,98 @@ +# frozen_string_literal: true +RSpec.shared_examples 'GET request permissions for admin mode' do + it_behaves_like 'GET request permissions for admin mode when user' + it_behaves_like 'GET request permissions for admin mode when admin' +end + +RSpec.shared_examples 'PUT request permissions for admin mode' do |params| + it_behaves_like 'PUT request permissions for admin mode when user', params + it_behaves_like 'PUT request permissions for admin mode when admin', params +end + +RSpec.shared_examples 'POST request permissions for admin mode' do |params| + it_behaves_like 'POST request permissions for admin mode when user', params + it_behaves_like 'POST request permissions for admin mode when admin', params +end + +RSpec.shared_examples 'DELETE request permissions for admin mode' do + it_behaves_like 'DELETE request permissions for admin mode when user' + it_behaves_like 'DELETE request permissions for admin mode when admin' +end + +RSpec.shared_examples 'GET request permissions for admin mode when user' do + subject { get api(path, current_user, admin_mode: admin_mode) } + + let_it_be(:current_user) { create(:user) } + + it_behaves_like 'admin mode on', true, :forbidden + it_behaves_like 'admin mode on', false, :forbidden +end + +RSpec.shared_examples 'GET request permissions for admin mode when admin' do + subject { get api(path, current_user, admin_mode: admin_mode) } + + let_it_be(:current_user) { create(:admin) } + + it_behaves_like 'admin mode on', true, :ok + it_behaves_like 'admin mode on', false, :forbidden +end + +RSpec.shared_examples 'PUT request permissions for admin mode when user' do |params| + subject { put api(path, current_user, admin_mode: admin_mode), params: params } + + let_it_be(:current_user) { create(:user) } + + it_behaves_like 'admin mode on', true, :forbidden + it_behaves_like 'admin mode on', false, :forbidden +end + +RSpec.shared_examples 'PUT request permissions for admin mode when admin' do |params| + subject { put api(path, current_user, admin_mode: admin_mode), params: params } + + let_it_be(:current_user) { create(:admin) } + + it_behaves_like 'admin mode on', true, :ok + it_behaves_like 'admin mode on', false, :forbidden +end + +RSpec.shared_examples 'POST request permissions for admin mode when user' do |params| + subject { post api(path, current_user, admin_mode: admin_mode), params: params } + + let_it_be(:current_user) { create(:user) } + + it_behaves_like 'admin mode on', true, :forbidden + it_behaves_like 'admin mode on', false, :forbidden +end + +RSpec.shared_examples 'POST request permissions for admin mode when admin' do |params| + subject { post api(path, current_user, admin_mode: admin_mode), params: params } + + let_it_be(:current_user) { create(:admin) } + + it_behaves_like 'admin mode on', true, :created + it_behaves_like 'admin mode on', false, :forbidden +end + +RSpec.shared_examples 'DELETE request permissions for admin mode when user' do + subject { delete api(path, current_user, admin_mode: admin_mode) } + + let_it_be(:current_user) { create(:user) } + + it_behaves_like 'admin mode on', true, :forbidden + it_behaves_like 'admin mode on', false, :forbidden +end + +RSpec.shared_examples 'DELETE request permissions for admin mode when admin' do + subject { delete api(path, current_user, admin_mode: admin_mode) } + + let_it_be(:current_user) { create(:admin) } + + it_behaves_like 'admin mode on', true, :no_content + it_behaves_like 'admin mode on', false, :forbidden +end + +RSpec.shared_examples "admin mode on" do |admin_mode, status| + let_it_be(:admin_mode) { admin_mode } + + it_behaves_like 'returning response status', status +end |