Allow for bulk deletion of draft comments
This change adds a banner to the dashboard view that has controls for deleting draft comments from all closed changes at once. Bug: Issue 9740 Change-Id: I0b44219ca4e2207753139e0f4519b18a34284e77
This commit is contained in:
		
				
					committed by
					
						
						Logan Hanks
					
				
			
			
				
	
			
			
			
						parent
						
							6750cf9c66
						
					
				
				
					commit
					0dac13a034
				
			@@ -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>
 | 
			
		||||
 
 | 
			
		||||
@@ -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();
 | 
			
		||||
    },
 | 
			
		||||
  });
 | 
			
		||||
})();
 | 
			
		||||
 
 | 
			
		||||
@@ -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');
 | 
			
		||||
 
 | 
			
		||||
@@ -2925,5 +2925,13 @@
 | 
			
		||||
        reportEndpointAsIs: true,
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    deleteDraftComments(query) {
 | 
			
		||||
      return this._send({
 | 
			
		||||
        method: 'POST',
 | 
			
		||||
        url: '/accounts/self/drafts:delete',
 | 
			
		||||
        query,
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
  });
 | 
			
		||||
})();
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user