Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-05-24 15:09:43 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-05-24 15:09:43 +0300
commit7186033c5110609384da4ffb4456093801cd547b (patch)
tree7a75c37b5ea582dd8a604d7fce381860ebf003fa
parent52b219b6f40e49471c66765a95a550f6de256b6a (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab/ci/package-and-test-nightly/main.gitlab-ci.yml25
-rw-r--r--.gitlab/ci/package-and-test/main.gitlab-ci.yml65
-rw-r--r--.gitlab/ci/qa-common/rules.gitlab-ci.yml13
-rw-r--r--CHANGELOG.md6
-rw-r--r--app/assets/javascripts/work_items/components/notes/work_item_add_note.vue9
-rw-r--r--app/assets/javascripts/work_items/components/notes/work_item_comment_form.vue41
-rw-r--r--app/assets/javascripts/work_items/components/notes/work_item_discussion.vue14
-rw-r--r--app/assets/javascripts/work_items/components/notes/work_item_note.vue2
-rw-r--r--app/assets/javascripts/work_items/components/notes/work_item_note_replying.vue13
-rw-r--r--app/controllers/registrations/welcome_controller.rb4
-rw-r--r--app/models/integrations/jira.rb2
-rw-r--r--app/models/namespace/aggregation_schedule.rb2
-rw-r--r--app/services/ci/job_token_scope/remove_project_service.rb2
-rw-r--r--app/views/admin/sessions/_new_base.html.haml2
-rw-r--r--app/views/admin/sessions/_two_factor_otp.html.haml2
-rw-r--r--app/views/devise/sessions/_new_base.html.haml2
-rw-r--r--app/views/devise/sessions/_new_ldap.html.haml2
-rw-r--r--app/views/shared/_new_merge_request_checkbox.html.haml15
-rw-r--r--app/workers/all_queues.yml4
-rw-r--r--app/workers/gitlab/bitbucket_server_import/import_pull_request_notes_worker.rb4
-rw-r--r--app/workers/gitlab/bitbucket_server_import/import_pull_request_worker.rb4
-rw-r--r--config/initializers/warden.rb2
-rw-r--r--config/routes/project.rb17
-rw-r--r--config/routes/repository_deprecated.rb19
-rw-r--r--doc/administration/monitoring/prometheus/gitlab_metrics.md6
-rw-r--r--lib/api/project_job_token_scope.rb32
-rw-r--r--lib/gitlab/devise_failure.rb8
-rw-r--r--lib/gitlab/metrics/subscribers/rails_cache.rb18
-rw-r--r--locale/gitlab.pot41
-rw-r--r--spec/controllers/sessions_controller_spec.rb4
-rw-r--r--spec/frontend/vue_shared/components/markdown/markdown_editor_spec.js4
-rw-r--r--spec/frontend/work_items/components/notes/work_item_add_note_spec.js226
-rw-r--r--spec/frontend/work_items/components/notes/work_item_comment_form_spec.js51
-rw-r--r--spec/frontend/work_items/components/notes/work_item_discussion_spec.js17
-rw-r--r--spec/frontend/work_items/components/notes/work_item_note_replying_spec.js8
-rw-r--r--spec/frontend/work_items/components/notes/work_item_note_spec.js18
-rw-r--r--spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb8
-rw-r--r--spec/models/namespace/aggregation_schedule_spec.rb7
-rw-r--r--spec/requests/api/doorkeeper_access_spec.rb4
-rw-r--r--spec/requests/api/project_job_token_scope_spec.rb124
-rw-r--r--spec/requests/warden_spec.rb26
-rw-r--r--spec/routing/project_routing_spec.rb74
-rw-r--r--spec/services/ci/job_token_scope/remove_project_service_spec.rb10
-rw-r--r--spec/support/shared_contexts/unique_ip_check_shared_context.rb4
-rw-r--r--spec/support/stub_dot_com_check.rb2
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">&middot;</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,