Merge "Give change metadata chips a disabled state"
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html
index 388a3f7..978fab2 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html
@@ -102,6 +102,11 @@
       section.topic {
         @apply(--change-metadata-topic);
       }
+      gr-account-chip([disabled]),
+      gr-linked-chip([disabled]) {
+        opacity: 0;
+        pointer-events: none;
+      }
       #externalStyle {
         display: block;
       }
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js
index 7ea7201..88b2444 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js
@@ -249,11 +249,13 @@
     _onDeleteVote(e) {
       e.preventDefault();
       const target = Polymer.dom(e).rootTarget;
+      target.disabled = true;
       const labelName = target.labelName;
       const accountID = parseInt(target.getAttribute('data-account-id'), 10);
       this._xhrPromise =
           this.$.restAPI.deleteVote(this.change._number, accountID, labelName)
           .then(response => {
+            target.disabled = false;
             if (!response.ok) { return response; }
             const label = this.change.labels[labelName];
             const labels = label.all || [];
@@ -271,6 +273,9 @@
                 break;
               }
             }
+          }).catch(err => {
+            target.disabled = false;
+            return;
           });
     },
 
@@ -323,20 +328,31 @@
       return Gerrit.Nav.getUrlForHashtag(hashtag);
     },
 
-    _handleTopicRemoved() {
+    _handleTopicRemoved(e) {
+      const target = Polymer.dom(e).rootTarget;
+      target.disabled = true;
       this.$.restAPI.setChangeTopic(this.change._number, null).then(() => {
+        target.disabled = false;
         this.set(['change', 'topic'], '');
         this.dispatchEvent(
             new CustomEvent('topic-changed', {bubbles: true}));
+      }).catch(err => {
+        target.disabled = false;
+        return;
       });
     },
 
     _handleHashtagRemoved(e) {
       e.preventDefault();
       const target = Polymer.dom(e).rootTarget.text;
+      target.disabled = true;
       this.$.restAPI.setChangeHashtag(this.change._number, {remove: [target]})
           .then(newHashtag => {
+            target.disabled = false;
             this.set(['change', 'hashtags'], newHashtag);
+          }).catch(err => {
+            target.disabled = false;
+            return;
           });
     },
 
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html
index 636f662..2c3330f 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html
@@ -438,16 +438,6 @@
         const deleteStub = sandbox.stub(element.$.restAPI, 'deleteVote')
             .returns(Promise.resolve({ok: true}));
 
-        const spliceStub = sandbox.stub(element, 'splice', (path, index,
-            length) => {
-          assert.deepEqual(path, ['change.labels', 'test', 'all']);
-          assert.equal(index, 0);
-          assert.equal(length, 1);
-          assert.notOk(element.change.labels.test.recommended);
-          assert.isTrue(deleteStub.calledWithExactly(42, 1, 'test'));
-          spliceStub.restore();
-          done();
-        });
         element.change.removable_reviewers = [
           {
             _account_id: 1,
@@ -457,8 +447,23 @@
         element.change.labels.test.recommended = {_account_id: 1};
         element.mutable = true;
         flushAsynchronousOperations();
-        const button = element.$$('gr-account-chip').$$('gr-button');
+        const chip = element.$$('gr-account-chip');
+        const button = chip.$$('gr-button');
+
+        const spliceStub = sandbox.stub(element, 'splice', (path, index,
+            length) => {
+          assert.isFalse(chip.disabled);
+          assert.deepEqual(path, ['change.labels', 'test', 'all']);
+          assert.equal(index, 0);
+          assert.equal(length, 1);
+          assert.notOk(element.change.labels.test.recommended);
+          assert.isTrue(deleteStub.calledWithExactly(42, 1, 'test'));
+          spliceStub.restore();
+          done();
+        });
+
         MockInteractions.tap(button);
+        assert.isTrue(chip.disabled);
       });
 
       test('changing topic', () => {
@@ -480,14 +485,17 @@
       test('topic removal', () => {
         sandbox.stub(element.$.restAPI, 'setChangeTopic').returns(
             Promise.resolve());
-        const remove = element.$$('gr-linked-chip').$.remove;
+        const chip = element.$$('gr-linked-chip');
+        const remove = chip.$.remove;
         const topicChangedSpy = sandbox.spy();
         element.addEventListener('topic-changed', topicChangedSpy);
         MockInteractions.tap(remove);
+        assert.isTrue(chip.disabled);
         assert.isTrue(element.$.restAPI.setChangeTopic.calledWith(
             42, null));
         return element.$.restAPI.setChangeTopic.lastCall.returnValue
             .then(() => {
+              assert.isFalse(chip.disabled);
               assert.equal(element.change.topic, '');
               assert.isTrue(topicChangedSpy.called);
             });
diff --git a/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.html b/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.html
index a517e6a..90594de 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.html
+++ b/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.html
@@ -69,6 +69,10 @@
       gr-button.transparentBackground {
         background-color: transparent;
       }
+      :host([disabled]) {
+        opacity: .6;
+        pointer-events: none;
+      }
     </style>
     <div class$="container [[_getBackgroundClass(transparentBackground)]]">
       <gr-account-link account="[[account]]"></gr-account-link>
diff --git a/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.js b/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.js
index c4650f3e..e029ab0 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.js
+++ b/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.js
@@ -33,6 +33,11 @@
 
     properties: {
       account: Object,
+      disabled: {
+        type: Boolean,
+        value: false,
+        reflectToAttribute: true,
+      },
       removable: {
         type: Boolean,
         value: false,
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip.html b/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip.html
index c4f7ee0..ef5dab8 100644
--- a/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip.html
+++ b/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip.html
@@ -55,6 +55,10 @@
       gr-button.transparentBackground {
         background-color: transparent;
       }
+      :host([disabled]) {
+        opacity: .6;
+        pointer-events: none;
+      }
     </style>
     <div class$="container [[_getBackgroundClass(transparentBackground)]]">
       <a href$="[[href]]">[[text]]</a>
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip.js b/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip.js
index bfba1c4..dca1417 100644
--- a/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip.js
+++ b/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip.js
@@ -19,6 +19,11 @@
 
     properties: {
       href: String,
+      disabled: {
+        type: Boolean,
+        value: false,
+        reflectToAttribute: true,
+      },
       removable: {
         type: Boolean,
         value: false,