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="../../../styles/shared-styles.html">
<link rel="import" href="../../change-list/gr-change-list/gr-change-list.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="../../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="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
<link rel="import" href="../gr-user-header/gr-user-header.html"> <link rel="import" href="../gr-user-header/gr-user-header.html">
@@ -37,18 +40,42 @@ limitations under the License.
gr-change-list { gr-change-list {
width: 100%; width: 100%;
} }
.hide {
display: none;
}
gr-user-header { gr-user-header {
border-bottom: 1px solid var(--border-color); 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) { @media only screen and (max-width: 50em) {
.loading { .loading {
padding: 0 var(--default-horizontal-margin); padding: 0 var(--default-horizontal-margin);
} }
} }
</style> </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 class="loading" hidden$="[[!_loading]]">Loading...</div>
<div hidden$="[[_loading]]" hidden> <div hidden$="[[_loading]]" hidden>
<gr-user-header <gr-user-header
@@ -64,6 +91,21 @@ limitations under the License.
on-toggle-star="_handleToggleStar" on-toggle-star="_handleToggleStar"
on-toggle-reviewed="_handleToggleReviewed"></gr-change-list> on-toggle-reviewed="_handleToggleReviewed"></gr-change-list>
</div> </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-rest-api-interface id="restAPI"></gr-rest-api-interface>
<gr-reporting id="reporting"></gr-reporting> <gr-reporting id="reporting"></gr-reporting>
</template> </template>

View File

@@ -51,6 +51,11 @@
type: Boolean, type: Boolean,
value: true, value: true,
}, },
_showDraftsBanner: {
type: Boolean,
value: false,
},
}, },
observers: [ observers: [
@@ -153,6 +158,7 @@
return dashboardPromise.then(this._fetchDashboardChanges.bind(this)) return dashboardPromise.then(this._fetchDashboardChanges.bind(this))
.then(() => { .then(() => {
this._maybeShowDraftsBanner();
this.$.reporting.dashboardDisplayed(); this.$.reporting.dashboardDisplayed();
}).catch(err => { }).catch(err => {
console.warn(err); console.warn(err);
@@ -199,5 +205,44 @@
this.$.restAPI.saveChangeReviewed(e.detail.change._number, this.$.restAPI.saveChangeReviewed(e.detail.change._number,
e.detail.reviewed); 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(); 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', () => { test('_computeTitle', () => {
assert.equal(element._computeTitle('self'), 'My Reviews'); assert.equal(element._computeTitle('self'), 'My Reviews');
assert.equal(element._computeTitle('not self'), 'Dashboard for not self'); assert.equal(element._computeTitle('not self'), 'Dashboard for not self');

View File

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