Prompt to confirm to add large group as reviewer

Depending on server configuration, a group with a certain number of
members must be marked as explicitly confirmed by the user before it
may be added as a reviewer. This change introduces an overlay on top
of the reply dialog to prompt the user for this confirmation in
advance of submission.

Change-Id: I0a61819e785c9a80aaa12ebae347907e41b24aac
diff --git a/polygerrit-ui/app/elements/change/gr-account-list/gr-account-list.js b/polygerrit-ui/app/elements/change/gr-account-list/gr-account-list.js
index 828169d..3b792e2 100644
--- a/polygerrit-ui/app/elements/change/gr-account-list/gr-account-list.js
+++ b/polygerrit-ui/app/elements/change/gr-account-list/gr-account-list.js
@@ -24,6 +24,11 @@
       },
       change: Object,
       placeholder: String,
+      pendingConfirmation: {
+        type: Object,
+        value: null,
+        notify: true,
+      },
       readonly: Boolean,
 
       filter: {
@@ -52,10 +57,22 @@
         var account = Object.assign({}, reviewer.account, {_pendingAdd: true});
         this.push('accounts', account);
       } else if (reviewer.group) {
+        if (reviewer.confirm) {
+          this.pendingConfirmation = reviewer;
+          return;
+        }
         var group = Object.assign({}, reviewer.group,
             {_pendingAdd: true, _group: true});
         this.push('accounts', group);
       }
+      this.pendingConfirmation = null;
+    },
+
+    confirmGroup: function(group) {
+      group = Object.assign(
+          {}, group, {confirmed: true, _pendingAdd: true, _group: true});
+      this.push('accounts', group);
+      this.pendingConfirmation = null;
     },
 
     _computeChipClass: function(account) {
diff --git a/polygerrit-ui/app/elements/change/gr-account-list/gr-account-list_test.html b/polygerrit-ui/app/elements/change/gr-account-list/gr-account-list_test.html
index 79d5e59..bb55d08 100644
--- a/polygerrit-ui/app/elements/change/gr-account-list/gr-account-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-account-list/gr-account-list_test.html
@@ -199,5 +199,38 @@
         },
       ]);
     });
+
+    test('large group confirmations', function() {
+      assert.isNull(element.pendingConfirmation);
+      assert.deepEqual(element.additions(), []);
+
+      var group = makeGroup();
+      var reviewer = {
+        group: group,
+        count: 10,
+        confirm: true,
+      };
+      element._handleAdd({
+        detail: {
+          value: reviewer,
+        },
+      });
+
+      assert.deepEqual(element.pendingConfirmation, reviewer);
+      assert.deepEqual(element.additions(), []);
+
+      element.confirmGroup(group);
+      assert.isNull(element.pendingConfirmation);
+      assert.deepEqual(element.additions(), [
+        {
+          group: {
+            id: group.id,
+            _group: true,
+            _pendingAdd: true,
+            confirmed: true,
+          },
+        },
+      ]);
+    });
   });
 </script>
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 89cb4a2..e923d48 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
@@ -20,6 +20,7 @@
 <link rel="import" href="../../../behaviors/rest-client-behavior.html">
 <link rel="import" href="../../shared/gr-button/gr-button.html">
 <link rel="import" href="../../shared/gr-js-api-interface/gr-js-api-interface.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-account-list/gr-account-list.html">
 
@@ -64,6 +65,19 @@
       gr-account-list {
         flex: 1;
       }
+      #reviewerConfirmationOverlay {
+        padding: 1em;
+        text-align: center;
+      }
+      .reviewerConfirmationButtons {
+        margin-top: 1em;
+      }
+      .groupName {
+        font-weight: bold;
+      }
+      .groupSize {
+        font-style: italic;
+      }
       .textareaContainer {
         position: relative;
         display: flex;
@@ -135,9 +149,32 @@
               id="reviewers"
               accounts="[[_reviewers]]"
               change="[[change]]"
+              pending-confirmation="{{_reviewerPendingConfirmation}}"
               placeholder="Add reviewer...">
           </gr-account-list>
         </div>
+        <gr-overlay
+            id="reviewerConfirmationOverlay"
+            on-iron-overlay-canceled="_cancelPendingReviewer"
+            with-backdrop>
+          <div class="reviewerConfirmation">
+            Group
+            <span class="groupName">
+              {{_reviewerPendingConfirmation.group.name}}
+            </span>
+            has
+            <span class="groupSize">
+              {{_reviewerPendingConfirmation.count}}
+            </span>
+            members.
+            <br>
+            Are you sure you want to add them all?
+          </div>
+          <div class="reviewerConfirmationButtons">
+            <gr-button on-tap="_confirmPendingReviewer">Yes</gr-button>
+            <gr-button on-tap="_cancelPendingReviewer">No</gr-button>
+          </div>
+        </gr-overlay>
       </section>
       <section class="textareaContainer">
         <iron-autogrow-textarea
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 9c2d023..b1f5d17 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
@@ -54,6 +54,10 @@
       _account: Object,
       _owners: Array,
       _reviewers: Array,
+      _reviewerPendingConfirmation: {
+        type: Object,
+        observer: '_reviewerPendingConfirmationUpdated',
+      },
     },
 
     FocusTarget: FocusTarget,
