diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-05-24 15:09:43 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-05-24 15:09:43 +0300 |
commit | 7186033c5110609384da4ffb4456093801cd547b (patch) | |
tree | 7a75c37b5ea582dd8a604d7fce381860ebf003fa | |
parent | 52b219b6f40e49471c66765a95a550f6de256b6a (diff) |
Add latest changes from gitlab-org/gitlab@master
45 files changed, 735 insertions, 228 deletions
diff --git a/.gitlab/ci/package-and-test-nightly/main.gitlab-ci.yml b/.gitlab/ci/package-and-test-nightly/main.gitlab-ci.yml index f7fdc0175fe..51d264d6b02 100644 --- a/.gitlab/ci/package-and-test-nightly/main.gitlab-ci.yml +++ b/.gitlab/ci/package-and-test-nightly/main.gitlab-ci.yml @@ -22,24 +22,12 @@ trigger-omnibus-env: extends: - .trigger-omnibus-env -trigger-omnibus-env-ce: - extends: - - .trigger-omnibus-env-ce - variables: - FOSS_ONLY: "1" # set FOSS_ONLY because we don't pass it via trigger job - trigger-omnibus: extends: - .trigger-omnibus needs: - trigger-omnibus-env -trigger-omnibus-ce: - extends: - - .trigger-omnibus-ce - needs: - - trigger-omnibus-env-ce - download-knapsack-report: extends: - .download-knapsack-report @@ -50,19 +38,6 @@ download-knapsack-report: # ========================================== # ------------------------------------------ -# Update jobs -# ------------------------------------------ -update-ee-to-ce: - extends: - - .qa - - .update-script - - .ce - variables: - UPDATE_TYPE: minor - UPDATE_FROM_EDITION: ee - QA_RSPEC_TAGS: --tag smoke - -# ------------------------------------------ # Network limiting jobs # ------------------------------------------ airgapped: diff --git a/.gitlab/ci/package-and-test/main.gitlab-ci.yml b/.gitlab/ci/package-and-test/main.gitlab-ci.yml index d9eda474040..0f3884ef5f1 100644 --- a/.gitlab/ci/package-and-test/main.gitlab-ci.yml +++ b/.gitlab/ci/package-and-test/main.gitlab-ci.yml @@ -410,30 +410,6 @@ packages-selective-parallel: # ------------------------------------------ # Non parallel jobs # ------------------------------------------ -update-minor: - extends: - - .qa - - .update-script - variables: - UPDATE_TYPE: minor - QA_RSPEC_TAGS: --tag smoke - rules: - - !reference [.rules:test:update, rules] - - if: $QA_SUITES =~ /Test::Instance::Smoke/ - - !reference [.rules:test:manual, rules] - -update-major: - extends: - - .qa - - .update-script - variables: - UPDATE_TYPE: major - QA_RSPEC_TAGS: --tag smoke - rules: - - !reference [.rules:test:update, rules] - - if: $QA_SUITES =~ /Test::Instance::Smoke/ - - !reference [.rules:test:manual, rules] - gitlab-pages: extends: .qa variables: @@ -655,6 +631,47 @@ importers: - if: $QA_SUITES =~ /Test::Integration::Import/ - !reference [.rules:test:manual, rules] +# ------------------------------------------ +# Update jobs +# ------------------------------------------ +update-minor: + extends: + - .qa + - .update-script + variables: + UPDATE_TYPE: minor + QA_RSPEC_TAGS: --tag smoke + rules: + - !reference [.rules:test:update, rules] + - if: $QA_SUITES =~ /Test::Instance::Smoke/ + - !reference [.rules:test:manual, rules] + +update-major: + extends: + - .qa + - .update-script + variables: + UPDATE_TYPE: major + QA_RSPEC_TAGS: --tag smoke + rules: + - !reference [.rules:test:update, rules] + - if: $QA_SUITES =~ /Test::Instance::Smoke/ + - !reference [.rules:test:manual, rules] + +update-ee-to-ce: + extends: + - .qa + - .update-script + variables: + UPDATE_TYPE: minor + UPDATE_FROM_EDITION: ee + QA_RSPEC_TAGS: --tag smoke + rules: + - !reference [.rules:test:ce-only, rules] + - !reference [.rules:test:update, rules] + - if: $QA_SUITES =~ /Test::Instance::Smoke/ + - !reference [.rules:test:manual, rules] + # ========================================== # Post test stage # ========================================== diff --git a/.gitlab/ci/qa-common/rules.gitlab-ci.yml b/.gitlab/ci/qa-common/rules.gitlab-ci.yml index 4d0e0138443..653af4eaa82 100644 --- a/.gitlab/ci/qa-common/rules.gitlab-ci.yml +++ b/.gitlab/ci/qa-common/rules.gitlab-ci.yml @@ -135,6 +135,11 @@ - *qa-run-all-tests - *feature-flags-set-manual +.rules:test:ce-only: + rules: + - if: $FOSS_ONLY != "1" + when: never + .rules:test:ee-only: rules: - if: $FOSS_ONLY == "1" @@ -146,12 +151,10 @@ # these jobs need gitlab version because we can't reliably detect it from just the image - if: $GITLAB_SEMVER_VERSION !~ /^\d+\.\d+\.\d+/ when: never - # update type tests are used to check if gitlab upgrade can be performed correctly (mainly migrations) - # there isn't much benefit in running tests after update with new sidebar enabled and there - # is also an issue to properly pass feature toggle to this job due to how gitlab-qa parses cli args - - if: $QA_SUPER_SIDEBAR_ENABLED == "true" + # $QA_SUPER_SIDEBAR_ENABLED is now only present as a variable when testing old nav so we skip update jobs + # in pipeline where it is explicitly disabled + - if: $QA_SUPER_SIDEBAR_ENABLED when: never - - !reference [.rules:test:ee-only, rules] - !reference [.rules:test:qa, rules] .rules:test:qa-default-branch: diff --git a/CHANGELOG.md b/CHANGELOG.md index 9762196babe..b834564d3c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -780,6 +780,12 @@ entry. - [Add index to group_group_links table](gitlab-org/gitlab@9a3f2c1a90b54074e61d0abf07101ce664198e81) ([merge request](gitlab-org/gitlab!117386)) - [Validate the projects.creator_id foregin key synchronously](gitlab-org/gitlab@ed9351984a16f20506babf6eab6706b917904ed1) ([merge request](gitlab-org/gitlab!117147)) +## 15.11.6 (2023-05-24) + +### Changed (1 change) + +- [Introduce parallelised BitBucket Server Importer](gitlab-org/gitlab@41fead2e5b8b8c61c269de902282e2aa75b967a5) ([merge request](gitlab-org/gitlab!121332)) + ## 15.11.5 (2023-05-19) ### Fixed (5 changes) diff --git a/app/assets/javascripts/work_items/components/notes/work_item_add_note.vue b/app/assets/javascripts/work_items/components/notes/work_item_add_note.vue index e10a82b5197..e47cc2e3888 100644 --- a/app/assets/javascripts/work_items/components/notes/work_item_add_note.vue +++ b/app/assets/javascripts/work_items/components/notes/work_item_add_note.vue @@ -69,6 +69,11 @@ export default { required: false, default: false, }, + isInternalThread: { + type: Boolean, + required: false, + default: false, + }, }, data() { return { @@ -148,6 +153,7 @@ export default { 'note note-wrapper note-comment discussion-reply-holder gl-border-t-0! clearfix': !this .isNewDiscussion, 'gl-bg-white! gl-pt-0!': this.isEditing, + 'gl-bg-orange-50!': this.isInternalThread, }; }, }, @@ -162,7 +168,7 @@ export default { }, }, methods: { - async updateWorkItem(commentText) { + async updateWorkItem({ commentText, isNoteInternal = false }) { this.isSubmitting = true; this.$emit('replying', commentText); try { @@ -175,6 +181,7 @@ export default { noteableId: this.workItemId, body: commentText, discussionId: this.discussionId || null, + internal: isNoteInternal, }, }, update(store, createNoteData) { diff --git a/app/assets/javascripts/work_items/components/notes/work_item_comment_form.vue b/app/assets/javascripts/work_items/components/notes/work_item_comment_form.vue index cea28b30d42..c317ec48732 100644 --- a/app/assets/javascripts/work_items/components/notes/work_item_comment_form.vue +++ b/app/assets/javascripts/work_items/components/notes/work_item_comment_form.vue @@ -1,5 +1,5 @@ <script> -import { GlButton } from '@gitlab/ui'; +import { GlButton, GlFormCheckbox, GlIcon, GlTooltipDirective } from '@gitlab/ui'; import * as Sentry from '@sentry/browser'; import { helpPagePath } from '~/helpers/help_page_helper'; import { s__, __, sprintf } from '~/locale'; @@ -19,12 +19,24 @@ import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue import { getUpdateWorkItemMutation } from '~/work_items/components/update_work_item'; export default { + i18n: { + internal: s__('Notes|Make this an internal note'), + internalVisibility: s__( + 'Notes|Internal notes are only visible to members with the role of Reporter or higher', + ), + addInternalNote: __('Add internal note'), + }, constantOptions: { markdownDocsPath: helpPagePath('user/markdown'), }, components: { GlButton, MarkdownEditor, + GlFormCheckbox, + GlIcon, + }, + directives: { + GlTooltip: GlTooltipDirective, }, mixins: [Tracking.mixin()], inject: ['fullPath'], @@ -89,6 +101,7 @@ export default { return { commentText: getDraft(this.autosaveKey) || this.initialValue || '', updateInProgress: false, + isNoteInternal: false, }; }, computed: { @@ -118,6 +131,9 @@ export default { cancelButtonText() { return this.isNewDiscussion ? this.toggleWorkItemStateText : __('Cancel'); }, + commentButtonTextComputed() { + return this.isNoteInternal ? this.$options.i18n.addInternalNote : this.commentButtonText; + }, }, methods: { setCommentText(newText) { @@ -213,18 +229,33 @@ export default { supports-quick-actions :autofocus="autofocus" @input="setCommentText" - @keydown.meta.enter="$emit('submitForm', commentText)" - @keydown.ctrl.enter="$emit('submitForm', commentText)" + @keydown.meta.enter="$emit('submitForm', { commentText, isNoteInternal })" + @keydown.ctrl.enter="$emit('submitForm', { commentText, isNoteInternal })" @keydown.esc.stop="cancelEditing" /> + <gl-form-checkbox + v-if="isNewDiscussion" + v-model="isNoteInternal" + class="gl-mb-2" + data-testid="internal-note-checkbox" + > + {{ $options.i18n.internal }} + <gl-icon + v-gl-tooltip:tooltipcontainer.bottom + name="question-o" + :size="16" + :title="$options.i18n.internalVisibility" + class="gl-text-blue-500" + /> + </gl-form-checkbox> <gl-button category="primary" variant="confirm" data-testid="confirm-button" :disabled="!commentText.length" :loading="isSubmitting" - @click="$emit('submitForm', commentText)" - >{{ commentButtonText }} + @click="$emit('submitForm', { commentText, isNoteInternal })" + >{{ commentButtonTextComputed }} </gl-button> <gl-button data-testid="cancel-button" diff --git a/app/assets/javascripts/work_items/components/notes/work_item_discussion.vue b/app/assets/javascripts/work_items/components/notes/work_item_discussion.vue index e98e03f76fd..f030363664f 100644 --- a/app/assets/javascripts/work_items/components/notes/work_item_discussion.vue +++ b/app/assets/javascripts/work_items/components/notes/work_item_discussion.vue @@ -164,12 +164,7 @@ export default { @reportAbuse="$emit('reportAbuse', note)" @error="$emit('error', $event)" /> - <timeline-entry-item - v-else - :class="{ 'internal-note': note.internal }" - :data-note-id="noteId" - class="note note-discussion gl-px-0" - > + <timeline-entry-item v-else :data-note-id="noteId" class="note note-discussion gl-px-0"> <div class="timeline-content"> <div class="discussion"> <div class="discussion-body"> @@ -222,7 +217,11 @@ export default { @error="$emit('error', $event)" /> </template> - <work-item-note-replying v-if="isReplying" :body="replyingText" /> + <work-item-note-replying + v-if="isReplying" + :is-internal-note="note.internal" + :body="replyingText" + /> <work-item-add-note v-if="shouldShowReplyForm" :notes-form="false" @@ -235,6 +234,7 @@ export default { :add-padding="true" :autocomplete-data-sources="autocompleteDataSources" :markdown-preview-path="markdownPreviewPath" + :is-internal-thread="note.internal" @startReplying="showReplyForm" @cancelEditing="hideReplyForm" @replied="onReplied" diff --git a/app/assets/javascripts/work_items/components/notes/work_item_note.vue b/app/assets/javascripts/work_items/components/notes/work_item_note.vue index 75b0970a89e..39100467081 100644 --- a/app/assets/javascripts/work_items/components/notes/work_item_note.vue +++ b/app/assets/javascripts/work_items/components/notes/work_item_note.vue @@ -106,6 +106,7 @@ export default { 'note note-wrapper note-comment': true, target: this.isTarget, 'inner-target': this.isTarget && !this.isFirstNote, + 'internal-note': this.note.internal, }; }, showReply() { @@ -309,6 +310,7 @@ export default { :created-at="note.createdAt" :note-id="note.id" :note-url="note.url" + :is-internal-note="note.internal" > <span v-if="note.createdAt" class="d-none d-sm-inline">·</span> </note-header> diff --git a/app/assets/javascripts/work_items/components/notes/work_item_note_replying.vue b/app/assets/javascripts/work_items/components/notes/work_item_note_replying.vue index f053f6e1d7c..e4c25f2c93a 100644 --- a/app/assets/javascripts/work_items/components/notes/work_item_note_replying.vue +++ b/app/assets/javascripts/work_items/components/notes/work_item_note_replying.vue @@ -26,6 +26,11 @@ export default { required: false, default: '', }, + isInternalNote: { + type: Boolean, + required: false, + default: false, + }, }, computed: { author() { @@ -36,12 +41,18 @@ export default { username: window.gon.current_username, }; }, + entryClass() { + return { + 'note note-wrapper note-comment being-posted': true, + 'internal-note': this.isInternalNote, + }; + }, }, }; </script> <template> - <timeline-entry-item class="note note-wrapper note-comment being-posted"> + <timeline-entry-item :class="entryClass"> <div class="timeline-avatar gl-float-left"> <gl-avatar :src="$options.constantOptions.avatarUrl" :size="32" /> </div> diff --git a/app/controllers/registrations/welcome_controller.rb b/app/controllers/registrations/welcome_controller.rb index ac8959e0f52..1fbec8f9e73 100644 --- a/app/controllers/registrations/welcome_controller.rb +++ b/app/controllers/registrations/welcome_controller.rb @@ -81,6 +81,7 @@ module Registrations members_activity_path(members) else # subscription registrations goes through here as well + finish_onboarding_if_in_subscription_flow path_for_signed_in_user(current_user) end end @@ -95,6 +96,9 @@ module Registrations def welcome_update_params {} end + + # overridden in EE + def finish_onboarding_if_in_subscription_flow; end end end diff --git a/app/models/integrations/jira.rb b/app/models/integrations/jira.rb index b550f9dade2..4e0c2dde13b 100644 --- a/app/models/integrations/jira.rb +++ b/app/models/integrations/jira.rb @@ -631,7 +631,7 @@ module Integrations yield rescue StandardError => e @error = e - log_exception(e, message: 'Error sending message', client_url: client_url, client_path: path, client_status: e.code) + log_exception(e, message: 'Error sending message', client_url: client_url, client_path: path, client_status: e.try(:code)) nil end diff --git a/app/models/namespace/aggregation_schedule.rb b/app/models/namespace/aggregation_schedule.rb index e08c08f9ced..6c977505f17 100644 --- a/app/models/namespace/aggregation_schedule.rb +++ b/app/models/namespace/aggregation_schedule.rb @@ -14,7 +14,7 @@ class Namespace::AggregationSchedule < ApplicationRecord def default_lease_timeout if Feature.enabled?(:reduce_aggregation_schedule_lease, namespace.root_ancestor) - 2.minutes.to_i + ::Gitlab::CurrentSettings.namespace_aggregation_schedule_lease_duration_in_seconds else 30.minutes.to_i end diff --git a/app/services/ci/job_token_scope/remove_project_service.rb b/app/services/ci/job_token_scope/remove_project_service.rb index d6a2defd5b9..eddd4d79484 100644 --- a/app/services/ci/job_token_scope/remove_project_service.rb +++ b/app/services/ci/job_token_scope/remove_project_service.rb @@ -26,7 +26,7 @@ module Ci ServiceResponse.error(message: link.errors.full_messages.to_sentence, payload: { project_link: link }) end rescue EditScopeValidations::ValidationError => e - ServiceResponse.error(message: e.message) + ServiceResponse.error(message: e.message, reason: :insufficient_permissions) end end end diff --git a/app/views/admin/sessions/_new_base.html.haml b/app/views/admin/sessions/_new_base.html.haml index 13c647cd45f..d0ee3acf0b8 100644 --- a/app/views/admin/sessions/_new_base.html.haml +++ b/app/views/admin/sessions/_new_base.html.haml @@ -3,5 +3,5 @@ = label_tag :user_password, _('Password'), class: 'label-bold' = password_field_tag 'user[password]', nil, { class: 'form-control js-password', data: { id: 'user_password', name: 'user[password]', qa_selector: 'password_field', testid: 'password-field' } } - .submit-container.move-submit-down + .submit-container = submit_tag _('Enter admin mode'), class: 'gl-button btn btn-confirm', data: { qa_selector: 'enter_admin_mode_button' } diff --git a/app/views/admin/sessions/_two_factor_otp.html.haml b/app/views/admin/sessions/_two_factor_otp.html.haml index f7b4035488d..a27dea52884 100644 --- a/app/views/admin/sessions/_two_factor_otp.html.haml +++ b/app/views/admin/sessions/_two_factor_otp.html.haml @@ -5,5 +5,5 @@ %p.form-text.text-muted.hint = _("Enter the code from your two-factor authenticator app. If you've lost your device, you can enter one of your recovery codes.") - .submit-container.move-submit-down + .submit-container = submit_tag 'Verify code', class: 'gl-button btn btn-confirm' diff --git a/app/views/devise/sessions/_new_base.html.haml b/app/views/devise/sessions/_new_base.html.haml index 698e8c89a08..e21510d450a 100644 --- a/app/views/devise/sessions/_new_base.html.haml +++ b/app/views/devise/sessions/_new_base.html.haml @@ -24,7 +24,7 @@ .gl-px-5 = recaptcha_tags nonce: content_security_policy_nonce - .submit-container.move-submit-down.gl-px-5.gl-pb-5 + .submit-container.gl-px-5.gl-pb-5 = f.button _('Sign in'), type: :submit, class: "gl-button btn btn-block btn-confirm js-sign-in-button#{' js-no-auto-disable' if Feature.enabled?(:arkose_labs_login_challenge)}", data: { qa_selector: 'sign_in_button', testid: 'sign-in-button' } - if Gitlab::CurrentSettings.sign_in_text.present? && Feature.enabled?(:restyle_login_page, @project) .gl-px-5 diff --git a/app/views/devise/sessions/_new_ldap.html.haml b/app/views/devise/sessions/_new_ldap.html.haml index 5fa9b3272f0..89f17a62998 100644 --- a/app/views/devise/sessions/_new_ldap.html.haml +++ b/app/views/devise/sessions/_new_ldap.html.haml @@ -15,5 +15,5 @@ - c.with_label do = _('Remember me') - .submit-container.move-submit-down.gl-px-5.gl-pb-5 + .submit-container.gl-px-5.gl-pb-5 = submit_tag submit_message, class: "gl-button btn btn-confirm", data: { qa_selector: 'sign_in_button' } diff --git a/app/views/shared/_new_merge_request_checkbox.html.haml b/app/views/shared/_new_merge_request_checkbox.html.haml index 6bc6d0943c9..75289e2e6a5 100644 --- a/app/views/shared/_new_merge_request_checkbox.html.haml +++ b/app/views/shared/_new_merge_request_checkbox.html.haml @@ -1,8 +1,9 @@ -.form-check.gl-mt-3 +.form-group.gl-mt-3 - nonce = SecureRandom.hex - = check_box_tag 'create_merge_request', 1, true, class: 'js-create-merge-request form-check-input', id: "create_merge_request-#{nonce}" - = label_tag "create_merge_request-#{nonce}", class: 'form-check-label' do - - translation_variables = { new_merge_request: "<strong>#{_('new merge request')}</strong>" } - - translation = _('Start a %{new_merge_request} with these changes') % translation_variables - #{ translation.html_safe } - + = render Pajamas::CheckboxTagComponent.new(name: 'create_merge_request', + checked: true, + checkbox_options: { class: 'js-create-merge-request', id: "create_merge_request-#{nonce}" }) do |c| + = c.label do + - translation_variables = { new_merge_request: "<strong>#{_('new merge request')}</strong>" } + - translation = _('Start a %{new_merge_request} with these changes') % translation_variables + #{ translation.html_safe } diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index 06e07a59311..51c2cea1983 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -2341,7 +2341,7 @@ :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: true + :idempotent: false :tags: [] - :name: bitbucket_server_import_import_pull_request_notes :worker_name: Gitlab::BitbucketServerImport::ImportPullRequestNotesWorker @@ -2350,7 +2350,7 @@ :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: true + :idempotent: false :tags: [] - :name: bitbucket_server_import_stage_finish_import :worker_name: Gitlab::BitbucketServerImport::Stage::FinishImportWorker diff --git a/app/workers/gitlab/bitbucket_server_import/import_pull_request_notes_worker.rb b/app/workers/gitlab/bitbucket_server_import/import_pull_request_notes_worker.rb index a32343172c8..b06d6ccd132 100644 --- a/app/workers/gitlab/bitbucket_server_import/import_pull_request_notes_worker.rb +++ b/app/workers/gitlab/bitbucket_server_import/import_pull_request_notes_worker.rb @@ -2,11 +2,9 @@ module Gitlab module BitbucketServerImport - class ImportPullRequestNotesWorker + class ImportPullRequestNotesWorker # rubocop:disable Scalability/IdempotentWorker include ObjectImporter - idempotent! - def importer_class Importers::PullRequestNotesImporter end diff --git a/app/workers/gitlab/bitbucket_server_import/import_pull_request_worker.rb b/app/workers/gitlab/bitbucket_server_import/import_pull_request_worker.rb index 86b0a39346a..7f70057d54f 100644 --- a/app/workers/gitlab/bitbucket_server_import/import_pull_request_worker.rb +++ b/app/workers/gitlab/bitbucket_server_import/import_pull_request_worker.rb @@ -2,11 +2,9 @@ module Gitlab module BitbucketServerImport - class ImportPullRequestWorker + class ImportPullRequestWorker # rubocop:disable Scalability/IdempotentWorker include ObjectImporter - idempotent! - def importer_class Importers::PullRequestImporter end diff --git a/config/initializers/warden.rb b/config/initializers/warden.rb index 4aa5941747d..14bcaa80064 100644 --- a/config/initializers/warden.rb +++ b/config/initializers/warden.rb @@ -17,6 +17,8 @@ Rails.application.configure do |config| else activity.user_session_override! end + rescue Gitlab::Auth::TooManyIps + throw(:warden, scope: opts[:scope], reason: :too_many_requests) # rubocop:disable Cop/BanCatchThrow end Warden::Manager.after_authentication(scope: :user) do |user, auth, opts| diff --git a/config/routes/project.rb b/config/routes/project.rb index 995c9879aa3..f296143dca8 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -632,8 +632,23 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do constraints: { id: /\d+/ } # rubocop: enable Cop/PutProjectRoutesUnderScope end - end + # All new routes should go under /-/ scope. + # Look for scope '-' at the top of the file. + + # Legacy routes. + # Introduced in 12.0. + # Should be removed with https://gitlab.com/gitlab-org/gitlab/issues/28848. + Gitlab::Routing.redirect_legacy_paths( + self, :mirror, :tags, :hooks, + :commits, :commit, :find_file, :files, :compare, + :cycle_analytics, :mattermost, :variables, :triggers, + :environments, :protected_environments, :error_tracking, :alert_management, + :serverless, :clusters, :audit_events, :wikis, :merge_requests, + :vulnerability_feedback, :security, :dependencies, :issues, + :pipelines, :pipeline_schedules, :runners, :snippets + ) + end # rubocop: disable Cop/PutProjectRoutesUnderScope resources( :projects, diff --git a/config/routes/repository_deprecated.rb b/config/routes/repository_deprecated.rb index 00206592fc8..32682000941 100644 --- a/config/routes/repository_deprecated.rb +++ b/config/routes/repository_deprecated.rb @@ -10,6 +10,21 @@ resource :repository, only: [:create] # Don't use format parameter as file extension (old 3.0.x behavior) # See http://guides.rubyonrails.org/routing.html#route-globbing-and-wildcard-segments scope format: false do + get '/refs/switch', + to: redirect('%{namespace_id}/%{project_id}/-/refs/switch') + + get '/refs/:id/logs_tree', + to: redirect('%{namespace_id}/%{project_id}/-/refs/%{id}/logs_tree'), + constraints: { id: Gitlab::PathRegex.git_reference_regex } + + get '/refs/:id/logs_tree/*path', + constraints: { id: /.*/, path: /[^\0]*/ }, + to: redirect { |params, _request| + path = params[:path] + path.gsub!('@', '-/') + Addressable::URI.escape("#{params[:namespace_id]}/#{params[:project_id]}/-/refs/#{params[:id]}/logs_tree/#{path}") + } + scope constraints: { id: /[^\0]+/ } do # Deprecated. Keep for compatibility. # Issue https://gitlab.com/gitlab-org/gitlab/-/issues/118849 @@ -17,5 +32,9 @@ scope format: false do get '/blob/*id', to: 'blob#show', as: :deprecated_blob get '/raw/*id', to: 'raw#show', as: :deprecated_raw get '/blame/*id', to: 'blame#show', as: :deprecated_blame + + # Redirect those explicitly since `redirect_legacy_paths` conflicts with project new/edit actions + get '/new/*id', to: redirect('%{namespace_id}/%{project_id}/-/new/%{id}') + get '/edit/*id', to: redirect('%{namespace_id}/%{project_id}/-/edit/%{id}') end end diff --git a/doc/administration/monitoring/prometheus/gitlab_metrics.md b/doc/administration/monitoring/prometheus/gitlab_metrics.md index f8cab0c605e..3523721c342 100644 --- a/doc/administration/monitoring/prometheus/gitlab_metrics.md +++ b/doc/administration/monitoring/prometheus/gitlab_metrics.md @@ -36,7 +36,7 @@ The following metrics are available: | Metric | Type | Since | Description | Labels | | :--------------------------------------------------------------- | :---------- | ------: | :-------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------- | -| `gitlab_cache_misses_total` | Counter | 10.2 | Cache read miss | `controller`, `action` | +| `gitlab_cache_misses_total` | Counter | 10.2 | Cache read miss | `controller`, `action`, `store` | | `gitlab_cache_operation_duration_seconds` | Histogram | 10.2 | Cache access time | `operation`, `store` | | `gitlab_cache_operations_total` | Counter | 12.2 | Cache operations by controller or action | `controller`, `action`, `operation`, `store` | | `gitlab_cache_read_multikey_count` | Histogram | 15.7 | Count of keys in multi-key cache read operations | `controller`, `action`, `store` | @@ -63,8 +63,8 @@ The following metrics are available: | `gitlab_transaction_cache_<key>_duration_total` | Counter | 10.2 | Counter for total time (seconds) spent in Rails cache calls (per key) | | | `gitlab_transaction_cache_count_total` | Counter | 10.2 | Counter for total Rails cache calls (aggregate) | | | `gitlab_transaction_cache_duration_total` | Counter | 10.2 | Counter for total time (seconds) spent in Rails cache calls (aggregate) | | -| `gitlab_transaction_cache_read_hit_count_total` | Counter | 10.2 | Counter for cache hits for Rails cache calls | `controller`, `action` | -| `gitlab_transaction_cache_read_miss_count_total` | Counter | 10.2 | Counter for cache misses for Rails cache calls | `controller`, `action` | +| `gitlab_transaction_cache_read_hit_count_total` | Counter | 10.2 | Counter for cache hits for Rails cache calls | `controller`, `action`, `store` | +| `gitlab_transaction_cache_read_miss_count_total` | Counter | 10.2 | Counter for cache misses for Rails cache calls | `controller`, `action`, `store` | | `gitlab_transaction_duration_seconds` | Histogram | 10.2 | Duration for successful requests (`gitlab_transaction_*` metrics) | `controller`, `action` | | `gitlab_transaction_event_build_found_total` | Counter | 9.4 | Counter for build found for API /jobs/request | | | `gitlab_transaction_event_build_invalid_total` | Counter | 9.4 | Counter for build invalid due to concurrency conflict for API /jobs/request | | diff --git a/lib/api/project_job_token_scope.rb b/lib/api/project_job_token_scope.rb index e9e25efea59..a0fac590269 100644 --- a/lib/api/project_job_token_scope.rb +++ b/lib/api/project_job_token_scope.rb @@ -73,6 +73,38 @@ module API present paginate(inbound_projects), with: Entities::BasicProjectDetails end + + desc 'Delete project from allowlist.' do + failure [ + { code: 400, message: 'Bad Request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] + success code: 204 + tags %w[projects_job_token_scope] + end + + params do + requires :id, type: Integer, desc: 'ID of user project', documentation: { example: 1 } + requires :target_project_id, type: Integer, + desc: 'ID of the project to be removed from the allowlist', documentation: { example: 2 } + end + delete ':id/job_token_scope/allowlist/:target_project_id' do + target_project = find_project!(params[:target_project_id]) + + result = ::Ci::JobTokenScope::RemoveProjectService + .new(user_project, current_user) + .execute(target_project, :inbound) + + if result.success? + no_content! + elsif result.reason == :insufficient_permissions + forbidden!(result.message) + else + bad_request!(result.message) + end + end end end end diff --git a/lib/gitlab/devise_failure.rb b/lib/gitlab/devise_failure.rb index ffd057e1d33..2c48ad3b46b 100644 --- a/lib/gitlab/devise_failure.rb +++ b/lib/gitlab/devise_failure.rb @@ -7,6 +7,14 @@ module Gitlab def http_auth? request_format && super end + + def respond + if warden_options[:reason] == :too_many_requests + self.status = 403 + else + super + end + end end end diff --git a/lib/gitlab/metrics/subscribers/rails_cache.rb b/lib/gitlab/metrics/subscribers/rails_cache.rb index b4e9e85a012..4639ea63622 100644 --- a/lib/gitlab/metrics/subscribers/rails_cache.rb +++ b/lib/gitlab/metrics/subscribers/rails_cache.rb @@ -13,7 +13,7 @@ module Gitlab return unless current_transaction - labels = { store: event.payload[:store].split('::').last } + labels = { store: extract_store_name(event) } current_transaction.observe(:gitlab_cache_read_multikey_count, event.payload[:key].size, labels) do buckets [10, 50, 100, 1000] docstring 'Number of keys for mget in read_multi/fetch_multi' @@ -48,23 +48,26 @@ module Gitlab def cache_fetch_hit(event) return unless current_transaction - current_transaction.increment(:gitlab_transaction_cache_read_hit_count_total, 1) + labels = { store: extract_store_name(event) } + current_transaction.increment(:gitlab_transaction_cache_read_hit_count_total, 1, labels) end def cache_generate(event) return unless current_transaction - current_transaction.increment(:gitlab_cache_misses_total, 1) do + labels = { store: extract_store_name(event) } + + current_transaction.increment(:gitlab_cache_misses_total, 1, labels) do docstring 'Cache read miss' end - current_transaction.increment(:gitlab_transaction_cache_read_miss_count_total, 1) + current_transaction.increment(:gitlab_transaction_cache_read_miss_count_total, 1, labels) end def observe(key, event) return unless current_transaction - labels = { operation: key, store: event.payload[:store].split('::').last } + labels = { operation: key, store: extract_store_name(event) } current_transaction.increment(:gitlab_cache_operations_total, 1, labels) do docstring 'Cache operations' @@ -76,6 +79,11 @@ module Gitlab private + def extract_store_name(event) + # see payload documentation in https://guides.rubyonrails.org/active_support_instrumentation.html#active-support + event.payload[:store].to_s.split('::').last + end + def current_transaction ::Gitlab::Metrics::WebTransaction.current end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 8df4d6eeffb..0576cb19803 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -1322,6 +1322,9 @@ msgstr "" msgid "'projects' is not yet supported" msgstr "" +msgid "'schemaVersion' '%{given_version}' is not supported, it must be '%{required_version}'" +msgstr "" + msgid "'starterProjects' is not yet supported" msgstr "" @@ -10985,6 +10988,9 @@ msgstr "" msgid "Command" msgstr "" +msgid "Command id '%{command}' must not start with '%{prefix}'" +msgstr "" + msgid "Command line instructions" msgstr "" @@ -11497,6 +11503,18 @@ msgstr "" msgid "Component" msgstr "" +msgid "Component name '%{component}' for command id '%{command}' must not start with '%{prefix}'" +msgstr "" + +msgid "Component name '%{component}' must not start with '%{prefix}'" +msgstr "" + +msgid "Component type '%s' is not yet supported" +msgstr "" + +msgid "Components must have a 'name'" +msgstr "" + msgid "Confidence" msgstr "" @@ -16797,6 +16815,9 @@ msgstr "" msgid "End time" msgstr "" +msgid "Endpoint name '%{endpoint}' of component '%{component}' must not start with '%{prefix}'" +msgstr "" + msgid "Ends" msgstr "" @@ -17754,9 +17775,15 @@ msgstr "" msgid "Even if you reach the number of seats in your subscription, you can continue to add users, and GitLab will bill you for the overage." msgstr "" +msgid "Event '%{event}' of type '%{event_type}' must not start with '%{prefix}'" +msgstr "" + msgid "Event tag (optional)" msgstr "" +msgid "Event type '%s' is not yet supported" +msgstr "" + msgid "EventFilterBy|Filter by all" msgstr "" @@ -24303,6 +24330,9 @@ msgstr "" msgid "Introducing Your DevOps Reports" msgstr "" +msgid "Invalid 'schemaVersion' '%s'" +msgstr "" + msgid "Invalid Insights config file detected" msgstr "" @@ -29239,6 +29269,9 @@ msgstr "" msgid "Multiple Prometheus integrations are not supported" msgstr "" +msgid "Multiple components(%s) have 'gl/inject-editor' attribute" +msgstr "" + msgid "Multiple integrations of a single type are not supported for this project" msgstr "" @@ -29960,7 +29993,7 @@ msgstr "" msgid "No component has 'gl/inject-editor' attribute" msgstr "" -msgid "No components present in the devfile" +msgid "No components present in devfile" msgstr "" msgid "No confirmation email received? Check your spam folder or %{request_link_start}request new confirmation email%{request_link_end}." @@ -36457,6 +36490,9 @@ msgstr "" msgid "Prompt users to upload SSH keys" msgstr "" +msgid "Property 'dedicatedPod' of component '%s' is not yet supported" +msgstr "" + msgid "Protect" msgstr "" @@ -49424,6 +49460,9 @@ msgstr "" msgid "Variable" msgstr "" +msgid "Variable name '%{variable}' must not start with '%{prefix}'" +msgstr "" + msgid "Variable value will be evaluated as raw string." msgstr "" diff --git a/spec/controllers/sessions_controller_spec.rb b/spec/controllers/sessions_controller_spec.rb index 80856512bba..a09b3318c25 100644 --- a/spec/controllers/sessions_controller_spec.rb +++ b/spec/controllers/sessions_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe SessionsController do +RSpec.describe SessionsController, feature_category: :system_access do include DeviseHelpers include LdapHelpers @@ -180,7 +180,7 @@ RSpec.describe SessionsController do end include_examples 'user login request with unique ip limit', 302 do - def request + def gitlab_request post(:create, params: { user: user_params }) expect(subject.current_user).to eq user subject.sign_out user diff --git a/spec/frontend/vue_shared/components/markdown/markdown_editor_spec.js b/spec/frontend/vue_shared/components/markdown/markdown_editor_spec.js index b149a597fa2..72454c113b0 100644 --- a/spec/frontend/vue_shared/components/markdown/markdown_editor_spec.js +++ b/spec/frontend/vue_shared/components/markdown/markdown_editor_spec.js @@ -120,7 +120,9 @@ describe('vue_shared/component/markdown/markdown_editor', () => { }); }); - it('passes render_quick_actions param to renderMarkdownPath if quick actions are enabled', async () => { + // quarantine flaky spec:https://gitlab.com/gitlab-org/gitlab/-/issues/412618 + // eslint-disable-next-line jest/no-disabled-tests + it.skip('passes render_quick_actions param to renderMarkdownPath if quick actions are enabled', async () => { buildWrapper({ propsData: { supportsQuickActions: true } }); await enableContentEditor(); diff --git a/spec/frontend/work_items/components/notes/work_item_add_note_spec.js b/spec/frontend/work_items/components/notes/work_item_add_note_spec.js index e575b6bc097..fc907edcac9 100644 --- a/spec/frontend/work_items/components/notes/work_item_add_note_spec.js +++ b/spec/frontend/work_items/components/notes/work_item_add_note_spec.js @@ -79,142 +79,170 @@ describe('Work item add note', () => { }; describe('adding a comment', () => { - it('calls update widgets mutation', async () => { - const noteText = 'updated desc'; - - await createComponent({ - isEditing: true, - signedIn: true, + describe.each` + isInternalComment + ${false} + ${true} + `('when internal comment is $isInternalComment', ({ isInternalComment }) => { + it('calls update widgets mutation', async () => { + const noteText = 'updated desc'; + + await createComponent({ + isEditing: true, + signedIn: true, + }); + + findCommentForm().vm.$emit('submitForm', { + commentText: noteText, + isNoteInternal: isInternalComment, + }); + + await waitForPromises(); + + expect(mutationSuccessHandler).toHaveBeenCalledWith({ + input: { + noteableId: workItemId, + body: noteText, + discussionId: null, + internal: isInternalComment, + }, + }); }); - findCommentForm().vm.$emit('submitForm', noteText); + it('tracks adding comment', async () => { + await createComponent(); + const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); - await waitForPromises(); + findCommentForm().vm.$emit('submitForm', { + commentText: 'test', + isNoteInternal: isInternalComment, + }); - expect(mutationSuccessHandler).toHaveBeenCalledWith({ - input: { - noteableId: workItemId, - body: noteText, - discussionId: null, - }, - }); - }); + await waitForPromises(); - it('tracks adding comment', async () => { - await createComponent(); - const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); + expect(trackingSpy).toHaveBeenCalledWith(TRACKING_CATEGORY_SHOW, 'add_work_item_comment', { + category: TRACKING_CATEGORY_SHOW, + label: 'item_comment', + property: 'type_Task', + }); + }); - findCommentForm().vm.$emit('submitForm', 'test'); + it('emits `replied` event and hides form after successful mutation', async () => { + await createComponent({ isEditing: true, signedIn: true }); - await waitForPromises(); + findCommentForm().vm.$emit('submitForm', { + commentText: 'some text', + isNoteInternal: isInternalComment, + }); + await waitForPromises(); - expect(trackingSpy).toHaveBeenCalledWith(TRACKING_CATEGORY_SHOW, 'add_work_item_comment', { - category: TRACKING_CATEGORY_SHOW, - label: 'item_comment', - property: 'type_Task', + expect(wrapper.emitted('replied')).toEqual([[]]); }); - }); - - it('emits `replied` event and hides form after successful mutation', async () => { - await createComponent({ isEditing: true, signedIn: true }); - findCommentForm().vm.$emit('submitForm', 'some text'); - await waitForPromises(); + it('clears a draft after successful mutation', async () => { + await createComponent({ + isEditing: true, + signedIn: true, + }); - expect(wrapper.emitted('replied')).toEqual([[]]); - }); + findCommentForm().vm.$emit('submitForm', { + commentText: 'some text', + isNoteInternal: isInternalComment, + }); + await waitForPromises(); - it('clears a draft after successful mutation', async () => { - await createComponent({ - isEditing: true, - signedIn: true, + expect(clearDraft).toHaveBeenCalledWith('gid://gitlab/WorkItem/1-comment'); }); - findCommentForm().vm.$emit('submitForm', 'some text'); - await waitForPromises(); - - expect(clearDraft).toHaveBeenCalledWith('gid://gitlab/WorkItem/1-comment'); - }); + it('emits error when mutation returns error', async () => { + const error = 'eror'; - it('emits error when mutation returns error', async () => { - const error = 'eror'; - - await createComponent({ - isEditing: true, - mutationHandler: jest.fn().mockResolvedValue({ - data: { - createNote: { - note: { - id: 'gid://gitlab/Discussion/c872ba2d7d3eb780d2255138d67ca8b04f65b122', - discussion: { + await createComponent({ + isEditing: true, + mutationHandler: jest.fn().mockResolvedValue({ + data: { + createNote: { + note: { id: 'gid://gitlab/Discussion/c872ba2d7d3eb780d2255138d67ca8b04f65b122', - notes: { - nodes: [], - __typename: 'NoteConnection', + discussion: { + id: 'gid://gitlab/Discussion/c872ba2d7d3eb780d2255138d67ca8b04f65b122', + notes: { + nodes: [], + __typename: 'NoteConnection', + }, + __typename: 'Discussion', }, - __typename: 'Discussion', + __typename: 'Note', }, - __typename: 'Note', + __typename: 'CreateNotePayload', + errors: [error], }, - __typename: 'CreateNotePayload', - errors: [error], }, - }, - }), - }); + }), + }); - findCommentForm().vm.$emit('submitForm', 'updated desc'); + findCommentForm().vm.$emit('submitForm', { + commentText: 'updated desc', + isNoteInternal: isInternalComment, + }); - await waitForPromises(); + await waitForPromises(); - expect(wrapper.emitted('error')).toEqual([[error]]); - }); + expect(wrapper.emitted('error')).toEqual([[error]]); + }); - it('emits error when mutation fails', async () => { - const error = 'eror'; + it('emits error when mutation fails', async () => { + const error = 'eror'; - await createComponent({ - isEditing: true, - mutationHandler: jest.fn().mockRejectedValue(new Error(error)), - }); + await createComponent({ + isEditing: true, + mutationHandler: jest.fn().mockRejectedValue(new Error(error)), + }); - findCommentForm().vm.$emit('submitForm', 'updated desc'); + findCommentForm().vm.$emit('submitForm', { + commentText: 'updated desc', + isNoteInternal: isInternalComment, + }); - await waitForPromises(); + await waitForPromises(); - expect(wrapper.emitted('error')).toEqual([[error]]); - }); + expect(wrapper.emitted('error')).toEqual([[error]]); + }); - it('ignores errors when mutation returns additional information as errors for quick actions', async () => { - await createComponent({ - isEditing: true, - mutationHandler: jest.fn().mockResolvedValue({ - data: { - createNote: { - note: { - id: 'gid://gitlab/Discussion/c872ba2d7d3eb780d2255138d67ca8b04f65b122', - discussion: { + it('ignores errors when mutation returns additional information as errors for quick actions', async () => { + await createComponent({ + isEditing: true, + mutationHandler: jest.fn().mockResolvedValue({ + data: { + createNote: { + note: { id: 'gid://gitlab/Discussion/c872ba2d7d3eb780d2255138d67ca8b04f65b122', - notes: { - nodes: [], - __typename: 'NoteConnection', + discussion: { + id: 'gid://gitlab/Discussion/c872ba2d7d3eb780d2255138d67ca8b04f65b122', + notes: { + nodes: [], + __typename: 'NoteConnection', + }, + __typename: 'Discussion', }, - __typename: 'Discussion', + __typename: 'Note', }, - __typename: 'Note', + __typename: 'CreateNotePayload', + errors: ['Commands only Removed assignee @foobar.', 'Command names ["unassign"]'], }, - __typename: 'CreateNotePayload', - errors: ['Commands only Removed assignee @foobar.', 'Command names ["unassign"]'], }, - }, - }), - }); + }), + }); - findCommentForm().vm.$emit('submitForm', 'updated desc'); + findCommentForm().vm.$emit('submitForm', { + commentText: 'updated desc', + isNoteInternal: isInternalComment, + }); - await waitForPromises(); + await waitForPromises(); - expect(clearDraft).toHaveBeenCalledWith('gid://gitlab/WorkItem/1-comment'); + expect(clearDraft).toHaveBeenCalledWith('gid://gitlab/WorkItem/1-comment'); + }); }); }); diff --git a/spec/frontend/work_items/components/notes/work_item_comment_form_spec.js b/spec/frontend/work_items/components/notes/work_item_comment_form_spec.js index 147f2904761..6c00d52aac5 100644 --- a/spec/frontend/work_items/components/notes/work_item_comment_form_spec.js +++ b/spec/frontend/work_items/components/notes/work_item_comment_form_spec.js @@ -1,6 +1,8 @@ +import { GlFormCheckbox, GlIcon } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import Vue, { nextTick } from 'vue'; import VueApollo from 'vue-apollo'; +import { createMockDirective } from 'helpers/vue_mock_directive'; import waitForPromises from 'helpers/wait_for_promises'; import * as autosave from '~/lib/utils/autosave'; import { ESC_KEY, ENTER_KEY } from '~/lib/utils/keys'; @@ -40,6 +42,8 @@ describe('Work item comment form component', () => { const findMarkdownEditor = () => wrapper.findComponent(MarkdownEditor); const findCancelButton = () => wrapper.find('[data-testid="cancel-button"]'); const findConfirmButton = () => wrapper.find('[data-testid="confirm-button"]'); + const findInternalNoteCheckbox = () => wrapper.findComponent(GlFormCheckbox); + const findInternalNoteTooltipIcon = () => wrapper.findComponent(GlIcon); const mutationSuccessHandler = jest.fn().mockResolvedValue(updateWorkItemMutationResponse); @@ -68,6 +72,9 @@ describe('Work item comment form component', () => { provide: { fullPath: 'test-project-path', }, + directives: { + GlTooltip: createMockDirective('gl-tooltip'), + }, }); }; @@ -168,7 +175,9 @@ describe('Work item comment form component', () => { createComponent(); findConfirmButton().vm.$emit('click'); - expect(wrapper.emitted('submitForm')).toEqual([[draftComment]]); + expect(wrapper.emitted('submitForm')).toEqual([ + [{ commentText: draftComment, isNoteInternal: false }], + ]); }); it('emits `submitForm` event on pressing enter with meta key on markdown editor', () => { @@ -178,7 +187,9 @@ describe('Work item comment form component', () => { new KeyboardEvent('keydown', { key: ENTER_KEY, metaKey: true }), ); - expect(wrapper.emitted('submitForm')).toEqual([[draftComment]]); + expect(wrapper.emitted('submitForm')).toEqual([ + [{ commentText: draftComment, isNoteInternal: false }], + ]); }); it('emits `submitForm` event on pressing ctrl+enter on markdown editor', () => { @@ -188,7 +199,9 @@ describe('Work item comment form component', () => { new KeyboardEvent('keydown', { key: ENTER_KEY, ctrlKey: true }), ); - expect(wrapper.emitted('submitForm')).toEqual([[draftComment]]); + expect(wrapper.emitted('submitForm')).toEqual([ + [{ commentText: draftComment, isNoteInternal: false }], + ]); }); describe('when used as a top level/is a new discussion', () => { @@ -249,4 +262,36 @@ describe('Work item comment form component', () => { }); }); }); + + describe('internal note', () => { + it('internal note checkbox should not be visible by default', () => { + createComponent(); + + expect(findInternalNoteCheckbox().exists()).toBe(false); + }); + + describe('when used as a new discussion', () => { + beforeEach(() => { + createComponent({ isNewDiscussion: true }); + }); + + it('should have the add as internal note capability', () => { + expect(findInternalNoteCheckbox().exists()).toBe(true); + }); + + it('should have the tooltip explaining the internal note capabilities', () => { + expect(findInternalNoteTooltipIcon().exists()).toBe(true); + expect(findInternalNoteTooltipIcon().attributes('title')).toBe( + WorkItemCommentForm.i18n.internalVisibility, + ); + }); + + it('should change the submit button text on change of value', async () => { + findInternalNoteCheckbox().vm.$emit('input', true); + await nextTick(); + + expect(findConfirmButton().text()).toBe(WorkItemCommentForm.i18n.addInternalNote); + }); + }); + }); }); diff --git a/spec/frontend/work_items/components/notes/work_item_discussion_spec.js b/spec/frontend/work_items/components/notes/work_item_discussion_spec.js index fac5011b6af..9d22a64f2cb 100644 --- a/spec/frontend/work_items/components/notes/work_item_discussion_spec.js +++ b/spec/frontend/work_items/components/notes/work_item_discussion_spec.js @@ -90,6 +90,16 @@ describe('Work Item Discussion', () => { expect(findWorkItemAddNote().exists()).toBe(true); expect(findWorkItemAddNote().props('autofocus')).toBe(true); }); + + it('should send the correct props is when the main comment is internal', async () => { + const mainComment = findThreadAtIndex(0); + + mainComment.vm.$emit('startReplying'); + await nextTick(); + expect(findWorkItemAddNote().props('isInternalThread')).toBe( + mockWorkItemNotesWidgetResponseWithComments.discussions.nodes[0].notes.nodes[0].internal, + ); + }); }); describe('When replying to any comment', () => { @@ -115,6 +125,13 @@ describe('Work Item Discussion', () => { expect(findToggleRepliesWidget().exists()).toBe(true); expect(findToggleRepliesWidget().props('collapsed')).toBe(false); }); + + it('should pass `is-internal-note` props to make sure the correct background is set', () => { + expect(findWorkItemNoteReplying().exists()).toBe(true); + expect(findWorkItemNoteReplying().props('isInternalNote')).toBe( + mockWorkItemNotesWidgetResponseWithComments.discussions.nodes[0].notes.nodes[0].internal, + ); + }); }); it('emits `deleteNote` event with correct parameter when child note component emits `deleteNote` event', () => { diff --git a/spec/frontend/work_items/components/notes/work_item_note_replying_spec.js b/spec/frontend/work_items/components/notes/work_item_note_replying_spec.js index 225cc3bacaf..5a6894400b6 100644 --- a/spec/frontend/work_items/components/notes/work_item_note_replying_spec.js +++ b/spec/frontend/work_items/components/notes/work_item_note_replying_spec.js @@ -10,10 +10,11 @@ describe('Work Item Note Replying', () => { const findTimelineEntry = () => wrapper.findComponent(TimelineEntryItem); const findNoteHeader = () => wrapper.findComponent(NoteHeader); - const createComponent = ({ body = mockNoteBody } = {}) => { + const createComponent = ({ body = mockNoteBody, isInternalNote = false } = {}) => { wrapper = shallowMount(WorkItemNoteReplying, { propsData: { body, + isInternalNote, }, }); @@ -31,4 +32,9 @@ describe('Work Item Note Replying', () => { expect(findTimelineEntry().exists()).toBe(true); expect(findNoteHeader().html()).toMatchSnapshot(); }); + + it('should have the correct class when internal note', () => { + createComponent({ isInternalNote: true }); + expect(findTimelineEntry().classes()).toContain('internal-note'); + }); }); diff --git a/spec/frontend/work_items/components/notes/work_item_note_spec.js b/spec/frontend/work_items/components/notes/work_item_note_spec.js index f2cf5171cc1..9de799a6b28 100644 --- a/spec/frontend/work_items/components/notes/work_item_note_spec.js +++ b/spec/frontend/work_items/components/notes/work_item_note_spec.js @@ -318,5 +318,23 @@ describe('Work Item Note', () => { }, ); }); + + describe('internal note', () => { + it('does not have the internal note class set by default', () => { + createComponent(); + expect(findTimelineEntryItem().classes()).not.toContain('internal-note'); + }); + + it('timeline entry item and note header has the class for internal notes', () => { + createComponent({ + note: { + ...mockWorkItemCommentNote, + internal: true, + }, + }); + expect(findTimelineEntryItem().classes()).toContain('internal-note'); + expect(findNoteHeader().props('isInternalNote')).toBe(true); + }); + }); }); }); diff --git a/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb b/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb index 2d4c6d1cc56..98f0fc5ea83 100644 --- a/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb +++ b/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb @@ -56,7 +56,7 @@ RSpec.describe Gitlab::Metrics::Subscribers::RailsCache do it 'does not increment cache read miss total' do expect(transaction).not_to receive(:increment) - .with(:gitlab_cache_misses_total, 1) + .with(:gitlab_cache_misses_total, 1, { store: store_label }) subscriber.cache_read(event) end @@ -145,7 +145,7 @@ RSpec.describe Gitlab::Metrics::Subscribers::RailsCache do it 'increments the cache_read_hit count' do expect(transaction).to receive(:increment) - .with(:gitlab_transaction_cache_read_hit_count_total, 1) + .with(:gitlab_transaction_cache_read_hit_count_total, 1, { store: store_label }) subscriber.cache_fetch_hit(event) end @@ -168,9 +168,9 @@ RSpec.describe Gitlab::Metrics::Subscribers::RailsCache do end it 'increments the cache_fetch_miss count and cache_read_miss total' do - expect(transaction).to receive(:increment).with(:gitlab_cache_misses_total, 1) + expect(transaction).to receive(:increment).with(:gitlab_cache_misses_total, 1, { store: store_label }) expect(transaction).to receive(:increment) - .with(:gitlab_transaction_cache_read_miss_count_total, 1) + .with(:gitlab_transaction_cache_read_miss_count_total, 1, { store: store_label }) subscriber.cache_generate(event) end diff --git a/spec/models/namespace/aggregation_schedule_spec.rb b/spec/models/namespace/aggregation_schedule_spec.rb index 0289e4a5462..ea9dddf2513 100644 --- a/spec/models/namespace/aggregation_schedule_spec.rb +++ b/spec/models/namespace/aggregation_schedule_spec.rb @@ -17,9 +17,12 @@ RSpec.describe Namespace::AggregationSchedule, :clean_gitlab_redis_shared_state, end context 'when reduce_aggregation_schedule_lease FF is enabled' do - it 'is 2 minutes' do + it 'returns namespace_aggregation_schedule_lease_duration value from Gitlabsettings' do + allow(::Gitlab::CurrentSettings).to receive(:namespace_aggregation_schedule_lease_duration_in_seconds) + .and_return(240) stub_feature_flags(reduce_aggregation_schedule_lease: true) - expect(aggregation_schedule.default_lease_timeout).to eq 2.minutes.to_i + + expect(aggregation_schedule.default_lease_timeout).to eq 4.minutes.to_i end end diff --git a/spec/requests/api/doorkeeper_access_spec.rb b/spec/requests/api/doorkeeper_access_spec.rb index 888220c2251..8a21abf02e2 100644 --- a/spec/requests/api/doorkeeper_access_spec.rb +++ b/spec/requests/api/doorkeeper_access_spec.rb @@ -14,7 +14,7 @@ RSpec.describe 'doorkeeper access', feature_category: :system_access do end include_examples 'user login request with unique ip limit' do - def request + def gitlab_request get api('/user'), params: { access_token: token.plaintext_token } end end @@ -34,7 +34,7 @@ RSpec.describe 'doorkeeper access', feature_category: :system_access do end include_examples 'user login request with unique ip limit' do - def request + def gitlab_request get api('/user', user) end end diff --git a/spec/requests/api/project_job_token_scope_spec.rb b/spec/requests/api/project_job_token_scope_spec.rb index 23c27c8ce13..b7ee1fe774f 100644 --- a/spec/requests/api/project_job_token_scope_spec.rb +++ b/spec/requests/api/project_job_token_scope_spec.rb @@ -263,4 +263,128 @@ RSpec.describe API::ProjectJobTokenScope, feature_category: :secrets_management end end end + + describe 'DELETE /projects/:id/job_token_scope/allowlist/:target_project_id' do + let_it_be(:project) { create(:project, :public) } + let_it_be(:target_project) { create(:project, :public) } + let_it_be(:user) { create(:user) } + let_it_be(:link) do + create(:ci_job_token_project_scope_link, + source_project: project, + target_project: target_project) + end + + let(:project_id) { project.id } + let(:delete_job_token_scope_path) do + "/projects/#{project_id}/job_token_scope/allowlist/#{target_project.id}" + end + + subject { delete api(delete_job_token_scope_path, user) } + + context 'when unauthenticated user (missing user)' do + let(:user) { nil } + + context 'for public project' do + it 'does not delete requested project from allowlist' do + project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC) + + subject + + expect(response).to have_gitlab_http_status(:unauthorized) + end + end + end + + context 'when user has no permissions to project' do + it 'responds with 401 forbidden' do + subject + + expect(response).to have_gitlab_http_status(:forbidden) + end + end + + context 'when authenticated user as a developer' do + before do + project.add_developer(user) + end + + it 'returns 403 Forbidden' do + subject + + expect(response).to have_gitlab_http_status(:forbidden) + end + end + + context 'when authenticated user as a maintainer' do + before do + project.add_maintainer(user) + end + + context 'for the target project member' do + before do + target_project.add_guest(user) + end + + it 'returns no content and deletes requested project from allowlist' do + expect_next_instance_of( + Ci::JobTokenScope::RemoveProjectService, + project, + user + ) do |service| + expect(service).to receive(:execute).with(target_project, :inbound) + .and_return(instance_double('ServiceResponse', success?: true)) + end + + subject + + expect(response).to have_gitlab_http_status(:no_content) + expect(response.body).to be_blank + end + + context 'when fails to remove target project' do + it 'returns a bad request' do + expect_next_instance_of( + Ci::JobTokenScope::RemoveProjectService, + project, + user + ) do |service| + expect(service).to receive(:execute).with(target_project, :inbound) + .and_return(instance_double('ServiceResponse', + success?: false, + reason: nil, + message: 'Failed to remove')) + end + + subject + + expect(response).to have_gitlab_http_status(:bad_request) + end + end + end + + context 'when user project does not exists' do + before do + project.destroy! + end + + it 'responds with 404 Not found' do + subject + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + context 'when target project does not exists' do + before do + target_project.destroy! + end + + it 'responds with 404 Not found' do + subject + + expect(response).to have_gitlab_http_status(:not_found) + end + end + end + end end diff --git a/spec/requests/warden_spec.rb b/spec/requests/warden_spec.rb new file mode 100644 index 00000000000..b5423af58a7 --- /dev/null +++ b/spec/requests/warden_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe "Warden", feature_category: :system_access do + describe "rate limit" do + include_context 'unique ips sign in limit' + let(:user) { create(:user) } + + before do + # Set the rate limit to 1 request per IP address per user. + stub_application_setting(unique_ips_limit_per_user: 1) + sign_in(user) + end + + it 'limits the number of requests that can be made from a single IP address per user' do + change_ip('ip1') + get user_path(user) + expect(response).to be_successful + + change_ip('ip2') + get user_path(user) + expect(response).to be_forbidden + end + end +end diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb index aebb68ec822..c2458d3485f 100644 --- a/spec/routing/project_routing_spec.rb +++ b/spec/routing/project_routing_spec.rb @@ -107,6 +107,9 @@ RSpec.describe 'project routing' do it_behaves_like 'wiki routing' do let(:base_path) { '/gitlab/gitlabhq/-/wikis' } end + + it_behaves_like 'redirecting a legacy path', "/gitlab/gitlabhq/wikis", "/gitlab/gitlabhq/-/wikis" + it_behaves_like 'redirecting a legacy path', "/gitlab/gitlabhq/wikis/home/edit", "/gitlab/gitlabhq/-/wikis/home/edit" end # branches_project_repository GET /:project_id/repository/branches(.:format) projects/repositories#branches @@ -161,6 +164,8 @@ RSpec.describe 'project routing' do expect(delete('/gitlab/gitlabhq/-/tags/feature%2B45/foo/bar/baz')).to route_to('projects/tags#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature+45/foo/bar/baz') expect(delete('/gitlab/gitlabhq/-/tags/feature@45/foo/bar/baz')).to route_to('projects/tags#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature@45/foo/bar/baz') end + + it_behaves_like 'redirecting a legacy path', "/gitlab/gitlabhq/tags", "/gitlab/gitlabhq/-/tags" end # project_deploy_keys GET /:project_id/deploy_keys(.:format) deploy_keys#index @@ -212,6 +217,20 @@ RSpec.describe 'project routing' do namespace_id: 'gitlab', project_id: 'gitlabhq', id: "stable", path: "new\n\nline.txt" }) end + + it_behaves_like 'redirecting a legacy path', '/gitlab/gitlabhq/refs/switch', '/gitlab/gitlabhq/-/refs/switch' + + it_behaves_like 'redirecting a legacy path', + '/gitlab/gitlabhq/refs/feature%2345/logs_tree', + '/gitlab/gitlabhq/-/refs/feature%2345/logs_tree' + + it_behaves_like 'redirecting a legacy path', + '/gitlab/gitlabhq/refs/stable/logs_tree/new%0A%0Aline.txt', + '/gitlab/gitlabhq/-/refs/stable/logs_tree/new%0A%0Aline.txt' + + it_behaves_like 'redirecting a legacy path', + '/gitlab/gitlabhq/refs/feature%2345/logs_tree/../../../../../@example.com/tree/a', + '/gitlab/gitlabhq/-/refs/feature#45/logs_tree/../../../../../-/example.com/tree/a' end describe Projects::MergeRequestsController, 'routing' do @@ -248,6 +267,9 @@ RSpec.describe 'project routing' do let(:actions) { %i[index edit show update] } let(:base_path) { '/gitlab/gitlabhq/-/merge_requests' } end + + it_behaves_like 'redirecting a legacy path', "/gitlab/gitlabhq/merge_requests", "/gitlab/gitlabhq/-/merge_requests" + it_behaves_like 'redirecting a legacy path', "/gitlab/gitlabhq/merge_requests/1/diffs", "/gitlab/gitlabhq/-/merge_requests/1/diffs" end describe Projects::MergeRequests::CreationsController, 'routing' do @@ -276,6 +298,8 @@ RSpec.describe 'project routing' do it 'to #diffs' do expect(get('/gitlab/gitlabhq/-/merge_requests/new/diffs.json')).to route_to('projects/merge_requests/creations#diffs', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'json') end + + it_behaves_like 'redirecting a legacy path', "/gitlab/gitlabhq/merge_requests/new", "/gitlab/gitlabhq/-/merge_requests/new" end describe Projects::MergeRequests::DiffsController, 'routing' do @@ -319,6 +343,8 @@ RSpec.describe 'project routing' do it 'to #raw from unscope routing' do expect(get('/gitlab/gitlabhq/snippets/1/raw')).to route_to('projects/snippets#raw', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') end + + it_behaves_like 'redirecting a legacy path', '/gitlab/gitlabhq/snippets/1', '/gitlab/gitlabhq/-/snippets/1' end # test_project_hook POST /:project_id/-/hooks/:id/test(.:format) hooks#test @@ -336,6 +362,8 @@ RSpec.describe 'project routing' do let(:actions) { %i[index create destroy edit update] } let(:base_path) { '/gitlab/gitlabhq/-/hooks' } end + + it_behaves_like 'redirecting a legacy path', '/gitlab/gitlabhq/hooks', '/gitlab/gitlabhq/-/hooks' end # retry_namespace_project_hook_hook_log POST /:project_id/-/hooks/:hook_id/hook_logs/:id/retry(.:format) projects/hook_logs#retry @@ -348,6 +376,8 @@ RSpec.describe 'project routing' do it 'to #show' do expect(get('/gitlab/gitlabhq/-/hooks/1/hook_logs/1')).to route_to('projects/hook_logs#show', namespace_id: 'gitlab', project_id: 'gitlabhq', hook_id: '1', id: '1') end + + it_behaves_like 'redirecting a legacy path', '/gitlab/gitlabhq/hooks/hook_logs/1', '/gitlab/gitlabhq/-/hooks/hook_logs/1' end # project_commit GET /:project_id/commit/:id(.:format) commit#show {id: /\h{7,40}/, project_id: /[^\/]+/} @@ -358,6 +388,8 @@ RSpec.describe 'project routing' do expect(get('/gitlab/gitlabhq/-/commit/4246fbd.patch')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fbd', format: 'patch') expect(get('/gitlab/gitlabhq/-/commit/4246fbd13872934f72a8fd0d6fb1317b47b59cb5')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fbd13872934f72a8fd0d6fb1317b47b59cb5') end + + it_behaves_like 'redirecting a legacy path', "/gitlab/gitlabhq/commit/4246fbd", "/gitlab/gitlabhq/-/commit/4246fbd" end # patch_project_commit GET /:project_id/commits/:id/patch(.:format) commits#patch @@ -373,6 +405,8 @@ RSpec.describe 'project routing' do it 'to #show' do expect(get('/gitlab/gitlabhq/-/commits/master.atom')).to route_to('projects/commits#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master.atom') end + + it_behaves_like 'redirecting a legacy path', "/gitlab/gitlabhq/commits/master", "/gitlab/gitlabhq/-/commits/master" end # project_project_members GET /:project_id/project_members(.:format) project_members#index @@ -431,6 +465,9 @@ RSpec.describe 'project routing' do let(:actions) { %i[index create new edit show update] } let(:base_path) { '/gitlab/gitlabhq/-/issues' } end + + it_behaves_like 'redirecting a legacy path', "/gitlab/gitlabhq/issues", "/gitlab/gitlabhq/-/issues" + it_behaves_like 'redirecting a legacy path', "/gitlab/gitlabhq/issues/1/edit", "/gitlab/gitlabhq/-/issues/1/edit" end # project_noteable_notes GET /:project_id/noteable/:target_type/:target_id/notes notes#index @@ -545,6 +582,9 @@ RSpec.describe 'project routing' do namespace_id: 'gitlab', project_id: 'gitlabhq', id: newline_file.to_s }) end + + it_behaves_like 'redirecting a legacy path', "/gitlab/gitlabhq/find_file", "/gitlab/gitlabhq/-/find_file" + it_behaves_like 'redirecting a legacy path', "/gitlab/gitlabhq/files/master", "/gitlab/gitlabhq/-/files/master" end describe Projects::BlobController, 'routing' do @@ -575,6 +615,9 @@ RSpec.describe 'project routing' do namespace_id: 'gitlab', project_id: 'gitlabhq', id: "master/docs/#{newline_file}" }) end + + it_behaves_like 'redirecting a legacy path', "/gitlab/gitlabhq/new/master", "/gitlab/gitlabhq/-/new/master" + it_behaves_like 'redirecting a legacy path', "/gitlab/gitlabhq/edit/master/README", "/gitlab/gitlabhq/-/edit/master/README" end # project_raw GET /:project_id/-/raw/:id(.:format) raw#show {id: /[^\0]+/, project_id: /[^\/]+/} @@ -610,6 +653,9 @@ RSpec.describe 'project routing' do expect(get('/gitlab/gitlabhq/-/compare/master...stable')).to route_to('projects/compare#show', namespace_id: 'gitlab', project_id: 'gitlabhq', from: 'master', to: 'stable') expect(get('/gitlab/gitlabhq/-/compare/issue/1234...stable')).to route_to('projects/compare#show', namespace_id: 'gitlab', project_id: 'gitlabhq', from: 'issue/1234', to: 'stable') end + + it_behaves_like 'redirecting a legacy path', '/gitlab/gitlabhq/compare', '/gitlab/gitlabhq/-/compare' + it_behaves_like 'redirecting a legacy path', '/gitlab/gitlabhq/compare/master...stable', '/gitlab/gitlabhq/-/compare/master...stable' end describe Projects::NetworkController, 'routing' do @@ -718,12 +764,16 @@ RSpec.describe 'project routing' do it 'to #show' do expect(get('/gitlab/gitlabhq/-/pipelines/12')).to route_to('projects/pipelines#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '12') end + + it_behaves_like 'redirecting a legacy path', '/gitlab/gitlabhq/pipelines', '/gitlab/gitlabhq/-/pipelines' end describe Projects::PipelineSchedulesController, 'routing' do it 'to #index' do expect(get('/gitlab/gitlabhq/-/pipeline_schedules')).to route_to('projects/pipeline_schedules#index', namespace_id: 'gitlab', project_id: 'gitlabhq') end + + it_behaves_like 'redirecting a legacy path', '/gitlab/gitlabhq/pipeline_schedules', '/gitlab/gitlabhq/-/pipeline_schedules' end describe Projects::Settings::OperationsController, 'routing' do @@ -819,26 +869,26 @@ RSpec.describe 'project routing' do end describe Projects::EnvironmentsController, 'routing' do - # TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/411431 - it 'routes to projects/environments#index' do - expect(get('/gitlab/gitlabhq/-/environments')) - .to route_to('projects/environments#index', namespace_id: 'gitlab', project_id: 'gitlabhq') + describe 'legacy routing' do + it_behaves_like 'redirecting a legacy path', "/gitlab/gitlabhq/environments", "/gitlab/gitlabhq/-/environments" end end describe Projects::ClustersController, 'routing' do - # TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/411434 - it 'routes to projects/clusters#index' do - expect(get('/gitlab/gitlabhq/-/clusters')) - .to route_to('projects/clusters#index', namespace_id: 'gitlab', project_id: 'gitlabhq') + describe 'legacy routing' do + it_behaves_like 'redirecting a legacy path', "/gitlab/gitlabhq/clusters", "/gitlab/gitlabhq/-/clusters" end end describe Projects::ErrorTrackingController, 'routing' do - # TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/411436 - it 'routes to projects/clusters#index' do - expect(get('/gitlab/gitlabhq/-/error_tracking')) - .to route_to('projects/error_tracking#index', namespace_id: 'gitlab', project_id: 'gitlabhq') + describe 'legacy routing' do + it_behaves_like 'redirecting a legacy path', "/gitlab/gitlabhq/error_tracking", "/gitlab/gitlabhq/-/error_tracking" + end + end + + describe Projects::Serverless, 'routing' do + describe 'legacy routing' do + it_behaves_like 'redirecting a legacy path', "/gitlab/gitlabhq/serverless", "/gitlab/gitlabhq/-/serverless" end end diff --git a/spec/services/ci/job_token_scope/remove_project_service_spec.rb b/spec/services/ci/job_token_scope/remove_project_service_spec.rb index 5b39f8908f2..c1f28ea4523 100644 --- a/spec/services/ci/job_token_scope/remove_project_service_spec.rb +++ b/spec/services/ci/job_token_scope/remove_project_service_spec.rb @@ -52,6 +52,16 @@ RSpec.describe Ci::JobTokenScope::RemoveProjectService, feature_category: :conti it_behaves_like 'returns error', "Source project cannot be removed from the job token scope" end + + context 'when target project is not in the job token scope' do + let_it_be(:target_project) { create(:project, :public) } + + before do + project.add_maintainer(current_user) + end + + it_behaves_like 'returns error', 'Target project is not in the job token scope' + end end end end diff --git a/spec/support/shared_contexts/unique_ip_check_shared_context.rb b/spec/support/shared_contexts/unique_ip_check_shared_context.rb index 8d199df1c10..5c191f72849 100644 --- a/spec/support/shared_contexts/unique_ip_check_shared_context.rb +++ b/spec/support/shared_contexts/unique_ip_check_shared_context.rb @@ -28,7 +28,9 @@ RSpec.shared_context 'unique ips sign in limit' do def request_from_ip(ip) change_ip(ip) - request + # Implement this method while including this shared context to simulate a request to GitLab + # The method name gitlab_request was chosen over request to avoid conflict with rack request + gitlab_request response end diff --git a/spec/support/stub_dot_com_check.rb b/spec/support/stub_dot_com_check.rb index db2904d3ac5..8134e15cb12 100644 --- a/spec/support/stub_dot_com_check.rb +++ b/spec/support/stub_dot_com_check.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true RSpec.configure do |config| - %i[saas saas_registration saas_sso_registration].each do |metadata| + %i[saas saas_registration saas_sso_registration saas_subscription_registration].each do |metadata| config.before(:context, metadata) do # Ensure Gitlab.com? returns true during context. # This is needed for let_it_be which is shared across examples, |