Allow plugins to extend Gerrit screens with GWT controls

Gerrit screens can now define extension points where plugins can add
GWT panels with custom controls. For now only 2 extension points are
provided, one on change screen below the change info block and one on
the user profile screen below the grid with the profile data.

Gerrit screens can also pass some values to the plugin panels, e.g.
the change screen passes the numeric change ID to the plugin panels.

Plugins must register extension panels early at plugin module load.
The way how extension panels are registered and embedded in Gerrit
screens is similar to how plugins can add custom screens.

Errors on loading the extension panels are ignored and logged to the
browser console so that in case of errors the Gerrit screen still
loads without the extension panels.

In the cookbook plugin, examples for both UI extension points were
implemented.

Change-Id: I5e0c93b2ab0cd130dd35ee8b32c37db6ba251d48
Signed-off-by: Edwin Kempin <edwin.kempin@sap.com>
This commit is contained in:
Edwin Kempin
2015-07-07 16:37:50 +02:00
parent 964ea9331c
commit 52f79ac87f
12 changed files with 374 additions and 1 deletions

View File

@@ -19,6 +19,8 @@ import static com.google.gerrit.client.FormatUtil.mediumFormat;
import com.google.gerrit.client.AvatarImage;
import com.google.gerrit.client.FormatUtil;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.GerritUiExtensionPoint;
import com.google.gerrit.client.api.ExtensionPanel;
import com.google.gerrit.client.rpc.NativeString;
import com.google.gerrit.client.rpc.RestApi;
import com.google.gerrit.reviewdb.client.Account;
@@ -86,6 +88,7 @@ public class MyProfileScreen extends SettingsScreen {
@Override
protected void onLoad() {
super.onLoad();
add(new ExtensionPanel(GerritUiExtensionPoint.PROFILE_SCREEN_BOTTOM));
display(Gerrit.getUserAccount());
display();
}

View File

@@ -35,11 +35,13 @@ public class ApiGlue {
private static native void init0() /*-{
var serverUrl = @com.google.gwt.core.client.GWT::getHostPageBaseURL()();
var ScreenDefinition = @com.google.gerrit.client.api.ExtensionScreen.Definition::TYPE;
var PanelDefinition = @com.google.gerrit.client.api.ExtensionPanel.Definition::TYPE;
$wnd.Gerrit = {
JsonString: @com.google.gerrit.client.rpc.NativeString::TYPE,
events: {},
plugins: {},
screens: {},
panels: {},
change_actions: {},
edit_actions: {},
revision_actions: {},
@@ -85,6 +87,11 @@ public class ApiGlue {
var s = new ScreenDefinition(r,c);
(this.screens[p] || (this.screens[p]=[])).push(s);
},
panel: function(i,c){this._panel(this.getPluginName(),i,c)},
_panel: function(n,i,c){
var p = new PanelDefinition(n,c);
(this.panels[i] || (this.panels[i]=[])).push(p);
},
url: function (d) {
if (d && d.length > 0)

View File

@@ -0,0 +1,149 @@
// Copyright (C) 2015 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.
package com.google.gerrit.client.api;
import com.google.gerrit.client.GerritUiExtensionPoint;
import com.google.gerrit.client.rpc.Natives;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.dom.client.Element;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.SimplePanel;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
public class ExtensionPanel extends FlowPanel {
private static final Logger logger =
Logger.getLogger(ExtensionPanel.class.getName());
private final GerritUiExtensionPoint extensionPoint;
private final List<Context> contexts;
public ExtensionPanel(GerritUiExtensionPoint extensionPoint) {
this.extensionPoint = extensionPoint;
this.contexts = create();
}
private List<Context> create() {
List<Context> contexts = new ArrayList<>();
for (Definition def : Natives.asList(Definition.get(extensionPoint.name()))) {
SimplePanel p = new SimplePanel();
add(p);
contexts.add(Context.create(def, p));
}
return contexts;
}
public void put(GerritUiExtensionPoint.Key key, String value) {
for (Context ctx : contexts) {
ctx.put(key.name(), value);
}
}
public void putInt(GerritUiExtensionPoint.Key key, int value) {
for (Context ctx : contexts) {
ctx.putInt(key.name(), value);
}
}
public void putBoolean(GerritUiExtensionPoint.Key key, boolean value) {
for (Context ctx : contexts) {
ctx.putBoolean(key.name(), value);
}
}
@Override
protected void onLoad() {
super.onLoad();
for (Context ctx : contexts) {
try {
ctx.onLoad();
} catch (RuntimeException e) {
logger.log(Level.SEVERE,
"Failed to load extension panel for extension point "
+ extensionPoint.name() + " from plugin " + ctx.getPluginName()
+ ": " + e.getMessage());
}
}
}
@Override
protected void onUnload() {
super.onUnload();
for (Context ctx : contexts) {
for (JavaScriptObject u : Natives.asList(ctx.unload())) {
ApiGlue.invoke(u);
}
}
}
static class Definition extends JavaScriptObject {
static final JavaScriptObject TYPE = init();
private static native JavaScriptObject init() /*-{
function PanelDefinition(n, c) {
this.pluginName = n;
this.onLoad = c;
};
return PanelDefinition;
}-*/;
static native JsArray<Definition> get(String i)
/*-{ return $wnd.Gerrit.panels[i] || [] }-*/;
protected Definition() {
}
}
static class Context extends JavaScriptObject {
static final Context create(
Definition def,
SimplePanel panel) {
return create(TYPE, def, panel.getElement());
}
final native void onLoad() /*-{ this._d.onLoad(this) }-*/;
final native JsArray<JavaScriptObject> unload() /*-{ return this._u }-*/;
final native String getPluginName() /*-{ return this._d.pluginName; }-*/;
final native void put(String k, String v) /*-{ this.p[k] = v; }-*/;
final native void putInt(String k, int v) /*-{ this.p[k] = v; }-*/;
final native void putBoolean(String k, boolean v) /*-{ this.p[k] = v; }-*/;
private static final native Context create(
JavaScriptObject T,
Definition d,
Element e)
/*-{ return new T(d,e) }-*/;
private static final JavaScriptObject TYPE = init();
private static final native JavaScriptObject init() /*-{
var T = function(d,e) {
this._d = d;
this._u = [];
this.body = e;
this.p = {};
};
T.prototype = {
onUnload: function(f){this._u.push(f)},
};
return T;
}-*/;
protected Context() {
}
}
}

