Emphasize the reply button when there are drafts

Bug: Issue 3844
Change-Id: I85cb4ad343f2a27598b8f84559cc8330e997e7a3
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
index f143fc2..a394b2b 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
@@ -21,9 +21,10 @@
 <link rel="import" href="../../shared/gr-ajax/gr-ajax.html">
 <link rel="import" href="../../shared/gr-button/gr-button.html">
 <link rel="import" href="../../shared/gr-change-star/gr-change-star.html">
-<link rel="import" href="../../shared/gr-overlay/gr-overlay.html">
 <link rel="import" href="../../shared/gr-date-formatter/gr-date-formatter.html">
 <link rel="import" href="../../shared/gr-linked-text/gr-linked-text.html">
+<link rel="import" href="../../shared/gr-overlay/gr-overlay.html">
+<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
 
 <link rel="import" href="../gr-change-actions/gr-change-actions.html">
 <link rel="import" href="../gr-change-metadata/gr-change-metadata.html">
@@ -79,7 +80,7 @@
       }
       .download,
       .patchSelectLabel {
-        margin-left: var(--default-horizontal-margin);
+        margin-left: 1em;
       }
       .header select {
         margin-left: .5em;
@@ -242,8 +243,12 @@
             <span class="changeStatus">[[_computeChangeStatus(_change, _patchNum)]]</span>
           </span>
           <span class="header-actions">
-            <gr-button class="reply" hidden$="[[!_loggedIn]]" hidden on-tap="_handleReplyTap">Reply</gr-button>
-            <gr-button link class="download" on-tap="_handleDownloadTap">Download</gr-button>
+            <gr-button hidden
+                class="reply"
+                primary$="[[_computeReplyButtonHighlighted(_diffDrafts)]]"
+                hidden$="[[!_loggedIn]]"
+                on-tap="_handleReplyTap">[[_replyButtonLabel]]</gr-button>
+            <gr-button class="download" on-tap="_handleDownloadTap">Download</gr-button>
             <span>
               <label class="patchSelectLabel" for="patchSetSelect">Patch set</label>
               <select id="patchSetSelect" on-change="_handlePatchChange">
@@ -290,6 +295,7 @@
           change-num="[[_changeNum]]"
           patch-num="[[_patchNum]]"
           comments="[[_comments]]"
+          drafts="[[_diffDrafts]]"
           selected-index="{{viewState.selectedFileIndex}}"></gr-file-list>
       <gr-messages-list id="messageList"
           change-num="[[_changeNum]]"
@@ -314,10 +320,12 @@
           patch-num="[[_patchNum]]"
           labels="[[_change.labels]]"
           permitted-labels="[[_change.permitted_labels]]"
+          diff-drafts="[[_diffDrafts]]"
           on-send="_handleReplySent"
           on-cancel="_handleReplyCancel"
           hidden$="[[!_loggedIn]]">Reply</gr-reply-dialog>
     </gr-overlay>
+    <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
   </template>
   <script src="gr-change-view.js"></script>
 </dom-module>
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
index f784f0a..9af6aa9 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
@@ -49,6 +49,7 @@
       },
       _commitInfo: Object,
       _changeNum: String,
+      _diffDrafts: Object,
       _patchNum: String,
       _allPatchSets: {
         type: Array,
@@ -66,6 +67,11 @@
         type: Function,
         value: function() { return this._handleBodyScroll.bind(this); },
       },
+      _replyButtonLabel: {
+        type: String,
+        value: 'Reply',
+        computed: '_computeReplyButtonLabel(_diffDrafts)',
+      },
     },
 
     behaviors: [
@@ -74,13 +80,14 @@
     ],
 
     ready: function() {
-      app.accountReady.then(function() {
-        this._loggedIn = app.loggedIn;
-      }.bind(this));
       this._headerEl = this.$$('.header');
     },
 
     attached: function() {
+      this._getLoggedIn().then(function(loggedIn) {
+        this._loggedIn = loggedIn;
+      }.bind(this));
+
       window.addEventListener('scroll', this._boundScrollHandler);
     },
 
