diff --git a/services/web/app/coffee/Features/DocumentUpdater/DocumentUpdaterHandler.coffee b/services/web/app/coffee/Features/DocumentUpdater/DocumentUpdaterHandler.coffee index 805f063808..f3cbe4be7f 100644 --- a/services/web/app/coffee/Features/DocumentUpdater/DocumentUpdaterHandler.coffee +++ b/services/web/app/coffee/Features/DocumentUpdater/DocumentUpdaterHandler.coffee @@ -123,20 +123,23 @@ module.exports = DocumentUpdaterHandler = logger.error project_id:project_id, doc_id:doc_id, url: url, "doc updater returned a non-success status code: #{res.statusCode}" callback new Error("doc updater returned a non-success status code: #{res.statusCode}") - acceptChange: (project_id, doc_id, change_id, callback = (error) ->) -> - timer = new metrics.Timer("accept-change") - url = "#{settings.apis.documentupdater.url}/project/#{project_id}/doc/#{doc_id}/change/#{change_id}/accept" - logger.log {project_id, doc_id, change_id}, "accepting change in document updater" - request.post url, (error, res, body)-> + acceptChanges: (project_id, doc_id, change_ids = [], callback = (error) ->) -> + timer = new metrics.Timer("accept-changes") + reqSettings = + url: "#{settings.apis.documentupdater.url}/project/#{project_id}/doc/#{doc_id}/change/accept" + json: + change_ids: change_ids + logger.log {project_id, doc_id }, "accepting #{ change_ids.length } changes" + request.post reqSettings, (error, res, body)-> timer.done() if error? - logger.error {err:error, project_id, doc_id, change_id}, "error accepting change in doc updater" + logger.error {err:error, project_id, doc_id }, "error accepting #{ change_ids.length } changes in doc updater" return callback(error) if res.statusCode >= 200 and res.statusCode < 300 - logger.log {project_id, doc_id, change_id}, "accepted change in document updater" + logger.log {project_id, doc_id }, "accepted #{ change_ids.length } changes in document updater" return callback(null) else - logger.error {project_id, doc_id, change_id}, "doc updater returned a non-success status code: #{res.statusCode}" + logger.error {project_id, doc_id }, "doc updater returned a non-success status code: #{res.statusCode}" callback new Error("doc updater returned a non-success status code: #{res.statusCode}") deleteThread: (project_id, doc_id, thread_id, callback = (error) ->) -> diff --git a/services/web/app/views/project/editor/review-panel.pug b/services/web/app/views/project/editor/review-panel.pug index 2035af4775..47e21eb848 100644 --- a/services/web/app/views/project/editor/review-panel.pug +++ b/services/web/app/views/project/editor/review-panel.pug @@ -6,6 +6,22 @@ ng-click="toggleReviewPanel();" ng-class="{ 'rp-track-changes-indicator-on-dark' : darkTheme }" ) !{translate("track_changes_is_on")} + a.rp-bulk-actions-btn( + href + ng-if="reviewPanel.selectedEntryIds.length > 1" + ng-click="showBulkAcceptDialog();" + ) + i.fa.fa-check + |  #{translate("accept_all")} + | ({{ reviewPanel.selectedEntryIds.length }}) + a.rp-bulk-actions-btn( + href + ng-if="reviewPanel.selectedEntryIds.length > 1" + ng-click="showBulkRejectDialog();" + ) + i.fa.fa-times + |  #{translate("reject_all")} + | ({{ reviewPanel.selectedEntryIds.length }}) a.rp-add-comment-btn( href ng-if="reviewPanel.entries[editor.open_doc_id]['add-comment'] != null" @@ -13,6 +29,7 @@ ) i.fa.fa-comment |  #{translate("add_comment")} + .review-panel-toolbar resolved-comments-dropdown( class="rp-flex-block" @@ -82,6 +99,12 @@ on-submit="submitNewComment(content);" on-cancel="cancelNewComment();" ) + div(ng-if="entry.type === 'bulk-actions'") + bulk-actions-entry( + on-bulk-accept="showBulkAcceptDialog();" + on-bulk-reject="showBulkRejectDialog();" + n-entries="reviewPanel.selectedEntryIds.length" + ) .rp-entry-list( ng-if="reviewPanel.subView === SubViews.OVERVIEW" @@ -353,6 +376,25 @@ script(type='text/ng-template', id='addCommentEntryTemplate') i.fa.fa-comment |  #{translate("comment")} +script(type='text/ng-template', id='bulkActionsEntryTemplate') + div(ng-if="nEntries > 1") + .rp-entry-callout.rp-entry-callout-bulk-actions + .rp-entry.rp-entry-bulk-actions + a.rp-bulk-actions-btn( + href + ng-click="bulkReject();" + ) + i.fa.fa-times + |  #{translate("reject_all")} + | ({{ nEntries }}) + a.rp-bulk-actions-btn( + href + ng-click="bulkAccept();" + ) + i.fa.fa-check + |  #{translate("accept_all")} + | ({{ nEntries }}) + script(type='text/ng-template', id='resolvedCommentsDropdownTemplate') .resolved-comments .resolved-comments-backdrop( @@ -433,3 +475,24 @@ script(type="text/ng-template", id="trackChangesUpgradeModalTemplate") ng-click="cancel()" ) span #{translate("close")} + +script(type="text/ng-template", id="bulkActionsModalTemplate") + .modal-header + button.close( + type="button" + data-dismiss="modal" + ng-click="cancel()" + ) × + h3 {{ isAccept ? '#{translate("accept_all")}' : '#{translate("reject_all")}' }} + .modal-body + p(ng-if="isAccept") #{translate("bulk_accept_confirm", { nChanges: "{{ nChanges }}"})} + p(ng-if="!isAccept") #{translate("bulk_reject_confirm", { nChanges: "{{ nChanges }}"})} + .modal-footer() + button.btn.btn-default( + ng-click="cancel()" + ) + span #{translate("cancel")} + button.btn.btn-primary( + ng-click="confirm()" + ) + span #{translate("ok")} diff --git a/services/web/public/coffee/ide/review-panel/ReviewPanelManager.coffee b/services/web/public/coffee/ide/review-panel/ReviewPanelManager.coffee index 9b622ffa05..420c9c6139 100644 --- a/services/web/public/coffee/ide/review-panel/ReviewPanelManager.coffee +++ b/services/web/public/coffee/ide/review-panel/ReviewPanelManager.coffee @@ -1,11 +1,13 @@ define [ "ide/review-panel/controllers/ReviewPanelController" "ide/review-panel/controllers/TrackChangesUpgradeModalController" + "ide/review-panel/controllers/BulkActionsModalController" "ide/review-panel/directives/reviewPanelSorted" "ide/review-panel/directives/reviewPanelToggle" "ide/review-panel/directives/changeEntry" "ide/review-panel/directives/commentEntry" "ide/review-panel/directives/addCommentEntry" + "ide/review-panel/directives/bulkActionsEntry" "ide/review-panel/directives/resolvedCommentEntry" "ide/review-panel/directives/resolvedCommentsDropdown" "ide/review-panel/directives/reviewPanelCollapseHeight" diff --git a/services/web/public/coffee/ide/review-panel/controllers/BulkActionsModalController.coffee b/services/web/public/coffee/ide/review-panel/controllers/BulkActionsModalController.coffee new file mode 100644 index 0000000000..575fcb9716 --- /dev/null +++ b/services/web/public/coffee/ide/review-panel/controllers/BulkActionsModalController.coffee @@ -0,0 +1,10 @@ +define [ + "base" +], (App) -> + App.controller "BulkActionsModalController", ($scope, $modalInstance, isAccept, nChanges) -> + $scope.isAccept = isAccept + $scope.nChanges = nChanges + $scope.cancel = () -> + $modalInstance.dismiss() + $scope.confirm = () -> + $modalInstance.close(isAccept) \ No newline at end of file diff --git a/services/web/public/coffee/ide/review-panel/controllers/ReviewPanelController.coffee b/services/web/public/coffee/ide/review-panel/controllers/ReviewPanelController.coffee index 46eb75aba0..28a8290e5e 100644 --- a/services/web/public/coffee/ide/review-panel/controllers/ReviewPanelController.coffee +++ b/services/web/public/coffee/ide/review-panel/controllers/ReviewPanelController.coffee @@ -26,6 +26,7 @@ define [ resolvedThreadIds: {} rendererData: {} loadingThreads: false + selectedEntryIds: [] window.addEventListener "beforeunload", () -> collapsedStates = {} @@ -176,7 +177,7 @@ define [ entries = $scope.reviewPanel.entries[$scope.editor.open_doc_id] or {} permEntries = {} for entry, entryData of entries - if entry != "add-comment" + if entry not in [ "add-comment", "bulk-actions" ] permEntries[entry] = entryData Object.keys(permEntries).length ), (nEntries) -> @@ -294,23 +295,35 @@ define [ $scope.$on "editor:focus:changed", (e, selection_offset_start, selection_offset_end, selection) -> doc_id = $scope.editor.open_doc_id entries = getDocEntries(doc_id) + $scope.reviewPanel.selectedEntryIds = [] delete entries["add-comment"] + delete entries["bulk-actions"] + if selection entries["add-comment"] = { type: "add-comment" offset: selection_offset_start length: selection_offset_end - selection_offset_start } + entries["bulk-actions"] = { + type: "bulk-actions" + offset: selection_offset_start + length: selection_offset_end - selection_offset_start + } for id, entry of entries if entry.type == "comment" and not $scope.reviewPanel.resolvedThreadIds[entry.thread_id] entry.focused = (entry.offset <= selection_offset_start <= entry.offset + entry.content.length) else if entry.type == "insert" + isEntryWithinSelection = entry.offset >= selection_offset_start and entry.offset + entry.content.length <= selection_offset_end entry.focused = (entry.offset <= selection_offset_start <= entry.offset + entry.content.length) + $scope.reviewPanel.selectedEntryIds.push id if isEntryWithinSelection else if entry.type == "delete" + isEntryWithinSelection = selection_offset_start <= entry.offset <= selection_offset_end entry.focused = (entry.offset == selection_offset_start) - else if entry.type == "add-comment" and selection + $scope.reviewPanel.selectedEntryIds.push id if isEntryWithinSelection + else if entry.type in [ "add-comment", "bulk-actions" ] and selection entry.focused = true $scope.$broadcast "review-panel:recalculate-screen-positions" @@ -325,6 +338,43 @@ define [ $scope.$broadcast "change:reject", entry_id event_tracking.sendMB "rp-change-rejected", { view: if $scope.ui.reviewPanelOpen then $scope.reviewPanel.subView else 'mini' } + bulkAccept = () -> + entry_ids = $scope.reviewPanel.selectedEntryIds.slice() + $http.post "/project/#{$scope.project_id}/doc/#{$scope.editor.open_doc_id}/changes/accept", { change_ids: entry_ids, _csrf: window.csrfToken} + $scope.$broadcast "change:bulk-accept", entry_ids + $scope.reviewPanel.selectedEntryIds = [] + event_tracking.sendMB "rp-bulk-accept", { + view: if $scope.ui.reviewPanelOpen then $scope.reviewPanel.subView else 'mini', + nEntries: $scope.reviewPanel.selectedEntryIds.length + } + + bulkReject = () -> + $scope.$broadcast "change:bulk-reject", $scope.reviewPanel.selectedEntryIds.slice() + $scope.reviewPanel.selectedEntryIds = [] + event_tracking.sendMB "rp-bulk-reject", { + view: if $scope.ui.reviewPanelOpen then $scope.reviewPanel.subView else 'mini', + nEntries: $scope.reviewPanel.selectedEntryIds.length + } + + $scope.showBulkAcceptDialog = () -> + showBulkActionsDialog true + + $scope.showBulkRejectDialog = () -> showBulkActionsDialog false + + showBulkActionsDialog = (isAccept) -> + $modal.open({ + templateUrl: "bulkActionsModalTemplate" + controller: "BulkActionsModalController" + resolve: + isAccept: () -> isAccept + nChanges: () -> $scope.reviewPanel.selectedEntryIds.length + scope: $scope.$new() + }).result.then (isAccept) -> + if isAccept + bulkAccept() + else + bulkReject() + $scope.addNewComment = () -> $scope.$broadcast "comment:start_adding" $scope.toggleReviewPanel() diff --git a/services/web/public/coffee/ide/review-panel/directives/bulkActionsEntry.coffee b/services/web/public/coffee/ide/review-panel/directives/bulkActionsEntry.coffee new file mode 100644 index 0000000000..4c206ab635 --- /dev/null +++ b/services/web/public/coffee/ide/review-panel/directives/bulkActionsEntry.coffee @@ -0,0 +1,15 @@ +define [ + "base" +], (App) -> + App.directive "bulkActionsEntry", () -> + restrict: "E" + templateUrl: "bulkActionsEntryTemplate" + scope: + onBulkAccept: "&" + onBulkReject: "&" + nEntries: "=" + link: (scope, element, attrs) -> + scope.bulkAccept = () -> + scope.onBulkAccept() + scope.bulkReject = () -> + scope.onBulkReject() \ No newline at end of file diff --git a/services/web/public/stylesheets/app/editor/review-panel.less b/services/web/public/stylesheets/app/editor/review-panel.less index 59f0e40566..018a402015 100644 --- a/services/web/public/stylesheets/app/editor/review-panel.less +++ b/services/web/public/stylesheets/app/editor/review-panel.less @@ -277,6 +277,9 @@ right: 5px; } } + &-bulk-actions { + right: auto; + } } .rp-state-overview & { border-radius: 0; @@ -335,6 +338,12 @@ border-left-color: @rp-yellow; } } + + &-bulk-actions { + background-color: transparent; + right: auto; + border-left-width: 0; + } } .rp-entry-body { display: flex; @@ -451,17 +460,35 @@ padding: 0 5px; } - .rp-add-comment-btn { + .rp-add-comment-btn, + .rp-bulk-actions-btn { .rp-button(); - display: block; + display: inline-block; padding: 5px 10px; border-radius: 3px; .rp-in-editor-widgets & { + display: block; white-space: nowrap; border-radius: 0; + + &:last-child { + border-bottom-left-radius: 3px; + } + } + } + + .rp-bulk-actions-btn { + border-radius: 0; + &:first-child { + border-top-left-radius: 3px; border-bottom-left-radius: 3px; } + &:last-child { + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; + margin-left: 1px; + } } .rp-new-comment { @@ -903,6 +930,9 @@ .rp-size-mini & { right: @review-off-width; } + .rp-size-expanded & { + display: none; + } } .rp-track-changes-indicator { display: block; @@ -931,10 +961,6 @@ background-color: rgba(240, 240, 240, 1); color: @rp-type-blue; } - - .rp-size-expanded & { - display: none; - } } // Helper class for elements which aren't treated as flex-items by IE10, e.g: