event.rb 6.55 KB
Newer Older
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
1
class Event < ActiveRecord::Base
2
  include Sortable
3
  default_scope { where.not(author_id: nil) }
4

5 6 7 8 9 10 11 12 13
  CREATED   = 1
  UPDATED   = 2
  CLOSED    = 3
  REOPENED  = 4
  PUSHED    = 5
  COMMENTED = 6
  MERGED    = 7
  JOINED    = 8 # User joined project
  LEFT      = 9 # User left project
14
  DESTROYED = 10
15
  EXPIRED   = 11 # User left project due to expiry
16

17 18
  RESET_PROJECT_ACTIVITY_INTERVAL = 1.hour

Nihad Abbasov's avatar
Nihad Abbasov committed
19 20 21
  delegate :name, :email, to: :author, prefix: true, allow_nil: true
  delegate :title, to: :issue, prefix: true, allow_nil: true
  delegate :title, to: :merge_request, prefix: true, allow_nil: true
22
  delegate :title, to: :note, prefix: true, allow_nil: true
Nihad Abbasov's avatar
Nihad Abbasov committed
23

randx's avatar
randx committed
24
  belongs_to :author, class_name: "User"
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
25
  belongs_to :project
26
  belongs_to :target, polymorphic: true
27

randx's avatar
randx committed
28 29
  # For Hash only
  serialize :data
30

31 32 33
  # Callbacks
  after_create :reset_project_activity

Andrey Kumanyaev's avatar
Andrey Kumanyaev committed
34
  # Scopes
35
  scope :recent, -> { reorder(id: :desc) }
36
  scope :code_push, -> { where(action: PUSHED) }
37 38

  scope :in_projects, ->(projects) do
Josh Frye's avatar
Josh Frye committed
39
    where(project_id: projects.map(&:id)).recent
40 41
  end

42
  scope :with_associations, -> { includes(project: :namespace) }
43
  scope :for_milestone_id, ->(milestone_id) { where(target_type: "Milestone", target_id: milestone_id) }
44

Andrey Kumanyaev's avatar
Andrey Kumanyaev committed
45
  class << self
46 47 48 49 50
    def reset_event_cache_for(target)
      Event.where(target_id: target.id, target_type: target.class.to_s).
        order('id DESC').limit(100).
        update_all(updated_at: Time.now)
    end
51 52 53 54 55 56

    def contributions
      where("action = ? OR (target_type in (?) AND action in (?))",
            Event::PUSHED, ["MergeRequest", "Issue"],
            [Event::CREATED, Event::CLOSED, Event::MERGED])
    end
57

Yorick Peterse's avatar
Yorick Peterse committed
58 59 60
    def limit_recent(limit = 20, offset = nil)
      recent.limit(limit).offset(offset)
    end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
61 62
  end

63
  def visible_to_user?(user = nil)
64 65 66 67
    if push?
      true
    elsif membership_changed?
      true
68 69
    elsif created_project?
      true
70
    elsif issue? || issue_note?
71
      Ability.allowed?(user, :read_issue, note? ? note_target : target)
72 73
    elsif merge_request? || merge_request_note?
      Ability.allowed?(user, :read_merge_request, note? ? note_target : target)
74
    else
75
      milestone?
76
    end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
77 78
  end

79 80
  def project_name
    if project
81
      project.name_with_namespace
82
    else
Riyad Preukschas's avatar
Riyad Preukschas committed
83
      "(deleted project)"
84 85 86
    end
  end

87
  def target_title
88
    target.try(:title)
89 90 91 92
  end

  def created?
    action == CREATED
93 94
  end

95
  def push?
96
    action == PUSHED && valid_push?
97 98
  end

99
  def merged?
100
    action == MERGED
101 102
  end

103
  def closed?
104
    action == CLOSED
105 106 107
  end

  def reopened?
108 109 110 111 112 113 114 115 116 117 118
    action == REOPENED
  end

  def joined?
    action == JOINED
  end

  def left?
    action == LEFT
  end

119 120 121 122
  def expired?
    action == EXPIRED
  end

123 124 125 126
  def destroyed?
    action == DESTROYED
  end

127 128 129 130 131
  def commented?
    action == COMMENTED
  end

  def membership_changed?
132
    joined? || left? || expired?
133 134
  end

135
  def created_project?
136
    created? && !target && target_type.nil?
137 138 139 140 141 142
  end

  def created_target?
    created? && target
  end

143 144 145 146 147
  def milestone?
    target_type == "Milestone"
  end

  def note?
148
    target.is_a?(Note)
149 150
  end

151
  def issue?
152
    target_type == "Issue"
153 154
  end

155
  def merge_request?
156
    target_type == "MergeRequest"
