Show reviewers in change log

Recognize reviewer_updates from change/detail REST API response and
display inline with the messages and comments.

Change-Id: Iaa8740aeb5a194cc548cce777c9cef79cad652a2
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 8c22e7c..5513f2e 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
@@ -297,6 +297,7 @@
       <gr-messages-list id="messageList"
           change-num="[[_changeNum]]"
           messages="[[_change.messages]]"
+          reviewer-updates="[[_change.reviewer_updates]]"
           comments="[[_comments]]"
           project-config="[[_projectConfig]]"
           show-reply-buttons="[[_loggedIn]]"
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 700f162..76874c0 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
@@ -552,6 +552,9 @@
               function(change) {
                 // Issue 4190: Coalesce missing topics to null.
                 if (!change.topic) { change.topic = null; }
+                if (!change.reviewer_updates) {
+                  change.reviewer_updates = null;
+                }
                 this._change = change;
               }.bind(this));
     },
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message.html b/polygerrit-ui/app/elements/change/gr-message/gr-message.html
index 7c287db..66254d0 100644
--- a/polygerrit-ui/app/elements/change/gr-message/gr-message.html
+++ b/polygerrit-ui/app/elements/change/gr-message/gr-message.html
@@ -50,15 +50,15 @@
       }
       .showAvatar.collapsed .contentContainer {
         margin-left: calc(var(--default-horizontal-margin) + 1.75em);
-        padding: 10px 75px 10px 0;
+        padding: .75em 2em .75em 0;
       }
       .hideAvatar.collapsed .contentContainer,
       .hideAvatar.expanded .contentContainer {
         margin-left: 0;
-        padding: 10px 75px 10px 0;
+        padding: .75em 2em .75em 0;
       }
       .collapsed gr-avatar {
-        top: 8px;
+        top: .5em;
         height: 1.75em;
         width: 1.75em;
       }
@@ -75,7 +75,8 @@
       }
       .collapsed .name,
       .collapsed .content,
-      .collapsed .message {
+      .collapsed .message,
+      gr-account-chip {
         display: inline;
       }
       .collapsed gr-comment-list,
@@ -99,23 +100,34 @@
       }
     </style>
     <div class$="[[_computeClass(expanded, showAvatar)]]">
-      <gr-avatar account="[[message.author]]" image-size="100"></gr-avatar>
+      <gr-avatar account="[[author]]" image-size="100"></gr-avatar>
       <div class="contentContainer">
