BigW Consortium Gitlab

event.rb 7.7 KB
Newer Older
1
class Event < ActiveRecord::Base
2
  include Sortable
3
  default_scope { reorder(nil).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 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
  ACTIONS = HashWithIndifferentAccess.new(
    created:    CREATED,
    updated:    UPDATED,
    closed:     CLOSED,
    reopened:   REOPENED,
    pushed:     PUSHED,
    commented:  COMMENTED,
    merged:     MERGED,
    joined:     JOINED,
    left:       LEFT,
    destroyed:  DESTROYED,
    expired:    EXPIRED
  ).freeze

  TARGET_TYPES = HashWithIndifferentAccess.new(
    issue:          Issue,
    milestone:      Milestone,
    merge_request:  MergeRequest,
    note:           Note,
    project:        Project,
    snippet:        Snippet,
    user:           User
  ).freeze

41 42
  RESET_PROJECT_ACTIVITY_INTERVAL = 1.hour

43
  delegate :name, :email, :public_email, :username, to: :author, prefix: true, allow_nil: true
44 45
  delegate :title, to: :issue, prefix: true, allow_nil: true
  delegate :title, to: :merge_request, prefix: true, allow_nil: true
46
  delegate :title, to: :note, prefix: true, allow_nil: true
47

randx committed
48
  belongs_to :author, class_name: "User"
49
  belongs_to :project
50
  belongs_to :target, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations
51

52
  # For Hash only
53
  serialize :data # rubocop:disable Cop/ActiveRecordSerialize
54

55 56
  # Callbacks
  after_create :reset_project_activity
57
  after_create :set_last_repository_updated_at, if: :push?
58

Andrey Kumanyaev committed
59
  # Scopes
60
  scope :recent, -> { reorder(id: :desc) }
61
  scope :code_push, -> { where(action: PUSHED) }
62 63

  scope :in_projects, ->(projects) do
64
    where(project_id: projects.pluck(:id)).recent
65 66
  end

67
  scope :with_associations, -> { includes(:author, :project, project: :namespace).preload(:target) }
68
  scope :for_milestone_id, ->(milestone_id) { where(target_type: "Milestone", target_id: milestone_id) }
69

Andrey Kumanyaev committed
70
  class << self
71
    # Update Gitlab::ContributionsCalendar#activity_dates if this changes
72
    def contributions
73 74
      where("action = ? OR (target_type IN (?) AND action IN (?)) OR (target_type = ? AND action = ?)",
            Event::PUSHED,
Douwe Maan committed
75
            %w(MergeRequest Issue), [Event::CREATED, Event::CLOSED, Event::MERGED],
76
            "Note", Event::COMMENTED)
77
    end
78

79 80 81
    def limit_recent(limit = 20, offset = nil)
      recent.limit(limit).offset(offset)
    end
82 83 84 85 86 87 88 89

    def actions
      ACTIONS.keys
    end

    def target_types
      TARGET_TYPES.keys
    end
Dmitriy Zaporozhets committed
90 91
  end

92
  def visible_to_user?(user = nil)
93
    if push? || commit_note?
94
      Ability.allowed?(user, :download_code, project)
95 96
    elsif membership_changed?
      true
97 98
    elsif created_project?
      true
99
    elsif issue? || issue_note?
100
      Ability.allowed?(user, :read_issue, note? ? note_target : target)
101 102
    elsif merge_request? || merge_request_note?
      Ability.allowed?(user, :read_merge_request, note? ? note_target : target)
103
    else
104
      milestone?
105
    end
106 107
  end

108 109
  def project_name
    if project
110
      project.name_with_namespace
111
    else
112
      "(deleted project)"
113 114 115
    end
  end

116
  def target_title
117
    target.try(:title)
118 119 120 121
  end

  def created?
    action == CREATED
122 123
  end

124
  def push?
125
    action == PUSHED && valid_push?
126 127
  end

128
  def merged?
129
    action == MERGED
130 131
  end

132
  def closed?
133
    action == CLOSED
134 135 136
  end

  def reopened?
137 138 139 140 141 142 143 144 145 146 147
    action == REOPENED
  end

  def joined?
    action == JOINED
  end

  def left?
    action == LEFT
  end

148 149 150 151
  def expired?
    action == EXPIRED
  end

152 153 154 155
  def destroyed?
    action == DESTROYED
  end

156 157 158 159 160
  def commented?
    action == COMMENTED
  end

  def membership_changed?
161
    joined? || left? || expired?
162 163
  end

164
  def created_project?
165
    created? && !target && target_type.nil?
166 167 168 169 170 171
  end

  def created_target?
    created? && target
  end

172 173 174 175 176
  def milestone?
    target_type == "Milestone"
  end

  def note?
177
    target.is_a?(Note)
178 179
  end

180
  def issue?
181
    target_type == "Issue"
182 183
  end

184
  def merge_request?
185
    target_type == "MergeRequest"
186 187
  end

188 189
  def milestone
    target if milestone?
190 191
  end

192
  def issue
193
    target if issue?
194 195 196
  end

  def merge_request
197
    target if merge_request?
198 199
  end

200
  def note
201
    target if note?
202 203
  end

204
  def action_name
205 206 207 208 209 210 211 212 213
    if push?
      if new_ref?
        "pushed new"
      elsif rm_ref?
        "deleted"
      else
        "pushed to"
      end
    elsif closed?
214 215
      "closed"
    elsif merged?
216
      "accepted"
217 218
    elsif joined?
      'joined'
219 220
    elsif left?
      'left'
221 222
    elsif expired?
      'removed due to membership expiration from'
223 224
    elsif destroyed?
      'destroyed'
225 226
    elsif commented?
      "commented on"
227
    elsif created_project?
228
      if project.external_import?
229 230 231 232
        "imported"
      else
        "created"
      end
233
    else
234
      "opened"
235 236
    end
  end
Dmitriy Zaporozhets committed
237 238

  def valid_push?
239
    data[:ref] && ref_name.present?
240
  rescue
Dmitriy Zaporozhets committed
241 242 243 244
    false
  end

  def tag?
245
    Gitlab::Git.tag_ref?(data[:ref])
Dmitriy Zaporozhets committed
246 247 248
  end

  def branch?
249
    Gitlab::Git.branch_ref?(data[:ref])
Dmitriy Zaporozhets committed
250 251 252
  end

  def new_ref?
253
    Gitlab::Git.blank_ref?(commit_from)
Dmitriy Zaporozhets committed
254 255 256
  end

  def rm_ref?
257
    Gitlab::Git.blank_ref?(commit_to)
Dmitriy Zaporozhets committed
258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280
  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
281
    @branch_name ||= Gitlab::Git.ref_name(data[:ref])
Dmitriy Zaporozhets committed
282 283 284
  end

  def tag_name
285
    @tag_name ||= Gitlab::Git.ref_name(data[:ref])
Dmitriy Zaporozhets committed
286 287 288 289
  end

  # Max 20 commits from push DESC
  def commits
290
    @commits ||= (data[:commits] || []).reverse
Dmitriy Zaporozhets committed
291 292 293 294 295 296 297 298 299 300 301
  end

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

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

  def push_with_commits?
302
    !commits.empty? && commit_from && commit_to
Dmitriy Zaporozhets committed
303 304 305 306 307 308
  end

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

309 310 311 312
  def target_iid
    target.respond_to?(:iid) ? target.iid : target_id
  end

313
  def commit_note?
314
    note? && target && target.for_commit?
Dmitriy Zaporozhets committed
315 316
  end

317
  def issue_note?
318
    note? && target && target.for_issue?
319 320
  end

321 322 323 324
  def merge_request_note?
    note? && target && target.for_merge_request?
  end

325
  def project_snippet_note?
326
    note? && target && target.for_snippet?
Andrew8xx8 committed
327 328
  end

Dmitriy Zaporozhets committed
329 330 331 332 333
  def note_target
    target.noteable
  end

  def note_target_id
334
    if commit_note?
Dmitriy Zaporozhets committed
335 336 337 338 339 340
      target.commit_id
    else
      target.noteable_id.to_s
    end
  end

341 342 343 344 345 346
  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 committed
347
    else
348 349
      note_target.to_reference
    end
Dmitriy Zaporozhets committed
350 351
  end

Dmitriy Zaporozhets committed
352 353 354 355 356 357 358
  def note_target_type
    if target.noteable_type.present?
      target.noteable_type.titleize
    else
      "Wall"
    end.downcase
  end
359 360 361

  def body?
    if push?
362
      push_with_commits? || rm_ref?
363 364 365 366 367 368
    elsif note?
      true
    else
      target.respond_to? :title
    end
  end
369 370

  def reset_project_activity
371 372
    return unless project

373
    # Don't bother updating if we know the project was updated recently.
374
    return if recent_update?
375

376 377 378
    # 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.
379 380 381
    Project.unscoped.where(id: project_id)
      .where('last_activity_at <= ?', RESET_PROJECT_ACTIVITY_INTERVAL.ago)
      .update_all(last_activity_at: created_at)
382 383
  end

384 385 386 387
  def authored_by?(user)
    user ? author_id == user.id : false
  end

388 389 390 391 392
  private

  def recent_update?
    project.last_activity_at > RESET_PROJECT_ACTIVITY_INTERVAL.ago
  end
393 394

  def set_last_repository_updated_at
395 396
    Project.unscoped.where(id: project_id)
      .update_all(last_repository_updated_at: created_at)
397
  end
398
end