@@ -130,15 +134,17 @@
       var newReviewers = this.$.reviewers.additions();
       newReviewers.forEach(function(reviewer) {
         var reviewerId;
+        var confirmed;
         if (reviewer.account) {
           reviewerId = reviewer.account._account_id;
         } else if (reviewer.group) {
           reviewerId = reviewer.group.id;
+          confirmed = reviewer.group.confirmed;
         }
         if (!obj.reviewers) {
           obj.reviewers = [];
         }
-        obj.reviewers.push({reviewer: reviewerId});
+        obj.reviewers.push({reviewer: reviewerId, confirmed: confirmed});
       });
 
       this.disabled = true;
@@ -262,5 +268,23 @@
       return this.$.restAPI.saveChangeReview(this.change._number, this.patchNum,
           review);
     },
+
+    _reviewerPendingConfirmationUpdated: function(reviewer) {
+      if (reviewer === null) {
+        this.$.reviewerConfirmationOverlay.close();
+      } else {
+        this.$.reviewerConfirmationOverlay.open();
+      }
+    },
+
+    _confirmPendingReviewer: function() {
+      this.$.reviewers.confirmGroup(this._reviewerPendingConfirmation.group);
+      this.focusOn(FocusTarget.REVIEWERS);
+    },
+
+    _cancelPendingReviewer: function() {
+      this._reviewerPendingConfirmation = null;
+      this.focusOn(FocusTarget.REVIEWERS);
+    },
   });
 })();
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html
index cbef78d..0456be1 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html
@@ -144,5 +144,92 @@
         });
       });
     });
+
+    function getActiveElement() {
+      return Polymer.IronOverlayManager.deepActiveElement;
+    }
+
+    function isVisible(el) {
+      assert.ok(el);
+      return getComputedStyle(el).getPropertyValue('display') != 'none';
+    }
+
+    function overlayObserver(mode) {
+      return new Promise(function(resolve) {
+        function listener() {
+          element.removeEventListener('iron-overlay-' + mode, listener);
+          resolve();
+        }
+        element.addEventListener('iron-overlay-' + mode, listener);
+      });
+    }
+
+    test('reviewer confirmation', function(done) {
+      var yesButton =
+          element.$$('.reviewerConfirmationButtons gr-button:first-child');
+      var noButton =
+          element.$$('.reviewerConfirmationButtons gr-button:last-child');
+
+      element._reviewerPendingConfirmation = null;
+      flushAsynchronousOperations();
+      assert.isFalse(isVisible(element.$.reviewerConfirmationOverlay));
+
+      // Cause the confirmation dialog to display.
+      var observer = overlayObserver('opened');
+      var group = {
+        id: 'id',
+        name: 'name',
+        count: 10,
+      };
+      element._reviewerPendingConfirmation = {
+        group: group,
+      };
+
+      observer.then(function() {
+        assert.isTrue(isVisible(element.$.reviewerConfirmationOverlay));
+        observer = overlayObserver('closed');
+        MockInteractions.tap(noButton); // close the overlay
+        return observer;
+      }).then(function() {
+        assert.isFalse(isVisible(element.$.reviewerConfirmationOverlay));
+
+        // We should be focused on account entry input.
+        assert.equal(getActiveElement().id, 'input');
+
+        // No reviewer should have been added.
+        assert.deepEqual(element.$.reviewers.additions(), []);
+
+        // Reopen confirmation dialog.
+        observer = overlayObserver('opened');
+        element._reviewerPendingConfirmation = {
+          group: group,
+        };
+        return observer;
+      }).then(function() {
+        assert.isTrue(isVisible(element.$.reviewerConfirmationOverlay));
+        observer = overlayObserver('closed');
+        MockInteractions.tap(yesButton); // confirm the group
+        return observer;
+      }).then(function() {
+        assert.isFalse(isVisible(element.$.reviewerConfirmationOverlay));
+        assert.deepEqual(
+            element.$.reviewers.additions(),
+            [
+              {
+                group: {
+                  id: 'id',
+                  name: 'name',
+                  count: 10,
+                  confirmed: true,
+                  _group: true,
+                  _pendingAdd: true,
+                },
+              },
+            ]);
+
+        // We should be focused on account entry input.
+        assert.equal(getActiveElement().id, 'input');
+      }).then(done);
+    });
   });
 </script>