diff options
Diffstat (limited to 'app/services')
18 files changed, 217 insertions, 50 deletions
diff --git a/app/services/application_settings/update_service.rb b/app/services/application_settings/update_service.rb index 19cf34e2ac4..2e4643ed668 100644 --- a/app/services/application_settings/update_service.rb +++ b/app/services/application_settings/update_service.rb @@ -11,11 +11,19 @@ module ApplicationSettings params[:performance_bar_allowed_group_id] = performance_bar_allowed_group_id end + if usage_stats_updated? && !params.delete(:skip_usage_stats_user) + params[:usage_stats_set_by_user_id] = current_user.id + end + @application_setting.update(@params) end private + def usage_stats_updated? + params.key?(:usage_ping_enabled) || params.key?(:version_check_enabled) + end + def update_terms(terms) return unless terms.present? diff --git a/app/services/emails/base_service.rb b/app/services/emails/base_service.rb index ba7b689a9af..988215ffc78 100644 --- a/app/services/emails/base_service.rb +++ b/app/services/emails/base_service.rb @@ -2,6 +2,8 @@ module Emails class BaseService + attr_reader :current_user + def initialize(current_user, params = {}) @current_user, @params = current_user, params.dup @user = params.delete(:user) diff --git a/app/services/emails/create_service.rb b/app/services/emails/create_service.rb index acf575e24e5..56925a724fe 100644 --- a/app/services/emails/create_service.rb +++ b/app/services/emails/create_service.rb @@ -3,7 +3,12 @@ module Emails class CreateService < ::Emails::BaseService def execute(extra_params = {}) - @user.emails.create(@params.merge(extra_params)) + skip_confirmation = @params.delete(:skip_confirmation) + + email = @user.emails.create(@params.merge(extra_params)) + + email&.confirm if skip_confirmation && current_user.admin? + email end end end diff --git a/app/services/issuable/common_system_notes_service.rb b/app/services/issuable/common_system_notes_service.rb index 028b350ca07..765de9c66b0 100644 --- a/app/services/issuable/common_system_notes_service.rb +++ b/app/services/issuable/common_system_notes_service.rb @@ -17,6 +17,7 @@ module Issuable create_labels_note(old_labels) if issuable.labels != old_labels create_discussion_lock_note if issuable.previous_changes.include?('discussion_locked') create_milestone_note if issuable.previous_changes.include?('milestone_id') + create_due_date_note if issuable.previous_changes.include?('due_date') end private @@ -55,7 +56,9 @@ module Issuable added_labels = issuable.labels - old_labels removed_labels = old_labels - issuable.labels - SystemNoteService.change_label(issuable, issuable.project, current_user, added_labels, removed_labels) + ResourceEvents::ChangeLabelsService + .new(issuable, current_user) + .execute(added_labels: added_labels, removed_labels: removed_labels) end def create_title_change_note(old_title) @@ -88,6 +91,10 @@ module Issuable SystemNoteService.change_milestone(issuable, issuable.project, current_user, issuable.milestone) end + def create_due_date_note + SystemNoteService.change_due_date(issuable, issuable.project, current_user, issuable.due_date) + end + def create_discussion_lock_note SystemNoteService.discussion_lock(issuable, current_user) end diff --git a/app/services/issues/move_service.rb b/app/services/issues/move_service.rb index 841bce9949e..ec9d8944e4e 100644 --- a/app/services/issues/move_service.rb +++ b/app/services/issues/move_service.rb @@ -36,6 +36,7 @@ module Issues def update_new_issue rewrite_notes + copy_resource_label_events rewrite_issue_award_emoji add_note_moved_from end @@ -96,6 +97,18 @@ module Issues end end + def copy_resource_label_events + @old_issue.resource_label_events.find_in_batches do |batch| + events = batch.map do |event| + event.attributes + .except('id', 'reference', 'reference_html') + .merge('issue_id' => @new_issue.id, 'action' => ResourceLabelEvent.actions[event.action]) + end + + Gitlab::Database.bulk_insert(ResourceLabelEvent.table_name, events) + end + end + def rewrite_issue_award_emoji rewrite_award_emoji(@old_issue, @new_issue) end diff --git a/app/services/labels/promote_service.rb b/app/services/labels/promote_service.rb index 623a5f0950e..fcdcea2d0ea 100644 --- a/app/services/labels/promote_service.rb +++ b/app/services/labels/promote_service.rb @@ -13,6 +13,7 @@ module Labels label_ids_for_merge(new_label).find_in_batches(batch_size: BATCH_SIZE) do |batched_ids| update_issuables(new_label, batched_ids) + update_resource_label_events(new_label, batched_ids) update_issue_board_lists(new_label, batched_ids) update_priorities(new_label, batched_ids) subscribe_users(new_label, batched_ids) @@ -52,6 +53,12 @@ module Labels .update_all(label_id: new_label) end + def update_resource_label_events(new_label, label_ids) + ResourceLabelEvent + .where(label: label_ids) + .update_all(label_id: new_label) + end + def update_issue_board_lists(new_label, label_ids) List .where(label: label_ids) diff --git a/app/services/merge_requests/reload_diffs_service.rb b/app/services/merge_requests/reload_diffs_service.rb index 8d85dc9eb5f..1390ae0e199 100644 --- a/app/services/merge_requests/reload_diffs_service.rb +++ b/app/services/merge_requests/reload_diffs_service.rb @@ -30,7 +30,7 @@ module MergeRequests def clear_cache(new_diff) # Executing the iteration we cache highlighted diffs for each diff file of # MergeRequestDiff. - new_diff.diffs_collection.diff_files.to_a + new_diff.diffs_collection.write_cache # Remove cache for all diffs on this MR. Do not use the association on the # model, as that will interfere with other actions happening when @@ -38,7 +38,7 @@ module MergeRequests MergeRequestDiff.where(merge_request: merge_request).each do |merge_request_diff| next if merge_request_diff == new_diff - merge_request_diff.diffs_collection.clear_cache! + merge_request_diff.diffs_collection.clear_cache end end end diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index 4511c500fca..50fa373025b 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -407,6 +407,12 @@ class NotificationService end end + def autodevops_disabled(pipeline, recipients) + recipients.each do |recipient| + mailer.autodevops_disabled_email(pipeline, recipient).deliver_later + end + end + def pages_domain_verification_succeeded(domain) recipients_for_pages_domain(domain).each do |user| mailer.pages_domain_verification_succeeded_email(domain, user).deliver_later diff --git a/app/services/projects/auto_devops/disable_service.rb b/app/services/projects/auto_devops/disable_service.rb new file mode 100644 index 00000000000..9745ab67dbd --- /dev/null +++ b/app/services/projects/auto_devops/disable_service.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module Projects + module AutoDevops + class DisableService < BaseService + def execute + return false unless implicitly_enabled_and_first_pipeline_failure? + + disable_auto_devops + end + + private + + def implicitly_enabled_and_first_pipeline_failure? + project.has_auto_devops_implicitly_enabled? && + first_pipeline_failure? + end + + # We're using `limit` to optimize `auto_devops pipeline` query, + # since we only care about the first element, and using only `.count` + # is an expensive operation. See + # https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/21172#note_99037378 + # for more context. + def first_pipeline_failure? + auto_devops_pipelines.success.limit(1).count.zero? && + auto_devops_pipelines.failed.limit(1).count.nonzero? + end + + def disable_auto_devops + project.auto_devops_attributes = { enabled: false } + project.save! + end + + def auto_devops_pipelines + @auto_devops_pipelines ||= project.pipelines.auto_devops_source + end + end + end +end diff --git a/app/services/projects/container_repository/destroy_service.rb b/app/services/projects/container_repository/destroy_service.rb new file mode 100644 index 00000000000..a8e7eab6068 --- /dev/null +++ b/app/services/projects/container_repository/destroy_service.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module Projects + module ContainerRepository + class DestroyService < BaseService + def execute(container_repository) + return false unless can?(current_user, :update_container_image, project) + + container_repository.destroy + end + end + end +end diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb index 76e22507698..01de6afcd8e 100644 --- a/app/services/projects/destroy_service.rb +++ b/app/services/projects/destroy_service.rb @@ -159,7 +159,7 @@ module Projects def remove_legacy_registry_tags return true unless Gitlab.config.registry.enabled - ContainerRepository.build_root_repository(project).tap do |repository| + ::ContainerRepository.build_root_repository(project).tap do |repository| break repository.has_tags? ? repository.delete_tags! : true end end diff --git a/app/services/projects/update_remote_mirror_service.rb b/app/services/projects/update_remote_mirror_service.rb index 591b38b8151..85b9eb02803 100644 --- a/app/services/projects/update_remote_mirror_service.rb +++ b/app/services/projects/update_remote_mirror_service.rb @@ -5,13 +5,14 @@ module Projects attr_reader :errors def execute(remote_mirror) - @errors = [] - return success unless remote_mirror.enabled? + errors = [] + begin remote_mirror.ensure_remote! repository.fetch_remote(remote_mirror.remote_name, no_tags: true) + project.update_root_ref(remote_mirror.remote_name) opts = {} if remote_mirror.only_protected_branches? diff --git a/app/services/quick_actions/interpret_service.rb b/app/services/quick_actions/interpret_service.rb index a4c4c9e4812..be9d1e48435 100644 --- a/app/services/quick_actions/interpret_service.rb +++ b/app/services/quick_actions/interpret_service.rb @@ -489,6 +489,30 @@ module QuickActions "#{comment} #{TABLEFLIP}" end + desc "Lock the discussion" + explanation "Locks the discussion" + condition do + issuable.is_a?(Issuable) && + issuable.persisted? && + !issuable.discussion_locked? && + current_user.can?(:"admin_#{issuable.to_ability_name}", issuable) + end + command :lock do + @updates[:discussion_locked] = true + end + + desc "Unlock the discussion" + explanation "Unlocks the discussion" + condition do + issuable.is_a?(Issuable) && + issuable.persisted? && + issuable.discussion_locked? && + current_user.can?(:"admin_#{issuable.to_ability_name}", issuable) + end + command :unlock do + @updates[:discussion_locked] = false + end + # This is a dummy command, so that it appears in the autocomplete commands desc 'CC' params '@user' diff --git a/app/services/resource_events/change_labels_service.rb b/app/services/resource_events/change_labels_service.rb index 8edb0ddb3ed..039d6e2ebad 100644 --- a/app/services/resource_events/change_labels_service.rb +++ b/app/services/resource_events/change_labels_service.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -# This service is not used yet, it will be used for: -# https://gitlab.com/gitlab-org/gitlab-ce/issues/48483 module ResourceEvents class ChangeLabelsService attr_reader :resource, :user @@ -25,6 +23,7 @@ module ResourceEvents end Gitlab::Database.bulk_insert(ResourceLabelEvent.table_name, labels) + resource.expire_note_etag_cache end private diff --git a/app/services/resource_events/merge_into_notes_service.rb b/app/services/resource_events/merge_into_notes_service.rb new file mode 100644 index 00000000000..1b02a1602e2 --- /dev/null +++ b/app/services/resource_events/merge_into_notes_service.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +# We store events about issuable label changes in a separate table (not as +# other system notes), but we still want to display notes about label changes +# as classic system notes in UI. This service generates "synthetic" notes for +# label event changes and merges them with classic notes and sorts them by +# creation time. + +module ResourceEvents + class MergeIntoNotesService + include Gitlab::Utils::StrongMemoize + + attr_reader :resource, :current_user, :params + + def initialize(resource, current_user, params = {}) + @resource = resource + @current_user = current_user + @params = params + end + + def execute(notes = []) + (notes + label_notes).sort_by { |n| n.created_at } + end + + private + + def label_notes + label_events_by_discussion_id.map do |discussion_id, events| + LabelNote.from_events(events, resource: resource, resource_parent: resource_parent) + end + end + + def label_events_by_discussion_id + return [] unless resource.respond_to?(:resource_label_events) + + events = resource.resource_label_events.includes(:label, :user) + events = since_fetch_at(events) + + events.group_by { |event| event.discussion_id } + end + + def since_fetch_at(events) + return events unless params[:last_fetched_at].present? + + last_fetched_at = Time.at(params.fetch(:last_fetched_at).to_i) + events.created_after(last_fetched_at - NotesFinder::FETCH_OVERLAP) + end + + def resource_parent + strong_memoize(:resource_parent) do + resource.project || resource.group + end + end + end +end diff --git a/app/services/submit_usage_ping_service.rb b/app/services/submit_usage_ping_service.rb index 93c2e222963..62222d3fd2a 100644 --- a/app/services/submit_usage_ping_service.rb +++ b/app/services/submit_usage_ping_service.rb @@ -15,6 +15,7 @@ class SubmitUsagePingService def execute return false unless Gitlab::CurrentSettings.usage_ping_enabled? + return false if User.single_user&.requires_usage_stats_consent? response = Gitlab::HTTP.post( URL, diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index dda89830179..c5d05992575 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -98,66 +98,45 @@ module SystemNoteService create_note(NoteSummary.new(issue, project, author, body, action: 'assignee')) end - # Called when one or more labels on a Noteable are added and/or removed + # Called when the milestone of a Noteable is changed # - # noteable - Noteable object - # project - Project owning noteable - # author - User performing the change - # added_labels - Array of Labels added - # removed_labels - Array of Labels removed + # noteable - Noteable object + # project - Project owning noteable + # author - User performing the change + # milestone - Milestone being assigned, or nil # # Example Note text: # - # "added ~1 and removed ~2 ~3 labels" - # - # "added ~4 label" + # "removed milestone" # - # "removed ~5 label" + # "changed milestone to 7.11" # # Returns the created Note object - def change_label(noteable, project, author, added_labels, removed_labels) - labels_count = added_labels.count + removed_labels.count - - references = ->(label) { label.to_reference(format: :id) } - added_labels = added_labels.map(&references).join(' ') - removed_labels = removed_labels.map(&references).join(' ') - - text_parts = [] - - if added_labels.present? - text_parts << "added #{added_labels}" - text_parts << 'and' if removed_labels.present? - end - - if removed_labels.present? - text_parts << "removed #{removed_labels}" - end - - text_parts << 'label'.pluralize(labels_count) - body = text_parts.join(' ') + def change_milestone(noteable, project, author, milestone) + format = milestone&.group_milestone? ? :name : :iid + body = milestone.nil? ? 'removed milestone' : "changed milestone to #{milestone.to_reference(project, format: format)}" - create_note(NoteSummary.new(noteable, project, author, body, action: 'label')) + create_note(NoteSummary.new(noteable, project, author, body, action: 'milestone')) end - # Called when the milestone of a Noteable is changed + # Called when the due_date of a Noteable is changed # # noteable - Noteable object # project - Project owning noteable # author - User performing the change - # milestone - Milestone being assigned, or nil + # due_date - Due date being assigned, or nil # # Example Note text: # - # "removed milestone" + # "removed due date" # - # "changed milestone to 7.11" + # "changed due date to September 20, 2018" # # Returns the created Note object - def change_milestone(noteable, project, author, milestone) - format = milestone&.group_milestone? ? :name : :iid - body = milestone.nil? ? 'removed milestone' : "changed milestone to #{milestone.to_reference(project, format: format)}" + def change_due_date(noteable, project, author, due_date) + body = due_date ? "changed due date to #{due_date.to_s(:long)}" : 'removed due date' - create_note(NoteSummary.new(noteable, project, author, body, action: 'milestone')) + create_note(NoteSummary.new(noteable, project, author, body, action: 'due_date')) end # Called when the estimated time of a Noteable is changed diff --git a/app/services/wikis/create_attachment_service.rb b/app/services/wikis/create_attachment_service.rb index 30fe0e371a6..df31ad7c8ea 100644 --- a/app/services/wikis/create_attachment_service.rb +++ b/app/services/wikis/create_attachment_service.rb @@ -11,7 +11,7 @@ module Wikis def initialize(*args) super - @file_name = truncate_file_name(params[:file_name]) + @file_name = clean_file_name(params[:file_name]) @file_path = File.join(ATTACHMENT_PATH, SecureRandom.hex, @file_name) if @file_name @commit_message ||= "Upload attachment #{@file_name}" @branch_name ||= wiki.default_branch @@ -23,8 +23,16 @@ module Wikis private - def truncate_file_name(file_name) + def clean_file_name(file_name) return unless file_name.present? + + file_name = truncate_file_name(file_name) + # CommonMark does not allow Urls with whitespaces, so we have to replace them + # Using the same regex Carrierwave use to replace invalid characters + file_name.gsub(CarrierWave::SanitizedFile.sanitize_regexp, '_') + end + + def truncate_file_name(file_name) return file_name if file_name.length <= MAX_FILENAME_LENGTH extension = File.extname(file_name) |