Skip to content
Snippets Groups Projects
Commit 0d5d80b7 authored by Douwe Maan's avatar Douwe Maan
Browse files

Merge branch 'comment-updated-by' into 'master'

Show who last edited a comment if it wasn't the original author

Fixes #1796.

The `updated_by` user is also tracked for issues and merge requests, but it isn't currently shown, because "edited by Person" would give the idea that Person edited the description, while most updates to those are assignee/milestone/labels, in which case who made that change is already visible in the comments.

See merge request !1075
parents 9b6c513b ad55f0d6
No related branches found
No related tags found
No related merge requests found
Showing
with 52 additions and 66 deletions
Loading
Loading
@@ -29,6 +29,7 @@ v 7.14.0 (unreleased)
- Add project star and fork count, group avatar URL and user/group web URL attributes to API
- Fix bug causing Bitbucket importer to crash when OAuth application had been removed.
- Add fetch command to the MR page.
- Show who last edited a comment if it wasn't the original author
- Add ability to manage user email addresses via the API.
- Show buttons to add license, changelog and contribution guide if they're missing.
- Tweak project page buttons.
Loading
Loading
Loading
Loading
@@ -30,13 +30,10 @@ def create
end
 
def update
if note.editable?
note.update_attributes(note_params)
note.reset_events_cache
end
@note = Notes::UpdateService.new(project, current_user, note_params).execute(note)
 
respond_to do |format|
format.json { render_note_json(note) }
format.json { render_note_json(@note) }
format.html { redirect_to :back }
end
end
Loading
Loading
Loading
Loading
@@ -43,21 +43,6 @@ def url_for_issue(issue_iid, project = @project, options = {})
end
end
 