-        <div class="name" on-tap="_handleNameTap">[[message.author.name]]</div>
-        <div class="content">
-          <gr-linked-text class="message"
-              pre="[[expanded]]"
-              content="[[message.message]]"
-              disabled="[[!expanded]]"
-              config="[[projectConfig.commentlinks]]"></gr-linked-text>
-          <gr-comment-list
-              comments="[[comments]]"
-              change-num="[[changeNum]]"
-              patch-num="[[message._revision_number]]"></gr-comment-list>
-        </div>
-        <a class="date" href$="[[_computeMessageHash(message)]]" on-tap="_handleLinkTap">
-          <gr-date-formatter date-str="[[message.date]]"></gr-date-formatter>
-        </a>
+        <div class="name" on-tap="_handleNameTap">[[author.name]]</div>
+        <template is="dom-if" if="[[message.message]]">
+          <div class="content">
+            <gr-linked-text
+                class="message"
+                pre="[[expanded]]"
+                content="[[message.message]]"
+                disabled="[[!expanded]]"
+                config="[[projectConfig.commentlinks]]"></gr-linked-text>
+            <gr-comment-list
+                comments="[[comments]]"
+                change-num="[[changeNum]]"
+                patch-num="[[message._revision_number]]"></gr-comment-list>
+          </div>
+          <a class="date" href$="[[_computeMessageHash(message)]]" on-tap="_handleLinkTap">
+            <gr-date-formatter date-str="[[message.date]]"></gr-date-formatter>
+          </a>
+        </template>
+        <template is="dom-if" if="[[message.reviewer]]">
+          set reviewer status for
+          <gr-account-chip account="[[message.reviewer]]">
+          </gr-account-chip>
+          to [[message.state]].
+          <gr-date-formatter class="date" date-str="[[message.updated]]">
+          </gr-date-formatter>
+        </template>
       </div>
       <div class="replyContainer" hidden$="[[!showReplyButton]]" hidden>
         <gr-button small on-tap="_handleReplyTap">Reply</gr-button>
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message.js b/polygerrit-ui/app/elements/change/gr-message/gr-message.js
index 26b9fb9..c92ad07 100644
--- a/polygerrit-ui/app/elements/change/gr-message/gr-message.js
+++ b/polygerrit-ui/app/elements/change/gr-message/gr-message.js
@@ -36,10 +36,15 @@
     properties: {
       changeNum: Number,
       message: Object,
+      author: {
+        type: Object,
+        computed: '_computeAuthor(message)',
+      },
       comments: {
         type: Object,
         observer: '_commentsChanged',
       },
+      config: Object,
       expanded: {
         type: Boolean,
         value: true,
@@ -47,22 +52,33 @@
       },
       showAvatar: {
         type: Boolean,
-        value: false,
+        computed: '_computeShowAvatar(author, config)',
       },
       showReplyButton: {
         type: Boolean,
-        value: false,
+        computed: '_computeShowReplyButton(message)',
       },
       projectConfig: Object,
     },
 
     ready: function() {
-      this.$.restAPI.getConfig().then(function(cfg) {
-        this.showAvatar = !!(cfg && cfg.plugin && cfg.plugin.has_avatars) &&
-            this.message && this.message.author;
+      this.$.restAPI.getConfig().then(function(config) {
+        this.config = config;
       }.bind(this));
     },
 
+    _computeAuthor: function(message) {
+      return message.author || message.updated_by;
+    },
+
+    _computeShowAvatar: function(author, config) {
+      return !!(author && config && config.plugin && config.plugin.has_avatars);
+    },
+
+    _computeShowReplyButton: function(message) {
+      return !!message.message;
+    },
+
     _commentsChanged: function(value) {
       this.expanded = Object.keys(value || {}).length > 0;
     },
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message_test.html b/polygerrit-ui/app/elements/change/gr-message/gr-message_test.html
index 7302cf2..c90f58a 100644
--- a/polygerrit-ui/app/elements/change/gr-message/gr-message_test.html
+++ b/polygerrit-ui/app/elements/change/gr-message/gr-message_test.html
@@ -56,8 +56,34 @@
         assert.deepEqual(e.detail.message, element.message);
         done();
       });
+      flushAsynchronousOperations();
       MockInteractions.tap(element.$$('.replyContainer gr-button'));
     });
 
+    test('reviewer update', function() {
+      var updatedBy = {
+        _account_id: 1115495,
+        name: 'Andrew Bonventre',
+        email: 'andybons@chromium.org',
+      };
+      var reviewer = {
+        _account_id: 123456,
+        name: 'Foo Bar',
+        email: 'barbar@chromium.org',
+      };
+      element.message = {
+        updated_by: updatedBy,
+        reviewer: reviewer,
+        state: 'CC',
+        updated: '2016-01-12 20:24:49.448000000',
+      };
+      flushAsynchronousOperations();
+      var content = element.$$('.contentContainer');
+      assert.isOk(content);
+      assert.strictEqual(
+          content.querySelector('gr-account-chip').account, reviewer);
+      assert.equal(0, content.textContent.trim().indexOf(updatedBy.name));
+    });
+
   });
 </script>
diff --git a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.html b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.html
index 8a66d03..b6b6d6d 100644
--- a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.html
+++ b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.html
@@ -47,7 +47,10 @@
         [[_computeExpandCollapseMessage(_expanded)]]
       </gr-button>
     </div>
-    <template is="dom-repeat" items="[[messages]]" as="message">
+    <template
+        is="dom-repeat"
+        items="[[_computeItems(messages, reviewerUpdates)]]"
+        as="message">
       <gr-message
           change-num="[[changeNum]]"
           message="[[message]]"
