debian-mirror-gitlab/app/models/event.rb

450 lines
10 KiB
Ruby
Raw Normal View History

2018-11-18 11:00:15 +05:30
# frozen_string_literal: true
2019-07-07 11:18:12 +05:30
class Event < ApplicationRecord
2015-04-26 12:48:37 +05:30
include Sortable
2018-12-05 23:21:45 +05:30
include FromUnion
2019-12-04 20:38:33 +05:30
include Presentable
2020-03-13 15:44:24 +05:30
include DeleteWithLimit
include CreatedAtFilterable
2020-04-22 19:07:51 +05:30
include Gitlab::Utils::StrongMemoize
2020-06-23 00:09:42 +05:30
include UsageStatistics
2020-10-24 23:57:45 +05:30
include ShaAttribute
2019-12-04 20:38:33 +05:30
2020-06-23 00:09:42 +05:30
default_scope { reorder(nil) } # rubocop:disable Cop/DefaultScope
2014-09-02 18:07:02 +05:30
2017-09-10 17:25:29 +05:30
ACTIONS = HashWithIndifferentAccess.new(
2020-06-23 00:09:42 +05:30
created: 1,
updated: 2,
closed: 3,
reopened: 4,
pushed: 5,
commented: 6,
merged: 7,
joined: 8, # User joined project
left: 9, # User left project
destroyed: 10,
expired: 11, # User left project due to expiry
approved: 12,
archived: 13 # Recoverable deletion
2017-09-10 17:25:29 +05:30
).freeze
2020-06-23 00:09:42 +05:30
private_constant :ACTIONS
WIKI_ACTIONS = [:created, :updated, :destroyed].freeze
DESIGN_ACTIONS = [:created, :updated, :destroyed, :archived].freeze
2020-04-22 19:07:51 +05:30
2017-09-10 17:25:29 +05:30
TARGET_TYPES = HashWithIndifferentAccess.new(
issue: Issue,
milestone: Milestone,
merge_request: MergeRequest,
note: Note,
project: Project,
snippet: Snippet,
2020-04-22 19:07:51 +05:30
user: User,
2020-06-23 00:09:42 +05:30
wiki: WikiPage::Meta,
design: DesignManagement::Design
2017-09-10 17:25:29 +05:30
).freeze
2016-09-29 09:46:39 +05:30
RESET_PROJECT_ACTIVITY_INTERVAL = 1.hour
2018-11-08 19:23:39 +05:30
REPOSITORY_UPDATED_AT_INTERVAL = 5.minutes
2016-09-29 09:46:39 +05:30
2020-10-24 23:57:45 +05:30
sha_attribute :fingerprint
2020-06-23 00:09:42 +05:30
enum action: ACTIONS, _suffix: true
2017-08-17 22:00:37 +05:30
delegate :name, :email, :public_email, :username, to: :author, prefix: true, allow_nil: true
2014-09-02 18:07:02 +05:30
delegate :title, to: :issue, prefix: true, allow_nil: true
delegate :title, to: :merge_request, prefix: true, allow_nil: true
delegate :title, to: :note, prefix: true, allow_nil: true
2020-06-23 00:09:42 +05:30
delegate :title, to: :design, prefix: true, allow_nil: true
2014-09-02 18:07:02 +05:30
belongs_to :author, class_name: "User"
belongs_to :project
2019-12-04 20:38:33 +05:30
belongs_to :group
2014-09-02 18:07:02 +05:30
2018-03-17 18:26:18 +05:30
belongs_to :target, -> {
# If the association for "target" defines an "author" association we want to
# eager-load this so Banzai & friends don't end up performing N+1 queries to
2018-05-09 12:01:36 +05:30
# get the authors of notes, issues, etc. (likewise for "noteable").
incs = %i(author noteable).select do |a|
reflections['events'].active_record.reflect_on_association(a)
2018-03-17 18:26:18 +05:30
end
2018-05-09 12:01:36 +05:30
incs.reduce(self) { |obj, a| obj.includes(a) }
2018-03-17 18:26:18 +05:30
}, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations
has_one :push_event_payload
2014-09-02 18:07:02 +05:30
# Callbacks
after_create :reset_project_activity
2019-09-04 21:01:54 +05:30
after_create :set_last_repository_updated_at, if: :push_action?
2020-06-23 00:09:42 +05:30
after_create ->(event) { UserInteractedProject.track(event) }
2014-09-02 18:07:02 +05:30
# Scopes
2015-11-26 14:37:03 +05:30
scope :recent, -> { reorder(id: :desc) }
2020-04-22 19:07:51 +05:30
scope :for_wiki_page, -> { where(target_type: 'WikiPage::Meta') }
2020-06-23 00:09:42 +05:30
scope :for_design, -> { where(target_type: 'DesignManagement::Design') }
2020-10-24 23:57:45 +05:30
scope :for_fingerprint, ->(fingerprint) do
fingerprint.present? ? where(fingerprint: fingerprint) : none
end
scope :for_action, ->(action) { where(action: action) }
2020-04-22 19:07:51 +05:30
2017-09-10 17:25:29 +05:30
scope :with_associations, -> do
# We're using preload for "push_event_payload" as otherwise the association
# is not always available (depending on the query being built).
2019-02-15 15:39:39 +05:30
includes(:author, :project, project: [:project_feature, :import_data, :namespace])
2017-09-10 17:25:29 +05:30
.preload(:target, :push_event_payload)
2016-04-02 18:10:28 +05:30
end
2015-09-25 12:07:36 +05:30
scope :for_milestone_id, ->(milestone_id) { where(target_type: "Milestone", target_id: milestone_id) }
2020-05-24 23:13:21 +05:30
scope :for_wiki_meta, ->(meta) { where(target_type: 'WikiPage::Meta', target_id: meta.id) }
scope :created_at, ->(time) { where(created_at: time) }
2014-09-02 18:07:02 +05:30
2018-03-17 18:26:18 +05:30
# Authors are required as they're used to display who pushed data.
#
# We're just validating the presence of the ID here as foreign key constraints
# should ensure the ID points to a valid user.
validates :author_id, presence: true
2020-06-23 00:09:42 +05:30
validates :action_enum_value,
if: :design?,
inclusion: {
in: actions.values_at(*DESIGN_ACTIONS),
message: ->(event, _data) { "#{event.action} is not a valid design action" }
}
2017-09-10 17:25:29 +05:30
self.inheritance_column = 'action'
2014-09-02 18:07:02 +05:30
class << self
2017-09-10 17:25:29 +05:30
def model_name
ActiveModel::Name.new(self, nil, 'event')
end
def find_sti_class(action)
2020-06-23 00:09:42 +05:30
if actions.fetch(action, action) == actions[:pushed] # action can be integer or symbol
2017-09-10 17:25:29 +05:30
PushEvent
else
Event
end
end
2016-11-24 13:41:30 +05:30
# Update Gitlab::ContributionsCalendar#activity_dates if this changes
2015-04-26 12:48:37 +05:30
def contributions
2017-08-17 22:00:37 +05:30
where("action = ? OR (target_type IN (?) AND action IN (?)) OR (target_type = ? AND action = ?)",
2020-06-23 00:09:42 +05:30
actions[:pushed],
%w(MergeRequest Issue), [actions[:created], actions[:closed], actions[:merged]],
"Note", actions[:commented])
2015-04-26 12:48:37 +05:30
end
2015-11-26 14:37:03 +05:30
def limit_recent(limit = 20, offset = nil)
recent.limit(limit).offset(offset)
end
2017-09-10 17:25:29 +05:30
def target_types
TARGET_TYPES.keys
end
2014-09-02 18:07:02 +05:30
end
2019-12-04 20:38:33 +05:30
def present
super(presenter_class: ::EventPresenter)
end
2016-06-02 11:05:42 +05:30
def visible_to_user?(user = nil)
2020-04-08 14:13:33 +05:30
return false unless capability.present?
2020-06-23 00:09:42 +05:30
capability.all? do |rule|
Ability.allowed?(user, rule, permission_object)
end
2014-09-02 18:07:02 +05:30
end
2019-12-04 20:38:33 +05:30
def resource_parent
project || group
2014-09-02 18:07:02 +05:30
end
def target_title
2016-06-02 11:05:42 +05:30
target.try(:title)
2015-04-26 12:48:37 +05:30
end
2019-09-04 21:01:54 +05:30
def push_action?
2018-03-17 18:26:18 +05:30
false
2014-09-02 18:07:02 +05:30
end
2015-04-26 12:48:37 +05:30
def membership_changed?
2019-09-04 21:01:54 +05:30
joined_action? || left_action? || expired_action?
2015-04-26 12:48:37 +05:30
end
2019-09-04 21:01:54 +05:30
def created_project_action?
created_action? && !target && target_type.nil?
2015-04-26 12:48:37 +05:30
end
2020-04-22 19:07:51 +05:30
def created_wiki_page?
2020-06-23 00:09:42 +05:30
wiki_page? && created_action?
2020-04-22 19:07:51 +05:30
end
def updated_wiki_page?
2020-06-23 00:09:42 +05:30
wiki_page? && updated_action?
2020-04-22 19:07:51 +05:30
end
2015-04-26 12:48:37 +05:30
def created_target?
2019-09-04 21:01:54 +05:30
created_action? && target
2014-09-02 18:07:02 +05:30
end
def milestone?
target_type == "Milestone"
end
def note?
2016-08-24 12:49:21 +05:30
target.is_a?(Note)
2014-09-02 18:07:02 +05:30
end
def issue?
target_type == "Issue"
end
def merge_request?
target_type == "MergeRequest"
end
2020-04-22 19:07:51 +05:30
def wiki_page?
target_type == 'WikiPage::Meta'
end
2020-06-23 00:09:42 +05:30
def design?
target_type == 'DesignManagement::Design'
end
2015-04-26 12:48:37 +05:30
def milestone
target if milestone?
2014-09-02 18:07:02 +05:30
end
def issue
2015-04-26 12:48:37 +05:30
target if issue?
2014-09-02 18:07:02 +05:30
end
2020-06-23 00:09:42 +05:30
def design
target if design?
end
2014-09-02 18:07:02 +05:30
def merge_request
2015-04-26 12:48:37 +05:30
target if merge_request?
2014-09-02 18:07:02 +05:30
end
2020-04-22 19:07:51 +05:30
def wiki_page
strong_memoize(:wiki_page) do
next unless wiki_page?
ProjectWiki.new(project, author).find_page(target.canonical_slug)
end
end
2014-09-02 18:07:02 +05:30
def note
2015-04-26 12:48:37 +05:30
target if note?
2014-09-02 18:07:02 +05:30
end
def action_name
2019-09-04 21:01:54 +05:30
if push_action?
2018-03-17 18:26:18 +05:30
push_action_name
2020-06-23 00:09:42 +05:30
elsif design?
design_action_names[action.to_sym]
2019-09-04 21:01:54 +05:30
elsif closed_action?
2014-09-02 18:07:02 +05:30
"closed"
2019-09-04 21:01:54 +05:30
elsif merged_action?
2014-09-02 18:07:02 +05:30
"accepted"
2019-09-04 21:01:54 +05:30
elsif joined_action?
2014-09-02 18:07:02 +05:30
'joined'
2019-09-04 21:01:54 +05:30
elsif left_action?
2014-09-02 18:07:02 +05:30
'left'
2019-09-04 21:01:54 +05:30
elsif expired_action?
2017-08-17 22:00:37 +05:30
'removed due to membership expiration from'
2019-09-04 21:01:54 +05:30
elsif destroyed_action?
2015-09-25 12:07:36 +05:30
'destroyed'
2019-09-04 21:01:54 +05:30
elsif commented_action?
2015-04-26 12:48:37 +05:30
"commented on"
2020-04-22 19:07:51 +05:30
elsif created_wiki_page?
'created'
elsif updated_wiki_page?
'updated'
2019-09-04 21:01:54 +05:30
elsif created_project_action?
2018-03-17 18:26:18 +05:30
created_project_action_name
2014-09-02 18:07:02 +05:30
else
"opened"
end
end
def target_iid
target.respond_to?(:iid) ? target.iid : target_id
end
2016-06-02 11:05:42 +05:30
def commit_note?
2016-11-24 13:41:30 +05:30
note? && target && target.for_commit?
2014-09-02 18:07:02 +05:30
end
2016-06-02 11:05:42 +05:30
def issue_note?
note? && target && target.for_issue?
2014-09-02 18:07:02 +05:30
end
2016-11-03 12:29:30 +05:30
def merge_request_note?
note? && target && target.for_merge_request?
end
2016-06-02 11:05:42 +05:30
def project_snippet_note?
2016-11-24 13:41:30 +05:30
note? && target && target.for_snippet?
2014-09-02 18:07:02 +05:30
end
2018-11-08 19:23:39 +05:30
def personal_snippet_note?
note? && target && target.for_personal_snippet?
end
2020-05-24 23:13:21 +05:30
def design_note?
note? && note.for_design?
end
2014-09-02 18:07:02 +05:30
def note_target
target.noteable
end
def note_target_id
2016-06-02 11:05:42 +05:30
if commit_note?
2014-09-02 18:07:02 +05:30
target.commit_id
else
target.noteable_id.to_s
end
end
2016-06-02 11:05:42 +05:30
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
2014-09-02 18:07:02 +05:30
else
2016-06-02 11:05:42 +05:30
note_target.to_reference
end
2014-09-02 18:07:02 +05:30
end
def note_target_type
if target.noteable_type.present?
target.noteable_type.titleize
else
"Wall"
end.downcase
end
def body?
2019-09-04 21:01:54 +05:30
if push_action?
2017-09-10 17:25:29 +05:30
push_with_commits?
2014-09-02 18:07:02 +05:30
elsif note?
true
else
target.respond_to? :title
end
end
def reset_project_activity
2016-09-29 09:46:39 +05:30
return unless project
2016-11-03 12:29:30 +05:30
# Don't bother updating if we know the project was updated recently.
2016-09-29 09:46:39 +05:30
return if recent_update?
2016-11-03 12:29:30 +05:30
# 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.
2017-09-10 17:25:29 +05:30
Project.unscoped.where(id: project_id)
.where('last_activity_at <= ?', RESET_PROJECT_ACTIVITY_INTERVAL.ago)
.update_all(last_activity_at: created_at)
2016-09-29 09:46:39 +05:30
end
2017-08-17 22:00:37 +05:30
def authored_by?(user)
user ? author_id == user.id : false
end
2017-09-10 17:25:29 +05:30
def to_partial_path
# We are intentionally using `Event` rather than `self.class` so that
# subclasses also use the `Event` implementation.
Event._to_partial_path
end
2020-04-08 14:13:33 +05:30
protected
def capability
@capability ||= begin
2020-06-23 00:09:42 +05:30
capabilities.flat_map do |ability, syms|
if syms.any? { |sym| send(sym) } # rubocop: disable GitlabSecurity/PublicSend
[ability]
else
[]
end
end
end
end
def capabilities
{
download_code: %i[push_action? commit_note?],
read_project: %i[membership_changed? created_project_action?],
read_issue: %i[issue? issue_note?],
read_merge_request: %i[merge_request? merge_request_note?],
read_snippet: %i[personal_snippet_note? project_snippet_note?],
read_milestone: %i[milestone?],
read_wiki: %i[wiki_page?],
read_design: %i[design_note? design?]
}
end
2020-04-08 14:13:33 +05:30
2016-09-29 09:46:39 +05:30
private
2020-04-08 14:13:33 +05:30
def permission_object
if note?
note_target
elsif target_id.present?
target
else
project
end
end
2018-03-17 18:26:18 +05:30
def push_action_name
if new_ref?
"pushed new"
elsif rm_ref?
"deleted"
else
"pushed to"
end
end
def created_project_action_name
if project.external_import?
"imported"
else
"created"
end
end
2016-09-29 09:46:39 +05:30
def recent_update?
project.last_activity_at > RESET_PROJECT_ACTIVITY_INTERVAL.ago
end
2017-08-17 22:00:37 +05:30
def set_last_repository_updated_at
2017-09-10 17:25:29 +05:30
Project.unscoped.where(id: project_id)
2018-11-08 19:23:39 +05:30
.where("last_repository_updated_at < ? OR last_repository_updated_at IS NULL", REPOSITORY_UPDATED_AT_INTERVAL.ago)
2017-09-10 17:25:29 +05:30
.update_all(last_repository_updated_at: created_at)
2017-08-17 22:00:37 +05:30
end
2018-03-27 19:54:05 +05:30
2020-06-23 00:09:42 +05:30
def design_action_names
{
created: _('uploaded'),
updated: _('revised'),
destroyed: _('deleted'),
archived: _('archived')
}
end
def action_enum_value
self.class.actions[action]
2018-03-27 19:54:05 +05:30
end
2014-09-02 18:07:02 +05:30
end
2019-12-04 20:38:33 +05:30
Event.prepend_if_ee('EE::Event')