Read about our upcoming Code of Conduct on this issue

notification_service.rb 18.7 KB
Newer Older
1
2
# frozen_string_literal: true

3
4
# rubocop:disable GitlabSecurity/PublicSend

5
6
# NotificationService class
#
Johannes Schleifenbaum's avatar
Johannes Schleifenbaum committed
7
# Used for notifying users with emails about different events
8
9
10
11
#
# Ex.
#   NotificationService.new.new_issue(issue, current_user)
#
12
13
14
15
16
17
# When calculating the recipients of a notification is expensive (for instance,
# in the new issue case), `#async` will make that calculation happen in Sidekiq
# instead:
#
#   NotificationService.new.async.new_issue(issue, current_user)
#
18
class NotificationService
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
  class Async
    attr_reader :parent
    delegate :respond_to_missing, to: :parent

    def initialize(parent)
      @parent = parent
    end

    def method_missing(meth, *args)
      return super unless parent.respond_to?(meth)

      MailScheduler::NotificationServiceWorker.perform_async(meth.to_s, *args)
    end
  end

  def async
    @async ||= Async.new(self)
  end

38
39
  # Always notify user about ssh key added
  # only if ssh key is not deploy key
40
41
  #
  # This is security email so it will be sent
42
43
44
  # even if user disabled notifications. However,
  # it won't be sent to internal users like the
  # ghost user or the EE support bot.
45
  def new_key(key)
46
    if key.user&.can?(:receive_notifications)
Valery Sizov's avatar
Valery Sizov committed
47
      mailer.new_ssh_key_email(key.id).deliver_later
48
49
50
    end
  end

51
52
  # Always notify the user about gpg key added
  #
Takuya Noguchi's avatar
Takuya Noguchi committed
53
  # This is a security email so it will be sent even if the user disabled
54
55
  # notifications
  def new_gpg_key(gpg_key)
56
    if gpg_key.user&.can?(:receive_notifications)
57
58
59
60
      mailer.new_gpg_key_email(gpg_key.id).deliver_later
    end
  end

61
  # When create an issue we should send an email to:
62
  #
63
  #  * issue assignee if their notification level is not Disabled
64
  #  * project team members with notification level higher then Participating
65
  #  * watchers of the issue's labels
66
  #  * users with custom level checked with "new issue"
67
68
  #
  def new_issue(issue, current_user)
http://jneen.net/'s avatar
http://jneen.net/ committed
69
    new_resource_email(issue, :new_issue_email)
70
71
  end

72
73
74
75
76
77
78
79
80
81
82
83
84
  # When issue text is updated, we should send an email to:
  #
  #  * newly mentioned project team members with notification level higher than Participating
  #
  def new_mentions_in_issue(issue, new_mentioned_users, current_user)
    new_mentions_in_resource_email(
      issue,
      new_mentioned_users,
      current_user,
      :new_mention_in_issue_email
    )
  end

85
  # When we close an issue we should send an email to:
86
  #
87
88
  #  * issue author if their notification level is not Disabled
  #  * issue assignee if their notification level is not Disabled
89
  #  * project team members with notification level higher then Participating
90
  #  * users with custom level checked with "close issue"
91
92
  #
  def close_issue(issue, current_user)
http://jneen.net/'s avatar
http://jneen.net/ committed
93
    close_resource_email(issue, current_user, :closed_issue_email)
94
95
  end

96
  # When we reassign an issue we should send an email to:
97
  #
98
99
  #  * issue old assignee if their notification level is not Disabled
  #  * issue new assignee if their notification level is not Disabled
100
  #  * users with custom level checked with "reassign issue"
101
  #
102
  def reassigned_issue(issue, current_user, previous_assignees = [])
103
    recipients = NotificationRecipientService.build_recipients(
104
105
106
107
108
109
      issue,
      current_user,
      action: "reassign",
      previous_assignee: previous_assignees
    )

