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
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-01-18 22:00:14 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-01-18 22:00:14 +0300
commit05f0ebba3a2c8ddf39e436f412dc2ab5bf1353b2 (patch)
tree11d0f2a6ec31c7793c184106cedc2ded3d9a2cc5 /app/services
parentec73467c23693d0db63a797d10194da9e72a74af (diff)
Add latest changes from gitlab-org/gitlab@15-8-stable-eev15.8.0-rc42
Diffstat (limited to 'app/services')
-rw-r--r--app/services/achievements/base_service.rb20
-rw-r--r--app/services/achievements/create_service.rb25
-rw-r--r--app/services/boards/base_items_list_service.rb6
-rw-r--r--app/services/bulk_imports/create_service.rb13
-rw-r--r--app/services/captcha/captcha_verification_service.rb4
-rw-r--r--app/services/chat_names/authorize_user_service.rb4
-rw-r--r--app/services/ci/create_pipeline_service.rb7
-rw-r--r--app/services/ci/job_artifacts/create_service.rb10
-rw-r--r--app/services/ci/job_artifacts/delete_service.rb3
-rw-r--r--app/services/ci/job_artifacts/destroy_associations_service.rb17
-rw-r--r--app/services/ci/job_artifacts/destroy_batch_service.rb22
-rw-r--r--app/services/clusters/aws/authorize_role_service.rb74
-rw-r--r--app/services/clusters/aws/fetch_credentials_service.rb80
-rw-r--r--app/services/clusters/aws/finalize_creation_service.rb139
-rw-r--r--app/services/clusters/aws/provision_service.rb85
-rw-r--r--app/services/clusters/aws/verify_provision_status_service.rb50
-rw-r--r--app/services/clusters/create_service.rb4
-rw-r--r--app/services/clusters/gcp/fetch_operation_service.rb31
-rw-r--r--app/services/clusters/gcp/finalize_creation_service.rb127
-rw-r--r--app/services/clusters/gcp/provision_service.rb56
-rw-r--r--app/services/clusters/gcp/verify_provision_status_service.rb50
-rw-r--r--app/services/concerns/integrations/project_test_data.rb24
-rw-r--r--app/services/design_management/save_designs_service.rb2
-rw-r--r--app/services/discussions/resolve_service.rb15
-rw-r--r--app/services/discussions/unresolve_service.rb17
-rw-r--r--app/services/draft_notes/publish_service.rb13
-rw-r--r--app/services/environments/stop_stale_service.rb24
-rw-r--r--app/services/files/base_service.rb15
-rw-r--r--app/services/git/branch_hooks_service.rb9
-rw-r--r--app/services/groups/import_export/export_service.rb14
-rw-r--r--app/services/groups/import_export/import_service.rb34
-rw-r--r--app/services/groups/transfer_service.rb5
-rw-r--r--app/services/ide/schemas_config_service.rb4
-rw-r--r--app/services/import/github/gists_import_service.rb10
-rw-r--r--app/services/import/github_service.rb14
-rw-r--r--app/services/integrations/test/base_service.rb4
-rw-r--r--app/services/issuable/discussions_list_service.rb3
-rw-r--r--app/services/issuable_links/create_service.rb2
-rw-r--r--app/services/issues/base_service.rb12
-rw-r--r--app/services/issues/create_service.rb13
-rw-r--r--app/services/lfs/file_transformer.rb16
-rw-r--r--app/services/members/approve_access_request_service.rb2
-rw-r--r--app/services/members/creator_service.rb2
-rw-r--r--app/services/members/destroy_service.rb44
-rw-r--r--app/services/members/update_service.rb59
-rw-r--r--app/services/merge_requests/base_service.rb12
-rw-r--r--app/services/merge_requests/rebase_service.rb21
-rw-r--r--app/services/merge_requests/refresh_service.rb4
-rw-r--r--app/services/merge_requests/update_service.rb2
-rw-r--r--app/services/ml/experiment_tracking/candidate_repository.rb10
-rw-r--r--app/services/notes/build_service.rb2
-rw-r--r--app/services/notes/create_service.rb22
-rw-r--r--app/services/notification_service.rb4
-rw-r--r--app/services/pages_domains/create_service.rb1
-rw-r--r--app/services/pages_domains/delete_service.rb1
-rw-r--r--app/services/pages_domains/retry_acme_order_service.rb1
-rw-r--r--app/services/pages_domains/update_service.rb1
-rw-r--r--app/services/personal_access_tokens/revoke_service.rb19
-rw-r--r--app/services/projects/autocomplete_service.rb2
-rw-r--r--app/services/projects/create_service.rb6
-rw-r--r--app/services/projects/import_service.rb2
-rw-r--r--app/services/projects/refresh_build_artifacts_size_statistics_service.rb19
-rw-r--r--app/services/repositories/housekeeping_service.rb20
-rw-r--r--app/services/search_service.rb19
-rw-r--r--app/services/security/ci_configuration/base_create_service.rb23
-rw-r--r--app/services/security/ci_configuration/container_scanning_create_service.rb4
-rw-r--r--app/services/security/ci_configuration/dependency_scanning_create_service.rb4
-rw-r--r--app/services/security/ci_configuration/sast_create_service.rb12
-rw-r--r--app/services/security/ci_configuration/sast_iac_create_service.rb4
-rw-r--r--app/services/security/ci_configuration/secret_detection_create_service.rb4
-rw-r--r--app/services/service_ping/submit_service.rb8
-rw-r--r--app/services/service_response.rb29
-rw-r--r--app/services/test_hooks/base_service.rb7
-rw-r--r--app/services/todo_service.rb9
-rw-r--r--app/services/users/block_service.rb8
-rw-r--r--app/services/users/signup_service.rb4
-rw-r--r--app/services/users/unblock_service.rb29
-rw-r--r--app/services/work_items/parent_links/create_service.rb2
78 files changed, 565 insertions, 934 deletions
diff --git a/app/services/achievements/base_service.rb b/app/services/achievements/base_service.rb
new file mode 100644
index 00000000000..0a8e6ee3c78
--- /dev/null
+++ b/app/services/achievements/base_service.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+module Achievements
+ class BaseService < ::BaseContainerService
+ def initialize(namespace:, current_user: nil, params: {})
+ @namespace = namespace
+ super(container: namespace, current_user: current_user, params: params)
+ end
+
+ private
+
+ def allowed?
+ current_user&.can?(:admin_achievement, @namespace)
+ end
+
+ def error(message)
+ ServiceResponse.error(message: Array(message))
+ end
+ end
+end
diff --git a/app/services/achievements/create_service.rb b/app/services/achievements/create_service.rb
new file mode 100644
index 00000000000..2843df6c191
--- /dev/null
+++ b/app/services/achievements/create_service.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Achievements
+ class CreateService < BaseService
+ def execute
+ return error_no_permissions unless allowed?
+
+ achievement = Achievements::Achievement.create(params.merge(namespace_id: @namespace.id))
+
+ return error_creating(achievement) unless achievement.persisted?
+
+ ServiceResponse.success(payload: achievement)
+ end
+
+ private
+
+ def error_no_permissions
+ error('You have insufficient permissions to create achievements for this namespace')
+ end
+
+ def error_creating(achievement)
+ error(achievement&.errors&.full_messages || 'Failed to create achievement')
+ end
+ end
+end
diff --git a/app/services/boards/base_items_list_service.rb b/app/services/boards/base_items_list_service.rb
index 2a9cbb83cc4..bf68aee2c1f 100644
--- a/app/services/boards/base_items_list_service.rb
+++ b/app/services/boards/base_items_list_service.rb
@@ -18,7 +18,6 @@ module Boards
# TODO: eliminate need for SQL literal fragment
columns = Arel.sql(fields.values_at(*keys).join(', '))
results = item_model.where(id: collection_ids)
- results = query_additions(results, required_fields)
results = results.select(columns)
Hash[keys.zip(results.pluck(columns).flatten)]
@@ -27,11 +26,6 @@ module Boards
private
- # override if needed
- def query_additions(items, required_fields)
- items
- end
-
def collection_ids
@collection_ids ||= init_collection.select(item_model.arel_table[:id])
end
diff --git a/app/services/bulk_imports/create_service.rb b/app/services/bulk_imports/create_service.rb
index 124b5964232..35a35e7b7c9 100644
--- a/app/services/bulk_imports/create_service.rb
+++ b/app/services/bulk_imports/create_service.rb
@@ -36,6 +36,8 @@ module BulkImports
end
def execute
+ validate!
+
bulk_import = create_bulk_import
Gitlab::Tracking.event(self.class.name, 'create', label: 'bulk_import_group')
@@ -43,7 +45,8 @@ module BulkImports
BulkImportWorker.perform_async(bulk_import.id)
ServiceResponse.success(payload: bulk_import)
- rescue ActiveRecord::RecordInvalid => e
+
+ rescue ActiveRecord::RecordInvalid, BulkImports::Error, BulkImports::NetworkError => e
ServiceResponse.error(
message: e.message,
http_status: :unprocessable_entity
@@ -52,6 +55,11 @@ module BulkImports
private
+ def validate!
+ client.validate_instance_version!
+ client.validate_import_scopes!
+ end
+
def create_bulk_import
BulkImport.transaction do
bulk_import = BulkImport.create!(
@@ -70,7 +78,8 @@ module BulkImports
source_type: entity[:source_type],
source_full_path: entity[:source_full_path],
destination_slug: entity[:destination_slug],
- destination_namespace: entity[:destination_namespace]
+ destination_namespace: entity[:destination_namespace],
+ migrate_projects: Gitlab::Utils.to_boolean(entity[:migrate_projects], default: true)
)
end
diff --git a/app/services/captcha/captcha_verification_service.rb b/app/services/captcha/captcha_verification_service.rb
index 3ed8ea12f3a..b7b699f7108 100644
--- a/app/services/captcha/captcha_verification_service.rb
+++ b/app/services/captcha/captcha_verification_service.rb
@@ -5,7 +5,7 @@ module Captcha
# Encapsulates logic of checking captchas.
#
class CaptchaVerificationService
- include Recaptcha::Verify
+ include Recaptcha::Adapters::ControllerMethods
# Currently the only value that is used out of the request by the reCAPTCHA library
# is 'remote_ip'. Therefore, we just create a struct to avoid passing the full request
@@ -45,7 +45,7 @@ module Captcha
attr_reader :spam_params
- # The recaptcha library's Recaptcha::Verify#verify_recaptcha method requires that
+ # The recaptcha library's Recaptcha::Adapters::ControllerMethods#verify_recaptcha method requires that
# 'request' be a readable attribute - it doesn't support passing it as an options argument.
attr_reader :request
end
diff --git a/app/services/chat_names/authorize_user_service.rb b/app/services/chat_names/authorize_user_service.rb
index 6c28a1cea7e..32714831fb8 100644
--- a/app/services/chat_names/authorize_user_service.rb
+++ b/app/services/chat_names/authorize_user_service.rb
@@ -4,8 +4,7 @@ module ChatNames
class AuthorizeUserService
include Gitlab::Routing
- def initialize(integration, params)
- @integration = integration
+ def initialize(params)
@params = params
end
@@ -29,7 +28,6 @@ module ChatNames
def chat_name_params
{
- integration_id: @integration.id,
team_id: @params[:team_id],
team_domain: @params[:team_domain],
chat_id: @params[:user_id],
diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb
index 9c3cc803587..eb25aeaf5a5 100644
--- a/app/services/ci/create_pipeline_service.rb
+++ b/app/services/ci/create_pipeline_service.rb
@@ -157,6 +157,13 @@ module Ci
duration >= LOG_MAX_CREATION_THRESHOLD
end
+
+ l.log_when do |observations|
+ pipeline_includes_count = observations['pipeline_includes_count']
+ next false unless pipeline_includes_count
+
+ pipeline_includes_count.to_i > Gitlab::Ci::Config::External::Context::MAX_INCLUDES
+ end
end
end
end
diff --git a/app/services/ci/job_artifacts/create_service.rb b/app/services/ci/job_artifacts/create_service.rb
index ee9982cf3ab..6e2ba76682f 100644
--- a/app/services/ci/job_artifacts/create_service.rb
+++ b/app/services/ci/job_artifacts/create_service.rb
@@ -92,7 +92,8 @@ module Ci
file: artifacts_file,
file_type: params[:artifact_type],
file_format: params[:artifact_format],
- file_sha256: artifacts_file.sha256
+ file_sha256: artifacts_file.sha256,
+ accessibility: accessibility(params)
)
)
@@ -102,7 +103,8 @@ module Ci
file: metadata_file,
file_type: :metadata,
file_format: :gzip,
- file_sha256: metadata_file.sha256
+ file_sha256: metadata_file.sha256,
+ accessibility: accessibility(params)
)
)
end
@@ -110,6 +112,10 @@ module Ci
[artifact, artifact_metadata]
end
+ def accessibility(params)
+ params[:accessibility] || 'public'
+ end
+
def parse_artifact(artifact)
case artifact.file_type
when 'dotenv' then parse_dotenv_artifact(artifact)
diff --git a/app/services/ci/job_artifacts/delete_service.rb b/app/services/ci/job_artifacts/delete_service.rb
index c9d590eccc4..fc5c6b12389 100644
--- a/app/services/ci/job_artifacts/delete_service.rb
+++ b/app/services/ci/job_artifacts/delete_service.rb
@@ -26,8 +26,7 @@ module Ci
if result.fetch(:status) == :success
ServiceResponse.success(payload:
{
- destroyed_artifacts_count: result.fetch(:destroyed_artifacts_count),
- statistics_updates: result.fetch(:statistics_updates)
+ destroyed_artifacts_count: result.fetch(:destroyed_artifacts_count)
})
else
ServiceResponse.error(message: result.fetch(:message))
diff --git a/app/services/ci/job_artifacts/destroy_associations_service.rb b/app/services/ci/job_artifacts/destroy_associations_service.rb
index 794d24eadf2..fd3e69a5913 100644
--- a/app/services/ci/job_artifacts/destroy_associations_service.rb
+++ b/app/services/ci/job_artifacts/destroy_associations_service.rb
@@ -2,27 +2,32 @@
module Ci
module JobArtifacts
+ # This class is used by Ci::JobArtifact's FastDestroyAll implementation.
+ # Ci::JobArtifact.begin_fast_destroy instantiates this service and calls #destroy_records.
+ # This will set @statistics_updates instance variables.
+ # The same instance is passed to Ci::JobArtifact.finalize_fast_destroy, which then calls
+ # #update_statistics, using @statistics_updates set by #destroy_records.
class DestroyAssociationsService
BATCH_SIZE = 100
def initialize(job_artifacts_relation)
@job_artifacts_relation = job_artifacts_relation
- @statistics = {}
+ @statistics_updates = {}
end
def destroy_records
@job_artifacts_relation.each_batch(of: BATCH_SIZE) do |relation|
service = Ci::JobArtifacts::DestroyBatchService.new(relation, pick_up_at: Time.current)
result = service.execute(update_stats: false)
- updates = result[:statistics_updates]
-
- @statistics.merge!(updates) { |_key, oldval, newval| newval + oldval }
+ @statistics_updates.merge!(result[:statistics_updates]) do |_project, existing_updates, new_updates|
+ existing_updates.concat(new_updates)
+ end
end
end
def update_statistics
- @statistics.each do |project, delta|
- project.increment_statistic_value(Ci::JobArtifact.project_statistics_name, delta)
+ @statistics_updates.each do |project, increments|
+ ProjectStatistics.bulk_increment_statistic(project, Ci::JobArtifact.project_statistics_name, increments)
end
end
end
diff --git a/app/services/ci/job_artifacts/destroy_batch_service.rb b/app/services/ci/job_artifacts/destroy_batch_service.rb
index e0307d9bd53..7cb1be95a3e 100644
--- a/app/services/ci/job_artifacts/destroy_batch_service.rb
+++ b/app/services/ci/job_artifacts/destroy_batch_service.rb
@@ -46,14 +46,13 @@ module Ci
after_batch_destroy_hook(@job_artifacts)
- # This is executed outside of the transaction because it depends on Redis
update_project_statistics! if update_stats
+
increment_monitoring_statistics(artifacts_count, artifacts_bytes)
Gitlab::Ci::Artifacts::Logger.log_deleted(@job_artifacts, 'Ci::JobArtifacts::DestroyBatchService#execute')
- success(destroyed_artifacts_count: artifacts_count,
- statistics_updates: affected_project_statistics)
+ success(destroyed_artifacts_count: artifacts_count, statistics_updates: statistics_updates_per_project)
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -74,17 +73,18 @@ module Ci
# using ! here since this can't be called inside a transaction
def update_project_statistics!
- affected_project_statistics.each do |project, delta|
- project.increment_statistic_value(Ci::JobArtifact.project_statistics_name, delta)
+ statistics_updates_per_project.each do |project, increments|
+ ProjectStatistics.bulk_increment_statistic(project, Ci::JobArtifact.project_statistics_name, increments)
end
end
- def affected_project_statistics
- strong_memoize(:affected_project_statistics) do
- artifacts_by_project = @job_artifacts.group_by(&:project)
- artifacts_by_project.each.with_object({}) do |(project, artifacts), accumulator|
- delta = -artifacts.sum { |artifact| artifact.size.to_i }
- accumulator[project] = delta
+ def statistics_updates_per_project
+ strong_memoize(:statistics_updates_per_project) do
+ result = Hash.new { |updates, project| updates[project] = [] }
+
+ @job_artifacts.each_with_object(result) do |job_artifact, result|
+ increment = Gitlab::Counters::Increment.new(amount: -job_artifact.size.to_i, ref: job_artifact.id)
+ result[job_artifact.project] << increment
end
end
end
diff --git a/app/services/clusters/aws/authorize_role_service.rb b/app/services/clusters/aws/authorize_role_service.rb
deleted file mode 100644
index 7ca20289bf7..00000000000
--- a/app/services/clusters/aws/authorize_role_service.rb
+++ /dev/null
@@ -1,74 +0,0 @@
-# frozen_string_literal: true
-
-module Clusters
- module Aws
- class AuthorizeRoleService
- attr_reader :user
-
- Response = Struct.new(:status, :body)
-
- ERRORS = [
- ActiveRecord::RecordInvalid,
- ActiveRecord::RecordNotFound,
- Clusters::Aws::FetchCredentialsService::MissingRoleError,
- ::Aws::Errors::MissingCredentialsError,
- ::Aws::STS::Errors::ServiceError
- ].freeze
-
- def initialize(user, params:)
- @user = user
- @role_arn = params[:role_arn]
- @region = params[:region]
- end
-
- def execute
- ensure_role_exists!
- update_role_arn!
-
- Response.new(:ok, credentials)
- rescue *ERRORS => e
- Gitlab::ErrorTracking.track_exception(e)
-
- Response.new(:unprocessable_entity, response_details(e))
- end
-
- private
-
- attr_reader :role, :role_arn, :region
-
- def ensure_role_exists!
- @role = ::Aws::Role.find_by_user_id!(user.id)
- end
-
- def update_role_arn!
- role.update!(role_arn: role_arn, region: region)
- end
-
- def credentials
- Clusters::Aws::FetchCredentialsService.new(role).execute
- end
-
- def response_details(exception)
- message =
- case exception
- when ::Aws::STS::Errors::AccessDenied
- _("Access denied: %{error}") % { error: exception.message }
- when ::Aws::STS::Errors::ServiceError
- _("AWS service error: %{error}") % { error: exception.message }
- when ActiveRecord::RecordNotFound
- _("Error: Unable to find AWS role for current user")
- when ActiveRecord::RecordInvalid
- exception.message
- when Clusters::Aws::FetchCredentialsService::MissingRoleError
- _("Error: No AWS provision role found for user")
- when ::Aws::Errors::MissingCredentialsError
- _("Error: No AWS credentials were supplied")
- else
- _('An error occurred while authorizing your role')
- end
-
- { message: message }.compact
- end
- end
- end
-end
diff --git a/app/services/clusters/aws/fetch_credentials_service.rb b/app/services/clusters/aws/fetch_credentials_service.rb
deleted file mode 100644
index e38852c7ec7..00000000000
--- a/app/services/clusters/aws/fetch_credentials_service.rb
+++ /dev/null
@@ -1,80 +0,0 @@
-# frozen_string_literal: true
-
-module Clusters
- module Aws
- class FetchCredentialsService
- attr_reader :provision_role
-
- MissingRoleError = Class.new(StandardError)
-
- def initialize(provision_role, provider: nil)
- @provision_role = provision_role
- @provider = provider
- @region = provider&.region || provision_role&.region || Clusters::Providers::Aws::DEFAULT_REGION
- end
-
- def execute
- raise MissingRoleError, 'AWS provisioning role not configured' unless provision_role.present?
-
- ::Aws::AssumeRoleCredentials.new(
- client: client,
- role_arn: provision_role.role_arn,
- role_session_name: session_name,
- external_id: provision_role.role_external_id,
- policy: session_policy
- ).credentials
- end
-
- private
-
- attr_reader :provider, :region
-
- def client
- ::Aws::STS::Client.new(**client_args)
- end
-
- def client_args
- { region: region, credentials: gitlab_credentials }.compact
- end
-
- def gitlab_credentials
- # These are not needed for IAM instance profiles
- return unless access_key_id.present? && secret_access_key.present?
-
- ::Aws::Credentials.new(access_key_id, secret_access_key)
- end
-
- def access_key_id
- Gitlab::CurrentSettings.eks_access_key_id
- end
-
- def secret_access_key
- Gitlab::CurrentSettings.eks_secret_access_key
- end
-
- ##
- # If we haven't created a provider record yet,
- # we restrict ourselves to read-only access so
- # that we can safely expose credentials to the
- # frontend (to be used when populating the
- # creation form).
- def session_policy
- if provider.nil?
- File.read(read_only_policy)
- end
- end
-
- def read_only_policy
- Rails.root.join('vendor', 'aws', 'iam', "eks_cluster_read_only_policy.json")
- end
-
- def session_name
- if provider.present?
- "gitlab-eks-cluster-#{provider.cluster_id}-user-#{provision_role.user_id}"
- else
- "gitlab-eks-autofill-user-#{provision_role.user_id}"
- end
- end
- end
- end
-end
diff --git a/app/services/clusters/aws/finalize_creation_service.rb b/app/services/clusters/aws/finalize_creation_service.rb
deleted file mode 100644
index 54f07e1d44c..00000000000
--- a/app/services/clusters/aws/finalize_creation_service.rb
+++ /dev/null
@@ -1,139 +0,0 @@
-# frozen_string_literal: true
-
-module Clusters
- module Aws
- class FinalizeCreationService
- include Gitlab::Utils::StrongMemoize
-
- attr_reader :provider
-
- delegate :cluster, to: :provider
-
- def execute(provider)
- @provider = provider
-
- configure_provider
- create_gitlab_service_account!
- configure_platform_kubernetes
- configure_node_authentication!
-
- cluster.save!
- rescue ::Aws::CloudFormation::Errors::ServiceError => e
- log_service_error(e.class.name, provider.id, e.message)
- provider.make_errored!(s_('ClusterIntegration|Failed to fetch CloudFormation stack: %{message}') % { message: e.message })
- rescue Kubeclient::HttpError => e
- log_service_error(e.class.name, provider.id, e.message)
- provider.make_errored!(s_('ClusterIntegration|Failed to run Kubeclient: %{message}') % { message: e.message })
- rescue ActiveRecord::RecordInvalid => e
- log_service_error(e.class.name, provider.id, e.message)
- provider.make_errored!(s_('ClusterIntegration|Failed to configure EKS provider: %{message}') % { message: e.message })
- end
-
- private
-
- def create_gitlab_service_account!
- Clusters::Kubernetes::CreateOrUpdateServiceAccountService.gitlab_creator(
- kube_client,
- rbac: true
- ).execute
- end
-
- def configure_provider
- provider.status_event = :make_created
- end
-
- def configure_platform_kubernetes
- cluster.build_platform_kubernetes(
- api_url: cluster_endpoint,
- ca_cert: cluster_certificate,
- token: request_kubernetes_token)
- end
-
- def request_kubernetes_token
- Clusters::Kubernetes::FetchKubernetesTokenService.new(
- kube_client,
- Clusters::Kubernetes::GITLAB_ADMIN_TOKEN_NAME,
- Clusters::Kubernetes::GITLAB_SERVICE_ACCOUNT_NAMESPACE
- ).execute
- end
-
- def kube_client
- @kube_client ||= build_kube_client!(
- cluster_endpoint,
- cluster_certificate
- )
- end
-
- def build_kube_client!(api_url, ca_pem)
- raise "Incomplete settings" unless api_url
-
- Gitlab::Kubernetes::KubeClient.new(
- api_url,
- auth_options: kubeclient_auth_options,
- ssl_options: kubeclient_ssl_options(ca_pem),
- http_proxy_uri: ENV['http_proxy']
- )
- end
-
- def kubeclient_auth_options
- { bearer_token: Kubeclient::AmazonEksCredentials.token(provider.credentials, cluster.name) }
- end
-
- def kubeclient_ssl_options(ca_pem)
- opts = { verify_ssl: OpenSSL::SSL::VERIFY_PEER }
-
- if ca_pem.present?
- opts[:cert_store] = OpenSSL::X509::Store.new
- opts[:cert_store].add_cert(OpenSSL::X509::Certificate.new(ca_pem))
- end
-
- opts
- end
-
- def cluster_stack
- @cluster_stack ||= provider.api_client.describe_stacks(stack_name: provider.cluster.name).stacks.first
- end
-
- def stack_output_value(key)
- cluster_stack.outputs.detect { |output| output.output_key == key }.output_value
- end
-
- def node_instance_role_arn
- stack_output_value('NodeInstanceRole')
- end
-
- def cluster_endpoint
- strong_memoize(:cluster_endpoint) do
- stack_output_value('ClusterEndpoint')
- end
- end
-
- def cluster_certificate
- strong_memoize(:cluster_certificate) do
- Base64.decode64(stack_output_value('ClusterCertificate'))
- end
- end
-
- def configure_node_authentication!
- kube_client.create_config_map(node_authentication_config)
- end
-
- def node_authentication_config
- Gitlab::Kubernetes::ConfigMaps::AwsNodeAuth.new(node_instance_role_arn).generate
- end
-
- def logger
- @logger ||= Gitlab::Kubernetes::Logger.build
- end
-
- def log_service_error(exception, provider_id, message)
- logger.error(
- exception: exception.class.name,
- service: self.class.name,
- provider_id: provider_id,
- message: message
- )
- end
- end
- end
-end
diff --git a/app/services/clusters/aws/provision_service.rb b/app/services/clusters/aws/provision_service.rb
deleted file mode 100644
index b454a7a5f59..00000000000
--- a/app/services/clusters/aws/provision_service.rb
+++ /dev/null
@@ -1,85 +0,0 @@
-# frozen_string_literal: true
-
-module Clusters
- module Aws
- class ProvisionService
- attr_reader :provider
-
- def execute(provider)
- @provider = provider
-
- configure_provider_credentials
- provision_cluster
-
- if provider.make_creating
- WaitForClusterCreationWorker.perform_in(
- Clusters::Aws::VerifyProvisionStatusService::INITIAL_INTERVAL,
- provider.cluster_id
- )
- else
- provider.make_errored!("Failed to update provider record; #{provider.errors.full_messages}")
- end
- rescue Clusters::Aws::FetchCredentialsService::MissingRoleError
- provider.make_errored!('Amazon role is not configured')
- rescue ::Aws::Errors::MissingCredentialsError
- provider.make_errored!('Amazon credentials are not configured')
- rescue ::Aws::STS::Errors::ServiceError => e
- provider.make_errored!("Amazon authentication failed; #{e.message}")
- rescue ::Aws::CloudFormation::Errors::ServiceError => e
- provider.make_errored!("Amazon CloudFormation request failed; #{e.message}")
- end
-
- private
-
- def provision_role
- provider.created_by_user&.aws_role
- end
-
- def credentials
- @credentials ||= Clusters::Aws::FetchCredentialsService.new(
- provision_role,
- provider: provider
- ).execute
- end
-
- def configure_provider_credentials
- provider.update!(
- access_key_id: credentials.access_key_id,
- secret_access_key: credentials.secret_access_key,
- session_token: credentials.session_token
- )
- end
-
- def provision_cluster
- provider.api_client.create_stack(
- stack_name: provider.cluster.name,
- template_body: stack_template,
- parameters: parameters,
- capabilities: ["CAPABILITY_IAM"]
- )
- end
-
- def parameters
- [
- parameter('ClusterName', provider.cluster.name),
- parameter('ClusterRole', provider.role_arn),
- parameter('KubernetesVersion', provider.kubernetes_version),
- parameter('ClusterControlPlaneSecurityGroup', provider.security_group_id),
- parameter('VpcId', provider.vpc_id),
- parameter('Subnets', provider.subnet_ids.join(',')),
- parameter('NodeAutoScalingGroupDesiredCapacity', provider.num_nodes.to_s),
- parameter('NodeInstanceType', provider.instance_type),
- parameter('KeyName', provider.key_name)
- ]
- end
-
- def parameter(key, value)
- { parameter_key: key, parameter_value: value }
- end
-
- def stack_template
- File.read(Rails.root.join('vendor', 'aws', 'cloudformation', 'eks_cluster.yaml'))
- end
- end
- end
-end
diff --git a/app/services/clusters/aws/verify_provision_status_service.rb b/app/services/clusters/aws/verify_provision_status_service.rb
deleted file mode 100644
index 99532662bc4..00000000000
--- a/app/services/clusters/aws/verify_provision_status_service.rb
+++ /dev/null
@@ -1,50 +0,0 @@
-# frozen_string_literal: true
-
-module Clusters
- module Aws
- class VerifyProvisionStatusService
- attr_reader :provider
-
- INITIAL_INTERVAL = 5.minutes
- POLL_INTERVAL = 1.minute
- TIMEOUT = 30.minutes
-
- def execute(provider)
- @provider = provider
-
- case cluster_stack.stack_status
- when 'CREATE_IN_PROGRESS'
- continue_creation
- when 'CREATE_COMPLETE'
- finalize_creation
- else
- provider.make_errored!("Unexpected status; #{cluster_stack.stack_status}")
- end
- rescue ::Aws::CloudFormation::Errors::ServiceError => e
- provider.make_errored!("Amazon CloudFormation request failed; #{e.message}")
- end
-
- private
-
- def cluster_stack
- @cluster_stack ||= provider.api_client.describe_stacks(stack_name: provider.cluster.name).stacks.first
- end
-
- def continue_creation
- if timeout_threshold.future?
- WaitForClusterCreationWorker.perform_in(POLL_INTERVAL, provider.cluster_id)
- else
- provider.make_errored!(_('Kubernetes cluster creation time exceeds timeout; %{timeout}') % { timeout: TIMEOUT })
- end
- end
-
- def timeout_threshold
- cluster_stack.creation_time + TIMEOUT
- end
-
- def finalize_creation
- Clusters::Aws::FinalizeCreationService.new.execute(provider)
- end
- end
- end
-end
diff --git a/app/services/clusters/create_service.rb b/app/services/clusters/create_service.rb
index cb2de8b943c..4c7384806ad 100644
--- a/app/services/clusters/create_service.rb
+++ b/app/services/clusters/create_service.rb
@@ -24,9 +24,7 @@ module Clusters
return cluster if cluster.errors.present?
- cluster.tap do |cluster|
- cluster.save && ClusterProvisionWorker.perform_async(cluster.id)
- end
+ cluster.tap(&:save)
end
private
diff --git a/app/services/clusters/gcp/fetch_operation_service.rb b/app/services/clusters/gcp/fetch_operation_service.rb
deleted file mode 100644
index 6c648b443a0..00000000000
--- a/app/services/clusters/gcp/fetch_operation_service.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-# frozen_string_literal: true
-
-module Clusters
- module Gcp
- class FetchOperationService
- def execute(provider)
- operation = provider.api_client.projects_zones_operations(
- provider.gcp_project_id,
- provider.zone,
- provider.operation_id)
-
- yield(operation) if block_given?
- rescue Google::Apis::ServerError, Google::Apis::ClientError, Google::Apis::AuthorizationError => e
- logger.error(
- exception: e.class.name,
- service: self.class.name,
- provider_id: provider.id,
- message: e.message
- )
-
- provider.make_errored!("Failed to request to CloudPlatform; #{e.message}")
- end
-
- private
-
- def logger
- @logger ||= Gitlab::Kubernetes::Logger.build
- end
- end
- end
-end
diff --git a/app/services/clusters/gcp/finalize_creation_service.rb b/app/services/clusters/gcp/finalize_creation_service.rb
deleted file mode 100644
index 73d6fc4dc8f..00000000000
--- a/app/services/clusters/gcp/finalize_creation_service.rb
+++ /dev/null
@@ -1,127 +0,0 @@
-# frozen_string_literal: true
-
-module Clusters
- module Gcp
- class FinalizeCreationService
- attr_reader :provider
-
- def execute(provider)
- @provider = provider
-
- configure_provider
- create_gitlab_service_account!
- configure_kubernetes
- configure_pre_installed_knative if provider.knative_pre_installed?
- cluster.save!
- rescue Google::Apis::ServerError, Google::Apis::ClientError, Google::Apis::AuthorizationError => e
- log_service_error(e.class.name, provider.id, e.message)
- provider.make_errored!(s_('ClusterIntegration|Failed to request to Google Cloud Platform: %{message}') % { message: e.message })
- rescue Kubeclient::HttpError => e
- log_service_error(e.class.name, provider.id, e.message)
- provider.make_errored!(s_('ClusterIntegration|Failed to run Kubeclient: %{message}') % { message: e.message })
- rescue ActiveRecord::RecordInvalid => e
- log_service_error(e.class.name, provider.id, e.message)
- provider.make_errored!(s_('ClusterIntegration|Failed to configure Google Kubernetes Engine Cluster: %{message}') % { message: e.message })
- end
-
- private
-
- def create_gitlab_service_account!
- Clusters::Kubernetes::CreateOrUpdateServiceAccountService.gitlab_creator(
- kube_client,
- rbac: create_rbac_cluster?
- ).execute
- end
-
- def configure_provider
- provider.endpoint = gke_cluster.endpoint
- provider.status_event = :make_created
- end
-
- def configure_kubernetes
- cluster.platform_type = :kubernetes
- cluster.build_platform_kubernetes(
- api_url: 'https://' + gke_cluster.endpoint,
- ca_cert: Base64.decode64(gke_cluster.master_auth.cluster_ca_certificate),
- authorization_type: authorization_type,
- token: request_kubernetes_token)
- end
-
- def configure_pre_installed_knative
- knative = cluster.build_application_knative(
- hostname: 'example.com'
- )
- knative.make_pre_installed!
- end
-
- def request_kubernetes_token
- Clusters::Kubernetes::FetchKubernetesTokenService.new(
- kube_client,
- Clusters::Kubernetes::GITLAB_ADMIN_TOKEN_NAME,
- Clusters::Kubernetes::GITLAB_SERVICE_ACCOUNT_NAMESPACE
- ).execute
- end
-
- def authorization_type
- create_rbac_cluster? ? 'rbac' : 'abac'
- end
-
- def create_rbac_cluster?
- !provider.legacy_abac?
- end
-
- def kube_client
- @kube_client ||= build_kube_client!(
- 'https://' + gke_cluster.endpoint,
- Base64.decode64(gke_cluster.master_auth.cluster_ca_certificate)
- )
- end
-
- def build_kube_client!(api_url, ca_pem)
- raise "Incomplete settings" unless api_url
-
- Gitlab::Kubernetes::KubeClient.new(
- api_url,
- auth_options: { bearer_token: provider.access_token },
- ssl_options: kubeclient_ssl_options(ca_pem),
- http_proxy_uri: ENV['http_proxy']
- )
- end
-
- def kubeclient_ssl_options(ca_pem)
- opts = { verify_ssl: OpenSSL::SSL::VERIFY_PEER }
-
- if ca_pem.present?
- opts[:cert_store] = OpenSSL::X509::Store.new
- opts[:cert_store].add_cert(OpenSSL::X509::Certificate.new(ca_pem))
- end
-
- opts
- end
-
- def gke_cluster
- @gke_cluster ||= provider.api_client.projects_zones_clusters_get(
- provider.gcp_project_id,
- provider.zone,
- cluster.name)
- end
-
- def cluster
- @cluster ||= provider.cluster
- end
-
- def logger
- @logger ||= Gitlab::Kubernetes::Logger.build
- end
-
- def log_service_error(exception, provider_id, message)
- logger.error(
- exception: exception.class.name,
- service: self.class.name,
- provider_id: provider_id,
- message: message
- )
- end
- end
- end
-end
diff --git a/app/services/clusters/gcp/provision_service.rb b/app/services/clusters/gcp/provision_service.rb
deleted file mode 100644
index 7dc2d3c32f1..00000000000
--- a/app/services/clusters/gcp/provision_service.rb
+++ /dev/null
@@ -1,56 +0,0 @@
-# frozen_string_literal: true
-
-module Clusters
- module Gcp
- class ProvisionService
- CLOUD_RUN_ADDONS = %i[http_load_balancing istio_config cloud_run_config].freeze
-
- attr_reader :provider
-
- def execute(provider)
- @provider = provider
-
- get_operation_id do |operation_id|
- if provider.make_creating(operation_id)
- WaitForClusterCreationWorker.perform_in(
- Clusters::Gcp::VerifyProvisionStatusService::INITIAL_INTERVAL,
- provider.cluster_id)
- else
- provider.make_errored!("Failed to update provider record; #{provider.errors}")
- end
- end
- end
-
- private
-
- def get_operation_id
- enable_addons = provider.cloud_run? ? CLOUD_RUN_ADDONS : []
-
- operation = provider.api_client.projects_zones_clusters_create(
- provider.gcp_project_id,
- provider.zone,
- provider.cluster.name,
- provider.num_nodes,
- machine_type: provider.machine_type,
- legacy_abac: provider.legacy_abac,
- enable_addons: enable_addons
- )
-
- unless operation.status == 'PENDING' || operation.status == 'RUNNING'
- return provider.make_errored!("Operation status is unexpected; #{operation.status_message}")
- end
-
- operation_id = provider.api_client.parse_operation_id(operation.self_link)
-
- unless operation_id
- return provider.make_errored!('Can not find operation_id from self_link')
- end
-
- yield(operation_id)
-
- rescue Google::Apis::ServerError, Google::Apis::ClientError, Google::Apis::AuthorizationError => e
- provider.make_errored!("Failed to request to CloudPlatform; #{e.message}")
- end
- end
- end
-end
diff --git a/app/services/clusters/gcp/verify_provision_status_service.rb b/app/services/clusters/gcp/verify_provision_status_service.rb
deleted file mode 100644
index ddb2832aae6..00000000000
--- a/app/services/clusters/gcp/verify_provision_status_service.rb
+++ /dev/null
@@ -1,50 +0,0 @@
-# frozen_string_literal: true
-
-module Clusters
- module Gcp
- class VerifyProvisionStatusService
- attr_reader :provider
-
- INITIAL_INTERVAL = 2.minutes
- EAGER_INTERVAL = 10.seconds
- TIMEOUT = 20.minutes
-
- def execute(provider)
- @provider = provider
-
- request_operation do |operation|
- case operation.status
- when 'PENDING', 'RUNNING'
- continue_creation(operation)
- when 'DONE'
- finalize_creation
- else
- provider.make_errored!("Unexpected operation status; #{operation.status} #{operation.status_message}")
- end
- end
- end
-
- private
-
- def continue_creation(operation)
- if elapsed_time_from_creation(operation) < TIMEOUT
- WaitForClusterCreationWorker.perform_in(EAGER_INTERVAL, provider.cluster_id)
- else
- provider.make_errored!(_('Kubernetes cluster creation time exceeds timeout; %{timeout}') % { timeout: TIMEOUT })
- end
- end
-
- def elapsed_time_from_creation(operation)
- Time.current.utc - operation.start_time.to_time.utc
- end
-
- def finalize_creation
- Clusters::Gcp::FinalizeCreationService.new.execute(provider)
- end
-
- def request_operation(&blk)
- Clusters::Gcp::FetchOperationService.new.execute(provider, &blk)
- end
- end
- end
-end
diff --git a/app/services/concerns/integrations/project_test_data.rb b/app/services/concerns/integrations/project_test_data.rb
index ae1e1d1e66c..b3427697052 100644
--- a/app/services/concerns/integrations/project_test_data.rb
+++ b/app/services/concerns/integrations/project_test_data.rb
@@ -2,8 +2,14 @@
module Integrations
module ProjectTestData
+ NoDataError = Class.new(ArgumentError)
+
private
+ def no_data_error(msg)
+ raise NoDataError, msg
+ end
+
def push_events_data
Gitlab::DataBuilder::Push.build_sample(project, current_user)
end
@@ -11,7 +17,7 @@ module Integrations
def note_events_data
note = NotesFinder.new(current_user, project: project, target: project, sort: 'id_desc').execute.first
- return { error: s_('TestHooks|Ensure the project has notes.') } unless note.present?
+ no_data_error(s_('TestHooks|Ensure the project has notes.')) unless note.present?
Gitlab::DataBuilder::Note.build(note, current_user)
end
@@ -19,7 +25,7 @@ module Integrations
def issues_events_data
issue = IssuesFinder.new(current_user, project_id: project.id, sort: 'created_desc').execute.first
- return { error: s_('TestHooks|Ensure the project has issues.') } unless issue.present?
+ no_data_error(s_('TestHooks|Ensure the project has issues.')) unless issue.present?
issue.to_hook_data(current_user)
end
@@ -27,7 +33,7 @@ module Integrations
def merge_requests_events_data
merge_request = MergeRequestsFinder.new(current_user, project_id: project.id, sort: 'created_desc').execute.first
- return { error: s_('TestHooks|Ensure the project has merge requests.') } unless merge_request.present?
+ no_data_error(s_('TestHooks|Ensure the project has merge requests.')) unless merge_request.present?
merge_request.to_hook_data(current_user)
end
@@ -35,7 +41,7 @@ module Integrations
def job_events_data
build = Ci::JobsFinder.new(current_user: current_user, project: project).execute.first
- return { error: s_('TestHooks|Ensure the project has CI jobs.') } unless build.present?
+ no_data_error(s_('TestHooks|Ensure the project has CI jobs.')) unless build.present?
Gitlab::DataBuilder::Build.build(build)
end
@@ -43,7 +49,7 @@ module Integrations
def pipeline_events_data
pipeline = Ci::PipelinesFinder.new(project, current_user, order_by: 'id', sort: 'desc').execute.first
- return { error: s_('TestHooks|Ensure the project has CI pipelines.') } unless pipeline.present?
+ no_data_error(s_('TestHooks|Ensure the project has CI pipelines.')) unless pipeline.present?
Gitlab::DataBuilder::Pipeline.build(pipeline)
end
@@ -51,9 +57,7 @@ module Integrations
def wiki_page_events_data
page = project.wiki.list_pages(limit: 1).first
- if !project.wiki_enabled? || page.blank?
- return { error: s_('TestHooks|Ensure the wiki is enabled and has pages.') }
- end
+ no_data_error(s_('TestHooks|Ensure the wiki is enabled and has pages.')) if !project.wiki_enabled? || page.blank?
Gitlab::DataBuilder::WikiPage.build(page, current_user, 'create')
end
@@ -61,7 +65,7 @@ module Integrations
def deployment_events_data
deployment = DeploymentsFinder.new(project: project, order_by: 'created_at', sort: 'desc').execute.first
- return { error: s_('TestHooks|Ensure the project has deployments.') } unless deployment.present?
+ no_data_error(s_('TestHooks|Ensure the project has deployments.')) unless deployment.present?
Gitlab::DataBuilder::Deployment.build(deployment, deployment.status, Time.current)
end
@@ -69,7 +73,7 @@ module Integrations
def releases_events_data
release = ReleasesFinder.new(project, current_user, order_by: :created_at, sort: :desc).execute.first
- return { error: s_('TestHooks|Ensure the project has releases.') } unless release.present?
+ no_data_error(s_('TestHooks|Ensure the project has releases.')) unless release.present?
release.to_hook_data('create')
end
diff --git a/app/services/design_management/save_designs_service.rb b/app/services/design_management/save_designs_service.rb
index 64537293e65..ea5675c6ddd 100644
--- a/app/services/design_management/save_designs_service.rb
+++ b/app/services/design_management/save_designs_service.rb
@@ -113,7 +113,7 @@ module DesignManagement
def file_content(file, full_path)
transformer = ::Lfs::FileTransformer.new(project, repository, target_branch)
- transformer.new_file(full_path, file.to_io).content
+ transformer.new_file(full_path, file.to_io, detect_content_type: Feature.enabled?(:design_management_allow_dangerous_images, project)).content
end
# Returns the latest blobs for the designs as a Hash of `{ Design => Blob }`
diff --git a/app/services/discussions/resolve_service.rb b/app/services/discussions/resolve_service.rb
index baf14aa8a03..54fc452ac85 100644
--- a/app/services/discussions/resolve_service.rb
+++ b/app/services/discussions/resolve_service.rb
@@ -17,7 +17,8 @@ module Discussions
def execute
discussions.each(&method(:resolve_discussion))
- process_auto_merge
+
+ after_resolve_cleanup
end
private
@@ -67,9 +68,19 @@ module Discussions
end
end
- def process_auto_merge
+ def after_resolve_cleanup
return unless merge_request
return unless @resolved_count > 0
+
+ send_graphql_triggers
+ process_auto_merge
+ end
+
+ def send_graphql_triggers
+ GraphqlTriggers.merge_request_merge_status_updated(merge_request)
+ end
+
+ def process_auto_merge
return unless discussions_ready_to_merge?
AutoMergeProcessWorker.perform_async(merge_request.id)
diff --git a/app/services/discussions/unresolve_service.rb b/app/services/discussions/unresolve_service.rb
index fbd96ceafe7..f6685b79a33 100644
--- a/app/services/discussions/unresolve_service.rb
+++ b/app/services/discussions/unresolve_service.rb
@@ -12,10 +12,27 @@ module Discussions
end
def execute
+ @all_discussions_resolved_before = merge_request ? @discussion.noteable.discussions_resolved? : false
+
@discussion.unresolve!
+ send_graphql_triggers
+
Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter
.track_unresolve_thread_action(user: @user)
end
+
+ private
+
+ def merge_request
+ @discussion.noteable if @discussion.for_merge_request?
+ end
+ strong_memoize_attr :merge_request
+
+ def send_graphql_triggers
+ return unless merge_request && @all_discussions_resolved_before
+
+ GraphqlTriggers.merge_request_merge_status_updated(merge_request)
+ end
end
end
diff --git a/app/services/draft_notes/publish_service.rb b/app/services/draft_notes/publish_service.rb
index a82a6e22a5a..fab7a227e7d 100644
--- a/app/services/draft_notes/publish_service.rb
+++ b/app/services/draft_notes/publish_service.rb
@@ -34,7 +34,12 @@ module DraftNotes
created_notes = draft_notes.map do |draft_note|
draft_note.review = review
- create_note_from_draft(draft_note, skip_capture_diff_note_position: true, skip_keep_around_commits: true)
+ create_note_from_draft(
+ draft_note,
+ skip_capture_diff_note_position: true,
+ skip_keep_around_commits: true,
+ skip_merge_status_trigger: true
+ )
end
capture_diff_note_positions(created_notes)
@@ -43,16 +48,18 @@ module DraftNotes
set_reviewed
notification_service.async.new_review(review)
MergeRequests::ResolvedDiscussionNotificationService.new(project: project, current_user: current_user).execute(merge_request)
+ GraphqlTriggers.merge_request_merge_status_updated(merge_request)
end
- def create_note_from_draft(draft, skip_capture_diff_note_position: false, skip_keep_around_commits: false)
+ def create_note_from_draft(draft, skip_capture_diff_note_position: false, skip_keep_around_commits: false, skip_merge_status_trigger: false)
# Make sure the diff file is unfolded in order to find the correct line
# codes.
draft.diff_file&.unfold_diff_lines(draft.original_position)
note_params = draft.publish_params.merge(skip_keep_around_commits: skip_keep_around_commits)
note = Notes::CreateService.new(draft.project, draft.author, note_params).execute(
- skip_capture_diff_note_position: skip_capture_diff_note_position
+ skip_capture_diff_note_position: skip_capture_diff_note_position,
+ skip_merge_status_trigger: skip_merge_status_trigger
)
set_discussion_resolve_status(note, draft)
diff --git a/app/services/environments/stop_stale_service.rb b/app/services/environments/stop_stale_service.rb
new file mode 100644
index 00000000000..7b7032cb670
--- /dev/null
+++ b/app/services/environments/stop_stale_service.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module Environments
+ class StopStaleService < BaseService
+ def execute
+ return ServiceResponse.error(message: 'Before date must be provided') unless params[:before].present?
+
+ return ServiceResponse.error(message: 'Unauthorized') unless can?(current_user, :stop_environment, project)
+
+ Environment.available
+ .deployed_and_updated_before(project.id, params[:before])
+ .without_protected(project)
+ .in_batches(of: 100) do |env_batch| # rubocop:disable Cop/InBatches
+ Environments::AutoStopWorker.bulk_perform_async_with_contexts(
+ env_batch,
+ arguments_proc: ->(environment) { environment.id },
+ context_proc: ->(environment) { { project: project } }
+ )
+ end
+
+ ServiceResponse.success(message: 'Successfully requested stop for all stale environments')
+ end
+ end
+end
diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb
index 1055f5ff088..8f722de2019 100644
--- a/app/services/files/base_service.rb
+++ b/app/services/files/base_service.rb
@@ -9,7 +9,7 @@ module Files
git_user = Gitlab::Git::User.from_gitlab(current_user) if current_user.present?
- @author_email = params[:author_email] || git_user&.email
+ @author_email = commit_email(git_user)
@author_name = params[:author_name] || git_user&.name
@commit_message = params[:commit_message]
@last_commit_sha = params[:last_commit_sha]
@@ -33,5 +33,18 @@ module Files
last_commit.sha != commit_id
end
+
+ private
+
+ def commit_email(git_user)
+ return params[:author_email] if params[:author_email].present?
+ return unless current_user
+
+ namespace_commit_email = current_user.namespace_commit_email_for_project(@start_project)
+
+ return namespace_commit_email.email.email if namespace_commit_email
+
+ git_user.email
+ end
end
end
diff --git a/app/services/git/branch_hooks_service.rb b/app/services/git/branch_hooks_service.rb
index 71dd9501648..6087efce9fd 100644
--- a/app/services/git/branch_hooks_service.rb
+++ b/app/services/git/branch_hooks_service.rb
@@ -165,14 +165,11 @@ module Git
end
def signature_types
- types = [
+ [
::CommitSignatures::GpgSignature,
- ::CommitSignatures::X509CommitSignature
+ ::CommitSignatures::X509CommitSignature,
+ ::CommitSignatures::SshSignature
]
-
- types.push(::CommitSignatures::SshSignature) if Feature.enabled?(:ssh_commit_signatures, project)
-
- types
end
def unsigned_commit_shas(commits)
diff --git a/app/services/groups/import_export/export_service.rb b/app/services/groups/import_export/export_service.rb
index bd54b48c5f4..2d88283661c 100644
--- a/app/services/groups/import_export/export_service.rb
+++ b/app/services/groups/import_export/export_service.rb
@@ -71,7 +71,7 @@ module Groups
end
def tree_exporter
- tree_exporter_class.new(
+ Gitlab::ImportExport::Group::TreeSaver.new(
group: group,
current_user: current_user,
shared: shared,
@@ -79,18 +79,6 @@ module Groups
)
end
- def tree_exporter_class
- if ndjson?
- Gitlab::ImportExport::Group::TreeSaver
- else
- Gitlab::ImportExport::Group::LegacyTreeSaver
- end
- end
-
- def ndjson?
- ::Feature.enabled?(:group_export_ndjson, group&.parent)
- end
-
def version_saver
Gitlab::ImportExport::VersionSaver.new(shared: shared)
end
diff --git a/app/services/groups/import_export/import_service.rb b/app/services/groups/import_export/import_service.rb
index ac181245986..15948ab82a2 100644
--- a/app/services/groups/import_export/import_service.rb
+++ b/app/services/groups/import_export/import_service.rb
@@ -29,7 +29,7 @@ module Groups
def execute
Gitlab::Tracking.event(self.class.name, 'create', label: 'import_group_from_file')
- if valid_user_permissions? && import_file && restorers.all?(&:restore)
+ if valid_user_permissions? && import_file && valid_import_file? && restorers.all?(&:restore)
notify_success
Gitlab::Tracking.event(
@@ -75,25 +75,11 @@ module Groups
def tree_restorer
@tree_restorer ||=
- if ndjson?
- Gitlab::ImportExport::Group::TreeRestorer.new(
- user: current_user,
- shared: shared,
- group: group
- )
- else
- Gitlab::ImportExport::Group::LegacyTreeRestorer.new(
- user: current_user,
- shared: shared,
- group: group,
- group_hash: nil
- )
- end
- end
-
- def ndjson?
- ::Feature.enabled?(:group_import_ndjson, group&.parent) &&
- File.exist?(File.join(shared.export_path, 'tree/groups/_all.ndjson'))
+ Gitlab::ImportExport::Group::TreeRestorer.new(
+ user: current_user,
+ shared: shared,
+ group: group
+ )
end
def remove_import_file
@@ -115,6 +101,14 @@ module Groups
end
end
+ def valid_import_file?
+ return true if File.exist?(File.join(shared.export_path, 'tree/groups/_all.ndjson'))
+
+ shared.error(::Gitlab::ImportExport::Error.incompatible_import_file_error)
+
+ false
+ end
+
def notify_success
@logger.info(
group_id: group.id,
diff --git a/app/services/groups/transfer_service.rb b/app/services/groups/transfer_service.rb
index 6fbf7daeb81..0a9705181ba 100644
--- a/app/services/groups/transfer_service.rb
+++ b/app/services/groups/transfer_service.rb
@@ -34,6 +34,7 @@ module Groups
update_integrations
remove_issue_contacts(old_root_ancestor_id, was_root_group)
update_crm_objects(was_root_group)
+ remove_namespace_commit_emails(was_root_group)
end
post_update_hooks(@updated_project_ids, old_root_ancestor_id)
@@ -279,6 +280,10 @@ module Groups
Gitlab::EventStore.publish(event)
end
+
+ def remove_namespace_commit_emails(was_root_group)
+ Users::NamespaceCommitEmail.delete_for_namespace(@group) if was_root_group
+ end
end
end
diff --git a/app/services/ide/schemas_config_service.rb b/app/services/ide/schemas_config_service.rb
index a013a4679b5..5292f15dc37 100644
--- a/app/services/ide/schemas_config_service.rb
+++ b/app/services/ide/schemas_config_service.rb
@@ -33,9 +33,7 @@ module Ide
end
def predefined_schemas
- return PREDEFINED_SCHEMAS if Feature.enabled?(:schema_linting)
-
- []
+ PREDEFINED_SCHEMAS
end
def get_cached(url)
diff --git a/app/services/import/github/gists_import_service.rb b/app/services/import/github/gists_import_service.rb
index df1bbe306e7..e57430916fa 100644
--- a/app/services/import/github/gists_import_service.rb
+++ b/app/services/import/github/gists_import_service.rb
@@ -3,16 +3,20 @@
module Import
module Github
class GistsImportService < ::BaseService
- def initialize(user, params)
+ def initialize(user, client, params)
@current_user = user
@params = params
+ @client = client
end
def execute
return error('Import already in progress', 422) if import_status.started?
+ check_user_token
start_import
success
+ rescue Octokit::Unauthorized
+ error('Access denied to the GitHub account.', 401)
end
private
@@ -29,6 +33,10 @@ module Import
Gitlab::GithubGistsImport::StartImportWorker.perform_async(current_user.id, encrypted_token)
import_status.start!
end
+
+ def check_user_token
+ @client.octokit.user.present?
+ end
end
end
end
diff --git a/app/services/import/github_service.rb b/app/services/import/github_service.rb
index 2378a4b11b1..b30c344723d 100644
--- a/app/services/import/github_service.rb
+++ b/app/services/import/github_service.rb
@@ -46,12 +46,8 @@ module Import
@project_name ||= params[:new_name].presence || repo[:name]
end
- def namespace_path
- @namespace_path ||= params[:target_namespace].presence || current_user.namespace_path
- end
-
def target_namespace
- @target_namespace ||= find_or_create_namespace(namespace_path, current_user.namespace_path)
+ @target_namespace ||= Namespace.find_by_full_path(target_namespace_path)
end
def extra_project_attrs
@@ -104,6 +100,8 @@ module Import
def validate_context
if blocked_url?
log_and_return_error("Invalid URL: #{url}", _("Invalid URL: %{url}") % { url: url }, :bad_request)
+ elsif target_namespace.nil?
+ error(_('Namespace or group to import repository into does not exist.'), :unprocessable_entity)
elsif !authorized?
error(_('This namespace has already been taken. Choose a different one.'), :unprocessable_entity)
elsif oversized?
@@ -111,6 +109,12 @@ module Import
end
end
+ def target_namespace_path
+ raise ArgumentError, 'Target namespace is required' if params[:target_namespace].blank?
+
+ params[:target_namespace]
+ end
+
def log_error(exception)
Gitlab::GithubImport::Logger.error(
message: 'Import failed due to a GitHub error',
diff --git a/app/services/integrations/test/base_service.rb b/app/services/integrations/test/base_service.rb
index a8a027092d5..6291f2dfbaa 100644
--- a/app/services/integrations/test/base_service.rb
+++ b/app/services/integrations/test/base_service.rb
@@ -21,9 +21,9 @@ module Integrations
return error('Testing not available for this event')
end
- return error(data[:error]) if data[:error].present?
-
integration.test(data)
+ rescue ArgumentError => e
+ error(e.message)
end
private
diff --git a/app/services/issuable/discussions_list_service.rb b/app/services/issuable/discussions_list_service.rb
index 1e5e37e4e1b..10e7660289b 100644
--- a/app/services/issuable/discussions_list_service.rb
+++ b/app/services/issuable/discussions_list_service.rb
@@ -19,7 +19,7 @@ module Issuable
return Note.none unless can_read_issuable_notes?
notes = NotesFinder.new(current_user, params.merge({ target: issuable, project: issuable.project }))
- .execute.with_web_entity_associations.inc_relations_for_view.fresh
+ .execute.with_web_entity_associations.inc_relations_for_view(issuable).fresh
if paginator
paginated_discussions_by_type = paginator.records.group_by(&:table_name)
@@ -49,7 +49,6 @@ module Issuable
def paginator
return if params[:per_page].blank?
- return if issuable.instance_of?(MergeRequest) && Feature.disabled?(:paginated_mr_discussions, issuable.project)
strong_memoize(:paginator) do
issuable
diff --git a/app/services/issuable_links/create_service.rb b/app/services/issuable_links/create_service.rb
index 2e9775af8c2..f244f54b25f 100644
--- a/app/services/issuable_links/create_service.rb
+++ b/app/services/issuable_links/create_service.rb
@@ -121,7 +121,7 @@ module IssuableLinks
end
def target_issuable_type
- :issue
+ 'issue'
end
def create_notes(issuable_link)
diff --git a/app/services/issues/base_service.rb b/app/services/issues/base_service.rb
index 10407e99715..553fb6e2ac9 100644
--- a/app/services/issues/base_service.rb
+++ b/app/services/issues/base_service.rb
@@ -97,6 +97,16 @@ module Issues
hooks_scope = issue.confidential? ? :confidential_issue_hooks : :issue_hooks
issue.project.execute_hooks(issue_data, hooks_scope)
issue.project.execute_integrations(issue_data, hooks_scope)
+
+ execute_incident_hooks(issue, issue_data) if issue.incident?
+ end
+
+ # We can remove this code after proposal in
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/367550#proposal is updated.
+ def execute_incident_hooks(issue, issue_data)
+ issue_data[:object_kind] = 'incident'
+ issue_data[:event_type] = 'incident'
+ issue.project.execute_integrations(issue_data, :incident_hooks)
end
def update_project_counter_caches?(issue)
@@ -117,7 +127,7 @@ module Issues
override :allowed_create_params
def allowed_create_params(params)
- super(params).except(:issue_type, :work_item_type_id, :work_item_type)
+ super(params).except(:work_item_type_id, :work_item_type)
end
end
end
diff --git a/app/services/issues/create_service.rb b/app/services/issues/create_service.rb
index afad8d0c6bf..f6a1db2dcaa 100644
--- a/app/services/issues/create_service.rb
+++ b/app/services/issues/create_service.rb
@@ -24,6 +24,9 @@ module Issues
return error(_('Operation not allowed'), 403) unless @current_user.can?(authorization_action, @project)
@issue = @build_service.execute
+ # issue_type is set in BuildService, so we can delete it from params, in later phase
+ # it can be set also from quick actions - in that case work_item_id is synced later again
+ params.delete(:issue_type)
handle_move_between_ids(@issue)
@@ -68,6 +71,7 @@ module Issues
handle_escalation_status_change(issue)
create_timeline_event(issue)
try_to_associate_contacts(issue)
+ change_additional_attributes(issue)
super
end
@@ -127,6 +131,15 @@ module Issues
set_crm_contacts(issue, contacts)
end
+
+ override :change_additional_attributes
+ def change_additional_attributes(issue)
+ super
+
+ # issue_type can be still set through quick actions, in that case
+ # we have to make sure to re-sync work_item_type with it
+ issue.work_item_type_id = find_work_item_type_id(params[:issue_type]) if params[:issue_type]
+ end
end
end
diff --git a/app/services/lfs/file_transformer.rb b/app/services/lfs/file_transformer.rb
index 69d33e1c873..a02fce552cf 100644
--- a/app/services/lfs/file_transformer.rb
+++ b/app/services/lfs/file_transformer.rb
@@ -29,11 +29,11 @@ module Lfs
@branch_name = branch_name
end
- def new_file(file_path, file_content, encoding: nil)
+ def new_file(file_path, file_content, encoding: nil, detect_content_type: false)
if project.lfs_enabled? && lfs_file?(file_path)
file_content = parse_file_content(file_content, encoding: encoding)
lfs_pointer_file = Gitlab::Git::LfsPointerFile.new(file_content)
- lfs_object = create_lfs_object!(lfs_pointer_file, file_content)
+ lfs_object = create_lfs_object!(lfs_pointer_file, file_content, detect_content_type)
link_lfs_object!(lfs_object)
@@ -63,9 +63,17 @@ module Lfs
end
# rubocop: disable CodeReuse/ActiveRecord
- def create_lfs_object!(lfs_pointer_file, file_content)
+ def create_lfs_object!(lfs_pointer_file, file_content, detect_content_type)
LfsObject.find_or_create_by(oid: lfs_pointer_file.sha256, size: lfs_pointer_file.size) do |lfs_object|
- lfs_object.file = CarrierWaveStringFile.new(file_content)
+ lfs_object.file = if detect_content_type && (content_type = Gitlab::Utils::MimeType.from_string(file_content))
+ CarrierWaveStringFile.new_file(
+ file_content: file_content,
+ filename: '',
+ content_type: content_type
+ )
+ else
+ CarrierWaveStringFile.new(file_content)
+ end
end
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/app/services/members/approve_access_request_service.rb b/app/services/members/approve_access_request_service.rb
index 51f9492ec91..5e73d7a957b 100644
--- a/app/services/members/approve_access_request_service.rb
+++ b/app/services/members/approve_access_request_service.rb
@@ -6,7 +6,7 @@ module Members
validate_access!(access_requester) unless skip_authorization
access_requester.access_level = params[:access_level] if params[:access_level]
- access_requester.accept_request
+ access_requester.accept_request(current_user)
after_execute(member: access_requester, skip_log_audit_event: skip_log_audit_event)
diff --git a/app/services/members/creator_service.rb b/app/services/members/creator_service.rb
index f59a3ed77eb..2d378a64c02 100644
--- a/app/services/members/creator_service.rb
+++ b/app/services/members/creator_service.rb
@@ -225,7 +225,7 @@ module Members
access_level: access_level)
.execute(
member,
- skip_authorization: ldap,
+ skip_authorization: ldap || skip_authorization?,
skip_log_audit_event: ldap
)
end
diff --git a/app/services/members/destroy_service.rb b/app/services/members/destroy_service.rb
index 5afc13701e0..24c5b12b335 100644
--- a/app/services/members/destroy_service.rb
+++ b/app/services/members/destroy_service.rb
@@ -13,24 +13,48 @@ module Members
end
@skip_auth = skip_authorization
- last_owner = true
+
+ if a_group_owner?(member)
+ process_destroy_of_group_owner_member(member, skip_subresources, unassign_issuables)
+ else
+ destroy_member(member)
+ destroy_data_related_to_member(member, skip_subresources, unassign_issuables)
+ end
+
+ member
+ end
+
+ private
+
+ def process_destroy_of_group_owner_member(member, skip_subresources, unassign_issuables)
+ # Deleting 2 different group owners via the API in quick succession could lead to
+ # wrong results for the `last_owner?` check due to race conditions. To prevent this
+ # we wrap both the last_owner? check and the deletes of owners within a lock.
+ last_group_owner = true
in_lock("delete_members:#{member.source.class}:#{member.source.id}", sleep_sec: 0.1.seconds) do
- break if member.is_a?(GroupMember) && member.source.last_owner?(member.user)
+ break if member.source.last_owner?(member.user)
- last_owner = false
- member.destroy
+ last_group_owner = false
+ destroy_member(member)
end
- unless last_owner
- member.user&.invalidate_cache_counts
- delete_member_associations(member, skip_subresources, unassign_issuables)
- end
+ # deletion of related data does not have to be within the lock.
+ destroy_data_related_to_member(member, skip_subresources, unassign_issuables) unless last_group_owner
+ end
- member
+ def destroy_member(member)
+ member.destroy
end
- private
+ def destroy_data_related_to_member(member, skip_subresources, unassign_issuables)
+ member.user&.invalidate_cache_counts
+ delete_member_associations(member, skip_subresources, unassign_issuables)
+ end
+
+ def a_group_owner?(member)
+ member.is_a?(GroupMember) && member.owner?
+ end
def delete_member_associations(member, skip_subresources, unassign_issuables)
if member.request? && member.user != current_user
diff --git a/app/services/members/update_service.rb b/app/services/members/update_service.rb
index 0e6b02f7a80..b2c0fffc12d 100644
--- a/app/services/members/update_service.rb
+++ b/app/services/members/update_service.rb
@@ -11,10 +11,9 @@ module Members
[member.id, { human_access: member.human_access, expires_at: member.expires_at }]
end
- if Feature.enabled?(:bulk_update_membership_roles, current_user)
- multiple_members_update(members, permission, old_access_level_expiry_map)
- else
- single_member_update(members.first, permission, old_access_level_expiry_map)
+ updated_members = update_members(members, permission)
+ Member.transaction do
+ updated_members.each { |member| post_update(member, permission, old_access_level_expiry_map) }
end
prepare_response(members)
@@ -22,35 +21,22 @@ module Members
private
- def single_member_update(member, permission, old_access_level_expiry_map)
+ def update_members(members, permission)
+ # `filter_map` avoids the `post_update` call for the member that resulted in no change
+ Member.transaction do
+ members.filter_map { |member| update_member(member, permission) }
+ end
+ rescue ActiveRecord::RecordInvalid
+ []
+ end
+
+ def update_member(member, permission)
raise Gitlab::Access::AccessDeniedError unless has_update_permissions?(member, permission)
member.attributes = params
- return success(member: member) unless member.changed?
-
- post_update(member, permission, old_access_level_expiry_map) if member.save
- end
-
- def multiple_members_update(members, permission, old_access_level_expiry_map)
- begin
- updated_members =
- Member.transaction do
- # Using `next` with `filter_map` avoids the `post_update` call for the member that resulted in no change
- members.filter_map do |member|
- raise Gitlab::Access::AccessDeniedError unless has_update_permissions?(member, permission)
-
- member.attributes = params
- next unless member.changed?
-
- member.save!
- member
- end
- end
- rescue ActiveRecord::RecordInvalid
- return
- end
+ return unless member.changed?
- updated_members.each { |member| post_update(member, permission, old_access_level_expiry_map) }
+ member.tap(&:save!)
end
def post_update(member, permission, old_access_level_expiry_map)
@@ -62,18 +48,13 @@ module Members
end
def prepare_response(members)
- errored_member = members.detect { |member| member.errors.any? }
- if errored_member.present?
- return error(errored_member.errors.full_messages.to_sentence, pass_back: { member: errored_member })
+ errored_members = members.select { |member| member.errors.any? }
+ if errored_members.present?
+ error_message = errored_members.flat_map { |member| member.errors.full_messages }.uniq.to_sentence
+ return error(error_message, pass_back: { members: errored_members })
end
- # TODO: Remove the :member key when removing the bulk_update_membership_roles FF and update where it's used.
- # https://gitlab.com/gitlab-org/gitlab/-/issues/373257
- if members.one?
- success(member: members.first)
- else
- success(members: members)
- end
+ success(members: members)
end
def has_update_permissions?(member, permission)
diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb
index 468cadb03c7..f6cbe889128 100644
--- a/app/services/merge_requests/base_service.rb
+++ b/app/services/merge_requests/base_service.rb
@@ -94,6 +94,10 @@ module MergeRequests
private
+ def refresh_pipelines_on_merge_requests(merge_request, allow_duplicate: false)
+ create_pipeline_for(merge_request, current_user, async: true, allow_duplicate: allow_duplicate)
+ end
+
def enqueue_jira_connect_messages_for(merge_request)
return unless project.jira_subscription_exists?
@@ -184,16 +188,18 @@ module MergeRequests
merge_request, merge_request.project, current_user, old_reviewers)
end
- def create_pipeline_for(merge_request, user, async: false)
+ def create_pipeline_for(merge_request, user, async: false, allow_duplicate: false)
+ create_pipeline_params = params.slice(:push_options).merge(allow_duplicate: allow_duplicate)
+
if async
MergeRequests::CreatePipelineWorker.perform_async(
project.id,
user.id,
merge_request.id,
- params.slice(:push_options).deep_stringify_keys)
+ create_pipeline_params.deep_stringify_keys)
else
MergeRequests::CreatePipelineService
- .new(project: project, current_user: user, params: params.slice(:push_options))
+ .new(project: project, current_user: user, params: create_pipeline_params)
.execute(merge_request)
end
end
diff --git a/app/services/merge_requests/rebase_service.rb b/app/services/merge_requests/rebase_service.rb
index 1c4e1784b34..792f1728b88 100644
--- a/app/services/merge_requests/rebase_service.rb
+++ b/app/services/merge_requests/rebase_service.rb
@@ -6,6 +6,19 @@ module MergeRequests
attr_reader :merge_request, :rebase_error
+ def validate(merge_request)
+ return error_response(_('Source branch does not exist')) unless
+ merge_request.source_branch_exists?
+
+ return error_response(_('Cannot push to source branch')) unless
+ user_access.can_push_to_branch?(merge_request.source_branch)
+
+ return error_response(_('Source branch is protected from force push')) unless
+ merge_request.permits_force_push?
+
+ ServiceResponse.success
+ end
+
def execute(merge_request, skip_ci: false)
@merge_request = merge_request
@skip_ci = skip_ci
@@ -40,5 +53,13 @@ module MergeRequests
REBASE_ERROR
end
end
+
+ def user_access
+ Gitlab::UserAccess.new(current_user, container: project)
+ end
+
+ def error_response(message)
+ ServiceResponse.error(message: message)
+ end
end
end
diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb
index 533d0052fb8..ce49d5dd43c 100644
--- a/app/services/merge_requests/refresh_service.rb
+++ b/app/services/merge_requests/refresh_service.rb
@@ -162,10 +162,6 @@ module MergeRequests
@outdate_service ||= Suggestions::OutdateService.new
end
- def refresh_pipelines_on_merge_requests(merge_request)
- create_pipeline_for(merge_request, current_user, async: true)
- end
-
def abort_auto_merges(merge_request)
abort_auto_merge(merge_request, 'source branch was updated')
end
diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb
index 745647b727c..a273b853c0d 100644
--- a/app/services/merge_requests/update_service.rb
+++ b/app/services/merge_requests/update_service.rb
@@ -169,6 +169,8 @@ module MergeRequests
merge_request.target_branch
)
+ refresh_pipelines_on_merge_requests(merge_request, allow_duplicate: true)
+
abort_auto_merge(merge_request, 'target branch was changed')
end
diff --git a/app/services/ml/experiment_tracking/candidate_repository.rb b/app/services/ml/experiment_tracking/candidate_repository.rb
index 1dbeb30145b..f1fd93d7816 100644
--- a/app/services/ml/experiment_tracking/candidate_repository.rb
+++ b/app/services/ml/experiment_tracking/candidate_repository.rb
@@ -14,9 +14,10 @@ module Ml
::Ml::Candidate.with_project_id_and_iid(project.id, iid)
end
- def create!(experiment, start_time, tags = nil)
+ def create!(experiment, start_time, tags = nil, name = nil)
candidate = experiment.candidates.create!(
user: user,
+ name: candidate_name(name, tags),
start_time: start_time || 0
)
@@ -85,6 +86,13 @@ module Ml
entity_class.insert_all(entities, returning: false) unless entities.empty?
end
+
+ def candidate_name(name, tags)
+ return name if name.present?
+ return unless tags.present?
+
+ tags.detect { |t| t[:key] == 'mlflow.runName' }&.dig(:value)
+ end
end
end
end
diff --git a/app/services/notes/build_service.rb b/app/services/notes/build_service.rb
index cc5c81cf280..e6766273441 100644
--- a/app/services/notes/build_service.rb
+++ b/app/services/notes/build_service.rb
@@ -35,7 +35,7 @@ module Notes
note.author = current_user
parent_confidential = discussion&.confidential?
- can_set_confidential = can?(current_user, :mark_note_as_confidential, note)
+ can_set_confidential = can?(current_user, :mark_note_as_internal, note)
return discussion_not_found if parent_confidential && !can_set_confidential
diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb
index 555d60dc291..5f05b613288 100644
--- a/app/services/notes/create_service.rb
+++ b/app/services/notes/create_service.rb
@@ -4,7 +4,7 @@ module Notes
class CreateService < ::Notes::BaseService
include IncidentManagement::UsageData
- def execute(skip_capture_diff_note_position: false)
+ def execute(skip_capture_diff_note_position: false, skip_merge_status_trigger: false)
note = Notes::BuildService.new(project, current_user, params.except(:merge_request_diff_head_sha)).execute
# n+1: https://gitlab.com/gitlab-org/gitlab-foss/issues/37440
@@ -34,7 +34,13 @@ module Notes
end
end
- when_saved(note, skip_capture_diff_note_position: skip_capture_diff_note_position) if note_saved
+ if note_saved
+ when_saved(
+ note,
+ skip_capture_diff_note_position: skip_capture_diff_note_position,
+ skip_merge_status_trigger: skip_merge_status_trigger
+ )
+ end
end
note
@@ -72,15 +78,21 @@ module Notes
end
end
- def when_saved(note, skip_capture_diff_note_position: false)
+ def when_saved(note, skip_capture_diff_note_position: false, skip_merge_status_trigger: false)
todo_service.new_note(note, current_user)
clear_noteable_diffs_cache(note)
Suggestions::CreateService.new(note).execute
increment_usage_counter(note)
track_event(note, current_user)
- if !skip_capture_diff_note_position && note.for_merge_request? && note.diff_note? && note.start_of_discussion?
- Discussions::CaptureDiffNotePositionService.new(note.noteable, note.diff_file&.paths).execute(note.discussion)
+ if note.for_merge_request? && note.start_of_discussion?
+ if !skip_capture_diff_note_position && note.diff_note?
+ Discussions::CaptureDiffNotePositionService.new(note.noteable, note.diff_file&.paths).execute(note.discussion)
+ end
+
+ if !skip_merge_status_trigger && note.to_be_resolved?
+ GraphqlTriggers.merge_request_merge_status_updated(note.noteable)
+ end
end
end
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index 550bd6d4c55..777d02c590d 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -91,10 +91,10 @@ class NotificationService
end
# Notify the user when at least one of their personal access tokens has expired today
- def access_token_expired(user)
+ def access_token_expired(user, token_names = [])
return unless user.can?(:receive_notifications)
- mailer.access_token_expired_email(user).deliver_later
+ mailer.access_token_expired_email(user, token_names).deliver_later
end
# Notify the user when one of their personal access tokens is revoked
diff --git a/app/services/pages_domains/create_service.rb b/app/services/pages_domains/create_service.rb
index 1f771ca3a05..17194fbe5e4 100644
--- a/app/services/pages_domains/create_service.rb
+++ b/app/services/pages_domains/create_service.rb
@@ -24,6 +24,7 @@ module PagesDomains
project_id: project.id,
namespace_id: project.namespace_id,
root_namespace_id: project.root_namespace.id,
+ domain_id: domain.id,
domain: domain.domain
}
)
diff --git a/app/services/pages_domains/delete_service.rb b/app/services/pages_domains/delete_service.rb
index af69e1845a9..89e598acee0 100644
--- a/app/services/pages_domains/delete_service.rb
+++ b/app/services/pages_domains/delete_service.rb
@@ -22,6 +22,7 @@ module PagesDomains
project_id: project.id,
namespace_id: project.namespace_id,
root_namespace_id: project.root_namespace.id,
+ domain_id: domain.id,
domain: domain.domain
}
)
diff --git a/app/services/pages_domains/retry_acme_order_service.rb b/app/services/pages_domains/retry_acme_order_service.rb
index 6251c9d3615..01647a8ecf5 100644
--- a/app/services/pages_domains/retry_acme_order_service.rb
+++ b/app/services/pages_domains/retry_acme_order_service.rb
@@ -30,6 +30,7 @@ module PagesDomains
project_id: domain.project.id,
namespace_id: domain.project.namespace_id,
root_namespace_id: domain.project.root_namespace.id,
+ domain_id: domain.id,
domain: domain.domain
}
)
diff --git a/app/services/pages_domains/update_service.rb b/app/services/pages_domains/update_service.rb
index b038aaa95b6..1887441d8b8 100644
--- a/app/services/pages_domains/update_service.rb
+++ b/app/services/pages_domains/update_service.rb
@@ -24,6 +24,7 @@ module PagesDomains
project_id: project.id,
namespace_id: project.namespace_id,
root_namespace_id: project.root_namespace.id,
+ domain_id: domain.id,
domain: domain.domain
}
)
diff --git a/app/services/personal_access_tokens/revoke_service.rb b/app/services/personal_access_tokens/revoke_service.rb
index bb5edc27340..237c95bc456 100644
--- a/app/services/personal_access_tokens/revoke_service.rb
+++ b/app/services/personal_access_tokens/revoke_service.rb
@@ -4,13 +4,17 @@ module PersonalAccessTokens
class RevokeService < BaseService
attr_reader :token, :current_user, :group
- VALID_SOURCES = %w[secret_detection].freeze
+ VALID_SOURCES = %i[self secret_detection].freeze
def initialize(current_user = nil, token: nil, group: nil, source: nil)
@current_user = current_user
@token = token
@group = group
@source = source
+
+ @source = :self if @current_user && !@source
+
+ raise ArgumentError unless VALID_SOURCES.include?(@source)
end
def execute
@@ -36,22 +40,21 @@ module PersonalAccessTokens
end
def revocation_permitted?
- if current_user
+ case @source
+ when :self
Ability.allowed?(current_user, :revoke_token, token)
+ when :secret_detection
+ true
else
- source && VALID_SOURCES.include?(source)
+ false
end
end
- def source
- current_user&.username || @source
- end
-
def log_event
Gitlab::AppLogger.info(
class: self.class.name,
message: "PAT Revoked",
- revoked_by: source,
+ revoked_by: current_user&.username || @source,
revoked_for: token.user.username,
token_id: token.id)
end
diff --git a/app/services/projects/autocomplete_service.rb b/app/services/projects/autocomplete_service.rb
index ae5aae87a77..11437ad90fc 100644
--- a/app/services/projects/autocomplete_service.rb
+++ b/app/services/projects/autocomplete_service.rb
@@ -23,7 +23,7 @@ module Projects
MergeRequestsFinder.new(current_user, project_id: project.id, state: 'opened').execute.select([:iid, :title])
end
- def commands(noteable, type)
+ def commands(noteable)
return [] unless noteable && current_user
QuickActions::InterpretService.new(project, current_user).available_commands(noteable)
diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb
index a4b473f35c6..d3313526eaf 100644
--- a/app/services/projects/create_service.rb
+++ b/app/services/projects/create_service.rb
@@ -22,6 +22,12 @@ module Projects
end
def execute
+ params[:wiki_enabled] = params[:wiki_access_level] if params[:wiki_access_level]
+ params[:builds_enabled] = params[:builds_access_level] if params[:builds_access_level]
+ params[:snippets_enabled] = params[:builds_access_level] if params[:snippets_access_level]
+ params[:merge_requests_enabled] = params[:merge_requests_access_level] if params[:merge_requests_access_level]
+ params[:issues_enabled] = params[:issues_access_level] if params[:issues_access_level]
+
if create_from_template?
return ::Projects::CreateFromTemplateService.new(current_user, params).execute
end
diff --git a/app/services/projects/import_service.rb b/app/services/projects/import_service.rb
index 967a1e990b2..e6ccae0a22b 100644
--- a/app/services/projects/import_service.rb
+++ b/app/services/projects/import_service.rb
@@ -166,7 +166,7 @@ module Projects
.then do |(import_url, resolved_host)|
next '' if resolved_host.nil? || !import_url.scheme.in?(%w[http https])
- import_url.host.to_s
+ import_url.hostname.to_s
end
end
diff --git a/app/services/projects/refresh_build_artifacts_size_statistics_service.rb b/app/services/projects/refresh_build_artifacts_size_statistics_service.rb
index 8e006dc8c34..f11083d6c04 100644
--- a/app/services/projects/refresh_build_artifacts_size_statistics_service.rb
+++ b/app/services/projects/refresh_build_artifacts_size_statistics_service.rb
@@ -2,28 +2,31 @@
module Projects
class RefreshBuildArtifactsSizeStatisticsService
- BATCH_SIZE = 1000
+ BATCH_SIZE = 500
+ REFRESH_INTERVAL_SECONDS = 0.1
def execute
refresh = Projects::BuildArtifactsSizeRefresh.process_next_refresh!
- return unless refresh
+
+ return unless refresh&.running?
batch = refresh.next_batch(limit: BATCH_SIZE).to_a
if batch.any?
- # We are doing the sum in ruby because the query takes too long when done in SQL
- total_artifacts_size = batch.sum { |artifact| artifact.size.to_i }
+ increments = batch.map do |artifact|
+ Gitlab::Counters::Increment.new(amount: artifact.size.to_i, ref: artifact.id)
+ end
Projects::BuildArtifactsSizeRefresh.transaction do
# Mark the refresh ready for another worker to pick up and process the next batch
refresh.requeue!(batch.last.id)
- refresh.project.statistics.increment_counter(:build_artifacts_size, total_artifacts_size)
+ ProjectStatistics.bulk_increment_statistic(refresh.project, :build_artifacts_size, increments)
end
+
+ sleep REFRESH_INTERVAL_SECONDS
else
- # Remove the refresh job from the table if there are no more
- # remaining job artifacts to calculate for the given project.
- refresh.destroy!
+ refresh.schedule_finalize!
end
refresh
diff --git a/app/services/repositories/housekeeping_service.rb b/app/services/repositories/housekeeping_service.rb
index e12d69807f2..f314e210442 100644
--- a/app/services/repositories/housekeeping_service.rb
+++ b/app/services/repositories/housekeeping_service.rb
@@ -10,7 +10,7 @@ module Repositories
class HousekeepingService < BaseService
# Timeout set to 24h
LEASE_TIMEOUT = 86400
- PACK_REFS_PERIOD = 6
+ GC_PERIOD = 200
class LeaseTaken < StandardError
def to_s
@@ -74,21 +74,13 @@ module Repositories
if pushes_since_gc % gc_period == 0
:gc
- elsif pushes_since_gc % full_repack_period == 0
- :full_repack
- elsif pushes_since_gc % repack_period == 0
- :incremental_repack
else
- :pack_refs
+ :incremental_repack
end
end
def period_match?
- if Feature.enabled?(:optimized_housekeeping)
- pushes_since_gc % repack_period == 0
- else
- [gc_period, full_repack_period, repack_period, PACK_REFS_PERIOD].any? { |period| pushes_since_gc % period == 0 }
- end
+ [gc_period, repack_period].any? { |period| pushes_since_gc % period == 0 }
end
def housekeeping_enabled?
@@ -96,11 +88,7 @@ module Repositories
end
def gc_period
- Gitlab::CurrentSettings.housekeeping_gc_period
- end
-
- def full_repack_period
- Gitlab::CurrentSettings.housekeeping_full_repack_period
+ GC_PERIOD
end
def repack_period
diff --git a/app/services/search_service.rb b/app/services/search_service.rb
index 403a2f077b0..b4344a009b2 100644
--- a/app/services/search_service.rb
+++ b/app/services/search_service.rb
@@ -112,6 +112,25 @@ class SearchService
false
end
+ def global_search_enabled_for_scope?
+ case params[:scope]
+ when 'blobs'
+ Feature.enabled?(:global_search_code_tab, current_user, type: :ops)
+ when 'commits'
+ Feature.enabled?(:global_search_commits_tab, current_user, type: :ops)
+ when 'issues'
+ Feature.enabled?(:global_search_issues_tab, current_user, type: :ops)
+ when 'merge_requests'
+ Feature.enabled?(:global_search_merge_requests_tab, current_user, type: :ops)
+ when 'wiki_blobs'
+ Feature.enabled?(:global_search_wiki_tab, current_user, type: :ops)
+ when 'users'
+ Feature.enabled?(:global_search_users_tab, current_user, type: :ops)
+ else
+ true
+ end
+ end
+
private
def page
diff --git a/app/services/security/ci_configuration/base_create_service.rb b/app/services/security/ci_configuration/base_create_service.rb
index 7f3b66d40e1..aaa850fde39 100644
--- a/app/services/security/ci_configuration/base_create_service.rb
+++ b/app/services/security/ci_configuration/base_create_service.rb
@@ -3,7 +3,7 @@
module Security
module CiConfiguration
class BaseCreateService
- attr_reader :branch_name, :current_user, :project
+ attr_reader :branch_name, :current_user, :project, :name
def initialize(project, current_user)
@project = project
@@ -41,8 +41,18 @@ module Security
end
def existing_gitlab_ci_content
- @gitlab_ci_yml ||= project.ci_config_for(project.repository.root_ref_sha)
+ root_ref = root_ref_sha(project)
+ return if root_ref.nil?
+
+ @gitlab_ci_yml ||= project.ci_config_for(root_ref)
YAML.safe_load(@gitlab_ci_yml) if @gitlab_ci_yml
+ rescue Psych::BadAlias
+ raise Gitlab::Graphql::Errors::MutationError,
+ ".gitlab-ci.yml with aliases/anchors is not supported. Please change the CI configuration manually."
+ rescue Psych::Exception => e
+ Gitlab::AppLogger.error("Failed to process existing .gitlab-ci.yml: #{e.message}")
+ raise Gitlab::Graphql::Errors::MutationError,
+ "#{name} merge request creation mutation failed"
end
def successful_change_path
@@ -61,6 +71,15 @@ module Security
self.class.to_s, action[:action], label: action[:default_values_overwritten].to_s
)
end
+
+ def root_ref_sha(project)
+ project.repository.root_ref_sha
+ rescue StandardError => e
+ # this might fail on the very first commit,
+ # and unfortunately it raises a StandardError
+ Gitlab::ErrorTracking.track_exception(e, project_id: project.id)
+ nil
+ end
end
end
end
diff --git a/app/services/security/ci_configuration/container_scanning_create_service.rb b/app/services/security/ci_configuration/container_scanning_create_service.rb
index da2f1ac0981..4dfd05451ad 100644
--- a/app/services/security/ci_configuration/container_scanning_create_service.rb
+++ b/app/services/security/ci_configuration/container_scanning_create_service.rb
@@ -21,6 +21,10 @@ module Security
def description
_('Configure Container Scanning in `.gitlab-ci.yml` using the GitLab managed template. You can [add variable overrides](https://docs.gitlab.com/ee/user/application_security/container_scanning/#customizing-the-container-scanning-settings) to customize Container Scanning settings.')
end
+
+ def name
+ 'Container Scanning'
+ end
end
end
end
diff --git a/app/services/security/ci_configuration/dependency_scanning_create_service.rb b/app/services/security/ci_configuration/dependency_scanning_create_service.rb
index b11eccc680c..66dd76c4b5d 100644
--- a/app/services/security/ci_configuration/dependency_scanning_create_service.rb
+++ b/app/services/security/ci_configuration/dependency_scanning_create_service.rb
@@ -21,6 +21,10 @@ module Security
def description
_('Configure Dependency Scanning in `.gitlab-ci.yml` using the GitLab managed template. You can [add variable overrides](https://docs.gitlab.com/ee/user/application_security/dependency_scanning/#customizing-the-dependency-scanning-settings) to customize Dependency Scanning settings.')
end
+
+ def name
+ 'Dependency Scanning'
+ end
end
end
end
diff --git a/app/services/security/ci_configuration/sast_create_service.rb b/app/services/security/ci_configuration/sast_create_service.rb
index d78e22f1fe1..643cb7f89dc 100644
--- a/app/services/security/ci_configuration/sast_create_service.rb
+++ b/app/services/security/ci_configuration/sast_create_service.rb
@@ -20,13 +20,7 @@ module Security
end
def action
- existing_content = begin
- existing_gitlab_ci_content # this can fail on the very first commit
- rescue StandardError
- nil
- end
-
- Security::CiConfiguration::SastBuildAction.new(project.auto_devops_enabled?, params, existing_content, project.ci_config_path).generate
+ Security::CiConfiguration::SastBuildAction.new(project.auto_devops_enabled?, params, existing_gitlab_ci_content, project.ci_config_path).generate
end
def next_branch
@@ -40,6 +34,10 @@ module Security
def description
_('Configure SAST in `.gitlab-ci.yml` using the GitLab managed template. You can [add variable overrides](https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings) to customize SAST settings.')
end
+
+ def name
+ 'SAST'
+ end
end
end
end
diff --git a/app/services/security/ci_configuration/sast_iac_create_service.rb b/app/services/security/ci_configuration/sast_iac_create_service.rb
index fbc65484216..61bbebd77d0 100644
--- a/app/services/security/ci_configuration/sast_iac_create_service.rb
+++ b/app/services/security/ci_configuration/sast_iac_create_service.rb
@@ -21,6 +21,10 @@ module Security
def description
_('Configure SAST IaC in `.gitlab-ci.yml` using the GitLab managed template. You can [add variable overrides](https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings) to customize SAST IaC settings.')
end
+
+ def name
+ 'SAST IaC'
+ end
end
end
end
diff --git a/app/services/security/ci_configuration/secret_detection_create_service.rb b/app/services/security/ci_configuration/secret_detection_create_service.rb
index ca5138b6ed6..792fe4986e9 100644
--- a/app/services/security/ci_configuration/secret_detection_create_service.rb
+++ b/app/services/security/ci_configuration/secret_detection_create_service.rb
@@ -21,6 +21,10 @@ module Security
def description
_('Configure Secret Detection in `.gitlab-ci.yml` using the GitLab managed template. You can [add variable overrides](https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings) to customize Secret Detection settings.')
end
+
+ def name
+ 'Secret Detection'
+ end
end
end
end
diff --git a/app/services/service_ping/submit_service.rb b/app/services/service_ping/submit_service.rb
index da2a51562f8..6d39174b6c7 100644
--- a/app/services/service_ping/submit_service.rb
+++ b/app/services/service_ping/submit_service.rb
@@ -42,20 +42,20 @@ module ServicePing
{
metadata: {
uuid: service_ping_payload[:uuid],
- metrics: metrics_collection_time(service_ping_payload)
+ metrics: metrics_collection_metadata(service_ping_payload)
}
}
end
- def metrics_collection_time(payload, parents = [])
+ def metrics_collection_metadata(payload, parents = [])
return [] unless payload.is_a?(Hash)
payload.flat_map do |key, metric_value|
key_path = parents.dup.append(key)
if metric_value.respond_to?(:duration)
- { name: key_path.join('.'), time_elapsed: metric_value.duration }
+ { name: key_path.join('.'), time_elapsed: metric_value.duration, error: metric_value.error }.compact
else
- metrics_collection_time(metric_value, key_path)
+ metrics_collection_metadata(metric_value, key_path)
end
end
end
diff --git a/app/services/service_response.rb b/app/services/service_response.rb
index 848f90e7f25..da4773ab9c7 100644
--- a/app/services/service_response.rb
+++ b/app/services/service_response.rb
@@ -26,22 +26,22 @@ class ServiceResponse
self.reason = reason
end
- def track_exception(as: StandardError, **extra_data)
- if error?
- e = as.new(message)
- Gitlab::ErrorTracking.track_exception(e, extra_data)
+ def log_and_raise_exception(as: StandardError, **extra_data)
+ error_tracking(as) do |ex|
+ Gitlab::ErrorTracking.log_and_raise_exception(ex, extra_data)
end
+ end
- self
+ def track_exception(as: StandardError, **extra_data)
+ error_tracking(as) do |ex|
+ Gitlab::ErrorTracking.track_exception(ex, extra_data)
+ end
end
def track_and_raise_exception(as: StandardError, **extra_data)
- if error?
- e = as.new(message)
- Gitlab::ErrorTracking.track_and_raise_exception(e, extra_data)
+ error_tracking(as) do |ex|
+ Gitlab::ErrorTracking.track_and_raise_exception(ex, extra_data)
end
-
- self
end
def [](key)
@@ -73,4 +73,13 @@ class ServiceResponse
private
attr_writer :status, :message, :http_status, :payload, :reason
+
+ def error_tracking(error_klass)
+ if error?
+ ex = error_klass.new(message)
+ yield ex
+ end
+
+ self
+ end
end
diff --git a/app/services/test_hooks/base_service.rb b/app/services/test_hooks/base_service.rb
index b41a9959c13..3f2949a53ba 100644
--- a/app/services/test_hooks/base_service.rb
+++ b/app/services/test_hooks/base_service.rb
@@ -16,9 +16,16 @@ module TestHooks
trigger_key = hook.class.triggers.key(trigger.to_sym)
return error('Testing not available for this hook') if trigger_key.nil? || data.blank?
+
return error(data[:error]) if data[:error].present?
hook.execute(data, trigger_key, force: true)
+ rescue ArgumentError => e
+ error(e.message)
+ end
+
+ def error(message)
+ ServiceResponse.error(message: message)
end
end
end
diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb
index 9ae31f8ac58..bfd1e55507c 100644
--- a/app/services/todo_service.rb
+++ b/app/services/todo_service.rb
@@ -220,7 +220,7 @@ class TodoService
create_todos(reviewers, attributes, project.namespace, project)
end
- def create_member_access_request(member)
+ def create_member_access_request_todos(member)
source = member.source
attributes = attributes_for_access_request_todos(source, member.user, Todo::MEMBER_ACCESS_REQUESTED)
@@ -433,7 +433,12 @@ class TodoService
note: note
}
- attributes[:group_id] = source.id unless source.instance_of? Project
+ if source.instance_of? Project
+ attributes[:project_id] = source.id
+ attributes[:group_id] = source.group.id if source.group.present?
+ else
+ attributes[:group_id] = source.id
+ end
attributes
end
diff --git a/app/services/users/block_service.rb b/app/services/users/block_service.rb
index 37921c477b4..0715e299e87 100644
--- a/app/services/users/block_service.rb
+++ b/app/services/users/block_service.rb
@@ -20,8 +20,14 @@ module Users
private
+ # overridden by EE module
def after_block_hook(user)
- # overridden by EE module
+ custom_attribute = {
+ user_id: user.id,
+ key: UserCustomAttribute::BLOCKED_BY,
+ value: "#{current_user.username}/#{current_user.id}+#{Time.current}"
+ }
+ UserCustomAttribute.upsert_custom_attributes([custom_attribute])
end
end
end
diff --git a/app/services/users/signup_service.rb b/app/services/users/signup_service.rb
index 1087ae76216..9eb1e75988c 100644
--- a/app/services/users/signup_service.rb
+++ b/app/services/users/signup_service.rb
@@ -12,9 +12,9 @@ module Users
inject_validators
if @user.save
- success
+ ServiceResponse.success
else
- error(@user.errors.full_messages.join('. '))
+ ServiceResponse.error(message: @user.errors.full_messages.join('. '))
end
end
diff --git a/app/services/users/unblock_service.rb b/app/services/users/unblock_service.rb
new file mode 100644
index 00000000000..1302395662f
--- /dev/null
+++ b/app/services/users/unblock_service.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module Users
+ class UnblockService < BaseService
+ def initialize(current_user)
+ @current_user = current_user
+ end
+
+ def execute(user)
+ if user.activate
+ after_unblock_hook(user)
+ ServiceResponse.success(payload: { user: user })
+ else
+ ServiceResponse.error(message: user.errors.full_messages)
+ end
+ end
+
+ private
+
+ def after_unblock_hook(user)
+ custom_attribute = {
+ user_id: user.id,
+ key: UserCustomAttribute::UNBLOCKED_BY,
+ value: "#{current_user.username}/#{current_user.id}+#{Time.current}"
+ }
+ UserCustomAttribute.upsert_custom_attributes([custom_attribute])
+ end
+ end
+end
diff --git a/app/services/work_items/parent_links/create_service.rb b/app/services/work_items/parent_links/create_service.rb
index e7906f1fcdd..288ca152f93 100644
--- a/app/services/work_items/parent_links/create_service.rb
+++ b/app/services/work_items/parent_links/create_service.rb
@@ -46,7 +46,7 @@ module WorkItems
end
def target_issuable_type
- issuable.issue_type == 'issue' ? 'task' : issuable.issue_type
+ 'work item'
end
def issuables_not_found_message