summaryrefslogtreecommitdiff
path: root/app/models
diff options
context:
space:
mode:
authorFilipa Lacerda <filipa@gitlab.com>2017-05-07 15:00:58 +0100
committerFilipa Lacerda <filipa@gitlab.com>2017-05-07 15:00:58 +0100
commit842918602dbe622dc20593c0abea5293e304ac62 (patch)
treec748164aab8cfa43fe3332640c60e3308b4e9c29 /app/models
parent214d7880c3d651b367eb73651a6e0e3046868287 (diff)
parent6ad3814e1b31bfacfae7a2aabb4e4607b12ca66f (diff)
downloadgitlab-ce-remove-old-isobject.tar.gz
Merge branch 'master' into remove-old-isobjectremove-old-isobject
* master: (226 commits) Real time pipeline show action Fix `Routable.find_by_full_path` on MySQL add CHANGELOG.md entry for !11138 add tooltips to user contrib graph key Use an absolute path for locale path in FastGettext config Colorize labels in issue search field Fix Karma failures for jQuery deferreds Reduce risk of deadlocks Fix failing spec and eslint Resolve discussions Resolve discussions Dry up routable lookups. Fixes #30317 Add “project moved” flash message on redirect Resolve discussions Fix Rubocop failures Index redirect_routes path for LIKE Add index for source association and for path Fix or workaround spec failure Refactor Delete conflicting redirects ...
Diffstat (limited to 'app/models')
-rw-r--r--app/models/application_setting.rb4
-rw-r--r--app/models/blob.rb1
-rw-r--r--app/models/blob_viewer/balsamiq.rb12
-rw-r--r--app/models/ci/build.rb11
-rw-r--r--app/models/ci/group.rb40
-rw-r--r--app/models/ci/stage.rb8
-rw-r--r--app/models/concerns/issuable.rb29
-rw-r--r--app/models/concerns/milestoneish.rb2
-rw-r--r--app/models/concerns/routable.rb24
-rw-r--r--app/models/deployment.rb4
-rw-r--r--app/models/global_milestone.rb6
-rw-r--r--app/models/issue.rb35
-rw-r--r--app/models/issue_assignee.rb13
-rw-r--r--app/models/merge_request.rb32
-rw-r--r--app/models/milestone.rb5
-rw-r--r--app/models/note.rb6
-rw-r--r--app/models/redirect_route.rb12
-rw-r--r--app/models/route.rb55
-rw-r--r--app/models/snippet.rb5
-rw-r--r--app/models/system_note_metadata.rb2
-rw-r--r--app/models/user.rb13
21 files changed, 260 insertions, 59 deletions
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index cf042717c95..54f01f8637e 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -62,6 +62,10 @@ class ApplicationSetting < ActiveRecord::Base
presence: true,
if: :sentry_enabled
+ validates :clientside_sentry_dsn,
+ presence: true,
+ if: :clientside_sentry_enabled
+
validates :akismet_api_key,
presence: true,
if: :akismet_enabled
diff --git a/app/models/blob.rb b/app/models/blob.rb
index a4fae22a0c4..eaf0b713122 100644
--- a/app/models/blob.rb
+++ b/app/models/blob.rb
@@ -26,6 +26,7 @@ class Blob < SimpleDelegator
BlobViewer::Image,
BlobViewer::Sketch,
+ BlobViewer::Balsamiq,
BlobViewer::Video,
diff --git a/app/models/blob_viewer/balsamiq.rb b/app/models/blob_viewer/balsamiq.rb
new file mode 100644
index 00000000000..f982521db99
--- /dev/null
+++ b/app/models/blob_viewer/balsamiq.rb
@@ -0,0 +1,12 @@
+module BlobViewer
+ class Balsamiq < Base
+ include Rich
+ include ClientSide
+
+ self.partial_name = 'balsamiq'
+ self.extensions = %w(bmpr)
+ self.binary = true
+ self.switcher_icon = 'file-image-o'
+ self.switcher_title = 'preview'
+ end
+end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index b426c27afbb..971ab7cb0ee 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -111,14 +111,9 @@ module Ci
end
def play(current_user)
- # Try to queue a current build
- if self.enqueue
- self.update(user: current_user)
- self
- else
- # Otherwise we need to create a duplicate
- Ci::Build.retry(self, current_user)
- end
+ Ci::PlayBuildService
+ .new(project, current_user)
+ .execute(self)
end
def cancelable?
diff --git a/app/models/ci/group.rb b/app/models/ci/group.rb
new file mode 100644
index 00000000000..87898b086c6
--- /dev/null
+++ b/app/models/ci/group.rb
@@ -0,0 +1,40 @@
+module Ci
+ ##
+ # This domain model is a representation of a group of jobs that are related
+ # to each other, like `rspec 0 1`, `rspec 0 2`.
+ #
+ # It is not persisted in the database.
+ #
+ class Group
+ include StaticModel
+
+ attr_reader :stage, :name, :jobs
+
+ delegate :size, to: :jobs
+
+ def initialize(stage, name:, jobs:)
+ @stage = stage
+ @name = name
+ @jobs = jobs
+ end
+
+ def status
+ @status ||= commit_statuses.status
+ end
+
+ def detailed_status(current_user)
+ if jobs.one?
+ jobs.first.detailed_status(current_user)
+ else
+ Gitlab::Ci::Status::Group::Factory
+ .new(self, current_user).fabricate!
+ end
+ end
+
+ private
+
+ def commit_statuses
+ @commit_statuses ||= CommitStatus.where(id: jobs.map(&:id))
+ end
+ end
+end
diff --git a/app/models/ci/stage.rb b/app/models/ci/stage.rb
index e7d6b17d445..9bda3186c30 100644
--- a/app/models/ci/stage.rb
+++ b/app/models/ci/stage.rb
@@ -15,6 +15,14 @@ module Ci
@warnings = warnings
end
+ def groups
+ @groups ||= statuses.ordered.latest
+ .sort_by(&:sortable_name).group_by(&:group_name)
+ .map do |group_name, grouped_statuses|
+ Ci::Group.new(self, name: group_name, jobs: grouped_statuses)
+ end
+ end
+
def to_param
name
end
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 26dbf4d9570..075ec575f9d 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -26,8 +26,8 @@ module Issuable
cache_markdown_field :description, issuable_state_filter_enabled: true
belongs_to :author, class_name: "User"
- belongs_to :assignee, class_name: "User"
belongs_to :updated_by, class_name: "User"
+ belongs_to :last_edited_by, class_name: 'User'
belongs_to :milestone
has_many :notes, as: :noteable, inverse_of: :noteable, dependent: :destroy do
def authors_loaded?
@@ -65,11 +65,8 @@ module Issuable
validates :title, presence: true, length: { maximum: 255 }
scope :authored, ->(user) { where(author_id: user) }
- scope :assigned_to, ->(u) { where(assignee_id: u.id)}
scope :recent, -> { reorder(id: :desc) }
scope :order_position_asc, -> { reorder(position: :asc) }
- scope :assigned, -> { where("assignee_id IS NOT NULL") }
- scope :unassigned, -> { where("assignee_id IS NULL") }
scope :of_projects, ->(ids) { where(project_id: ids) }
scope :of_milestones, ->(ids) { where(milestone_id: ids) }
scope :with_milestone, ->(title) { left_joins_milestones.where(milestones: { title: title }) }
@@ -92,23 +89,14 @@ module Issuable
attr_mentionable :description
participant :author
- participant :assignee
participant :notes_with_associations
strip_attributes :title
acts_as_paranoid
- after_save :update_assignee_cache_counts, if: :assignee_id_changed?
after_save :record_metrics, unless: :imported?
- def update_assignee_cache_counts
- # make sure we flush the cache for both the old *and* new assignees(if they exist)
- previous_assignee = User.find_by_id(assignee_id_was) if assignee_id_was
- previous_assignee&.update_cache_counts
- assignee&.update_cache_counts
- end
-
# We want to use optimistic lock for cases when only title or description are involved
# http://api.rubyonrails.org/classes/ActiveRecord/Locking/Optimistic.html
def locking_enabled?
@@ -237,10 +225,6 @@ module Issuable
today? && created_at == updated_at
end
- def is_being_reassigned?
- assignee_id_changed?
- end
-
def open?
opened? || reopened?
end
@@ -269,7 +253,11 @@ module Issuable
# DEPRECATED
repository: project.hook_attrs.slice(:name, :url, :description, :homepage)
}
- hook_data[:assignee] = assignee.hook_attrs if assignee
+ if self.is_a?(Issue)
+ hook_data[:assignees] = assignees.map(&:hook_attrs) if assignees.any?
+ else
+ hook_data[:assignee] = assignee.hook_attrs if assignee
+ end
hook_data
end
@@ -331,11 +319,6 @@ module Issuable
false
end
- def assignee_or_author?(user)
- # We're comparing IDs here so we don't need to load any associations.
- author_id == user.id || assignee_id == user.id
- end
-
def record_metrics
metrics = self.metrics || create_metrics
metrics.record!
diff --git a/app/models/concerns/milestoneish.rb b/app/models/concerns/milestoneish.rb
index f449229864d..a3472af5c55 100644
--- a/app/models/concerns/milestoneish.rb
+++ b/app/models/concerns/milestoneish.rb
@@ -40,7 +40,7 @@ module Milestoneish
def issues_visible_to_user(user)
memoize_per_user(user, :issues_visible_to_user) do
IssuesFinder.new(user, issues_finder_params)
- .execute.where(milestone_id: milestoneish_ids)
+ .execute.includes(:assignees).where(milestone_id: milestoneish_ids)
end
end
diff --git a/app/models/concerns/routable.rb b/app/models/concerns/routable.rb
index b28e05d0c28..c4463abdfe6 100644
--- a/app/models/concerns/routable.rb
+++ b/app/models/concerns/routable.rb
@@ -5,6 +5,7 @@ module Routable
included do
has_one :route, as: :source, autosave: true, dependent: :destroy
+ has_many :redirect_routes, as: :source, autosave: true, dependent: :destroy
validates_associated :route
validates :route, presence: true
@@ -26,16 +27,31 @@ module Routable
# Klass.find_by_full_path('gitlab-org/gitlab-ce')
#
# Returns a single object, or nil.
- def find_by_full_path(path)
+ def find_by_full_path(path, follow_redirects: false)
# On MySQL we want to ensure the ORDER BY uses a case-sensitive match so
# any literal matches come first, for this we have to use "BINARY".
# Without this there's still no guarantee in what order MySQL will return
# rows.
+ #
+ # Why do we do this?
+ #
+ # Even though we have Rails validation on Route for unique paths
+ # (case-insensitive), there are old projects in our DB (and possibly
+ # clients' DBs) that have the same path with different cases.
+ # See https://gitlab.com/gitlab-org/gitlab-ce/issues/18603. Also note that
+ # our unique index is case-sensitive in Postgres.
binary = Gitlab::Database.mysql? ? 'BINARY' : ''
-
order_sql = "(CASE WHEN #{binary} routes.path = #{connection.quote(path)} THEN 0 ELSE 1 END)"
-
- where_full_path_in([path]).reorder(order_sql).take
+ found = where_full_path_in([path]).reorder(order_sql).take
+ return found if found
+
+ if follow_redirects
+ if Gitlab::Database.postgresql?
+ joins(:redirect_routes).find_by("LOWER(redirect_routes.path) = LOWER(?)", path)
+ else
+ joins(:redirect_routes).find_by(redirect_routes: { path: path })
+ end
+ end
end
# Builds a relation to find multiple objects by their full paths.
diff --git a/app/models/deployment.rb b/app/models/deployment.rb
index afad001d50f..37adfb4de73 100644
--- a/app/models/deployment.rb
+++ b/app/models/deployment.rb
@@ -85,8 +85,8 @@ class Deployment < ActiveRecord::Base
end
def stop_action
- return nil unless on_stop.present?
- return nil unless manual_actions
+ return unless on_stop.present?
+ return unless manual_actions
@stop_action ||= manual_actions.find_by(name: on_stop)
end
diff --git a/app/models/global_milestone.rb b/app/models/global_milestone.rb
index 0afbca2cb32..538615130a7 100644
--- a/app/models/global_milestone.rb
+++ b/app/models/global_milestone.rb
@@ -36,7 +36,7 @@ class GlobalMilestone
closed = count_by_state(milestones_by_state_and_title, 'closed')
all = milestones_by_state_and_title.map { |(_, title), _| title }.uniq.count
- {
+ {
opened: opened,
closed: closed,
all: all
@@ -86,7 +86,7 @@ class GlobalMilestone
end
def issues
- @issues ||= Issue.of_milestones(milestoneish_ids).includes(:project, :assignee, :labels)
+ @issues ||= Issue.of_milestones(milestoneish_ids).includes(:project, :assignees, :labels)
end
def merge_requests
@@ -94,7 +94,7 @@ class GlobalMilestone
end
def participants
- @participants ||= milestones.includes(:participants).map(&:participants).flatten.compact.uniq
+ @participants ||= milestones.map(&:participants).flatten.uniq
end
def labels
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 78bde6820da..27e3ed9bc7f 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -24,10 +24,17 @@ class Issue < ActiveRecord::Base
has_many :merge_requests_closing_issues, class_name: 'MergeRequestsClosingIssues', dependent: :delete_all
+ has_many :issue_assignees
+ has_many :assignees, class_name: "User", through: :issue_assignees
+
validates :project, presence: true
scope :in_projects, ->(project_ids) { where(project_id: project_ids) }
+ scope :assigned, -> { where('EXISTS (SELECT TRUE FROM issue_assignees WHERE issue_id = issues.id)') }
+ scope :unassigned, -> { where('NOT EXISTS (SELECT TRUE FROM issue_assignees WHERE issue_id = issues.id)') }
+ scope :assigned_to, ->(u) { where('EXISTS (SELECT TRUE FROM issue_assignees WHERE user_id = ? AND issue_id = issues.id)', u.id)}
+
scope :without_due_date, -> { where(due_date: nil) }
scope :due_before, ->(date) { where('issues.due_date < ?', date) }
scope :due_between, ->(from_date, to_date) { where('issues.due_date >= ?', from_date).where('issues.due_date <= ?', to_date) }
@@ -37,13 +44,15 @@ class Issue < ActiveRecord::Base
scope :created_after, -> (datetime) { where("created_at >= ?", datetime) }
- scope :include_associations, -> { includes(:assignee, :labels, project: :namespace) }
+ scope :include_associations, -> { includes(:labels, project: :namespace) }
after_save :expire_etag_cache
attr_spammable :title, spam_title: true
attr_spammable :description, spam_description: true
+ participant :assignees
+
state_machine :state, initial: :opened do
event :close do
transition [:reopened, :opened] => :closed
@@ -63,10 +72,14 @@ class Issue < ActiveRecord::Base
end
def hook_attrs
+ assignee_ids = self.assignee_ids
+
attrs = {
total_time_spent: total_time_spent,
human_total_time_spent: human_total_time_spent,
- human_time_estimate: human_time_estimate
+ human_time_estimate: human_time_estimate,
+ assignee_ids: assignee_ids,
+ assignee_id: assignee_ids.first # This key is deprecated
}
attributes.merge!(attrs)
@@ -114,6 +127,22 @@ class Issue < ActiveRecord::Base
"id DESC")
end
+ # Returns a Hash of attributes to be used for Twitter card metadata
+ def card_attributes
+ {
+ 'Author' => author.try(:name),
+ 'Assignee' => assignee_list
+ }
+ end
+
+ def assignee_or_author?(user)
+ author_id == user.id || assignees.exists?(user.id)
+ end
+
+ def assignee_list
+ assignees.map(&:name).to_sentence
+ end
+
# `from` argument can be a Namespace or Project.
def to_reference(from = nil, full: false)
reference = "#{self.class.reference_prefix}#{iid}"
@@ -248,7 +277,7 @@ class Issue < ActiveRecord::Base
true
elsif confidential?
author == user ||
- assignee == user ||
+ assignees.include?(user) ||
project.team.member?(user, Gitlab::Access::REPORTER)
else
project.public? ||
diff --git a/app/models/issue_assignee.rb b/app/models/issue_assignee.rb
new file mode 100644
index 00000000000..0663d3aaef8
--- /dev/null
+++ b/app/models/issue_assignee.rb
@@ -0,0 +1,13 @@
+class IssueAssignee < ActiveRecord::Base
+ extend Gitlab::CurrentSettings
+
+ belongs_to :issue
+ belongs_to :assignee, class_name: "User", foreign_key: :user_id
+
+ after_create :update_assignee_cache_counts
+ after_destroy :update_assignee_cache_counts
+
+ def update_assignee_cache_counts
+ assignee&.update_cache_counts
+ end
+end
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 12c5481cd6d..35231bab12e 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -17,6 +17,8 @@ class MergeRequest < ActiveRecord::Base
has_many :merge_requests_closing_issues, class_name: 'MergeRequestsClosingIssues', dependent: :delete_all
+ belongs_to :assignee, class_name: "User"
+
serialize :merge_params, Hash
after_create :ensure_merge_request_diff, unless: :importing?
@@ -114,8 +116,14 @@ class MergeRequest < ActiveRecord::Base
scope :join_project, -> { joins(:target_project) }
scope :references_project, -> { references(:target_project) }
+ scope :assigned, -> { where("assignee_id IS NOT NULL") }
+ scope :unassigned, -> { where("assignee_id IS NULL") }
+ scope :assigned_to, ->(u) { where(assignee_id: u.id)}
+
+ participant :assignee
after_save :keep_around_commit
+ after_save :update_assignee_cache_counts, if: :assignee_id_changed?
def self.reference_prefix
'!'
@@ -177,6 +185,30 @@ class MergeRequest < ActiveRecord::Base
work_in_progress?(title) ? title : "WIP: #{title}"
end
+ def update_assignee_cache_counts
+ # make sure we flush the cache for both the old *and* new assignees(if they exist)
+ previous_assignee = User.find_by_id(assignee_id_was) if assignee_id_was
+ previous_assignee&.update_cache_counts
+ assignee&.update_cache_counts
+ end
+
+ # Returns a Hash of attributes to be used for Twitter card metadata
+ def card_attributes
+ {
+ 'Author' => author.try(:name),
+ 'Assignee' => assignee.try(:name)
+ }
+ end
+
+ # This method is needed for compatibility with issues to not mess view and other code
+ def assignees
+ Array(assignee)
+ end
+
+ def assignee_or_author?(user)
+ author_id == user.id || assignee_id == user.id
+ end
+
# `from` argument can be a Namespace or Project.
def to_reference(from = nil, full: false)
reference = "#{self.class.reference_prefix}#{iid}"
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index 652b1551928..c06bfe0ccdd 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -21,7 +21,6 @@ class Milestone < ActiveRecord::Base
has_many :issues
has_many :labels, -> { distinct.reorder('labels.title') }, through: :issues
has_many :merge_requests
- has_many :participants, -> { distinct.reorder('users.name') }, through: :issues, source: :assignee
has_many :events, as: :target, dependent: :destroy
scope :active, -> { with_state(:active) }
@@ -107,6 +106,10 @@ class Milestone < ActiveRecord::Base
end
end
+ def participants
+ User.joins(assigned_issues: :milestone).where("milestones.id = ?", id)
+ end
+
def self.sort(method)
case method.to_s
when 'due_date_asc'
diff --git a/app/models/note.rb b/app/models/note.rb
index b06985b4a6f..46d0a4f159f 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -18,6 +18,11 @@ class Note < ActiveRecord::Base
cache_markdown_field :note, pipeline: :note, issuable_state_filter_enabled: true
+ # Aliases to make application_helper#edited_time_ago_with_tooltip helper work properly with notes.
+ # See https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10392/diffs#note_28719102
+ alias_attribute :last_edited_at, :updated_at
+ alias_attribute :last_edited_by, :updated_by
+
# Attribute containing rendered and redacted Markdown as generated by
# Banzai::ObjectRenderer.
attr_accessor :redacted_note_html
@@ -38,6 +43,7 @@ class Note < ActiveRecord::Base
belongs_to :noteable, polymorphic: true, touch: true
belongs_to :author, class_name: "User"
belongs_to :updated_by, class_name: "User"
+ belongs_to :last_edited_by, class_name: 'User'
has_many :todos, dependent: :destroy
has_many :events, as: :target, dependent: :destroy
diff --git a/app/models/redirect_route.rb b/app/models/redirect_route.rb
new file mode 100644
index 00000000000..99812bcde53
--- /dev/null
+++ b/app/models/redirect_route.rb
@@ -0,0 +1,12 @@
+class RedirectRoute < ActiveRecord::Base
+ belongs_to :source, polymorphic: true
+
+ validates :source, presence: true
+
+ validates :path,
+ length: { within: 1..255 },
+ presence: true,
+ uniqueness: { case_sensitive: false }
+
+ scope :matching_path_and_descendants, -> (path) { where('redirect_routes.path = ? OR redirect_routes.path LIKE ?', path, "#{sanitize_sql_like(path)}/%") }
+end
diff --git a/app/models/route.rb b/app/models/route.rb
index 4b3efab5c3c..12a7fa3d01b 100644
--- a/app/models/route.rb
+++ b/app/models/route.rb
@@ -8,29 +8,58 @@ class Route < ActiveRecord::Base
presence: true,
uniqueness: { case_sensitive: false }
+ after_create :delete_conflicting_redirects
+ after_update :delete_conflicting_redirects, if: :path_changed?
+ after_update :create_redirect_for_old_path
after_update :rename_descendants
scope :inside_path, -> (path) { where('routes.path LIKE ?', "#{sanitize_sql_like(path)}/%") }
def rename_descendants
- if path_changed? || name_changed?
- descendants = self.class.inside_path(path_was)
+ return unless path_changed? || name_changed?
- descendants.each do |route|
- attributes = {}
+ descendant_routes = self.class.inside_path(path_was)
- if path_changed? && route.path.present?
- attributes[:path] = route.path.sub(path_was, path)
- end
+ descendant_routes.each do |route|
+ attributes = {}
- if name_changed? && name_was.present? && route.name.present?
- attributes[:name] = route.name.sub(name_was, name)
- end
+ if path_changed? && route.path.present?
+ attributes[:path] = route.path.sub(path_was, path)
+ end
- # Note that update_columns skips validation and callbacks.
- # We need this to avoid recursive call of rename_descendants method
- route.update_columns(attributes) unless attributes.empty?
+ if name_changed? && name_was.present? && route.name.present?
+ attributes[:name] = route.name.sub(name_was, name)
+ end
+
+ if attributes.present?
+ old_path = route.path
+
+ # Callbacks must be run manually
+ route.update_columns(attributes)
+
+ # We are not calling route.delete_conflicting_redirects here, in hopes
+ # of avoiding deadlocks. The parent (self, in this method) already
+ # called it, which deletes conflicts for all descendants.
+ route.create_redirect(old_path) if attributes[:path]
end
end
end
+
+ def delete_conflicting_redirects
+ conflicting_redirects.delete_all
+ end
+
+ def conflicting_redirects
+ RedirectRoute.matching_path_and_descendants(path)
+ end
+
+ def create_redirect(path)
+ RedirectRoute.create(source: source, path: path)
+ end
+
+ private
+
+ def create_redirect_for_old_path
+ create_redirect(path_was) if path_changed?
+ end
end
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index d8860718cb5..abfbefdf9a0 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -12,6 +12,11 @@ class Snippet < ActiveRecord::Base
cache_markdown_field :title, pipeline: :single_line
cache_markdown_field :content
+ # Aliases to make application_helper#edited_time_ago_with_tooltip helper work properly with snippets.
+ # See https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10392/diffs#note_28719102
+ alias_attribute :last_edited_at, :updated_at
+ alias_attribute :last_edited_by, :updated_by
+
# If file_name changes, it invalidates content
alias_method :default_content_html_invalidator, :content_html_invalidated?
def content_html_invalidated?
diff --git a/app/models/system_note_metadata.rb b/app/models/system_note_metadata.rb
index 1e6fc837a75..b44f4fe000c 100644
--- a/app/models/system_note_metadata.rb
+++ b/app/models/system_note_metadata.rb
@@ -1,6 +1,6 @@
class SystemNoteMetadata < ActiveRecord::Base
ICON_TYPES = %w[
- commit merge confidential visible label assignee cross_reference
+ commit description merge confidential visible label assignee cross_reference
title time_tracking branch milestone discussion task moved opened closed merged
].freeze
diff --git a/app/models/user.rb b/app/models/user.rb
index 43c5fdc038d..accaa91b805 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -100,6 +100,10 @@ class User < ActiveRecord::Base
has_many :award_emoji, dependent: :destroy
has_many :triggers, dependent: :destroy, class_name: 'Ci::Trigger', foreign_key: :owner_id
+ has_many :issue_assignees
+ has_many :assigned_issues, class_name: "Issue", through: :issue_assignees, source: :issue
+ has_many :assigned_merge_requests, dependent: :nullify, foreign_key: :assignee_id, class_name: "MergeRequest"
+
# Issues that a user owns are expected to be moved to the "ghost" user before
# the user is destroyed. If the user owns any issues during deletion, this
# should be treated as an exceptional condition.
@@ -333,6 +337,11 @@ class User < ActiveRecord::Base
find_by(id: Key.unscoped.select(:user_id).where(id: key_id))
end
+ def find_by_full_path(path, follow_redirects: false)
+ namespace = Namespace.find_by_full_path(path, follow_redirects: follow_redirects)
+ namespace&.owner
+ end
+
def reference_prefix
'@'
end
@@ -355,6 +364,10 @@ class User < ActiveRecord::Base
end
end
+ def full_path
+ username
+ end
+
def self.internal_attributes
[:ghost]
end