summaryrefslogtreecommitdiff
path: root/app/services
diff options
context:
space:
mode:
Diffstat (limited to 'app/services')
-rw-r--r--app/services/base_service.rb4
-rw-r--r--app/services/boards/create_service.rb2
-rw-r--r--app/services/boards/issues/list_service.rb4
-rw-r--r--app/services/boards/issues/move_service.rb4
-rw-r--r--app/services/ci/process_pipeline_service.rb15
-rw-r--r--app/services/ci/retry_pipeline_service.rb4
-rw-r--r--app/services/create_branch_service.rb6
-rw-r--r--app/services/groups/update_service.rb8
-rw-r--r--app/services/issues/update_service.rb2
-rw-r--r--app/services/labels/base_service.rb161
-rw-r--r--app/services/labels/create_service.rb25
-rw-r--r--app/services/labels/find_or_create_service.rb4
-rw-r--r--app/services/labels/update_service.rb15
-rw-r--r--app/services/merge_requests/build_service.rb4
-rw-r--r--app/services/merge_requests/update_service.rb2
-rw-r--r--app/services/note_summary.rb20
-rw-r--r--app/services/notes/update_service.rb4
-rw-r--r--app/services/notification_recipient_service.rb48
-rw-r--r--app/services/notification_service.rb7
-rw-r--r--app/services/projects/import_service.rb31
-rw-r--r--app/services/projects/update_pages_service.rb1
-rw-r--r--app/services/search/global_service.rb8
-rw-r--r--app/services/search/project_service.rb4
-rw-r--r--app/services/search/snippet_service.rb4
-rw-r--r--app/services/search_service.rb63
-rw-r--r--app/services/spam_check_service.rb3
-rw-r--r--app/services/system_note_service.rb89
-rw-r--r--app/services/todo_service.rb40
-rw-r--r--app/services/users/create_service.rb112
-rw-r--r--app/services/users/destroy_service.rb4
30 files changed, 587 insertions, 111 deletions
diff --git a/app/services/base_service.rb b/app/services/base_service.rb
index 745c2c4b681..a0cb00dba58 100644
--- a/app/services/base_service.rb
+++ b/app/services/base_service.rb
@@ -24,6 +24,10 @@ class BaseService
Gitlab::AppLogger.info message
end
+ def log_error(message)
+ Gitlab::AppLogger.error message
+ end
+
def system_hook_service
SystemHooksService.new
end
diff --git a/app/services/boards/create_service.rb b/app/services/boards/create_service.rb
index f6275a63109..fd9ff115eab 100644
--- a/app/services/boards/create_service.rb
+++ b/app/services/boards/create_service.rb
@@ -12,7 +12,7 @@ module Boards
def create_board!
board = project.boards.create
- board.lists.create(list_type: :done)
+ board.lists.create(list_type: :closed)
board
end
diff --git a/app/services/boards/issues/list_service.rb b/app/services/boards/issues/list_service.rb
index 83f51947bd4..533e6787855 100644
--- a/app/services/boards/issues/list_service.rb
+++ b/app/services/boards/issues/list_service.rb
@@ -3,7 +3,7 @@ module Boards
class ListService < BaseService
def execute
issues = IssuesFinder.new(current_user, filter_params).execute
- issues = without_board_labels(issues) unless movable_list?
+ issues = without_board_labels(issues) unless list
issues = with_list_label(issues) if movable_list?
issues.order_by_position_and_priority
end
@@ -41,7 +41,7 @@ module Boards
end
def set_state
- params[:state] = list && list.done? ? 'closed' : 'opened'
+ params[:state] = list && list.closed? ? 'closed' : 'opened'
end
def board_label_ids
diff --git a/app/services/boards/issues/move_service.rb b/app/services/boards/issues/move_service.rb
index 2a9981ab884..d5735f13c1e 100644
--- a/app/services/boards/issues/move_service.rb
+++ b/app/services/boards/issues/move_service.rb
@@ -48,8 +48,8 @@ module Boards
end
def issue_state
- return 'reopen' if moving_from_list.done?
- return 'close' if moving_to_list.done?
+ return 'reopen' if moving_from_list.closed?
+ return 'close' if moving_to_list.closed?
end
def add_label_ids
diff --git a/app/services/ci/process_pipeline_service.rb b/app/services/ci/process_pipeline_service.rb
index 2935d00c075..33edcd60944 100644
--- a/app/services/ci/process_pipeline_service.rb
+++ b/app/services/ci/process_pipeline_service.rb
@@ -5,8 +5,6 @@ module Ci
def execute(pipeline)
@pipeline = pipeline
- ensure_created_builds! # TODO, remove me in 9.0
-
new_builds =
stage_indexes_of_created_builds.map do |index|
process_stage(index)
@@ -73,18 +71,5 @@ module Ci
def created_builds
pipeline.builds.created
end
-
- # This method is DEPRECATED and should be removed in 9.0.
- #
- # We need it to maintain backwards compatibility with previous versions
- # when builds were not created within one transaction with the pipeline.
- #
- def ensure_created_builds!
- return if created_builds.any?
-
- Ci::CreatePipelineBuildsService
- .new(project, current_user)
- .execute(pipeline)
- end
end
end
diff --git a/app/services/ci/retry_pipeline_service.rb b/app/services/ci/retry_pipeline_service.rb
index 574561adc4c..f72ddbf690c 100644
--- a/app/services/ci/retry_pipeline_service.rb
+++ b/app/services/ci/retry_pipeline_service.rb
@@ -7,14 +7,14 @@ module Ci
raise Gitlab::Access::AccessDeniedError
end
- pipeline.builds.failed_or_canceled.find_each do |build|
+ pipeline.builds.latest.failed_or_canceled.find_each do |build|
next unless build.retryable?
Ci::RetryBuildService.new(project, current_user)
.reprocess(build)
end
- pipeline.builds.skipped.find_each do |skipped|
+ pipeline.builds.latest.skipped.find_each do |skipped|
retry_optimistic_lock(skipped) { |build| build.process }
end
diff --git a/app/services/create_branch_service.rb b/app/services/create_branch_service.rb
index b07338d500a..673ed02f952 100644
--- a/app/services/create_branch_service.rb
+++ b/app/services/create_branch_service.rb
@@ -25,12 +25,12 @@ class CreateBranchService < BaseService
private
def create_master_branch
- project.repository.commit_file(
+ project.repository.create_file(
current_user,
'/README.md',
'',
message: 'Add README.md',
- branch_name: 'master',
- update: false)
+ branch_name: 'master'
+ )
end
end
diff --git a/app/services/groups/update_service.rb b/app/services/groups/update_service.rb
index 4e878ec556a..1d65c76d282 100644
--- a/app/services/groups/update_service.rb
+++ b/app/services/groups/update_service.rb
@@ -1,6 +1,8 @@
module Groups
class UpdateService < Groups::BaseService
def execute
+ reject_parent_id!
+
# check that user is allowed to set specified visibility_level
new_visibility = params[:visibility_level]
if new_visibility && new_visibility.to_i != group.visibility_level
@@ -22,5 +24,11 @@ module Groups
false
end
end
+
+ private
+
+ def reject_parent_id!
+ params.except!(:parent_id)
+ end
end
end
diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb
index a444c78b609..b7fe5cb168b 100644
--- a/app/services/issues/update_service.rb
+++ b/app/services/issues/update_service.rb
@@ -19,7 +19,7 @@ module Issues
if issue.previous_changes.include?('title') ||
issue.previous_changes.include?('description')
- todo_service.update_issue(issue, current_user)
+ todo_service.update_issue(issue, current_user, old_mentioned_users)
end
if issue.previous_changes.include?('milestone_id')
diff --git a/app/services/labels/base_service.rb b/app/services/labels/base_service.rb
new file mode 100644
index 00000000000..91d72a57b4e
--- /dev/null
+++ b/app/services/labels/base_service.rb
@@ -0,0 +1,161 @@
+module Labels
+ class BaseService < ::BaseService
+ COLOR_NAME_TO_HEX = {
+ black: '#000000',
+ silver: '#C0C0C0',
+ gray: '#808080',
+ white: '#FFFFFF',
+ maroon: '#800000',
+ red: '#FF0000',
+ purple: '#800080',
+ fuchsia: '#FF00FF',
+ green: '#008000',
+ lime: '#00FF00',
+ olive: '#808000',
+ yellow: '#FFFF00',
+ navy: '#000080',
+ blue: '#0000FF',
+ teal: '#008080',
+ aqua: '#00FFFF',
+ orange: '#FFA500',
+ aliceblue: '#F0F8FF',
+ antiquewhite: '#FAEBD7',
+ aquamarine: '#7FFFD4',
+ azure: '#F0FFFF',
+ beige: '#F5F5DC',
+ bisque: '#FFE4C4',
+ blanchedalmond: '#FFEBCD',
+ blueviolet: '#8A2BE2',
+ brown: '#A52A2A',
+ burlywood: '#DEB887',
+ cadetblue: '#5F9EA0',
+ chartreuse: '#7FFF00',
+ chocolate: '#D2691E',
+ coral: '#FF7F50',
+ cornflowerblue: '#6495ED',
+ cornsilk: '#FFF8DC',
+ crimson: '#DC143C',
+ darkblue: '#00008B',
+ darkcyan: '#008B8B',
+ darkgoldenrod: '#B8860B',
+ darkgray: '#A9A9A9',
+ darkgreen: '#006400',
+ darkgrey: '#A9A9A9',
+ darkkhaki: '#BDB76B',
+ darkmagenta: '#8B008B',
+ darkolivegreen: '#556B2F',
+ darkorange: '#FF8C00',
+ darkorchid: '#9932CC',
+ darkred: '#8B0000',
+ darksalmon: '#E9967A',
+ darkseagreen: '#8FBC8F',
+ darkslateblue: '#483D8B',
+ darkslategray: '#2F4F4F',
+ darkslategrey: '#2F4F4F',
+ darkturquoise: '#00CED1',
+ darkviolet: '#9400D3',
+ deeppink: '#FF1493',
+ deepskyblue: '#00BFFF',
+ dimgray: '#696969',
+ dimgrey: '#696969',
+ dodgerblue: '#1E90FF',
+ firebrick: '#B22222',
+ floralwhite: '#FFFAF0',
+ forestgreen: '#228B22',
+ gainsboro: '#DCDCDC',
+ ghostwhite: '#F8F8FF',
+ gold: '#FFD700',
+ goldenrod: '#DAA520',
+ greenyellow: '#ADFF2F',
+ grey: '#808080',
+ honeydew: '#F0FFF0',
+ hotpink: '#FF69B4',
+ indianred: '#CD5C5C',
+ indigo: '#4B0082',
+ ivory: '#FFFFF0',
+ khaki: '#F0E68C',
+ lavender: '#E6E6FA',
+ lavenderblush: '#FFF0F5',
+ lawngreen: '#7CFC00',
+ lemonchiffon: '#FFFACD',
+ lightblue: '#ADD8E6',
+ lightcoral: '#F08080',
+ lightcyan: '#E0FFFF',
+ lightgoldenrodyellow: '#FAFAD2',
+ lightgray: '#D3D3D3',
+ lightgreen: '#90EE90',
+ lightgrey: '#D3D3D3',
+ lightpink: '#FFB6C1',
+ lightsalmon: '#FFA07A',
+ lightseagreen: '#20B2AA',
+ lightskyblue: '#87CEFA',
+ lightslategray: '#778899',
+ lightslategrey: '#778899',
+ lightsteelblue: '#B0C4DE',
+ lightyellow: '#FFFFE0',
+ limegreen: '#32CD32',
+ linen: '#FAF0E6',
+ mediumaquamarine: '#66CDAA',
+ mediumblue: '#0000CD',
+ mediumorchid: '#BA55D3',
+ mediumpurple: '#9370DB',
+ mediumseagreen: '#3CB371',
+ mediumslateblue: '#7B68EE',
+ mediumspringgreen: '#00FA9A',
+ mediumturquoise: '#48D1CC',
+ mediumvioletred: '#C71585',
+ midnightblue: '#191970',
+ mintcream: '#F5FFFA',
+ mistyrose: '#FFE4E1',
+ moccasin: '#FFE4B5',
+ navajowhite: '#FFDEAD',
+ oldlace: '#FDF5E6',
+ olivedrab: '#6B8E23',
+ orangered: '#FF4500',
+ orchid: '#DA70D6',
+ palegoldenrod: '#EEE8AA',
+ palegreen: '#98FB98',
+ paleturquoise: '#AFEEEE',
+ palevioletred: '#DB7093',
+ papayawhip: '#FFEFD5',
+ peachpuff: '#FFDAB9',
+ peru: '#CD853F',
+ pink: '#FFC0CB',
+ plum: '#DDA0DD',
+ powderblue: '#B0E0E6',
+ rosybrown: '#BC8F8F',
+ royalblue: '#4169E1',
+ saddlebrown: '#8B4513',
+ salmon: '#FA8072',
+ sandybrown: '#F4A460',
+ seagreen: '#2E8B57',
+ seashell: '#FFF5EE',
+ sienna: '#A0522D',
+ skyblue: '#87CEEB',
+ slateblue: '#6A5ACD',
+ slategray: '#708090',
+ slategrey: '#708090',
+ snow: '#FFFAFA',
+ springgreen: '#00FF7F',
+ steelblue: '#4682B4',
+ tan: '#D2B48C',
+ thistle: '#D8BFD8',
+ tomato: '#FF6347',
+ turquoise: '#40E0D0',
+ violet: '#EE82EE',
+ wheat: '#F5DEB3',
+ whitesmoke: '#F5F5F5',
+ yellowgreen: '#9ACD32',
+ rebeccapurple: '#663399'
+ }.freeze
+
+ def convert_color_name_to_hex
+ color = params[:color]
+ color_name = color.strip.downcase
+
+ return color if color_name.start_with?('#')
+
+ COLOR_NAME_TO_HEX[color_name.to_sym] || color
+ end
+ end
+end
diff --git a/app/services/labels/create_service.rb b/app/services/labels/create_service.rb
new file mode 100644
index 00000000000..6c399c92377
--- /dev/null
+++ b/app/services/labels/create_service.rb
@@ -0,0 +1,25 @@
+module Labels
+ class CreateService < Labels::BaseService
+ def initialize(params = {})
+ @params = params.dup.with_indifferent_access
+ end
+
+ # returns the created label
+ def execute(target_params)
+ params[:color] = convert_color_name_to_hex if params[:color].present?
+
+ project_or_group = target_params[:project] || target_params[:group]
+
+ if project_or_group.present?
+ project_or_group.labels.create(params)
+ elsif target_params[:template]
+ label = Label.new(params)
+ label.template = true
+ label.save
+ label
+ else
+ Rails.logger.warn("target_params should contain :project or :group or :template, actual value: #{target_params}")
+ end
+ end
+ end
+end
diff --git a/app/services/labels/find_or_create_service.rb b/app/services/labels/find_or_create_service.rb
index cf4f7606c94..940c8b333d3 100644
--- a/app/services/labels/find_or_create_service.rb
+++ b/app/services/labels/find_or_create_service.rb
@@ -3,7 +3,7 @@ module Labels
def initialize(current_user, project, params = {})
@current_user = current_user
@project = project
- @params = params.dup
+ @params = params.dup.with_indifferent_access
end
def execute(skip_authorization: false)
@@ -28,7 +28,7 @@ module Labels
new_label = available_labels.find_by(title: title)
if new_label.nil? && (skip_authorization || Ability.allowed?(current_user, :admin_label, project))
- new_label = project.labels.create(params)
+ new_label = Labels::CreateService.new(params).execute(project: project)
end
new_label
diff --git a/app/services/labels/update_service.rb b/app/services/labels/update_service.rb
new file mode 100644
index 00000000000..28dcabf9541
--- /dev/null
+++ b/app/services/labels/update_service.rb
@@ -0,0 +1,15 @@
+module Labels
+ class UpdateService < Labels::BaseService
+ def initialize(params = {})
+ @params = params.dup.with_indifferent_access
+ end
+
+ # returns the updated label
+ def execute(label)
+ params[:color] = convert_color_name_to_hex if params[:color].present?
+
+ label.update(params)
+ label
+ end
+ end
+end
diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb
index fdce542bd9e..d45da5180e1 100644
--- a/app/services/merge_requests/build_service.rb
+++ b/app/services/merge_requests/build_service.rb
@@ -21,7 +21,9 @@ module MergeRequests
delegate :target_branch, :source_branch, :source_project, :target_project, :compare_commits, :wip_title, :description, :errors, to: :merge_request
def find_source_project
- source_project || project
+ return source_project if source_project.present? && can?(current_user, :read_project, source_project)
+
+ project
end
def find_target_project
diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb
index 3cb9aae83f6..ab7fcf3b6e2 100644
--- a/app/services/merge_requests/update_service.rb
+++ b/app/services/merge_requests/update_service.rb
@@ -28,7 +28,7 @@ module MergeRequests
if merge_request.previous_changes.include?('title') ||
merge_request.previous_changes.include?('description')
- todo_service.update_merge_request(merge_request, current_user)
+ todo_service.update_merge_request(merge_request, current_user, old_mentioned_users)
end
if merge_request.previous_changes.include?('target_branch')
diff --git a/app/services/note_summary.rb b/app/services/note_summary.rb
new file mode 100644
index 00000000000..a6f6320d573
--- /dev/null
+++ b/app/services/note_summary.rb
@@ -0,0 +1,20 @@
+class NoteSummary
+ attr_reader :note
+ attr_reader :metadata
+
+ def initialize(noteable, project, author, body, action: nil, commit_count: nil)
+ @note = { noteable: noteable, project: project, author: author, note: body }
+ @metadata = { action: action, commit_count: commit_count }.compact
+
+ set_commit_params if note[:noteable].is_a?(Commit)
+ end
+
+ def metadata?
+ metadata.present?
+ end
+
+ def set_commit_params
+ note.merge!(noteable_type: 'Commit', commit_id: note[:noteable].id)
+ note[:noteable] = nil
+ end
+end
diff --git a/app/services/notes/update_service.rb b/app/services/notes/update_service.rb
index 75a4b3ed826..75fd08ea0a9 100644
--- a/app/services/notes/update_service.rb
+++ b/app/services/notes/update_service.rb
@@ -3,11 +3,13 @@ module Notes
def execute(note)
return note unless note.editable?
+ old_mentioned_users = note.mentioned_users.to_a
+
note.update_attributes(params.merge(updated_by: current_user))
note.create_new_cross_references!(current_user)
if note.previous_changes.include?('note')
- TodoService.new.update_note(note, current_user)
+ TodoService.new.update_note(note, current_user, old_mentioned_users)
end
note
diff --git a/app/services/notification_recipient_service.rb b/app/services/notification_recipient_service.rb
index 44ae23fad18..8bb995158de 100644
--- a/app/services/notification_recipient_service.rb
+++ b/app/services/notification_recipient_service.rb
@@ -3,7 +3,7 @@
#
class NotificationRecipientService
attr_reader :project
-
+
def initialize(project)
@project = project
end
@@ -12,11 +12,7 @@ class NotificationRecipientService
custom_action = build_custom_key(action, target)
recipients = target.participants(current_user)
-
- unless NotificationSetting::EXCLUDED_WATCHER_EVENTS.include?(custom_action)
- recipients = add_project_watchers(recipients)
- end
-
+ recipients = add_project_watchers(recipients)
recipients = add_custom_notifications(recipients, custom_action)
recipients = reject_mention_users(recipients)
@@ -38,16 +34,38 @@ class NotificationRecipientService
recipients = reject_unsubscribed_users(recipients, target)
recipients = reject_users_without_access(recipients, target)
- recipients.delete(current_user) if skip_current_user
+ recipients.delete(current_user) if skip_current_user && !current_user.notified_of_own_activity?
recipients.uniq
end
+ def build_pipeline_recipients(target, current_user, action:)
+ return [] unless current_user
+
+ custom_action =
+ case action.to_s
+ when 'failed'
+ :failed_pipeline
+ when 'success'
+ :success_pipeline
+ end
+
+ notification_setting = notification_setting_for_user_project(current_user, target.project)
+
+ return [] if notification_setting.mention? || notification_setting.disabled?
+
+ return [] if notification_setting.custom? && !notification_setting.public_send(custom_action)
+
+ return [] if (notification_setting.watch? || notification_setting.participating?) && NotificationSetting::EXCLUDED_WATCHER_EVENTS.include?(custom_action)
+
+ reject_users_without_access([current_user], target)
+ end
+
def build_relabeled_recipients(target, current_user, labels:)
recipients = add_labels_subscribers([], target, labels: labels)
recipients = reject_unsubscribed_users(recipients, target)
recipients = reject_users_without_access(recipients, target)
- recipients.delete(current_user)
+ recipients.delete(current_user) unless current_user.notified_of_own_activity?
recipients.uniq
end
@@ -88,7 +106,7 @@ class NotificationRecipientService
recipients = reject_unsubscribed_users(recipients, note.noteable)
recipients = reject_users_without_access(recipients, note.noteable)
- recipients.delete(note.author)
+ recipients.delete(note.author) unless note.author.notified_of_own_activity?
recipients.uniq
end
@@ -290,4 +308,16 @@ class NotificationRecipientService
def build_custom_key(action, object)
"#{action}_#{object.class.model_name.name.underscore}".to_sym
end
+
+ def notification_setting_for_user_project(user, project)
+ project_setting = user.notification_settings_for(project)
+
+ return project_setting unless project_setting.global?
+
+ group_setting = user.notification_settings_for(project.group)
+
+ return group_setting unless group_setting.global?
+
+ user.global_notification_setting
+ end
end
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index f9aa2346759..6b186263bd1 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -278,10 +278,11 @@ class NotificationService
return unless mailer.respond_to?(email_template)
- recipients ||= NotificationRecipientService.new(pipeline.project).build_recipients(
+ recipients ||= NotificationRecipientService.new(pipeline.project).build_pipeline_recipients(
pipeline,
- nil, # The acting user, who won't be added to recipients
- action: pipeline.status).map(&:notification_email)
+ pipeline.user,
+ action: pipeline.status,
+ ).map(&:notification_email)
if recipients.any?
mailer.public_send(email_template, pipeline, recipients).deliver_later
diff --git a/app/services/projects/import_service.rb b/app/services/projects/import_service.rb
index d484a96f785..4c72d5e117d 100644
--- a/app/services/projects/import_service.rb
+++ b/app/services/projects/import_service.rb
@@ -11,7 +11,7 @@ module Projects
success
rescue => e
- error(e.message)
+ error("Error importing repository #{project.import_url} into #{project.path_with_namespace} - #{e.message}")
end
private
@@ -32,23 +32,40 @@ module Projects
end
def import_repository
+ raise Error, 'Blocked import URL.' if Gitlab::UrlBlocker.blocked_url?(project.import_url)
+
begin
- raise Error, "Blocked import URL." if Gitlab::UrlBlocker.blocked_url?(project.import_url)
- gitlab_shell.import_repository(project.repository_storage_path, project.path_with_namespace, project.import_url)
- rescue => e
+ if project.github_import? || project.gitea_import?
+ fetch_repository
+ else
+ clone_repository
+ end
+ rescue Gitlab::Shell::Error => e
# Expire cache to prevent scenarios such as:
# 1. First import failed, but the repo was imported successfully, so +exists?+ returns true
# 2. Retried import, repo is broken or not imported but +exists?+ still returns true
- project.repository.before_import if project.repository_exists?
+ project.repository.expire_content_cache if project.repository_exists?
- raise Error, "Error importing repository #{project.import_url} into #{project.path_with_namespace} - #{e.message}"
+ raise Error, e.message
end
end
+ def clone_repository
+ gitlab_shell.import_repository(project.repository_storage_path, project.path_with_namespace, project.import_url)
+ end
+
+ def fetch_repository
+ project.create_repository
+ project.repository.add_remote(project.import_type, project.import_url)
+ project.repository.set_remote_as_mirror(project.import_type)
+ project.repository.fetch_remote(project.import_type, forced: true)
+ project.repository.remove_remote(project.import_type)
+ end
+
def import_data
return unless has_importer?
- project.repository.before_import unless project.gitlab_project_import?
+ project.repository.expire_content_cache unless project.gitlab_project_import?
unless importer.execute
raise Error, 'The remote data could not be imported.'
diff --git a/app/services/projects/update_pages_service.rb b/app/services/projects/update_pages_service.rb
index 523b9f41916..17cf71cf098 100644
--- a/app/services/projects/update_pages_service.rb
+++ b/app/services/projects/update_pages_service.rb
@@ -46,6 +46,7 @@ module Projects
end
def error(message, http_status = nil)
+ log_error("Projects::UpdatePagesService: #{message}")
@status.allow_failure = !latest?
@status.description = message
@status.drop
diff --git a/app/services/search/global_service.rb b/app/services/search/global_service.rb
index 781cd13b44b..c1549df5ac6 100644
--- a/app/services/search/global_service.rb
+++ b/app/services/search/global_service.rb
@@ -16,5 +16,13 @@ module Search
Gitlab::SearchResults.new(current_user, projects, params[:search])
end
+
+ def scope
+ @scope ||= begin
+ allowed_scopes = %w[issues merge_requests milestones]
+
+ allowed_scopes.delete(params[:scope]) { 'projects' }
+ end
+ end
end
end
diff --git a/app/services/search/project_service.rb b/app/services/search/project_service.rb
index 4b500914cfb..9a22abae635 100644
--- a/app/services/search/project_service.rb
+++ b/app/services/search/project_service.rb
@@ -12,5 +12,9 @@ module Search
params[:search],
params[:repository_ref])
end
+
+ def scope
+ @scope ||= %w[notes issues merge_requests milestones wiki_blobs commits].delete(params[:scope]) { 'blobs' }
+ end
end
end
diff --git a/app/services/search/snippet_service.rb b/app/services/search/snippet_service.rb
index 0b3e713e220..4f161beea4d 100644
--- a/app/services/search/snippet_service.rb
+++ b/app/services/search/snippet_service.rb
@@ -11,5 +11,9 @@ module Search
Gitlab::SnippetSearchResults.new(snippets, params[:search])
end
+
+ def scope
+ @scope ||= %w[snippet_titles].delete(params[:scope]) { 'snippet_blobs' }
+ end
end
end
diff --git a/app/services/search_service.rb b/app/services/search_service.rb
new file mode 100644
index 00000000000..8d46a8dab3e
--- /dev/null
+++ b/app/services/search_service.rb
@@ -0,0 +1,63 @@
+class SearchService
+ include Gitlab::Allowable
+
+ def initialize(current_user, params = {})
+ @current_user = current_user
+ @params = params.dup
+ end
+
+ def project
+ return @project if defined?(@project)
+
+ @project =
+ if params[:project_id].present?
+ the_project = Project.find_by(id: params[:project_id])
+ can?(current_user, :download_code, the_project) ? the_project : nil
+ else
+ nil
+ end
+ end
+
+ def group
+ return @group if defined?(@group)
+
+ @group =
+ if params[:group_id].present?
+ the_group = Group.find_by(id: params[:group_id])
+ can?(current_user, :read_group, the_group) ? the_group : nil
+ else
+ nil
+ end
+ end
+
+ def show_snippets?
+ return @show_snippets if defined?(@show_snippets)
+
+ @show_snippets = params[:snippets] == 'true'
+ end
+
+ delegate :scope, to: :search_service
+
+ def search_results
+ @search_results ||= search_service.execute
+ end
+
+ def search_objects
+ @search_objects ||= search_results.objects(scope, params[:page])
+ end
+
+ private
+
+ def search_service
+ @search_service ||=
+ if project
+ Search::ProjectService.new(project, current_user, params)
+ elsif show_snippets?
+ Search::SnippetService.new(current_user, params)
+ else
+ Search::GlobalService.new(current_user, params)
+ end
+ end
+
+ attr_reader :current_user, :params
+end
diff --git a/app/services/spam_check_service.rb b/app/services/spam_check_service.rb
index 023e0824e85..11030bee8f1 100644
--- a/app/services/spam_check_service.rb
+++ b/app/services/spam_check_service.rb
@@ -14,6 +14,9 @@ module SpamCheckService
@spam_log_id = params.delete(:spam_log_id)
end
+ # In order to be proceed to the spam check process, @spammable has to be
+ # a dirty instance, which means it should be already assigned with the new
+ # attribute values.
def spam_check(spammable, user)
spam_service = SpamService.new(spammable, @request)
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index 8e02fe3741a..35cfcc3682e 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -26,7 +26,7 @@ module SystemNoteService
body << new_commit_summary(new_commits).join("\n")
body << "\n\n[Compare with previous version](#{diff_comparison_url(noteable, project, oldrev)})"
- create_note(noteable: noteable, project: project, author: author, note: body)
+ create_note(NoteSummary.new(noteable, project, author, body, action: 'commit', commit_count: total_count))
end
# Called when the assignee of a Noteable is changed or removed
@@ -46,7 +46,7 @@ module SystemNoteService
def change_assignee(noteable, project, author, assignee)
body = assignee.nil? ? 'removed assignee' : "assigned to #{assignee.to_reference}"
- create_note(noteable: noteable, project: project, author: author, note: body)
+ create_note(NoteSummary.new(noteable, project, author, body, action: 'assignee'))
end
# Called when one or more labels on a Noteable are added and/or removed
@@ -86,7 +86,7 @@ module SystemNoteService
body << ' ' << 'label'.pluralize(labels_count)
- create_note(noteable: noteable, project: project, author: author, note: body)
+ create_note(NoteSummary.new(noteable, project, author, body, action: 'label'))
end
# Called when the milestone of a Noteable is changed
@@ -106,7 +106,7 @@ module SystemNoteService
def change_milestone(noteable, project, author, milestone)
body = milestone.nil? ? 'removed milestone' : "changed milestone to #{milestone.to_reference(project)}"
- create_note(noteable: noteable, project: project, author: author, note: body)
+ create_note(NoteSummary.new(noteable, project, author, body, action: 'milestone'))
end
# Called when the estimated time of a Noteable is changed
@@ -132,7 +132,7 @@ module SystemNoteService
"changed time estimate to #{parsed_time}"
end
- create_note(noteable: noteable, project: project, author: author, note: body)
+ create_note(NoteSummary.new(noteable, project, author, body, action: 'time_tracking'))
end
# Called when the spent time of a Noteable is changed
@@ -161,7 +161,7 @@ module SystemNoteService
body = "#{action} #{parsed_time} of time spent"
end
- create_note(noteable: noteable, project: project, author: author, note: body)
+ create_note(NoteSummary.new(noteable, project, author, body, action: 'time_tracking'))
end
# Called when the status of a Noteable is changed
@@ -183,53 +183,59 @@ module SystemNoteService
body = status.dup
body << " via #{source.gfm_reference(project)}" if source
- create_note(noteable: noteable, project: project, author: author, note: body)
+ action = status == 'reopened' ? 'opened' : status
+
+ create_note(NoteSummary.new(noteable, project, author, body, action: action))
end
# Called when 'merge when pipeline succeeds' is executed
def merge_when_pipeline_succeeds(noteable, project, author, last_commit)
body = "enabled an automatic merge when the pipeline for #{last_commit.to_reference(project)} succeeds"
- create_note(noteable: noteable, project: project, author: author, note: body)
+ create_note(NoteSummary.new(noteable, project, author, body, action: 'merge'))
end
# Called when 'merge when pipeline succeeds' is canceled
def cancel_merge_when_pipeline_succeeds(noteable, project, author)
body = 'canceled the automatic merge'
- create_note(noteable: noteable, project: project, author: author, note: body)
+ create_note(NoteSummary.new(noteable, project, author, body, action: 'merge'))
end
def remove_merge_request_wip(noteable, project, author)
body = 'unmarked as a **Work In Progress**'
- create_note(noteable: noteable, project: project, author: author, note: body)
+ create_note(NoteSummary.new(noteable, project, author, body, action: 'title'))
end
def add_merge_request_wip(noteable, project, author)
body = 'marked as a **Work In Progress**'
- create_note(noteable: noteable, project: project, author: author, note: body)
+ create_note(NoteSummary.new(noteable, project, author, body, action: 'title'))
end
def add_merge_request_wip_from_commit(noteable, project, author, commit)
body = "marked as a **Work In Progress** from #{commit.to_reference(project)}"
- create_note(noteable: noteable, project: project, author: author, note: body)
+ create_note(NoteSummary.new(noteable, project, author, body, action: 'title'))
end
def self.resolve_all_discussions(merge_request, project, author)
body = "resolved all discussions"
- create_note(noteable: merge_request, project: project, author: author, note: body)
+ create_note(NoteSummary.new(merge_request, project, author, body, action: 'discussion'))
end
def discussion_continued_in_issue(discussion, project, author, issue)
body = "created #{issue.to_reference} to continue this discussion"
- note_attributes = discussion.reply_attributes.merge(project: project, author: author, note: body)
- note_attributes[:type] = note_attributes.delete(:note_type)
- create_note(note_attributes)
+ note_params = discussion.reply_attributes.merge(project: project, author: author, note: body)
+ note_params[:type] = note_params.delete(:note_type)
+
+ note = Note.create(note_params.merge(system: true))
+ note.system_note_metadata = SystemNoteMetadata.new({ action: 'discussion' })
+
+ note
end
# Called when the title of a Noteable is changed
@@ -253,7 +259,8 @@ module SystemNoteService
marked_new_title = Gitlab::Diff::InlineDiffMarker.new(new_title).mark(new_diffs, mode: :addition, markdown: true)
body = "changed title from **#{marked_old_title}** to **#{marked_new_title}**"
- create_note(noteable: noteable, project: project, author: author, note: body)
+
+ create_note(NoteSummary.new(noteable, project, author, body, action: 'title'))
end
# Called when the confidentiality changes
@@ -268,8 +275,15 @@ module SystemNoteService
#
# Returns the created Note object
def change_issue_confidentiality(issue, project, author)
- body = issue.confidential ? 'made the issue confidential' : 'made the issue visible to everyone'
- create_note(noteable: issue, project: project, author: author, note: body)
+ if issue.confidential
+ body = 'made the issue confidential'
+ action = 'confidential'
+ else
+ body = 'made the issue visible to everyone'
+ action = 'visible'
+ end
+
+ create_note(NoteSummary.new(issue, project, author, body, action: action))
end
# Called when a branch in Noteable is changed
@@ -288,7 +302,8 @@ module SystemNoteService
# Returns the created Note object
def change_branch(noteable, project, author, branch_type, old_branch, new_branch)
body = "changed #{branch_type} branch from `#{old_branch}` to `#{new_branch}`"
- create_note(noteable: noteable, project: project, author: author, note: body)
+
+ create_note(NoteSummary.new(noteable, project, author, body, action: 'branch'))
end
# Called when a branch in Noteable is added or deleted
@@ -314,7 +329,8 @@ module SystemNoteService
end
body = "#{verb} #{branch_type} branch `#{branch}`"
- create_note(noteable: noteable, project: project, author: author, note: body)
+
+ create_note(NoteSummary.new(noteable, project, author, body, action: 'branch'))
end
# Called when a branch is created from the 'new branch' button on a issue
@@ -325,7 +341,8 @@ module SystemNoteService
link = url_helpers.namespace_project_compare_url(project.namespace, project, from: project.default_branch, to: branch)
body = "created branch [`#{branch}`](#{link})"
- create_note(noteable: issue, project: project, author: author, note: body)
+
+ create_note(NoteSummary.new(issue, project, author, body, action: 'branch'))
end
# Called when a Mentionable references a Noteable
@@ -349,23 +366,12 @@ module SystemNoteService
return if cross_reference_disallowed?(noteable, mentioner)
gfm_reference = mentioner.gfm_reference(noteable.project)
-
- note_options = {
- project: noteable.project,
- author: author,
- note: cross_reference_note_content(gfm_reference)
- }
-
- if noteable.is_a?(Commit)
- note_options.merge!(noteable_type: 'Commit', commit_id: noteable.id)
- else
- note_options[:noteable] = noteable
- end
+ body = cross_reference_note_content(gfm_reference)
if noteable.is_a?(ExternalIssue)
noteable.project.issues_tracker.create_cross_reference_note(noteable, mentioner, author)
else
- create_note(note_options)
+ create_note(NoteSummary.new(noteable, noteable.project, author, body, action: 'cross_reference'))
end
end
@@ -444,7 +450,8 @@ module SystemNoteService
def change_task_status(noteable, project, author, new_task)
status_label = new_task.complete? ? Taskable::COMPLETED : Taskable::INCOMPLETE
body = "marked the task **#{new_task.source}** as #{status_label}"
- create_note(noteable: noteable, project: project, author: author, note: body)
+
+ create_note(NoteSummary.new(noteable, project, author, body, action: 'task'))
end
# Called when noteable has been moved to another project
@@ -466,7 +473,8 @@ module SystemNoteService
cross_reference = noteable_ref.to_reference(project)
body = "moved #{direction} #{cross_reference}"
- create_note(noteable: noteable, project: project, author: author, note: body)
+
+ create_note(NoteSummary.new(noteable, project, author, body, action: 'moved'))
end
private
@@ -482,8 +490,11 @@ module SystemNoteService
end
end
- def create_note(args = {})
- Note.create(args.merge(system: true))
+ def create_note(note_summary)
+ note = Note.create(note_summary.note.merge(system: true))
+ note.system_note_metadata = SystemNoteMetadata.new(note_summary.metadata) if note_summary.metadata?
+
+ note
end
def cross_reference_note_prefix
diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb
index bf7e76ec59e..b6e88b0280f 100644
--- a/app/services/todo_service.rb
+++ b/app/services/todo_service.rb
@@ -19,8 +19,8 @@ class TodoService
#
# * mark all pending todos related to the issue for the current user as done
#
- def update_issue(issue, current_user)
- update_issuable(issue, current_user)
+ def update_issue(issue, current_user, skip_users = [])
+ update_issuable(issue, current_user, skip_users)
end
# When close an issue we should:
@@ -60,8 +60,8 @@ class TodoService
#
# * create a todo for each mentioned user on merge request
#
- def update_merge_request(merge_request, current_user)
- update_issuable(merge_request, current_user)
+ def update_merge_request(merge_request, current_user, skip_users = [])
+ update_issuable(merge_request, current_user, skip_users)
end
# When close a merge request we should:
@@ -123,7 +123,7 @@ class TodoService
mark_pending_todos_as_done(merge_request, merge_request.author)
mark_pending_todos_as_done(merge_request, merge_request.merge_user) if merge_request.merge_when_pipeline_succeeds?
end
-
+
# When a merge request could not be automatically merged due to its unmergeable state we should:
#
# * create a todo for a merge_user
@@ -131,7 +131,7 @@ class TodoService
def merge_request_became_unmergeable(merge_request)
create_unmergeable_todo(merge_request, merge_request.merge_user) if merge_request.merge_when_pipeline_succeeds?
end
-
+
# When create a note we should:
#
# * mark all pending todos related to the noteable for the note author as done
@@ -146,8 +146,8 @@ class TodoService
# * mark all pending todos related to the noteable for the current user as done
# * create a todo for each new user mentioned on note
#
- def update_note(note, current_user)
- handle_note(note, current_user)
+ def update_note(note, current_user, skip_users = [])
+ handle_note(note, current_user, skip_users)
end
# When an emoji is awarded we should:
@@ -204,7 +204,7 @@ class TodoService
# Only update those that are not really on that state
todos = todos.where.not(state: state)
todos_ids = todos.pluck(:id)
- todos.update_all(state: state)
+ todos.unscope(:order).update_all(state: state)
current_user.update_todos_count_cache
todos_ids
end
@@ -223,11 +223,11 @@ class TodoService
create_mention_todos(issuable.project, issuable, author)
end
- def update_issuable(issuable, author)
+ def update_issuable(issuable, author, skip_users = [])
# Skip toggling a task list item in a description
return if toggling_tasks?(issuable)
- create_mention_todos(issuable.project, issuable, author)
+ create_mention_todos(issuable.project, issuable, author, nil, skip_users)
end
def destroy_issuable(issuable, user)
@@ -239,7 +239,7 @@ class TodoService
issuable.tasks? && issuable.updated_tasks.any?
end
- def handle_note(note, author)
+ def handle_note(note, author, skip_users = [])
# Skip system notes, and notes on project snippet
return if note.system? || note.for_snippet?
@@ -247,7 +247,7 @@ class TodoService
target = note.noteable
mark_pending_todos_as_done(target, author)
- create_mention_todos(project, target, author, note)
+ create_mention_todos(project, target, author, note, skip_users)
end
def create_assignment_todo(issuable, author)
@@ -257,14 +257,14 @@ class TodoService
end
end
- def create_mention_todos(project, target, author, note = nil)
+ def create_mention_todos(project, target, author, note = nil, skip_users = [])
# Create Todos for directly addressed users
- directly_addressed_users = filter_directly_addressed_users(project, note || target, author)
+ directly_addressed_users = filter_directly_addressed_users(project, note || target, author, skip_users)
attributes = attributes_for_todo(project, target, author, Todo::DIRECTLY_ADDRESSED, note)
create_todos(directly_addressed_users, attributes)
# Create Todos for mentioned users
- mentioned_users = filter_mentioned_users(project, note || target, author)
+ mentioned_users = filter_mentioned_users(project, note || target, author, skip_users)
attributes = attributes_for_todo(project, target, author, Todo::MENTIONED, note)
create_todos(mentioned_users, attributes)
end
@@ -307,13 +307,13 @@ class TodoService
reject_users_without_access(users, project, target).uniq
end
- def filter_mentioned_users(project, target, author)
- mentioned_users = target.mentioned_users(author)
+ def filter_mentioned_users(project, target, author, skip_users = [])
+ mentioned_users = target.mentioned_users(author) - skip_users
filter_todo_users(mentioned_users, project, target)
end
- def filter_directly_addressed_users(project, target, author)
- directly_addressed_users = target.directly_addressed_users(author)
+ def filter_directly_addressed_users(project, target, author, skip_users = [])
+ directly_addressed_users = target.directly_addressed_users(author) - skip_users
filter_todo_users(directly_addressed_users, project, target)
end
diff --git a/app/services/users/create_service.rb b/app/services/users/create_service.rb
new file mode 100644
index 00000000000..a847a71a66a
--- /dev/null
+++ b/app/services/users/create_service.rb
@@ -0,0 +1,112 @@
+module Users
+ # Service for creating a new user.
+ class CreateService < BaseService
+ def initialize(current_user, params = {})
+ @current_user = current_user
+ @params = params.dup
+ end
+
+ def build
+ raise Gitlab::Access::AccessDeniedError unless can_create_user?
+
+ user = User.new(build_user_params)
+
+ if current_user&.is_admin?
+ if params[:reset_password]
+ @reset_token = user.generate_reset_token
+ params[:force_random_password] = true
+ end
+
+ if params[:force_random_password]
+ random_password = Devise.friendly_token.first(Devise.password_length.min)
+ user.password = user.password_confirmation = random_password
+ end
+ end
+
+ identity_attrs = params.slice(:extern_uid, :provider)
+
+ if identity_attrs.any?
+ user.identities.build(identity_attrs)
+ end
+
+ user
+ end
+
+ def execute
+ user = build
+
+ if user.save
+ log_info("User \"#{user.name}\" (#{user.email}) was created")
+ notification_service.new_user(user, @reset_token) if @reset_token
+ system_hook_service.execute_hooks_for(user, :create)
+ end
+
+ user
+ end
+
+ private
+
+ def can_create_user?
+ (current_user.nil? && current_application_settings.signup_enabled?) || current_user&.is_admin?
+ end
+
+ # Allowed params for creating a user (admins only)
+ def admin_create_params
+ [
+ :access_level,
+ :admin,
+ :avatar,
+ :bio,
+ :can_create_group,
+ :color_scheme_id,
+ :email,
+ :external,
+ :force_random_password,
+ :password_automatically_set,
+ :hide_no_password,
+ :hide_no_ssh_key,
+ :key_id,
+ :linkedin,
+ :name,
+ :password,
+ :password_expires_at,
+ :projects_limit,
+ :remember_me,
+ :skip_confirmation,
+ :skype,
+ :theme_id,
+ :twitter,
+ :username,
+ :website_url
+ ]
+ end
+
+ # Allowed params for user signup
+ def signup_params
+ [
+ :email,
+ :email_confirmation,
+ :password_automatically_set,
+ :name,
+ :password,
+ :username
+ ]
+ end
+
+ def build_user_params
+ if current_user&.is_admin?
+ user_params = params.slice(*admin_create_params)
+ user_params[:created_by_id] = current_user&.id
+
+ if params[:reset_password]
+ user_params.merge!(force_random_password: true, password_expires_at: nil)
+ end
+ else
+ user_params = params.slice(*signup_params)
+ user_params[:skip_confirmation] = !current_application_settings.send_user_confirmation_email
+ end
+
+ user_params
+ end
+ end
+end
diff --git a/app/services/users/destroy_service.rb b/app/services/users/destroy_service.rb
index 833da5bc5d1..a3b32a71a64 100644
--- a/app/services/users/destroy_service.rb
+++ b/app/services/users/destroy_service.rb
@@ -20,10 +20,10 @@ module Users
Groups::DestroyService.new(group, current_user).execute
end
- user.personal_projects.each do |project|
+ user.personal_projects.with_deleted.each do |project|
# Skip repository removal because we remove directory with namespace
# that contain all this repositories
- ::Projects::DestroyService.new(project, current_user, skip_repo: true).async_execute
+ ::Projects::DestroyService.new(project, current_user, skip_repo: true).execute
end
move_issues_to_ghost_user(user)