diff --git a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.js b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.js
index 1b9ce14..4462fdf 100644
--- a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.js
+++ b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.js
@@ -23,6 +23,10 @@
         type: Array,
         value: function() { return []; },
       },
+      reviewerUpdates: {
+        type: Array,
+        value: function() { return []; },
+      },
       comments: Object,
       projectConfig: Object,
       topMargin: Number,
@@ -52,6 +56,34 @@
       this._highlightEl(el);
     },
 
+    _computeItems: function(messages, reviewerUpdates) {
+      messages = messages || [];
+      reviewerUpdates = reviewerUpdates || [];
+      var result = [];
+      var mDate;
+      var rDate;
+      while (reviewerUpdates.length || messages.length) {
+        if (!reviewerUpdates.length) {
+          result = result.concat(messages);
+          break;
+        } else if (!messages.length) {
+          result = result.concat(reviewerUpdates);
+          break;
+        } else {
+          mDate = mDate || util.parseDate(messages[0].date);
+          rDate = rDate || util.parseDate(reviewerUpdates[0].updated);
+          if (rDate < mDate) {
+            result.push(reviewerUpdates.shift());
+            rDate = null;
+          } else {
+            result.push(messages.shift());
+            mDate = null;
+          }
+        }
+      }
+      return result;
+    },
+
     _highlightEl: function(el) {
       var highlightedEls =
           Polymer.dom(this.root).querySelectorAll('.highlighted');
@@ -84,6 +116,9 @@
     },
 
     _computeCommentsForMessage: function(comments, message, index) {
+      if (!message.message) {
+        return [];
+      }
       comments = comments || {};
       var messages = this.messages || [];
       var msgComments = {};
diff --git a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.html b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.html
index 9ef9d02..3cda480 100644
--- a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.html
@@ -39,37 +39,37 @@
       element = fixture('basic');
       element.messages = [
         {
-          'id': '47c43261_55aa2c41',
-          'author': {
-            '_account_id': 1115495,
-            'name': 'Andrew Bonventre',
-            'email': 'andybons@chromium.org',
+          id: '47c43261_55aa2c41',
+          author: {
+            _account_id: 1115495,
+            name: 'Andrew Bonventre',
+            email: 'andybons@chromium.org',
           },
-          'date': '2016-01-12 20:24:49.448000000',
-          'message': 'Uploaded patch set 1.',
-          '_revision_number': 1
+          date: '2016-01-12 20:24:49.448000000',
+          message: 'Uploaded patch set 1.',
+          _revision_number: 1
         },
         {
-          'id': '47c43261_9593e420',
-          'author': {
-            '_account_id': 1115495,
-            'name': 'Andrew Bonventre',
-            'email': 'andybons@chromium.org',
+          id: '47c43261_9593e420',
+          author: {
+            _account_id: 1115495,
+            name: 'Andrew Bonventre',
+            email: 'andybons@chromium.org',
           },
-          'date': '2016-01-12 20:28:33.038000000',
-          'message': 'Patch Set 1:\n\n(1 comment)',
-          '_revision_number': 1
+          date: '2016-01-12 20:28:33.038000000',
+          message: 'Patch Set 1:\n\n(1 comment)',
+          _revision_number: 1
         },
         {
-          'id': '87b2aaf4_f73260c5',
-          'author': {
-            '_account_id': 1143760,
-            'name': 'Mark Mentovai',
-            'email': 'mark@chromium.org',
+          id: '87b2aaf4_f73260c5',
+          author: {
+            _account_id: 1143760,
+            name: 'Mark Mentovai',
+            email: 'mark@chromium.org',
           },
-          'date': '2016-01-12 21:17:07.554000000',
-          'message': 'Patch Set 1:\n\n(3 comments)',
-          '_revision_number': 1
+          date: '2016-01-12 21:17:07.554000000',
+          message: 'Patch Set 1:\n\n(3 comments)',
+          _revision_number: 1
         }
       ];
       flushAsynchronousOperations();
@@ -127,6 +127,5 @@
       scrollToStub.restore();
       highlightStub.restore();
     });
-
   });
 </script>