diff --git a/app/assets/javascripts/lib/utils/text_markdown.js b/app/assets/javascripts/lib/utils/text_markdown.js
index 1e80e25d0b9..320b3371c77 100644
--- a/app/assets/javascripts/lib/utils/text_markdown.js
+++ b/app/assets/javascripts/lib/utils/text_markdown.js
@@ -876,7 +876,6 @@ function handleContinueList(e, textArea) {
}
function handleContinueIndentedText(e, textArea) {
- if (!gon.features?.continueIndentedText) return;
if (!gon.markdown_maintain_indentation) return;
if (!shouldHandleIndentation(e, textArea)) {
diff --git a/app/assets/javascripts/wikis/graphql/notes/wiki_page_note.fragment.graphql b/app/assets/javascripts/wikis/graphql/notes/wiki_page_note.fragment.graphql
index 0e7d51e037b..818e3670d18 100644
--- a/app/assets/javascripts/wikis/graphql/notes/wiki_page_note.fragment.graphql
+++ b/app/assets/javascripts/wikis/graphql/notes/wiki_page_note.fragment.graphql
@@ -12,6 +12,7 @@ fragment WikiPageNote on Note {
}
}
maxAccessLevelOfAuthor
+ internal
body
bodyHtml
createdAt
diff --git a/app/assets/javascripts/wikis/graphql/wiki_page.query.graphql b/app/assets/javascripts/wikis/graphql/wiki_page.query.graphql
index cd17e6b3b73..bb870886083 100644
--- a/app/assets/javascripts/wikis/graphql/wiki_page.query.graphql
+++ b/app/assets/javascripts/wikis/graphql/wiki_page.query.graphql
@@ -4,6 +4,9 @@ query wikiPageQuery($slug: String, $projectId: ProjectID, $namespaceId: Namespac
wikiPage(slug: $slug, projectId: $projectId, namespaceId: $namespaceId) {
id
title
+ userPermissions {
+ markNoteAsInternal
+ }
discussions {
nodes {
id
diff --git a/app/assets/javascripts/wikis/wiki_notes/components/wiki_note.vue b/app/assets/javascripts/wikis/wiki_notes/components/wiki_note.vue
index 64839cfcd11..074b64cae62 100644
--- a/app/assets/javascripts/wikis/wiki_notes/components/wiki_note.vue
+++ b/app/assets/javascripts/wikis/wiki_notes/components/wiki_note.vue
@@ -285,6 +285,7 @@ export default {
:note-id="noteId"
:noteable-type="noteableType"
:email-participant="note.externalAuthor"
+ :is-internal-note="note.internal"
>
·
diff --git a/app/assets/javascripts/wikis/wiki_notes/components/wiki_notes_app.vue b/app/assets/javascripts/wikis/wiki_notes/components/wiki_notes_app.vue
index 35c680e3ad8..c75cd762134 100644
--- a/app/assets/javascripts/wikis/wiki_notes/components/wiki_notes_app.vue
+++ b/app/assets/javascripts/wikis/wiki_notes/components/wiki_notes_app.vue
@@ -45,6 +45,7 @@ export default {
result({ data }) {
this.noteableId = data?.wikiPage?.id || '';
this.discussions = cloneDeep(data?.wikiPage?.discussions?.nodes) || [];
+ this.userPermissions = data?.wikiPage?.userPermissions || {};
},
},
},
@@ -52,6 +53,7 @@ export default {
return {
wikiPage: {},
noteableId: '',
+ userPermissions: {},
loadingFailed: false,
placeholderNote: {},
slotKeys: ['comments', 'place-holder-note', 'form'],
@@ -175,6 +177,7 @@ export default {
v-if="!isLoading"
:noteable-id="noteableId"
:note-id="noteableId"
+ :can-set-internal-note="userPermissions.markNoteAsInternal"
@creating-note:start="setPlaceHolderNote"
@creating-note:done="removePlaceholder"
@creating-note:success="(discussion) => updateCache({ discussion })"
diff --git a/app/controllers/profiles/two_factor_auths_controller.rb b/app/controllers/profiles/two_factor_auths_controller.rb
index e5acee09d7d..58bc7348be1 100644
--- a/app/controllers/profiles/two_factor_auths_controller.rb
+++ b/app/controllers/profiles/two_factor_auths_controller.rb
@@ -38,7 +38,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
render 'create'
else
- @error = { message: _('Invalid pin code.') }
+ @otp_error = { message: _('Invalid pin code.') }
@account_string = account_string
setup_show_page
diff --git a/app/graphql/types/permission_types/wikis/wiki_page.rb b/app/graphql/types/permission_types/wikis/wiki_page.rb
index 6e9434293a4..562c32ef7ab 100644
--- a/app/graphql/types/permission_types/wikis/wiki_page.rb
+++ b/app/graphql/types/permission_types/wikis/wiki_page.rb
@@ -6,7 +6,7 @@ module Types
class WikiPage < BasePermissionType
graphql_name 'WikiPagePermissions'
- abilities :read_wiki_page, :create_note
+ abilities :read_wiki_page, :create_note, :mark_note_as_internal
end
end
end
diff --git a/app/helpers/auth_helper.rb b/app/helpers/auth_helper.rb
index 101bcc9a28b..a843cae877b 100644
--- a/app/helpers/auth_helper.rb
+++ b/app/helpers/auth_helper.rb
@@ -241,9 +241,10 @@ module AuthHelper
end
{ button_text: _('Delete one-time password authenticator'),
+ icon: ('remove' if Feature.enabled?(:redesign_user_account_otp, current_user)),
message: message,
path: destroy_otp_profile_two_factor_auth_path,
- password_required: password_required.to_s }
+ password_required: password_required.to_s }.compact
end
def delete_webauthn_device_data(password_required, path)
diff --git a/app/models/ci/processable.rb b/app/models/ci/processable.rb
index 8710faf6045..8f63e18ef23 100644
--- a/app/models/ci/processable.rb
+++ b/app/models/ci/processable.rb
@@ -277,7 +277,7 @@ module Ci
end
def manual_confirmation_message
- options[:manual_confirmation] if manual_job?
+ options[:manual_confirmation] if manual_job? && playable?
end
def redis_state
diff --git a/app/models/note.rb b/app/models/note.rb
index ec0ca626f2d..70caa1465b3 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -865,7 +865,7 @@ class Note < ApplicationRecord
end
def noteable_can_have_confidential_note?
- for_issuable?
+ for_issuable? || for_wiki_page?
end
def set_internal_flag
diff --git a/app/policies/wiki_page_policy.rb b/app/policies/wiki_page_policy.rb
index 09f239e44d7..b246d20ef3a 100644
--- a/app/policies/wiki_page_policy.rb
+++ b/app/policies/wiki_page_policy.rb
@@ -5,10 +5,18 @@ class WikiPagePolicy < BasePolicy
overrides :read_note, :create_note
+ condition(:planner_or_reporter_access) do
+ can?(:reporter_access) || can?(:planner_access)
+ end
+
rule { can?(:read_wiki) }.policy do
enable :read_wiki_page
enable :read_note
enable :create_note
enable :update_subscription
end
+
+ rule { can?(:read_wiki) & planner_or_reporter_access }.policy do
+ enable :mark_note_as_internal
+ end
end
diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml
index caf63a659a8..f32fec69e17 100644
--- a/app/views/profiles/preferences/show.html.haml
+++ b/app/views/profiles/preferences/show.html.haml
@@ -144,11 +144,10 @@
= f.gitlab_ui_checkbox_component :markdown_automatic_lists,
s_('Preferences|Automatically add new list items'),
help_text: html_escape(s_('Preferences|When you type in a description or comment box, pressing %{kbdOpen}Enter%{kbdClose} in a list adds a new item below.')) % { kbdOpen: ''.html_safe, kbdClose: ''.html_safe }
- - if Feature.enabled?(:continue_indented_text, current_user)
- .form-group
- = f.gitlab_ui_checkbox_component :markdown_maintain_indentation,
- s_('Preferences|Maintain indentation'),
- help_text: safe_format(s_('Preferences|When you type in a description or comment box, pressing %{kbdOpen}Enter%{kbdClose} creates a new line indented the same as the previous line.'), tag_pair(tag.kbd, :kbdOpen, :kbdClose))
+ .form-group
+ = f.gitlab_ui_checkbox_component :markdown_maintain_indentation,
+ s_('Preferences|Maintain indentation'),
+ help_text: safe_format(s_('Preferences|When you type in a description or comment box, pressing %{kbdOpen}Enter%{kbdClose} creates a new line indented the same as the previous line.'), tag_pair(tag.kbd, :kbdOpen, :kbdClose))
.form-group
= f.label :tab_width, s_('Preferences|Tab width'), class: 'label-bold'
= f.number_field :tab_width,
diff --git a/app/views/profiles/two_factor_auths/_otp_registration.html.haml b/app/views/profiles/two_factor_auths/_otp_registration.html.haml
new file mode 100644
index 00000000000..0d17ea5c2ae
--- /dev/null
+++ b/app/views/profiles/two_factor_auths/_otp_registration.html.haml
@@ -0,0 +1,60 @@
+- troubleshooting_link = local_assigns[:troubleshooting_link]
+
+= render ::Layouts::CrudComponent.new(_('One-time password authenticator (OTP)'),
+ icon: 'lock',
+ count: current_user.two_factor_otp_enabled? ? 1 : 0,
+ description: _('Use a one-time password authenticator on your mobile device or computer to enable two-factor authentication (2FA).'),
+ form_options: { class: @otp_error.present? ? '' : 'gl-hidden js-toggle-content' },
+ options: { class: 'js-toggle-container js-token-card' }) do |c|
+ - if !current_user.two_factor_otp_enabled?
+ - c.with_actions do
+ = render Pajamas::ButtonComponent.new(size: :small, button_options: { class: 'js-toggle-button js-toggle-content', data: { testid: 'register-otp-authenticator-button' } }) do
+ = _('Register authenticator')
+ - c.with_form do
+ .gl-inline-block.gl-p-3.gl-pb-2.gl-mb-5{ style: 'background: #fff' }
+ = raw @qr_code
+ .gl-mb-5
+ = render Pajamas::CardComponent.new do |c|
+ - c.with_body do
+ %h3.gl-mt-0.gl-mt-0.gl-mb-3.gl-font-bold.gl-leading-reset.gl-text-base.gl-text-default
+ = _("Can't scan the code?")
+ %p.gl-mt-0.gl-mb-3
+ = _("To add the entry manually, provide the following details to the application on your phone.")
+ %p.gl-my-0
+ = _('Account: %{account}') % { account: @account_string }
+ %p.gl-my-0{ data: { testid: 'otp-secret-content' } }
+ = _('Key:')
+ %code.two-factor-secret= current_user.otp_secret.scan(/.{4}/).join(' ')
+ %p.gl-mb-0.two-factor-new-manual-content
+ = _('Time based: Yes')
+ = form_tag profile_two_factor_auth_path, method: :post do |f|
+ - if @otp_error.present?
+ = render Pajamas::AlertComponent.new(title: @otp_error[:message],
+ variant: :danger,
+ alert_options: { class: 'gl-mb-3' },
+ dismissible: false) do |c|
+ - c.with_body do
+ = troubleshooting_link
+ - if current_password_required?
+ .form-group
+ = label_tag :current_password, _('Current password'), class: 'label-bold'
+ = password_field_tag :current_password, nil, autocomplete: 'current-password', required: true, class: 'form-control gl-form-input', data: { testid: 'current-password-field' }
+ %p.form-text.gl-text-subtle
+ = _('Your current password is required to register a two-factor authenticator app.')
+ .form-group
+ = label_tag :pin_code, _('Enter verification code'), class: "label-bold"
+ = text_field_tag :pin_code, nil, autocomplete: 'off', inputmode: 'numeric', class: "form-control gl-form-input", required: true, data: { testid: 'pin-code-field' }
+ .gl-mt-3
+ = render Pajamas::ButtonComponent.new(type: :submit, variant: :confirm, button_options: { data: { testid: 'register-2fa-app-button' } }) do
+ = _('Register with two-factor app')
+ = render Pajamas::ButtonComponent.new(button_options: { type: 'reset', class: 'gl-ml-2 js-toggle-button' }) do
+ = _('Cancel')
+ - c.with_body do
+ - if current_user.two_factor_otp_enabled?
+ .gl-flex.gl-gap-8
+ %div= _("You've already enabled two-factor authentication using a one-time password authenticator. In order to register a different device, you must first delete this authenticator.")
+ .gl-float-right
+ .js-two-factor-action-confirm{ data: delete_otp_authenticator_data(current_password_required?) }
+ - else
+ = _('We recommend using cloud-based authenticator applications that can restore access if you lose your hardware device.')
+ = link_to _('What are some examples?'), help_page_path('user/profile/account/two_factor_authentication.md', anchor: 'enable-a-one-time-password-authenticator'), target: '_blank', rel: 'noopener noreferrer'
diff --git a/app/views/profiles/two_factor_auths/show.html.haml b/app/views/profiles/two_factor_auths/show.html.haml
index af618009cb1..6defdf2c20f 100644
--- a/app/views/profiles/two_factor_auths/show.html.haml
+++ b/app/views/profiles/two_factor_auths/show.html.haml
@@ -15,57 +15,62 @@
= render ::Layouts::PageHeadingComponent.new(title, options: { class: 'gl-mb-3' })
-= render ::Layouts::SettingsSectionComponent.new(_('Register a one-time password authenticator')) do |c|
- - c.with_description do
- = _('Use a one-time password authenticator on your mobile device or computer to enable two-factor authentication (2FA).')
- - if current_user.two_factor_otp_enabled?
- = _("You've already enabled two-factor authentication using a one-time password authenticator. In order to register a different device, you must first delete this authenticator.")
- - else
- - register_2fa_token = _('We recommend using cloud-based authenticator applications that can restore access if you lose your hardware device.')
- = register_2fa_token.html_safe
- = link_to _('What are some examples?'), help_page_path('user/profile/account/two_factor_authentication.md', anchor: 'enable-a-one-time-password-authenticator'), target: '_blank', rel: 'noopener noreferrer'
- - c.with_body do
- - if current_user.two_factor_otp_enabled?
- .gl-inline-block.gl-w-full.sm:gl-w-auto
- .js-two-factor-action-confirm{ data: delete_otp_authenticator_data(current_password_required?) }
+- if Feature.enabled?(:redesign_user_account_otp, current_user)
+ = render ::Layouts::SettingsSectionComponent.new(_('Register a one-time password authenticator')) do |c|
+ - c.with_body do
+ = render 'otp_registration', {troubleshooting_link: troubleshooting_link}
+- else
+ = render ::Layouts::SettingsSectionComponent.new(_('Register a one-time password authenticator')) do |c|
+ - c.with_description do
+ = _('Use a one-time password authenticator on your mobile device or computer to enable two-factor authentication (2FA).')
+ - if current_user.two_factor_otp_enabled?
+ = _("You've already enabled two-factor authentication using a one-time password authenticator. In order to register a different device, you must first delete this authenticator.")
+ - else
+ - register_2fa_token = _('We recommend using cloud-based authenticator applications that can restore access if you lose your hardware device.')
+ = register_2fa_token.html_safe
+ = link_to _('What are some examples?'), help_page_path('user/profile/account/two_factor_authentication.md', anchor: 'enable-a-one-time-password-authenticator'), target: '_blank', rel: 'noopener noreferrer'
+ - c.with_body do
+ - if current_user.two_factor_otp_enabled?
+ .gl-inline-block.gl-w-full.sm:gl-w-auto
+ .js-two-factor-action-confirm{ data: delete_otp_authenticator_data(current_password_required?) }
- - else
- .gl-inline-block.gl-p-3.gl-pb-2.gl-mb-5{ style: 'background: #fff' }
- = raw @qr_code
- .gl-mb-5
- = render Pajamas::CardComponent.new do |c|
- - c.with_body do
- %h3.gl-mt-0.gl-mt-0.gl-mb-3.gl-font-bold.gl-leading-reset.gl-text-base.gl-text-default
- = _("Can't scan the code?")
- %p.gl-mt-0.gl-mb-3
- = _("To add the entry manually, provide the following details to the application on your phone.")
- %p.gl-my-0
- = _('Account: %{account}') % { account: @account_string }
- %p.gl-my-0{ data: { testid: 'otp-secret-content' } }
- = _('Key:')
- %code.two-factor-secret= current_user.otp_secret.scan(/.{4}/).join(' ')
- %p.gl-mb-0.two-factor-new-manual-content
- = _('Time based: Yes')
- = form_tag profile_two_factor_auth_path, method: :post do |f|
- - if @otp_error
- = render Pajamas::AlertComponent.new(title: @otp_error[:message],
- variant: :danger,
- alert_options: { class: 'gl-mb-3' },
- dismissible: false) do |c|
+ - else
+ .gl-inline-block.gl-p-3.gl-pb-2.gl-mb-5{ style: 'background: #fff' }
+ = raw @qr_code
+ .gl-mb-5
+ = render Pajamas::CardComponent.new do |c|
- c.with_body do
- = troubleshooting_link
- - if current_password_required?
+ %h3.gl-mt-0.gl-mt-0.gl-mb-3.gl-font-bold.gl-leading-reset.gl-text-base.gl-text-default
+ = _("Can't scan the code?")
+ %p.gl-mt-0.gl-mb-3
+ = _("To add the entry manually, provide the following details to the application on your phone.")
+ %p.gl-my-0
+ = _('Account: %{account}') % { account: @account_string }
+ %p.gl-my-0{ data: { testid: 'otp-secret-content' } }
+ = _('Key:')
+ %code.two-factor-secret= current_user.otp_secret.scan(/.{4}/).join(' ')
+ %p.gl-mb-0.two-factor-new-manual-content
+ = _('Time based: Yes')
+ = form_tag profile_two_factor_auth_path, method: :post do |f|
+ - if @otp_error
+ = render Pajamas::AlertComponent.new(title: @otp_error[:message],
+ variant: :danger,
+ alert_options: { class: 'gl-mb-3' },
+ dismissible: false) do |c|
+ - c.with_body do
+ = troubleshooting_link
+ - if current_password_required?
+ .form-group
+ = label_tag :current_password, _('Current password'), class: 'label-bold'
+ = password_field_tag :current_password, nil, autocomplete: 'current-password', required: true, class: 'form-control gl-form-input', data: { testid: 'current-password-field' }
+ %p.form-text.gl-text-subtle
+ = _('Your current password is required to register a two-factor authenticator app.')
.form-group
- = label_tag :current_password, _('Current password'), class: 'label-bold'
- = password_field_tag :current_password, nil, autocomplete: 'current-password', required: true, class: 'form-control gl-form-input', data: { testid: 'current-password-field' }
- %p.form-text.gl-text-subtle
- = _('Your current password is required to register a two-factor authenticator app.')
- .form-group
- = label_tag :pin_code, _('Enter verification code'), class: "label-bold"
- = text_field_tag :pin_code, nil, autocomplete: 'off', inputmode: 'numeric', class: "form-control gl-form-input", required: true, data: { testid: 'pin-code-field' }
- .gl-mt-3
- = render Pajamas::ButtonComponent.new(type: :submit, variant: :confirm, button_options: { data: { testid: 'register-2fa-app-button' } }) do
- = _('Register with two-factor app')
+ = label_tag :pin_code, _('Enter verification code'), class: "label-bold"
+ = text_field_tag :pin_code, nil, autocomplete: 'off', inputmode: 'numeric', class: "form-control gl-form-input", required: true, data: { testid: 'pin-code-field' }
+ .gl-mt-3
+ = render Pajamas::ButtonComponent.new(type: :submit, variant: :confirm, button_options: { data: { testid: 'register-2fa-app-button' } }) do
+ = _('Register with two-factor app')
= render ::Layouts::SettingsSectionComponent.new(_('Register a WebAuthn device')) do |c|
- c.with_description do
diff --git a/config/feature_flags/gitlab_com_derisk/redesign_user_account_otp.yml b/config/feature_flags/gitlab_com_derisk/redesign_user_account_otp.yml
new file mode 100644
index 00000000000..9d79c1c01a2
--- /dev/null
+++ b/config/feature_flags/gitlab_com_derisk/redesign_user_account_otp.yml
@@ -0,0 +1,10 @@
+---
+name: redesign_user_account_otp
+description:
+feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/549999
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/196832
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/554205
+milestone: '18.2'
+group: group::authentication
+type: gitlab_com_derisk
+default_enabled: false
diff --git a/config/feature_flags/wip/continue_indented_text.yml b/config/feature_flags/wip/continue_indented_text.yml
deleted file mode 100644
index edfa9d31641..00000000000
--- a/config/feature_flags/wip/continue_indented_text.yml
+++ /dev/null
@@ -1,7 +0,0 @@
----
-name: continue_indented_text
-feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/23978
-milestone: '18.2'
-group: group::knowledge
-type: wip
-default_enabled: false
diff --git a/db/click_house/main.sql b/db/click_house/main.sql
index e3841537c0b..147293b308e 100644
--- a/db/click_house/main.sql
+++ b/db/click_house/main.sql
@@ -304,6 +304,49 @@ PRIMARY KEY id
ORDER BY id
SETTINGS index_granularity = 8192;
+CREATE TABLE hierarchy_work_items
+(
+ `traversal_path` String,
+ `id` Int64,
+ `title` String DEFAULT '',
+ `author_id` Nullable(Int64),
+ `created_at` DateTime64(6, 'UTC') DEFAULT now(),
+ `updated_at` DateTime64(6, 'UTC') DEFAULT now(),
+ `milestone_id` Nullable(Int64),
+ `iid` Nullable(Int64),
+ `updated_by_id` Nullable(Int64),
+ `weight` Nullable(Int64),
+ `confidential` Bool DEFAULT false,
+ `due_date` Nullable(Date32),
+ `moved_to_id` Nullable(Int64),
+ `time_estimate` Nullable(Int64) DEFAULT 0,
+ `relative_position` Nullable(Int64),
+ `last_edited_at` Nullable(DateTime64(6, 'UTC')),
+ `last_edited_by_id` Nullable(Int64),
+ `closed_at` Nullable(DateTime64(6, 'UTC')),
+ `closed_by_id` Nullable(Int64),
+ `state_id` Int8 DEFAULT 1,
+ `duplicated_to_id` Nullable(Int64),
+ `promoted_to_epic_id` Nullable(Int64),
+ `health_status` Nullable(Int8),
+ `sprint_id` Nullable(Int64),
+ `blocking_issues_count` Int64 DEFAULT 0,
+ `upvotes_count` Int64 DEFAULT 0,
+ `work_item_type_id` Int64,
+ `namespace_id` Int64,
+ `start_date` Nullable(Date32),
+ `label_ids` Array(Int64) DEFAULT [],
+ `assignee_ids` Array(Int64) DEFAULT [],
+ `custom_status_id` Int64,
+ `system_defined_status_id` Int64,
+ `version` DateTime64(6, 'UTC') DEFAULT now(),
+ `deleted` Bool DEFAULT false
+)
+ENGINE = ReplacingMergeTree(version, deleted)
+PRIMARY KEY (traversal_path, work_item_type_id, id)
+ORDER BY (traversal_path, work_item_type_id, id)
+SETTINGS index_granularity = 8192;
+
CREATE TABLE namespace_traversal_paths
(
`id` Int64 DEFAULT 0,
@@ -389,6 +432,19 @@ PRIMARY KEY id
ORDER BY id
SETTINGS index_granularity = 8192;
+CREATE TABLE siphon_issue_assignees
+(
+ `user_id` Int64,
+ `issue_id` Int64,
+ `namespace_id` Int64,
+ `_siphon_replicated_at` DateTime64(6, 'UTC') DEFAULT now(),
+ `_siphon_deleted` Bool DEFAULT false
+)
+ENGINE = ReplacingMergeTree(_siphon_replicated_at, _siphon_deleted)
+PRIMARY KEY (issue_id, user_id)
+ORDER BY (issue_id, user_id)
+SETTINGS index_granularity = 8192;
+
CREATE TABLE siphon_issues
(
`id` Int64,
@@ -446,6 +502,22 @@ PRIMARY KEY id
ORDER BY id
SETTINGS index_granularity = 8192;
+CREATE TABLE siphon_label_links
+(
+ `id` Int64,
+ `label_id` Nullable(Int64),
+ `target_id` Nullable(Int64),
+ `target_type` Nullable(String),
+ `created_at` DateTime64(6, 'UTC') DEFAULT now(),
+ `updated_at` DateTime64(6, 'UTC') DEFAULT now(),
+ `_siphon_replicated_at` DateTime64(6, 'UTC') DEFAULT now(),
+ `_siphon_deleted` Bool DEFAULT false
+)
+ENGINE = ReplacingMergeTree(_siphon_replicated_at, _siphon_deleted)
+PRIMARY KEY id
+ORDER BY id
+SETTINGS index_granularity = 8192;
+
CREATE TABLE siphon_merge_requests
(
`id` Int64,
@@ -735,6 +807,22 @@ PRIMARY KEY id
ORDER BY id
SETTINGS index_granularity = 8192;
+CREATE TABLE siphon_work_item_current_statuses
+(
+ `id` Int64,
+ `namespace_id` Int64,
+ `work_item_id` Int64,
+ `system_defined_status_id` Int64,
+ `custom_status_id` Int64,
+ `updated_at` DateTime64(6, 'UTC'),
+ `_siphon_replicated_at` DateTime64(6, 'UTC') DEFAULT now(),
+ `_siphon_deleted` Bool DEFAULT false
+)
+ENGINE = ReplacingMergeTree(_siphon_replicated_at, _siphon_deleted)
+PRIMARY KEY (work_item_id, id)
+ORDER BY (work_item_id, id)
+SETTINGS index_granularity = 8192;
+
CREATE TABLE subscription_user_add_on_assignment_versions
(
`id` UInt64,
@@ -809,6 +897,21 @@ ENGINE = AggregatingMergeTree
ORDER BY (namespace_path, user_id, assignment_id)
SETTINGS index_granularity = 8192;
+CREATE TABLE work_item_label_links
+(
+ `id` Int64,
+ `label_id` Int64,
+ `work_item_id` Int64,
+ `created_at` DateTime64(6, 'UTC'),
+ `updated_at` DateTime64(6, 'UTC'),
+ `version` DateTime64(6, 'UTC') DEFAULT now(),
+ `deleted` Bool DEFAULT false
+)
+ENGINE = ReplacingMergeTree(version, deleted)
+PRIMARY KEY (work_item_id, label_id, id)
+ORDER BY (work_item_id, label_id, id)
+SETTINGS index_granularity = 8192;
+
CREATE MATERIALIZED VIEW ci_finished_builds_aggregated_queueing_delay_percentiles_by_owner_mv TO ci_finished_builds_aggregated_queueing_delay_percentiles_by_owner
(
`started_at_bucket` DateTime('UTC'),
@@ -1129,6 +1232,185 @@ FROM cte
LEFT JOIN group_lookups ON group_lookups.id = cte.group_id
LEFT JOIN project_lookups ON project_lookups.id = cte.project_id;
+CREATE MATERIALIZED VIEW hierarchy_work_items_mv TO hierarchy_work_items
+(
+ `traversal_path` String,
+ `id` Int64,
+ `title` String,
+ `author_id` Nullable(Int64),
+ `created_at` DateTime64(6, 'UTC'),
+ `updated_at` DateTime64(6, 'UTC'),
+ `milestone_id` Nullable(Int64),
+ `iid` Nullable(Int64),
+ `updated_by_id` Nullable(Int64),
+ `weight` Nullable(Int64),
+ `confidential` Bool,
+ `due_date` Nullable(Date32),
+ `moved_to_id` Nullable(Int64),
+ `time_estimate` Nullable(Int64),
+ `relative_position` Nullable(Int64),
+ `last_edited_at` Nullable(DateTime64(6, 'UTC')),
+ `last_edited_by_id` Nullable(Int64),
+ `closed_at` Nullable(DateTime64(6, 'UTC')),
+ `closed_by_id` Nullable(Int64),
+ `state_id` Int8,
+ `duplicated_to_id` Nullable(Int64),
+ `promoted_to_epic_id` Nullable(Int64),
+ `health_status` Nullable(Int8),
+ `sprint_id` Nullable(Int64),
+ `blocking_issues_count` Int64,
+ `upvotes_count` Int64,
+ `work_item_type_id` Int64,
+ `namespace_id` Int64,
+ `start_date` Nullable(Date32),
+ `label_ids` Array(Int64),
+ `assignee_ids` Array(Int64),
+ `custom_status_id` Int64,
+ `system_defined_status_id` Int64,
+ `version` DateTime64(6, 'UTC'),
+ `deleted` Bool
+)
+AS WITH
+ cte AS
+ (
+ SELECT *
+ FROM siphon_issues
+ ),
+ namespace_paths AS
+ (
+ SELECT *
+ FROM
+ (
+ SELECT
+ id,
+ argMax(traversal_path, version) AS traversal_path,
+ argMax(deleted, version) AS deleted
+ FROM namespace_traversal_paths
+ WHERE id IN (
+ SELECT DISTINCT namespace_id
+ FROM cte
+ )
+ GROUP BY id
+ )
+ WHERE deleted = false
+ ),
+ collected_label_ids AS
+ (
+ SELECT
+ work_item_id,
+ arraySort(groupArray(label_id)) AS label_ids
+ FROM
+ (
+ SELECT
+ work_item_id,
+ label_id,
+ id,
+ argMax(deleted, version) AS deleted
+ FROM work_item_label_links
+ WHERE work_item_id IN (
+ SELECT id
+ FROM cte
+ )
+ GROUP BY
+ work_item_id,
+ label_id,
+ id
+ )
+ WHERE deleted = false
+ GROUP BY work_item_id
+ ),
+ collected_assignee_ids AS
+ (
+ SELECT
+ issue_id,
+ arraySort(groupArray(user_id)) AS user_ids
+ FROM
+ (
+ SELECT
+ issue_id,
+ user_id,
+ argMax(_siphon_deleted, _siphon_replicated_at) AS _siphon_deleted
+ FROM siphon_issue_assignees
+ WHERE issue_id IN (
+ SELECT id
+ FROM cte
+ )
+ GROUP BY
+ issue_id,
+ user_id
+ )
+ WHERE _siphon_deleted = false
+ GROUP BY issue_id
+ ),
+ collected_custom_status_records AS
+ (
+ SELECT
+ work_item_id,
+ max(system_defined_status_id) AS system_defined_status_id,
+ max(custom_status_id) AS custom_status_id
+ FROM
+ (
+ SELECT
+ work_item_id,
+ id,
+ argMax(system_defined_status_id, _siphon_replicated_at) AS system_defined_status_id,
+ argMax(custom_status_id, _siphon_replicated_at) AS custom_status_id,
+ argMax(_siphon_deleted, _siphon_replicated_at) AS _siphon_deleted
+ FROM siphon_work_item_current_statuses
+ GROUP BY
+ work_item_id,
+ id
+ )
+ WHERE _siphon_deleted = false
+ GROUP BY work_item_id
+ ),
+ finalized AS
+ (
+ SELECT
+ multiIf(cte.namespace_id != 0, namespace_paths.traversal_path, '0/') AS traversal_path,
+ cte.id AS id,
+ cte.title,
+ cte.author_id,
+ cte.created_at,
+ cte.updated_at,
+ cte.milestone_id,
+ cte.iid,
+ cte.updated_by_id,
+ cte.weight,
+ cte.confidential,
+ cte.due_date,
+ cte.moved_to_id,
+ cte.time_estimate,
+ cte.relative_position,
+ cte.last_edited_at,
+ cte.last_edited_by_id,
+ cte.closed_at,
+ cte.closed_by_id,
+ cte.state_id,
+ cte.duplicated_to_id,
+ cte.promoted_to_epic_id,
+ cte.health_status,
+ cte.sprint_id,
+ cte.blocking_issues_count,
+ cte.upvotes_count,
+ cte.work_item_type_id,
+ cte.namespace_id,
+ cte.start_date,
+ collected_label_ids.label_ids AS label_ids,
+ collected_assignee_ids.user_ids AS assignee_ids,
+ collected_custom_status_records.custom_status_id AS custom_status_id,
+ collected_custom_status_records.system_defined_status_id AS system_defined_status_id,
+ cte._siphon_replicated_at AS version,
+ cte._siphon_deleted AS deleted
+ FROM cte
+ LEFT JOIN namespace_paths ON namespace_paths.id = cte.namespace_id
+ LEFT JOIN collected_assignee_ids ON collected_assignee_ids.issue_id = cte.id
+ LEFT JOIN collected_label_ids ON collected_label_ids.work_item_id = cte.id
+ LEFT JOIN collected_custom_status_records ON collected_custom_status_records.work_item_id = cte.id
+ )
+SELECT *
+FROM finalized;
+
CREATE MATERIALIZED VIEW namespace_traversal_paths_mv TO namespace_traversal_paths
(
`id` Int64,
@@ -1203,4 +1485,25 @@ GROUP BY
namespace_path,
user_id,
purchase_id,
- add_on_name
\ No newline at end of file
+ add_on_name;
+
+CREATE MATERIALIZED VIEW work_item_label_links_mv TO work_item_label_links
+(
+ `id` Int64,
+ `label_id` Nullable(Int64),
+ `work_item_id` Nullable(Int64),
+ `created_at` DateTime64(6, 'UTC'),
+ `updated_at` DateTime64(6, 'UTC'),
+ `version` DateTime64(6, 'UTC'),
+ `deleted` Bool
+)
+AS SELECT
+ id,
+ label_id,
+ target_id AS work_item_id,
+ created_at,
+ updated_at,
+ _siphon_replicated_at AS version,
+ _siphon_deleted AS deleted
+FROM siphon_label_links
+WHERE (target_type = 'Issue') AND (target_id IS NOT NULL) AND (label_id IS NOT NULL)
\ No newline at end of file
diff --git a/db/click_house/migrate/main/20250707095655_create_siphon_issue_assignees.rb b/db/click_house/migrate/main/20250707095655_create_siphon_issue_assignees.rb
new file mode 100644
index 00000000000..5c8a11d6cb5
--- /dev/null
+++ b/db/click_house/migrate/main/20250707095655_create_siphon_issue_assignees.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+class CreateSiphonIssueAssignees < ClickHouse::Migration
+ def up
+ execute <<-SQL
+ CREATE TABLE IF NOT EXISTS siphon_issue_assignees
+ (
+ user_id Int64,
+ issue_id Int64,
+ namespace_id Int64,
+ _siphon_replicated_at DateTime64(6, 'UTC') DEFAULT now(),
+ _siphon_deleted Bool DEFAULT FALSE
+ )
+ ENGINE = ReplacingMergeTree(_siphon_replicated_at, _siphon_deleted)
+ PRIMARY KEY (issue_id, user_id)
+ SQL
+ end
+
+ def down
+ execute <<-SQL
+ DROP TABLE IF EXISTS siphon_issue_assignees
+ SQL
+ end
+end
diff --git a/db/click_house/migrate/main/20250707095824_create_siphon_label_links.rb b/db/click_house/migrate/main/20250707095824_create_siphon_label_links.rb
new file mode 100644
index 00000000000..41685402685
--- /dev/null
+++ b/db/click_house/migrate/main/20250707095824_create_siphon_label_links.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+class CreateSiphonLabelLinks < ClickHouse::Migration
+ def up
+ execute <<-SQL
+ CREATE TABLE IF NOT EXISTS siphon_label_links
+ (
+ id Int64,
+ label_id Nullable(Int64),
+ target_id Nullable(Int64),
+ target_type Nullable(String),
+ created_at DateTime64(6, 'UTC') DEFAULT now(),
+ updated_at DateTime64(6, 'UTC') DEFAULT now(),
+ _siphon_replicated_at DateTime64(6, 'UTC') DEFAULT now(),
+ _siphon_deleted Bool DEFAULT FALSE
+ )
+ ENGINE = ReplacingMergeTree(_siphon_replicated_at, _siphon_deleted)
+ PRIMARY KEY id
+ SQL
+ end
+
+ def down
+ execute <<-SQL
+ DROP TABLE IF EXISTS siphon_label_links
+ SQL
+ end
+end
diff --git a/db/click_house/migrate/main/20250707100345_create_work_item_label_links.rb b/db/click_house/migrate/main/20250707100345_create_work_item_label_links.rb
new file mode 100644
index 00000000000..53143a70c5b
--- /dev/null
+++ b/db/click_house/migrate/main/20250707100345_create_work_item_label_links.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+class CreateWorkItemLabelLinks < ClickHouse::Migration
+ def up
+ execute <<-SQL
+ CREATE TABLE IF NOT EXISTS work_item_label_links
+ (
+ id Int64,
+ label_id Int64,
+ work_item_id Int64,
+ created_at DateTime64(6, 'UTC'),
+ updated_at DateTime64(6, 'UTC'),
+ version DateTime64(6, 'UTC') DEFAULT now(),
+ deleted Bool DEFAULT FALSE
+ )
+ ENGINE = ReplacingMergeTree(version, deleted)
+ PRIMARY KEY (work_item_id, label_id, id)
+ SQL
+
+ execute <<-SQL
+ CREATE MATERIALIZED VIEW IF NOT EXISTS work_item_label_links_mv
+ TO work_item_label_links
+ AS
+ SELECT
+ id,
+ label_id,
+ target_id AS work_item_id,
+ created_at,
+ updated_at,
+ _siphon_replicated_at AS version,
+ _siphon_deleted AS deleted
+ FROM siphon_label_links
+ WHERE
+ target_type = 'Issue' AND
+ target_id IS NOT NULL AND
+ label_id IS NOT NULL
+ SQL
+ end
+
+ def down
+ execute <<-SQL
+ DROP VIEW IF EXISTS work_item_label_links_mv
+ SQL
+
+ execute <<-SQL
+ DROP TABLE IF EXISTS work_item_label_links
+ SQL
+ end
+end
diff --git a/db/click_house/migrate/main/20250707100721_create_siphon_work_item_current_statuses.rb b/db/click_house/migrate/main/20250707100721_create_siphon_work_item_current_statuses.rb
new file mode 100644
index 00000000000..8b7afc3a244
--- /dev/null
+++ b/db/click_house/migrate/main/20250707100721_create_siphon_work_item_current_statuses.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+class CreateSiphonWorkItemCurrentStatuses < ClickHouse::Migration
+ def up
+ execute <<-SQL
+ CREATE TABLE IF NOT EXISTS siphon_work_item_current_statuses
+ (
+ id Int64,
+ namespace_id Int64,
+ work_item_id Int64,
+ system_defined_status_id Int64,
+ custom_status_id Int64,
+ updated_at DateTime64(6, 'UTC'),
+ _siphon_replicated_at DateTime64(6, 'UTC') DEFAULT now(),
+ _siphon_deleted Bool DEFAULT FALSE
+ )
+ ENGINE = ReplacingMergeTree(_siphon_replicated_at, _siphon_deleted)
+ PRIMARY KEY (work_item_id, id)
+ SQL
+ end
+
+ def down
+ execute <<-SQL
+ DROP TABLE IF EXISTS siphon_work_item_current_statuses
+ SQL
+ end
+end
diff --git a/db/click_house/migrate/main/20250707164341_create_hierarchy_work_items_table.rb b/db/click_house/migrate/main/20250707164341_create_hierarchy_work_items_table.rb
new file mode 100644
index 00000000000..da0c5f985b0
--- /dev/null
+++ b/db/click_house/migrate/main/20250707164341_create_hierarchy_work_items_table.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+class CreateHierarchyWorkItemsTable < ClickHouse::Migration
+ def up
+ execute <<-SQL
+ CREATE TABLE IF NOT EXISTS hierarchy_work_items
+ (
+ traversal_path String,
+ id Int64,
+ title String DEFAULT '',
+ author_id Nullable(Int64),
+ created_at DateTime64(6, 'UTC') DEFAULT now(),
+ updated_at DateTime64(6, 'UTC') DEFAULT now(),
+ milestone_id Nullable(Int64),
+ iid Nullable(Int64),
+ updated_by_id Nullable(Int64),
+ weight Nullable(Int64),
+ confidential Bool DEFAULT false,
+ due_date Nullable(Date32),
+ moved_to_id Nullable(Int64),
+ time_estimate Nullable(Int64) DEFAULT 0,
+ relative_position Nullable(Int64),
+ last_edited_at Nullable(DateTime64(6, 'UTC')),
+ last_edited_by_id Nullable(Int64),
+ closed_at Nullable(DateTime64(6, 'UTC')),
+ closed_by_id Nullable(Int64),
+ state_id Int8 DEFAULT 1,
+ duplicated_to_id Nullable(Int64),
+ promoted_to_epic_id Nullable(Int64),
+ health_status Nullable(Int8),
+ sprint_id Nullable(Int64),
+ blocking_issues_count Int64 DEFAULT 0,
+ upvotes_count Int64 DEFAULT 0,
+ work_item_type_id Int64,
+ namespace_id Int64,
+ start_date Nullable(Date32),
+ label_ids Array(Int64) DEFAULT [],
+ assignee_ids Array(Int64) DEFAULT [],
+ custom_status_id Int64,
+ system_defined_status_id Int64,
+ version DateTime64(6, 'UTC') DEFAULT now(),
+ deleted Bool DEFAULT FALSE
+ )
+ ENGINE = ReplacingMergeTree(version, deleted)
+ PRIMARY KEY (traversal_path, work_item_type_id, id)
+ SQL
+ end
+
+ def down
+ execute <<-SQL
+ DROP TABLE IF EXISTS hierarchy_work_items
+ SQL
+ end
+end
diff --git a/db/click_house/migrate/main/20250707164711_create_hierarchy_work_items_mv.rb b/db/click_house/migrate/main/20250707164711_create_hierarchy_work_items_mv.rb
new file mode 100644
index 00000000000..58150a3c6e8
--- /dev/null
+++ b/db/click_house/migrate/main/20250707164711_create_hierarchy_work_items_mv.rb
@@ -0,0 +1,127 @@
+# frozen_string_literal: true
+
+class CreateHierarchyWorkItemsMv < ClickHouse::Migration
+ def up
+ execute <<-SQL
+ CREATE MATERIALIZED VIEW IF NOT EXISTS hierarchy_work_items_mv TO hierarchy_work_items
+ AS WITH
+ cte AS
+ (
+ SELECT *
+ FROM siphon_issues
+ ),
+ namespace_paths AS
+ (
+ -- look up `traversal_path` values
+ SELECT * FROM (
+ SELECT
+ id,
+ argMax(traversal_path, version) AS traversal_path,
+ argMax(deleted, version) AS deleted
+ FROM namespace_traversal_paths
+ WHERE id IN (
+ SELECT DISTINCT namespace_id
+ FROM cte
+ )
+ GROUP BY id
+ ) WHERE deleted = false
+ ),
+ collected_label_ids AS
+ (
+ SELECT work_item_id, arraySort(groupArray(label_id)) AS label_ids
+ FROM (
+ SELECT
+ work_item_id,
+ label_id,
+ id,
+ argMax(deleted, version) AS deleted
+ FROM work_item_label_links
+ WHERE work_item_id IN (SELECT id FROM cte)
+ GROUP BY work_item_id, label_id, id
+ ) WHERE deleted = false
+ GROUP BY work_item_id
+ ),
+ collected_assignee_ids AS
+ (
+ SELECT issue_id, arraySort(groupArray(user_id)) AS user_ids
+ FROM (
+ SELECT
+ issue_id,
+ user_id,
+ argMax(_siphon_deleted, _siphon_replicated_at) AS _siphon_deleted
+ FROM siphon_issue_assignees
+ WHERE issue_id IN (SELECT id FROM cte)
+ GROUP BY issue_id, user_id
+ ) WHERE _siphon_deleted = false
+ GROUP BY issue_id
+ ),
+ collected_custom_status_records AS
+ (
+ SELECT work_item_id, max(system_defined_status_id) AS system_defined_status_id, max(custom_status_id) AS custom_status_id
+ FROM (
+ SELECT
+ work_item_id,
+ id,
+ argMax(system_defined_status_id, _siphon_replicated_at) AS system_defined_status_id,
+ argMax(custom_status_id, _siphon_replicated_at) AS custom_status_id,
+ argMax(_siphon_deleted, _siphon_replicated_at) AS _siphon_deleted
+ FROM siphon_work_item_current_statuses
+ GROUP BY work_item_id, id
+ ) WHERE _siphon_deleted = false
+ GROUP BY work_item_id
+ ),
+ finalized AS
+ (
+ SELECT
+ -- handle the case where namespace_id is null
+ multiIf(cte.namespace_id != 0, namespace_paths.traversal_path, '0/') AS traversal_path,
+ cte.id AS id,
+ cte.title,
+ cte.author_id,
+ cte.created_at,
+ cte.updated_at,
+ cte.milestone_id,
+ cte.iid,
+ cte.updated_by_id,
+ cte.weight,
+ cte.confidential,
+ cte.due_date,
+ cte.moved_to_id,
+ cte.time_estimate,
+ cte.relative_position,
+ cte.last_edited_at,
+ cte.last_edited_by_id,
+ cte.closed_at,
+ cte.closed_by_id,
+ cte.state_id,
+ cte.duplicated_to_id,
+ cte.promoted_to_epic_id,
+ cte.health_status,
+ cte.sprint_id,
+ cte.blocking_issues_count,
+ cte.upvotes_count,
+ cte.work_item_type_id,
+ cte.namespace_id,
+ cte.start_date,
+ collected_label_ids.label_ids AS label_ids,
+ collected_assignee_ids.user_ids AS assignee_ids,
+ collected_custom_status_records.custom_status_id AS custom_status_id,
+ collected_custom_status_records.system_defined_status_id AS system_defined_status_id,
+ cte._siphon_replicated_at AS version,
+ cte._siphon_deleted AS deleted
+ FROM cte
+ LEFT JOIN namespace_paths ON namespace_paths.id = cte.namespace_id
+ LEFT JOIN collected_assignee_ids ON collected_assignee_ids.issue_id = cte.id
+ LEFT JOIN collected_label_ids ON collected_label_ids.work_item_id = cte.id
+ LEFT JOIN collected_custom_status_records ON collected_custom_status_records.work_item_id = cte.id
+ )
+ SELECT * FROM finalized
+ SQL
+ end
+
+ def down
+ execute <<-SQL
+ DROP VIEW IF EXISTS hierarchy_work_items_mv
+ SQL
+ end
+end
diff --git a/db/click_house/schema_migrations/main/20250707095655 b/db/click_house/schema_migrations/main/20250707095655
new file mode 100644
index 00000000000..6ab4974fb5e
--- /dev/null
+++ b/db/click_house/schema_migrations/main/20250707095655
@@ -0,0 +1 @@
+88db53812d40a7c0ba095a5e4f774783afffcf2d788161c35c18ab7d49dca0ed
\ No newline at end of file
diff --git a/db/click_house/schema_migrations/main/20250707095824 b/db/click_house/schema_migrations/main/20250707095824
new file mode 100644
index 00000000000..926c83cafcd
--- /dev/null
+++ b/db/click_house/schema_migrations/main/20250707095824
@@ -0,0 +1 @@
+6b6449aba8bdcf6b1ce1fbc0af31999e7ff19f6dfcc63a6911522eca43ea13ff
\ No newline at end of file
diff --git a/db/click_house/schema_migrations/main/20250707100345 b/db/click_house/schema_migrations/main/20250707100345
new file mode 100644
index 00000000000..8e0167d2eff
--- /dev/null
+++ b/db/click_house/schema_migrations/main/20250707100345
@@ -0,0 +1 @@
+3d0ef91b778c21def19f2c6cd44bfaf5297fff51519002a2981baef7d0d0ef6b
\ No newline at end of file
diff --git a/db/click_house/schema_migrations/main/20250707100721 b/db/click_house/schema_migrations/main/20250707100721
new file mode 100644
index 00000000000..be52622864b
--- /dev/null
+++ b/db/click_house/schema_migrations/main/20250707100721
@@ -0,0 +1 @@
+256bae23f58fdcb97a513f2cda8e50f101fb12f0345f3180f4ba00fd1f22d4e4
\ No newline at end of file
diff --git a/db/click_house/schema_migrations/main/20250707164341 b/db/click_house/schema_migrations/main/20250707164341
new file mode 100644
index 00000000000..72c4af86909
--- /dev/null
+++ b/db/click_house/schema_migrations/main/20250707164341
@@ -0,0 +1 @@
+970b3717bd692c445a95590fb199dac6b7702496ab1f9d4f38f49eefc3b8ab7e
\ No newline at end of file
diff --git a/db/click_house/schema_migrations/main/20250707164711 b/db/click_house/schema_migrations/main/20250707164711
new file mode 100644
index 00000000000..b2e96cd17b9
--- /dev/null
+++ b/db/click_house/schema_migrations/main/20250707164711
@@ -0,0 +1 @@
+b57a8a57156f4e52eaa701e70bc9a61813643442e37f4ff05aed1263f0baf47b
\ No newline at end of file
diff --git a/db/docs/batched_background_migrations/backfill_merge_request_context_commit_diff_files_project_id.yml b/db/docs/batched_background_migrations/backfill_merge_request_context_commit_diff_files_project_id.yml
index 2ec38b409d4..c309ccb9748 100644
--- a/db/docs/batched_background_migrations/backfill_merge_request_context_commit_diff_files_project_id.yml
+++ b/db/docs/batched_background_migrations/backfill_merge_request_context_commit_diff_files_project_id.yml
@@ -5,4 +5,4 @@ feature_category: code_review_workflow
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/183935
milestone: '17.11'
queued_migration_version: 20250308115805
-finalized_by: # version of the migration that finalized this BBM
+finalized_by: '20250708203406'
diff --git a/db/post_migrate/20250708203406_finalize_backfill_merge_request_context_commit_diff_files_project_id.rb b/db/post_migrate/20250708203406_finalize_backfill_merge_request_context_commit_diff_files_project_id.rb
new file mode 100644
index 00000000000..ccf4b255f31
--- /dev/null
+++ b/db/post_migrate/20250708203406_finalize_backfill_merge_request_context_commit_diff_files_project_id.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+class FinalizeBackfillMergeRequestContextCommitDiffFilesProjectId < Gitlab::Database::Migration[2.3]
+ milestone '18.2'
+ disable_ddl_transaction!
+
+ restrict_gitlab_migration gitlab_schema: :gitlab_main_cell
+
+ def up
+ ensure_batched_background_migration_is_finished(
+ job_class_name: 'BackfillMergeRequestContextCommitDiffFilesProjectId',
+ table_name: :merge_request_context_commit_diff_files,
+ column_name: :merge_request_context_commit_id,
+ job_arguments: [:project_id, :merge_request_context_commits, :project_id, :merge_request_context_commit_id],
+ finalize: true
+ )
+ end
+
+ def down; end
+end
diff --git a/db/schema_migrations/20250708203406 b/db/schema_migrations/20250708203406
new file mode 100644
index 00000000000..579cc426efd
--- /dev/null
+++ b/db/schema_migrations/20250708203406
@@ -0,0 +1 @@
+f6f7597c8d84f958c66e4ce1bc860d468ae50a5258a174eb3bea2d9dc3e24057
\ No newline at end of file
diff --git a/doc/administration/gitlab_duo_self_hosted/supported_models_and_hardware_requirements.md b/doc/administration/gitlab_duo_self_hosted/supported_models_and_hardware_requirements.md
index fa536bd8cd5..bdfd408b560 100644
--- a/doc/administration/gitlab_duo_self_hosted/supported_models_and_hardware_requirements.md
+++ b/doc/administration/gitlab_duo_self_hosted/supported_models_and_hardware_requirements.md
@@ -46,6 +46,7 @@ Support for the following GitLab-supported large language models (LLMs) is gener
| Mistral | [Mistral 7B-it v0.3](https://huggingface.co/mistralai/Mistral-7B-Instruct-v0.3) | [vLLM](supported_llm_serving_platforms.md#for-self-hosted-model-deployments) | {{< icon name="check-circle-dashed" >}} Largely compatible | {{< icon name="check-circle-filled" >}} Fully compatible | {{< icon name="dash-circle" >}} Not compatible |
| Mistral | [Mixtral 8x7B-it v0.1](https://huggingface.co/mistralai/Mixtral-8x7B-Instruct-v0.1) | [vLLM](supported_llm_serving_platforms.md#for-self-hosted-model-deployments), [AWS Bedrock](https://aws.amazon.com/bedrock/mistral/) | {{< icon name="check-circle-dashed" >}} Largely compatible | {{< icon name="check-circle-filled" >}} Fully compatible | {{< icon name="check-circle-dashed" >}} Largely compatible |
| Mistral | [Mixtral 8x22B-it v0.1](https://huggingface.co/mistralai/Mixtral-8x22B-Instruct-v0.1) | [vLLM](supported_llm_serving_platforms.md#for-self-hosted-model-deployments) | {{< icon name="check-circle-filled" >}} Fully compatible | {{< icon name="check-circle-filled" >}} Fully compatible | {{< icon name="check-circle-dashed" >}} Largely compatible |
+| Mistral | [Mistral Small 24B Instruct 2506](https://huggingface.co/mistralai/Mistral-Small-3.2-24B-Instruct-2506) | [vLLM](supported_llm_serving_platforms.md#for-self-hosted-model-deployments) | {{< icon name="check-circle-filled" >}} Fully compatible | {{< icon name="check-circle-filled" >}} Fully compatible | {{< icon name="check-circle-filled" >}} Fully compatible |
| Claude 3 | [Claude 3.5 Sonnet](https://www.anthropic.com/news/claude-3-5-sonnet) | [AWS Bedrock](https://aws.amazon.com/bedrock/claude/) | {{< icon name="check-circle-filled" >}} Fully compatible | {{< icon name="check-circle-filled" >}} Fully compatible | {{< icon name="check-circle-filled" >}} Fully compatible |
| Claude 3 | [Claude 3.7 Sonnet](https://www.anthropic.com/news/claude-3-7-sonnet) | [AWS Bedrock](https://aws.amazon.com/bedrock/claude/) | {{< icon name="check-circle-filled" >}} Fully compatible | {{< icon name="check-circle-filled" >}} Fully compatible | {{< icon name="check-circle-filled" >}} Fully compatible |
| GPT | [GPT-4 Turbo](https://learn.microsoft.com/en-us/azure/ai-services/openai/concepts/models?tabs=python-secure#gpt-4) | [Azure OpenAI](https://learn.microsoft.com/en-us/azure/ai-services/openai/overview) | {{< icon name="check-circle-filled" >}} Fully compatible | {{< icon name="check-circle-filled" >}} Fully compatible | {{< icon name="check-circle-dashed" >}} Largely compatible |
diff --git a/doc/api/graphql/reference/_index.md b/doc/api/graphql/reference/_index.md
index bb6d8a202ee..9cd17c60553 100644
--- a/doc/api/graphql/reference/_index.md
+++ b/doc/api/graphql/reference/_index.md
@@ -43484,6 +43484,7 @@ four standard [pagination arguments](#pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
| `createNote` | [`Boolean!`](#boolean) | If `true`, the user can perform `create_note` on this resource. |
+| `markNoteAsInternal` | [`Boolean!`](#boolean) | If `true`, the user can perform `mark_note_as_internal` on this resource. |
| `readWikiPage` | [`Boolean!`](#boolean) | If `true`, the user can perform `read_wiki_page` on this resource. |
### `WorkItem`
diff --git a/doc/development/database/batched_background_migrations.md b/doc/development/database/batched_background_migrations.md
index f446416e3bd..a9965f018b2 100644
--- a/doc/development/database/batched_background_migrations.md
+++ b/doc/development/database/batched_background_migrations.md
@@ -291,8 +291,9 @@ turn can be handled by defining foreign keys with cascading deletes.
### Finalize a batched background migration
Finalizing a batched background migration is done by calling
-`ensure_batched_background_migration_is_finished`, after at-least, one required stop from queuing it.
-This ensures a smooth upgrade process for GitLab Self-Managed instances.
+`ensure_batched_background_migration_is_finished`, but only if the migration was added
+in or before the last required stop. This ensures a smooth upgrade process for
+GitLab Self-Managed instances.
It is important to finalize all batched background migrations when it is safe
to do so. Leaving around old batched background migration is a form of
diff --git a/doc/user/application_security/secret_detection/detected_secrets.md b/doc/user/application_security/secret_detection/detected_secrets.md
index b3a8cce6a43..f19d0172035 100644
--- a/doc/user/application_security/secret_detection/detected_secrets.md
+++ b/doc/user/application_security/secret_detection/detected_secrets.md
@@ -18,6 +18,9 @@ This table lists the secrets detected by:
- Client-side secret detection
- Secret push protection
+Secret detection rules are updated in the [default ruleset](https://gitlab.com/gitlab-org/security-products/secret-detection/secret-detection-rules/-/tree/main).
+Detected secrets with patterns that have been removed or updated remain open so you can triage them.
+
diff --git a/gems/activerecord-gitlab/Gemfile.lock b/gems/activerecord-gitlab/Gemfile.lock
index 25b40a992de..380ae59e8e9 100644
--- a/gems/activerecord-gitlab/Gemfile.lock
+++ b/gems/activerecord-gitlab/Gemfile.lock
@@ -52,7 +52,7 @@ GEM
ast (~> 2.4.1)
racc
racc (1.7.1)
- rack (3.1.8)
+ rack (3.1.16)
rainbow (3.1.1)
regexp_parser (2.8.1)
rexml (3.3.9)
@@ -142,7 +142,7 @@ CHECKSUMS
parallel (1.23.0) sha256=27154713ad6ef32fa3dcb7788a721d6c07bca77e72443b4c6080a14145288c49
parser (3.2.2.3) sha256=10685f358ab36ffea2252dc4952e5b8fad3a297a8152a85f59adc982747b91eb
racc (1.7.1) sha256=af64124836fdd3c00e830703d7f873ea5deabde923f37006a39f5a5e0da16387
- rack (3.1.8) sha256=d3fbcbca43dc2b43c9c6d7dfbac01667ae58643c42cea10013d0da970218a1b1
+ rack (3.1.16) sha256=efb5606c351efc56b85b10c3493055d0d35209d23f44792ec4e1183eb0234635
rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a
regexp_parser (2.8.1) sha256=83f63e2bfae3db38f988c66f114485140ff1791321fd827480bc75aa42cacb8c
rexml (3.3.9) sha256=d71875b85299f341edf47d44df0212e7658cbdf35aeb69cefdb63f57af3137c9
diff --git a/gems/click_house-client/Gemfile.lock b/gems/click_house-client/Gemfile.lock
index 915ddd14f8b..7c148c7fc5e 100644
--- a/gems/click_house-client/Gemfile.lock
+++ b/gems/click_house-client/Gemfile.lock
@@ -37,7 +37,7 @@ GEM
racc
public_suffix (5.0.3)
racc (1.7.1)
- rack (3.1.8)
+ rack (3.1.16)
rainbow (3.1.1)
rake (13.0.6)
regexp_parser (2.8.1)
@@ -118,7 +118,7 @@ CHECKSUMS
parser (3.3.3.0) sha256=a2e23c90918d9b7e866b18dca2b6835f227769dd2fa8e59c5841f3389cf53eeb
public_suffix (5.0.3) sha256=337d475da2bd2ea1de0446751cb972ad43243b4b00aa8cf91cb904fa593d3259
racc (1.7.1) sha256=af64124836fdd3c00e830703d7f873ea5deabde923f37006a39f5a5e0da16387
- rack (3.1.8) sha256=d3fbcbca43dc2b43c9c6d7dfbac01667ae58643c42cea10013d0da970218a1b1
+ rack (3.1.16) sha256=efb5606c351efc56b85b10c3493055d0d35209d23f44792ec4e1183eb0234635
rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a
rake (13.0.6) sha256=5ce4bf5037b4196c24ac62834d8db1ce175470391026bd9e557d669beeb19097
regexp_parser (2.8.1) sha256=83f63e2bfae3db38f988c66f114485140ff1791321fd827480bc75aa42cacb8c
diff --git a/gems/csv_builder/Gemfile.lock b/gems/csv_builder/Gemfile.lock
index 9481b36c456..dd553cab7f2 100644
--- a/gems/csv_builder/Gemfile.lock
+++ b/gems/csv_builder/Gemfile.lock
@@ -34,7 +34,7 @@ GEM
coderay (~> 1.1)
method_source (~> 1.0)
racc (1.7.1)
- rack (3.1.8)
+ rack (3.1.16)
rainbow (3.1.1)
regexp_parser (2.8.1)
rexml (3.3.9)
@@ -112,7 +112,7 @@ CHECKSUMS
parser (3.2.2.3) sha256=10685f358ab36ffea2252dc4952e5b8fad3a297a8152a85f59adc982747b91eb
pry (0.14.2) sha256=c4fe54efedaca1d351280b45b8849af363184696fcac1c72e0415f9bdac4334d
racc (1.7.1) sha256=af64124836fdd3c00e830703d7f873ea5deabde923f37006a39f5a5e0da16387
- rack (3.1.8) sha256=d3fbcbca43dc2b43c9c6d7dfbac01667ae58643c42cea10013d0da970218a1b1
+ rack (3.1.16) sha256=efb5606c351efc56b85b10c3493055d0d35209d23f44792ec4e1183eb0234635
rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a
regexp_parser (2.8.1) sha256=83f63e2bfae3db38f988c66f114485140ff1791321fd827480bc75aa42cacb8c
rexml (3.3.9) sha256=d71875b85299f341edf47d44df0212e7658cbdf35aeb69cefdb63f57af3137c9
diff --git a/gems/gitlab-active-context/Gemfile.lock b/gems/gitlab-active-context/Gemfile.lock
index 68ce905afdc..51534a521c6 100644
--- a/gems/gitlab-active-context/Gemfile.lock
+++ b/gems/gitlab-active-context/Gemfile.lock
@@ -154,7 +154,7 @@ GEM
stringio
public_suffix (6.0.2)
racc (1.8.1)
- rack (3.1.14)
+ rack (3.1.16)
rack-session (2.1.1)
base64 (>= 0.1.0)
rack (>= 3.0.0)
@@ -354,7 +354,7 @@ CHECKSUMS
psych (5.2.4) sha256=f2d9810f7f383a6b0fbc705202851e1a55b236bcb8e168ab5dfa5741842ec7c5
public_suffix (6.0.2) sha256=bfa7cd5108066f8c9602e0d6d4114999a5df5839a63149d3e8b0f9c1d3558394
racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f
- rack (3.1.14) sha256=84613c2a8df193bb6711d9c14ecc6d5a65a7cb4312379a65e793562608944b44
+ rack (3.1.16) sha256=efb5606c351efc56b85b10c3493055d0d35209d23f44792ec4e1183eb0234635
rack-session (2.1.1) sha256=0b6dc07dea7e4b583f58a48e8b806d4c9f1c6c9214ebc202ec94562cbea2e4e9
rack-test (2.2.0) sha256=005a36692c306ac0b4a9350355ee080fd09ddef1148a5f8b2ac636c720f5c463
rackup (2.2.1) sha256=f737191fd5c5b348b7f0a4412a3b86383f88c43e13b8217b63d4c8d90b9e798d
diff --git a/gems/gitlab-backup-cli/Gemfile.lock b/gems/gitlab-backup-cli/Gemfile.lock
index 690dc5b1d25..da4380e046a 100644
--- a/gems/gitlab-backup-cli/Gemfile.lock
+++ b/gems/gitlab-backup-cli/Gemfile.lock
@@ -148,7 +148,7 @@ GEM
method_source (~> 1.0)
public_suffix (6.0.1)
racc (1.8.1)
- rack (2.2.10)
+ rack (2.2.17)
rainbow (3.1.1)
rake (13.2.1)
regexp_parser (2.9.2)
@@ -285,7 +285,7 @@ CHECKSUMS
pry (0.14.2) sha256=c4fe54efedaca1d351280b45b8849af363184696fcac1c72e0415f9bdac4334d
public_suffix (6.0.1) sha256=61d44e1cab5cbbbe5b31068481cf16976dd0dc1b6b07bd95617ef8c5e3e00c6f
racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f
- rack (2.2.10) sha256=e4a5ee3f8f2ba45614a4498114d6dc7da1c51a0f0dd810d891906ea71d3aa72b
+ rack (2.2.17) sha256=5fe02a1ca80d6fb2271dba00985ee2962d6f5620b6f46dfed89f5301ac4699dd
rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a
rake (13.2.1) sha256=46cb38dae65d7d74b6020a4ac9d48afed8eb8149c040eccf0523bec91907059d
regexp_parser (2.9.2) sha256=5a27e767ad634f8a4b544520d5cd28a0db7aa1198a5d7c9d7e11d7b3d9066446
diff --git a/gems/gitlab-database-load_balancing/Gemfile.lock b/gems/gitlab-database-load_balancing/Gemfile.lock
index df34815a306..6fe6df1c82e 100644
--- a/gems/gitlab-database-load_balancing/Gemfile.lock
+++ b/gems/gitlab-database-load_balancing/Gemfile.lock
@@ -181,7 +181,7 @@ GEM
method_source (~> 1.0)
public_suffix (5.0.3)
racc (1.8.1)
- rack (2.2.10)
+ rack (2.2.17)
rack-test (2.1.0)
rack (>= 1.3)
rails (7.0.8.7)
@@ -376,7 +376,7 @@ CHECKSUMS
pry (0.14.2) sha256=c4fe54efedaca1d351280b45b8849af363184696fcac1c72e0415f9bdac4334d
public_suffix (5.0.3) sha256=337d475da2bd2ea1de0446751cb972ad43243b4b00aa8cf91cb904fa593d3259
racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f
- rack (2.2.10) sha256=e4a5ee3f8f2ba45614a4498114d6dc7da1c51a0f0dd810d891906ea71d3aa72b
+ rack (2.2.17) sha256=5fe02a1ca80d6fb2271dba00985ee2962d6f5620b6f46dfed89f5301ac4699dd
rack-test (2.1.0) sha256=0c61fc61904049d691922ea4bb99e28004ed3f43aa5cfd495024cc345f125dfb
rails (7.0.8.7) sha256=5e67ed4dd915746349bfb8c7ae2f531d3a36eb68fbe2f60ede02a0500715cded
rails-dom-testing (2.2.0) sha256=e515712e48df1f687a1d7c380fd7b07b8558faa26464474da64183a7426fa93b
diff --git a/gems/gitlab-database-lock_retries/Gemfile.lock b/gems/gitlab-database-lock_retries/Gemfile.lock
index 85b86b56963..7aa7aee339a 100644
--- a/gems/gitlab-database-lock_retries/Gemfile.lock
+++ b/gems/gitlab-database-lock_retries/Gemfile.lock
@@ -45,7 +45,7 @@ GEM
ast (~> 2.4.1)
racc
racc (1.7.3)
- rack (3.1.8)
+ rack (3.1.16)
rainbow (3.1.1)
regexp_parser (2.8.2)
rexml (3.3.9)
@@ -127,7 +127,7 @@ CHECKSUMS
parallel (1.23.0) sha256=27154713ad6ef32fa3dcb7788a721d6c07bca77e72443b4c6080a14145288c49
parser (3.2.2.4) sha256=edbe6751f85599c8152173ccadbd708f444b7214de2a1d4969441a68e06ac964
racc (1.7.3) sha256=b785ab8a30ec43bce073c51dbbe791fd27000f68d1c996c95da98bf685316905
- rack (3.1.8) sha256=d3fbcbca43dc2b43c9c6d7dfbac01667ae58643c42cea10013d0da970218a1b1
+ rack (3.1.16) sha256=efb5606c351efc56b85b10c3493055d0d35209d23f44792ec4e1183eb0234635
rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a
regexp_parser (2.8.2) sha256=5e65506e536e4f14ce2cd98a3daecf20b88ac77b6268412928bec98c872e2ab5
rexml (3.3.9) sha256=d71875b85299f341edf47d44df0212e7658cbdf35aeb69cefdb63f57af3137c9
diff --git a/gems/gitlab-housekeeper/Gemfile.lock b/gems/gitlab-housekeeper/Gemfile.lock
index e45b312fcbf..19998429405 100644
--- a/gems/gitlab-housekeeper/Gemfile.lock
+++ b/gems/gitlab-housekeeper/Gemfile.lock
@@ -131,7 +131,7 @@ GEM
stringio
public_suffix (5.0.3)
racc (1.7.3)
- rack (3.1.8)
+ rack (3.1.16)
rack-session (2.0.0)
rack (>= 3.0.0)
rack-test (2.1.0)
@@ -317,7 +317,7 @@ CHECKSUMS
psych (5.2.2) sha256=a4a9477c85d3e858086c38cf64e7096abe40d1b1eed248b01020dec0ff9906ab
public_suffix (5.0.3) sha256=337d475da2bd2ea1de0446751cb972ad43243b4b00aa8cf91cb904fa593d3259
racc (1.7.3) sha256=b785ab8a30ec43bce073c51dbbe791fd27000f68d1c996c95da98bf685316905
- rack (3.1.8) sha256=d3fbcbca43dc2b43c9c6d7dfbac01667ae58643c42cea10013d0da970218a1b1
+ rack (3.1.16) sha256=efb5606c351efc56b85b10c3493055d0d35209d23f44792ec4e1183eb0234635
rack-session (2.0.0) sha256=db04b2063e180369192a9046b4559af311990af38c6a93d4c600cee4eb6d4e81
rack-test (2.1.0) sha256=0c61fc61904049d691922ea4bb99e28004ed3f43aa5cfd495024cc345f125dfb
rackup (2.2.1) sha256=f737191fd5c5b348b7f0a4412a3b86383f88c43e13b8217b63d4c8d90b9e798d
diff --git a/gems/gitlab-http/Gemfile.lock b/gems/gitlab-http/Gemfile.lock
index ee878cc2dbb..d850b203a5c 100644
--- a/gems/gitlab-http/Gemfile.lock
+++ b/gems/gitlab-http/Gemfile.lock
@@ -120,7 +120,7 @@ GEM
unparser
public_suffix (5.0.1)
racc (1.8.1)
- rack (2.2.10)
+ rack (2.2.17)
rack-test (2.1.0)
rack (>= 1.3)
rails-dom-testing (2.0.3)
@@ -285,7 +285,7 @@ CHECKSUMS
proc_to_ast (0.2.0) sha256=4bb446419c3878c21d8792f8a129616690168f636b9e460b5a0ed26dd6680bbe
public_suffix (5.0.1) sha256=65603917ff4ecb32f499f42c14951aeed2380054fa7fc51758fc0a8d455fe043
racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f
- rack (2.2.10) sha256=e4a5ee3f8f2ba45614a4498114d6dc7da1c51a0f0dd810d891906ea71d3aa72b
+ rack (2.2.17) sha256=5fe02a1ca80d6fb2271dba00985ee2962d6f5620b6f46dfed89f5301ac4699dd
rack-test (2.1.0) sha256=0c61fc61904049d691922ea4bb99e28004ed3f43aa5cfd495024cc345f125dfb
rails-dom-testing (2.0.3) sha256=b140c4f39f6e609c8113137b9a60dfc2ecb89864e496f87f23a68b3b8f12d8d1
rails-html-sanitizer (1.6.1) sha256=e3d2fb10339f03b802e39c7f6cac28c54fd404d3f65ae39c31cca9d150c5cbf0
diff --git a/gems/gitlab-rspec/Gemfile.lock b/gems/gitlab-rspec/Gemfile.lock
index c3c44189ff9..d0ea5ba4cf6 100644
--- a/gems/gitlab-rspec/Gemfile.lock
+++ b/gems/gitlab-rspec/Gemfile.lock
@@ -88,7 +88,7 @@ GEM
parser
unparser
racc (1.8.1)
- rack (2.2.10)
+ rack (2.2.17)
rack-test (2.1.0)
rack (>= 1.3)
rails-dom-testing (2.0.3)
@@ -242,7 +242,7 @@ CHECKSUMS
parser (3.2.2.1) sha256=1d6542b6d3c5e15bedb500fa68eb937aa0eaae644eb0eda43e9a1fa9b54dc821
proc_to_ast (0.1.0) sha256=92a73fa66e2250a83f8589f818b0751bcf227c68f85916202df7af85082f8691
racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f
- rack (2.2.10) sha256=e4a5ee3f8f2ba45614a4498114d6dc7da1c51a0f0dd810d891906ea71d3aa72b
+ rack (2.2.17) sha256=5fe02a1ca80d6fb2271dba00985ee2962d6f5620b6f46dfed89f5301ac4699dd
rack-test (2.1.0) sha256=0c61fc61904049d691922ea4bb99e28004ed3f43aa5cfd495024cc345f125dfb
rails-dom-testing (2.0.3) sha256=b140c4f39f6e609c8113137b9a60dfc2ecb89864e496f87f23a68b3b8f12d8d1
rails-html-sanitizer (1.6.1) sha256=e3d2fb10339f03b802e39c7f6cac28c54fd404d3f65ae39c31cca9d150c5cbf0
diff --git a/gems/gitlab-rspec_flaky/Gemfile.lock b/gems/gitlab-rspec_flaky/Gemfile.lock
index 3e2a36665fa..82091f585cc 100644
--- a/gems/gitlab-rspec_flaky/Gemfile.lock
+++ b/gems/gitlab-rspec_flaky/Gemfile.lock
@@ -69,7 +69,7 @@ GEM
parser
unparser
racc (1.7.1)
- rack (3.1.8)
+ rack (3.1.16)
rainbow (3.1.1)
regexp_parser (2.8.1)
rexml (3.3.9)
@@ -175,7 +175,7 @@ CHECKSUMS
parser (3.2.2.3) sha256=10685f358ab36ffea2252dc4952e5b8fad3a297a8152a85f59adc982747b91eb
proc_to_ast (0.1.0) sha256=92a73fa66e2250a83f8589f818b0751bcf227c68f85916202df7af85082f8691
racc (1.7.1) sha256=af64124836fdd3c00e830703d7f873ea5deabde923f37006a39f5a5e0da16387
- rack (3.1.8) sha256=d3fbcbca43dc2b43c9c6d7dfbac01667ae58643c42cea10013d0da970218a1b1
+ rack (3.1.16) sha256=efb5606c351efc56b85b10c3493055d0d35209d23f44792ec4e1183eb0234635
rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a
regexp_parser (2.8.1) sha256=83f63e2bfae3db38f988c66f114485140ff1791321fd827480bc75aa42cacb8c
rexml (3.3.9) sha256=d71875b85299f341edf47d44df0212e7658cbdf35aeb69cefdb63f57af3137c9
diff --git a/gems/gitlab-safe_request_store/Gemfile.lock b/gems/gitlab-safe_request_store/Gemfile.lock
index afaa3d4e630..9cc66fc76b2 100644
--- a/gems/gitlab-safe_request_store/Gemfile.lock
+++ b/gems/gitlab-safe_request_store/Gemfile.lock
@@ -36,7 +36,7 @@ GEM
coderay (~> 1.1)
method_source (~> 1.0)
racc (1.6.2)
- rack (2.2.10)
+ rack (2.2.17)
rainbow (3.1.1)
regexp_parser (2.7.0)
request_store (1.5.1)
@@ -116,7 +116,7 @@ CHECKSUMS
parser (3.2.2.3) sha256=10685f358ab36ffea2252dc4952e5b8fad3a297a8152a85f59adc982747b91eb
pry (0.14.2) sha256=c4fe54efedaca1d351280b45b8849af363184696fcac1c72e0415f9bdac4334d
racc (1.6.2) sha256=58d26b3666382396fea84d33dc0639b7ee8d704156a52f8f22681f07b2f94f26
- rack (2.2.10) sha256=e4a5ee3f8f2ba45614a4498114d6dc7da1c51a0f0dd810d891906ea71d3aa72b
+ rack (2.2.17) sha256=5fe02a1ca80d6fb2271dba00985ee2962d6f5620b6f46dfed89f5301ac4699dd
rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a
regexp_parser (2.7.0) sha256=f8b8b7f34cc53c907fad6aec2b9da996a4311a0ddd92f3bfd3b999de5420c234
request_store (1.5.1) sha256=07a204d161590789f2b1d27f9f0eadcdecd6d868cb2f03240250e1bc747df78e
diff --git a/gems/gitlab-schema-validation/Gemfile.lock b/gems/gitlab-schema-validation/Gemfile.lock
index 609736a0e5a..9c22e0a6c4b 100644
--- a/gems/gitlab-schema-validation/Gemfile.lock
+++ b/gems/gitlab-schema-validation/Gemfile.lock
@@ -59,7 +59,7 @@ GEM
coderay (~> 1.1)
method_source (~> 1.0)
racc (1.7.1)
- rack (3.1.8)
+ rack (3.1.16)
rainbow (3.1.1)
rake (13.3.0)
regexp_parser (2.10.0)
@@ -175,7 +175,7 @@ CHECKSUMS
proc_to_ast (0.1.0) sha256=92a73fa66e2250a83f8589f818b0751bcf227c68f85916202df7af85082f8691
pry (0.14.2) sha256=c4fe54efedaca1d351280b45b8849af363184696fcac1c72e0415f9bdac4334d
racc (1.7.1) sha256=af64124836fdd3c00e830703d7f873ea5deabde923f37006a39f5a5e0da16387
- rack (3.1.8) sha256=d3fbcbca43dc2b43c9c6d7dfbac01667ae58643c42cea10013d0da970218a1b1
+ rack (3.1.16) sha256=efb5606c351efc56b85b10c3493055d0d35209d23f44792ec4e1183eb0234635
rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a
rake (13.3.0) sha256=96f5092d786ff412c62fde76f793cc0541bd84d2eb579caa529aa8a059934493
regexp_parser (2.10.0) sha256=cb6f0ddde88772cd64bff1dbbf68df66d376043fe2e66a9ef77fcb1b0c548c61
diff --git a/gems/gitlab-utils/Gemfile.lock b/gems/gitlab-utils/Gemfile.lock
index 68b9f92b159..77135f05c61 100644
--- a/gems/gitlab-utils/Gemfile.lock
+++ b/gems/gitlab-utils/Gemfile.lock
@@ -101,7 +101,7 @@ GEM
unparser
public_suffix (5.0.0)
racc (1.8.1)
- rack (2.2.10)
+ rack (2.2.17)
rack-test (2.1.0)
rack (>= 1.3)
rails-dom-testing (2.0.3)
@@ -260,7 +260,7 @@ CHECKSUMS
proc_to_ast (0.1.0) sha256=92a73fa66e2250a83f8589f818b0751bcf227c68f85916202df7af85082f8691
public_suffix (5.0.0) sha256=26ee4fbce33ada25eb117ac71f2c24bf4d8b3414ab6b34f05b4708a3e90f1c6b
racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f
- rack (2.2.10) sha256=e4a5ee3f8f2ba45614a4498114d6dc7da1c51a0f0dd810d891906ea71d3aa72b
+ rack (2.2.17) sha256=5fe02a1ca80d6fb2271dba00985ee2962d6f5620b6f46dfed89f5301ac4699dd
rack-test (2.1.0) sha256=0c61fc61904049d691922ea4bb99e28004ed3f43aa5cfd495024cc345f125dfb
rails-dom-testing (2.0.3) sha256=b140c4f39f6e609c8113137b9a60dfc2ecb89864e496f87f23a68b3b8f12d8d1
rails-html-sanitizer (1.6.1) sha256=e3d2fb10339f03b802e39c7f6cac28c54fd404d3f65ae39c31cca9d150c5cbf0
diff --git a/gems/ipynbdiff/Gemfile.lock b/gems/ipynbdiff/Gemfile.lock
index a6e68390182..ebdf4132bcb 100644
--- a/gems/ipynbdiff/Gemfile.lock
+++ b/gems/ipynbdiff/Gemfile.lock
@@ -53,7 +53,7 @@ GEM
coderay (~> 1.1)
method_source (~> 1.0)
racc (1.7.1)
- rack (3.1.8)
+ rack (3.1.16)
rainbow (3.1.1)
rake (13.0.6)
regexp_parser (2.8.1)
@@ -143,6 +143,7 @@ CHECKSUMS
activesupport (7.0.8.7) sha256=df4702375de924aae81709c831605317c5417f0bd9e502a0373ff84a067204ff
ast (2.4.2) sha256=1e280232e6a33754cde542bc5ef85520b74db2aac73ec14acef453784447cc12
benchmark-memory (0.2.0) sha256=ca1e436433b09535ee8f64f80600a5edb407cff1f6ac70e089ca238118e6ab5c
+ bigdecimal (3.1.9) sha256=2ffc742031521ad69c2dfc815a98e426a230a3d22aeac1995826a75dabfad8cc
binding_of_caller (1.0.0) sha256=3aad25d1d538fc6e7972978f9bf512ccd992784009947c81633bea776713161d
coderay (1.1.3) sha256=dc530018a4684512f8f38143cd2a096c9f02a1fc2459edcfe534787a7fc77d4b
concurrent-ruby (1.2.2) sha256=3879119b8b75e3b62616acc256c64a134d0b0a7a9a3fcba5a233025bcde22c4f
@@ -157,13 +158,14 @@ CHECKSUMS
memory_profiler (1.0.0) sha256=fbb8c010822f79dd3f346f79297eeb8f1dc25c1c9e8dd9db8694649f82531869
method_source (1.0.0) sha256=d779455a2b5666a079ce58577bfad8534f571af7cec8107f4dce328f0981dede
minitest (5.18.1) sha256=ab5ee381871aaddc3a6aa2a6abcab5c4590fec9affc20947d63f312a0fe4e9cd
- oj (3.13.23) sha256=206dfdc4020ad9974705037f269cfba211d61b7662a58c717cce771829ccef51
+ oj (3.16.10) sha256=7f26bed974e331e16d579b470b0865010757f6fe6ee30ea9b67df653fbe13d7c
+ ostruct (0.6.1) sha256=09a3fb7ecc1fa4039f25418cc05ae9c82bd520472c5c6a6f515f03e4988cb817
parallel (1.23.0) sha256=27154713ad6ef32fa3dcb7788a721d6c07bca77e72443b4c6080a14145288c49
parser (3.2.2.3) sha256=10685f358ab36ffea2252dc4952e5b8fad3a297a8152a85f59adc982747b91eb
proc_to_ast (0.1.0) sha256=92a73fa66e2250a83f8589f818b0751bcf227c68f85916202df7af85082f8691
pry (0.14.1) sha256=99b6df0665875dd5a39d85e0150aa5a12e2bb4fef401b6c4f64d32ee502f8454
racc (1.7.1) sha256=af64124836fdd3c00e830703d7f873ea5deabde923f37006a39f5a5e0da16387
- rack (3.1.8) sha256=d3fbcbca43dc2b43c9c6d7dfbac01667ae58643c42cea10013d0da970218a1b1
+ rack (3.1.16) sha256=efb5606c351efc56b85b10c3493055d0d35209d23f44792ec4e1183eb0234635
rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a
rake (13.0.6) sha256=5ce4bf5037b4196c24ac62834d8db1ce175470391026bd9e557d669beeb19097
regexp_parser (2.8.1) sha256=83f63e2bfae3db38f988c66f114485140ff1791321fd827480bc75aa42cacb8c
diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb
index c26ae704652..efd0563abdb 100644
--- a/lib/gitlab/gon_helper.rb
+++ b/lib/gitlab/gon_helper.rb
@@ -98,7 +98,6 @@ module Gitlab
push_frontend_feature_flag(:new_project_creation_form, current_user, type: :wip)
push_frontend_feature_flag(:work_items_client_side_boards, current_user)
push_frontend_feature_flag(:glql_work_items, current_user, type: :wip)
- push_frontend_feature_flag(:continue_indented_text, current_user)
end
# Exposes the state of a feature flag to the frontend code.
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 33f22e38fa4..ac7f6352747 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -43426,6 +43426,9 @@ msgstr ""
msgid "One or more of your personal access tokens will expire in %{days_to_expire} days or less:"
msgstr ""
+msgid "One-time password authenticator (OTP)"
+msgstr ""
+
msgid "One-time password authenticator has been deleted!"
msgstr ""
@@ -51406,6 +51409,9 @@ msgstr ""
msgid "Register a one-time password authenticator or a WebAuthn device first."
msgstr ""
+msgid "Register authenticator"
+msgstr ""
+
msgid "Register device"
msgstr ""
diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock
index 6822666ca8d..64c891034fe 100644
--- a/qa/Gemfile.lock
+++ b/qa/Gemfile.lock
@@ -262,7 +262,7 @@ GEM
pry (>= 0.13, < 0.16)
public_suffix (6.0.0)
racc (1.8.1)
- rack (2.2.10)
+ rack (2.2.17)
rack-test (1.1.0)
rack (>= 1.0, < 3)
rails-dom-testing (2.2.0)
diff --git a/qa/qa/page/profile/two_factor_auth.rb b/qa/qa/page/profile/two_factor_auth.rb
index cdfb78acc93..45c9cde2894 100644
--- a/qa/qa/page/profile/two_factor_auth.rb
+++ b/qa/qa/page/profile/two_factor_auth.rb
@@ -8,6 +8,15 @@ module QA
element 'configure-it-later-button'
end
+ view 'app/views/profiles/two_factor_auths/_otp_registration.html.haml' do
+ element 'register-otp-authenticator-button'
+ element 'otp-secret-content'
+ element 'pin-code-field'
+ element 'current-password-field'
+ element 'register-2fa-app-button'
+ end
+
+ # Remove when we delete the redesign_user_account_otp feature flag
view 'app/views/profiles/two_factor_auths/show.html.haml' do
element 'otp-secret-content'
element 'pin-code-field'
@@ -32,6 +41,8 @@ module QA
end
def otp_secret_content
+ click_register_otp_authenticator_button if Runtime::Feature.enabled?(:redesign_user_account_otp)
+
find_element('otp-secret-content').text.gsub('Key:', '').delete(' ')
end
@@ -43,6 +54,10 @@ module QA
fill_element('current-password-field', password)
end
+ def click_register_otp_authenticator_button
+ click_element 'register-otp-authenticator-button'
+ end
+
def click_register_2fa_app_button
click_element 'register-2fa-app-button'
end
diff --git a/spec/controllers/profiles/two_factor_auths_controller_spec.rb b/spec/controllers/profiles/two_factor_auths_controller_spec.rb
index ce85e166b49..b7906f65229 100644
--- a/spec/controllers/profiles/two_factor_auths_controller_spec.rb
+++ b/spec/controllers/profiles/two_factor_auths_controller_spec.rb
@@ -221,7 +221,7 @@ RSpec.describe Profiles::TwoFactorAuthsController, feature_category: :system_acc
it 'assigns error' do
go
- expect(assigns[:error]).to eq({ message: 'Invalid pin code.' })
+ expect(assigns[:otp_error]).to eq({ message: 'Invalid pin code.' })
end
it 'assigns qr_code' do
diff --git a/spec/features/profiles/two_factor_auths_spec.rb b/spec/features/profiles/two_factor_auths_spec.rb
index cd65055d090..6a5151d03a5 100644
--- a/spec/features/profiles/two_factor_auths_spec.rb
+++ b/spec/features/profiles/two_factor_auths_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe 'Two factor auths', feature_category: :system_access do
include Spec::Support::Helpers::ModalHelpers
+ include Features::TwoFactorHelpers
context 'when signed in' do
let(:invalid_current_pwd_msg) { 'You must provide a valid current password' }
@@ -18,11 +19,11 @@ RSpec.describe 'Two factor auths', feature_category: :system_access do
it 'requires the current password to set up two factor authentication', :js do
visit profile_two_factor_auth_path
- register_2fa(user.current_otp, '123')
+ otp_authenticator_registration(user.current_otp, '123')
expect(page).to have_selector('.gl-alert-title', text: invalid_current_pwd_msg, count: 1)
- register_2fa(user.reload.current_otp, user.password)
+ otp_authenticator_registration(user.reload.current_otp, user.password)
expect(page).to have_content('Please copy, download, or print your recovery codes before proceeding.')
@@ -38,8 +39,7 @@ RSpec.describe 'Two factor auths', feature_category: :system_access do
it 'does not require the current password to set up two factor authentication', :js do
visit profile_two_factor_auth_path
- fill_in 'pin_code', with: user.current_otp
- click_button 'Register with two-factor app'
+ otp_authenticator_registration(user.current_otp)
expect(page).to have_content('Please copy, download, or print your recovery codes before proceeding.')
@@ -56,8 +56,7 @@ RSpec.describe 'Two factor auths', feature_category: :system_access do
it 'renders a error alert with a link to the troubleshooting section' do
visit profile_two_factor_auth_path
- fill_in 'pin_code', with: '123'
- click_button 'Register with two-factor app'
+ otp_authenticator_registration('123')
expect(page).to have_link('Try the troubleshooting steps here.', href: help_page_path('user/profile/account/two_factor_authentication_troubleshooting.md'))
end
@@ -119,8 +118,7 @@ RSpec.describe 'Two factor auths', feature_category: :system_access do
it 'renders alert for global settings' do
visit profile_two_factor_auth_path
- fill_in 'pin_code', with: '123'
- click_button 'Register with two-factor app'
+ otp_authenticator_registration('123')
expect(page).to have_content('The global settings require you to enable Two-Factor Authentication for your account. You need to do this before ')
end
@@ -132,7 +130,7 @@ RSpec.describe 'Two factor auths', feature_category: :system_access do
it 'renders a error alert with a link to the troubleshooting section' do
visit profile_two_factor_auth_path
- register_2fa(user.current_otp, 'abc')
+ otp_authenticator_registration(user.current_otp, 'abc')
click_button 'Register with two-factor app'
expect(page).to have_content(
@@ -149,12 +147,12 @@ RSpec.describe 'Two factor auths', feature_category: :system_access do
it 'requires the current_password to delete the OTP authenticator', :js do
visit profile_two_factor_auth_path
- click_button _('Delete one-time password authenticator')
+ find_button(_('Delete one-time password authenticator')).click
modal_submit('wrong_password')
expect(page).to have_selector('.gl-alert-title', text: invalid_current_pwd_msg, count: 1)
- click_button _('Delete one-time password authenticator')
+ find_button(_('Delete one-time password authenticator')).click
modal_submit(user.password)
expect(page).to have_content(_('One-time password authenticator has been deleted!'))
@@ -195,7 +193,7 @@ RSpec.describe 'Two factor auths', feature_category: :system_access do
it 'does not require the current_password to delete the OTP authenticator', :js do
visit profile_two_factor_auth_path
- click_button _('Delete one-time password authenticator')
+ find_button(_('Delete one-time password authenticator')).click
modal_submit_without_password
expect(page).to have_content(_('One-time password authenticator has been deleted!'))
@@ -222,13 +220,6 @@ RSpec.describe 'Two factor auths', feature_category: :system_access do
end
end
- def register_2fa(pin, password)
- fill_in 'pin_code', with: pin
- fill_in 'current_password', with: password
-
- click_button 'Register with two-factor app'
- end
-
def modal_submit(password)
within_modal do
fill_in 'current_password', with: password
diff --git a/spec/features/users/login_spec.rb b/spec/features/users/login_spec.rb
index 9a3a7bab081..c962ef3aa7b 100644
--- a/spec/features/users/login_spec.rb
+++ b/spec/features/users/login_spec.rb
@@ -6,6 +6,7 @@ RSpec.describe 'Login', :with_current_organization, :clean_gitlab_redis_sessions
include TermsHelper
include UserLoginHelper
include SessionHelpers
+ include Features::TwoFactorHelpers
before do
stub_authentication_activity_metrics(debug: true)
@@ -1006,15 +1007,12 @@ RSpec.describe 'Login', :with_current_organization, :clean_gitlab_redis_sessions
# page is shown.
wait_for_requests
+ click_button _('Register authenticator')
otp_secret = page.find('.two-factor-secret').text.gsub('Key:', '').delete(' ')
current_otp = ROTP::TOTP.new(otp_secret).now
+ click_button _('Cancel')
- fill_in 'pin_code', with: current_otp
- fill_in 'current_password', with: user.password
-
- click_button 'Register with two-factor app'
- click_button 'Copy codes'
- click_link 'Proceed'
+ otp_authenticator_registration_and_copy_codes(current_otp, user.password)
expect(page).to have_current_path(profile_account_path, ignore_query: true)
expect(page).to have_content('You have set up 2FA for your account! If you lose access to your 2FA device, you can use your recovery codes to access your account. Alternatively, if you upload an SSH key, you can use that key to generate additional recovery codes.')
diff --git a/spec/features/work_items/new/create_issue_work_item_spec.rb b/spec/features/work_items/new/create_issue_work_item_spec.rb
deleted file mode 100644
index 78ded40d5ac..00000000000
--- a/spec/features/work_items/new/create_issue_work_item_spec.rb
+++ /dev/null
@@ -1,44 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe 'Create issue work item', :js, feature_category: :team_planning do
- let_it_be(:user) { create(:user) }
- let_it_be(:project) { create(:project, :public, developers: user) }
-
- context 'when on new work items page' do
- before do
- sign_in(user)
- visit "#{project_path(project)}/-/work_items/new"
- end
-
- context 'when "Issue" is selected from drop down' do
- before do
- select 'Issue', from: 'Type'
- end
-
- it 'creates an issue work item', :aggregate_failures do
- # check all the widgets are rendered
- expect(page).to have_selector('[data-testid="work-item-title-input"]')
- expect(page).to have_selector('[data-testid="work-item-description-wrapper"]')
- expect(page).to have_selector('[data-testid="work-item-assignees"]')
- expect(page).to have_selector('[data-testid="work-item-labels"]')
- expect(page).to have_selector('[data-testid="work-item-milestone"]')
-
- send_keys 'I am a new issue'
- click_button 'Create issue'
-
- expect(page).to have_css('h1', text: 'I am a new issue')
- expect(page).to have_text 'Issue created'
- end
-
- context 'when using keyboard' do
- it 'supports shortcuts' do
- find('body').native.send_key('l')
-
- expect(find('.js-labels')).to have_selector('.gl-new-dropdown-panel')
- end
- end
- end
- end
-end
diff --git a/spec/features/work_items/new/user_creates_work_item_spec.rb b/spec/features/work_items/new/user_creates_work_item_spec.rb
new file mode 100644
index 00000000000..b72dab5597e
--- /dev/null
+++ b/spec/features/work_items/new/user_creates_work_item_spec.rb
@@ -0,0 +1,87 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'User creates work items', :js, feature_category: :team_planning do
+ include ListboxHelpers
+ include WorkItemsHelpers
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project, :public, developers: user) }
+
+ before do
+ sign_in(user)
+ end
+
+ context 'when on new work items page' do
+ before do
+ visit "#{project_path(project)}/-/work_items/new"
+ end
+
+ context 'when creating an issue' do
+ it_behaves_like 'creates work item with widgets from new page', 'issue', %w[
+ work-item-title-input
+ work-item-description-wrapper
+ work-item-assignees
+ work-item-labels
+ work-item-milestone
+ ]
+
+ context 'when using keyboard shortcuts' do
+ it 'supports label shortcuts' do
+ find('body').native.send_key('l')
+
+ expect(find('.js-labels')).to have_selector('.gl-new-dropdown-panel')
+ end
+ end
+ end
+ end
+
+ context 'when on project work items list page' do
+ before do
+ visit project_work_items_path(project)
+ end
+
+ context 'when creating an work item' do
+ let_it_be(:label) { create(:label, title: 'Label 1', project: project) }
+ let_it_be(:milestone) { create(:milestone, project: project, title: 'Milestone') }
+ let(:issuable_container) { '[data-testid="issuable-container"]' }
+
+ before do
+ click_link 'New item'
+ end
+
+ it_behaves_like 'creates work item with widgets from a modal', 'issue', %w[
+ work-item-title-input
+ work-item-description-wrapper
+ work-item-assignees
+ work-item-labels
+ work-item-milestone
+ ]
+
+ it 'renders metadata as set during work item creation' do
+ allow(Gitlab::QueryLimiting::Transaction).to receive(:threshold).and_return(125)
+
+ select_work_item_type('Issue')
+
+ fill_work_item_title('Issue with metadata')
+
+ assign_work_item_to_yourself
+
+ set_work_item_label(label.title)
+
+ set_work_item_milestone(milestone.title)
+
+ create_work_item_with_type('issue')
+
+ wait_for_all_requests
+
+ within(all(issuable_container)[0]) do
+ expect(page).to have_link(milestone.title)
+ .and have_link(label.name)
+ .and have_link(user.name, href: user_path(user))
+ end
+ end
+ end
+ end
+end
diff --git a/spec/frontend/lib/utils/text_markdown_spec.js b/spec/frontend/lib/utils/text_markdown_spec.js
index b81f500ea19..ef80e9cf765 100644
--- a/spec/frontend/lib/utils/text_markdown_spec.js
+++ b/spec/frontend/lib/utils/text_markdown_spec.js
@@ -449,9 +449,6 @@ describe('init markdown', () => {
beforeEach(() => {
enterEvent = new KeyboardEvent('keydown', { key: 'Enter', cancelable: true });
- gon.features = {
- continueIndentedText: true,
- };
});
// Note that the ` a` tests use an invisible newline followed by a space, `0A20`
diff --git a/spec/frontend/wikis/notes/components/wiki_notes_app_spec.js b/spec/frontend/wikis/notes/components/wiki_notes_app_spec.js
index 663de3c7035..29be3049ba0 100644
--- a/spec/frontend/wikis/notes/components/wiki_notes_app_spec.js
+++ b/spec/frontend/wikis/notes/components/wiki_notes_app_spec.js
@@ -36,6 +36,7 @@ const mockDiscussion = (...children) => {
lastEditedBy: null,
url: 'https://path/to/2/',
awardEmoji: null,
+ internal: false,
userPermissions: {
adminNote: true,
awardEmoji: true,
@@ -69,6 +70,9 @@ describe('WikiNotesApp', () => {
wikiPage: {
id: 'gid://gitlab/WikiPage/1',
title: 'home',
+ userPermissions: {
+ markNoteAsInternal: true,
+ },
discussions: {
nodes: [mockDiscussion('Discussion 1')],
},
@@ -94,6 +98,9 @@ describe('WikiNotesApp', () => {
wikiPage: {
id: 'gid://gitlab/WikiPage/1',
title: 'home',
+ userPermissions: {
+ markNoteAsInternal: true,
+ },
discussions: {
nodes: [mockDiscussion('Discussion 1')],
},
@@ -213,6 +220,9 @@ describe('WikiNotesApp', () => {
wikiPage: {
id: 'gid://gitlab/WikiPage/1',
title: 'home',
+ userPermissions: {
+ markNoteAsInternal: true,
+ },
discussions,
},
},
@@ -260,6 +270,9 @@ describe('WikiNotesApp', () => {
wikiPage: {
id: 'gid://gitlab/WikiPage/1',
title: 'home',
+ userPermissions: {
+ markNoteAsInternal: true,
+ },
discussions,
},
},
diff --git a/spec/frontend/wikis/notes/mock_data.js b/spec/frontend/wikis/notes/mock_data.js
index fb061b15ef9..eab57004a1d 100644
--- a/spec/frontend/wikis/notes/mock_data.js
+++ b/spec/frontend/wikis/notes/mock_data.js
@@ -14,6 +14,9 @@ export const pageInfo = {
markdownHelpPath: '/help/user/markdown.md',
markdownPreviewPath: '/flightjs/Flight/-/wikis/home/preview_markdown',
createPath: '/flightjs/Flight/-/wikis',
+ userPermissions: {
+ markNoteAsInternal: true,
+ },
};
const registerPath = '/users/sign_up?redirect_to_referer=yes';
const signInPath = '/users/sign_in?redirect_to_referer=yes';
@@ -64,6 +67,7 @@ export const note = {
createdAt: '2024-11-10T14:19:58Z',
lastEditedAt: '2024-11-10T14:19:58Z',
url: 'http://127.0.0.1:3000/flightjs/Flight/-/wikis/home#note_1524',
+ internal: false,
userPermissions: {
__typename: 'NotePermissions',
adminNote: false,
diff --git a/spec/helpers/auth_helper_spec.rb b/spec/helpers/auth_helper_spec.rb
index 52f07b3ddca..05abd1af713 100644
--- a/spec/helpers/auth_helper_spec.rb
+++ b/spec/helpers/auth_helper_spec.rb
@@ -4,6 +4,7 @@ require "spec_helper"
RSpec.describe AuthHelper, feature_category: :system_access do
include LoginHelpers
+ include Devise::Test::ControllerHelpers # Remove when we delete the redesign_user_account_otp feature flag
describe "#enabled_button_based_providers_for_signup" do
[[true, %w[github gitlab]],
@@ -574,6 +575,7 @@ RSpec.describe AuthHelper, feature_category: :system_access do
it 'returns data to delete the OTP authenticator' do
expect(helper.delete_otp_authenticator_data(true)).to match(a_hash_including({
button_text: _('Delete one-time password authenticator'),
+ icon: 'remove',
message: _('Are you sure you want to delete this one-time password authenticator? ' \
'Enter your password to continue.'),
path: destroy_otp_profile_two_factor_auth_path,
diff --git a/spec/models/ci/processable_spec.rb b/spec/models/ci/processable_spec.rb
index 45bc2945213..b4d69dc759f 100644
--- a/spec/models/ci/processable_spec.rb
+++ b/spec/models/ci/processable_spec.rb
@@ -624,17 +624,25 @@ RSpec.describe Ci::Processable, feature_category: :continuous_integration do
describe 'manual_confirmation_message' do
context 'when job is manual' do
- subject { build(:ci_build, :manual, :with_manual_confirmation) }
+ subject(:job) { build(:ci_build, :manual, :with_manual_confirmation) }
it 'return manual_confirmation from option' do
- expect(subject.manual_confirmation_message).to eq('Please confirm. Do you want to proceed?')
+ expect(job.manual_confirmation_message).to eq('Please confirm. Do you want to proceed?')
+ end
+
+ context "when job is not playable because it's archived" do
+ before do
+ allow(job).to receive(:archived?).and_return(true)
+ end
+
+ it { expect(job.manual_confirmation_message).to be_nil }
end
end
context 'when job is not manual' do
- subject { build(:ci_build) }
+ subject(:job) { build(:ci_build) }
- it { expect(subject.manual_confirmation_message).to be_nil }
+ it { expect(job.manual_confirmation_message).to be_nil }
end
end
diff --git a/spec/policies/wiki_page_policy_spec.rb b/spec/policies/wiki_page_policy_spec.rb
index 57c587b4200..1e40822685d 100644
--- a/spec/policies/wiki_page_policy_spec.rb
+++ b/spec/policies/wiki_page_policy_spec.rb
@@ -16,7 +16,7 @@ RSpec.describe WikiPagePolicy, feature_category: :wiki do
subject(:policy) { described_class.new(user, wiki_page) }
where(:project_level, :feature_access_level, :membership, :admin_mode, :expected_count) do
- permission_table_for_guest_feature_access
+ permission_table_for_notes_feature_access
end
with_them do
@@ -24,12 +24,20 @@ RSpec.describe WikiPagePolicy, feature_category: :wiki do
enable_admin_mode!(user) if admin_mode
update_feature_access_level(project, feature_access_level)
- if expected_count == 1
+ if expected_count == 2
+ expect(policy).to be_allowed(:mark_note_as_internal)
+ expect(policy).to be_allowed(:read_wiki_page)
+ expect(policy).to be_allowed(:read_note)
+ expect(policy).to be_allowed(:create_note)
+ expect(policy).to be_allowed(:update_subscription)
+ elsif expected_count == 1
+ expect(policy).to be_disallowed(:mark_note_as_internal)
expect(policy).to be_allowed(:read_wiki_page)
expect(policy).to be_allowed(:read_note)
expect(policy).to be_allowed(:create_note)
expect(policy).to be_allowed(:update_subscription)
else
+ expect(policy).to be_disallowed(:mark_note_as_internal)
expect(policy).to be_disallowed(:read_wiki_page)
expect(policy).to be_disallowed(:read_note)
expect(policy).to be_disallowed(:create_note)
diff --git a/spec/requests/api/graphql/mutations/notes/create/note_spec.rb b/spec/requests/api/graphql/mutations/notes/create/note_spec.rb
index e8c4f6a6453..3c84047ad06 100644
--- a/spec/requests/api/graphql/mutations/notes/create/note_spec.rb
+++ b/spec/requests/api/graphql/mutations/notes/create/note_spec.rb
@@ -90,6 +90,31 @@ RSpec.describe 'Adding a Note', feature_category: :team_planning do
end
end
+ context 'for a wiki page' do
+ let_it_be_with_reload(:wiki_page_meta) { create(:wiki_page_meta, :for_wiki_page, container: project) }
+ let(:noteable) { wiki_page_meta }
+ let(:mutation) { graphql_mutation(:create_note, variables) }
+ let(:variables_extra) { {} }
+ let(:variables) do
+ {
+ noteable_id: GitlabSchema.id_from_object(noteable).to_s,
+ body: body
+ }.merge(variables_extra)
+ end
+
+ context 'when using internal param' do
+ let(:variables_extra) { { internal: true } }
+
+ it_behaves_like 'a Note mutation with confidential notes'
+
+ context 'when user does not have permission' do
+ let(:current_user) { user }
+
+ it_behaves_like 'a Note mutation when the user does not have permission'
+ end
+ end
+ end
+
context 'for an issue' do
let_it_be_with_reload(:issue) { create(:issue, project: project) }
let(:noteable) { issue }
diff --git a/spec/requests/api/graphql/wikis/wiki_page_spec.rb b/spec/requests/api/graphql/wikis/wiki_page_spec.rb
index 8387069fcb0..9350c7556a1 100644
--- a/spec/requests/api/graphql/wikis/wiki_page_spec.rb
+++ b/spec/requests/api/graphql/wikis/wiki_page_spec.rb
@@ -34,7 +34,8 @@ RSpec.describe 'getting a wiki page', feature_category: :wiki do
'title' => wiki_page_meta.title,
'userPermissions' => {
'readWikiPage' => true,
- 'createNote' => true
+ 'createNote' => true,
+ 'markNoteAsInternal' => true
}
)
end
diff --git a/spec/support/helpers/features/two_factor_helpers.rb b/spec/support/helpers/features/two_factor_helpers.rb
index 996bbe1e4d3..c328eb2c9b6 100644
--- a/spec/support/helpers/features/two_factor_helpers.rb
+++ b/spec/support/helpers/features/two_factor_helpers.rb
@@ -29,6 +29,20 @@ module Features
wait_for_requests
end
+ # Register OTP authenticator via UI
+ def otp_authenticator_registration(pin, password = nil)
+ click_button _('Register authenticator')
+ fill_in 'current_password', with: password if password
+ fill_in 'pin_code', with: pin
+ click_button _('Register with two-factor app')
+ end
+
+ def otp_authenticator_registration_and_copy_codes(pin, password = nil)
+ otp_authenticator_registration(pin, password)
+ click_button _('Copy codes')
+ click_link _('Proceed')
+ end
+
# Registers webauthn device via UI
def webauthn_device_registration(webauthn_device: nil, name: 'My device', password: 'fake')
webauthn_device ||= FakeWebauthnDevice.new(page, name)
diff --git a/spec/support/helpers/work_items_helpers.rb b/spec/support/helpers/work_items_helpers.rb
new file mode 100644
index 00000000000..e00b77bc930
--- /dev/null
+++ b/spec/support/helpers/work_items_helpers.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+module WorkItemsHelpers
+ def select_work_item_type(type)
+ select type.to_s.capitalize, from: 'Type'
+ end
+
+ def fill_work_item_title(title)
+ find_by_testid('work-item-title-input').send_keys(title)
+ end
+
+ def fill_work_item_description(description)
+ fill_in _('Description'), with: description
+ end
+
+ def assign_work_item_to_yourself
+ within_testid 'work-item-assignees' do
+ click_button 'assign yourself'
+ end
+ end
+
+ def set_work_item_label(label_title)
+ within_testid 'work-item-labels' do
+ click_button 'Edit'
+ select_listbox_item(label_title)
+ # The listbox is hiding Apply button,
+ # click listbox to dismiss and apply label
+ find_field('Search').send_keys(:escape)
+ end
+ end
+
+ def set_work_item_milestone(milestone_title)
+ within_testid 'work-item-milestone' do
+ click_button 'Edit'
+ select_listbox_item(milestone_title)
+ end
+ end
+
+ def create_work_item_with_type(type)
+ click_button "Create #{type}"
+ end
+
+ def expect_work_item_widgets(widget_names)
+ widget_names.each do |widget|
+ expect(page).to have_selector("[data-testid=\"#{widget}\"]")
+ end
+ end
+end
diff --git a/spec/support/shared_examples/features/work_items/new_work_item_shared_examples.rb b/spec/support/shared_examples/features/work_items/new_work_item_shared_examples.rb
new file mode 100644
index 00000000000..f554a3e51ff
--- /dev/null
+++ b/spec/support/shared_examples/features/work_items/new_work_item_shared_examples.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'creates work item with widgets from a modal' do |work_item_type, expected_widgets|
+ it "creates #{work_item_type} work item with expected widgets", :aggregate_failures do
+ select_work_item_type(work_item_type.to_s.capitalize)
+
+ expect_work_item_widgets(expected_widgets)
+
+ fill_work_item_title("#{work_item_type} work item")
+
+ create_work_item_with_type(work_item_type)
+
+ expect(page).to have_link "#{work_item_type} work item"
+ end
+end
+
+RSpec.shared_examples 'creates work item with widgets from new page' do |work_item_type, expected_widgets|
+ it "creates #{work_item_type} work item with expected widgets", :aggregate_failures do
+ select_work_item_type(work_item_type.to_s.capitalize)
+
+ expect_work_item_widgets(expected_widgets)
+
+ fill_work_item_title("#{work_item_type} work item")
+
+ create_work_item_with_type(work_item_type)
+
+ expect(page).to have_css('h1', text: "#{work_item_type} work item")
+ end
+end