Create a pending task when a user is mentioned on a note

parent 14fc05eb
...@@ -59,6 +59,15 @@ ...@@ -59,6 +59,15 @@
.task-note { .task-note {
word-wrap: break-word; word-wrap: break-word;
.md {
color: #7f8fa4;
font-size: $gl-font-size;
p {
color: #5c5d5e;
}
}
pre { pre {
border: none; border: none;
background: #f9f9f9; background: #f9f9f9;
...@@ -68,10 +77,25 @@ ...@@ -68,10 +77,25 @@
overflow: hidden; overflow: hidden;
} }
.note-image-attach {
margin-top: 4px;
margin-left: 0px;
max-width: 200px;
float: none;
}
p:last-child { p:last-child {
margin-bottom: 0; margin-bottom: 0;
} }
} }
.task-note-icon {
color: #777;
float: left;
font-size: $gl-font-size;
line-height: 16px;
margin-right: 5px;
}
} }
&:last-child { border:none } &:last-child { border:none }
......
...@@ -22,4 +22,20 @@ module TasksHelper ...@@ -22,4 +22,20 @@ module TasksHelper
[task.action_name, target].join(" ") [task.action_name, target].join(" ")
end end
def task_note_link_html(task)
link_to task_note_target_path(task) do
"##{task.target_iid}"
end
end
def task_note_target_path(task)
polymorphic_path([task.project.namespace.becomes(Namespace),
task.project, task.target], anchor: dom_id(task.note))
end
def task_note(text, options = {})
text = first_line_in_markdown(text, 150, options)
sanitize(text, tags: %w(a img b pre code p span))
end
end end
...@@ -37,6 +37,8 @@ class Note < ActiveRecord::Base ...@@ -37,6 +37,8 @@ class Note < ActiveRecord::Base
belongs_to :author, class_name: "User" belongs_to :author, class_name: "User"
belongs_to :updated_by, class_name: "User" belongs_to :updated_by, class_name: "User"
has_many :tasks, dependent: :delete_all
delegate :name, to: :project, prefix: true delegate :name, to: :project, prefix: true
delegate :name, :email, to: :author, prefix: true delegate :name, :email, to: :author, prefix: true
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
# target_id :integer not null # target_id :integer not null
# target_type :string not null # target_type :string not null
# author_id :integer # author_id :integer
# note_id :integer
# action :integer # action :integer
# state :string not null # state :string not null
# created_at :datetime # created_at :datetime
...@@ -19,6 +20,7 @@ class Task < ActiveRecord::Base ...@@ -19,6 +20,7 @@ class Task < ActiveRecord::Base
MENTIONED = 2 MENTIONED = 2
belongs_to :author, class_name: "User" belongs_to :author, class_name: "User"
belongs_to :note
belongs_to :project belongs_to :project
belongs_to :target, polymorphic: true, touch: true belongs_to :target, polymorphic: true, touch: true
belongs_to :user belongs_to :user
...@@ -52,6 +54,10 @@ class Task < ActiveRecord::Base ...@@ -52,6 +54,10 @@ class Task < ActiveRecord::Base
target.respond_to? :title target.respond_to? :title
end end
def note_text
note.try(:note)
end
def target_iid def target_iid
target.respond_to?(:iid) ? target.iid : target_id target.respond_to?(:iid) ? target.iid : target_id
end end
......
...@@ -77,7 +77,17 @@ class TaskService ...@@ -77,7 +77,17 @@ class TaskService
def new_note(note) def new_note(note)
# Skip system notes, like status changes and cross-references # Skip system notes, like status changes and cross-references
unless note.system unless note.system
mark_pending_tasks_as_done(note.noteable, note.author) project = note.project
target = note.noteable
author = note.author
mark_pending_tasks_as_done(target, author)
mentioned_users = build_mentioned_users(project, note, author)
mentioned_users.each do |user|
create_task(project, target, author, user, Task::MENTIONED, note)
end
end end
end end
...@@ -94,14 +104,15 @@ class TaskService ...@@ -94,14 +104,15 @@ class TaskService
private private
def create_task(project, target, author, user, action) def create_task(project, target, author, user, action, note = nil)
attributes = { attributes = {
project: project, project: project,
user_id: user.id, user_id: user.id,
author_id: author.id, author_id: author.id,
target_id: target.id, target_id: target.id,
target_type: target.class.name, target_type: target.class.name,
action: action action: action,
note: note
} }
Task.create(attributes) Task.create(attributes)
......
.task-title
%span.author_name= link_to_author task
%span.task_label{class: task.action_name}
= task_action_name(task)
%strong= link_to "##{task.target_iid}", [task.project.namespace.becomes(Namespace), task.project, task.target]
&middot; #{time_ago_with_tooltip(task.created_at)}
- if task.pending?
.task-actions.pull-right
= link_to 'Done', [:dashboard, task], method: :delete, class: 'btn'
- if task.body?
.task-body
.task-note
= task.target.title
.task-title
%span.author_name
= link_to_author task
%span.task_label{class: task.action_name}
= task_action_name(task)
= task_note_link_html(task)
&middot; #{time_ago_with_tooltip(task.created_at)}
- if task.pending?
.task-actions.pull-right
= link_to 'Done', [:dashboard, task], method: :delete, class: 'btn'
.task-body
.task-note
.md
= task_note(task.note_text, project: task.project)
- note = task.note
- if note.attachment.url
- if note.attachment.image?
= link_to note.attachment.url, target: '_blank' do
= image_tag note.attachment.url, class: 'note-image-attach'
- else
= link_to note.attachment.url, target: "_blank", class: 'note-file-attach' do
%i.fa.fa-paperclip
= note.attachment_identifier
...@@ -2,20 +2,7 @@ ...@@ -2,20 +2,7 @@
.task-item{class: "#{task.body? ? 'task-block' : 'task-inline' }"} .task-item{class: "#{task.body? ? 'task-block' : 'task-inline' }"}
= image_tag avatar_icon(task.author_email, 40), class: "avatar s40", alt:'' = image_tag avatar_icon(task.author_email, 40), class: "avatar s40", alt:''
.task-title - if task.note.present?
%span.author_name= link_to_author task = render 'note', task: task
%span.task_label{class: task.action_name} - else
= task_action_name(task) = render 'common', task: task
%strong= link_to "##{task.target_iid}", [task.project.namespace.becomes(Namespace), task.project, task.target]
&middot; #{time_ago_with_tooltip(task.created_at)}
- if task.pending?
.task-actions.pull-right
= link_to 'Done', [:dashboard, task], method: :delete, class: 'btn'
- if task.body?
.task-body
.task-note
= task.target.title
class AddNoteToTasks < ActiveRecord::Migration
def change
add_reference :tasks, :note, index: true
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20160217100506) do ActiveRecord::Schema.define(version: 20160217174422) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -834,9 +834,11 @@ ActiveRecord::Schema.define(version: 20160217100506) do ...@@ -834,9 +834,11 @@ ActiveRecord::Schema.define(version: 20160217100506) do
t.string "state", null: false t.string "state", null: false
t.datetime "created_at" t.datetime "created_at"
t.datetime "updated_at" t.datetime "updated_at"
t.integer "note_id"
end end
add_index "tasks", ["author_id"], name: "index_tasks_on_author_id", using: :btree add_index "tasks", ["author_id"], name: "index_tasks_on_author_id", using: :btree
add_index "tasks", ["note_id"], name: "index_tasks_on_note_id", using: :btree
add_index "tasks", ["project_id"], name: "index_tasks_on_project_id", using: :btree add_index "tasks", ["project_id"], name: "index_tasks_on_project_id", using: :btree
add_index "tasks", ["state"], name: "index_tasks_on_state", using: :btree add_index "tasks", ["state"], name: "index_tasks_on_state", using: :btree
add_index "tasks", ["target_type", "target_id"], name: "index_tasks_on_target_type_and_target_id", using: :btree add_index "tasks", ["target_type", "target_id"], name: "index_tasks_on_target_type_and_target_id", using: :btree
......
...@@ -26,6 +26,8 @@ describe Note, models: true do ...@@ -26,6 +26,8 @@ describe Note, models: true do
it { is_expected.to belong_to(:project) } it { is_expected.to belong_to(:project) }
it { is_expected.to belong_to(:noteable) } it { is_expected.to belong_to(:noteable) }
it { is_expected.to belong_to(:author).class_name('User') } it { is_expected.to belong_to(:author).class_name('User') }
it { is_expected.to have_many(:tasks).dependent(:delete_all) }
end end
describe 'validation' do describe 'validation' do
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
# target_id :integer not null # target_id :integer not null
# target_type :string not null # target_type :string not null
# author_id :integer # author_id :integer
# note_id :integer
# action :integer # action :integer
# state :string not null # state :string not null
# created_at :datetime # created_at :datetime
...@@ -19,6 +20,7 @@ require 'spec_helper' ...@@ -19,6 +20,7 @@ require 'spec_helper'
describe Task, models: true do describe Task, models: true do
describe 'relationships' do describe 'relationships' do
it { is_expected.to belong_to(:author).class_name("User") } it { is_expected.to belong_to(:author).class_name("User") }
it { is_expected.to belong_to(:note) }
it { is_expected.to belong_to(:project) } it { is_expected.to belong_to(:project) }
it { is_expected.to belong_to(:target).touch(true) } it { is_expected.to belong_to(:target).touch(true) }
it { is_expected.to belong_to(:user) } it { is_expected.to belong_to(:user) }
...@@ -48,6 +50,20 @@ describe Task, models: true do ...@@ -48,6 +50,20 @@ describe Task, models: true do
it 'returns false when target does not respond to title' it 'returns false when target does not respond to title'
end end
describe '#note_text' do
it 'returns nil when note is blank' do
subject.note = nil
expect(subject.note_text).to be_nil
end
it 'returns note when note is present' do
subject.note = build(:note, note: 'quick fix')
expect(subject.note_text).to eq 'quick fix'
end
end
describe '#target_iid' do describe '#target_iid' do
it 'returns target.iid when target respond to iid' it 'returns target.iid when target respond to iid'
it 'returns target_id when target does not respond to iid' it 'returns target_id when target does not respond to iid'
......
...@@ -128,6 +128,17 @@ describe TaskService, services: true do ...@@ -128,6 +128,17 @@ describe TaskService, services: true do
expect(first_pending_task.reload).to be_pending expect(first_pending_task.reload).to be_pending
expect(second_pending_task.reload).to be_pending expect(second_pending_task.reload).to be_pending
end end
it 'creates a task for each valid mentioned user' do
note.update_attribute(:note, mentions)
service.new_note(note)
should_create_task(user: michael, target: issue, author: john_doe, action: Task::MENTIONED, note: note)
should_create_task(user: author, target: issue, author: john_doe, action: Task::MENTIONED, note: note)
should_not_create_task(user: john_doe, target: issue, author: john_doe, action: Task::MENTIONED, note: note)
should_not_create_task(user: stranger, target: issue, author: john_doe, action: Task::MENTIONED, note: note)
end
end end
end end
......
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