diff options
Diffstat (limited to 'app/services')
27 files changed, 326 insertions, 95 deletions
diff --git a/app/services/base_count_service.rb b/app/services/base_count_service.rb index 99cc9a196e6..f2844854112 100644 --- a/app/services/base_count_service.rb +++ b/app/services/base_count_service.rb @@ -9,11 +9,15 @@ class BaseCountService end def count - Rails.cache.fetch(cache_key, raw: raw?) { uncached_count }.to_i + Rails.cache.fetch(cache_key, cache_options) { uncached_count }.to_i end - def refresh_cache - Rails.cache.write(cache_key, uncached_count, raw: raw?) + def count_stored? + Rails.cache.read(cache_key).present? + end + + def refresh_cache(&block) + Rails.cache.write(cache_key, block_given? ? yield : uncached_count, raw: raw?) end def uncached_count @@ -31,4 +35,10 @@ class BaseCountService def cache_key raise NotImplementedError, 'cache_key must be implemented and return a String' end + + # subclasses can override to add any specific options, such as + # super.merge({ expires_in: 5.minutes }) + def cache_options + { raw: raw? } + end end diff --git a/app/services/boards/issues/list_service.rb b/app/services/boards/issues/list_service.rb index d85d93e251b..6078fe38064 100644 --- a/app/services/boards/issues/list_service.rb +++ b/app/services/boards/issues/list_service.rb @@ -54,10 +54,11 @@ module Boards def without_board_labels(issues) return issues unless board_label_ids.any? - issues.where.not( - LabelLink.where("label_links.target_type = 'Issue' AND label_links.target_id = issues.id") - .where(label_id: board_label_ids).limit(1).arel.exists - ) + issues.where.not(issues_label_links.limit(1).arel.exists) + end + + def issues_label_links + LabelLink.where("label_links.target_type = 'Issue' AND label_links.target_id = issues.id").where(label_id: board_label_ids) end def with_list_label(issues) diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb index 31a712ccc1b..c8b112132b3 100644 --- a/app/services/ci/create_pipeline_service.rb +++ b/app/services/ci/create_pipeline_service.rb @@ -2,37 +2,35 @@ module Ci class CreatePipelineService < BaseService attr_reader :pipeline - SEQUENCE = [Gitlab::Ci::Pipeline::Chain::Validate::Abilities, + SEQUENCE = [Gitlab::Ci::Pipeline::Chain::Build, + Gitlab::Ci::Pipeline::Chain::Validate::Abilities, Gitlab::Ci::Pipeline::Chain::Validate::Repository, Gitlab::Ci::Pipeline::Chain::Validate::Config, Gitlab::Ci::Pipeline::Chain::Skip, Gitlab::Ci::Pipeline::Chain::Create].freeze def execute(source, ignore_skip_ci: false, save_on_errors: true, trigger_request: nil, schedule: nil, &block) - @pipeline = Ci::Pipeline.new( + @pipeline = Ci::Pipeline.new + + command = Gitlab::Ci::Pipeline::Chain::Command.new( source: source, + origin_ref: params[:ref], + checkout_sha: params[:checkout_sha], + after_sha: params[:after], + before_sha: params[:before], + trigger_request: trigger_request, + schedule: schedule, + ignore_skip_ci: ignore_skip_ci, + save_incompleted: save_on_errors, + seeds_block: block, project: project, - ref: ref, - sha: sha, - before_sha: before_sha, - tag: tag_exists?, - trigger_requests: Array(trigger_request), - user: current_user, - pipeline_schedule: schedule, - protected: project.protected_for?(ref) - ) - - command = OpenStruct.new(ignore_skip_ci: ignore_skip_ci, - save_incompleted: save_on_errors, - seeds_block: block, - project: project, - current_user: current_user) + current_user: current_user) sequence = Gitlab::Ci::Pipeline::Chain::Sequence .new(pipeline, command, SEQUENCE) sequence.build! do |pipeline, sequence| - update_merge_requests_head_pipeline if pipeline.persisted? + schedule_head_pipeline_update if sequence.complete? cancel_pending_pipelines if project.auto_cancel_pending_pipelines? @@ -41,6 +39,8 @@ module Ci pipeline.process! end end + + pipeline end private @@ -53,13 +53,6 @@ module Ci commit.try(:id) end - def update_merge_requests_head_pipeline - return unless pipeline.latest? - - MergeRequest.where(source_project: @pipeline.project, source_branch: @pipeline.ref) - .update_all(head_pipeline_id: @pipeline.id) - end - def cancel_pending_pipelines Gitlab::OptimisticLocking.retry_lock(auto_cancelable_pipelines) do |cancelables| cancelables.find_each do |cancelable| @@ -76,29 +69,19 @@ module Ci .created_or_pending end - def before_sha - params[:checkout_sha] || params[:before] || Gitlab::Git::BLANK_SHA - end - - def origin_sha - params[:checkout_sha] || params[:after] - end - - def origin_ref - params[:ref] - end - - def tag_exists? - project.repository.tag_exists?(ref) + def pipeline_created_counter + @pipeline_created_counter ||= Gitlab::Metrics + .counter(:pipelines_created_total, "Counter of pipelines created") end - def ref - @ref ||= Gitlab::Git.ref_name(origin_ref) + def schedule_head_pipeline_update + related_merge_requests.each do |merge_request| + UpdateHeadPipelineForMergeRequestWorker.perform_async(merge_request.id) + end end - def pipeline_created_counter - @pipeline_created_counter ||= Gitlab::Metrics - .counter(:pipelines_created_total, "Counter of pipelines created") + def related_merge_requests + MergeRequest.opened.where(source_project: pipeline.project, source_branch: pipeline.ref) end end end diff --git a/app/services/ci/register_job_service.rb b/app/services/ci/register_job_service.rb index b8db709211a..f832b79ef21 100644 --- a/app/services/ci/register_job_service.rb +++ b/app/services/ci/register_job_service.rb @@ -22,17 +22,31 @@ module Ci valid = true + if Feature.enabled?('ci_job_request_with_tags_matcher') + # pick builds that does not have other tags than runner's one + builds = builds.matches_tag_ids(runner.tags.ids) + + # pick builds that have at least one tag + unless runner.run_untagged? + builds = builds.with_any_tags + end + end + builds.find do |build| next unless runner.can_pick?(build) begin # In case when 2 runners try to assign the same build, second runner will be declined # with StateMachines::InvalidTransition or StaleObjectError when doing run! or save method. - build.runner_id = runner.id - build.run! - register_success(build) - - return Result.new(build, true) + begin + build.runner_id = runner.id + build.run! + register_success(build) + + return Result.new(build, true) + rescue Ci::Build::MissingDependenciesError + build.drop!(:missing_dependency_failure) + end rescue StateMachines::InvalidTransition, ActiveRecord::StaleObjectError # We are looping to find another build that is not conflicting # It also indicates that this build can be picked and passed to runner. diff --git a/app/services/clusters/create_service.rb b/app/services/clusters/create_service.rb index 1d407739b21..0471b0f17a2 100644 --- a/app/services/clusters/create_service.rb +++ b/app/services/clusters/create_service.rb @@ -2,9 +2,11 @@ module Clusters class CreateService < BaseService attr_reader :access_token - def execute(access_token) + def execute(access_token = nil) @access_token = access_token + raise ArgumentError.new('Instance does not support multiple clusters') unless can_create_cluster? + create_cluster.tap do |cluster| ClusterProvisionWorker.perform_async(cluster.id) if cluster.persisted? end @@ -25,5 +27,9 @@ module Clusters @cluster_params = params.merge(user: current_user, projects: [project]) end + + def can_create_cluster? + project.clusters.empty? + end end end diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb index 2c51ac13815..e7463e6e25c 100644 --- a/app/services/issuable_base_service.rb +++ b/app/services/issuable_base_service.rb @@ -106,12 +106,14 @@ class IssuableBaseService < BaseService end def merge_quick_actions_into_params!(issuable) + original_description = params.fetch(:description, issuable.description) + description, command_params = QuickActions::InterpretService.new(project, current_user) - .execute(params[:description], issuable) + .execute(original_description, issuable) # Avoid a description already set on an issuable to be overwritten by a nil - params[:description] = description if params.key?(:description) + params[:description] = description if description params.merge!(command_params) end diff --git a/app/services/members/approve_access_request_service.rb b/app/services/members/approve_access_request_service.rb index c13f289f61e..2a2bb0cae5b 100644 --- a/app/services/members/approve_access_request_service.rb +++ b/app/services/members/approve_access_request_service.rb @@ -35,8 +35,17 @@ module Members def can_update_access_requester?(access_requester, opts = {}) access_requester && ( opts[:force] || - can?(current_user, action_member_permission(:update, access_requester), access_requester) + can?(current_user, update_member_permission(access_requester), access_requester) ) end + + def update_member_permission(member) + case member + when GroupMember + :update_group_member + when ProjectMember + :update_project_member + end + end end end diff --git a/app/services/members/destroy_service.rb b/app/services/members/destroy_service.rb index 46c505baf8b..05b93ac8fdb 100644 --- a/app/services/members/destroy_service.rb +++ b/app/services/members/destroy_service.rb @@ -36,7 +36,16 @@ module Members end def can_destroy_member?(member) - member && can?(current_user, action_member_permission(:destroy, member), member) + member && can?(current_user, destroy_member_permission(member), member) + end + + def destroy_member_permission(member) + case member + when GroupMember + :destroy_group_member + when ProjectMember + :destroy_project_member + end end end end diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb index c2fb01466df..9622a5c5462 100644 --- a/app/services/merge_requests/build_service.rb +++ b/app/services/merge_requests/build_service.rb @@ -10,8 +10,12 @@ module MergeRequests merge_request.target_branch = find_target_branch merge_request.can_be_created = branches_valid? - compare_branches if branches_present? - assign_title_and_description if merge_request.can_be_created + # compare branches only if branches are valid, otherwise + # compare_branches may raise an error + if merge_request.can_be_created + compare_branches + assign_title_and_description + end merge_request end diff --git a/app/services/merge_requests/create_service.rb b/app/services/merge_requests/create_service.rb index 820709583fa..49cf534dc0d 100644 --- a/app/services/merge_requests/create_service.rb +++ b/app/services/merge_requests/create_service.rb @@ -35,6 +35,12 @@ module MergeRequests super end + # expose issuable create method so it can be called from email + # handler CreateMergeRequestHandler + def create(merge_request) + super + end + private def update_merge_requests_head_pipeline(merge_request) diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb index bf3d4855122..9f05535d4d4 100644 --- a/app/services/merge_requests/refresh_service.rb +++ b/app/services/merge_requests/refresh_service.rb @@ -6,7 +6,7 @@ module MergeRequests @oldrev, @newrev = oldrev, newrev @branch_name = Gitlab::Git.ref_name(ref) - find_new_commits + Gitlab::GitalyClient.allow_n_plus_1_calls(&method(:find_new_commits)) # Be sure to close outstanding MRs before reloading them to avoid generating an # empty diff during a manual merge close_merge_requests @@ -76,6 +76,7 @@ module MergeRequests end merge_request.mark_as_unchecked + UpdateHeadPipelineForMergeRequestWorker.perform_async(merge_request.id) end end diff --git a/app/services/metrics_service.rb b/app/services/metrics_service.rb index 6b3939aeba5..236e9fe8c44 100644 --- a/app/services/metrics_service.rb +++ b/app/services/metrics_service.rb @@ -20,7 +20,7 @@ class MetricsService end def metrics_text - "#{health_metrics_text}#{prometheus_metrics_text}" + prometheus_metrics_text.concat(health_metrics_text) end private diff --git a/app/services/notification_recipient_service.rb b/app/services/notification_recipient_service.rb index c9f07c140f7..3eb8cfcca9b 100644 --- a/app/services/notification_recipient_service.rb +++ b/app/services/notification_recipient_service.rb @@ -98,6 +98,12 @@ module NotificationRecipientService self << [target.participants(user), :participating] end + def add_mentions(user, target:) + return unless target.respond_to?(:mentioned_users) + + self << [target.mentioned_users(user), :mention] + end + # Get project/group users with CUSTOM notification level def add_custom_notifications user_ids = [] @@ -227,6 +233,11 @@ module NotificationRecipientService add_subscribed_users if [:new_issue, :new_merge_request].include?(custom_action) + # These will all be participants as well, but adding with the :mention + # type ensures that users with the mention notification level will + # receive them, too. + add_mentions(current_user, target: target) + add_labels_subscribers end end @@ -263,7 +274,7 @@ module NotificationRecipientService def build! # Add all users participating in the thread (author, assignee, comment authors) add_participants(note.author) - self << [note.mentioned_users, :mention] + add_mentions(note.author, target: note) unless note.for_personal_snippet? # Merge project watchers diff --git a/app/services/projects/autocomplete_service.rb b/app/services/projects/autocomplete_service.rb index 724a77c873a..1ae2c40872a 100644 --- a/app/services/projects/autocomplete_service.rb +++ b/app/services/projects/autocomplete_service.rb @@ -20,8 +20,23 @@ module Projects MergeRequestsFinder.new(current_user, project_id: project.id, state: 'opened').execute.select([:iid, :title]) end - def labels - LabelsFinder.new(current_user, project_id: project.id).execute.select([:title, :color]) + def labels(target = nil) + labels = LabelsFinder.new(current_user, project_id: project.id).execute.select([:color, :title]) + + return labels unless target&.respond_to?(:labels) + + issuable_label_titles = target.labels.pluck(:title) + + if issuable_label_titles + labels = labels.as_json(only: [:title, :color]) + + issuable_label_titles.each do |issuable_label_title| + found_label = labels.find { |label| label['title'] == issuable_label_title } + found_label[:set] = true if found_label + end + end + + labels end def commands(noteable, type) @@ -33,7 +48,7 @@ module Projects @project.merge_requests.build end - return [] unless noteable && noteable.is_a?(Issuable) + return [] unless noteable&.is_a?(Issuable) opts = { project: project, diff --git a/app/services/projects/batch_count_service.rb b/app/services/projects/batch_count_service.rb new file mode 100644 index 00000000000..178ebc5a143 --- /dev/null +++ b/app/services/projects/batch_count_service.rb @@ -0,0 +1,31 @@ +# Service class for getting and caching the number of elements of several projects +# Warning: do not user this service with a really large set of projects +# because the service use maps to retrieve the project ids. +module Projects + class BatchCountService + def initialize(projects) + @projects = projects + end + + def refresh_cache + @projects.each do |project| + service = count_service.new(project) + unless service.count_stored? + service.refresh_cache { global_count[project.id].to_i } + end + end + end + + def project_ids + @projects.map(&:id) + end + + def global_count(project) + raise NotImplementedError, 'global_count must be implemented and return an hash indexed by the project id' + end + + def count_service + raise NotImplementedError, 'count_service must be implemented and return a Projects::CountService object' + end + end +end diff --git a/app/services/projects/batch_forks_count_service.rb b/app/services/projects/batch_forks_count_service.rb new file mode 100644 index 00000000000..e61fe6c86b2 --- /dev/null +++ b/app/services/projects/batch_forks_count_service.rb @@ -0,0 +1,18 @@ +# Service class for getting and caching the number of forks of several projects +# Warning: do not user this service with a really large set of projects +# because the service use maps to retrieve the project ids +module Projects + class BatchForksCountService < Projects::BatchCountService + def global_count + @global_count ||= begin + count_service.query(project_ids) + .group(:forked_from_project_id) + .count + end + end + + def count_service + ::Projects::ForksCountService + end + end +end diff --git a/app/services/projects/batch_open_issues_count_service.rb b/app/services/projects/batch_open_issues_count_service.rb new file mode 100644 index 00000000000..3b0ade2419b --- /dev/null +++ b/app/services/projects/batch_open_issues_count_service.rb @@ -0,0 +1,16 @@ +# Service class for getting and caching the number of issues of several projects +# Warning: do not user this service with a really large set of projects +# because the service use maps to retrieve the project ids +module Projects + class BatchOpenIssuesCountService < Projects::BatchCountService + def global_count + @global_count ||= begin + count_service.query(project_ids).group(:project_id).count + end + end + + def count_service + ::Projects::OpenIssuesCountService + end + end +end diff --git a/app/services/projects/count_service.rb b/app/services/projects/count_service.rb index 7e575b2d6f3..933829b557b 100644 --- a/app/services/projects/count_service.rb +++ b/app/services/projects/count_service.rb @@ -11,6 +11,10 @@ module Projects @project = project end + def relation_for_count + self.class.query(@project.id) + end + def cache_key_name raise( NotImplementedError, @@ -21,5 +25,12 @@ module Projects def cache_key ['projects', 'count_service', VERSION, @project.id, cache_key_name] end + + def self.query(project_ids) + raise( + NotImplementedError, + '"query" must be implemented and return an ActiveRecord::Relation' + ) + end end end diff --git a/app/services/projects/fork_service.rb b/app/services/projects/fork_service.rb index eb5cce5ab98..03be7039b2a 100644 --- a/app/services/projects/fork_service.rb +++ b/app/services/projects/fork_service.rb @@ -1,6 +1,24 @@ module Projects class ForkService < BaseService - def execute + def execute(fork_to_project = nil) + if fork_to_project + link_existing_project(fork_to_project) + else + fork_new_project + end + end + + private + + def link_existing_project(fork_to_project) + return if fork_to_project.forked? + + link_fork_network(fork_to_project) + + fork_to_project + end + + def fork_new_project new_params = { forked_from_project_id: @project.id, visibility_level: allowed_visibility_level, @@ -21,15 +39,11 @@ module Projects builds_access_level = @project.project_feature.builds_access_level new_project.project_feature.update_attributes(builds_access_level: builds_access_level) - refresh_forks_count - link_fork_network(new_project) new_project end - private - def fork_network if @project.fork_network @project.fork_network @@ -43,9 +57,17 @@ module Projects end end - def link_fork_network(new_project) - fork_network.fork_network_members.create(project: new_project, + def link_fork_network(fork_to_project) + fork_network.fork_network_members.create(project: fork_to_project, forked_from_project: @project) + + # TODO: remove this when ForkedProjectLink model is removed + unless fork_to_project.forked_project_link + fork_to_project.create_forked_project_link(forked_to_project: fork_to_project, + forked_from_project: @project) + end + + refresh_forks_count end def refresh_forks_count diff --git a/app/services/projects/forks_count_service.rb b/app/services/projects/forks_count_service.rb index d9bdf3a8ad7..dc6eb19affd 100644 --- a/app/services/projects/forks_count_service.rb +++ b/app/services/projects/forks_count_service.rb @@ -1,12 +1,15 @@ module Projects # Service class for getting and caching the number of forks of a project. class ForksCountService < Projects::CountService - def relation_for_count - @project.forks - end - def cache_key_name 'forks_count' end + + def self.query(project_ids) + # We can't directly change ForkedProjectLink to ForkNetworkMember here + # Nowadays, when a call using v3 to projects/:id/fork is made, + # the relationship to ForkNetworkMember is not updated + ForkedProjectLink.where(forked_from_project: project_ids) + end end end diff --git a/app/services/projects/open_issues_count_service.rb b/app/services/projects/open_issues_count_service.rb index 25de97325e2..a975a06a05c 100644 --- a/app/services/projects/open_issues_count_service.rb +++ b/app/services/projects/open_issues_count_service.rb @@ -2,14 +2,14 @@ module Projects # Service class for counting and caching the number of open issues of a # project. class OpenIssuesCountService < Projects::CountService - def relation_for_count - # We don't include confidential issues in this number since this would - # expose the number of confidential issues to non project members. - @project.issues.opened.public_only - end - def cache_key_name 'open_issues_count' end + + def self.query(project_ids) + # We don't include confidential issues in this number since this would + # expose the number of confidential issues to non project members. + Issue.opened.public_only.where(project: project_ids) + end end end diff --git a/app/services/projects/update_pages_service.rb b/app/services/projects/update_pages_service.rb index d34903c9989..a773222bf17 100644 --- a/app/services/projects/update_pages_service.rb +++ b/app/services/projects/update_pages_service.rb @@ -18,7 +18,7 @@ module Projects @status.enqueue! @status.run! - raise 'missing pages artifacts' unless build.artifacts_file? + raise 'missing pages artifacts' unless build.artifacts? raise 'pages are outdated' unless latest? # Create temporary directory in which we will extract the artifacts diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb index 72eecc61c96..ff4c73c886e 100644 --- a/app/services/projects/update_service.rb +++ b/app/services/projects/update_service.rb @@ -15,7 +15,7 @@ module Projects return error("Could not set the default branch") unless project.change_head(params[:default_branch]) end - if project.update_attributes(update_params) + if project.update_attributes(params.except(:default_branch)) if project.previous_changes.include?('path') project.rename_repo else @@ -32,15 +32,13 @@ module Projects end def run_auto_devops_pipeline? - params.dig(:run_auto_devops_pipeline_explicit) == 'true' || params.dig(:run_auto_devops_pipeline_implicit) == 'true' + return false if project.repository.gitlab_ci_yml || !project.auto_devops.previous_changes.include?('enabled') + + project.auto_devops.enabled? || (project.auto_devops.enabled.nil? && current_application_settings.auto_devops_enabled?) end private - def update_params - params.except(:default_branch, :run_auto_devops_pipeline_explicit, :run_auto_devops_pipeline_implicit) - end - def renaming_project_with_container_registry_tags? new_path = params[:path] diff --git a/app/services/protected_branches/access_level_params.rb b/app/services/protected_branches/access_level_params.rb new file mode 100644 index 00000000000..253ae8b0124 --- /dev/null +++ b/app/services/protected_branches/access_level_params.rb @@ -0,0 +1,33 @@ +module ProtectedBranches + class AccessLevelParams + attr_reader :type, :params + + def initialize(type, params) + @type = type + @params = params_with_default(params) + end + + def access_levels + ce_style_access_level + end + + private + + def params_with_default(params) + params[:"#{type}_access_level"] ||= Gitlab::Access::MASTER if use_default_access_level?(params) + params + end + + def use_default_access_level?(params) + true + end + + def ce_style_access_level + access_level = params[:"#{type}_access_level"] + + return [] unless access_level + + [{ access_level: access_level }] + end + end +end diff --git a/app/services/protected_branches/api_service.rb b/app/services/protected_branches/api_service.rb new file mode 100644 index 00000000000..4b40200644b --- /dev/null +++ b/app/services/protected_branches/api_service.rb @@ -0,0 +1,24 @@ +module ProtectedBranches + class ApiService < BaseService + def create + @push_params = AccessLevelParams.new(:push, params) + @merge_params = AccessLevelParams.new(:merge, params) + + verify_params! + + protected_branch_params = { + name: params[:name], + push_access_levels_attributes: @push_params.access_levels, + merge_access_levels_attributes: @merge_params.access_levels + } + + ::ProtectedBranches::CreateService.new(@project, @current_user, protected_branch_params).execute + end + + private + + def verify_params! + # EE-only + end + end +end diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb index 911cc919bb8..690918b4a00 100644 --- a/app/services/system_hooks_service.rb +++ b/app/services/system_hooks_service.rb @@ -1,6 +1,10 @@ class SystemHooksService def execute_hooks_for(model, event) - execute_hooks(build_event_data(model, event)) + data = build_event_data(model, event) + + model.run_after_commit_or_now do + SystemHooksService.new.execute_hooks(data) + end end def execute_hooks(data, hooks_scope = :all) diff --git a/app/services/web_hook_service.rb b/app/services/web_hook_service.rb index cd99e0b90f9..6ebc7c89500 100644 --- a/app/services/web_hook_service.rb +++ b/app/services/web_hook_service.rb @@ -63,7 +63,7 @@ class WebHookService end def async_execute - Sidekiq::Client.enqueue(WebHookWorker, hook.id, data, hook_name) + WebHookWorker.perform_async(hook.id, data, hook_name) end private |