diff --git a/app/assets/stylesheets/pages/tasks.scss b/app/assets/stylesheets/pages/tasks.scss index f0e13f4d7860057631cf24146f17829c038d5410..a3dffeed4ab21206d614a143969962277ed53eab 100644 --- a/app/assets/stylesheets/pages/tasks.scss +++ b/app/assets/stylesheets/pages/tasks.scss @@ -59,6 +59,15 @@ .task-note { word-wrap: break-word; + .md { + color: #7f8fa4; + font-size: $gl-font-size; + + p { + color: #5c5d5e; + } + } + pre { border: none; background: #f9f9f9; @@ -68,10 +77,25 @@ overflow: hidden; } + .note-image-attach { + margin-top: 4px; + margin-left: 0px; + max-width: 200px; + float: none; + } + p:last-child { 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 } diff --git a/app/helpers/tasks_helper.rb b/app/helpers/tasks_helper.rb index cf4eab0ef94f910b8e8a6298dee4c9c50d8455c1..6975c1d1604d5e74ee2226e3fb06b6b0610fb28b 100644 --- a/app/helpers/tasks_helper.rb +++ b/app/helpers/tasks_helper.rb @@ -22,4 +22,20 @@ module TasksHelper [task.action_name, target].join(" ") 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 diff --git a/app/models/note.rb b/app/models/note.rb index b3809ad81e096eedd7df03ecb31f980fcaaecf33..73412024f4e56dcb22db22c45e9b04696399698e 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -37,6 +37,8 @@ class Note < ActiveRecord::Base belongs_to :author, class_name: "User" belongs_to :updated_by, class_name: "User" + has_many :tasks, dependent: :delete_all + delegate :name, to: :project, prefix: true delegate :name, :email, to: :author, prefix: true diff --git a/app/models/task.rb b/app/models/task.rb index c9881991e38f2c3c0968a711888ecbf42c0955cb..38c6637e456f7a69053ccb5f2c8c1d7cd6f3bdd5 100644 --- a/app/models/task.rb +++ b/app/models/task.rb @@ -8,6 +8,7 @@ # target_id :integer not null # target_type :string not null # author_id :integer +# note_id :integer # action :integer # state :string not null # created_at :datetime @@ -19,6 +20,7 @@ class Task < ActiveRecord::Base MENTIONED = 2 belongs_to :author, class_name: "User" + belongs_to :note belongs_to :project belongs_to :target, polymorphic: true, touch: true belongs_to :user @@ -52,6 +54,10 @@ class Task < ActiveRecord::Base target.respond_to? :title end + def note_text + note.try(:note) + end + def target_iid target.respond_to?(:iid) ? target.iid : target_id end diff --git a/app/services/task_service.rb b/app/services/task_service.rb index fa615f4cfcf2843f6262409a14cde0f64da5043a..d97fa146f5cc07fcfdf7b50ede292af080e86ec4 100644 --- a/app/services/task_service.rb +++ b/app/services/task_service.rb @@ -77,7 +77,17 @@ class TaskService def new_note(note) # Skip system notes, like status changes and cross-references 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 @@ -94,14 +104,15 @@ class TaskService private - def create_task(project, target, author, user, action) + def create_task(project, target, author, user, action, note = nil) attributes = { project: project, user_id: user.id, author_id: author.id, target_id: target.id, target_type: target.class.name, - action: action + action: action, + note: note } Task.create(attributes) diff --git a/app/views/dashboard/tasks/_common.html.haml b/app/views/dashboard/tasks/_common.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..b6d0c3c03acbbf3a10071899ed2ee7220b05f92d --- /dev/null +++ b/app/views/dashboard/tasks/_common.html.haml @@ -0,0 +1,17 @@ +.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] + + · #{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 diff --git a/app/views/dashboard/tasks/_note.html.haml b/app/views/dashboard/tasks/_note.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..2cfd55afccb29d074045fac3935862a09cd16292 --- /dev/null +++ b/app/views/dashboard/tasks/_note.html.haml @@ -0,0 +1,26 @@ +.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) + + · #{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 diff --git a/app/views/dashboard/tasks/_task.html.haml b/app/views/dashboard/tasks/_task.html.haml index b33f3894fd354138be63ab8fb61ff69c6882bc8d..2ca8f0dad63f9d8da55665426eeadb4408f83570 100644 --- a/app/views/dashboard/tasks/_task.html.haml +++ b/app/views/dashboard/tasks/_task.html.haml @@ -2,20 +2,7 @@ .task-item{class: "#{task.body? ? 'task-block' : 'task-inline' }"} = image_tag avatar_icon(task.author_email, 40), class: "avatar s40", alt:'' - .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] - - · #{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 + - if task.note.present? + = render 'note', task: task + - else + = render 'common', task: task diff --git a/db/migrate/20160217174422_add_note_to_tasks.rb b/db/migrate/20160217174422_add_note_to_tasks.rb new file mode 100644 index 0000000000000000000000000000000000000000..da5cb2e05db693fdd8f3b5b72c71eabfac39e53c --- /dev/null +++ b/db/migrate/20160217174422_add_note_to_tasks.rb @@ -0,0 +1,5 @@ +class AddNoteToTasks < ActiveRecord::Migration + def change + add_reference :tasks, :note, index: true + end +end diff --git a/db/schema.rb b/db/schema.rb index 183227a91cae87c8d4fcaa71671634cc92a01e92..2b726d176827aaa86f9a85abd2ac4110a58f85db 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # 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 enable_extension "plpgsql" @@ -834,9 +834,11 @@ ActiveRecord::Schema.define(version: 20160217100506) do t.string "state", null: false t.datetime "created_at" t.datetime "updated_at" + t.integer "note_id" end 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", ["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 diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index e6da3724d330bc09ae38276286ed1cf1ffb4c2ec..e146f53c3f759e82a008f2e328fcb9b5f4ffd2d0 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -26,6 +26,8 @@ describe Note, models: true do it { is_expected.to belong_to(:project) } it { is_expected.to belong_to(:noteable) } it { is_expected.to belong_to(:author).class_name('User') } + + it { is_expected.to have_many(:tasks).dependent(:delete_all) } end describe 'validation' do diff --git a/spec/models/task_spec.rb b/spec/models/task_spec.rb index 86317626cc3a7823d22d48f83abc905c9ba8a7ff..2f0b51ffc619f465da3f0b43a7d6af7d53574ad7 100644 --- a/spec/models/task_spec.rb +++ b/spec/models/task_spec.rb @@ -8,6 +8,7 @@ # target_id :integer not null # target_type :string not null # author_id :integer +# note_id :integer # action :integer # state :string not null # created_at :datetime @@ -19,6 +20,7 @@ require 'spec_helper' describe Task, models: true do describe 'relationships' do 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(:target).touch(true) } it { is_expected.to belong_to(:user) } @@ -48,6 +50,20 @@ describe Task, models: true do it 'returns false when target does not respond to title' 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 it 'returns target.iid when target respond to iid' it 'returns target_id when target does not respond to iid' diff --git a/spec/services/task_service_spec.rb b/spec/services/task_service_spec.rb index b35715c8b7ad6c99ebd79487d0b567e00217d11a..e373d3a4931b0d162f74e2fa587aa8967657cc7e 100644 --- a/spec/services/task_service_spec.rb +++ b/spec/services/task_service_spec.rb @@ -128,6 +128,17 @@ describe TaskService, services: true do expect(first_pending_task.reload).to be_pending expect(second_pending_task.reload).to be_pending 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