110
111
    previous_assignee_ids = previous_assignees.map(&:id)

112
113
114
    recipients.each do |recipient|
      mailer.send(
        :reassigned_issue_email,
115
        recipient.user.id,
116
        issue.id,
117
        previous_assignee_ids,
118
119
        current_user.id,
        recipient.reason
120
121
      ).deliver_later
    end
122
123
  end

124
  # When we add labels to an issue we should send an email to:
125
  #
126
127
128
  #  * watchers of the issue's labels
  #
  def relabeled_issue(issue, added_labels, current_user)
http://jneen.net/'s avatar
http://jneen.net/ committed
129
    relabeled_resource_email(issue, added_labels, current_user, :relabeled_issue_email)
130
131
  end

132
133
134
135
136
137
138
139
  def removed_milestone_issue(issue, current_user)
    removed_milestone_resource_email(issue, current_user, :removed_milestone_issue_email)
  end

  def changed_milestone_issue(issue, new_milestone, current_user)
    changed_milestone_resource_email(issue, new_milestone, current_user, :changed_milestone_issue_email)
  end

140
  # When create a merge request we should send an email to:
141
  #
lulalala's avatar
lulalala committed
142
  #  * mr author
143
  #  * mr assignee if their notification level is not Disabled
144
145
  #  * project team members with notification level higher then Participating
  #  * watchers of the mr's labels
146
  #  * users with custom level checked with "new merge request"
147
  #
148
  # In EE, approvers of the merge request are also included
149
  def new_merge_request(merge_request, current_user)
http://jneen.net/'s avatar
http://jneen.net/ committed
150
    new_resource_email(merge_request, :new_merge_request_email)
151
  end
152

153
154
155
156
157
158
159
160
161
162
  def push_to_merge_request(merge_request, current_user, new_commits: [], existing_commits: [])
    new_commits = new_commits.map { |c| { short_id: c.short_id, title: c.title } }
    existing_commits = existing_commits.map { |c| { short_id: c.short_id, title: c.title } }
    recipients = NotificationRecipientService.build_recipients(merge_request, current_user, action: "push_to")

    recipients.each do |recipient|
      mailer.send(:push_to_merge_request_email, recipient.user.id, merge_request.id, current_user.id, recipient.reason, new_commits: new_commits, existing_commits: existing_commits).deliver_later
    end
  end

163
164
165
166
167
168
169
170
171
  # When a merge request is found to be unmergeable, we should send an email to:
  #
  #  * mr author
  #  * mr merge user if set
  #
  def merge_request_unmergeable(merge_request)
    merge_request_unmergeable_email(merge_request)
  end

172
173
174
175
176
177
178
179
180
181
182
183
184
  # When merge request text is updated, we should send an email to:
  #
  #  * newly mentioned project team members with notification level higher than Participating
  #
  def new_mentions_in_merge_request(merge_request, new_mentioned_users, current_user)
    new_mentions_in_resource_email(
      merge_request,
      new_mentioned_users,
      current_user,
      :new_mention_in_merge_request_email
    )
  end

185
  # When we reassign a merge_request we should send an email to:
186
  #
187
188
  #  * merge_request old assignee if their notification level is not Disabled
  #  * merge_request assignee if their notification level is not Disabled
189
  #  * users with custom level checked with "reassign merge request"
190
  #
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
  def reassigned_merge_request(merge_request, current_user, previous_assignee)
    recipients = NotificationRecipientService.build_recipients(
      merge_request,
      current_user,
      action: "reassign",
      previous_assignee: previous_assignee
    )

    recipients.each do |recipient|
      mailer.reassigned_merge_request_email(
        recipient.user.id,
        merge_request.id,
        previous_assignee&.id,
        current_user.id,
        recipient.reason
      ).deliver_later
    end
208
  end
209

210
  # When we add labels to a merge request we should send an email to:
211
  #
