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
This commit is contained in:
Logan Hanks
2016-07-22 10:31:12 -07:00
committed by Andrew Bonventre
parent 44cf1b5d03
commit a008aa9428
5 changed files with 199 additions and 1 deletions

View File

@@ -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) {

View File

@@ -199,5 +199,38 @@ limitations under the License.
},
]);
});
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>

View File

@@ -20,6 +20,7 @@ limitations under the License.
<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 @@ limitations under the License.
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 @@ limitations under the License.
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

View File

@@ -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);
},
});
})();

View File

@@ -144,5 +144,92 @@ limitations under the License.
});
});
});
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>