summaryrefslogtreecommitdiff
path: root/app/models/sent_notification.rb
blob: e65b3df0fb6c7d6607a9e9c323c2780e5b5dbd0a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# frozen_string_literal: true

class SentNotification < ActiveRecord::Base
  serialize :position, Gitlab::Diff::Position # rubocop:disable Cop/ActiveRecordSerialize

  belongs_to :project
  belongs_to :noteable, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations
  belongs_to :recipient, class_name: "User"

  validates :recipient, presence: true
  validates :reply_key, presence: true, uniqueness: true
  validates :noteable_id, presence: true, unless: :for_commit?
  validates :commit_id, presence: true, if: :for_commit?
  validates :in_reply_to_discussion_id, format: { with: /\A\h{40}\z/, allow_nil: true }
  validate :note_valid

  after_save :keep_around_commit, if: :for_commit?

  class << self
    def reply_key
      SecureRandom.hex(16)
    end

    def for(reply_key)
      find_by(reply_key: reply_key)
    end

    def record(noteable, recipient_id, reply_key = self.reply_key, attrs = {})
      noteable_id = nil
      commit_id = nil
      if noteable.is_a?(Commit)
        commit_id = noteable.id
      else
        noteable_id = noteable.id
      end

      attrs.reverse_merge!(
        project: noteable.project,
        recipient_id: recipient_id,
        reply_key: reply_key,

        noteable_type: noteable.class.name,
        noteable_id: noteable_id,
        commit_id: commit_id
      )

      create(attrs)
    end

    def record_note(note, recipient_id, reply_key = self.reply_key, attrs = {})
      attrs[:in_reply_to_discussion_id] = note.discussion_id

      record(note.noteable, recipient_id, reply_key, attrs)
    end
  end

  def unsubscribable?
    !(for_commit? || for_snippet?)
  end

  def for_commit?
    noteable_type == "Commit"
  end

  def for_snippet?
    noteable_type.end_with?('Snippet')
  end

  def noteable
    if for_commit?
      project.commit(commit_id) rescue nil
    else
      super
    end
  end

  def position=(new_position)
    if new_position.is_a?(String)
      new_position = JSON.parse(new_position) rescue nil
    end

    if new_position.is_a?(Hash)
      new_position = new_position.with_indifferent_access
      new_position = Gitlab::Diff::Position.new(new_position)
    end

    super(new_position)
  end

  def to_param
    self.reply_key
  end

  def create_reply(message, dryrun: false)
    klass = dryrun ? Notes::BuildService : Notes::CreateService
    klass.new(self.project, self.recipient, reply_params.merge(note: message)).execute
  end

  private

  def reply_params
    attrs = {
      noteable_type: self.noteable_type,
      noteable_id: self.noteable_id,
      commit_id: self.commit_id
    }

    if self.in_reply_to_discussion_id.present?
      attrs[:in_reply_to_discussion_id] = self.in_reply_to_discussion_id
    else
      # Remove in GitLab 10.0, when we will not support replying to SentNotifications
      # that don't have `in_reply_to_discussion_id` anymore.
      attrs.merge!(
        type: self.note_type,

        # LegacyDiffNote
        line_code: self.line_code,

        # DiffNote
        position: self.position.to_json
      )
    end

    attrs
  end

  def note_valid
    note = create_reply('Test', dryrun: true)

    unless note.valid?
      self.errors.add(:base, "Note parameters are invalid: #{note.errors.full_messages.to_sentence}")
    end
  end

  def keep_around_commit
    project.repository.keep_around(self.commit_id)
  end
end