diff --git a/.gitlab/ci/frontend.gitlab-ci.yml b/.gitlab/ci/frontend.gitlab-ci.yml index 2eda2f6ab9d..9c494fd53bb 100644 --- a/.gitlab/ci/frontend.gitlab-ci.yml +++ b/.gitlab/ci/frontend.gitlab-ci.yml @@ -367,6 +367,7 @@ jest-with-fixtures vue3 mr: when: always paths: - junit_jest.xml + parallel: 1 script: - run_timed_command "yarn jest:ci:vue3-mr:with-fixtures" diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION index 288cc1f2838..9d68c6c43b5 100644 --- a/GITLAB_SHELL_VERSION +++ b/GITLAB_SHELL_VERSION @@ -1 +1 @@ -14.38.0 +14.39.0 diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss index 0ff994af59b..dd4faee1eee 100644 --- a/app/assets/stylesheets/pages/issues.scss +++ b/app/assets/stylesheets/pages/issues.scss @@ -237,6 +237,7 @@ ul.related-merge-requests > li gl-emoji { right: var(--application-bar-right); width: auto; top: $calc-application-header-height; + max-width: calc(100vw - #{$super-sidebar-width}); } .limit-container-width { diff --git a/app/models/concerns/linkable_item.rb b/app/models/concerns/linkable_item.rb index c91e3615ba7..ab8a6d8bb54 100644 --- a/app/models/concerns/linkable_item.rb +++ b/app/models/concerns/linkable_item.rb @@ -12,7 +12,7 @@ module LinkableItem include IssuableLink included do - validate :check_existing_parent_link + validate :check_existing_parent_link, on: :create scope :for_source, ->(item) { where(source_id: item.id) } scope :for_target, ->(item) { where(target_id: item.id) } diff --git a/app/models/work_items/parent_link.rb b/app/models/work_items/parent_link.rb index 545244e52d5..e0a2a3fca75 100644 --- a/app/models/work_items/parent_link.rb +++ b/app/models/work_items/parent_link.rb @@ -17,7 +17,7 @@ module WorkItems validate :validate_cyclic_reference validate :validate_max_children validate :validate_confidentiality - validate :check_existing_related_link + validate :check_existing_related_link, on: :create scope :for_parents, ->(parent_ids) { where(work_item_parent_id: parent_ids) } scope :for_children, ->(children_ids) { where(work_item: children_ids) } diff --git a/config/initializers/active_job_shard_support.rb b/config/initializers/active_job_shard_support.rb index ef1f86bfbf0..865df48c651 100644 --- a/config/initializers/active_job_shard_support.rb +++ b/config/initializers/active_job_shard_support.rb @@ -1,10 +1,9 @@ # frozen_string_literal: true -# As discussed in https://gitlab.com/gitlab-org/gitlab/-/merge_requests/148637#note_1850247875, -# Rails 7.1 introduces enqueue_all which is not covered in this patch. # We deliver emails using the `deliver_later` method and it uses ActiveJob # under the hood, which later processes the email via the defined ActiveJob adapter's `enqueue` method. # For GitLab, the ActiveJob adapter is Sidekiq (in development and production environments). +# # We need to set the following up to override the ActiveJob adapater # so as to ensure that mailer jobs are enqueued in a shard-aware manner. @@ -15,8 +14,18 @@ end module ActiveJob module QueueAdapters module ActiveJobShardSupport + if ::Gitlab.next_rails? + def enqueue_all(jobs) + Gitlab::SidekiqSharding::Router.route(ActionMailer::MailDeliveryJob) do + super(jobs) + end + end + end + %i[enqueue enqueue_at].each do |name| define_method(name) do |*args| + return super(*args) if ::Gitlab.next_rails? + Gitlab::SidekiqSharding::Router.route(ActionMailer::MailDeliveryJob) do super(*args) end diff --git a/doc/tutorials/observability/img/django_metrics.png b/doc/tutorials/observability/img/django_metrics.png index aa90b039d1e..c2a662bfe73 100644 Binary files a/doc/tutorials/observability/img/django_metrics.png and b/doc/tutorials/observability/img/django_metrics.png differ diff --git a/doc/tutorials/observability/img/django_traces.png b/doc/tutorials/observability/img/django_traces.png index 4bbc064ba8e..6d3bf1b226a 100644 Binary files a/doc/tutorials/observability/img/django_traces.png and b/doc/tutorials/observability/img/django_traces.png differ diff --git a/doc/tutorials/observability/img/java_configuration_menu.png b/doc/tutorials/observability/img/java_configuration_menu.png index 8f301aa9054..1fedf0411b6 100644 Binary files a/doc/tutorials/observability/img/java_configuration_menu.png and b/doc/tutorials/observability/img/java_configuration_menu.png differ diff --git a/doc/tutorials/observability/img/java_edit_configuration.png b/doc/tutorials/observability/img/java_edit_configuration.png index 4b067af302e..c043fc4ee16 100644 Binary files a/doc/tutorials/observability/img/java_edit_configuration.png and b/doc/tutorials/observability/img/java_edit_configuration.png differ diff --git a/doc/tutorials/observability/img/maven_changes.png b/doc/tutorials/observability/img/maven_changes.png index 7f094a70af7..1ac2d7a5d04 100644 Binary files a/doc/tutorials/observability/img/maven_changes.png and b/doc/tutorials/observability/img/maven_changes.png differ diff --git a/doc/user/application_security/vulnerabilities/img/example_code_flow_of_python_applications_v17_3.png b/doc/user/application_security/vulnerabilities/img/example_code_flow_of_python_applications_v17_3.png index c2880d6154c..fea66a95699 100644 Binary files a/doc/user/application_security/vulnerabilities/img/example_code_flow_of_python_applications_v17_3.png and b/doc/user/application_security/vulnerabilities/img/example_code_flow_of_python_applications_v17_3.png differ diff --git a/doc/user/infrastructure/clusters/connect/img/variables_civo.png b/doc/user/infrastructure/clusters/connect/img/variables_civo.png index acbe4f60df6..4a47c944c56 100644 Binary files a/doc/user/infrastructure/clusters/connect/img/variables_civo.png and b/doc/user/infrastructure/clusters/connect/img/variables_civo.png differ diff --git a/doc/user/project/img/issue_boards_blocked_icon_v17_3.png b/doc/user/project/img/issue_boards_blocked_icon_v17_3.png index 5023c9afa13..f5cb789c15e 100644 Binary files a/doc/user/project/img/issue_boards_blocked_icon_v17_3.png and b/doc/user/project/img/issue_boards_blocked_icon_v17_3.png differ diff --git a/doc/user/project/merge_requests/img/status_checks_create_form_v14_0.png b/doc/user/project/merge_requests/img/status_checks_create_form_v14_0.png index b9c0f85ca79..9f652fe9e6a 100644 Binary files a/doc/user/project/merge_requests/img/status_checks_create_form_v14_0.png and b/doc/user/project/merge_requests/img/status_checks_create_form_v14_0.png differ diff --git a/doc/user/project/merge_requests/img/status_checks_list_view_v14_0.png b/doc/user/project/merge_requests/img/status_checks_list_view_v14_0.png index ade983ef129..c39628b1464 100644 Binary files a/doc/user/project/merge_requests/img/status_checks_list_view_v14_0.png and b/doc/user/project/merge_requests/img/status_checks_list_view_v14_0.png differ diff --git a/doc/user/project/merge_requests/img/status_checks_update_form_v14_0.png b/doc/user/project/merge_requests/img/status_checks_update_form_v14_0.png index da849e3d6b8..31d33ce511b 100644 Binary files a/doc/user/project/merge_requests/img/status_checks_update_form_v14_0.png and b/doc/user/project/merge_requests/img/status_checks_update_form_v14_0.png differ diff --git a/lib/gitlab/patch/sidekiq_job_setter.rb b/lib/gitlab/patch/sidekiq_job_setter.rb index a9e0a56240c..b5f80c9884f 100644 --- a/lib/gitlab/patch/sidekiq_job_setter.rb +++ b/lib/gitlab/patch/sidekiq_job_setter.rb @@ -11,7 +11,25 @@ module Gitlab # so we only need to patch 1 method. def perform_async(*args) # rubocop:disable Gitlab/ModuleWithInstanceVariables -- @klass is present in the class we are patching - Gitlab::SidekiqSharding::Router.route(@klass) do + + route_with_klass = @klass + + # If an ActiveJob JobWrapper is pushed, check the arg hash's job_class for routing decisions. + # + # See https://github.com/rails/rails/blob/v7.1.0/activejob/lib/active_job/queue_adapters/sidekiq_adapter.rb#L21 + # `job.serialize` would return a hash containing `job_class` set in + # https://github.com/rails/rails/blob/v7.1.0/activejob/lib/active_job/core.rb#L110 + # + # In the GitLab Rails application, this only applies to ActionMailer::MailDeliveryJob + # but routing using the `job_class` keeps the option of using ActiveJob available for us. + # + if @klass == ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper && + args.first.is_a?(Hash) && + args.first['job_class'] + route_with_klass = args.first['job_class'].to_s.safe_constantize + end + + Gitlab::SidekiqSharding::Router.route(route_with_klass) do # rubocop:enable Gitlab/ModuleWithInstanceVariables super end diff --git a/lib/tasks/gitlab/shell.rake b/lib/tasks/gitlab/shell.rake index 5eb3ff8c28b..2bb14504d32 100644 --- a/lib/tasks/gitlab/shell.rake +++ b/lib/tasks/gitlab/shell.rake @@ -30,8 +30,7 @@ namespace :gitlab do File.open("config.yml", "w+") { |f| f.puts config.to_yaml } [ - %w[bin/install], - %w[make build] + %w[make make_necessary_dirs build] ].each do |cmd| unless Kernel.system(*cmd) raise "command failed: #{cmd.join(' ')}" diff --git a/package.json b/package.json index ed9710d4e6a..9104440c195 100644 --- a/package.json +++ b/package.json @@ -249,7 +249,7 @@ "yaml": "^2.0.0-10" }, "devDependencies": { - "@gitlab/eslint-plugin": "19.6.1", + "@gitlab/eslint-plugin": "20.0.0", "@gitlab/stylelint-config": "6.2.1", "@graphql-eslint/eslint-plugin": "3.20.1", "@originjs/vite-plugin-commonjs": "^1.0.3", diff --git a/spec/models/work_items/parent_link_spec.rb b/spec/models/work_items/parent_link_spec.rb index b53717f815e..6ce49c0bac1 100644 --- a/spec/models/work_items/parent_link_spec.rb +++ b/spec/models/work_items/parent_link_spec.rb @@ -168,6 +168,66 @@ RSpec.describe WorkItems::ParentLink, feature_category: :portfolio_management do end end + context 'when parent is already linked' do + shared_examples 'invalid link' do |link_factory| + let_it_be(:parent_link) { build(:parent_link, work_item_parent: issue, work_item: task1) } + let(:error_msg) { 'cannot assign a linked work item as a parent' } + + context 'when creating new link' do + context 'when parent is the link target' do + before do + create(link_factory, source_id: task1.id, target_id: issue.id) + end + + it do + expect(parent_link).not_to be_valid + expect(parent_link.errors[:work_item]).to include(error_msg) + end + end + + context 'when parent is the link source' do + before do + create(link_factory, source_id: issue.id, target_id: task1.id) + end + + it do + expect(parent_link).not_to be_valid + expect(parent_link.errors[:work_item]).to include(error_msg) + end + end + end + + context 'when updating existing link' do + context 'when parent is the link target' do + before do + create(link_factory, source_id: task1.id, target_id: issue.id) + parent_link.save!(validate: false) + end + + it do + expect(parent_link).to be_valid + expect(parent_link.errors[:work_item]).not_to include(error_msg) + end + end + + context 'when parent is the link source' do + before do + create(link_factory, source_id: issue.id, target_id: task1.id) + parent_link.save!(validate: false) + end + + it do + expect(parent_link).to be_valid + expect(parent_link.errors[:work_item]).not_to include(error_msg) + end + end + end + end + + it_behaves_like 'invalid link', :work_item_link + it_behaves_like 'invalid link', :issue_link + end + context 'when setting confidentiality' do using RSpec::Parameterized::TableSyntax @@ -191,38 +251,6 @@ RSpec.describe WorkItems::ParentLink, feature_category: :portfolio_management do end end end - - context 'when parent is already linked' do - shared_examples 'invalid link' do |link_factory| - let_it_be(:parent_link) { build(:parent_link, work_item_parent: issue, work_item: task1) } - let(:error_msg) { 'cannot assign a linked work item as a parent' } - - context 'when parent is the link target' do - before do - create(link_factory, source_id: task1.id, target_id: issue.id) - end - - it do - expect(parent_link).not_to be_valid - expect(parent_link.errors[:work_item]).to include(error_msg) - end - end - - context 'when parent is the link source' do - before do - create(link_factory, source_id: issue.id, target_id: task1.id) - end - - it do - expect(parent_link).not_to be_valid - expect(parent_link.errors[:work_item]).to include(error_msg) - end - end - end - - it_behaves_like 'invalid link', :work_item_link - it_behaves_like 'invalid link', :issue_link - end end end diff --git a/spec/support/shared_examples/models/concerns/linkable_items_shared_examples.rb b/spec/support/shared_examples/models/concerns/linkable_items_shared_examples.rb index eb37fe66c11..9720c997aa2 100644 --- a/spec/support/shared_examples/models/concerns/linkable_items_shared_examples.rb +++ b/spec/support/shared_examples/models/concerns/linkable_items_shared_examples.rb @@ -8,34 +8,65 @@ RSpec.shared_examples 'includes LinkableItem concern' do subject(:link) { build(link_factory, source_id: source.id, target_id: target.id) } describe '#check_existing_parent_link' do - shared_examples 'invalid due to existing link' do - it do - is_expected.to be_invalid - expect(link.errors.messages[:source]).to include("is a parent or child of this #{item_type}") + context 'for new issuable link' do + shared_examples 'invalid due to existing link' do + it do + is_expected.to be_invalid + expect(link.errors.messages[:source]).to include("is a parent or child of this #{item_type}") + end end - end - context 'without existing link parent' do - let(:source) { issue } - let(:target) { task } - - it 'is valid' do - is_expected.to be_valid - expect(link.errors).to be_empty - end - end - - context 'with existing link parent' do - let_it_be(:relationship) { create(:parent_link, work_item_parent: issue, work_item: task) } - - it_behaves_like 'invalid due to existing link' do + context 'without existing link parent' do let(:source) { issue } let(:target) { task } + + it 'is valid' do + is_expected.to be_valid + expect(link.errors).to be_empty + end end - it_behaves_like 'invalid due to existing link' do + context 'with existing link parent' do + let_it_be(:relationship) { create(:parent_link, work_item_parent: issue, work_item: task) } + + it_behaves_like 'invalid due to existing link' do + let(:source) { issue } + let(:target) { task } + end + + it_behaves_like 'invalid due to existing link' do + let(:source) { task } + let(:target) { issue } + end + end + end + + context 'for existing issuable link with existing parent link' do + let(:link) { build(link_factory, source_id: source.id, target_id: target.id) } + + before do + create(:parent_link, work_item_parent: issue, work_item: task) + link.save!(validate: false) + end + + context 'when source is issue' do + let(:source) { issue } + let(:target) { task } + + it 'is valid' do + expect(link).to be_valid + expect(link.errors).to be_empty + end + end + + context 'when source is task' do let(:source) { task } let(:target) { issue } + + it 'is valid' do + expect(link).to be_valid + expect(link.errors).to be_empty + end end end end diff --git a/spec/tasks/gitlab/shell_rake_spec.rb b/spec/tasks/gitlab/shell_rake_spec.rb index 4ca46a9ce40..5fb85a526b6 100644 --- a/spec/tasks/gitlab/shell_rake_spec.rb +++ b/spec/tasks/gitlab/shell_rake_spec.rb @@ -12,8 +12,7 @@ RSpec.describe 'gitlab:shell rake tasks', :silence_stdout do describe 'install task' do it 'installs and compiles gitlab-shell' do expect_any_instance_of(Gitlab::TaskHelpers).to receive(:checkout_or_clone_version) - allow(Kernel).to receive(:system).with('bin/install').and_return(true) - allow(Kernel).to receive(:system).with('make', 'build').and_return(true) + allow(Kernel).to receive(:system).with('make', 'make_necessary_dirs', 'build').and_return(true) run_rake_task('gitlab:shell:install') end diff --git a/spec/workers/concerns/application_worker_spec.rb b/spec/workers/concerns/application_worker_spec.rb index ade4c4b4f26..d41dd1dc2eb 100644 --- a/spec/workers/concerns/application_worker_spec.rb +++ b/spec/workers/concerns/application_worker_spec.rb @@ -652,6 +652,46 @@ RSpec.describe ApplicationWorker, feature_category: :shared do end end + context 'with ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper' do + let(:worker) { ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper } + + context 'when calling perform_async with setter' do + subject(:operation) { worker.set(testing: true).perform_async({ 'job_class' => ActionMailer::MailDeliveryJob }) } + + it_behaves_like 'uses shard router' + end + + context 'when calling perform_async with setter without job_class' do + subject(:operation) { worker.set(testing: true).perform_async } + + it_behaves_like 'uses shard router' + end + + context 'when calling perform_in with setter' do + subject(:operation) { worker.set(testing: true).perform_in(1, { 'job_class' => ActionMailer::MailDeliveryJob }) } + + it_behaves_like 'uses shard router' + end + + context 'when calling perform_in with setter without job_class' do + subject(:operation) { worker.set(testing: true).perform_in(1) } + + it_behaves_like 'uses shard router' + end + + context 'when calling perform_at with setter' do + subject(:operation) { worker.set(testing: true).perform_at(1, { 'job_class' => ActionMailer::MailDeliveryJob }) } + + it_behaves_like 'uses shard router' + end + + context 'when calling perform_at with setter without job_class' do + subject(:operation) { worker.set(testing: true).perform_at(1) } + + it_behaves_like 'uses shard router' + end + end + context 'when calling perform_async with setter' do subject(:operation) { worker.set(testing: true).perform_async } diff --git a/yarn.lock b/yarn.lock index 86007442c21..df3d85a4993 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1310,10 +1310,10 @@ core-js "^3.29.1" mitt "^3.0.1" -"@gitlab/eslint-plugin@19.6.1": - version "19.6.1" - resolved "https://registry.yarnpkg.com/@gitlab/eslint-plugin/-/eslint-plugin-19.6.1.tgz#1e59cb0d83e2f8522945b1b5f01d1fc284b7e729" - integrity sha512-VA5eH9U6qr3PH24/hhwNKS4AF3jPB78ZaBmuQipGsn0DlyOqOdg2JnuX7C5tR4yM1D7uK2FcAdrW4AGovEQ7Xw== +"@gitlab/eslint-plugin@20.0.0": + version "20.0.0" + resolved "https://registry.yarnpkg.com/@gitlab/eslint-plugin/-/eslint-plugin-20.0.0.tgz#d451169692b3ee51e7e946f6b9cead3de5132a51" + integrity sha512-D/nScJCBqR5TMJkg5jpanhTIvrRVsWPohPUUhkHmCrjyXDjjZdZ3hkLZBF66tst0yENFDh6l5mtRVCzCIU5xdA== dependencies: "@typescript-eslint/eslint-plugin" "^7.14.1" eslint-config-airbnb-base "^15.0.0"