Merge "Allow for bulk deletion of draft comments"

This commit is contained in:
Logan Hanks
2018-09-24 22:03:09 +00:00
committed by Gerrit Code Review
4 changed files with 177 additions and 3 deletions

View File

@@ -20,6 +20,9 @@ limitations under the License.
<link rel="import" href="../../../styles/shared-styles.html">
<link rel="import" href="../../change-list/gr-change-list/gr-change-list.html">
<link rel="import" href="../../core/gr-reporting/gr-reporting.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
<link rel="import" href="../../shared/gr-dialog/gr-dialog.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-user-header/gr-user-header.html">
@@ -37,18 +40,42 @@ limitations under the License.
gr-change-list {
width: 100%;
}
.hide {
display: none;
}
gr-user-header {
border-bottom: 1px solid var(--border-color);
}
.banner {
align-items: center;
background-color: var(--comment-background-color);
border-bottom: 1px solid var(--border-color);
display: flex;
justify-content: space-between;
padding: .25em var(--default-horizontal-margin);
}
.banner gr-button {
--gr-button: {
color: var(--primary-text-color);
}
}
.hide {
display: none;
}
@media only screen and (max-width: 50em) {
.loading {
padding: 0 var(--default-horizontal-margin);
}
}
</style>
<div class$="banner [[_computeBannerClass(_showDraftsBanner)]]">
<div>
You have draft comments on closed changes.
</div>
<div>
<gr-button
class="delete"
link
on-tap="_handleOpenDeleteDialog">Delete All</gr-button>
</div>
</div>
<div class="loading" hidden$="[[!_loading]]">Loading...</div>
<div hidden$="[[_loading]]" hidden>
<gr-user-header
@@ -64,6 +91,21 @@ limitations under the License.
on-toggle-star="_handleToggleStar"
on-toggle-reviewed="_handleToggleReviewed"></gr-change-list>
</div>
<gr-overlay id="confirmDeleteOverlay" with-backdrop>
<gr-dialog
id="confirmDeleteDialog"
confirm-label="Delete"
on-confirm="_handleConfirmDelete"
on-cancel="_closeConfirmDeleteOverlay">
<div class="header" slot="header">
Delete comments
</div>
<div class="main" slot="main">
Are you sure you want to delete all your draft comments in closed changes? This action
cannot be undone.
</div>
</gr-dialog>
</gr-overlay>
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
<gr-reporting id="reporting"></gr-reporting>
</template>

View File

@@ -51,6 +51,11 @@
type: Boolean,
value: true,
},
_showDraftsBanner: {
type: Boolean,
value: false,
},
},
observers: [
@@ -153,6 +158,7 @@
return dashboardPromise.then(this._fetchDashboardChanges.bind(this))
.then(() => {
this._maybeShowDraftsBanner();
this.$.reporting.dashboardDisplayed();
}).catch(err => {
console.warn(err);
@@ -199,5 +205,44 @@
this.$.restAPI.saveChangeReviewed(e.detail.change._number,
e.detail.reviewed);
},
/**
* Banner is shown if a user is on their own dashboard and they have draft
* comments on closed changes.
*/
_maybeShowDraftsBanner() {
this._showDraftsBanner = false;
if (!(this.params.user === 'self')) { return; }
const draftSection = this._results
.find(section => section.query === 'has:draft');
if (!draftSection || !draftSection.results.length) { return; }
const closedChanges = draftSection.results
.filter(change => !this.changeIsOpen(change.status));
if (!closedChanges.length) { return; }
this._showDraftsBanner = true;
},
_computeBannerClass(show) {
return show ? '' : 'hide';
},
_handleOpenDeleteDialog() {
this.$.confirmDeleteOverlay.open();
},
_handleConfirmDelete() {
this.$.confirmDeleteDialog.disabled = true;
return this.$.restAPI.deleteDraftComments('-is:open').then(() => {
this._closeConfirmDeleteOverlay();
this._reload();
});
},
_closeConfirmDeleteOverlay() {
this.$.confirmDeleteOverlay.close();
},
});
})();

View File

@@ -63,6 +63,85 @@ limitations under the License.
sandbox.restore();
});
suite('drafts banner functionality', () => {
suite('_maybeShowDraftsBanner', () => {
test('not dashboard/self', () => {
element.params = {user: 'notself'};
element._maybeShowDraftsBanner();
assert.isFalse(element._showDraftsBanner);
});
test('no drafts at all', () => {
element.params = {user: 'self'};
element._results = [];
element._maybeShowDraftsBanner();
assert.isFalse(element._showDraftsBanner);
});
test('no drafts on open changes', () => {
element.params = {user: 'self'};
element._results = [{query: 'has:draft', results: [{status: '_'}]}];
sandbox.stub(element, 'changeIsOpen').returns(true);
element._maybeShowDraftsBanner();
assert.isFalse(element._showDraftsBanner);
});
test('no drafts on open changes', () => {
element.params = {user: 'self'};
element._results = [{query: 'has:draft', results: [{status: '_'}]}];
sandbox.stub(element, 'changeIsOpen').returns(false);
element._maybeShowDraftsBanner();
assert.isTrue(element._showDraftsBanner);
});
});
test('_showDraftsBanner', () => {
element._showDraftsBanner = false;
flushAsynchronousOperations();
assert.isTrue(isHidden(element.$$('.banner')));
element._showDraftsBanner = true;
flushAsynchronousOperations();
assert.isFalse(isHidden(element.$$('.banner')));
});
test('delete tap opens dialog', () => {
sandbox.stub(element, '_handleOpenDeleteDialog');
element._showDraftsBanner = true;
flushAsynchronousOperations();
MockInteractions.tap(element.$$('.banner .delete'));
assert.isTrue(element._handleOpenDeleteDialog.called);
});
test('delete comments flow', async () => {
sandbox.spy(element, '_handleConfirmDelete');
sandbox.stub(element, '_reload');
// Set up control over timing of when RPC resolves.
let deleteDraftCommentsPromiseResolver;
const deleteDraftCommentsPromise = new Promise(resolve => {
deleteDraftCommentsPromiseResolver = resolve;
});
sandbox.stub(element.$.restAPI, 'deleteDraftComments')
.returns(deleteDraftCommentsPromise);
// Open confirmation dialog and tap confirm button.
await element.$.confirmDeleteOverlay.open();
MockInteractions.tap(element.$.confirmDeleteDialog.$.confirm);
flushAsynchronousOperations();
assert.isTrue(element.$.restAPI.deleteDraftComments
.calledWithExactly('-is:open'));
assert.isTrue(element.$.confirmDeleteDialog.disabled);
assert.equal(element._reload.callCount, 0);
// Verify state after RPC resolves.
deleteDraftCommentsPromiseResolver([]);
await deleteDraftCommentsPromise;
assert.equal(element._reload.callCount, 1);
});
});
test('_computeTitle', () => {
assert.equal(element._computeTitle('self'), 'My Reviews');
assert.equal(element._computeTitle('not self'), 'Dashboard for not self');

View File

@@ -2925,5 +2925,13 @@
reportEndpointAsIs: true,
});
},
deleteDraftComments(query) {
return this._send({
method: 'POST',
url: '/accounts/self/drafts:delete',
query,
});
},
});
})();