Merge "Adding checkbox to annotations API to toggle annotations on/off"
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html
index 794e1fb..36ae32a 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html
@@ -287,6 +287,12 @@
                   on-tap="_handlePrefsTap">Preferences</gr-button>
             </span>
           </span>
+          <gr-endpoint-decorator name="annotation-toggler">
+            <span hidden id="annotation-span">
+              <label for="annotation-checkbox" id="annotation-label"></label>
+              <input is="iron-input" type="checkbox" id="annotation-checkbox" disabled>
+            </span>
+          </gr-endpoint-decorator>
           <span class$="blameLoader [[_computeBlameLoaderClass(_isImageDiff, _isBlameSupported)]]">
             <span class="separator"></span>
             <gr-button
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api.js
index 94bae45..6350c54 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api.js
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api.js
@@ -52,6 +52,45 @@
   };
 
   /**
+   * Returns a checkbox HTMLElement that can be used to toggle annotations
+   * on/off. The checkbox will be initially disabled. Plugins should enable it
+   * when data is ready and should add a click handler to toggle CSS on/off.
+   *
+   * Note1: Calling this method from multiple plugins will only work for the
+   *        1st call. It will print an error message for all subsequent calls
+   *        and will not invoke their onAttached functions.
+   * Note2: This method will be deprecated and eventually removed when
+   *        https://bugs.chromium.org/p/gerrit/issues/detail?id=8077 is
+   *        implemented.
+   *
+   * @param {String} checkboxLabel Will be used as the label for the checkbox.
+   *     Optional. "Enable" is used if this is not specified.
+   * @param {Function<HTMLElement>} onAttached The function that will be called
+   *     when the checkbox is attached to the page.
+   */
+  GrAnnotationActionsInterface.prototype.enableToggleCheckbox = function(
+      checkboxLabel, onAttached) {
+    this.plugin.hook('annotation-toggler').onAttached(element => {
+      if (!element.content.hidden) {
+        console.error(
+            element.content.id + ' is already enabled. Cannot re-enable.');
+        return;
+      }
+      element.content.removeAttribute('hidden');
+
+      const label = element.content.querySelector('#annotation-label');
+      if (checkboxLabel) {
+        label.textContent = checkboxLabel;
+      } else {
+        label.textContent = 'Enable';
+      }
+      const checkbox = element.content.querySelector('#annotation-checkbox');
+      onAttached(checkbox);
+    });
+    return this;
+  };
+
+  /**
    * The notify function will call the listeners of all required annotation
    * layers. Intended to be called by the plugin when all required data for
    * annotation is available.
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api_test.html
index 39623ed..a19df85 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api_test.html
@@ -23,14 +23,23 @@
 <link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="../../change/gr-change-actions/gr-change-actions.html">
 
+<test-fixture id="basic">
+  <template>
+    <span hidden id="annotation-span">
+      <label for="annotation-checkbox" id="annotation-label"></label>
+      <input is="iron-input" type="checkbox" id="annotation-checkbox" disabled>
+    </span>
+  </template>
+</test-fixture>
+
 <script>
   suite('gr-annotation-actions-js-api tests', () => {
     let annotationActions;
     let sandbox;
+    let plugin;
 
     setup(() => {
       sandbox = sinon.sandbox.create();
-      let plugin;
       Gerrit.install(p => { plugin = p; }, '0.1',
           'http://test.com/plugins/testplugin/static/test.js');
       annotationActions = plugin.annotationApi();
@@ -101,6 +110,45 @@
       assert.isTrue(layer2Spy.called);
     });
 
+    test('toggle checkbox', () => {
+      fakeEl = {content: fixture('basic')};
+      const hookStub = {onAttached: sandbox.stub()};
+      sandbox.stub(plugin, 'hook').returns(hookStub);
+
+      let checkbox;
+      let onAttachedFuncCalled = false;
+      const onAttachedFunc = c => {
+        checkbox = c;
+        onAttachedFuncCalled = true;
+      };
+      annotationActions.enableToggleCheckbox('test label', onAttachedFunc);
+      emulateAttached = () => hookStub.onAttached.callArgWith(0, fakeEl);
+      emulateAttached();
+
+      // Assert that onAttachedFunc is called and HTML elements have the
+      // expected state.
+      assert.isTrue(onAttachedFuncCalled);
+      assert.equal(checkbox.id, 'annotation-checkbox');
+      assert.isTrue(checkbox.disabled);
+      assert.equal(document.getElementById('annotation-label').textContent,
+          'test label');
+      assert.isFalse(document.getElementById('annotation-span').hidden);
+
+      // Assert that error is shown if we try to enable checkbox again.
+      onAttachedFuncCalled = false;
+      annotationActions.enableToggleCheckbox('test label2', onAttachedFunc);
+      const errorStub = sandbox.stub(
+          console, 'error', (msg, err) => undefined);
+      emulateAttached();
+      assert.isTrue(
+          errorStub.calledWith(
+              'annotation-span is already enabled. Cannot re-enable.'));
+      // Assert that onAttachedFunc is not called and the label has not changed.
+      assert.isFalse(onAttachedFuncCalled);
+      assert.equal(document.getElementById('annotation-label').textContent,
+          'test label');
+    });
+
     test('layer notify listeners', () => {
       const annotationLayer = annotationActions.getLayer(
           '/dummy/path', 1, 2);
diff --git a/polygerrit-ui/app/samples/coverage-plugin.html b/polygerrit-ui/app/samples/coverage-plugin.html
index 6f76dc4..9bec658 100644
--- a/polygerrit-ui/app/samples/coverage-plugin.html
+++ b/polygerrit-ui/app/samples/coverage-plugin.html
@@ -20,35 +20,53 @@
         changeNum: 77001,
         patchNum: 1,
       };
+      coverageData['go/sklog/sklog.go'] = {
+        linesMissingCoverage: [3, 322, 323, 324],
+        totalLines: 350,
+        changeNum: 85963,
+        patchNum: 13,
+      };
     }
 
     Gerrit.install(plugin => {
       const coverageData = {};
-      plugin.annotationApi().addNotifier(notifyFunc => {
-        new Promise(resolve => setTimeout(resolve, 3000)).then(
-            () => {
-              populateWithDummyData(coverageData);
-              Object.keys(coverageData).forEach(file => {
-                notifyFunc(file, 0, coverageData[file].totalLines, 'right');
-              });
-            });
-      }).addLayer(context => {
+      let displayCoverage = false;
+      const annotationApi = plugin.annotationApi();
+      annotationApi.addLayer(context => {
         if (Object.keys(coverageData).length === 0) {
-          // Coverage data is not ready yet.
+           // Coverage data is not ready yet.
           return;
         }
         const path = context.path;
         const line = context.line;
-        // Highlight lines missing coverage with this background color.
-        const cssClass = Gerrit.css('background-color: #EF9B9B');
+          // Highlight lines missing coverage with this background color if
+          // coverage should be displayed, else do nothing.
+        const cssClass = displayCoverage
+                         ? Gerrit.css('background-color: #EF9B9B')
+                         : Gerrit.css('');
         if (coverageData[path] &&
-            coverageData[path].changeNum === context.changeNum &&
-            coverageData[path].patchNum === context.patchNum) {
+              coverageData[path].changeNum === context.changeNum &&
+              coverageData[path].patchNum === context.patchNum) {
           const linesMissingCoverage = coverageData[path].linesMissingCoverage;
           if (linesMissingCoverage.includes(line.afterNumber)) {
             context.annotateRange(0, line.text.length, cssClass, 'right');
           }
         }
+      }).enableToggleCheckbox('Display Coverage', checkbox => {
+        // Checkbox is attached so now add the notifier that will be controlled
+        // by the checkbox.
+        annotationApi.addNotifier(notifyFunc => {
+          new Promise(resolve => setTimeout(resolve, 3000)).then(() => {
+            populateWithDummyData(coverageData);
+            checkbox.disabled = false;
+            checkbox.onclick = e => {
+              displayCoverage = e.target.checked;
+              Object.keys(coverageData).forEach(file => {
+                notifyFunc(file, 0, coverageData[file].totalLines, 'right');
+              });
+            };
+          });
+        });
       });
     });
   </script>