@@ -145,9 +152,6 @@
     },
 
     _handleReplyOverlayOpen: function(e) {
-      this.$.replyDialog.reload().then(function() {
-        this.async(function() { this.$.replyOverlay.center() }, 1);
-      }.bind(this));
       this.$.replyDialog.focus();
     },
 
@@ -186,8 +190,8 @@
           }
         }.bind(this), 1);
 
-        app.accountReady.then(function() {
-          if (!this._loggedIn) { return; }
+        this._getLoggedIn().then(function(loggedIn) {
+          if (!loggedIn) { return; }
 
           if (this.viewState.showReplyDialog) {
             this.$.replyOverlay.open();
@@ -301,6 +305,23 @@
       return result;
     },
 
+    _computeReplyButtonHighlighted: function(drafts) {
+      return Object.keys(drafts || {}).length > 0;
+    },
+
+    _computeReplyButtonLabel: function(drafts) {
+      drafts = drafts || {};
+      var draftCount = Object.keys(drafts).reduce(function(count, file) {
+        return count + drafts[file].length;
+      }, 0);
+
+      var label = 'Reply';
+      if (draftCount > 0) {
+        label += ' (' + draftCount + ')';
+      }
+      return label;
+    },
+
     _handleKey: function(e) {
       if (this.shouldSupressKeyboardShortcut(e)) { return; }
 
@@ -322,9 +343,34 @@
       page.show(this.changePath(this._changeNum));
     },
 
+    _getDiffDrafts: function() {
+      return this.$.restAPI.getDiffDrafts(this._changeNum).then(
+          function(drafts) { return this._diffDrafts = drafts; }.bind(this));
+    },
+
+    _getLoggedIn: function() {
+      return this.$.restAPI.getLoggedIn();
+    },
+
+    _reloadDiffDrafts: function() {
+      this._diffDrafts = {};
+      this._getDiffDrafts().then(function() {
+        if (this.$.replyOverlay.opened) {
+          this.async(function() { this.$.replyOverlay.center(); }, 1);
+        }
+      }.bind(this));
+    },
+
     _reload: function() {
+      this._getLoggedIn().then(function(loggedIn) {
+        if (!loggedIn) { return; }
+
+        this._reloadDiffDrafts();
+      }.bind(this));
+
       var detailCompletes = this.$.detailXHR.generateRequest().completes;
       this.$.commentsXHR.generateRequest();
+
       var reloadPatchNumDependentResources = function() {
         return Promise.all([
           this.$.commitInfoXHR.generateRequest().completes,
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
index ef9fdb6..af14e06 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
@@ -79,6 +79,25 @@
       assert.isFalse(overlayEl.opened);
     });
 
+    test('reply button is highlighted when there are drafts', function() {
+      var replyButton = element.$$('gr-button.reply');
+      assert.ok(replyButton);
+      assert.isFalse(replyButton.hasAttribute('primary'));
+
+      element._diffDrafts = null;
+      assert.isFalse(replyButton.hasAttribute('primary'));
+
+      element._diffDrafts = {};
+      assert.isFalse(replyButton.hasAttribute('primary'));
+
+      element._diffDrafts = {
+        'file1.txt': [{}],
+        'file2.txt': [{}, {}],
+      };
+      assert.isTrue(replyButton.hasAttribute('primary'));
+      assert.equal(replyButton.textContent, 'Reply (3)');
+    });
+
     test('patch num change', function(done) {
       element._changeNum = '42';
       element._patchNum = 2;
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html
index e010468..7d69d7e 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html
@@ -115,9 +115,6 @@
     <gr-ajax id="filesXHR"
         url="[[_computeFilesURL(changeNum, patchNum)]]"
         on-response="_handleResponse"></gr-ajax>
-    <gr-ajax id="draftsXHR"
-        url="[[_computeDraftsURL(changeNum, patchNum)]]"
-        last-response="{{_drafts}}"></gr-ajax>
     <gr-ajax id="reviewedXHR"
         url="[[_computeReviewedURL(changeNum, patchNum)]]"
         last-response="{{_reviewed}}"></gr-ajax>
@@ -145,7 +142,7 @@
           [[_computeFileDisplayName(file.__path)]]
         </a>
         <div class="comments">
-          <span class="drafts">[[_computeDraftsString(_drafts, file.__path)]]</span>
+          <span class="drafts">[[_computeDraftsString(drafts, patchNum, file.__path)]]</span>
           [[_computeCommentsString(comments, patchNum, file.__path)]]
         </div>
         <div class$="[[_computeClass('stats', file.__path)]]">
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
index 3c66289..ead5d74 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
@@ -23,6 +23,7 @@
       patchNum: String,
       changeNum: String,
       comments: Object,
+      drafts: Object,
       files: Array,
       selectedIndex: {
         type: Number,
@@ -32,12 +33,10 @@
         type: Object,
         value: function() { return document.body; },
       },
-
       _loggedIn: {
         type: Boolean,
         value: false,
       },
-      _drafts: Object,
       _reviewed: {
         type: Array,
         value: function() { return []; },
@@ -62,7 +61,6 @@
         app.accountReady.then(function() {
           this._loggedIn = app.loggedIn;
           if (!app.loggedIn) { return; }
-          this.$.draftsXHR.generateRequest();
           this._reviewedRequestPromise =
               this.$.reviewedXHR.generateRequest().completes;
         }.bind(this)),
@@ -74,13 +72,22 @@
     },
 
     _computeCommentsString: function(comments, patchNum, path) {
+      return this._computeCountString(comments, patchNum, path, 'comment');
+    },
+
+    _computeDraftsString: function(drafts, patchNum, path) {
+      return this._computeCountString(drafts, patchNum, path, 'draft');
+    },
+
+    _computeCountString: function(comments, patchNum, path, noun) {
+      if (!comments) { return ''; }
+
       var patchComments = (comments[path] || []).filter(function(c) {
         return c.patch_set == patchNum;
       });
       var num = patchComments.length;
       if (num == 0) { return ''; }
-      if (num == 1) { return '1 comment'; }
-      if (num > 1) { return num + ' comments'; }
+      return num + ' ' + noun + (num > 1 ? 's' : '');
     },
 
     _computeReviewedURL: function(changeNum, patchNum) {
@@ -111,17 +118,6 @@
       }.bind(this));
     },
 
-    _computeDraftsURL: function(changeNum, patchNum) {
-      return this.changeBaseURL(changeNum, patchNum) + '/drafts';
-    },
-
-    _computeDraftsString: function(drafts, path) {
-      var num = (drafts[path] || []).length;
-      if (num == 0) { return ''; }
-      if (num == 1) { return '1 draft'; }
-      if (num > 1) { return num + ' drafts'; }
-    },
-
     _handleResponse: function(e, req) {
       var result = e.detail.response;
       var paths = Object.keys(result).sort();
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
index bc7b2a6..48e8cc7 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
@@ -248,24 +248,24 @@
         ],
       };
       assert.equal(
-          element._computeCommentsString(comments, '1', '/COMMIT_MSG'),
+          element._computeCountString(comments, '1', '/COMMIT_MSG', 'comment'),
           '2 comments');
       assert.equal(
-          element._computeCommentsString(comments, '1', 'myfile.txt'),
+          element._computeCountString(comments, '1', 'myfile.txt', 'comment'),
           '1 comment');
       assert.equal(
-          element._computeCommentsString(comments, '1',
-              'file_added_in_rev2.txt'),
+          element._computeCountString(comments, '1',
+              'file_added_in_rev2.txt', 'comment'),
           '');
       assert.equal(
-          element._computeCommentsString(comments, '2', '/COMMIT_MSG'),
+          element._computeCountString(comments, '2', '/COMMIT_MSG', 'comment'),
           '1 comment');
       assert.equal(
-          element._computeCommentsString(comments, '2', 'myfile.txt'),
+          element._computeCountString(comments, '2', 'myfile.txt', 'comment'),
           '2 comments');
       assert.equal(
-          element._computeCommentsString(comments, '2',
-              'file_added_in_rev2.txt'),
+          element._computeCountString(comments, '2',
+              'file_added_in_rev2.txt', 'comment'),
           '');
     });
 
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html
index a2c7e19..1a2fbab 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html
@@ -18,7 +18,6 @@
 <link rel="import" href="../../../bower_components/iron-autogrow-textarea/iron-autogrow-textarea.html">
 <link rel="import" href="../../../bower_components/iron-selector/iron-selector.html">
 <link rel="import" href="../../../behaviors/rest-client-behavior.html">
-<link rel="import" href="../../shared/gr-ajax/gr-ajax.html">
 <link rel="import" href="../../shared/gr-button/gr-button.html">
 <link rel="import" href="../../shared/gr-request/gr-request.html">
 
@@ -101,9 +100,6 @@
     }
   </style>
   <template>
-    <gr-ajax id="draftsXHR"
-        url="[[_computeDraftsURL(changeNum)]]"
-        last-response="{{_drafts}}"></gr-ajax>
     <div class="container">
       <section class="textareaContainer">
         <iron-autogrow-textarea
@@ -131,10 +127,10 @@
           </div>
         </template>
       </section>
-      <section class="draftsContainer" hidden$="[[_computeHideDraftList(_drafts)]]">
-        <h3>[[_computeDraftsTitle(_drafts)]]</h3>
+      <section class="draftsContainer" hidden$="[[_computeHideDraftList(diffDrafts)]]">
+        <h3>[[_computeDraftsTitle(diffDrafts)]]</h3>
         <gr-comment-list
-            comments="[[_drafts]]"
+            comments="[[diffDrafts]]"
             change-num="[[changeNum]]"
             patch-num="[[patchNum]]"></gr-comment-list>
       </section>
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
index 7b4b17a..6ec4d87 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
@@ -41,11 +41,11 @@
         type: String,
         value: '',
       },
+      diffDrafts: Object,
       labels: Object,
       permittedLabels: Object,
 
       _account: Object,
-      _drafts: Object,
       _xhrPromise: Object,  // Used for testing.
     },
 
