Commit 68aa43fd authored by Sean McGivern's avatar Sean McGivern

Merge branch '24784-system-notes-meta-data' into 'master'

Add metadata for system notes

See merge request !9964
parents 91f43587 c729d9da
...@@ -37,6 +37,7 @@ class Note < ActiveRecord::Base ...@@ -37,6 +37,7 @@ class Note < ActiveRecord::Base
has_many :todos, dependent: :destroy has_many :todos, dependent: :destroy
has_many :events, as: :target, dependent: :destroy has_many :events, as: :target, dependent: :destroy
has_one :system_note_metadata
delegate :gfm_reference, :local_reference, to: :noteable delegate :gfm_reference, :local_reference, to: :noteable
delegate :name, to: :project, prefix: true delegate :name, to: :project, prefix: true
...@@ -70,7 +71,9 @@ class Note < ActiveRecord::Base ...@@ -70,7 +71,9 @@ class Note < ActiveRecord::Base
scope :fresh, ->{ order(created_at: :asc, id: :asc) } scope :fresh, ->{ order(created_at: :asc, id: :asc) }
scope :inc_author_project, ->{ includes(:project, :author) } scope :inc_author_project, ->{ includes(:project, :author) }
scope :inc_author, ->{ includes(:author) } scope :inc_author, ->{ includes(:author) }
scope :inc_relations_for_view, ->{ includes(:project, :author, :updated_by, :resolved_by, :award_emoji) } scope :inc_relations_for_view, -> do
includes(:project, :author, :updated_by, :resolved_by, :award_emoji, :system_note_metadata)
end
scope :diff_notes, ->{ where(type: %w(LegacyDiffNote DiffNote)) } scope :diff_notes, ->{ where(type: %w(LegacyDiffNote DiffNote)) }
scope :non_diff_notes, ->{ where(type: ['Note', nil]) } scope :non_diff_notes, ->{ where(type: ['Note', nil]) }
......
class SystemNoteMetadata < ActiveRecord::Base
ICON_TYPES = %w[
commit merge confidentiality status label assignee cross_reference
title time_tracking branch milestone discussion task moved
].freeze
validates :note, presence: true
validates :action, inclusion: ICON_TYPES, allow_nil: true
belongs_to :note
end
class NoteSummary
attr_reader :note
attr_reader :metadata
def initialize(noteable, project, author, body, action: nil, commit_count: nil)
@note = { noteable: noteable, project: project, author: author, note: body }
@metadata = { action: action, commit_count: commit_count }.compact
set_commit_params if note[:noteable].is_a?(Commit)
end
def metadata?
metadata.present?
end
def set_commit_params
note.merge!(noteable_type: 'Commit', commit_id: note[:noteable].id)
note[:noteable] = nil
end
end
...@@ -26,7 +26,7 @@ module SystemNoteService ...@@ -26,7 +26,7 @@ module SystemNoteService
body << new_commit_summary(new_commits).join("\n") body << new_commit_summary(new_commits).join("\n")
body << "\n\n[Compare with previous version](#{diff_comparison_url(noteable, project, oldrev)})" body << "\n\n[Compare with previous version](#{diff_comparison_url(noteable, project, oldrev)})"
create_note(noteable: noteable, project: project, author: author, note: body) create_note(NoteSummary.new(noteable, project, author, body, action: 'commit', commit_count: total_count))
end end
# Called when the assignee of a Noteable is changed or removed # Called when the assignee of a Noteable is changed or removed
...@@ -46,7 +46,7 @@ module SystemNoteService ...@@ -46,7 +46,7 @@ module SystemNoteService
def change_assignee(noteable, project, author, assignee) def change_assignee(noteable, project, author, assignee)
body = assignee.nil? ? 'removed assignee' : "assigned to #{assignee.to_reference}" body = assignee.nil? ? 'removed assignee' : "assigned to #{assignee.to_reference}"
create_note(noteable: noteable, project: project, author: author, note: body) create_note(NoteSummary.new(noteable, project, author, body, action: 'assignee'))
end end
# Called when one or more labels on a Noteable are added and/or removed # Called when one or more labels on a Noteable are added and/or removed
...@@ -86,7 +86,7 @@ module SystemNoteService ...@@ -86,7 +86,7 @@ module SystemNoteService
body << ' ' << 'label'.pluralize(labels_count) body << ' ' << 'label'.pluralize(labels_count)
create_note(noteable: noteable, project: project, author: author, note: body) create_note(NoteSummary.new(noteable, project, author, body, action: 'label'))
end end
# Called when the milestone of a Noteable is changed # Called when the milestone of a Noteable is changed
...@@ -106,7 +106,7 @@ module SystemNoteService ...@@ -106,7 +106,7 @@ module SystemNoteService
def change_milestone(noteable, project, author, milestone) def change_milestone(noteable, project, author, milestone)
body = milestone.nil? ? 'removed milestone' : "changed milestone to #{milestone.to_reference(project)}" body = milestone.nil? ? 'removed milestone' : "changed milestone to #{milestone.to_reference(project)}"
create_note(noteable: noteable, project: project, author: author, note: body) create_note(NoteSummary.new(noteable, project, author, body, action: 'milestone'))
end end
# Called when the estimated time of a Noteable is changed # Called when the estimated time of a Noteable is changed
...@@ -132,7 +132,7 @@ module SystemNoteService ...@@ -132,7 +132,7 @@ module SystemNoteService
"changed time estimate to #{parsed_time}" "changed time estimate to #{parsed_time}"
end end
create_note(noteable: noteable, project: project, author: author, note: body) create_note(NoteSummary.new(noteable, project, author, body, action: 'time_tracking'))
end end
# Called when the spent time of a Noteable is changed # Called when the spent time of a Noteable is changed
...@@ -161,7 +161,7 @@ module SystemNoteService ...@@ -161,7 +161,7 @@ module SystemNoteService
body = "#{action} #{parsed_time} of time spent" body = "#{action} #{parsed_time} of time spent"
end end
create_note(noteable: noteable, project: project, author: author, note: body) create_note(NoteSummary.new(noteable, project, author, body, action: 'time_tracking'))
end end
# Called when the status of a Noteable is changed # Called when the status of a Noteable is changed
...@@ -183,53 +183,57 @@ module SystemNoteService ...@@ -183,53 +183,57 @@ module SystemNoteService
body = status.dup body = status.dup
body << " via #{source.gfm_reference(project)}" if source body << " via #{source.gfm_reference(project)}" if source
create_note(noteable: noteable, project: project, author: author, note: body) create_note(NoteSummary.new(noteable, project, author, body, action: 'status'))
end end
# Called when 'merge when pipeline succeeds' is executed # Called when 'merge when pipeline succeeds' is executed
def merge_when_pipeline_succeeds(noteable, project, author, last_commit) def merge_when_pipeline_succeeds(noteable, project, author, last_commit)
body = "enabled an automatic merge when the pipeline for #{last_commit.to_reference(project)} succeeds" body = "enabled an automatic merge when the pipeline for #{last_commit.to_reference(project)} succeeds"
create_note(noteable: noteable, project: project, author: author, note: body) create_note(NoteSummary.new(noteable, project, author, body, action: 'merge'))
end end
# Called when 'merge when pipeline succeeds' is canceled # Called when 'merge when pipeline succeeds' is canceled
def cancel_merge_when_pipeline_succeeds(noteable, project, author) def cancel_merge_when_pipeline_succeeds(noteable, project, author)
body = 'canceled the automatic merge' body = 'canceled the automatic merge'
create_note(noteable: noteable, project: project, author: author, note: body) create_note(NoteSummary.new(noteable, project, author, body, action: 'merge'))
end end
def remove_merge_request_wip(noteable, project, author) def remove_merge_request_wip(noteable, project, author)
body = 'unmarked as a **Work In Progress**' body = 'unmarked as a **Work In Progress**'
create_note(noteable: noteable, project: project, author: author, note: body) create_note(NoteSummary.new(noteable, project, author, body, action: 'title'))
end end
def add_merge_request_wip(noteable, project, author) def add_merge_request_wip(noteable, project, author)
body = 'marked as a **Work In Progress**' body = 'marked as a **Work In Progress**'
create_note(noteable: noteable, project: project, author: author, note: body) create_note(NoteSummary.new(noteable, project, author, body, action: 'title'))
end end
def add_merge_request_wip_from_commit(noteable, project, author, commit) def add_merge_request_wip_from_commit(noteable, project, author, commit)
body = "marked as a **Work In Progress** from #{commit.to_reference(project)}" body = "marked as a **Work In Progress** from #{commit.to_reference(project)}"
create_note(noteable: noteable, project: project, author: author, note: body) create_note(NoteSummary.new(noteable, project, author, body, action: 'title'))
end end
def self.resolve_all_discussions(merge_request, project, author) def self.resolve_all_discussions(merge_request, project, author)
body = "resolved all discussions" body = "resolved all discussions"
create_note(noteable: merge_request, project: project, author: author, note: body) create_note(NoteSummary.new(merge_request, project, author, body, action: 'discussion'))
end end
def discussion_continued_in_issue(discussion, project, author, issue) def discussion_continued_in_issue(discussion, project, author, issue)
body = "created #{issue.to_reference} to continue this discussion" body = "created #{issue.to_reference} to continue this discussion"
note_attributes = discussion.reply_attributes.merge(project: project, author: author, note: body)
note_attributes[:type] = note_attributes.delete(:note_type)
create_note(note_attributes) note_params = discussion.reply_attributes.merge(project: project, author: author, note: body)
note_params[:type] = note_params.delete(:note_type)
note = Note.create(note_params.merge(system: true))
note.system_note_metadata = SystemNoteMetadata.new({ action: 'discussion' })
note
end end
# Called when the title of a Noteable is changed # Called when the title of a Noteable is changed
...@@ -253,7 +257,8 @@ module SystemNoteService ...@@ -253,7 +257,8 @@ module SystemNoteService
marked_new_title = Gitlab::Diff::InlineDiffMarker.new(new_title).mark(new_diffs, mode: :addition, markdown: true) marked_new_title = Gitlab::Diff::InlineDiffMarker.new(new_title).mark(new_diffs, mode: :addition, markdown: true)
body = "changed title from **#{marked_old_title}** to **#{marked_new_title}**" body = "changed title from **#{marked_old_title}** to **#{marked_new_title}**"
create_note(noteable: noteable, project: project, author: author, note: body)
create_note(NoteSummary.new(noteable, project, author, body, action: 'title'))
end end
# Called when the confidentiality changes # Called when the confidentiality changes
...@@ -269,7 +274,8 @@ module SystemNoteService ...@@ -269,7 +274,8 @@ module SystemNoteService
# Returns the created Note object # Returns the created Note object
def change_issue_confidentiality(issue, project, author) def change_issue_confidentiality(issue, project, author)
body = issue.confidential ? 'made the issue confidential' : 'made the issue visible to everyone' body = issue.confidential ? 'made the issue confidential' : 'made the issue visible to everyone'
create_note(noteable: issue, project: project, author: author, note: body)
create_note(NoteSummary.new(issue, project, author, body, action: 'confidentiality'))
end end
# Called when a branch in Noteable is changed # Called when a branch in Noteable is changed
...@@ -288,7 +294,8 @@ module SystemNoteService ...@@ -288,7 +294,8 @@ module SystemNoteService
# Returns the created Note object # Returns the created Note object
def change_branch(noteable, project, author, branch_type, old_branch, new_branch) def change_branch(noteable, project, author, branch_type, old_branch, new_branch)
body = "changed #{branch_type} branch from `#{old_branch}` to `#{new_branch}`" body = "changed #{branch_type} branch from `#{old_branch}` to `#{new_branch}`"
create_note(noteable: noteable, project: project, author: author, note: body)
create_note(NoteSummary.new(noteable, project, author, body, action: 'branch'))
end end
# Called when a branch in Noteable is added or deleted # Called when a branch in Noteable is added or deleted
...@@ -314,7 +321,8 @@ module SystemNoteService ...@@ -314,7 +321,8 @@ module SystemNoteService
end end
body = "#{verb} #{branch_type} branch `#{branch}`" body = "#{verb} #{branch_type} branch `#{branch}`"
create_note(noteable: noteable, project: project, author: author, note: body)
create_note(NoteSummary.new(noteable, project, author, body, action: 'branch'))
end end
# Called when a branch is created from the 'new branch' button on a issue # Called when a branch is created from the 'new branch' button on a issue
...@@ -325,7 +333,8 @@ module SystemNoteService ...@@ -325,7 +333,8 @@ module SystemNoteService
link = url_helpers.namespace_project_compare_url(project.namespace, project, from: project.default_branch, to: branch) link = url_helpers.namespace_project_compare_url(project.namespace, project, from: project.default_branch, to: branch)
body = "created branch [`#{branch}`](#{link})" body = "created branch [`#{branch}`](#{link})"
create_note(noteable: issue, project: project, author: author, note: body)
create_note(NoteSummary.new(issue, project, author, body, action: 'branch'))
end end
# Called when a Mentionable references a Noteable # Called when a Mentionable references a Noteable
...@@ -349,23 +358,12 @@ module SystemNoteService ...@@ -349,23 +358,12 @@ module SystemNoteService
return if cross_reference_disallowed?(noteable, mentioner) return if cross_reference_disallowed?(noteable, mentioner)
gfm_reference = mentioner.gfm_reference(noteable.project) gfm_reference = mentioner.gfm_reference(noteable.project)
body = cross_reference_note_content(gfm_reference)
note_options = {
project: noteable.project,
author: author,
note: cross_reference_note_content(gfm_reference)
}
if noteable.is_a?(Commit)
note_options.merge!(noteable_type: 'Commit', commit_id: noteable.id)
else
note_options[:noteable] = noteable
end
if noteable.is_a?(ExternalIssue) if noteable.is_a?(ExternalIssue)
noteable.project.issues_tracker.create_cross_reference_note(noteable, mentioner, author) noteable.project.issues_tracker.create_cross_reference_note(noteable, mentioner, author)
else else
create_note(note_options) create_note(NoteSummary.new(noteable, noteable.project, author, body, action: 'cross_reference'))
end end
end end
...@@ -444,7 +442,8 @@ module SystemNoteService ...@@ -444,7 +442,8 @@ module SystemNoteService
def change_task_status(noteable, project, author, new_task) def change_task_status(noteable, project, author, new_task)
status_label = new_task.complete? ? Taskable::COMPLETED : Taskable::INCOMPLETE status_label = new_task.complete? ? Taskable::COMPLETED : Taskable::INCOMPLETE
body = "marked the task **#{new_task.source}** as #{status_label}" body = "marked the task **#{new_task.source}** as #{status_label}"
create_note(noteable: noteable, project: project, author: author, note: body)
create_note(NoteSummary.new(noteable, project, author, body, action: 'task'))
end end
# Called when noteable has been moved to another project # Called when noteable has been moved to another project
...@@ -466,7 +465,8 @@ module SystemNoteService ...@@ -466,7 +465,8 @@ module SystemNoteService
cross_reference = noteable_ref.to_reference(project) cross_reference = noteable_ref.to_reference(project)
body = "moved #{direction} #{cross_reference}" body = "moved #{direction} #{cross_reference}"
create_note(noteable: noteable, project: project, author: author, note: body)
create_note(NoteSummary.new(noteable, project, author, body, action: 'moved'))
end end
private private
...@@ -482,8 +482,11 @@ module SystemNoteService ...@@ -482,8 +482,11 @@ module SystemNoteService
end end
end end
def create_note(args = {}) def create_note(note_summary)
Note.create(args.merge(system: true)) note = Note.create(note_summary.note.merge(system: true))
note.system_note_metadata = SystemNoteMetadata.new(note_summary.metadata) if note_summary.metadata?
note
end end
def cross_reference_note_prefix def cross_reference_note_prefix
......
---
title: Add metadata to system notes
merge_request: 9964
author:
...@@ -10,5 +10,5 @@ ...@@ -10,5 +10,5 @@
# end # end
# #
ActiveSupport::Inflector.inflections do |inflect| ActiveSupport::Inflector.inflections do |inflect|
inflect.uncountable %w(award_emoji project_statistics) inflect.uncountable %w(award_emoji project_statistics system_note_metadata)
end end
class CreateSystemNoteMetadata < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
create_table :system_note_metadata do |t|
t.references :note, null: false
t.integer :commit_count
t.string :action
t.timestamps null: false
end
add_concurrent_foreign_key :system_note_metadata, :notes, column: :note_id
end
def down
drop_table :system_note_metadata
end
end
...@@ -1075,6 +1075,14 @@ ActiveRecord::Schema.define(version: 20170317203554) do ...@@ -1075,6 +1075,14 @@ ActiveRecord::Schema.define(version: 20170317203554) do
add_index "subscriptions", ["subscribable_id", "subscribable_type", "user_id", "project_id"], name: "index_subscriptions_on_subscribable_and_user_id_and_project_id", unique: true, using: :btree add_index "subscriptions", ["subscribable_id", "subscribable_type", "user_id", "project_id"], name: "index_subscriptions_on_subscribable_and_user_id_and_project_id", unique: true, using: :btree
create_table "system_note_metadata", force: :cascade do |t|
t.integer "note_id", null: false
t.integer "commit_count"
t.string "action"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "taggings", force: :cascade do |t| create_table "taggings", force: :cascade do |t|
t.integer "tag_id" t.integer "tag_id"
t.integer "taggable_id" t.integer "taggable_id"
...@@ -1308,6 +1316,7 @@ ActiveRecord::Schema.define(version: 20170317203554) do ...@@ -1308,6 +1316,7 @@ ActiveRecord::Schema.define(version: 20170317203554) do
add_foreign_key "protected_branch_merge_access_levels", "protected_branches" add_foreign_key "protected_branch_merge_access_levels", "protected_branches"
add_foreign_key "protected_branch_push_access_levels", "protected_branches" add_foreign_key "protected_branch_push_access_levels", "protected_branches"
add_foreign_key "subscriptions", "projects", on_delete: :cascade add_foreign_key "subscriptions", "projects", on_delete: :cascade
add_foreign_key "system_note_metadata", "notes", name: "fk_d83a918cb1", on_delete: :cascade
add_foreign_key "timelogs", "issues", name: "fk_timelogs_issues_issue_id", on_delete: :cascade add_foreign_key "timelogs", "issues", name: "fk_timelogs_issues_issue_id", on_delete: :cascade
add_foreign_key "timelogs", "merge_requests", name: "fk_timelogs_merge_requests_merge_request_id", on_delete: :cascade add_foreign_key "timelogs", "merge_requests", name: "fk_timelogs_merge_requests_merge_request_id", on_delete: :cascade
add_foreign_key "trending_projects", "projects", on_delete: :cascade add_foreign_key "trending_projects", "projects", on_delete: :cascade
......
FactoryGirl.define do
factory :system_note_metadata do
note
action 'merge'
end
end
...@@ -29,6 +29,7 @@ notes: ...@@ -29,6 +29,7 @@ notes:
- resolved_by - resolved_by
- todos - todos
- events - events
- system_note_metadata
label_links: label_links:
- target - target
- label - label
......
require 'spec_helper'
describe SystemNoteMetadata, models: true do
describe 'associations' do
it { is_expected.to belong_to(:note) }
end
describe 'validation' do
it { is_expected.to validate_presence_of(:note) }
context 'when action type is invalid' do
subject do
build(:system_note_metadata, note: build(:note), action: 'invalid_type' )
end
it { is_expected.to be_invalid }
end
context 'when action type is valid' do
subject do
build(:system_note_metadata, note: build(:note), action: 'merge' )
end
it { is_expected.to be_valid }
end
end
end
require 'spec_helper'
describe NoteSummary, services: true do
let(:project) { build(:empty_project) }
let(:noteable) { build(:issue) }
let(:user) { build(:user) }
def create_note_summary
described_class.new(noteable, project, user, 'note', action: 'icon', commit_count: 5)
end
describe '#metadata?' do
it 'returns true when metadata present' do
expect(create_note_summary.metadata?).to be_truthy
end
it 'returns false when metadata not present' do
expect(described_class.new(noteable, project, user, 'note').metadata?).to be_falsey
end
end
describe '#note' do
it 'returns note hash' do
expect(create_note_summary.note).to eq(noteable: noteable, project: project, author: user, note: 'note')
end
context 'when noteable is a commit' do
let(:noteable) { build(:commit) }
it 'returns note hash specific to commit' do
expect(create_note_summary.note).to eq(
noteable: nil, project: project, author: user, note: 'note',
noteable_type: 'Commit', commit_id: noteable.id
)
end
end
end
describe '#metadata' do
it 'returns metadata hash' do
expect(create_note_summary.metadata).to eq(action: 'icon', commit_count: 5)
end
end
end
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment