Merge "Standalone PolyGerrit plugins"
This commit is contained in:
24
Documentation/dev-plugins-pg.txt
Normal file
24
Documentation/dev-plugins-pg.txt
Normal file
@@ -0,0 +1,24 @@
|
||||
= Gerrit Code Review - PolyGerrit Plugin Development
|
||||
|
||||
CAUTION: Work in progress. Hard hat area. +
|
||||
This document will be populated with details along with implementation. +
|
||||
link:https://groups.google.com/d/topic/repo-discuss/vb8WJ4m0hK0/discussion[Join the discussion.]
|
||||
|
||||
== Plugin loading and initialization
|
||||
|
||||
link:https://gerrit-review.googlesource.com/Documentation/js-api.html#_entry_point[Entry point] for the plugin and the loading method is based on link:http://w3c.github.io/webcomponents/spec/imports/[HTML Imports] spec.
|
||||
|
||||
* Plugin provides index.html, similar to link:https://gerrit-review.googlesource.com/Documentation/dev-plugins.html#deployment[.js Web UI plugins]
|
||||
* index.html contains a `dom-module` tag with a script that uses `Gerrit.install()`.
|
||||
* PolyGerrit imports index.html along with all required resources defined in it (fonts, styles, etc)
|
||||
* For standalone plugins, the entry point file is a `pluginname.html` file located in `gerrit-site/plugins` folder, where pluginname is an alphanumeric plugin name.
|
||||
|
||||
Here's a sample `myplugin.html`:
|
||||
|
||||
``` html
|
||||
<dom-module id="my-plugin">
|
||||
<script>
|
||||
Gerrit.install(function() { console.log('Ready.'); });
|
||||
</script>
|
||||
</dom-module>
|
||||
```
|
||||
@@ -19,4 +19,5 @@ import java.util.List;
|
||||
public class PluginConfigInfo {
|
||||
public Boolean hasAvatars;
|
||||
public List<String> jsResourcePaths;
|
||||
public List<String> htmlResourcePaths;
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import com.google.gwt.user.client.rpc.AsyncCallback;
|
||||
import com.google.gwt.user.client.ui.DialogBox;
|
||||
import com.google.gwtexpui.progress.client.ProgressBar;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/** Loads JavaScript plugins with a progress meter visible. */
|
||||
public class PluginLoader extends DialogBox {
|
||||
@@ -37,6 +38,13 @@ public class PluginLoader extends DialogBox {
|
||||
List<String> plugins, int loadTimeout, AsyncCallback<VoidResult> callback) {
|
||||
if (plugins == null || plugins.isEmpty()) {
|
||||
callback.onSuccess(VoidResult.create());
|
||||
}
|
||||
plugins = plugins
|
||||
.stream()
|
||||
.filter(p -> p.endsWith(".js"))
|
||||
.collect(Collectors.toList());
|
||||
if (plugins.isEmpty()) {
|
||||
callback.onSuccess(VoidResult.create());
|
||||
} else {
|
||||
self = new PluginLoader(loadTimeout, callback);
|
||||
self.load(plugins);
|
||||
|
||||
@@ -64,6 +64,7 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
@@ -637,7 +638,11 @@ class HttpPluginServlet extends HttpServlet implements StartPluginListener, Relo
|
||||
Path path = plugin.getSrcFile();
|
||||
if (req.getRequestURI().endsWith(getJsPluginPath(plugin)) && Files.exists(path)) {
|
||||
res.setHeader("Content-Length", Long.toString(Files.size(path)));
|
||||
if (path.toString().toLowerCase(Locale.US).endsWith(".html")) {
|
||||
res.setContentType("text/html");
|
||||
} else {
|
||||
res.setContentType("application/javascript");
|
||||
}
|
||||
writeToResponse(res, Files.newInputStream(path));
|
||||
} else {
|
||||
resourceCache.put(key, Resource.NOT_FOUND);
|
||||
|
||||
@@ -309,9 +309,15 @@ public class GetServerInfo implements RestReadView<ConfigResource> {
|
||||
PluginConfigInfo info = new PluginConfigInfo();
|
||||
info.hasAvatars = toBoolean(avatar.get() != null);
|
||||
info.jsResourcePaths = new ArrayList<>();
|
||||
info.htmlResourcePaths = new ArrayList<>();
|
||||
for (WebUiPlugin u : plugins) {
|
||||
info.jsResourcePaths.add(
|
||||
String.format("plugins/%s/%s", u.getPluginName(), u.getJavaScriptResourcePath()));
|
||||
String path = String.format(
|
||||
"plugins/%s/%s", u.getPluginName(), u.getJavaScriptResourcePath());
|
||||
if (path.endsWith(".html")) {
|
||||
info.htmlResourcePaths.add(path);
|
||||
} else {
|
||||
info.jsResourcePaths.add(path);
|
||||
}
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
@@ -404,7 +404,7 @@ public class PluginLoader implements LifecycleListener {
|
||||
String name = entry.getKey();
|
||||
Path path = entry.getValue();
|
||||
String fileName = path.getFileName().toString();
|
||||
if (!isJsPlugin(fileName) && !serverPluginFactory.handles(path)) {
|
||||
if (!isUiPlugin(fileName) && !serverPluginFactory.handles(path)) {
|
||||
log.warn("No Plugin provider was found that handles this file format: {}", fileName);
|
||||
continue;
|
||||
}
|
||||
@@ -586,7 +586,7 @@ public class PluginLoader implements LifecycleListener {
|
||||
private Plugin loadPlugin(String name, Path srcPlugin, FileSnapshot snapshot)
|
||||
throws InvalidPluginException {
|
||||
String pluginName = srcPlugin.getFileName().toString();
|
||||
if (isJsPlugin(pluginName)) {
|
||||
if (isUiPlugin(pluginName)) {
|
||||
return loadJsPlugin(name, srcPlugin, snapshot);
|
||||
} else if (serverPluginFactory.handles(srcPlugin)) {
|
||||
return loadServerPlugin(srcPlugin, snapshot);
|
||||
@@ -718,8 +718,8 @@ public class PluginLoader implements LifecycleListener {
|
||||
|
||||
public String getGerritPluginName(Path srcPath) {
|
||||
String fileName = srcPath.getFileName().toString();
|
||||
if (isJsPlugin(fileName)) {
|
||||
return fileName.substring(0, fileName.length() - 3);
|
||||
if (isUiPlugin(fileName)) {
|
||||
return fileName.substring(0, fileName.lastIndexOf('.'));
|
||||
}
|
||||
if (serverPluginFactory.handles(srcPath)) {
|
||||
return serverPluginFactory.getPluginName(srcPath);
|
||||
@@ -735,8 +735,8 @@ public class PluginLoader implements LifecycleListener {
|
||||
return map;
|
||||
}
|
||||
|
||||
private static boolean isJsPlugin(String name) {
|
||||
return isPlugin(name, "js");
|
||||
private static boolean isUiPlugin(String name) {
|
||||
return isPlugin(name, "js") || isPlugin(name, "html");
|
||||
}
|
||||
|
||||
private static boolean isPlugin(String fileName, String ext) {
|
||||
|
||||
@@ -74,6 +74,18 @@ limitations under the License.
|
||||
.webLink {
|
||||
display: block;
|
||||
}
|
||||
section.assignee {
|
||||
@apply(--change-metadata-assignee);
|
||||
}
|
||||
section.labelStatus {
|
||||
@apply(--change-metadata-label-status);
|
||||
}
|
||||
section.strategy {
|
||||
@apply(--change-metadata-strategy);
|
||||
}
|
||||
section.topic {
|
||||
@apply(--change-metadata-topic);
|
||||
}
|
||||
@media screen and (max-width: 50em), screen and (min-width: 75em) {
|
||||
:host {
|
||||
display: table;
|
||||
|
||||
@@ -17,6 +17,7 @@ limitations under the License.
|
||||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
<link rel="import" href="../behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html">
|
||||
<link rel="import" href="../styles/app-theme.html">
|
||||
<link rel="import" href="./plugins/gr-plugin-host/gr-plugin-host.html">
|
||||
|
||||
<link rel="import" href="./admin/gr-admin-view/gr-admin-view.html">
|
||||
|
||||
@@ -170,6 +171,9 @@ limitations under the License.
|
||||
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
|
||||
<gr-reporting id="reporting"></gr-reporting>
|
||||
<gr-router id="router"></gr-router>
|
||||
<gr-plugin-host id="plugins"
|
||||
config="[[_serverConfig.plugin]]">
|
||||
</gr-plugin-host>
|
||||
</template>
|
||||
<script src="gr-app.js" crossorigin="anonymous"></script>
|
||||
</dom-module>
|
||||
|
||||
@@ -62,7 +62,6 @@
|
||||
|
||||
observers: [
|
||||
'_viewChanged(params.view)',
|
||||
'_loadPlugins(_serverConfig.plugin.js_resource_paths)',
|
||||
],
|
||||
|
||||
behaviors: [
|
||||
@@ -128,17 +127,6 @@
|
||||
}
|
||||
},
|
||||
|
||||
_loadPlugins: function(plugins) {
|
||||
Gerrit._setPluginsCount(plugins.length);
|
||||
for (var i = 0; i < plugins.length; i++) {
|
||||
var scriptEl = document.createElement('script');
|
||||
scriptEl.defer = true;
|
||||
scriptEl.src = '/' + plugins[i];
|
||||
scriptEl.onerror = Gerrit._pluginInstalled;
|
||||
document.body.appendChild(scriptEl);
|
||||
}
|
||||
},
|
||||
|
||||
_loginTapHandler: function(e) {
|
||||
e.preventDefault();
|
||||
page.show('/login/' + encodeURIComponent(
|
||||
|
||||
@@ -89,10 +89,16 @@ limitations under the License.
|
||||
});
|
||||
});
|
||||
|
||||
test('sets plugins count', function() {
|
||||
sandbox.stub(Gerrit, '_setPluginsCount');
|
||||
element._loadPlugins([]);
|
||||
assert.isTrue(Gerrit._setPluginsCount.calledWithExactly(0));
|
||||
test('passes config to gr-plugin-host', function(done) {
|
||||
var config = {plugin: {}};
|
||||
element.$.restAPI.getConfig.restore();
|
||||
sandbox.stub(
|
||||
element.$.restAPI, 'getConfig').returns(Promise.resolve(config));
|
||||
element.attached();
|
||||
flush(function() {
|
||||
assert.strictEqual(element.$.plugins.config, config.plugin);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
<link rel="import" href="../../../bower_components/polymer/polymer.html">
|
||||
<link rel="import" href="../../shared/gr-js-api-interface/gr-js-api-interface.html">
|
||||
|
||||
<dom-module id="gr-plugin-host">
|
||||
<script src="gr-plugin-host.js"></script>
|
||||
</dom-module>
|
||||
@@ -0,0 +1,52 @@
|
||||
// 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() {
|
||||
'use strict';
|
||||
|
||||
Polymer({
|
||||
is: 'gr-plugin-host',
|
||||
|
||||
properties: {
|
||||
config: {
|
||||
type: Object,
|
||||
observer: "_configChanged",
|
||||
},
|
||||
},
|
||||
|
||||
_configChanged: function(config) {
|
||||
var jsPlugins = config.js_resource_paths || [];
|
||||
var htmlPlugins = config.html_resource_paths || [];
|
||||
Gerrit._setPluginsCount(jsPlugins.length + htmlPlugins.length);
|
||||
this._loadJsPlugins(jsPlugins);
|
||||
this._importHtmlPlugins(htmlPlugins);
|
||||
},
|
||||
|
||||
_importHtmlPlugins: function(plugins) {
|
||||
plugins.forEach(function(url) {
|
||||
this.importHref('/' + url, null, Gerrit._pluginInstalled, true);
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
_loadJsPlugins: function(plugins) {
|
||||
for (var i = 0; i < plugins.length; i++) {
|
||||
var url = plugins[i];
|
||||
var scriptEl = document.createElement('script');
|
||||
scriptEl.defer = true;
|
||||
scriptEl.src = '/' + plugins[i];
|
||||
scriptEl.onerror = Gerrit._pluginInstalled;
|
||||
document.body.appendChild(scriptEl);
|
||||
}
|
||||
},
|
||||
});
|
||||
})();
|
||||
@@ -0,0 +1,67 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
|
||||
<title>gr-plugin-host</title>
|
||||
|
||||
<script src="../../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
|
||||
<script src="../../../bower_components/web-component-tester/browser.js"></script>
|
||||
|
||||
<link rel="import" href="gr-plugin-host.html">
|
||||
|
||||
<test-fixture id="basic">
|
||||
<template>
|
||||
<gr-plugin-host></gr-plugin-host>
|
||||
</template>
|
||||
</test-fixture>
|
||||
|
||||
<script>
|
||||
suite('gr-diff tests', function() {
|
||||
var element;
|
||||
var sandbox;
|
||||
|
||||
setup(function() {
|
||||
element = fixture('basic');
|
||||
sandbox = sinon.sandbox.create();
|
||||
sandbox.stub(document.body, 'appendChild');
|
||||
sandbox.stub(element, 'importHref');
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
test('counts plugins', function() {
|
||||
sandbox.stub(Gerrit, '_setPluginsCount');
|
||||
element.config = {
|
||||
html_resource_paths: ['foo/bar', 'baz'],
|
||||
js_resource_paths: ['42'],
|
||||
};
|
||||
assert.isTrue(Gerrit._setPluginsCount.calledWith(3));
|
||||
});
|
||||
|
||||
test('imports html plugins from config', function() {
|
||||
element.config = {
|
||||
html_resource_paths: ['foo/bar', 'baz'],
|
||||
};
|
||||
assert.isTrue(element.importHref.calledWith(
|
||||
'/foo/bar', null, Gerrit._pluginInstalled, true));
|
||||
assert.isTrue(element.importHref.calledWith(
|
||||
'/baz', null, Gerrit._pluginInstalled, true));
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -107,7 +107,8 @@
|
||||
}
|
||||
|
||||
// TODO(andybons): Polyfill currentScript for IE10/11 (edge supports it).
|
||||
var src = opt_src || (document.currentScript && document.currentScript.src);
|
||||
var src = opt_src || (document.currentScript &&
|
||||
document.currentScript.src || document.currentScript.baseURI);
|
||||
var plugin = new Plugin(src);
|
||||
try {
|
||||
callback(plugin);
|
||||
|
||||
@@ -37,9 +37,9 @@ limitations under the License.
|
||||
'change/gr-change-view/gr-change-view_test.html',
|
||||
'change/gr-comment-list/gr-comment-list_test.html',
|
||||
'change/gr-commit-info/gr-commit-info_test.html',
|
||||
'change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_test.html',
|
||||
'change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.html',
|
||||
'change/gr-confirm-revert-dialog/gr-confirm-revert-dialog_test.html',
|
||||
'change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_test.html',
|
||||
'change/gr-download-dialog/gr-download-dialog_test.html',
|
||||
'change/gr-file-list/gr-file-list_test.html',
|
||||
'change/gr-message/gr-message_test.html',
|
||||
@@ -53,8 +53,8 @@ limitations under the License.
|
||||
'core/gr-reporting/gr-reporting_test.html',
|
||||
'core/gr-search-bar/gr-search-bar_test.html',
|
||||
'diff/gr-diff-builder/gr-diff-builder_test.html',
|
||||
'diff/gr-diff-comment-thread/gr-diff-comment-thread_test.html',
|
||||
'diff/gr-diff-comment-thread-group/gr-diff-comment-thread-group_test.html',
|
||||
'diff/gr-diff-comment-thread/gr-diff-comment-thread_test.html',
|
||||
'diff/gr-diff-comment/gr-diff-comment_test.html',
|
||||
'diff/gr-diff-cursor/gr-diff-cursor_test.html',
|
||||
'diff/gr-diff-highlight/gr-annotation_test.html',
|
||||
@@ -71,6 +71,7 @@ limitations under the License.
|
||||
'diff/gr-syntax-layer/gr-syntax-layer_test.html',
|
||||
'diff/gr-syntax-lib-loader/gr-syntax-lib-loader_test.html',
|
||||
'gr-app_test.html',
|
||||
'plugins/gr-plugin-host/gr-plugin-host_test.html',
|
||||
'settings/gr-account-info/gr-account-info_test.html',
|
||||
'settings/gr-change-table-editor/gr-change-table-editor_test.html',
|
||||
'settings/gr-email-editor/gr-email-editor_test.html',
|
||||
|
||||
Reference in New Issue
Block a user