View File

@@ -59,6 +59,7 @@ final class Plugin extends JavaScriptObject {
on: function(e,f){G.on(e,f)},
onAction: function(t,n,c){G._onAction(this.name,t,n,c)},
screen: function(p,c){G._screen(this.name,p,c)},
panel: function(i,c){G._panel(this.name,i,c)},
url: function (u){return G.url(this._url(u))},
get: function(u,b){@com.google.gerrit.client.api.ActionContext::get(

View File

@@ -18,9 +18,11 @@ import com.google.gerrit.client.AvatarImage;
import com.google.gerrit.client.ErrorDialog;
import com.google.gerrit.client.FormatUtil;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.GerritUiExtensionPoint;
import com.google.gerrit.client.account.AccountInfo.AvatarInfo;
import com.google.gerrit.client.actions.ActionInfo;
import com.google.gerrit.client.api.ChangeGlue;
import com.google.gerrit.client.api.ExtensionPanel;
import com.google.gerrit.client.changes.ChangeApi;
import com.google.gerrit.client.changes.ChangeInfo;
import com.google.gerrit.client.changes.ChangeInfo.CommitInfo;
@@ -85,6 +87,7 @@ import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HTMLPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.ToggleButton;
import com.google.gwtexpui.globalkey.client.GlobalKey;
import com.google.gwtexpui.globalkey.client.KeyCommand;
@@ -167,6 +170,7 @@ public class ChangeScreen extends Screen {
@UiField Topic topic;
@UiField Element actionText;
@UiField Element actionDate;
@UiField SimplePanel changeExtension;
@UiField Actions actions;
@UiField Labels labels;
@@ -222,6 +226,10 @@ public class ChangeScreen extends Screen {
@Override
protected void onLoad() {
super.onLoad();
ExtensionPanel extensionPanel =
new ExtensionPanel(GerritUiExtensionPoint.CHANGE_SCREEN_BELOW_CHANGE_INFO_BLOCK);
extensionPanel.putInt(GerritUiExtensionPoint.Key.CHANGE_ID, changeId.get());
changeExtension.add(extensionPanel);
CallbackGroup group = new CallbackGroup();
if (Gerrit.isSignedIn()) {
ChangeList.query("change:" + changeId.get() + " has:draft",

View File

@@ -322,6 +322,10 @@ limitations under the License.
height: 16px !important;
vertical-align: bottom;
}
.changeExtension {
padding-top: 5px;
}
</ui:style>
<g:HTMLPanel styleName='{style.cs2}'>
@@ -472,6 +476,7 @@ limitations under the License.
</table>
<hr/>
<c:Labels ui:field='labels' styleName='{style.labels}'/>
<g:SimplePanel ui:field='changeExtension' styleName='{style.changeExtension}'/>
<div id='change_plugins'/>
</td>
<td class='{style.relatedColumn}'>