Commit c1fe066b authored by Phil Hughes's avatar Phil Hughes Committed by Douwe Maan

Added resolve button to discussions

Top count displays how many resolved discussions
parent 43131802
...@@ -6,11 +6,12 @@ ...@@ -6,11 +6,12 @@
$ => $ =>
@DiffNotesApp = new Vue @DiffNotesApp = new Vue
el: '#notes' el: '#diff-comments-app'
components: components:
'resolve-btn': ResolveBtn 'resolve-btn': ResolveBtn
'resolve-all': ResolveAll
new Vue new Vue
el: '#resolve-all-app' el: '#resolve-count-app'
components: components:
'resolve-all': ResolveAll 'resolve-count': ResolveCount
@ResolveAll = Vue.extend @ResolveAll = Vue.extend
props:
discussionId: String
namespace: String
data: -> data: ->
comments: CommentsStore.state comments: CommentsStore.state
loading: false loading: false
props:
namespace: String
computed: computed:
resolved: ->
resolvedCount = 0
for noteId, resolved of this.comments
resolvedCount++ if resolved
resolvedCount
commentsCount: ->
Object.keys(this.comments).length
buttonText: ->
if this.allResolved then 'Un-resolve all' else 'Resolve all'
allResolved: -> allResolved: ->
this.resolved is this.commentsCount isResolved = true
for noteId, resolved of this.comments[this.discussionId]
isResolved = false unless resolved
isResolved
buttonText: ->
if this.allResolved then "Un-resolve all" else "Resolve all"
methods: methods:
updateAll: -> resolve: ->
ids = CommentsStore.getAllForState(this.allResolved)
this.loading = true this.loading = true
ResolveService ResolveService
.resolveAll(this.namespace, ids, !this.allResolved) .resolveAll(this.namespace, this.discussionId, this.allResolved)
.then => .then =>
this.loading = false this.loading = false
@ResolveBtn = Vue.extend @ResolveBtn = Vue.extend
props: props:
noteId: Number noteId: Number
discussionId: String
resolved: Boolean resolved: Boolean
namespace: String namespace: String
data: -> data: ->
...@@ -9,7 +10,7 @@ ...@@ -9,7 +10,7 @@
computed: computed:
buttonText: -> buttonText: ->
if this.isResolved then "Mark as un-resolved" else "Mark as resolved" if this.isResolved then "Mark as un-resolved" else "Mark as resolved"
isResolved: -> this.comments[this.noteId] isResolved: -> CommentsStore.get(this.discussionId, this.noteId)
methods: methods:
updateTooltip: -> updateTooltip: ->
$(this.$els.button) $(this.$els.button)
...@@ -18,13 +19,13 @@ ...@@ -18,13 +19,13 @@
resolve: -> resolve: ->
this.loading = true this.loading = true
ResolveService ResolveService
.resolve(this.namespace, this.noteId, !this.isResolved) .resolve(this.namespace, this.discussionId, this.noteId, !this.isResolved)
.then => .then =>
this.loading = false this.loading = false
this.$nextTick this.updateTooltip this.$nextTick this.updateTooltip
compiled: -> compiled: ->
$(this.$els.button).tooltip() $(this.$els.button).tooltip()
destroyed: -> destroyed: ->
CommentsStore.delete(this.noteId) CommentsStore.delete(this.discussionId, this.noteId)
created: -> created: ->
CommentsStore.create(this.noteId, this.resolved) CommentsStore.create(this.discussionId, this.noteId, this.resolved)
@ResolveCount = Vue.extend
data: ->
comments: CommentsStore.state
loading: false
computed:
resolved: ->
resolvedCount = 0
for discussionId, comments of this.comments
resolved = true
for noteId, resolved of comments
resolved = false unless resolved
resolvedCount++ if resolved
resolvedCount
commentsCount: ->
Object.keys(this.comments).length
allResolved: ->
this.resolved is this.commentsCount
...@@ -12,20 +12,27 @@ class ResolveService ...@@ -12,20 +12,27 @@ class ResolveService
Vue.http.headers.common['X-CSRF-Token'] = $.rails.csrfToken() Vue.http.headers.common['X-CSRF-Token'] = $.rails.csrfToken()
@resource = Vue.resource('notes{/id}', {}, actions) @resource = Vue.resource('notes{/id}', {}, actions)
resolve: (namespace, id, resolve) -> resolve: (namespace, discussionId, noteId, resolve) ->
Vue.http.options.root = "/#{namespace}" Vue.http.options.root = "/#{namespace}"
@resource @resource
.resolve({ id: id }, { resolved: resolve }) .resolve({ id: noteId }, { discussion: discussionId, resolved: resolve })
.then (response) -> .then (response) ->
if response.status is 200 if response.status is 200
CommentsStore.update(id, resolve) CommentsStore.update(discussionId, noteId, resolve)
resolveAll: (namespace, ids, resolve) -> resolveAll: (namespace, discussionId, allResolve) ->
Vue.http.options.root = "/#{namespace}" Vue.http.options.root = "/#{namespace}"
ids = []
for noteId, resolved of CommentsStore.state[discussionId]
ids.push(noteId) if resolved is allResolve
@resource @resource
.all({}, { ids: ids, resolve: resolve }) .all({}, { ids: ids, discussion: discussionId, resolved: !allResolve })
.then (response) -> .then (response) ->
CommentsStore.updateAll(resolve) if response.status is 200
for noteId in ids
CommentsStore.update(discussionId, noteId, !allResolve)
$ -> $ ->
@ResolveService = new ResolveService() @ResolveService = new ResolveService()
@CommentsStore = @CommentsStore =
state: {} state: {}
create: (id, resolved) -> get: (discussionId, noteId) ->
Vue.set(this.state, id, resolved) this.state[discussionId][noteId]
update: (id, resolved) -> create: (discussionId, noteId, resolved) ->
this.state[id] = resolved unless this.state[discussionId]?
delete: (id) -> Vue.set(this.state, discussionId, {})
Vue.delete(this.state, id)
updateAll: (state) -> Vue.set(this.state[discussionId], noteId, resolved)
for id,resolved of this.state update: (discussionId, noteId, resolved) ->
this.update(id, state) if resolved isnt state this.state[discussionId][noteId] = resolved
getAllForState: (state) -> delete: (discussionId, noteId) ->
ids = [] Vue.delete(this.state[discussionId], noteId)
for id,resolved of this.state
ids.push(id) if resolved is state if Object.keys(this.state[discussionId]).length is 0
ids Vue.delete(this.state, discussionId)
...@@ -157,6 +157,11 @@ class @MergeRequestTabs ...@@ -157,6 +157,11 @@ class @MergeRequestTabs
url: "#{source}.json" + @_location.search url: "#{source}.json" + @_location.search
success: (data) => success: (data) =>
$('#diffs').html data.html $('#diffs').html data.html
if $('resolve-btn, resolve-all').length and DiffNotesApp?
$('resolve-btn, resolve-all').each ->
DiffNotesApp.$compile $(this).get(0)
gl.utils.localTimeAgo($('.js-timeago', 'div#diffs')) gl.utils.localTimeAgo($('.js-timeago', 'div#diffs'))
$('#diffs .js-syntax-highlight').syntaxHighlight() $('#diffs .js-syntax-highlight').syntaxHighlight()
$('#diffs .diff-file').singleFileDiff() $('#diffs .diff-file').singleFileDiff()
......
...@@ -271,8 +271,8 @@ class @Notes ...@@ -271,8 +271,8 @@ class @Notes
# append new note to all matching discussions # append new note to all matching discussions
discussionContainer.append note_html discussionContainer.append note_html
if $('resolve-btn').length and DiffNotesApp? if $('resolve-btn, resolve-all').length and DiffNotesApp?
$('resolve-btn').each -> $('resolve-btn, resolve-all').each ->
DiffNotesApp.$compile $(this).get(0) DiffNotesApp.$compile $(this).get(0)
gl.utils.localTimeAgo($('.js-timeago', note_html), false) gl.utils.localTimeAgo($('.js-timeago', note_html), false)
...@@ -507,10 +507,10 @@ class @Notes ...@@ -507,10 +507,10 @@ class @Notes
replyToDiscussionNote: (e) => replyToDiscussionNote: (e) =>
form = @formClone.clone() form = @formClone.clone()
replyLink = $(e.target).closest(".js-discussion-reply-button") replyLink = $(e.target).closest(".js-discussion-reply-button")
replyLink.hide() replyLink
.closest('.discussion-reply-holder')
# insert the form after the button .hide()
replyLink.after form .after form
# show the form # show the form
@setupDiscussionNoteForm(replyLink, form) @setupDiscussionNoteForm(replyLink, form)
...@@ -606,7 +606,9 @@ class @Notes ...@@ -606,7 +606,9 @@ class @Notes
form.find(".js-note-text").data("autosave").reset() form.find(".js-note-text").data("autosave").reset()
# show the reply button (will only work for replies) # show the reply button (will only work for replies)
form.prev(".js-discussion-reply-button").show() form
.prev('.discussion-reply-holder')
.show()
if row.is(".js-temp-notes-holder") if row.is(".js-temp-notes-holder")
# remove temporary row for diff lines # remove temporary row for diff lines
row.remove() row.remove()
......
...@@ -156,6 +156,27 @@ ...@@ -156,6 +156,27 @@
.discussion-reply-holder { .discussion-reply-holder {
background-color: $white-light; background-color: $white-light;
padding: 10px 16px; padding: 10px 16px;
.btn-group-justified {
table-layout: auto;
}
.btn-group:first-child {
width: 100%;
}
.discussion-with-resolve-btn {
.btn-group:first-child .btn {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.btn-group:last-child .btn {
margin-left: -1px;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
}
} }
} }
......
...@@ -81,10 +81,8 @@ module NotesHelper ...@@ -81,10 +81,8 @@ module NotesHelper
data = discussion.reply_attributes.merge(line_type: line_type) data = discussion.reply_attributes.merge(line_type: line_type)
content_tag(:div, class: "discussion-reply-holder") do button_tag 'Reply...', class: 'btn btn-text-field js-discussion-reply-button',
button_tag 'Reply...', class: 'btn btn-text-field js-discussion-reply-button', data: data, title: 'Add a reply'
data: data, title: 'Add a reply'
end
end end
def note_max_access_for_user(note) def note_max_access_for_user(note)
......
...@@ -3,4 +3,10 @@ ...@@ -3,4 +3,10 @@
%td.notes_content %td.notes_content
%ul.notes{ data: { discussion_id: discussion.id } } %ul.notes{ data: { discussion_id: discussion.id } }
= render partial: "projects/notes/note", collection: discussion.notes, as: :note = render partial: "projects/notes/note", collection: discussion.notes, as: :note
= link_to_reply_discussion(discussion)
.discussion-reply-holder
.btn-group-justified.discussion-with-resolve-btn{ role: "group" }
.btn-group{ role: "group" }
= link_to_reply_discussion(note)
.btn-group{ role: "group" }
= render "projects/notes/discussions/resolve_all", note: note
...@@ -5,7 +5,12 @@ ...@@ -5,7 +5,12 @@
%ul.notes{ data: { discussion_id: discussion_left.id } } %ul.notes{ data: { discussion_id: discussion_left.id } }
= render partial: "projects/notes/note", collection: discussion_left.notes, as: :note = render partial: "projects/notes/note", collection: discussion_left.notes, as: :note
= link_to_reply_discussion(discussion_left, 'old') .discussion-reply-holder
.btn-group-justified.discussion-with-resolve-btn{ role: "group" }
.btn-group{ role: "group" }
= link_to_reply_discussion(note_left, 'old')
.btn-group{ role: "group" }
= render "projects/notes/discussions/resolve_all", note: note_left
- else - else
%td.notes_line.old= "" %td.notes_line.old= ""
%td.notes_content.parallel.old= "" %td.notes_content.parallel.old= ""
...@@ -16,7 +21,12 @@ ...@@ -16,7 +21,12 @@
%ul.notes{ data: { discussion_id: discussion_right.id } } %ul.notes{ data: { discussion_id: discussion_right.id } }
= render partial: "projects/notes/note", collection: discussion_right.notes, as: :note = render partial: "projects/notes/note", collection: discussion_right.notes, as: :note
= link_to_reply_discussion(discussion_right, 'new') .discussion-reply-holder
.btn-group-justified.discussion-with-resolve-btn{ role: "group" }
.btn-group{ role: "group" }
= link_to_reply_discussion(note_right, 'new')
.btn-group{ role: "group" }
= render "projects/notes/discussions/resolve_all", note: note_right
- else - else
%td.notes_line.new= "" %td.notes_line.new= ""
%td.notes_content.parallel.new= "" %td.notes_content.parallel.new= ""
...@@ -45,14 +45,11 @@ ...@@ -45,14 +45,11 @@
= link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal" = link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal"
- if current_user - if current_user
#resolve-all-app{ "v-cloak" => true } #resolve-count-app{ "v-cloak" => true }
%resolve-all{ ":namespace" => "'#{@project.namespace.path}/#{@project.path}'", "inline-template" => true } %resolve-count{ "inline-template" => true }
.line-resolve-all{ "v-show" => "commentsCount > 0" } .line-resolve-all{ "v-show" => "commentsCount > 0" }
%button.btn.btn-gray{ type: "button", "aria-label" => "Resolve all", "@click" => "updateAll", ":disabled" => "loading" }
= icon("spinner spin", "v-show" => "loading")
{{ buttonText }}
%span.line-resolve-text %span.line-resolve-text
{{ resolved }}/{{ commentsCount }} comments resolved {{ resolved }}/{{ commentsCount }} discussions resolved
- if @commits_count.nonzero? - if @commits_count.nonzero?
%ul.merge-request-tabs.nav-links.no-top.no-bottom %ul.merge-request-tabs.nav-links.no-top.no-bottom
...@@ -74,7 +71,7 @@ ...@@ -74,7 +71,7 @@
Changes Changes
%span.badge= @merge_request.diff_size %span.badge= @merge_request.diff_size
.tab-content .tab-content#diff-comments-app
#notes.notes.tab-pane.voting_notes #notes.notes.tab-pane.voting_notes
.content-block.content-block-small.oneline-block .content-block.content-block-small.oneline-block
= render 'award_emoji/awards_block', awardable: @merge_request, inline: true = render 'award_emoji/awards_block', awardable: @merge_request, inline: true
......
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
- if access and not note.system - if access and not note.system
%span.note-role.hidden-xs= access %span.note-role.hidden-xs= access
- if !note.system && current_user - if !note.system && current_user
%resolve-btn{ ":namespace" => "'#{note.project.namespace.path}/#{note.project.path}'", ":note-id" => note.id, ":resolved" => "false", "inline-template" => true, "v-ref:note_#{note.id}" => true } %resolve-btn{ ":namespace" => "'#{note.project.namespace.path}/#{note.project.path}'", ":discussion-id" => "'#{note.discussion_id}'", ":note-id" => note.id, ":resolved" => "false", "inline-template" => true, "v-ref:note_#{note.id}" => true }
.note-action-button .note-action-button
= icon("spin spinner", "v-show" => "loading") = icon("spin spinner", "v-show" => "loading")
%button.line-resolve-btn{ type: "button", ":class" => "{ 'is-active': isResolved }", ":aria-label" => "buttonText", "@click" => "resolve", ":title" => "buttonText", "v-show" => "!loading", "v-el:button" => true } %button.line-resolve-btn{ type: "button", ":class" => "{ 'is-active': isResolved }", ":aria-label" => "buttonText", "@click" => "resolve", ":title" => "buttonText", "v-show" => "!loading", "v-el:button" => true }
......
%resolve-all{ ":namespace" => "'#{note.project.namespace.path}/#{note.project.path}'", ":discussion-id" => "'#{note.discussion_id}'", "inline-template" => true, "v-cloak" => true }
%button.btn.btn-success{ type: "button", "@click" => "resolve", ":disabled" => "loading" }
= icon("spinner spin", "v-show" => "loading")
{{ buttonText }}
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