@@ -59,20 +59,12 @@
       }.bind(this));
     },
 
-    reload: function() {
-      return this.$.draftsXHR.generateRequest().completes;
-    },
-
     focus: function() {
       this.async(function() {
         this.$.textarea.textarea.focus();
       }.bind(this));
     },
 
-    _computeDraftsURL: function(changeNum) {
-      return '/changes/' + changeNum + '/drafts';
-    },
-
     _computeHideDraftList: function(drafts) {
       return Object.keys(drafts || {}).length == 0;
     },
@@ -125,7 +117,6 @@
 
     _cancelTapHandler: function(e) {
       e.preventDefault();
-      this._drafts = null;
       this.fire('cancel', null, {bubbles: false});
     },
 
@@ -149,7 +140,6 @@
         this.fire('send', null, {bubbles: false});
         this.draft = '';
         this.disabled = false;
-        this._drafts = null;
       }.bind(this)).catch(function(err) {
         alert('Oops. Something went wrong. Check the console and bug the ' +
             'PolyGerrit team for assistance.');
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
index 27cd989..72570d9 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
@@ -98,6 +98,12 @@
       return this._fetchSharedCacheURL('/accounts/self/detail');
     },
 
+    getLoggedIn: function() {
+      return this.getAccount().then(function(account) {
+        return account != null;
+      });
+    },
+
     getPreferences: function() {
       return this._fetchSharedCacheURL('/accounts/self/preferences');
     },
@@ -143,38 +149,46 @@
           encodeURIComponent(path) + '/diff';
     },
 
