From 27211da9374a41cf01d9ce851fdb86143e562799 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Fri, 11 Jul 2025 21:07:14 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- .../javascripts/lib/utils/text_markdown.js | 1 - .../notes/wiki_page_note.fragment.graphql | 1 + .../wikis/graphql/wiki_page.query.graphql | 3 + .../wikis/wiki_notes/components/wiki_note.vue | 1 + .../wiki_notes/components/wiki_notes_app.vue | 3 + .../profiles/two_factor_auths_controller.rb | 2 +- .../types/permission_types/wikis/wiki_page.rb | 2 +- app/helpers/auth_helper.rb | 3 +- app/models/ci/processable.rb | 2 +- app/models/note.rb | 2 +- app/policies/wiki_page_policy.rb | 8 + app/views/profiles/preferences/show.html.haml | 9 +- .../_otp_registration.html.haml | 60 ++++ .../profiles/two_factor_auths/show.html.haml | 101 +++--- .../redesign_user_account_otp.yml | 10 + .../wip/continue_indented_text.yml | 7 - db/click_house/main.sql | 305 +++++++++++++++++- ...707095655_create_siphon_issue_assignees.rb | 24 ++ ...0250707095824_create_siphon_label_links.rb | 27 ++ ...0707100345_create_work_item_label_links.rb | 49 +++ ...reate_siphon_work_item_current_statuses.rb | 27 ++ ...64341_create_hierarchy_work_items_table.rb | 54 ++++ ...07164711_create_hierarchy_work_items_mv.rb | 127 ++++++++ .../schema_migrations/main/20250707095655 | 1 + .../schema_migrations/main/20250707095824 | 1 + .../schema_migrations/main/20250707100345 | 1 + .../schema_migrations/main/20250707100721 | 1 + .../schema_migrations/main/20250707164341 | 1 + .../schema_migrations/main/20250707164711 | 1 + ...t_context_commit_diff_files_project_id.yml | 2 +- ...st_context_commit_diff_files_project_id.rb | 20 ++ db/schema_migrations/20250708203406 | 1 + ...ported_models_and_hardware_requirements.md | 1 + doc/api/graphql/reference/_index.md | 1 + .../database/batched_background_migrations.md | 5 +- .../secret_detection/detected_secrets.md | 3 + gems/activerecord-gitlab/Gemfile.lock | 4 +- gems/click_house-client/Gemfile.lock | 4 +- gems/csv_builder/Gemfile.lock | 4 +- gems/gitlab-active-context/Gemfile.lock | 4 +- gems/gitlab-backup-cli/Gemfile.lock | 4 +- .../Gemfile.lock | 4 +- .../gitlab-database-lock_retries/Gemfile.lock | 4 +- gems/gitlab-housekeeper/Gemfile.lock | 4 +- gems/gitlab-http/Gemfile.lock | 4 +- gems/gitlab-rspec/Gemfile.lock | 4 +- gems/gitlab-rspec_flaky/Gemfile.lock | 4 +- gems/gitlab-safe_request_store/Gemfile.lock | 4 +- gems/gitlab-schema-validation/Gemfile.lock | 4 +- gems/gitlab-utils/Gemfile.lock | 4 +- gems/ipynbdiff/Gemfile.lock | 8 +- lib/gitlab/gon_helper.rb | 1 - locale/gitlab.pot | 6 + qa/Gemfile.lock | 2 +- qa/qa/page/profile/two_factor_auth.rb | 15 + .../two_factor_auths_controller_spec.rb | 2 +- .../profiles/two_factor_auths_spec.rb | 29 +- spec/features/users/login_spec.rb | 10 +- .../new/create_issue_work_item_spec.rb | 44 --- .../new/user_creates_work_item_spec.rb | 87 +++++ spec/frontend/lib/utils/text_markdown_spec.js | 3 - .../notes/components/wiki_notes_app_spec.js | 13 + spec/frontend/wikis/notes/mock_data.js | 4 + spec/helpers/auth_helper_spec.rb | 2 + spec/models/ci/processable_spec.rb | 16 +- spec/policies/wiki_page_policy_spec.rb | 12 +- .../mutations/notes/create/note_spec.rb | 25 ++ .../api/graphql/wikis/wiki_page_spec.rb | 3 +- .../helpers/features/two_factor_helpers.rb | 14 + spec/support/helpers/work_items_helpers.rb | 48 +++ .../new_work_item_shared_examples.rb | 29 ++ 71 files changed, 1113 insertions(+), 183 deletions(-) create mode 100644 app/views/profiles/two_factor_auths/_otp_registration.html.haml create mode 100644 config/feature_flags/gitlab_com_derisk/redesign_user_account_otp.yml delete mode 100644 config/feature_flags/wip/continue_indented_text.yml create mode 100644 db/click_house/migrate/main/20250707095655_create_siphon_issue_assignees.rb create mode 100644 db/click_house/migrate/main/20250707095824_create_siphon_label_links.rb create mode 100644 db/click_house/migrate/main/20250707100345_create_work_item_label_links.rb create mode 100644 db/click_house/migrate/main/20250707100721_create_siphon_work_item_current_statuses.rb create mode 100644 db/click_house/migrate/main/20250707164341_create_hierarchy_work_items_table.rb create mode 100644 db/click_house/migrate/main/20250707164711_create_hierarchy_work_items_mv.rb create mode 100644 db/click_house/schema_migrations/main/20250707095655 create mode 100644 db/click_house/schema_migrations/main/20250707095824 create mode 100644 db/click_house/schema_migrations/main/20250707100345 create mode 100644 db/click_house/schema_migrations/main/20250707100721 create mode 100644 db/click_house/schema_migrations/main/20250707164341 create mode 100644 db/click_house/schema_migrations/main/20250707164711 create mode 100644 db/post_migrate/20250708203406_finalize_backfill_merge_request_context_commit_diff_files_project_id.rb create mode 100644 db/schema_migrations/20250708203406 delete mode 100644 spec/features/work_items/new/create_issue_work_item_spec.rb create mode 100644 spec/features/work_items/new/user_creates_work_item_spec.rb create mode 100644 spec/support/helpers/work_items_helpers.rb create mode 100644 spec/support/shared_examples/features/work_items/new_work_item_shared_examples.rb 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