PageRenderTime 110ms CodeModel.GetById 39ms RepoModel.GetById 1ms app.codeStats 0ms

/app/models/event.rb

https://gitlab.com/alexkeramidas/gitlab-ce
Ruby | 403 lines | 304 code | 74 blank | 25 comment | 50 complexity | 9767f64448608ea83c913f9428ccd299 MD5 | raw file
  1. class Event < ActiveRecord::Base
  2. include Sortable
  3. include IgnorableColumn
  4. default_scope { reorder(nil) }
  5. CREATED = 1
  6. UPDATED = 2
  7. CLOSED = 3
  8. REOPENED = 4
  9. PUSHED = 5
  10. COMMENTED = 6
  11. MERGED = 7
  12. JOINED = 8 # User joined project
  13. LEFT = 9 # User left project
  14. DESTROYED = 10
  15. EXPIRED = 11 # User left project due to expiry
  16. ACTIONS = HashWithIndifferentAccess.new(
  17. created: CREATED,
  18. updated: UPDATED,
  19. closed: CLOSED,
  20. reopened: REOPENED,
  21. pushed: PUSHED,
  22. commented: COMMENTED,
  23. merged: MERGED,
  24. joined: JOINED,
  25. left: LEFT,
  26. destroyed: DESTROYED,
  27. expired: EXPIRED
  28. ).freeze
  29. TARGET_TYPES = HashWithIndifferentAccess.new(
  30. issue: Issue,
  31. milestone: Milestone,
  32. merge_request: MergeRequest,
  33. note: Note,
  34. project: Project,
  35. snippet: Snippet,
  36. user: User
  37. ).freeze
  38. RESET_PROJECT_ACTIVITY_INTERVAL = 1.hour
  39. delegate :name, :email, :public_email, :username, to: :author, prefix: true, allow_nil: true
  40. delegate :title, to: :issue, prefix: true, allow_nil: true
  41. delegate :title, to: :merge_request, prefix: true, allow_nil: true
  42. delegate :title, to: :note, prefix: true, allow_nil: true
  43. belongs_to :author, class_name: "User"
  44. belongs_to :project
  45. belongs_to :target, -> {
  46. # If the association for "target" defines an "author" association we want to
  47. # eager-load this so Banzai & friends don't end up performing N+1 queries to
  48. # get the authors of notes, issues, etc. (likewise for "noteable").
  49. incs = %i(author noteable).select do |a|
  50. reflections['events'].active_record.reflect_on_association(a)
  51. end
  52. incs.reduce(self) { |obj, a| obj.includes(a) }
  53. }, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations
  54. has_one :push_event_payload
  55. # Callbacks
  56. after_create :reset_project_activity
  57. after_create :set_last_repository_updated_at, if: :push?
  58. after_create :track_user_interacted_projects
  59. # Scopes
  60. scope :recent, -> { reorder(id: :desc) }
  61. scope :code_push, -> { where(action: PUSHED) }
  62. scope :in_projects, -> (projects) do
  63. sub_query = projects
  64. .except(:order)
  65. .select(1)
  66. .where('projects.id = events.project_id')
  67. where('EXISTS (?)', sub_query).recent
  68. end
  69. scope :with_associations, -> do
  70. # We're using preload for "push_event_payload" as otherwise the association
  71. # is not always available (depending on the query being built).
  72. includes(:author, :project, project: :namespace)
  73. .preload(:target, :push_event_payload)
  74. end
  75. scope :for_milestone_id, ->(milestone_id) { where(target_type: "Milestone", target_id: milestone_id) }
  76. # Authors are required as they're used to display who pushed data.
  77. #
  78. # We're just validating the presence of the ID here as foreign key constraints
  79. # should ensure the ID points to a valid user.
  80. validates :author_id, presence: true
  81. self.inheritance_column = 'action'
  82. class << self
  83. def model_name
  84. ActiveModel::Name.new(self, nil, 'event')
  85. end
  86. def find_sti_class(action)
  87. if action.to_i == PUSHED
  88. PushEvent
  89. else
  90. Event
  91. end
  92. end
  93. # Remove this method when removing Gitlab.rails5? code.
  94. def subclass_from_attributes(attrs)
  95. return super if Gitlab.rails5?
  96. # Without this Rails will keep calling this method on the returned class,
  97. # resulting in an infinite loop.
  98. return unless self == Event
  99. action = attrs.with_indifferent_access[inheritance_column].to_i
  100. PushEvent if action == PUSHED
  101. end
  102. # Update Gitlab::ContributionsCalendar#activity_dates if this changes
  103. def contributions
  104. where("action = ? OR (target_type IN (?) AND action IN (?)) OR (target_type = ? AND action = ?)",
  105. Event::PUSHED,
  106. %w(MergeRequest Issue), [Event::CREATED, Event::CLOSED, Event::MERGED],
  107. "Note", Event::COMMENTED)
  108. end
  109. def limit_recent(limit = 20, offset = nil)
  110. recent.limit(limit).offset(offset)
  111. end
  112. def actions
  113. ACTIONS.keys
  114. end
  115. def target_types
  116. TARGET_TYPES.keys
  117. end
  118. end
  119. def visible_to_user?(user = nil)
  120. if push? || commit_note?
  121. Ability.allowed?(user, :download_code, project)
  122. elsif membership_changed?
  123. true
  124. elsif created_project?
  125. true
  126. elsif issue? || issue_note?
  127. Ability.allowed?(user, :read_issue, note? ? note_target : target)
  128. elsif merge_request? || merge_request_note?
  129. Ability.allowed?(user, :read_merge_request, note? ? note_target : target)
  130. else
  131. milestone?
  132. end
  133. end
  134. def project_name
  135. if project
  136. project.full_name
  137. else
  138. "(deleted project)"
  139. end
  140. end
  141. def target_title
  142. target.try(:title)
  143. end
  144. def created?
  145. action == CREATED
  146. end
  147. def push?
  148. false
  149. end
  150. def merged?
  151. action == MERGED
  152. end
  153. def closed?
  154. action == CLOSED
  155. end
  156. def reopened?
  157. action == REOPENED
  158. end
  159. def joined?
  160. action == JOINED
  161. end
  162. def left?
  163. action == LEFT
  164. end
  165. def expired?
  166. action == EXPIRED
  167. end
  168. def destroyed?
  169. action == DESTROYED
  170. end
  171. def commented?
  172. action == COMMENTED
  173. end
  174. def membership_changed?
  175. joined? || left? || expired?
  176. end
  177. def created_project?
  178. created? && !target && target_type.nil?
  179. end
  180. def created_target?
  181. created? && target
  182. end
  183. def milestone?
  184. target_type == "Milestone"
  185. end
  186. def note?
  187. target.is_a?(Note)
  188. end
  189. def issue?
  190. target_type == "Issue"
  191. end
  192. def merge_request?
  193. target_type == "MergeRequest"
  194. end
  195. def milestone
  196. target if milestone?
  197. end
  198. def issue
  199. target if issue?
  200. end
  201. def merge_request
  202. target if merge_request?
  203. end
  204. def note
  205. target if note?
  206. end
  207. def action_name
  208. if push?
  209. push_action_name
  210. elsif closed?
  211. "closed"
  212. elsif merged?
  213. "accepted"
  214. elsif joined?
  215. 'joined'
  216. elsif left?
  217. 'left'
  218. elsif expired?
  219. 'removed due to membership expiration from'
  220. elsif destroyed?
  221. 'destroyed'
  222. elsif commented?
  223. "commented on"
  224. elsif created_project?
  225. created_project_action_name
  226. else
  227. "opened"
  228. end
  229. end
  230. def target_iid
  231. target.respond_to?(:iid) ? target.iid : target_id
  232. end
  233. def commit_note?
  234. note? && target && target.for_commit?
  235. end
  236. def issue_note?
  237. note? && target && target.for_issue?
  238. end
  239. def merge_request_note?
  240. note? && target && target.for_merge_request?
  241. end
  242. def project_snippet_note?
  243. note? && target && target.for_snippet?
  244. end
  245. def note_target
  246. target.noteable
  247. end
  248. def note_target_id
  249. if commit_note?
  250. target.commit_id
  251. else
  252. target.noteable_id.to_s
  253. end
  254. end
  255. def note_target_reference
  256. return unless note_target
  257. # Commit#to_reference returns the full SHA, but we want the short one here
  258. if commit_note?
  259. note_target.short_id
  260. else
  261. note_target.to_reference
  262. end
  263. end
  264. def note_target_type
  265. if target.noteable_type.present?
  266. target.noteable_type.titleize
  267. else
  268. "Wall"
  269. end.downcase
  270. end
  271. def body?
  272. if push?
  273. push_with_commits?
  274. elsif note?
  275. true
  276. else
  277. target.respond_to? :title
  278. end
  279. end
  280. def reset_project_activity
  281. return unless project
  282. # Don't bother updating if we know the project was updated recently.
  283. return if recent_update?
  284. # At this point it's possible for multiple threads/processes to try to
  285. # update the project. Only one query should actually perform the update,
  286. # hence we add the extra WHERE clause for last_activity_at.
  287. Project.unscoped.where(id: project_id)
  288. .where('last_activity_at <= ?', RESET_PROJECT_ACTIVITY_INTERVAL.ago)
  289. .update_all(last_activity_at: created_at)
  290. end
  291. def authored_by?(user)
  292. user ? author_id == user.id : false
  293. end
  294. def to_partial_path
  295. # We are intentionally using `Event` rather than `self.class` so that
  296. # subclasses also use the `Event` implementation.
  297. Event._to_partial_path
  298. end
  299. private
  300. def push_action_name
  301. if new_ref?
  302. "pushed new"
  303. elsif rm_ref?
  304. "deleted"
  305. else
  306. "pushed to"
  307. end
  308. end
  309. def created_project_action_name
  310. if project.external_import?
  311. "imported"
  312. else
  313. "created"
  314. end
  315. end
  316. def recent_update?
  317. project.last_activity_at > RESET_PROJECT_ACTIVITY_INTERVAL.ago
  318. end
  319. def set_last_repository_updated_at
  320. Project.unscoped.where(id: project_id)
  321. .update_all(last_repository_updated_at: created_at)
  322. end
  323. def track_user_interacted_projects
  324. # Note the call to .available? is due to earlier migrations
  325. # that would otherwise conflict with the call to .track
  326. # (because the table does not exist yet).
  327. UserInteractedProject.track(self) if UserInteractedProject.available?
  328. end
  329. end