212
213
214
  #  * watchers of the mr's labels
  #
  def relabeled_merge_request(merge_request, added_labels, current_user)
http://jneen.net/'s avatar
http://jneen.net/ committed
215
    relabeled_resource_email(merge_request, added_labels, current_user, :relabeled_merge_request_email)
216
217
  end

218
219
220
221
222
223
224
225
  def removed_milestone_merge_request(merge_request, current_user)
    removed_milestone_resource_email(merge_request, current_user, :removed_milestone_merge_request_email)
  end

  def changed_milestone_merge_request(merge_request, new_milestone, current_user)
    changed_milestone_resource_email(merge_request, new_milestone, current_user, :changed_milestone_merge_request_email)
  end

226
  def close_mr(merge_request, current_user)
http://jneen.net/'s avatar
http://jneen.net/ committed
227
    close_resource_email(merge_request, current_user, :closed_merge_request_email)
228
229
  end

230
  def reopen_issue(issue, current_user)
http://jneen.net/'s avatar
http://jneen.net/ committed
231
    reopen_resource_email(issue, current_user, :issue_status_changed_email, 'reopened')
232
233
  end

234
  def merge_mr(merge_request, current_user)
Valery Sizov's avatar
Valery Sizov committed
235
236
237
    close_resource_email(
      merge_request,
      current_user,
238
      :merged_merge_request_email,
239
      skip_current_user: !merge_request.merge_when_pipeline_succeeds?
Valery Sizov's avatar
Valery Sizov committed
240
    )
241
242
  end

243
  def reopen_mr(merge_request, current_user)
Valery Sizov's avatar
Valery Sizov committed
244
245
    reopen_resource_email(
      merge_request,
246
      current_user,
247
      :merge_request_status_email,
Valery Sizov's avatar
Valery Sizov committed
248
249
      'reopened'
    )
250
251
  end

252
  def resolve_all_discussions(merge_request, current_user)
253
    recipients = NotificationRecipientService.build_recipients(
254
255
256
      merge_request,
      current_user,
      action: "resolve_all_discussions")
257
258

    recipients.each do |recipient|
259
      mailer.resolved_all_discussions_email(recipient.user.id, merge_request.id, current_user.id, recipient.reason).deliver_later
260
261
262
    end
  end

263
  # Notify new user with email after creation
264
  def new_user(user, token = nil)
265
266
    return true unless notifiable?(user, :mention)

Johannes Schleifenbaum's avatar
Johannes Schleifenbaum committed
267
    # Don't email omniauth created users
Valery Sizov's avatar
Valery Sizov committed
268
    mailer.new_user_email(user.id, token).deliver_later unless user.identities.any?
269
270
271
272
  end

  # Notify users on new note in system
  def new_note(note)
273
274
    return true unless note.noteable_type.present?

275
    # ignore gitlab service messages
Z.J. van de Weg's avatar
Z.J. van de Weg committed
276
    return true if note.cross_reference? && note.system?
277

278
279
280
281
    send_new_note_notifications(note)
  end

  def send_new_note_notifications(note)
Jarka Kadlecova's avatar
Jarka Kadlecova committed
282
    notify_method = "note_#{note.to_ability_name}_email".to_sym
283

http://jneen.net/'s avatar
http://jneen.net/ committed
284
    recipients = NotificationRecipientService.build_new_note_recipients(note)
285
    recipients.each do |recipient|
286
      mailer.send(notify_method, recipient.user.id, note.id).deliver_later
287
288
    end
  end
289

Rémy Coutable's avatar
Rémy Coutable committed
290
291
  # Members
  def new_access_request(member)
292
293
    return true unless member.notifiable?(:subscription)

294
295
296
    recipients = member.source.members.active_without_invites_and_requests.owners_and_maintainers
    if fallback_to_group_owners_maintainers?(recipients, member)
      recipients = member.source.group.members.active_without_invites_and_requests.owners_and_maintainers