157 158
  end

159 160
  def milestone
    target if milestone?
161 162
  end

163
  def issue
164
    target if issue?
165 166 167
  end

  def merge_request
168
    target if merge_request?
169 170
  end

171
  def note
172
    target if note?
173 174
  end

175
  def action_name
176 177 178 179 180 181 182 183 184
    if push?
      if new_ref?
        "pushed new"
      elsif rm_ref?
        "deleted"
      else
        "pushed to"
      end
    elsif closed?
185 186
      "closed"
    elsif merged?
187
      "accepted"
188 189
    elsif joined?
      'joined'
Alex Denisov's avatar
Alex Denisov committed
190 191
    elsif left?
      'left'
192 193
    elsif expired?
      'removed due to membership expiration from'
194 195
    elsif destroyed?
      'destroyed'
196 197
    elsif commented?
      "commented on"
198
    elsif created_project?
199
      if project.external_import?
200 201 202 203
        "imported"
      else
        "created"
      end
204
    else
205
      "opened"
206 207
    end
  end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
208 209

  def valid_push?
210
    data[:ref] && ref_name.present?
211
  rescue
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
212 213 214 215
    false
  end

  def tag?
216
    Gitlab::Git.tag_ref?(data[:ref])
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
217 218 219
  end

  def branch?
220
    Gitlab::Git.branch_ref?(data[:ref])
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
221 222 223
  end

  def new_ref?
224
    Gitlab::Git.blank_ref?(commit_from)
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
225 226 227
  end

  def rm_ref?
228
    Gitlab::Git.blank_ref?(commit_to)
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251
  end

  def md_ref?
    !(rm_ref? || new_ref?)
  end

  def commit_from
    data[:before]
  end

  def commit_to
    data[:after]
  end

  def ref_name
    if tag?
      tag_name
    else
      branch_name
    end
  end

  def branch_name
252
    @branch_name ||= Gitlab::Git.ref_name(data[:ref])
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
253 254 255
  end

  def tag_name
256
    @tag_name ||= Gitlab::Git.ref_name(data[:ref])
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
257 258 259 260
  end

  # Max 20 commits from push DESC
  def commits
261
    @commits ||= (data[:commits] || []).reverse
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
262 263 264 265 266 267 268 269 270 271 272
  end

  def commits_count
    data[:total_commits_count] || commits.count || 0
  end

  def ref_type
    tag? ? "tag" : "branch"
  end

  def push_with_commits?
273
    !commits.empty? && commit_from && commit_to
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
274 275 276 277 278 279
  end

  def last_push_to_non_root?
    branch? && project.default_branch != branch_name
  end

280 281 282 283
  def target_iid
    target.respond_to?(:iid) ? target.iid : target_id
  end

284
  def commit_note?
285
    target.for_commit?
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
286 287
  end

288
  def issue_note?
289
    note? && target && target.for_issue?
290 291
  end

292 293 294 295
  def merge_request_note?
    note? && target && target.for_merge_request?
  end

296
  def project_snippet_note?
297
    target.for_snippet?
Andrew8xx8's avatar
Andrew8xx8 committed
298 299
  end

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
300 301 302 303 304
  def note_target
    target.noteable
  end

  def note_target_id
305
    if commit_note?
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
306 307 308 309 310 311
      target.commit_id
    else
      target.noteable_id.to_s
    end
  end

312 313 314 315 316 317
  def note_target_reference
    return unless note_target

    # Commit#to_reference returns the full SHA, but we want the short one here
    if commit_note?
      note_target.short_id
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
318
    else
319 320
      note_target.to_reference
    end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
321 322
  end

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
323 324 325 326 327 328 329
  def note_target_type
    if target.noteable_type.present?
      target.noteable_type.titleize
    else
      "Wall"
    end.downcase
  end
330 331 332

  def body?
    if push?
333
      push_with_commits? || rm_ref?
334 335 336 337 338 339
    elsif note?
      true
    else
      target.respond_to? :title
    end
  end
340 341

  def reset_project_activity
342 343
    return unless project

344
    # Don't bother updating if we know the project was updated recently.
345
    return if recent_update?
346

347 348 349 350
    # At this point it's possible for multiple threads/processes to try to
    # update the project. Only one query should actually perform the update,
    # hence we add the extra WHERE clause for last_activity_at.
    Project.unscoped.where(id: project_id).
351
      where('last_activity_at <= ?', RESET_PROJECT_ACTIVITY_INTERVAL.ago).
352
      update_all(last_activity_at: created_at)
353 354 355 356 357 358 359
  end

  private

  def recent_update?
    project.last_activity_at > RESET_PROJECT_ACTIVITY_INTERVAL.ago
  end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
360
end