-    getDiffComments: function(changeNum, basePatchNum, patchNum, path) {
-      return this._getDiffComments(changeNum, basePatchNum, patchNum, path,
-          '/comments');
+    getDiffComments: function(changeNum, opt_basePatchNum, opt_patchNum,
+        opt_path) {
+      return this._getDiffComments(changeNum, '/comments', opt_basePatchNum,
+          opt_patchNum, opt_path);
     },
 
-    getDiffDrafts: function(changeNum, basePatchNum, patchNum, path) {
-      return this._getDiffComments(changeNum, basePatchNum, patchNum, path,
-          '/drafts');
+    getDiffDrafts: function(changeNum, opt_basePatchNum, opt_patchNum,
+        opt_path) {
+      return this._getDiffComments(changeNum, '/drafts', opt_basePatchNum,
+          opt_patchNum, opt_path);
     },
 
-    _getDiffComments: function(changeNum, basePatchNum, patchNum, path,
-        endpoint) {
+    _getDiffComments: function(changeNum, endpoint, opt_basePatchNum,
+        opt_patchNum, opt_path) {
+      if (!opt_basePatchNum && !opt_patchNum && !opt_path) {
+        return this.fetchJSON(
+            this._getDiffCommentsFetchURL(changeNum, null, '/drafts'));
+      }
+
       function onlyParent(c) { return c.side == PARENT_PATCH_NUM; }
       function withoutParent(c) { return c.side != PARENT_PATCH_NUM; }
 
       var promises = [];
       var comments;
       var baseComments;
-      var url = this._getDiffCommentsFetchURL(changeNum, patchNum, endpoint);
+      var url =
+          this._getDiffCommentsFetchURL(changeNum, endpoint, opt_patchNum);
       promises.push(this.fetchJSON(url).then(function(response) {
-        comments = response[path] || [];
-        if (basePatchNum == PARENT_PATCH_NUM) {
+        comments = response[opt_path] || [];
+        if (opt_basePatchNum == PARENT_PATCH_NUM) {
           baseComments = comments.filter(onlyParent);
         }
         comments = comments.filter(withoutParent);
       }.bind(this)));
 
-      if (basePatchNum != PARENT_PATCH_NUM) {
-        var baseURL = this._getDiffCommentsFetchURL(changeNum, basePatchNum,
-            endpoint);
+      if (opt_basePatchNum != PARENT_PATCH_NUM) {
+        var baseURL = this._getDiffCommentsFetchURL(changeNum, endpoint,
+            opt_basePatchNum);
         promises.push(this.fetchJSON(baseURL).then(function(response) {
-          baseComments = (response[path] || []).filter(withoutParent);
+          baseComments = (response[opt_path] || []).filter(withoutParent);
         }));
       }
 
@@ -186,14 +200,14 @@
       });
     },
 
-    _getDiffCommentsFetchURL: function(changeNum, patchNum, endpoint) {
-      return this._changeBaseURL(changeNum, patchNum) + endpoint;
+    _getDiffCommentsFetchURL: function(changeNum, endpoint, opt_patchNum) {
+      return this._changeBaseURL(changeNum, opt_patchNum) + endpoint;
     },
 
-    _changeBaseURL: function(changeNum, patchNum) {
+    _changeBaseURL: function(changeNum, opt_patchNum) {
       var v = '/changes/' + changeNum;
-      if (patchNum) {
-        v += '/revisions/' + patchNum;
+      if (opt_patchNum) {
+        v += '/revisions/' + opt_patchNum;
       }
       return v;
     },
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
index 3902707f..44e49cb 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
@@ -129,7 +129,7 @@
           ],
         });
       });
-      element._getDiffComments('42', 'PARENT', 1, 'sieve.go', '').then(
+      element._getDiffComments('42', '', 'PARENT', 1, 'sieve.go').then(
         function(obj) {
           assert.equal(obj.baseComments.length, 1);
           assert.deepEqual(obj.baseComments[0], {
@@ -178,7 +178,7 @@
           });
         }
       });
-      element._getDiffComments('42', 1, 2, 'sieve.go', '').then(
+      element._getDiffComments('42', '', 1, 2, 'sieve.go').then(
         function(obj) {
           assert.equal(obj.baseComments.length, 1);
           assert.deepEqual(obj.baseComments[0], {