297
298
299
    end

    recipients.each { |recipient| deliver_access_request_email(recipient, member) }
300
301
  end

Rémy Coutable's avatar
Rémy Coutable committed
302
  def decline_access_request(member)
303
304
    return true unless member.notifiable?(:subscription)

Rémy Coutable's avatar
Rémy Coutable committed
305
    mailer.member_access_denied_email(member.real_source_type, member.source_id, member.user_id).deliver_later
306
307
  end

Rémy Coutable's avatar
Rémy Coutable committed
308
  # Project invite
Douwe Maan's avatar
Douwe Maan committed
309
  def invite_project_member(project_member, token)
310
311
    return true unless project_member.notifiable?(:subscription)

312
    mailer.member_invited_email(project_member.real_source_type, project_member.id, token).deliver_later
Douwe Maan's avatar
Douwe Maan committed
313
314
315
  end

  def accept_project_invite(project_member)
316
317
    return true unless project_member.notifiable?(:subscription)

318
    mailer.member_invite_accepted_email(project_member.real_source_type, project_member.id).deliver_later
Douwe Maan's avatar
Douwe Maan committed
319
320
  end

Douwe Maan's avatar
Douwe Maan committed
321
  def decline_project_invite(project_member)
322
    mailer.member_invite_declined_email(
323
      project_member.real_source_type,
Valery Sizov's avatar
Valery Sizov committed
324
325
326
327
      project_member.project.id,
      project_member.invite_email,
      project_member.created_by_id
    ).deliver_later
Douwe Maan's avatar
Douwe Maan committed
328
329
  end

330
  def new_project_member(project_member)
331
    return true unless project_member.notifiable?(:mention, skip_read_ability: true)
332

333
    mailer.member_access_granted_email(project_member.real_source_type, project_member.id).deliver_later
334
335
  end

336
  def update_project_member(project_member)
337
338
    return true unless project_member.notifiable?(:mention)

339
    mailer.member_access_granted_email(project_member.real_source_type, project_member.id).deliver_later
340
  end
341

Rémy Coutable's avatar
Rémy Coutable committed
342
  # Group invite
Douwe Maan's avatar
Douwe Maan committed
343
  def invite_group_member(group_member, token)
344
    mailer.member_invited_email(group_member.real_source_type, group_member.id, token).deliver_later
Douwe Maan's avatar
Douwe Maan committed
345
346
347
  end

  def accept_group_invite(group_member)
348
    mailer.member_invite_accepted_email(group_member.real_source_type, group_member.id).deliver_later
Douwe Maan's avatar
Douwe Maan committed
349
350
  end

Douwe Maan's avatar
Douwe Maan committed
351
  def decline_group_invite(group_member)
352
353
354
    # always send this one, since it's a response to the user's own
    # action

355
    mailer.member_invite_declined_email(
356
      group_member.real_source_type,
Valery Sizov's avatar
Valery Sizov committed
357
358
359
360
      group_member.group.id,
      group_member.invite_email,
      group_member.created_by_id
    ).deliver_later
Douwe Maan's avatar
Douwe Maan committed
361
362
  end

363
  def new_group_member(group_member)
364
365
    return true unless group_member.notifiable?(:mention)

366
    mailer.member_access_granted_email(group_member.real_source_type, group_member.id).deliver_later
367
368
  end

369
  def update_group_member(group_member)
370
371
    return true unless group_member.notifiable?(:mention)

372
    mailer.member_access_granted_email(group_member.real_source_type, group_member.id).deliver_later
373
374
  end

375
  def project_was_moved(project, old_path_with_namespace)
376
    recipients = notifiable_users(project.team.members, :mention, project: project)
377
378

    recipients.each do |recipient|
Valery Sizov's avatar
Valery Sizov committed
379
380
381
382
383
      mailer.project_was_moved_email(
        project.id,
        recipient.id,
        old_path_with_namespace
      ).deliver_later
