Show blame in diff

With this change a blame column is added to the left side of diff
tables. The column is empty and hidden until blame is loaded. A button
is added to the change view to trigger a load of the blame for that
diff, as well as a unload it if already loaded. In this stage, the blame
information is non-interactive and only displays the SHA, date and
commit author.

Feature: Issue 6075
Change-Id: Ifcb951265d0e6339094e6b7c9574ec9c69e60b51
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html
index f67abcc..008852e 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html
@@ -159,6 +159,12 @@
       .editLoaded .hideOnEdit {
         display: none;
       }
+      .blameLoader {
+        display: none;
+      }
+      .blameLoader.show {
+        display: inline;
+      }
       @media screen and (max-width: 50em) {
         header {
           padding: .5em var(--default-horizontal-margin);
@@ -312,6 +318,13 @@
                   on-tap="_handlePrefsTap">Preferences</gr-button>
             </span>
           </span>
+          <span class$="blameLoader [[_computeBlameLoaderClass(_isImageDiff, _isBlameSupported)]]">
+            <span class="separator">/</span>
+            <gr-button
+                link
+                disabled="[[_isBlameLoading]]"
+                on-tap="_toggleBlame">[[_computeBlameToggleLabel(_isBlameLoaded, _isBlameLoading)]]</gr-button>
+          </span>
         </div>
       </div>
       <div class="fileNav mobile">
@@ -340,6 +353,7 @@
         project-config="[[_projectConfig]]"
         project-name="[[_change.project]]"
         view-mode="[[_diffMode]]"
+        is-blame-loaded="{{_isBlameLoaded}}"
         on-line-selected="_onLineSelected">
     </gr-diff>
     <gr-diff-preferences
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
index 3bb373f..fbd845a 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
@@ -18,6 +18,8 @@
   const MERGE_LIST_PATH = '/MERGE_LIST';
 
   const ERR_REVIEW_STATUS = 'Couldn’t change file review status.';
+  const MSG_LOADING_BLAME = 'Loading blame...';
+  const MSG_LOADED_BLAME = 'Blame loaded';
 
   const PARENT = 'PARENT';
 
@@ -125,6 +127,16 @@
         type: Boolean,
         computed: '_computeEditLoaded(_patchRange.*)',
       },
+
+      _isBlameSupported: {
+        type: Boolean,
+        value: false,
+      },
+      _isBlameLoaded: Boolean,
+      _isBlameLoading: {
+        type: Boolean,
+        value: false,
+      },
     },
 
     behaviors: [
@@ -160,6 +172,10 @@
         this._loggedIn = loggedIn;
       });
 
+      this.$.restAPI.getConfig().then(config => {
+        this._isBlameSupported = config.change.allow_blame;
+      });
+
       this.$.cursor.push('diffs', this.$.diff);
     },
 
@@ -805,5 +821,36 @@
     _computeContainerClass(editLoaded) {
       return editLoaded ? 'editLoaded' : '';
     },
+
+    _computeBlameToggleLabel(loaded, loading) {
+      if (loaded) { return 'Hide blame'; }
+      return 'Show blame';
+    },
+
+    /**
+     * Load and display blame information if it has not already been loaded.
+     * Otherwise hide it.
+     */
+    _toggleBlame() {
+      if (this._isBlameLoaded) {
+        this.$.diff.clearBlame();
+        return;
+      }
+
+      this._isBlameLoading = true;
+      this.fire('show-alert', {message: MSG_LOADING_BLAME});
+      this.$.diff.loadBlame()
+          .then(() => {
+            this._isBlameLoading = false;
+            this.fire('show-alert', {message: MSG_LOADED_BLAME});
+          })
+          .catch(() => {
+            this._isBlameLoading = false;
+          });
+    },
+
+    _computeBlameLoaderClass(isImageDiff, supported) {
+      return !isImageDiff && supported ? 'show' : '';
+    },
   });
 })();