diff --git a/app/models/user_callout.rb b/app/models/user_callout.rb index 34996b771a0..0a4db707be6 100644 --- a/app/models/user_callout.rb +++ b/app/models/user_callout.rb @@ -29,7 +29,8 @@ class UserCallout < ApplicationRecord registration_enabled_callout: 25, new_user_signups_cap_reached: 26, # EE-only unfinished_tag_cleanup_callout: 27, - eoa_bronze_plan_banner: 28 # EE-only + eoa_bronze_plan_banner: 28, # EE-only + pipeline_needs_banner: 29 } validates :user, presence: true diff --git a/app/services/authorized_project_update/find_records_due_for_refresh_service.rb b/app/services/authorized_project_update/find_records_due_for_refresh_service.rb index d6de10fb8aa..c4b18a26d0e 100644 --- a/app/services/authorized_project_update/find_records_due_for_refresh_service.rb +++ b/app/services/authorized_project_update/find_records_due_for_refresh_service.rb @@ -54,6 +54,12 @@ module AuthorizedProjectUpdate [remove, add] end + def needs_refresh? + remove, add = execute + + remove.present? || add.present? + end + def fresh_access_levels_per_project fresh_authorizations.each_with_object({}) do |row, hash| hash[row.project_id] = row.access_level diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml index 6902215a76b..5f8ec5086bd 100644 --- a/app/views/admin/groups/show.html.haml +++ b/app/views/admin/groups/show.html.haml @@ -62,6 +62,7 @@ = link_to sprite_icon('question-o'), help_page_path('topics/git/lfs/index') = render_if_exists 'namespaces/shared_runner_status', namespace: @group + = render_if_exists 'namespaces/additional_minutes_status', namespace: @group = render 'shared/custom_attributes', custom_attributes: @group.custom_attributes diff --git a/app/views/devise/mailer/_confirmation_instructions_secondary.text.erb b/app/views/devise/mailer/_confirmation_instructions_secondary.text.erb index b91498ccfae..ab46aaaca1a 100644 --- a/app/views/devise/mailer/_confirmation_instructions_secondary.text.erb +++ b/app/views/devise/mailer/_confirmation_instructions_secondary.text.erb @@ -1,7 +1,7 @@ -<%= @resource.user.name %>, confirm your email address now! +<%= _(" %{name}, confirm your email address now! ") % { name: @resource.user.name } %> -Use the link below to confirm your email address (<%= @resource.email %>) +<%= _("Use the link below to confirm your email address (%{email})") % { email: @resource.email } %> <%= confirmation_url(@resource, confirmation_token: @token) %> -If this email was added in error, you can remove it here: <%= profile_emails_url %> +<%= _("If this email was added in error, you can remove it here: %{profile_emails_url}") % { profile_emails_url: profile_emails_url } %> diff --git a/app/views/devise/shared/_tabs_ldap.html.haml b/app/views/devise/shared/_tabs_ldap.html.haml index 27057d023b1..0ef4a30d820 100644 --- a/app/views/devise/shared/_tabs_ldap.html.haml +++ b/app/views/devise/shared/_tabs_ldap.html.haml @@ -4,7 +4,7 @@ %ul.nav-links.new-session-tabs.nav-tabs.nav{ class: ('custom-provider-tabs' if any_form_based_providers_enabled?) } - if crowd_enabled? %li.nav-item - = link_to "Crowd", "#crowd", class: "nav-link #{active_when(form_based_auth_provider_has_active_class?(:crowd))}", 'data-toggle' => 'tab', role: 'tab' + = link_to _("Crowd"), "#crowd", class: "nav-link #{active_when(form_based_auth_provider_has_active_class?(:crowd))}", 'data-toggle' => 'tab', role: 'tab' = render_if_exists "devise/shared/kerberos_tab" - ldap_servers.each_with_index do |server, i| %li.nav-item @@ -17,4 +17,4 @@ = link_to _('Standard'), '#login-pane', class: 'nav-link', data: { toggle: 'tab', qa_selector: 'standard_tab' }, role: 'tab' - if render_signup_link && allow_signup? %li.nav-item - = link_to 'Register', '#register-pane', class: 'nav-link', data: { toggle: 'tab', qa_selector: 'register_tab' }, role: 'tab' + = link_to _('Register'), '#register-pane', class: 'nav-link', data: { toggle: 'tab', qa_selector: 'register_tab' }, role: 'tab' diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index 692adcb6434..fa6ea54e342 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -25,7 +25,7 @@ :urgency: :low :resource_boundary: :unknown :weight: 1 - :idempotent: true + :idempotent: :tags: [] - :name: authorized_project_update:authorized_project_update_user_refresh_with_low_urgency :feature_category: :authentication_and_authorization diff --git a/app/workers/authorized_project_update/user_refresh_over_user_range_worker.rb b/app/workers/authorized_project_update/user_refresh_over_user_range_worker.rb index 9bd1ad2ed30..6635c322ab8 100644 --- a/app/workers/authorized_project_update/user_refresh_over_user_range_worker.rb +++ b/app/workers/authorized_project_update/user_refresh_over_user_range_worker.rb @@ -1,18 +1,49 @@ # frozen_string_literal: true module AuthorizedProjectUpdate - class UserRefreshOverUserRangeWorker + class UserRefreshOverUserRangeWorker # rubocop:disable Scalability/IdempotentWorker + # When the feature flag named `periodic_project_authorization_update_via_replica` is enabled, + # this worker checks if a specific user requires an update to their project_authorizations records. + # This check is done via the data read from the database replica (and not from the primary). + # If this check returns true, a completely new Sidekiq job is enqueued for this specific user + # so as to update its project_authorizations records. + + # There is a possibility that the data in the replica is lagging behind the primary + # and hence it becomes very important that we check if an update is indeed required for this user + # once again via the primary database, which is the reason why we enqueue a completely new Sidekiq job + # via `UserRefreshWithLowUrgencyWorker` for this user. + include ApplicationWorker feature_category :authentication_and_authorization urgency :low queue_namespace :authorized_project_update + # This job will not be deduplicated since it is marked with + # `data_consistency :delayed` and not `idempotent!` + # See https://gitlab.com/gitlab-org/gitlab/-/issues/325291 deduplicate :until_executing, including_scheduled: true - - idempotent! + data_consistency :delayed, feature_flag: :periodic_project_authorization_update_via_replica def perform(start_user_id, end_user_id) - AuthorizedProjectUpdate::RecalculateForUserRangeService.new(start_user_id, end_user_id).execute + if Feature.enabled?(:periodic_project_authorization_update_via_replica) + User.where(id: start_user_id..end_user_id).find_each do |user| # rubocop: disable CodeReuse/ActiveRecord + enqueue_project_authorizations_refresh(user) if project_authorizations_needs_refresh?(user) + end + else + AuthorizedProjectUpdate::RecalculateForUserRangeService.new(start_user_id, end_user_id).execute + end + end + + private + + def project_authorizations_needs_refresh?(user) + AuthorizedProjectUpdate::FindRecordsDueForRefreshService.new(user).needs_refresh? + end + + def enqueue_project_authorizations_refresh(user) + with_context(user: user) do + AuthorizedProjectUpdate::UserRefreshWithLowUrgencyWorker.perform_async(user.id) + end end end end diff --git a/changelogs/unreleased/Externalize-strings-in-_confirmation_instructions_secondary-text-erb.yml b/changelogs/unreleased/Externalize-strings-in-_confirmation_instructions_secondary-text-erb.yml new file mode 100644 index 00000000000..bdf208d432e --- /dev/null +++ b/changelogs/unreleased/Externalize-strings-in-_confirmation_instructions_secondary-text-erb.yml @@ -0,0 +1,5 @@ +--- +title: Externalize strings in _confirmation_instructions_secondary.text.erb +merge_request: 58218 +author: nuwe1 +type: other diff --git a/changelogs/unreleased/Externalize-strings-in-shared-_tabs_ldap-html-haml.yml b/changelogs/unreleased/Externalize-strings-in-shared-_tabs_ldap-html-haml.yml new file mode 100644 index 00000000000..81650f80344 --- /dev/null +++ b/changelogs/unreleased/Externalize-strings-in-shared-_tabs_ldap-html-haml.yml @@ -0,0 +1,5 @@ +--- +title: Externalise strings in shared/_tabs_ldap.html.haml +merge_request: 58285 +author: nuwe1 +type: other diff --git a/changelogs/unreleased/mo-codeclimate-prefix.yml b/changelogs/unreleased/mo-codeclimate-prefix.yml new file mode 100644 index 00000000000..caf27abeb95 --- /dev/null +++ b/changelogs/unreleased/mo-codeclimate-prefix.yml @@ -0,0 +1,5 @@ +--- +title: Add CODECLIMATE_PREFIX variable to code quality template +merge_request: 59041 +author: +type: added diff --git a/config/feature_flags/development/periodic_project_authorization_update_via_replica.yml b/config/feature_flags/development/periodic_project_authorization_update_via_replica.yml new file mode 100644 index 00000000000..abbc3faeb3a --- /dev/null +++ b/config/feature_flags/development/periodic_project_authorization_update_via_replica.yml @@ -0,0 +1,8 @@ +--- +name: periodic_project_authorization_update_via_replica +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/58752 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/327092 +milestone: '13.11' +type: development +group: group::access +default_enabled: false diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index e5950bfbba3..11b58426569 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -8597,6 +8597,7 @@ Name of the feature that the callout is for. | `GOLD_TRIAL_BILLINGS` | Callout feature name for gold_trial_billings. | | `NEW_USER_SIGNUPS_CAP_REACHED` | Callout feature name for new_user_signups_cap_reached. | | `PERSONAL_ACCESS_TOKEN_EXPIRY` | Callout feature name for personal_access_token_expiry. | +| `PIPELINE_NEEDS_BANNER` | Callout feature name for pipeline_needs_banner. | | `REGISTRATION_ENABLED_CALLOUT` | Callout feature name for registration_enabled_callout. | | `SERVICE_TEMPLATES_DEPRECATED_CALLOUT` | Callout feature name for service_templates_deprecated_callout. | | `SUGGEST_PIPELINE` | Callout feature name for suggest_pipeline. | diff --git a/lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml index fd6c51ea350..b29342216fc 100644 --- a/lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml @@ -36,6 +36,7 @@ code_quality: REPORT_STDOUT \ REPORT_FORMAT \ ENGINE_MEMORY_LIMIT_BYTES \ + CODECLIMATE_PREFIX \ ) \ --volume "$PWD":/code \ --volume /var/run/docker.sock:/var/run/docker.sock \ diff --git a/locale/gitlab.pot b/locale/gitlab.pot index b23e9390fbb..6f7d44e893e 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -16,6 +16,9 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" +msgid " %{name}, confirm your email address now! " +msgstr "" + msgid " %{project_name}#%{issuable_iid} · created %{issuable_created} by %{author} · updated %{issuable_updated}" msgstr "" @@ -2102,6 +2105,9 @@ msgstr "" msgid "Additional minutes" msgstr "" +msgid "Additional minutes:" +msgstr "" + msgid "Additional text" msgstr "" @@ -9514,6 +9520,9 @@ msgstr "" msgid "Crossplane" msgstr "" +msgid "Crowd" +msgstr "" + msgid "Current" msgstr "" @@ -16103,6 +16112,9 @@ msgstr "" msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded." msgstr "" +msgid "If this email was added in error, you can remove it here: %{profile_emails_url}" +msgstr "" + msgid "If this was a mistake you can %{leave_link_start}leave the %{source_type}%{link_end}." msgstr "" @@ -34017,6 +34029,9 @@ msgstr "" msgid "Use template" msgstr "" +msgid "Use the link below to confirm your email address (%{email})" +msgstr "" + msgid "Use the search bar on the top of this page" msgstr "" diff --git a/spec/services/authorized_project_update/find_records_due_for_refresh_service_spec.rb b/spec/services/authorized_project_update/find_records_due_for_refresh_service_spec.rb index cdc93eaa365..8a53d9fbf7c 100644 --- a/spec/services/authorized_project_update/find_records_due_for_refresh_service_spec.rb +++ b/spec/services/authorized_project_update/find_records_due_for_refresh_service_spec.rb @@ -94,6 +94,41 @@ RSpec.describe AuthorizedProjectUpdate::FindRecordsDueForRefreshService do end end + describe '#needs_refresh?' do + subject { service.needs_refresh? } + + context 'when there are records due for either removal or addition' do + context 'when there are both removals and additions to be made' do + before do + user.project_authorizations.delete_all + create(:project_authorization, user: user) + end + + it { is_expected.to eq(true) } + end + + context 'when there are no removals, but there are additions to be made' do + before do + user.project_authorizations.delete_all + end + + it { is_expected.to eq(true) } + end + + context 'when there are no additions, but there are removals to be made' do + before do + create(:project_authorization, user: user) + end + + it { is_expected.to eq(true) } + end + end + + context 'when there are no additions or removals to be made' do + it { is_expected.to eq(false) } + end + end + describe '#fresh_access_levels_per_project' do let(:hash) { service.fresh_access_levels_per_project } diff --git a/spec/workers/authorized_project_update/user_refresh_over_user_range_worker_spec.rb b/spec/workers/authorized_project_update/user_refresh_over_user_range_worker_spec.rb index a27c431523e..0501fc3b8cf 100644 --- a/spec/workers/authorized_project_update/user_refresh_over_user_range_worker_spec.rb +++ b/spec/workers/authorized_project_update/user_refresh_over_user_range_worker_spec.rb @@ -3,16 +3,67 @@ require 'spec_helper' RSpec.describe AuthorizedProjectUpdate::UserRefreshOverUserRangeWorker do - let(:start_user_id) { 42 } - let(:end_user_id) { 4242 } + let(:project) { create(:project) } + let(:user) { project.namespace.owner } + let(:start_user_id) { user.id } + let(:end_user_id) { start_user_id } + let(:execute_worker) { subject.perform(start_user_id, end_user_id) } + + it_behaves_like 'worker with data consistency', + described_class, + feature_flag: :periodic_project_authorization_update_via_replica, + data_consistency: :delayed describe '#perform' do - it 'calls AuthorizedProjectUpdate::RecalculateForUserRangeService' do - expect_next_instance_of(AuthorizedProjectUpdate::RecalculateForUserRangeService) do |service| - expect(service).to receive(:execute) + context 'when the feature flag `periodic_project_authorization_update_via_replica` is enabled' do + before do + stub_feature_flags(periodic_project_authorization_update_via_replica: true) end - subject.perform(start_user_id, end_user_id) + context 'checks if project authorization update is required' do + it 'checks if a project_authorization refresh is needed for each of the users' do + User.where(id: start_user_id..end_user_id).each do |user| + expect(AuthorizedProjectUpdate::FindRecordsDueForRefreshService).to( + receive(:new).with(user).and_call_original) + end + + execute_worker + end + end + + context 'when there are project authorization records due for either removal or addition for a specific user' do + before do + user.project_authorizations.delete_all + end + + it 'enqueues a new project authorization update job for the user' do + expect(AuthorizedProjectUpdate::UserRefreshWithLowUrgencyWorker).to receive(:perform_async).with(user.id) + + execute_worker + end + end + + context 'when there are no additions or removals to be made to project authorizations for a specific user' do + it 'does not enqueue a new project authorization update job for the user' do + expect(AuthorizedProjectUpdate::UserRefreshWithLowUrgencyWorker).not_to receive(:perform_async) + + execute_worker + end + end + end + + context 'when the feature flag `periodic_project_authorization_update_via_replica` is disabled' do + before do + stub_feature_flags(periodic_project_authorization_update_via_replica: false) + end + + it 'calls AuthorizedProjectUpdate::RecalculateForUserRangeService' do + expect_next_instance_of(AuthorizedProjectUpdate::RecalculateForUserRangeService, start_user_id, end_user_id) do |service| + expect(service).to receive(:execute) + end + + execute_worker + end end end end