384
385
386
    end
  end

387
  def issue_moved(issue, new_issue, current_user)
http://jneen.net/'s avatar
http://jneen.net/ committed
388
    recipients = NotificationRecipientService.build_recipients(issue, current_user, action: 'moved')
389
390

    recipients.map do |recipient|
391
      email = mailer.issue_moved_email(recipient.user, issue, new_issue, current_user, recipient.reason)
392
393
394
395
396
      email.deliver_later
      email
    end
  end

397
  def project_exported(project, current_user)
398
399
    return true unless notifiable?(current_user, :mention, project: project)

400
401
402
403
    mailer.project_was_exported_email(current_user, project).deliver_later
  end

  def project_not_exported(project, current_user, errors)
404
405
    return true unless notifiable?(current_user, :mention, project: project)

406
407
408
    mailer.project_was_not_exported_email(current_user, project, errors).deliver_later
  end

409
410
411
412
413
  def pipeline_finished(pipeline, recipients = nil)
    email_template = "pipeline_#{pipeline.status}_email"

    return unless mailer.respond_to?(email_template)

414
    recipients ||= notifiable_users(
415
      [pipeline.user], :watch,
416
      custom_action: :"#{pipeline.status}_pipeline",
417
      target: pipeline
Sean McGivern's avatar
Sean McGivern committed
418
    ).map(&:notification_email)
419

420
421
422
    if recipients.any?
      mailer.public_send(email_template, pipeline, recipients).deliver_later
    end
423
424
  end

425
426
427
428
429
430
  def autodevops_disabled(pipeline, recipients)
    recipients.each do |recipient|
      mailer.autodevops_disabled_email(pipeline, recipient).deliver_later
    end
  end

431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
  def pages_domain_verification_succeeded(domain)
    recipients_for_pages_domain(domain).each do |user|
      mailer.pages_domain_verification_succeeded_email(domain, user).deliver_later
    end
  end

  def pages_domain_verification_failed(domain)
    recipients_for_pages_domain(domain).each do |user|
      mailer.pages_domain_verification_failed_email(domain, user).deliver_later
    end
  end

  def pages_domain_enabled(domain)
    recipients_for_pages_domain(domain).each do |user|
      mailer.pages_domain_enabled_email(domain, user).deliver_later
    end
  end

  def pages_domain_disabled(domain)
    recipients_for_pages_domain(domain).each do |user|
      mailer.pages_domain_disabled_email(domain, user).deliver_later
    end
  end

455
  def issue_due(issue)
Stuart Nelson's avatar
Stuart Nelson committed
456
457
458
    recipients = NotificationRecipientService.build_recipients(
      issue,
      issue.author,
459
460
      action: 'due',
      custom_action: :issue_due,
Stuart Nelson's avatar
Stuart Nelson committed
461
      skip_current_user: false
Stuart Nelson's avatar
Stuart Nelson committed
462
463
464
465
466
467
468
    )

    recipients.each do |recipient|
      mailer.send(:issue_due_email, recipient.user.id, issue.id, recipient.reason).deliver_later
    end
  end

469
470
471
472
473
474
475
476
  def repository_cleanup_success(project, user)
    mailer.send(:repository_cleanup_success_email, project, user).deliver_later
  end

  def repository_cleanup_failure(project, user, error)
    mailer.send(:repository_cleanup_failure_email, project, user, error).deliver_later
  end

477
478
  protected

http://jneen.net/'s avatar
http://jneen.net/ committed
479
480
  def new_resource_email(target, method)
    recipients = NotificationRecipientService.build_recipients(target, target.author, action: "new")
481
482

    recipients.each do |recipient|
483
      mailer.send(method, recipient.user.id, target.id, recipient.reason).deliver_later
484
485
486
    end
  end

http://jneen.net/'s avatar
http://jneen.net/ committed
487
488
  def new_mentions_in_resource_email(target, new_mentioned_users, current_user, method)
    recipients = NotificationRecipientService.build_recipients(target, current_user, action: "new")
