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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-12-10 18:10:24 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-12-10 18:10:24 +0300
commitecc11e5d608ff4393fb6c44d02416569e7d2785d (patch)
treed6e2921cf11f525d8fd7bbbab213684983dba0cf /app
parente838c62efb5d95fe76b5bbb6cba8b73c40eb2008 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/issues/form.js7
-rw-r--r--app/assets/javascripts/issues/new/components/title_suggestions.vue (renamed from app/assets/javascripts/issues/suggestions/components/app.vue)8
-rw-r--r--app/assets/javascripts/issues/new/components/title_suggestions_item.vue (renamed from app/assets/javascripts/issues/suggestions/components/item.vue)0
-rw-r--r--app/assets/javascripts/issues/new/components/type_popover.vue (renamed from app/assets/javascripts/issues/type_selector/components/info_popover.vue)4
-rw-r--r--app/assets/javascripts/issues/new/index.js (renamed from app/assets/javascripts/issues/suggestions/index.js)32
-rw-r--r--app/assets/javascripts/issues/new/queries/issues.query.graphql (renamed from app/assets/javascripts/issues/suggestions/queries/issues.query.graphql)0
-rw-r--r--app/assets/javascripts/issues/type_selector/index.js16
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js11
-rw-r--r--app/assets/javascripts/runner/components/runner_list.vue14
-rw-r--r--app/assets/javascripts/runner/constants.js1
-rw-r--r--app/assets/javascripts/runner/graphql/runner_node.fragment.graphql1
-rw-r--r--app/assets/stylesheets/pages/issuable.scss4
-rw-r--r--app/controllers/application_controller.rb1
-rw-r--r--app/controllers/concerns/check_rate_limit.rb22
-rw-r--r--app/controllers/concerns/integrations/hooks_execution.rb12
-rw-r--r--app/controllers/concerns/notes_actions.rb7
-rw-r--r--app/controllers/groups_controller.rb10
-rw-r--r--app/controllers/import/base_controller.rb16
-rw-r--r--app/controllers/import/gitlab_groups_controller.rb11
-rw-r--r--app/controllers/profiles/emails_controller.rb16
-rw-r--r--app/controllers/projects/hooks_controller.rb2
-rw-r--r--app/controllers/projects/issues_controller.rb18
-rw-r--r--app/controllers/projects/pipeline_schedules_controller.rb10
-rw-r--r--app/controllers/projects/raw_controller.rb18
-rw-r--r--app/controllers/projects/repositories_controller.rb16
-rw-r--r--app/controllers/projects/settings/ci_cd_controller.rb1
-rw-r--r--app/controllers/projects_controller.rb14
-rw-r--r--app/models/ci/namespace_mirror.rb30
-rw-r--r--app/models/ci/project_mirror.rb9
-rw-r--r--app/models/container_repository.rb11
-rw-r--r--app/models/error_tracking/error_event.rb3
-rw-r--r--app/models/loose_foreign_keys/deleted_record.rb36
-rw-r--r--app/models/namespace.rb12
-rw-r--r--app/models/namespaces/sync_event.rb16
-rw-r--r--app/models/project.rb12
-rw-r--r--app/models/projects/sync_event.rb16
-rw-r--r--app/policies/namespaces/user_namespace_policy.rb3
-rw-r--r--app/presenters/ci/pipeline_presenter.rb9
-rw-r--r--app/presenters/prometheus_alert_presenter.rb2
-rw-r--r--app/services/auth/container_registry_authentication_service.rb2
-rw-r--r--app/services/ci/process_sync_events_service.rb58
-rw-r--r--app/services/ci/retry_build_service.rb10
-rw-r--r--app/services/search_service.rb2
-rw-r--r--app/views/shared/milestones/_sidebar.html.haml4
-rw-r--r--app/workers/all_queues.yml18
-rw-r--r--app/workers/namespaces/process_sync_events_worker.rb22
-rw-r--r--app/workers/projects/process_sync_events_worker.rb22
47 files changed, 383 insertions, 186 deletions
diff --git a/app/assets/javascripts/issues/form.js b/app/assets/javascripts/issues/form.js
index 20a8c251304..33371d065f9 100644
--- a/app/assets/javascripts/issues/form.js
+++ b/app/assets/javascripts/issues/form.js
@@ -4,8 +4,7 @@ import $ from 'jquery';
import IssuableForm from 'ee_else_ce/issuable/issuable_form';
import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
import GLForm from '~/gl_form';
-import initSuggestions from '~/issues/suggestions';
-import initIssuableTypeSelector from '~/issues/type_selector';
+import { initTitleSuggestions, initTypePopover } from '~/issues/new';
import LabelsSelect from '~/labels/labels_select';
import MilestoneSelect from '~/milestones/milestone_select';
import IssuableTemplateSelectors from '~/issuable/issuable_template_selectors';
@@ -20,6 +19,6 @@ export default () => {
warnTemplateOverride: true,
});
- initSuggestions();
- initIssuableTypeSelector();
+ initTitleSuggestions();
+ initTypePopover();
};
diff --git a/app/assets/javascripts/issues/suggestions/components/app.vue b/app/assets/javascripts/issues/new/components/title_suggestions.vue
index 48a5e220abf..0a9cdb12519 100644
--- a/app/assets/javascripts/issues/suggestions/components/app.vue
+++ b/app/assets/javascripts/issues/new/components/title_suggestions.vue
@@ -2,12 +2,12 @@
import { GlTooltipDirective, GlIcon } from '@gitlab/ui';
import { __ } from '~/locale';
import query from '../queries/issues.query.graphql';
-import Suggestion from './item.vue';
+import TitleSuggestionsItem from './title_suggestions_item.vue';
export default {
components: {
- Suggestion,
GlIcon,
+ TitleSuggestionsItem,
},
directives: {
GlTooltip: GlTooltipDirective,
@@ -66,7 +66,7 @@ export default {
</script>
<template>
- <div v-show="showSuggestions" class="form-group row issuable-suggestions">
+ <div v-show="showSuggestions" class="form-group row">
<div v-once class="col-form-label col-sm-2 pt-0">
{{ __('Similar issues') }}
<gl-icon
@@ -86,7 +86,7 @@ export default {
'gl-mb-3': index !== issues.length - 1,
}"
>
- <suggestion :suggestion="suggestion" />
+ <title-suggestions-item :suggestion="suggestion" />
</li>
</ul>
</div>
diff --git a/app/assets/javascripts/issues/suggestions/components/item.vue b/app/assets/javascripts/issues/new/components/title_suggestions_item.vue
index a01f4f747b9..a01f4f747b9 100644
--- a/app/assets/javascripts/issues/suggestions/components/item.vue
+++ b/app/assets/javascripts/issues/new/components/title_suggestions_item.vue
diff --git a/app/assets/javascripts/issues/type_selector/components/info_popover.vue b/app/assets/javascripts/issues/new/components/type_popover.vue
index 3a20ccba814..a70e79b70f9 100644
--- a/app/assets/javascripts/issues/type_selector/components/info_popover.vue
+++ b/app/assets/javascripts/issues/new/components/type_popover.vue
@@ -19,9 +19,9 @@ export default {
<template>
<span id="popovercontainer">
- <gl-icon id="issuable-type-info" name="question-o" class="gl-ml-5 gl-text-gray-500" />
+ <gl-icon id="issue-type-info" name="question-o" class="gl-ml-5 gl-text-gray-500" />
<gl-popover
- target="issuable-type-info"
+ target="issue-type-info"
container="popovercontainer"
:title="$options.i18n.issueTypes"
triggers="focus hover"
diff --git a/app/assets/javascripts/issues/suggestions/index.js b/app/assets/javascripts/issues/new/index.js
index 8f7f317d6b4..59a7cbec627 100644
--- a/app/assets/javascripts/issues/suggestions/index.js
+++ b/app/assets/javascripts/issues/new/index.js
@@ -1,14 +1,19 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
-import App from './components/app.vue';
+import TitleSuggestions from './components/title_suggestions.vue';
+import TypePopover from './components/type_popover.vue';
-Vue.use(VueApollo);
+export function initTitleSuggestions() {
+ Vue.use(VueApollo);
-export default function initIssuableSuggestions() {
const el = document.getElementById('js-suggestions');
const issueTitle = document.getElementById('issue_title');
- const { projectPath } = el.dataset;
+
+ if (!el) {
+ return undefined;
+ }
+
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(),
});
@@ -26,13 +31,26 @@ export default function initIssuableSuggestions() {
this.search = issueTitle.value;
});
},
- render(h) {
- return h(App, {
+ render(createElement) {
+ return createElement(TitleSuggestions, {
props: {
- projectPath,
+ projectPath: el.dataset.projectPath,
search: this.search,
},
});
},
});
}
+
+export function initTypePopover() {
+ const el = document.getElementById('js-type-popover');
+
+ if (!el) {
+ return undefined;
+ }
+
+ return new Vue({
+ el,
+ render: (createElement) => createElement(TypePopover),
+ });
+}
diff --git a/app/assets/javascripts/issues/suggestions/queries/issues.query.graphql b/app/assets/javascripts/issues/new/queries/issues.query.graphql
index dc0757b141f..dc0757b141f 100644
--- a/app/assets/javascripts/issues/suggestions/queries/issues.query.graphql
+++ b/app/assets/javascripts/issues/new/queries/issues.query.graphql
diff --git a/app/assets/javascripts/issues/type_selector/index.js b/app/assets/javascripts/issues/type_selector/index.js
deleted file mode 100644
index 433a62d1ae8..00000000000
--- a/app/assets/javascripts/issues/type_selector/index.js
+++ /dev/null
@@ -1,16 +0,0 @@
-import Vue from 'vue';
-import InfoPopover from './components/info_popover.vue';
-
-export default function initIssuableTypeSelector() {
- const el = document.getElementById('js-type-popover');
-
- return new Vue({
- el,
- components: {
- InfoPopover,
- },
- render(h) {
- return h(InfoPopover);
- },
- });
-}
diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js
index a82dad7e2c9..7235b38848c 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js
+++ b/app/assets/javascripts/lib/utils/common_utils.js
@@ -735,3 +735,14 @@ export const isFeatureFlagEnabled = (flag) => window.gon.features?.[flag];
export const convertArrayToCamelCase = (array) => array.map((i) => convertToCamelCase(i));
export const isLoggedIn = () => Boolean(window.gon?.current_user_id);
+
+/**
+ * This method takes in array of objects with snake_case
+ * property names and returns a new array of objects with
+ * camelCase property names
+ *
+ * @param {Array[Object]} array - Array to be converted
+ * @returns {Array[Object]} Converted array
+ */
+export const convertArrayOfObjectsToCamelCase = (array) =>
+ array.map((o) => convertObjectPropsToCamelCase(o));
diff --git a/app/assets/javascripts/runner/components/runner_list.vue b/app/assets/javascripts/runner/components/runner_list.vue
index f96eb0fa564..023308dbac2 100644
--- a/app/assets/javascripts/runner/components/runner_list.vue
+++ b/app/assets/javascripts/runner/components/runner_list.vue
@@ -2,8 +2,9 @@
import { GlTable, GlTooltipDirective, GlSkeletonLoader } from '@gitlab/ui';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
-import { __, s__ } from '~/locale';
+import { formatNumber, __, s__ } from '~/locale';
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
+import { RUNNER_JOB_COUNT_LIMIT } from '../constants';
import RunnerActionsCell from './cells/runner_actions_cell.vue';
import RunnerSummaryCell from './cells/runner_summary_cell.vue';
import RunnerStatusCell from './cells/runner_status_cell.vue';
@@ -52,6 +53,12 @@ export default {
},
},
methods: {
+ formatJobCount(jobCount) {
+ if (jobCount > RUNNER_JOB_COUNT_LIMIT) {
+ return `${formatNumber(RUNNER_JOB_COUNT_LIMIT)}+`;
+ }
+ return formatNumber(jobCount);
+ },
runnerTrAttr(runner) {
if (runner) {
return {
@@ -66,6 +73,7 @@ export default {
tableField({ key: 'summary', label: s__('Runners|Runner ID'), thClasses: ['gl-lg-w-25p'] }),
tableField({ key: 'version', label: __('Version') }),
tableField({ key: 'ipAddress', label: __('IP Address') }),
+ tableField({ key: 'jobCount', label: __('Jobs') }),
tableField({ key: 'tagList', label: __('Tags'), thClasses: ['gl-lg-w-25p'] }),
tableField({ key: 'contactedAt', label: __('Last contact') }),
tableField({ key: 'actions', label: '' }),
@@ -112,6 +120,10 @@ export default {
</tooltip-on-truncate>
</template>
+ <template #cell(jobCount)="{ item: { jobCount } }">
+ {{ formatJobCount(jobCount) }}
+ </template>
+
<template #cell(tagList)="{ item: { tagList } }">
<runner-tags :tag-list="tagList" size="sm" />
</template>
diff --git a/app/assets/javascripts/runner/constants.js b/app/assets/javascripts/runner/constants.js
index f0aa15ef64c..68e45fcf8e9 100644
--- a/app/assets/javascripts/runner/constants.js
+++ b/app/assets/javascripts/runner/constants.js
@@ -1,6 +1,7 @@
import { s__ } from '~/locale';
export const RUNNER_PAGE_SIZE = 20;
+export const RUNNER_JOB_COUNT_LIMIT = 1000;
export const GROUP_RUNNER_COUNT_LIMIT = 1000;
export const I18N_FETCH_ERROR = s__('Runners|Something went wrong while fetching runner data.');
diff --git a/app/assets/javascripts/runner/graphql/runner_node.fragment.graphql b/app/assets/javascripts/runner/graphql/runner_node.fragment.graphql
index 3828d725758..169f6ffd2ea 100644
--- a/app/assets/javascripts/runner/graphql/runner_node.fragment.graphql
+++ b/app/assets/javascripts/runner/graphql/runner_node.fragment.graphql
@@ -8,6 +8,7 @@ fragment RunnerNode on CiRunner {
ipAddress
active
locked
+ jobCount
tagList
contactedAt
status(legacyMode: null)
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 327135d17ab..8600a4059d8 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -869,10 +869,6 @@
}
}
-.issuable-suggestions svg {
- vertical-align: sub;
-}
-
.suggestion-footer {
font-size: 12px;
line-height: 15px;
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 976a9fa5038..d3ecbdcc1f6 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -23,6 +23,7 @@ class ApplicationController < ActionController::Base
include Gitlab::Utils::StrongMemoize
include ::Gitlab::EndpointAttributes
include FlocOptOut
+ include CheckRateLimit
before_action :authenticate_user!, except: [:route_not_found]
before_action :enforce_terms!, if: :should_enforce_terms?
diff --git a/app/controllers/concerns/check_rate_limit.rb b/app/controllers/concerns/check_rate_limit.rb
index c4de3315e22..5ccdf843525 100644
--- a/app/controllers/concerns/check_rate_limit.rb
+++ b/app/controllers/concerns/check_rate_limit.rb
@@ -5,19 +5,27 @@
# Controller concern that checks if the rate limit for a given action is throttled by calling the
# Gitlab::ApplicationRateLimiter class. If the action is throttled for the current user, the request
# will be logged and an error message will be rendered with a Too Many Requests response status.
+# See lib/api/helpers/rate_limiter.rb for API version
module CheckRateLimit
- def check_rate_limit(key)
- return unless rate_limiter.throttled?(key, scope: current_user, users_allowlist: rate_limit_users_allowlist)
+ def check_rate_limit!(key, scope:, redirect_back: false, **options)
+ return unless rate_limiter.throttled?(key, scope: scope, **options)
rate_limiter.log_request(request, "#{key}_request_limit".to_sym, current_user)
- render plain: _('This endpoint has been requested too many times. Try again later.'), status: :too_many_requests
+
+ return yield if block_given?
+
+ message = _('This endpoint has been requested too many times. Try again later.')
+
+ if redirect_back
+ redirect_back_or_default(options: { alert: message })
+ else
+ render plain: message, status: :too_many_requests
+ end
end
+ private
+
def rate_limiter
::Gitlab::ApplicationRateLimiter
end
-
- def rate_limit_users_allowlist
- Gitlab::CurrentSettings.current_application_settings.notes_create_limit_allowlist
- end
end
diff --git a/app/controllers/concerns/integrations/hooks_execution.rb b/app/controllers/concerns/integrations/hooks_execution.rb
index af039057a9c..6a9d3d51f9b 100644
--- a/app/controllers/concerns/integrations/hooks_execution.rb
+++ b/app/controllers/concerns/integrations/hooks_execution.rb
@@ -32,16 +32,4 @@ module Integrations::HooksExecution
flash[:alert] = "Hook execution failed: #{message}"
end
end
-
- def create_rate_limit(key, scope)
- if rate_limiter.throttled?(key, scope: [scope, current_user])
- rate_limiter.log_request(request, "#{key}_request_limit".to_sym, current_user)
-
- render plain: _('This endpoint has been requested too many times. Try again later.'), status: :too_many_requests
- end
- end
-
- def rate_limiter
- ::Gitlab::ApplicationRateLimiter
- end
end
diff --git a/app/controllers/concerns/notes_actions.rb b/app/controllers/concerns/notes_actions.rb
index 113030429d8..8410a8779f6 100644
--- a/app/controllers/concerns/notes_actions.rb
+++ b/app/controllers/concerns/notes_actions.rb
@@ -3,7 +3,6 @@
module NotesActions
include RendersNotes
include Gitlab::Utils::StrongMemoize
- include CheckRateLimit
extend ActiveSupport::Concern
# last_fetched_at is an integer number of microseconds, which is the same
@@ -16,7 +15,11 @@ module NotesActions
before_action :require_noteable!, only: [:index, :create]
before_action :authorize_admin_note!, only: [:update, :destroy]
before_action :note_project, only: [:create]
- before_action -> { check_rate_limit(:notes_create) }, only: [:create]
+ before_action -> {
+ check_rate_limit!(:notes_create,
+ scope: current_user,
+ users_allowlist: Gitlab::CurrentSettings.current_application_settings.notes_create_limit_allowlist)
+ }, only: [:create]
end
def index
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index 7f2b026b5b3..62336c7eede 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -37,7 +37,7 @@ class GroupsController < Groups::ApplicationController
push_frontend_feature_flag(:iteration_cadences, @group, default_enabled: :yaml)
end
- before_action :export_rate_limit, only: [:export, :download_export]
+ before_action :check_export_rate_limit!, only: [:export, :download_export]
helper_method :captcha_required?
@@ -314,16 +314,12 @@ class GroupsController < Groups::ApplicationController
url_for(safe_params)
end
- def export_rate_limit
+ def check_export_rate_limit!
prefixed_action = "group_#{params[:action]}".to_sym
scope = params[:action] == :download_export ? @group : nil
- if Gitlab::ApplicationRateLimiter.throttled?(prefixed_action, scope: [current_user, scope].compact)
- Gitlab::ApplicationRateLimiter.log_request(request, "#{prefixed_action}_request_limit".to_sym, current_user)
-
- render plain: _('This endpoint has been requested too many times. Try again later.'), status: :too_many_requests
- end
+ check_rate_limit!(prefixed_action, scope: [current_user, scope].compact)
end
def ensure_export_enabled
diff --git a/app/controllers/import/base_controller.rb b/app/controllers/import/base_controller.rb
index 53856e4575b..7ad3a2ee358 100644
--- a/app/controllers/import/base_controller.rb
+++ b/app/controllers/import/base_controller.rb
@@ -3,7 +3,7 @@
class Import::BaseController < ApplicationController
include ActionView::Helpers::SanitizeHelper
- before_action :import_rate_limit, only: [:create]
+ before_action -> { check_rate_limit!(:project_import, scope: [current_user, :project_import], redirect_back: true) }, only: [:create]
feature_category :importers
def status
@@ -98,18 +98,4 @@ class Import::BaseController < ApplicationController
def project_save_error(project)
project.errors.full_messages.join(', ')
end
-
- def import_rate_limit
- key = "project_import".to_sym
-
- if rate_limiter.throttled?(key, scope: [current_user, key])
- rate_limiter.log_request(request, "#{key}_request_limit".to_sym, current_user)
-
- redirect_back_or_default(options: { alert: _('This endpoint has been requested too many times. Try again later.') })
- end
- end
-
- def rate_limiter
- ::Gitlab::ApplicationRateLimiter
- end
end
diff --git a/app/controllers/import/gitlab_groups_controller.rb b/app/controllers/import/gitlab_groups_controller.rb
index 503b10f766b..aca71f6d57a 100644
--- a/app/controllers/import/gitlab_groups_controller.rb
+++ b/app/controllers/import/gitlab_groups_controller.rb
@@ -4,7 +4,7 @@ class Import::GitlabGroupsController < ApplicationController
include WorkhorseAuthorization
before_action :ensure_group_import_enabled
- before_action :import_rate_limit, only: %i[create]
+ before_action :check_import_rate_limit!, only: %i[create]
feature_category :importers
@@ -55,12 +55,9 @@ class Import::GitlabGroupsController < ApplicationController
render_404 unless Feature.enabled?(:group_import_export, @group, default_enabled: true)
end
- def import_rate_limit
- if Gitlab::ApplicationRateLimiter.throttled?(:group_import, scope: current_user)
- Gitlab::ApplicationRateLimiter.log_request(request, :group_import_request_limit, current_user)
-
- flash[:alert] = _('This endpoint has been requested too many times. Try again later.')
- redirect_to new_group_path
+ def check_import_rate_limit!
+ check_rate_limit!(:group_import, scope: current_user) do
+ redirect_to new_group_path, alert: _('This endpoint has been requested too many times. Try again later.')
end
end
diff --git a/app/controllers/profiles/emails_controller.rb b/app/controllers/profiles/emails_controller.rb
index 6e5b18cb885..be2cb270a19 100644
--- a/app/controllers/profiles/emails_controller.rb
+++ b/app/controllers/profiles/emails_controller.rb
@@ -2,8 +2,10 @@
class Profiles::EmailsController < Profiles::ApplicationController
before_action :find_email, only: [:destroy, :resend_confirmation_instructions]
- before_action -> { rate_limit!(:profile_add_new_email) }, only: [:create]
- before_action -> { rate_limit!(:profile_resend_email_confirmation) }, only: [:resend_confirmation_instructions]
+ before_action -> { check_rate_limit!(:profile_add_new_email, scope: current_user, redirect_back: true) },
+ only: [:create]
+ before_action -> { check_rate_limit!(:profile_resend_email_confirmation, scope: current_user, redirect_back: true) },
+ only: [:resend_confirmation_instructions]
feature_category :users
@@ -42,16 +44,6 @@ class Profiles::EmailsController < Profiles::ApplicationController
private
- def rate_limit!(action)
- rate_limiter = ::Gitlab::ApplicationRateLimiter
-
- if rate_limiter.throttled?(action, scope: current_user)
- rate_limiter.log_request(request, action, current_user)
-
- redirect_back_or_default(options: { alert: _('This action has been performed too many times. Try again later.') })
- end
- end
-
def email_params
params.require(:email).permit(:email)
end
diff --git a/app/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb
index c79e5a8cc85..99eba32e00f 100644
--- a/app/controllers/projects/hooks_controller.rb
+++ b/app/controllers/projects/hooks_controller.rb
@@ -6,7 +6,7 @@ class Projects::HooksController < Projects::ApplicationController
# Authorize
before_action :authorize_admin_project!
before_action :hook_logs, only: :edit
- before_action -> { create_rate_limit(:project_testing_hook, @project) }, only: :test
+ before_action -> { check_rate_limit!(:project_testing_hook, scope: [@project, current_user]) }, only: :test
respond_to :html
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index d2d7ecfab6f..970efd9cdfb 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -37,7 +37,9 @@ class Projects::IssuesController < Projects::ApplicationController
before_action :authorize_download_code!, only: [:related_branches]
# Limit the amount of issues created per minute
- before_action :create_rate_limit, only: [:create], if: -> { Feature.disabled?('rate_limited_service_issues_create', project, default_enabled: :yaml) }
+ before_action -> { check_rate_limit!(:issues_create, scope: [@project, @current_user])},
+ only: [:create],
+ if: -> { Feature.disabled?('rate_limited_service_issues_create', project, default_enabled: :yaml) }
before_action do
push_frontend_feature_flag(:tribute_autocomplete, @project)
@@ -363,20 +365,6 @@ class Projects::IssuesController < Projects::ApplicationController
project_compare_path(project, from: project.default_branch, to: branch[:name])
end
- def create_rate_limit
- key = :issues_create
-
- if rate_limiter.throttled?(key, scope: [@project, @current_user])
- rate_limiter.log_request(request, "#{key}_request_limit".to_sym, current_user)
-
- render plain: _('This endpoint has been requested too many times. Try again later.'), status: :too_many_requests
- end
- end
-
- def rate_limiter
- ::Gitlab::ApplicationRateLimiter
- end
-
def service_desk?
action_name == 'service_desk'
end
diff --git a/app/controllers/projects/pipeline_schedules_controller.rb b/app/controllers/projects/pipeline_schedules_controller.rb
index 4af7508b935..ac94cc001dd 100644
--- a/app/controllers/projects/pipeline_schedules_controller.rb
+++ b/app/controllers/projects/pipeline_schedules_controller.rb
@@ -3,7 +3,7 @@
class Projects::PipelineSchedulesController < Projects::ApplicationController
before_action :schedule, except: [:index, :new, :create]
- before_action :play_rate_limit, only: [:play]
+ before_action :check_play_rate_limit!, only: [:play]
before_action :authorize_play_pipeline_schedule!, only: [:play]
before_action :authorize_read_pipeline_schedule!
before_action :authorize_create_pipeline_schedule!, only: [:new, :create]
@@ -81,19 +81,15 @@ class Projects::PipelineSchedulesController < Projects::ApplicationController
private
- def play_rate_limit
+ def check_play_rate_limit!
return unless current_user
- if rate_limiter.throttled?(:play_pipeline_schedule, scope: [current_user, schedule])
+ check_rate_limit!(:play_pipeline_schedule, scope: [current_user, schedule]) do
flash[:alert] = _('You cannot play this scheduled pipeline at the moment. Please wait a minute.')
redirect_to pipeline_schedules_path(@project)
end
end
- def rate_limiter
- ::Gitlab::ApplicationRateLimiter
- end
-
def schedule
@schedule ||= project.pipeline_schedules.find(params[:id])
end
diff --git a/app/controllers/projects/raw_controller.rb b/app/controllers/projects/raw_controller.rb
index 8960783400e..9707b70f26f 100644
--- a/app/controllers/projects/raw_controller.rb
+++ b/app/controllers/projects/raw_controller.rb
@@ -13,7 +13,7 @@ class Projects::RawController < Projects::ApplicationController
before_action :set_ref_and_path
before_action :require_non_empty_project
before_action :authorize_download_code!
- before_action :show_rate_limit, only: [:show], unless: :external_storage_request?
+ before_action :check_show_rate_limit!, only: [:show], unless: :external_storage_request?
before_action :redirect_to_external_storage, only: :show, if: :static_objects_external_storage_enabled?
feature_category :source_code_management
@@ -33,23 +33,11 @@ class Projects::RawController < Projects::ApplicationController
@ref, @path = extract_ref(get_id)
end
- def show_rate_limit
- if rate_limiter.throttled?(:show_raw_controller, scope: [@project, @path], threshold: raw_blob_request_limit)
- rate_limiter.log_request(request, :raw_blob_request_limit, current_user)
-
+ def check_show_rate_limit!
+ check_rate_limit!(:raw_blob, scope: [@project, @path]) do
render plain: _('You cannot access the raw file. Please wait a minute.'), status: :too_many_requests
end
end
-
- def rate_limiter
- ::Gitlab::ApplicationRateLimiter
- end
-
- def raw_blob_request_limit
- Gitlab::CurrentSettings
- .current_application_settings
- .raw_blob_request_limit
- end
end
Projects::RawController.prepend_mod
diff --git a/app/controllers/projects/repositories_controller.rb b/app/controllers/projects/repositories_controller.rb
index 8beebb52980..77826a2f789 100644
--- a/app/controllers/projects/repositories_controller.rb
+++ b/app/controllers/projects/repositories_controller.rb
@@ -3,16 +3,16 @@
class Projects::RepositoriesController < Projects::ApplicationController
include ExtractsPath
include StaticObjectExternalStorage
- include Gitlab::RateLimitHelpers
include HotlinkInterceptor
+ include Gitlab::RepositoryArchiveRateLimiter
prepend_before_action(only: [:archive]) { authenticate_sessionless_user!(:archive) }
skip_before_action :default_cache_headers, only: :archive
# Authorize
+ before_action :check_archive_rate_limiting!, only: :archive
before_action :require_non_empty_project, except: :create
- before_action :archive_rate_limit!, only: :archive
before_action :intercept_hotlinking!, only: :archive
before_action :assign_archive_vars, only: :archive
before_action :assign_append_sha, only: :archive
@@ -42,12 +42,6 @@ class Projects::RepositoriesController < Projects::ApplicationController
private
- def archive_rate_limit!
- if archive_rate_limit_reached?(current_user, @project)
- render plain: ::Gitlab::RateLimitHelpers::ARCHIVE_RATE_LIMIT_REACHED_MESSAGE, status: :too_many_requests
- end
- end
-
def repo_params
@repo_params ||= { ref: @ref, path: params[:path], format: params[:format], append_sha: @append_sha }
end
@@ -125,6 +119,12 @@ class Projects::RepositoriesController < Projects::ApplicationController
[path, nil]
end
end
+
+ def check_archive_rate_limiting!
+ check_archive_rate_limit!(current_user, @project) do
+ render(plain: _('This archive has been requested too many times. Try again later.'), status: :too_many_requests)
+ end
+ end
end
Projects::RepositoriesController.prepend_mod_with('Projects::RepositoriesController')
diff --git a/app/controllers/projects/settings/ci_cd_controller.rb b/app/controllers/projects/settings/ci_cd_controller.rb
index 887f98362b4..ef6c10d43cd 100644
--- a/app/controllers/projects/settings/ci_cd_controller.rb
+++ b/app/controllers/projects/settings/ci_cd_controller.rb
@@ -9,6 +9,7 @@ module Projects
layout 'project_settings'
before_action :authorize_admin_pipeline!
+ before_action :check_builds_available!
before_action :define_variables
before_action do
push_frontend_feature_flag(:ajax_new_deploy_token, @project)
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 6af46e22d8f..428903a9e75 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -30,7 +30,7 @@ class ProjectsController < Projects::ApplicationController
before_action :event_filter, only: [:show, :activity]
# Project Export Rate Limit
- before_action :export_rate_limit, only: [:export, :download_export, :generate_new_export]
+ before_action :check_export_rate_limit!, only: [:export, :download_export, :generate_new_export]
before_action do
push_frontend_feature_flag(:lazy_load_commits, @project, default_enabled: :yaml)
@@ -544,20 +544,12 @@ class ProjectsController < Projects::ApplicationController
@project = @project.present(current_user: current_user)
end
- def export_rate_limit
+ def check_export_rate_limit!
prefixed_action = "project_#{params[:action]}".to_sym
project_scope = params[:action] == 'download_export' ? @project : nil
- if rate_limiter.throttled?(prefixed_action, scope: [current_user, project_scope].compact)
- rate_limiter.log_request(request, "#{prefixed_action}_request_limit".to_sym, current_user)
-
- render plain: _('This endpoint has been requested too many times. Try again later.'), status: :too_many_requests
- end
- end
-
- def rate_limiter
- ::Gitlab::ApplicationRateLimiter
+ check_rate_limit!(prefixed_action, scope: [current_user, project_scope].compact)
end
def render_edit
diff --git a/app/models/ci/namespace_mirror.rb b/app/models/ci/namespace_mirror.rb
index a497d2cabe5..8a4be3139e8 100644
--- a/app/models/ci/namespace_mirror.rb
+++ b/app/models/ci/namespace_mirror.rb
@@ -4,6 +4,34 @@ module Ci
# This model represents a record in a shadow table of the main database's namespaces table.
# It allows us to navigate the namespace hierarchy on the ci database without resorting to a JOIN.
class NamespaceMirror < ApplicationRecord
- # Will be filled by https://gitlab.com/gitlab-org/gitlab/-/merge_requests/75517
+ belongs_to :namespace
+
+ scope :contains_namespace, -> (id) do
+ where('traversal_ids @> ARRAY[?]::int[]', id)
+ end
+
+ class << self
+ def sync!(event)
+ namespace = event.namespace
+ traversal_ids = namespace.self_and_ancestor_ids(hierarchy_order: :desc)
+
+ upsert({ namespace_id: event.namespace_id, traversal_ids: traversal_ids },
+ unique_by: :namespace_id)
+
+ # It won't be necessary once we remove `sync_traversal_ids`.
+ # More info: https://gitlab.com/gitlab-org/gitlab/-/issues/347541
+ sync_children_namespaces!(event.namespace_id, traversal_ids)
+ end
+
+ private
+
+ def sync_children_namespaces!(namespace_id, traversal_ids)
+ contains_namespace(namespace_id)
+ .where.not(namespace_id: namespace_id)
+ .update_all(
+ "traversal_ids = ARRAY[#{sanitize_sql(traversal_ids.join(','))}]::int[] || traversal_ids[array_position(traversal_ids, #{sanitize_sql(namespace_id)}) + 1:]"
+ )
+ end
+ end
end
end
diff --git a/app/models/ci/project_mirror.rb b/app/models/ci/project_mirror.rb
index c6e3101fb3a..d6aaa3f50c1 100644
--- a/app/models/ci/project_mirror.rb
+++ b/app/models/ci/project_mirror.rb
@@ -4,6 +4,13 @@ module Ci
# This model represents a shadow table of the main database's projects table.
# It allows us to navigate the project and namespace hierarchy on the ci database.
class ProjectMirror < ApplicationRecord
- # Will be filled by https://gitlab.com/gitlab-org/gitlab/-/merge_requests/75517
+ belongs_to :project
+
+ class << self
+ def sync!(event)
+ upsert({ project_id: event.project_id, namespace_id: event.project.namespace_id },
+ unique_by: :project_id)
+ end
+ end
end
end
diff --git a/app/models/container_repository.rb b/app/models/container_repository.rb
index 8e130998f11..c914819f79d 100644
--- a/app/models/container_repository.rb
+++ b/app/models/container_repository.rb
@@ -145,9 +145,14 @@ class ContainerRepository < ApplicationRecord
name: path.repository_name)
end
- def self.create_from_path!(path)
- safe_find_or_create_by!(project: path.repository_project,
- name: path.repository_name)
+ def self.find_or_create_from_path(path)
+ repository = safe_find_or_create_by(
+ project: path.repository_project,
+ name: path.repository_name
+ )
+ return repository if repository.persisted?
+
+ find_by_path!(path)
end
def self.build_root_repository(project)
diff --git a/app/models/error_tracking/error_event.rb b/app/models/error_tracking/error_event.rb
index 0b638f65768..18c1467e6f6 100644
--- a/app/models/error_tracking/error_event.rb
+++ b/app/models/error_tracking/error_event.rb
@@ -3,6 +3,9 @@
class ErrorTracking::ErrorEvent < ApplicationRecord
belongs_to :error, counter_cache: :events_count
+ # Scrub null bytes
+ attribute :payload, Gitlab::Database::Type::JsonPgSafe.new
+
validates :payload, json_schema: { filename: 'error_tracking_event_payload' }
validates :error, presence: true
diff --git a/app/models/loose_foreign_keys/deleted_record.rb b/app/models/loose_foreign_keys/deleted_record.rb
index c3b3e76f67b..0fbdd2d8a5b 100644
--- a/app/models/loose_foreign_keys/deleted_record.rb
+++ b/app/models/loose_foreign_keys/deleted_record.rb
@@ -1,15 +1,45 @@
# frozen_string_literal: true
class LooseForeignKeys::DeletedRecord < ApplicationRecord
+ PARTITION_DURATION = 1.day
+
+ include PartitionedTable
+
self.primary_key = :id
+ self.ignored_columns = %i[partition]
+
+ partitioned_by :partition, strategy: :sliding_list,
+ next_partition_if: -> (active_partition) do
+ return false if Feature.disabled?(:lfk_automatic_partition_creation, default_enabled: :yaml)
+
+ oldest_record_in_partition = LooseForeignKeys::DeletedRecord
+ .select(:id, :created_at)
+ .for_partition(active_partition)
+ .order(:id)
+ .limit(1)
+ .take
+
+ oldest_record_in_partition.present? && oldest_record_in_partition.created_at < PARTITION_DURATION.ago
+ end,
+ detach_partition_if: -> (partition) do
+ return false if Feature.disabled?(:lfk_automatic_partition_dropping, default_enabled: :yaml)
+
+ !LooseForeignKeys::DeletedRecord
+ .for_partition(partition)
+ .status_pending
+ .exists?
+ end
scope :for_table, -> (table) { where(fully_qualified_table_name: table) }
+ scope :for_partition, -> (partition) { where(partition: partition) }
scope :consume_order, -> { order(:partition, :consume_after, :id) }
enum status: { pending: 1, processed: 2 }, _prefix: :status
def self.load_batch_for_table(table, batch_size)
- for_table(table)
+ # selecting partition as partition_number to workaround the sliding partitioning column ignore
+ select(arel_table[Arel.star], arel_table[:partition].as('partition_number'))
+ .for_table(table)
.status_pending
.consume_order
.limit(batch_size)
@@ -20,9 +50,9 @@ class LooseForeignKeys::DeletedRecord < ApplicationRecord
# Run a query for each partition to optimize the row lookup by primary key (partition, id)
update_count = 0
- all_records.group_by(&:partition).each do |partition, records_within_partition|
+ all_records.group_by(&:partition_number).each do |partition, records_within_partition|
update_count += status_pending
- .where(partition: partition)
+ .for_partition(partition)
.where(id: records_within_partition.pluck(:id))
.update_all(status: :processed)
end
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index db306221318..4b1cf2fa217 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -64,6 +64,9 @@ class Namespace < ApplicationRecord
has_one :admin_note, inverse_of: :namespace
accepts_nested_attributes_for :admin_note, update_only: true
+ has_one :ci_namespace_mirror, class_name: 'Ci::NamespaceMirror'
+ has_many :sync_events, class_name: 'Namespaces::SyncEvent'
+
validates :owner, presence: true, if: ->(n) { n.owner_required? }
validates :name,
presence: true,
@@ -104,6 +107,8 @@ class Namespace < ApplicationRecord
delegate :name, to: :owner, allow_nil: true, prefix: true
delegate :avatar_url, to: :owner, allow_nil: true
+ after_save :schedule_sync_event_worker, if: -> { saved_change_to_id? || saved_change_to_parent_id? }
+
after_commit :refresh_access_of_projects_invited_groups, on: :update, if: -> { previous_changes.key?('share_with_group_lock') }
before_create :sync_share_with_group_lock_with_parent
@@ -609,6 +614,13 @@ class Namespace < ApplicationRecord
def enforce_minimum_path_length?
path_changed? && !project_namespace?
end
+
+ # SyncEvents are created by PG triggers (with the function `insert_namespaces_sync_event`)
+ def schedule_sync_event_worker
+ run_after_commit do
+ Namespaces::SyncEvent.enqueue_worker
+ end
+ end
end
Namespace.prepend_mod_with('Namespace')
diff --git a/app/models/namespaces/sync_event.rb b/app/models/namespaces/sync_event.rb
new file mode 100644
index 00000000000..8534d8afb8c
--- /dev/null
+++ b/app/models/namespaces/sync_event.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+# This model serves to keep track of changes to the namespaces table in the main database, and allowing to safely
+# replicate these changes to other databases.
+class Namespaces::SyncEvent < ApplicationRecord
+ self.table_name = 'namespaces_sync_events'
+
+ belongs_to :namespace
+
+ scope :preload_synced_relation, -> { preload(:namespace) }
+ scope :order_by_id_asc, -> { order(id: :asc) }
+
+ def self.enqueue_worker
+ ::Namespaces::ProcessSyncEventsWorker.perform_async # rubocop:disable CodeReuse/Worker
+ end
+end
diff --git a/app/models/project.rb b/app/models/project.rb
index 088a2f9ea27..a751e8adeb0 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -102,6 +102,8 @@ class Project < ApplicationRecord
after_save :update_project_statistics, if: :saved_change_to_namespace_id?
+ after_save :schedule_sync_event_worker, if: -> { saved_change_to_id? || saved_change_to_namespace_id? }
+
after_save :create_import_state, if: ->(project) { project.import? && project.import_state.nil? }
after_save :save_topics
@@ -394,6 +396,9 @@ class Project < ApplicationRecord
has_many :timelogs
+ has_one :ci_project_mirror, class_name: 'Ci::ProjectMirror'
+ has_many :sync_events, class_name: 'Projects::SyncEvent'
+
accepts_nested_attributes_for :variables, allow_destroy: true
accepts_nested_attributes_for :project_feature, update_only: true
accepts_nested_attributes_for :project_setting, update_only: true
@@ -2938,6 +2943,13 @@ class Project < ApplicationRecord
project_namespace.shared_runners_enabled = shared_runners_enabled
project_namespace.visibility_level = visibility_level
end
+
+ # SyncEvents are created by PG triggers (with the function `insert_projects_sync_event`)
+ def schedule_sync_event_worker
+ run_after_commit do
+ Projects::SyncEvent.enqueue_worker
+ end
+ end
end
Project.prepend_mod_with('Project')
diff --git a/app/models/projects/sync_event.rb b/app/models/projects/sync_event.rb
new file mode 100644
index 00000000000..5221b00c55f
--- /dev/null
+++ b/app/models/projects/sync_event.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+# This model serves to keep track of changes to the namespaces table in the main database as they relate to projects,
+# allowing to safely replicate changes to other databases.
+class Projects::SyncEvent < ApplicationRecord
+ self.table_name = 'projects_sync_events'
+
+ belongs_to :project
+
+ scope :preload_synced_relation, -> { preload(:project) }
+ scope :order_by_id_asc, -> { order(id: :asc) }
+
+ def self.enqueue_worker
+ ::Projects::ProcessSyncEventsWorker.perform_async # rubocop:disable CodeReuse/Worker
+ end
+end
diff --git a/app/policies/namespaces/user_namespace_policy.rb b/app/policies/namespaces/user_namespace_policy.rb
index 93626c6d1a1..09b0f5d608d 100644
--- a/app/policies/namespaces/user_namespace_policy.rb
+++ b/app/policies/namespaces/user_namespace_policy.rb
@@ -4,7 +4,6 @@ module Namespaces
class UserNamespacePolicy < ::NamespacePolicy
rule { anonymous }.prevent_all
- condition(:personal_project, scope: :subject) { @subject.kind == 'user' }
condition(:can_create_personal_project, scope: :user) { @user.can_create_project? }
condition(:owner) { @subject.owner == @user }
@@ -19,7 +18,7 @@ module Namespaces
enable :read_package_settings
end
- rule { personal_project & ~can_create_personal_project }.prevent :create_projects
+ rule { ~can_create_personal_project }.prevent :create_projects
rule { (owner | admin) & can?(:create_projects) }.enable :transfer_projects
end
diff --git a/app/presenters/ci/pipeline_presenter.rb b/app/presenters/ci/pipeline_presenter.rb
index 00820e00863..7f5dffadcfb 100644
--- a/app/presenters/ci/pipeline_presenter.rb
+++ b/app/presenters/ci/pipeline_presenter.rb
@@ -3,7 +3,6 @@
module Ci
class PipelinePresenter < Gitlab::View::Presenter::Delegated
include Gitlab::Utils::StrongMemoize
- include ActionView::Helpers::UrlHelper
delegator_override_with Gitlab::Utils::StrongMemoize # TODO: Remove `Gitlab::Utils::StrongMemoize` inclusion as it's duplicate
delegator_override_with ActionView::Helpers::TagHelper # TODO: Remove `ActionView::Helpers::UrlHelper` inclusion as it overrides `Ci::Pipeline#tag`
@@ -108,7 +107,7 @@ module Ci
end
def link_to_pipeline_ref
- link_to(pipeline.ref,
+ ApplicationController.helpers.link_to(pipeline.ref,
project_commits_path(pipeline.project, pipeline.ref),
class: "ref-name")
end
@@ -116,7 +115,7 @@ module Ci
def link_to_merge_request
return unless merge_request_presenter
- link_to(merge_request_presenter.to_reference,
+ ApplicationController.helpers.link_to(merge_request_presenter.to_reference,
project_merge_request_path(merge_request_presenter.project, merge_request_presenter),
class: 'mr-iid')
end
@@ -143,7 +142,7 @@ module Ci
private
def plain_ref_name
- content_tag(:span, pipeline.ref, class: 'ref-name')
+ ApplicationController.helpers.content_tag(:span, pipeline.ref, class: 'ref-name')
end
def merge_request_presenter
@@ -160,7 +159,7 @@ module Ci
all_related_merge_requests.first(limit).map do |merge_request|
mr_path = project_merge_request_path(merge_request.project, merge_request)
- link_to "#{merge_request.to_reference} #{merge_request.title}", mr_path, class: 'mr-iid'
+ ApplicationController.helpers.link_to "#{merge_request.to_reference} #{merge_request.title}", mr_path, class: 'mr-iid'
end
end
diff --git a/app/presenters/prometheus_alert_presenter.rb b/app/presenters/prometheus_alert_presenter.rb
index 714329ede71..776e2baebdd 100644
--- a/app/presenters/prometheus_alert_presenter.rb
+++ b/app/presenters/prometheus_alert_presenter.rb
@@ -1,8 +1,6 @@
# frozen_string_literal: true
class PrometheusAlertPresenter < Gitlab::View::Presenter::Delegated
- include ActionView::Helpers::UrlHelper
-
presents ::PrometheusAlert, as: :prometheus_alert
def humanized_text
diff --git a/app/services/auth/container_registry_authentication_service.rb b/app/services/auth/container_registry_authentication_service.rb
index bc734465750..ea4723c9e28 100644
--- a/app/services/auth/container_registry_authentication_service.rb
+++ b/app/services/auth/container_registry_authentication_service.rb
@@ -156,7 +156,7 @@ module Auth
return if path.has_repository?
return unless actions.include?('push')
- ContainerRepository.create_from_path!(path)
+ ContainerRepository.find_or_create_from_path(path)
end
# Overridden in EE
diff --git a/app/services/ci/process_sync_events_service.rb b/app/services/ci/process_sync_events_service.rb
new file mode 100644
index 00000000000..6be8c41dc6a
--- /dev/null
+++ b/app/services/ci/process_sync_events_service.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+module Ci
+ class ProcessSyncEventsService
+ include Gitlab::Utils::StrongMemoize
+ include ExclusiveLeaseGuard
+
+ BATCH_SIZE = 1000
+
+ def initialize(sync_event_class, sync_class)
+ @sync_event_class = sync_event_class
+ @sync_class = sync_class
+ end
+
+ def execute
+ return unless ::Feature.enabled?(:ci_namespace_project_mirrors, default_enabled: :yaml)
+
+ # preventing parallel processing over the same event table
+ try_obtain_lease { process_events }
+
+ enqueue_worker_if_there_still_event
+ end
+
+ private
+
+ def process_events
+ events = @sync_event_class.preload_synced_relation.first(BATCH_SIZE)
+
+ return if events.empty?
+
+ first = events.first
+ last_processed = nil
+
+ begin
+ events.each do |event|
+ @sync_class.sync!(event)
+
+ last_processed = event
+ end
+ ensure
+ # remove events till the one that was last succesfully processed
+ @sync_event_class.id_in(first.id..last_processed.id).delete_all if last_processed
+ end
+ end
+
+ def enqueue_worker_if_there_still_event
+ @sync_event_class.enqueue_worker if @sync_event_class.exists?
+ end
+
+ def lease_key
+ "#{super}::#{@sync_event_class}"
+ end
+
+ def lease_timeout
+ 1.minute
+ end
+ end
+end
diff --git a/app/services/ci/retry_build_service.rb b/app/services/ci/retry_build_service.rb
index 3bf6644bb29..be21ed5b73d 100644
--- a/app/services/ci/retry_build_service.rb
+++ b/app/services/ci/retry_build_service.rb
@@ -7,7 +7,7 @@ module Ci
allow_failure stage stage_id stage_idx trigger_request
yaml_variables when environment coverage_regex
description tag_list protected needs_attributes
- resource_group scheduling_type].freeze
+ job_variables_attributes resource_group scheduling_type].freeze
end
def self.extra_accessors
@@ -68,13 +68,7 @@ module Ci
end
def build_attributes(build)
- clone_attributes = if ::Feature.enabled?(:clone_job_variables_at_job_retry, build.project, default_enabled: :yaml)
- self.class.clone_accessors + [:job_variables_attributes]
- else
- self.class.clone_accessors
- end
-
- attributes = clone_attributes.to_h do |attribute|
+ attributes = self.class.clone_accessors.to_h do |attribute|
[attribute, build.public_send(attribute)] # rubocop:disable GitlabSecurity/PublicSend
end
diff --git a/app/services/search_service.rb b/app/services/search_service.rb
index 9700daf1c4b..171d52c328d 100644
--- a/app/services/search_service.rb
+++ b/app/services/search_service.rb
@@ -80,7 +80,7 @@ class SearchService
def abuse_messages
return [] unless params.abusive?
- params.abuse_detection.errors.messages
+ params.abuse_detection.errors.full_messages
end
def valid_request?
diff --git a/app/views/shared/milestones/_sidebar.html.haml b/app/views/shared/milestones/_sidebar.html.haml
index c66ba5ba2e1..a1e94172ec3 100644
--- a/app/views/shared/milestones/_sidebar.html.haml
+++ b/app/views/shared/milestones/_sidebar.html.haml
@@ -79,7 +79,7 @@
%span= milestone.issues_visible_to_user(current_user).count
.title.hide-collapsed
= s_('MilestoneSidebar|Issues')
- %span.badge.badge-muted.badge-pill.gl-badge.sm= milestone.issues_visible_to_user(current_user).count
+ = gl_badge_tag milestone.issues_visible_to_user(current_user).count, variant: :muted, size: :sm
- if show_new_issue_link?(project)
= link_to new_project_issue_path(project, issue: { milestone_id: milestone.id }), class: "float-right", title: s_('MilestoneSidebar|New Issue') do
= s_('MilestoneSidebar|New issue')
@@ -111,7 +111,7 @@
%span= milestone.merge_requests.count
.title.hide-collapsed
= s_('MilestoneSidebar|Merge requests')
- %span.badge.badge-muted.badge-pill.gl-badge.sm= milestone.merge_requests.count
+ = gl_badge_tag milestone.merge_requests.count, variant: :muted, size: :sm
.value.hide-collapsed.bold
- if !project || can?(current_user, :read_merge_request, project)
%span.milestone-stat
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index 89a4d9dc7cf..e84646047e4 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -2492,6 +2492,15 @@
:weight: 1
:idempotent: true
:tags: []
+- :name: namespaces_process_sync_events
+ :worker_name: Namespaces::ProcessSyncEventsWorker
+ :feature_category: :sharding
+ :has_external_dependencies:
+ :urgency: :high
+ :resource_boundary: :unknown
+ :weight: 1
+ :idempotent: true
+ :tags: []
- :name: new_issue
:worker_name: NewIssueWorker
:feature_category: :team_planning
@@ -2663,6 +2672,15 @@
:weight: 1
:idempotent: true
:tags: []
+- :name: projects_process_sync_events
+ :worker_name: Projects::ProcessSyncEventsWorker
+ :feature_category: :sharding
+ :has_external_dependencies:
+ :urgency: :high
+ :resource_boundary: :unknown
+ :weight: 1
+ :idempotent: true
+ :tags: []
- :name: projects_schedule_bulk_repository_shard_moves
:worker_name: Projects::ScheduleBulkRepositoryShardMovesWorker
:feature_category: :gitaly
diff --git a/app/workers/namespaces/process_sync_events_worker.rb b/app/workers/namespaces/process_sync_events_worker.rb
new file mode 100644
index 00000000000..f3c4f5bebb1
--- /dev/null
+++ b/app/workers/namespaces/process_sync_events_worker.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module Namespaces
+ # This worker can be called multiple times at the same time but only one of them can
+ # process events at a time. This is ensured by `try_obtain_lease` in `Ci::ProcessSyncEventsService`.
+ # `until_executing` here is to reduce redundant worker enqueuing.
+ class ProcessSyncEventsWorker
+ include ApplicationWorker
+
+ data_consistency :always
+
+ feature_category :sharding
+ urgency :high
+
+ idempotent!
+ deduplicate :until_executing
+
+ def perform
+ ::Ci::ProcessSyncEventsService.new(::Namespaces::SyncEvent, ::Ci::NamespaceMirror).execute
+ end
+ end
+end
diff --git a/app/workers/projects/process_sync_events_worker.rb b/app/workers/projects/process_sync_events_worker.rb
new file mode 100644
index 00000000000..b7c4b4de3d0
--- /dev/null
+++ b/app/workers/projects/process_sync_events_worker.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module Projects
+ # This worker can be called multiple times at the same time but only one of them can
+ # process events at a time. This is ensured by `try_obtain_lease` in `Ci::ProcessSyncEventsService`.
+ # `until_executing` here is to reduce redundant worker enqueuing.
+ class ProcessSyncEventsWorker
+ include ApplicationWorker
+
+ data_consistency :always
+
+ feature_category :sharding
+ urgency :high
+
+ idempotent!
+ deduplicate :until_executing
+
+ def perform
+ ::Ci::ProcessSyncEventsService.new(::Projects::SyncEvent, ::Ci::ProjectMirror).execute
+ end
+ end
+end