From af1e0f8bf60a14e2e2ab90e4a44fe0376fd5c90b Mon Sep 17 00:00:00 2001 From: Ravi Mistry Date: Tue, 10 Oct 2017 09:47:26 -0400 Subject: [PATCH] Add ability to add annotation layers from plugins Highlights: * Adds a new getDiffLayers function to gr-js-api-interface.js. This is invoked by gr-diff-builder.html when gathering annotation layers. * New annotationApi function in gr-public-js-api.js for plugins to call. * The annotationApi function returns an instance of the new GrAnnotationActionsInterface in gr-annotation-actions-js-api.js * GrAnnotationActionsInterface has an API for the plugin to register an addLayerFunction and an optional method to call to get a notify callback. * The new samples/coverage-plugin.html is an end-to-end example of how to invoke the new APIs to annotate lines. Bug: Issue 7339 Change-Id: Ie51845e0b3564953aba5d7d41986cedce0337073 --- .../diff/gr-diff-builder/gr-diff-builder.html | 14 +- .../gr-diff-builder/gr-diff-builder_test.html | 27 ++++ .../app/elements/diff/gr-diff/gr-diff.html | 2 + .../app/elements/diff/gr-diff/gr-diff.js | 1 + .../gr-annotation-actions-context.js | 50 ++++++ .../gr-annotation-actions-context_test.html | 77 ++++++++++ .../gr-annotation-actions-js-api.js | 143 ++++++++++++++++++ .../gr-annotation-actions-js-api_test.html | 133 ++++++++++++++++ .../gr-js-api-interface.html | 2 + .../gr-js-api-interface.js | 15 ++ .../gr-js-api-interface/gr-public-js-api.js | 4 + .../app/samples/coverage-plugin.html | 55 +++++++ 12 files changed, 522 insertions(+), 1 deletion(-) create mode 100644 polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-context.js create mode 100644 polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-context_test.html create mode 100644 polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api.js create mode 100644 polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api_test.html create mode 100644 polygerrit-ui/app/samples/coverage-plugin.html diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.html b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.html index 1cfab26f33..306b51087b 100644 --- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.html +++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.html @@ -36,6 +36,7 @@ limitations under the License. id="processor" groups="{{_groups}}"> + @@ -89,6 +90,9 @@ limitations under the License. properties: { diff: Object, + diffPath: String, + changeNum: String, + patchNum: String, viewMode: String, comments: Object, isImageDiff: Boolean, @@ -111,7 +115,7 @@ limitations under the License. attached() { // Setup annotation layers. - this._layers = [ + const layers = [ this._createTrailingWhitespaceLayer(), this.$.syntaxLayer, this._createIntralineLayer(), @@ -119,6 +123,14 @@ limitations under the License. this.$.rangeLayer, ]; + // Get layers from plugins (if any). + for (const pluginLayer of this.$.jsAPI.getDiffLayers( + this.diffPath, this.changeNum, this.patchNum)) { + layers.push(pluginLayer); + } + + this._layers = layers; + this.async(() => { this._preRenderThread(); }); diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder_test.html b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder_test.html index f9e465e28d..2089b6fdc5 100644 --- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder_test.html +++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder_test.html @@ -621,6 +621,33 @@ limitations under the License. }); }); + suite('layers from plugins', () => { + let element; + let initialLayersCount; + + setup(() => { + element = fixture('basic'); + element._showTrailingWhitespace = true; + initialLayersCount = element._layers.length; + }); + + test('no plugin layers', () => { + const getDiffLayersStub = sinon.stub(element.$.jsAPI, 'getDiffLayers') + .returns([]); + element.attached(); + assert.isTrue(getDiffLayersStub.called); + assert.equal(element._layers.length, initialLayersCount); + }); + + test('with plugin layers', () => { + const getDiffLayersStub = sinon.stub(element.$.jsAPI, 'getDiffLayers') + .returns([{}, {}]); + element.attached(); + assert.isTrue(getDiffLayersStub.called); + assert.equal(element._layers.length, initialLayersCount+2); + }); + }); + suite('trailing whitespace', () => { let element; let layer; diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html index f32234a28f..9da6996b41 100644 --- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html +++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html @@ -266,6 +266,8 @@ limitations under the License. project-name="[[projectName]]" diff="[[_diff]]" diff-path="[[path]]" + change-num="[[changeNum]]" + patch-num="[[patchRange.patchNum]]" view-mode="[[viewMode]]" line-wrapping="[[lineWrapping]]" is-image-diff="[[isImageDiff]]" diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js index c3add28c9b..58a1d82961 100644 --- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js +++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js @@ -52,6 +52,7 @@ type: Boolean, value: false, }, + /** @type {?} */ patchRange: Object, path: String, prefs: { diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-context.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-context.js new file mode 100644 index 0000000000..7810d7d251 --- /dev/null +++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-context.js @@ -0,0 +1,50 @@ +// Copyright (C) 2017 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +(function(window) { + 'use strict'; + + /** + * Used to create a context for GrAnnotationActionsInterface. + * @param {HTMLElement} el The DIV.contentText element to apply the + * annotation to using annotateRange. + * @param {GrDiffLine} line The line object. + * @param {String} path The file path (eg: /COMMIT_MSG'). + * @param {String} changeNum The Gerrit change number. + * @param {String} patchNum The Gerrit patch number. + */ + function GrAnnotationActionsContext(el, line, path, changeNum, patchNum) { + this._el = el; + + this.line = line; + this.path = path; + this.changeNum = parseInt(changeNum); + this.patchNum = parseInt(patchNum); + } + + /** + * Method to add annotations to a line. + * @param {Number} start The line number where the update starts. + * @param {Number} end The line number where the update ends. + * @param {String} cssClass The name of a CSS class created using Gerrit.css. + * @param {String} side The side of the update. ('left' or 'right') + */ + GrAnnotationActionsContext.prototype.annotateRange = function( + start, end, cssClass, side) { + if (this._el.getAttribute('data-side') == side) { + GrAnnotation.annotateElement(this._el, start, end, cssClass); + } + }; + + window.GrAnnotationActionsContext = GrAnnotationActionsContext; +})(window); diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-context_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-context_test.html new file mode 100644 index 0000000000..55ca90fe94 --- /dev/null +++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-context_test.html @@ -0,0 +1,77 @@ + + + + +gr-annotation-actions-context + + + + + + + + + + + + + + + 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 new file mode 100644 index 0000000000..94bae452fd --- /dev/null +++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api.js @@ -0,0 +1,143 @@ +// Copyright (C) 2017 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +(function(window) { + 'use strict'; + + function GrAnnotationActionsInterface(plugin) { + this.plugin = plugin; + // Return this instance when there is an annotatediff event. + plugin.on('annotatediff', this); + + // Collect all annotation layers instantiated by getLayer. Will be used when + // notifying their listeners in the notify function. + this._annotationLayers = []; + + // Default impl is a no-op. + this._addLayerFunc = annotationActionsContext => {}; + } + + /** + * Register a function to call to apply annotations. Plugins should use + * GrAnnotationActionsContext.annotateRange to apply a CSS class to a range + * within a line. + * @param {Function} addLayerFunc The function + * that will be called when the AnnotationLayer is ready to annotate. + */ + GrAnnotationActionsInterface.prototype.addLayer = function(addLayerFunc) { + this._addLayerFunc = addLayerFunc; + return this; + }; + + /** + * The specified function will be called with a notify function for the plugin + * to call when it has all required data for annotation. Optional. + * @param {Function>} notifyFunc See + * doc of the notify function below to see what it does. + */ + GrAnnotationActionsInterface.prototype.addNotifier = function(notifyFunc) { + // Register the notify function with the plugin's function. + notifyFunc(this.notify.bind(this)); + 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. + * @param {String} path The file path whose listeners should be notified. + * @param {Number} start The line where the update starts. + * @param {Number} end The line where the update ends. + * @param {String} side The side of the update ('left' or 'right'). + */ + GrAnnotationActionsInterface.prototype.notify = function( + path, startRange, endRange, side) { + for (const annotationLayer of this._annotationLayers) { + // Notify only the annotation layer that is associated with the specified + // path. + if (annotationLayer._path === path) { + annotationLayer.notifyListeners(startRange, endRange, side); + break; + } + } + }; + + /** + * Should be called to register annotation layers by the framework. Not + * intended to be called by plugins. + * @param {String} path The file path (eg: /COMMIT_MSG'). + * @param {String} changeNum The Gerrit change number. + * @param {String} patchNum The Gerrit patch number. + */ + GrAnnotationActionsInterface.prototype.getLayer = function( + path, changeNum, patchNum) { + const annotationLayer = new AnnotationLayer(path, changeNum, patchNum, + this._addLayerFunc); + this._annotationLayers.push(annotationLayer); + return annotationLayer; + }; + + /** + * Used to create an instance of the Annotation Layer interface. + * @param {String} path The file path (eg: /COMMIT_MSG'). + * @param {String} changeNum The Gerrit change number. + * @param {String} patchNum The Gerrit patch number. + * @param {Function} addLayerFunc The function + * that will be called when the AnnotationLayer is ready to annotate. + */ + function AnnotationLayer(path, changeNum, patchNum, addLayerFunc) { + this._path = path; + this._changeNum = changeNum; + this._patchNum = patchNum; + this._addLayerFunc = addLayerFunc; + + this._listeners = []; + } + + /** + * Register a listener for layer updates. + * @param {Function} fn The update handler function. + * Should accept as arguments the line numbers for the start and end of + * the update and the side as a string. + */ + AnnotationLayer.prototype.addListener = function(fn) { + this._listeners.push(fn); + }; + + /** + * Layer method to add annotations to a line. + * @param {HTMLElement} el The DIV.contentText element to apply the + * annotation to. + * @param {GrDiffLine} line The line object. + */ + AnnotationLayer.prototype.annotate = function(el, line) { + const annotationActionsContext = new GrAnnotationActionsContext( + el, line, this._path, this._changeNum, this._patchNum); + this._addLayerFunc(annotationActionsContext); + }; + + /** + * Notify Layer listeners of changes to annotations. + * @param {Number} start The line where the update starts. + * @param {Number} end The line where the update ends. + * @param {String} side The side of the update. ('left' or 'right') + */ + AnnotationLayer.prototype.notifyListeners = function( + startRange, endRange, side) { + for (const listener of this._listeners) { + listener(startRange, endRange, side); + } + }; + + window.GrAnnotationActionsInterface = GrAnnotationActionsInterface; +})(window); 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 new file mode 100644 index 0000000000..39623edf8e --- /dev/null +++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api_test.html @@ -0,0 +1,133 @@ + + + + +gr-annotation-actions-js-api-js-api + + + + + + + diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.html index be5b72c85b..291e6feb6b 100644 --- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.html +++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.html @@ -26,6 +26,8 @@ limitations under the License. + + diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.js index 420d4afc85..38262eccf4 100644 --- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.js +++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.js @@ -23,6 +23,7 @@ COMMENT: 'comment', REVERT: 'revert', POST_REVERT: 'postrevert', + ANNOTATE_DIFF: 'annotatediff', }; const Element = { @@ -178,6 +179,20 @@ return revertMsg; }, + getDiffLayers(path, changeNum, patchNum) { + const layers = []; + for (const annotationApi of + this._getEventCallbacks(EventType.ANNOTATE_DIFF)) { + try { + const layer = annotationApi.getLayer(path, changeNum, patchNum); + layers.push(layer); + } catch (err) { + console.error(err); + } + } + return layers; + }, + getLabelValuesPostRevert(change) { let labels = {}; for (const cb of this._getEventCallbacks(EventType.POST_REVERT)) { diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js index 14ad2a661d..6c802199dc 100644 --- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js +++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js @@ -175,6 +175,10 @@ return Gerrit.delete(this.url(url), opt_callback); }; + Plugin.prototype.annotationApi = function() { + return new GrAnnotationActionsInterface(this); + }; + Plugin.prototype.changeActions = function() { return new GrChangeActionsInterface(this, Plugin._sharedAPIElement.getElement( diff --git a/polygerrit-ui/app/samples/coverage-plugin.html b/polygerrit-ui/app/samples/coverage-plugin.html new file mode 100644 index 0000000000..6f76dc4f1c --- /dev/null +++ b/polygerrit-ui/app/samples/coverage-plugin.html @@ -0,0 +1,55 @@ + + +