489
    recipients = recipients.select {|r| new_mentioned_users.include?(r.user) }
490
491

    recipients.each do |recipient|
492
      mailer.send(method, recipient.user.id, target.id, current_user.id, recipient.reason).deliver_later
493
494
495
    end
  end

http://jneen.net/'s avatar
http://jneen.net/ committed
496
  def close_resource_email(target, current_user, method, skip_current_user: true)
497
    action = method == :merged_merge_request_email ? "merge" : "close"
498

499
    recipients = NotificationRecipientService.build_recipients(
500
501
502
503
504
      target,
      current_user,
      action: action,
      skip_current_user: skip_current_user
    )
505
506

    recipients.each do |recipient|
507
      mailer.send(method, recipient.user.id, target.id, current_user.id, recipient.reason).deliver_later
508
509
510
    end
  end

http://jneen.net/'s avatar
http://jneen.net/ committed
511
  def relabeled_resource_email(target, labels, current_user, method)
512
    recipients = labels.flat_map { |l| l.subscribers(target.project) }.uniq
513
    recipients = notifiable_users(
514
515
      recipients, :subscription,
      target: target,
http://jneen.net/'s avatar
http://jneen.net/ committed
516
      acting_user: current_user
517
518
    )

519
520
521
    label_names = labels.map(&:name)

    recipients.each do |recipient|
522
      mailer.send(method, recipient.id, target.id, label_names, current_user.id).deliver_later
523
524
525
    end
  end

526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
  def removed_milestone_resource_email(target, current_user, method)
    recipients = NotificationRecipientService.build_recipients(
      target,
      current_user,
      action: 'removed_milestone'
    )

    recipients.each do |recipient|
      mailer.send(method, recipient.user.id, target.id, current_user.id).deliver_later
    end
  end

  def changed_milestone_resource_email(target, milestone, current_user, method)
    recipients = NotificationRecipientService.build_recipients(
      target,
      current_user,
      action: 'changed_milestone'
    )

    recipients.each do |recipient|
      mailer.send(method, recipient.user.id, target.id, milestone, current_user.id).deliver_later
    end
  end

http://jneen.net/'s avatar
http://jneen.net/ committed
550
551
  def reopen_resource_email(target, current_user, method, status)
    recipients = NotificationRecipientService.build_recipients(target, current_user, action: "reopen")
552
553

    recipients.each do |recipient|
554
      mailer.send(method, recipient.user.id, target.id, status, current_user.id, recipient.reason).deliver_later
555
556
557
    end
  end

558
559
560
561
562
563
564
565
  def merge_request_unmergeable_email(merge_request)
    recipients = NotificationRecipientService.build_merge_request_unmergeable_recipients(merge_request)

    recipients.each do |recipient|
      mailer.merge_request_unmergeable_email(recipient.user.id, merge_request.id).deliver_later
    end
  end

566
  def mailer
Valery Sizov's avatar
Valery Sizov committed
567
    Notify
568
  end
569

570
571
  private

572
573
574
575
576
  def recipients_for_pages_domain(domain)
    project = domain.project

    return [] unless project

577
    notifiable_users(project.team.maintainers, :watch, target: project)
578
579
  end

580
581
582
583
584
585
586
  def notifiable?(*args)
    NotificationRecipientService.notifiable?(*args)
  end

  def notifiable_users(*args)
    NotificationRecipientService.notifiable_users(*args)
  end
587
588
589
590
591

  def deliver_access_request_email(recipient, member)
    mailer.member_access_requested_email(member.real_source_type, member.id, recipient.user.notification_email).deliver_later
  end

592
  def fallback_to_group_owners_maintainers?(recipients, member)
593
594
595
596
    return false if recipients.present?

    member.source.respond_to?(:group) && member.source.group
  end
597
end