def issue_timestamp(issue)
# Shows the created at time and the updated at time if different
ts = time_ago_with_tooltip(issue.created_at, placement: 'bottom', html_class: 'note_created_ago')
if issue.updated_at != issue.created_at
ts << capture_haml do
haml_tag :span do
haml_concat '&middot;'
haml_concat icon('edit', title: 'edited')
haml_concat time_ago_with_tooltip(issue.updated_at, placement: 'bottom', html_class: 'issue_edited_ago')
end
end
end
ts.html_safe
end
def bulk_update_milestone_options
options_for_select([['None (backlog)', -1]]) +
options_from_collection_for_select(project_active_milestones, 'id',
Loading
Loading
Loading
Loading
@@ -23,21 +23,6 @@ def link_to_commit_diff_line_note(note)
end
end
 
def note_timestamp(note)
# Shows the created at time and the updated at time if different
ts = time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note_created_ago')
if note.updated_at != note.created_at
ts << capture_haml do
haml_tag :span do
haml_concat '&middot;'
haml_concat icon('edit', title: 'edited')
haml_concat time_ago_with_tooltip(note.updated_at, placement: 'bottom', html_class: 'note_edited_ago')
end
end
end
ts.html_safe
end
def noteable_json(noteable)
{
id: noteable.id,
Loading
Loading
Loading
Loading
@@ -21,7 +21,7 @@ def link_to_project(project)
end
 
def link_to_member(project, author, opts = {})
default_opts = { avatar: true, name: true, size: 16 }
default_opts = { avatar: true, name: true, size: 16, author_class: 'author' }
opts = default_opts.merge(opts)
 
return "(deleted)" unless author
Loading
Loading
@@ -32,7 +32,7 @@ def link_to_member(project, author, opts = {})
author_html << image_tag(avatar_icon(author.try(:email), opts[:size]), width: opts[:size], class: "avatar avatar-inline #{"s#{opts[:size]}" if opts[:size]}", alt:'') if opts[:avatar]
 
# Build name span tag
author_html << content_tag(:span, sanitize(author.name), class: 'author') if opts[:name]
author_html << content_tag(:span, sanitize(author.name), class: opts[:author_class]) if opts[:name]
 
author_html = author_html.html_safe
 
Loading
Loading
Loading
Loading
@@ -12,6 +12,7 @@ module Issuable
included do
belongs_to :author, class_name: "User"
belongs_to :assignee, class_name: "User"
belongs_to :updated_by, class_name: "User"
belongs_to :milestone
has_many :notes, as: :noteable, dependent: :destroy
has_many :label_links, as: :target, dependent: :destroy
Loading
Loading
Loading
Loading
@@ -33,6 +33,7 @@ class Note < ActiveRecord::Base
belongs_to :project
belongs_to :noteable, polymorphic: true
belongs_to :author, class_name: "User"
belongs_to :updated_by, class_name: "User"
 
delegate :name, to: :project, prefix: true
delegate :name, :email, to: :author, prefix: true
Loading
Loading
Loading
Loading
@@ -14,7 +14,7 @@ def execute(issue)
filter_params
old_labels = issue.labels.to_a
 
if params.present? && issue.update_attributes(params)
if params.present? && issue.update_attributes(params.merge(updated_by: current_user))
issue.reset_events_cache
 
if issue.labels != old_labels
Loading
Loading
Loading
Loading
@@ -24,7 +24,7 @@ def execute(merge_request)
filter_params
old_labels = merge_request.labels.to_a
 
if params.present? && merge_request.update_attributes(params)
if params.present? && merge_request.update_attributes(params.merge(updated_by: current_user))
merge_request.reset_events_cache
 
if merge_request.labels != old_labels
Loading
Loading
module Notes
class UpdateService < BaseService
def execute
note = project.notes.find(params[:note_id])
note.note = params[:note]
if note.save
notification_service.new_note(note)
def execute(note)
return note unless note.editable?
 
# Skip system notes, like status changes and cross-references.
unless note.system
event_service.leave_note(note, note.author)
note.update_attributes(params.merge(updated_by: current_user))
 
# Create a cross-reference note if this Note contains GFM that
# names an issue, merge request, or commit.
note.references.each do |mentioned|
SystemNoteService.cross_reference(mentioned, note.noteable, note.author)
end
end
end
note.reset_events_cache
 
note
end
Loading
Loading
Loading
Loading
@@ -9,7 +9,13 @@
Open
Issue ##{@issue.iid}
%small.creator
&middot; created by #{link_to_member(@project, @issue.author)} #{issue_timestamp(@issue)}
&middot; created by #{link_to_member(@project, @issue.author)}
= time_ago_with_tooltip(@issue.created_at, placement: 'bottom', html_class: 'issue_created_ago')
- if @issue.updated_at != @issue.created_at
%span
&middot;
= icon('edit', title: 'edited')
= time_ago_with_tooltip(@issue.updated_at, placement: 'bottom', html_class: 'issue_edited_ago')
 
.pull-right
- if can?(current_user, :create_issue, @project)
Loading
Loading
%h4.page-title
.issue-box{ class: issue_box_class(@merge_request) }
= @merge_request.state_human_name
= "Merge Request ##{@merge_request.iid}"
Merge Request ##{@merge_request.iid}
%small.creator
&middot;
created by #{link_to_member(@project, @merge_request.author)} #{time_ago_with_tooltip(@merge_request.created_at)}
created by #{link_to_member(@project, @merge_request.author)}
= time_ago_with_tooltip(@merge_request.created_at)
- if @merge_request.updated_at != @merge_request.created_at
%span
&middot;
= icon('edit', title: 'edited')
= time_ago_with_tooltip(@merge_request.updated_at, placement: 'bottom')
 
.issue-btn-group.pull-right
- if can?(current_user, :update_merge_request, @merge_request)
Loading
Loading
Loading
Loading
@@ -33,7 +33,14 @@
 
%span.note-last-update
= link_to "##{dom_id(note)}", name: dom_id(note), title: "Link here" do
= note_timestamp(note)
= time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note_created_ago')
- if note.updated_at != note.created_at
%span
&middot;
= icon('edit', title: 'edited')
= time_ago_with_tooltip(note.updated_at, placement: 'bottom', html_class: 'note_edited_ago')
- if note.updated_by && note.updated_by != note.author
by #{link_to_member(note.project, note.updated_by, avatar: false, author_class: nil)}
 
- if note.superceded?(@notes)
- if note.upvote?
Loading
Loading
class AddUpdatedByToIssuablesAndNotes < ActiveRecord::Migration
def change
add_column :notes, :updated_by_id, :integer
add_column :issues, :updated_by_id, :integer
add_column :merge_requests, :updated_by_id, :integer
end
end
Loading
Loading
@@ -136,12 +136,13 @@
t.integer "project_id"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "position", default: 0
t.integer "position", default: 0
t.string "branch_name"
t.text "description"
t.integer "milestone_id"
t.string "state"
t.integer "iid"
t.integer "updated_by_id"
end
 
add_index "issues", ["assignee_id"], name: "index_issues_on_assignee_id", using: :btree
Loading
Loading
@@ -238,6 +239,7 @@
t.text "description"
t.integer "position", default: 0
t.datetime "locked_at"
t.integer "updated_by_id"
end
 
add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree
Loading
Loading
@@ -297,6 +299,7 @@
t.integer "noteable_id"
t.boolean "system", default: false, null: false
t.text "st_diff"
t.integer "updated_by_id"
end
 
add_index "notes", ["author_id"], name: "index_notes_on_author_id", using: :btree
Loading
Loading
Loading
Loading
@@ -78,17 +78,15 @@ class Notes < Grape::API
put ":id/#{noteables_str}/:#{noteable_id_str}/notes/:note_id" do
required_attributes! [:body]
 
authorize! :admin_note, user_project.notes.find(params[:note_id])
note = user_project.notes.find(params[:note_id])
authorize! :admin_note, note
 
opts = {
note: params[:body],
note_id: params[:note_id],
noteable_type: noteables_str.classify,
noteable_id: params[noteable_id_str]
note: params[:body]
}
 
@note = ::Notes::UpdateService.new(user_project, current_user,
opts).execute
@note = ::Notes::UpdateService.new(user_project, current_user, opts).execute(note)
 
if @note.valid?
present @note, with: Entities::Note
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment