From ce344d76aa415c0ae7e293783f288afdbda12ce9 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Fri, 12 Jul 2013 22:00:30 -0700 Subject: [PATCH 01/10] Add NativeMap.create() as a utility Simplify creation of any type of map in caller code. Change-Id: I5a491cc689003db361c8c0cc8c0de1a8675e6298 --- .../src/main/java/com/google/gerrit/client/rpc/NativeMap.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/NativeMap.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/NativeMap.java index a971c51eff..2e407d419d 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/NativeMap.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/NativeMap.java @@ -22,6 +22,10 @@ import java.util.Set; /** A map of native JSON objects, keyed by a string. */ public class NativeMap extends JavaScriptObject { + public static NativeMap create() { + return createObject().cast(); + } + /** * Loop through the result map's entries and copy the key strings into the * "name" property of the corresponding child object. This only runs on the From 8bb7acab4f72d3e2bfb8d800fb2f715e1d1009d2 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Fri, 12 Jul 2013 22:01:56 -0700 Subject: [PATCH 02/10] Extract ReviewInput to top level class Permit reusing this type in the new ChangeScreen2. Simplify creation by moving init() into create(). Change-Id: I143884320390a5a99ac58f6140bffb311bd7d3df --- .../client/changes/PublishCommentScreen.java | 19 ---------- .../gerrit/client/changes/ReviewInput.java | 37 +++++++++++++++++++ 2 files changed, 37 insertions(+), 19 deletions(-) create mode 100644 gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ReviewInput.java diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PublishCommentScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PublishCommentScreen.java index 66bd470481..608a2c720b 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PublishCommentScreen.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PublishCommentScreen.java @@ -36,7 +36,6 @@ import com.google.gerrit.reviewdb.client.Change; import com.google.gerrit.reviewdb.client.Patch; import com.google.gerrit.reviewdb.client.PatchLineComment; import com.google.gerrit.reviewdb.client.PatchSet; -import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.core.client.JsArrayString; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; @@ -402,7 +401,6 @@ public class PublishCommentScreen extends AccountScreen implements private void onSend2(final boolean submit) { ReviewInput data = ReviewInput.create(); data.message(ChangeApi.emptyToNull(message.getText().trim())); - data.init(); for (final ValueRadioButton b : approvalButtons) { if (b.getValue()) { data.label(b.label.name(), b.parseValue()); @@ -432,23 +430,6 @@ public class PublishCommentScreen extends AccountScreen implements }); } - private static class ReviewInput extends JavaScriptObject { - static ReviewInput create() { - return (ReviewInput) createObject(); - } - - final native void message(String m) /*-{ if(m)this.message=m; }-*/; - final native void label(String n, short v) /*-{ this.labels[n]=v; }-*/; - final native void init() /*-{ - this.labels = {}; - this.strict_labels = true; - this.drafts = 'PUBLISH'; - }-*/; - - protected ReviewInput() { - } - } - private void submit() { ChangeApi.submit(patchSetId.getParentKey().get(), revision, new GerritCallback() { diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ReviewInput.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ReviewInput.java new file mode 100644 index 0000000000..ccc4e21c6f --- /dev/null +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ReviewInput.java @@ -0,0 +1,37 @@ +// Copyright (C) 2013 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.changes; + +import com.google.gwt.core.client.JavaScriptObject; + +public class ReviewInput extends JavaScriptObject { + public static ReviewInput create() { + ReviewInput r = createObject().cast(); + r.init(); + return r; + } + + public final native void message(String m) /*-{ if(m)this.message=m; }-*/; + public final native void label(String n, short v) /*-{ this.labels[n]=v; }-*/; + + private final native void init() /*-{ + this.labels = {}; + this.strict_labels = true; + this.drafts = 'PUBLISH'; + }-*/; + + protected ReviewInput() { + } +} From 32bd638bbe25c58262e0f19fdb438422709fa732 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Fri, 12 Jul 2013 22:03:00 -0700 Subject: [PATCH 03/10] Add KeyDownHandler for escape to close popups Chrome doesn't seem to deliver escape to KeyPressHandler. Register a KeyDownHandler to also catch escape and close the currently open popup. Change-Id: I0b95f3b6a5c3dc07d04a9076681971819c52a9ba --- .../google/gwtexpui/globalkey/client/GlobalKey.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/GlobalKey.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/GlobalKey.java index 1eaaa3cfdc..daf5d61394 100644 --- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/GlobalKey.java +++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/GlobalKey.java @@ -15,6 +15,8 @@ package com.google.gwtexpui.globalkey.client; import com.google.gwt.event.dom.client.KeyCodes; +import com.google.gwt.event.dom.client.KeyDownEvent; +import com.google.gwt.event.dom.client.KeyDownHandler; import com.google.gwt.event.dom.client.KeyPressEvent; import com.google.gwt.event.dom.client.KeyPressHandler; import com.google.gwt.event.logical.shared.CloseEvent; @@ -92,6 +94,14 @@ public class GlobalKey { active = new State(panel); active.add(new HidePopupPanelCommand(0, KeyCodes.KEY_ESCAPE, panel)); panel.addCloseHandler(restoreGlobal); + panel.addDomHandler(new KeyDownHandler() { + @Override + public void onKeyDown(KeyDownEvent event) { + if (event.getNativeKeyCode() == KeyCodes.KEY_ESCAPE) { + panel.hide(); + } + } + }, KeyDownEvent.getType()); } public static HandlerRegistration addApplication(final Widget widget, From ca8bc3d7a57024c12a287526e944ea24e8d3d5f3 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Fri, 12 Jul 2013 22:03:51 -0700 Subject: [PATCH 04/10] Add ChangeApi method to invoke rebase Simple utility to send a request to rebase the specified commit. Change-Id: I7be51ec7d59724b409039cd9c11b8c351450f594 --- .../java/com/google/gerrit/client/changes/ChangeApi.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeApi.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeApi.java index 2ab1a8a3eb..cb80cd8268 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeApi.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeApi.java @@ -100,6 +100,12 @@ public class ChangeApi { call(id, commit, "submit").post(in, cb); } + /** Rebase a revision onto the branch tip. */ + public static void rebase(int id, String commit, AsyncCallback cb) { + JavaScriptObject in = JavaScriptObject.createObject(); + call(id, commit, "rebase").post(in, cb); + } + private static class Input extends JavaScriptObject { final native void topic(String t) /*-{ if(t)this.topic=t; }-*/; final native void message(String m) /*-{ if(m)this.message=m; }-*/; From a22e50c292f62437750c3f8683851f07c03c7f32 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Fri, 12 Jul 2013 22:05:01 -0700 Subject: [PATCH 05/10] Support ListChangesOption in ChangeApi.detail() calls ChangeScreen2 needs to pass options to get the information it wants in as few API requests as possible. Change-Id: Iddea40878b0f14f5b36f9bb83804a44ea4637d82 --- .../gerrit/client/changes/ChangeApi.java | 18 +++++++++++++++++- .../gerrit/client/changes/ChangeList.java | 2 +- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeApi.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeApi.java index cb80cd8268..283c1532cc 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeApi.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeApi.java @@ -16,10 +16,13 @@ package com.google.gerrit.client.changes; import com.google.gerrit.client.rpc.NativeString; import com.google.gerrit.client.rpc.RestApi; +import com.google.gerrit.common.changes.ListChangesOption; import com.google.gerrit.reviewdb.client.PatchSet; import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.user.client.rpc.AsyncCallback; +import java.util.EnumSet; + /** * A collection of static methods which work on the Gerrit REST API for specific * changes. @@ -62,7 +65,20 @@ public class ChangeApi { } public static void detail(int id, AsyncCallback cb) { - call(id, "detail").get(cb); + detail(id).get(cb); + } + + public static void detail(int id, EnumSet options, + AsyncCallback cb) { + RestApi call = detail(id); + if (!options.isEmpty()) { + ChangeList.addOptions(call, options); + } + call.get(cb); + } + + private static RestApi detail(int id) { + return call(id, "detail"); } public static RestApi revision(int id, String revision) { diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeList.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeList.java index ab53f5b1db..fea117f7eb 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeList.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeList.java @@ -66,7 +66,7 @@ public class ChangeList extends JsArray { call.get(callback); } - static void addOptions(RestApi call, EnumSet s) { + public static void addOptions(RestApi call, EnumSet s) { call.addParameterRaw("O", Integer.toHexString(ListChangesOption.toBits(s))); } From 203b41f43cc300e8338953e78271f901c0a6743d Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Sat, 13 Jul 2013 14:48:59 -0700 Subject: [PATCH 06/10] Fix visible length of CopyableLabel on Change-Id The visible length should be the length of the value string being copied, not the length of the preview text. The preview text can be shorter, like on the ChangeScreen where its Ideadbeefc0ffee... but the copied value is longer with "Change-Id: Ideadbeefc0ffee...". Change-Id: I20bce725734cb48b6c6d9e33b4e352ed0b368361 --- .../java/com/google/gwtexpui/clippy/client/CopyableLabel.java | 1 - 1 file changed, 1 deletion(-) diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/client/CopyableLabel.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/client/CopyableLabel.java index a9296ebf1c..7a5f44f4ca 100644 --- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/client/CopyableLabel.java +++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/client/CopyableLabel.java @@ -117,7 +117,6 @@ public class CopyableLabel extends Composite implements HasText { public void setPreviewText(final String text) { if (textLabel != null) { textLabel.setText(text); - visibleLen = text.length(); } } From 1bfbe4e38e3c78c6bca39f934a97b754e930e83c Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Sat, 13 Jul 2013 14:45:49 -0700 Subject: [PATCH 07/10] Add no-arg constructor for CopyableLabel This constructor makes it easy to include CopyableLabel in UiBinder XML code and populate the strings at runtime. Change-Id: Ia2c839bd9e4ef004e2f7ee03050391aa0e4b245e --- .../java/com/google/gwtexpui/clippy/client/CopyableLabel.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/client/CopyableLabel.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/client/CopyableLabel.java index 7a5f44f4ca..242caedeb6 100644 --- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/client/CopyableLabel.java +++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/client/CopyableLabel.java @@ -71,6 +71,10 @@ public class CopyableLabel extends Composite implements HasText { private TextBox textBox; private Element swf; + public CopyableLabel() { + this(""); + } + /** * Create a new label * From e65895a04229754348ea6ff6b025dc05cd80079b Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Sat, 13 Jul 2013 14:49:27 -0700 Subject: [PATCH 08/10] Make parts of SubmitInfo and SubmitFailureDialog public This permits use from the new ChangeScreen2, which is currently planned to go in a different package. Change-Id: I53b7bf097caed99ff74c89d64507bd38fd542cf5 --- .../google/gerrit/client/changes/SubmitFailureDialog.java | 6 +++--- .../java/com/google/gerrit/client/changes/SubmitInfo.java | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/SubmitFailureDialog.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/SubmitFailureDialog.java index 70bf4b6bbf..bd97790e85 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/SubmitFailureDialog.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/SubmitFailureDialog.java @@ -18,13 +18,13 @@ import com.google.gerrit.client.ErrorDialog; import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder; import com.google.gwtjsonrpc.client.RemoteJsonException; -class SubmitFailureDialog extends ErrorDialog { - static boolean isConflict(Throwable err) { +public class SubmitFailureDialog extends ErrorDialog { + public static boolean isConflict(Throwable err) { return err instanceof RemoteJsonException && 409 == ((RemoteJsonException) err).getCode(); } - SubmitFailureDialog(String msg) { + public SubmitFailureDialog(String msg) { super(new SafeHtmlBuilder().append(msg.trim()).wikify()); setText(Util.C.submitFailed()); } diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/SubmitInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/SubmitInfo.java index a9206b740f..6d8bc308db 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/SubmitInfo.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/SubmitInfo.java @@ -17,7 +17,7 @@ package com.google.gerrit.client.changes; import com.google.gerrit.reviewdb.client.Change; import com.google.gwt.core.client.JavaScriptObject; -class SubmitInfo extends JavaScriptObject { +public class SubmitInfo extends JavaScriptObject { final Change.Status status() { return Change.Status.valueOf(statusRaw()); } From f27e698226fca0bcfa186f83542e68f685cb73f2 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Sat, 13 Jul 2013 15:08:48 -0700 Subject: [PATCH 09/10] Automatically refresh GWT UI on each page load This came out of frustration while working on the ChangeScreen2 work. For some types of UI work it can be easier to leave the Jetty server running and simply click reload in the browser to recompile the GWT code and load the new JavaScript. GWT Jetty ------------- server startup 6s 1s initial request 20s 39s no-op reload 10s 7s The real win comes from changing class structure in the GWT UI code. None of the UI classes are loaded into the Jetty server so there are no class schema compatibility issues. In the hosted mode debugger the developer must exit the debugger and restart it, bringing the edit-test cycle to significantly longer than the time it takes to run Buck. Another benefit is testing runs with a real browser, which can show different results in JSNI code than in the hosted mode debugger. Events and navigation is faster too, thanks to everything running natively in the browser. Overall this can make the UI development experience much less frustrating. Change-Id: Ib4a4ff547f60dd10ae32aaacc07f9c84b848cfdc --- .../gerrit/pgm/http/jetty/JettyServer.java | 72 +++++++++++++++---- 1 file changed, 59 insertions(+), 13 deletions(-) diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java index ddc12c8eb9..e167605d77 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java @@ -77,6 +77,12 @@ import java.util.zip.ZipFile; import javax.servlet.DispatcherType; import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; @Singleton public class JettyServer { @@ -343,7 +349,7 @@ public class JettyServer { // need to unpack them into yet another temporary directory prior to // serving to clients. // - app.setBaseResource(getBaseResource()); + app.setBaseResource(getBaseResource(app)); // HTTP front-end filter to be used as surrogate of Apache HTTP // reverse-proxy filtering. @@ -397,13 +403,14 @@ public class JettyServer { return app; } - private Resource getBaseResource() throws IOException { + private Resource getBaseResource(ServletContextHandler app) + throws IOException { if (baseResource == null) { try { baseResource = unpackWar(GerritLauncher.getDistributionArchive()); } catch (FileNotFoundException err) { if (err.getMessage() == GerritLauncher.NOT_ARCHIVED) { - baseResource = useDeveloperBuild(); + baseResource = useDeveloperBuild(app); } else { throw err; } @@ -412,7 +419,13 @@ public class JettyServer { return baseResource; } - private Resource unpackWar(File srcwar) throws IOException { + private static Resource unpackWar(File srcwar) throws IOException { + File dstwar = makeWarTempDir(); + unpack(srcwar, dstwar); + return Resource.newResource(dstwar.toURI()); + } + + private static File makeWarTempDir() throws IOException { // Obtain our local temporary directory, but it comes back as a file // so we have to switch it to be a directory post creation. // @@ -425,11 +438,13 @@ public class JettyServer { // a security feature. Try to resolve out any symlinks in the path. // try { - dstwar = dstwar.getCanonicalFile(); + return dstwar.getCanonicalFile(); } catch (IOException e) { - dstwar = dstwar.getAbsoluteFile(); + return dstwar.getAbsoluteFile(); } + } + private static void unpack(File srcwar, File dstwar) throws IOException { final ZipFile zf = new ZipFile(srcwar); try { final Enumeration e = zf.entries(); @@ -466,11 +481,9 @@ public class JettyServer { } finally { zf.close(); } - - return Resource.newResource(dstwar.toURI()); } - private void mkdir(final File dir) throws IOException { + private static void mkdir(File dir) throws IOException { if (!dir.isDirectory()) { mkdir(dir.getParentFile()); if (!dir.mkdir()) @@ -479,7 +492,8 @@ public class JettyServer { } } - private Resource useDeveloperBuild() throws IOException { + private Resource useDeveloperBuild(ServletContextHandler app) + throws IOException { // Find ourselves in the CLASSPATH. We should be a loose class file. // URL u = getClass().getResource(getClass().getSimpleName() + ".class"); @@ -510,12 +524,44 @@ public class JettyServer { dir = dir.getParentFile(); // pop classes if ("buck-out".equals(dir.getName())) { + final File dstwar = makeWarTempDir(); String pkg = "gerrit-gwtui"; String target = targetForBrowser(System.getProperty("gerrit.browser")); - File gen = new File(dir, "gen"); + final File gen = new File(dir, "gen"); String out = new File(new File(gen, pkg), target).getAbsolutePath(); - build(dir.getParentFile(), gen, "//" + pkg + ":" + target); - return unpackWar(new File(out + ".zip")); + final File zip = new File(out + ".zip"); + final File root = dir.getParentFile(); + final String name = "//" + pkg + ":" + target; + + File ui = new File(dstwar, "gerrit_ui"); + File p = new File(ui, "permutations"); + mkdir(ui); + p.createNewFile(); + p.deleteOnExit(); + + app.addFilter(new FilterHolder(new Filter() { + private long last; + + @Override + public void doFilter(ServletRequest request, ServletResponse res, + FilterChain chain) throws IOException, ServletException { + HttpServletRequest req = (HttpServletRequest) request; + build(root, gen, name); + if (last != zip.lastModified()) { + last = zip.lastModified(); + unpack(zip, dstwar); + } + chain.doFilter(req, res); + } + + @Override + public void init(FilterConfig config) { + } + @Override + public void destroy() { + } + }), "/", EnumSet.of(DispatcherType.REQUEST)); + return Resource.newResource(dstwar.toURI()); } else if ("target".equals(dir.getName())) { return useMavenDeveloperBuild(dir); } else { From 12e515954053a3655f8b5c5567ef70a391af6385 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Sat, 13 Jul 2013 22:08:40 -0700 Subject: [PATCH 10/10] Add change level actions map to ChangeInfo Core actions abandon, topic, restore and revert are listed based on the status of the change and the permissions granted. Change-Id: Iae50bc1a61d6b49a69dd71112e26eaa16dc11fdf --- Documentation/rest-api-changes.txt | 10 +++-- .../google/gerrit/server/change/Abandon.java | 37 +++++++++++++++++- .../gerrit/server/change/ChangeJson.java | 15 ++++++++ .../google/gerrit/server/change/PutTopic.java | 38 ++++++++++++++++++- .../google/gerrit/server/change/Restore.java | 37 +++++++++++++++++- .../google/gerrit/server/change/Revert.java | 38 ++++++++++++++++++- .../server/extensions/webui/UiCommands.java | 4 +- 7 files changed, 169 insertions(+), 10 deletions(-) diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt index d2a2784f27..e66de7eb31 100644 --- a/Documentation/rest-api-changes.txt +++ b/Documentation/rest-api-changes.txt @@ -2406,6 +2406,10 @@ Not set for merged changes. |`owner` || The owner of the change as an link:rest-api-accounts.html#account-info[ AccountInfo] entity. +|`actions` |optional| +Actions the caller might be able to perform on this revision. The +information is a map of view name to link:#action-info[ActionInfo] +entities. |`labels` |optional| The labels of the change as a map that maps the label names to link:#label-info[LabelInfo] entries. + @@ -2886,12 +2890,12 @@ The `RevisionInfo` entity contains information about a patch set. Information about how to fetch this patch set. The fetch information is provided as a map that maps the protocol name ("`git`", "`http`", "`ssh`") to link:#fetch-info[FetchInfo] entities. -|`commit` ||The commit of the patch set as +|`commit` |optional|The commit of the patch set as link:#commit-info[CommitInfo] entity. -|`files` || +|`files` |optional| The files of the patch set as a map that maps the file names to link:#file-info[FileInfo] entities. -|`actions` || +|`actions` |optional| Actions the caller might be able to perform on this revision. The information is a map of view name to link:#action-info[ActionInfo] entities. diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java index b48773f42d..388f0c7fe9 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java @@ -21,6 +21,7 @@ import com.google.gerrit.extensions.restapi.BadRequestException; import com.google.gerrit.extensions.restapi.DefaultInput; import com.google.gerrit.extensions.restapi.ResourceConflictException; import com.google.gerrit.extensions.restapi.RestModifyView; +import com.google.gerrit.extensions.webui.UiCommand; import com.google.gerrit.reviewdb.client.Change; import com.google.gerrit.reviewdb.client.ChangeMessage; import com.google.gerrit.reviewdb.server.ReviewDb; @@ -41,8 +42,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Collections; +import java.util.EnumSet; +import java.util.Set; -public class Abandon implements RestModifyView { +public class Abandon implements RestModifyView, + UiCommand { private static final Logger log = LoggerFactory.getLogger(Abandon.class); private final ChangeHooks hooks; @@ -128,6 +132,37 @@ public class Abandon implements RestModifyView { return json.format(change); } + @Override + public Set getPlaces() { + return EnumSet.of(Place.PATCHSET_ACTION_PANEL); + } + + @Override + public String getLabel(ChangeResource resource) { + return "Abandon"; + } + + @Override + public String getTitle(ChangeResource resource) { + return null; + } + + @Override + public boolean isVisible(ChangeResource resource) { + return isEnabled(resource); + } + + @Override + public boolean isEnabled(ChangeResource resource) { + return resource.getChange().getStatus().isOpen() + && resource.getControl().canAbandon(); + } + + @Override + public String getConfirmationMessage(ChangeResource resource) { + return null; + } + private ChangeMessage newMessage(Input input, IdentifiedUser caller, Change change) throws OrmException { StringBuilder msg = new StringBuilder(); diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java index a1656bf135..7a9a879be5 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java @@ -47,6 +47,8 @@ import com.google.gerrit.common.data.Permission; import com.google.gerrit.common.data.PermissionRange; import com.google.gerrit.common.data.SubmitRecord; import com.google.gerrit.common.data.UiCommandDetail; +import com.google.gerrit.extensions.registration.DynamicMap; +import com.google.gerrit.extensions.restapi.RestView; import com.google.gerrit.extensions.restapi.Url; import com.google.gerrit.extensions.webui.UiCommand; import com.google.gerrit.reviewdb.client.Account; @@ -129,6 +131,7 @@ public class ChangeJson { private final AccountInfo.Loader.Factory accountLoaderFactory; private final Provider urlProvider; private final Urls urls; + private final DynamicMap> changes; private final Revisions revisions; private ChangeControl.Factory changeControlUserFactory; @@ -150,6 +153,7 @@ public class ChangeJson { AccountInfo.Loader.Factory ailf, @CanonicalWebUrl Provider curl, Urls urls, + DynamicMap> changes, Revisions revisions) { this.db = db; this.labelNormalizer = ln; @@ -162,6 +166,7 @@ public class ChangeJson { this.accountLoaderFactory = ailf; this.urlProvider = curl; this.urls = urls; + this.changes = changes; this.revisions = revisions; options = EnumSet.noneOf(ListChangesOption.class); @@ -286,6 +291,15 @@ public class ChangeJson { } } + if (has(CURRENT_ACTIONS) && user instanceof IdentifiedUser) { + out.actions = Maps.newTreeMap(); + for (UiCommandDetail c : UiCommands.from( + changes, + new ChangeResource(control(cd)), + EnumSet.of(UiCommand.Place.PATCHSET_ACTION_PANEL))) { + out.actions.put(c.id, new ActionInfo(c)); + } + } lastControl = null; return out; } @@ -870,6 +884,7 @@ public class ChangeJson { AccountInfo owner; + Map actions; Map labels; Map> permitted_labels; Collection removable_reviewers; diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutTopic.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutTopic.java index 346d53ccd2..3fcdb0c483 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutTopic.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutTopic.java @@ -19,9 +19,10 @@ import com.google.gerrit.common.ChangeHooks; import com.google.gerrit.extensions.restapi.AuthException; import com.google.gerrit.extensions.restapi.BadRequestException; import com.google.gerrit.extensions.restapi.DefaultInput; -import com.google.gerrit.extensions.restapi.Response; import com.google.gerrit.extensions.restapi.ResourceConflictException; +import com.google.gerrit.extensions.restapi.Response; import com.google.gerrit.extensions.restapi.RestModifyView; +import com.google.gerrit.extensions.webui.UiCommand; import com.google.gerrit.reviewdb.client.Change; import com.google.gerrit.reviewdb.client.ChangeMessage; import com.google.gerrit.reviewdb.server.ReviewDb; @@ -35,8 +36,11 @@ import com.google.inject.Inject; import com.google.inject.Provider; import java.util.Collections; +import java.util.EnumSet; +import java.util.Set; -class PutTopic implements RestModifyView { +class PutTopic implements RestModifyView, + UiCommand { private final Provider dbProvider; private final ChangeIndexer indexer; private final ChangeHooks hooks; @@ -113,4 +117,34 @@ class PutTopic implements RestModifyView { ? Response.none() : newTopicName; } + + @Override + public Set getPlaces() { + return EnumSet.of(Place.PATCHSET_ACTION_PANEL); + } + + @Override + public String getLabel(ChangeResource resource) { + return "Edit Topic"; + } + + @Override + public String getTitle(ChangeResource resource) { + return null; + } + + @Override + public boolean isVisible(ChangeResource resource) { + return isEnabled(resource); + } + + @Override + public boolean isEnabled(ChangeResource resource) { + return resource.getControl().canEditTopicName(); + } + + @Override + public String getConfirmationMessage(ChangeResource resource) { + return null; + } } diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java index c5dc2b4066..874b4991da 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java @@ -20,6 +20,7 @@ import com.google.gerrit.extensions.restapi.AuthException; import com.google.gerrit.extensions.restapi.DefaultInput; import com.google.gerrit.extensions.restapi.ResourceConflictException; import com.google.gerrit.extensions.restapi.RestModifyView; +import com.google.gerrit.extensions.webui.UiCommand; import com.google.gerrit.reviewdb.client.Change; import com.google.gerrit.reviewdb.client.Change.Status; import com.google.gerrit.reviewdb.client.ChangeMessage; @@ -41,8 +42,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Collections; +import java.util.EnumSet; +import java.util.Set; -public class Restore implements RestModifyView { +public class Restore implements RestModifyView, + UiCommand { private static final Logger log = LoggerFactory.getLogger(Restore.class); private final ChangeHooks hooks; @@ -127,6 +131,37 @@ public class Restore implements RestModifyView { return json.format(change); } + @Override + public Set getPlaces() { + return EnumSet.of(Place.PATCHSET_ACTION_PANEL); + } + + @Override + public String getLabel(ChangeResource resource) { + return "Restore"; + } + + @Override + public String getTitle(ChangeResource resource) { + return null; + } + + @Override + public boolean isVisible(ChangeResource resource) { + return isEnabled(resource); + } + + @Override + public boolean isEnabled(ChangeResource resource) { + return resource.getChange().getStatus() == Change.Status.ABANDONED + && resource.getControl().canRestore(); + } + + @Override + public String getConfirmationMessage(ChangeResource resource) { + return null; + } + private ChangeMessage newMessage(Input input, IdentifiedUser caller, Change change) throws OrmException { StringBuilder msg = new StringBuilder(); diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java index a2f0a9b48b..efc6beacbc 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java @@ -20,6 +20,7 @@ import com.google.gerrit.extensions.restapi.AuthException; import com.google.gerrit.extensions.restapi.BadRequestException; import com.google.gerrit.extensions.restapi.ResourceConflictException; import com.google.gerrit.extensions.restapi.RestModifyView; +import com.google.gerrit.extensions.webui.UiCommand; import com.google.gerrit.reviewdb.client.Change; import com.google.gerrit.reviewdb.client.Change.Status; import com.google.gerrit.reviewdb.server.ReviewDb; @@ -40,7 +41,11 @@ import com.google.inject.Provider; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.Repository; -public class Revert implements RestModifyView { +import java.util.EnumSet; +import java.util.Set; + +public class Revert implements RestModifyView, + UiCommand { private final ChangeHooks hooks; private final RevertedSender.Factory revertedSenderFactory; private final CommitValidators.Factory commitValidatorsFactory; @@ -107,6 +112,37 @@ public class Revert implements RestModifyView { } } + @Override + public Set getPlaces() { + return EnumSet.of(Place.PATCHSET_ACTION_PANEL); + } + + @Override + public String getLabel(ChangeResource resource) { + return "Revert"; + } + + @Override + public String getTitle(ChangeResource resource) { + return null; + } + + @Override + public boolean isVisible(ChangeResource resource) { + return isEnabled(resource); + } + + @Override + public boolean isEnabled(ChangeResource resource) { + return resource.getChange().getStatus() == Change.Status.MERGED + && resource.getControl().getRefControl().canUpload(); + } + + @Override + public String getConfirmationMessage(ChangeResource resource) { + return null; + } + private static String status(Change change) { return change != null ? change.getStatus().name().toLowerCase() : "deleted"; } diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/webui/UiCommands.java b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/webui/UiCommands.java index 8a612d7495..b39c65f507 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/webui/UiCommands.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/webui/UiCommands.java @@ -22,7 +22,7 @@ import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.google.gerrit.common.data.UiCommandDetail; import com.google.gerrit.extensions.registration.DynamicMap; -import com.google.gerrit.extensions.restapi.ChildCollection; +import com.google.gerrit.extensions.restapi.RestCollection; import com.google.gerrit.extensions.restapi.RestResource; import com.google.gerrit.extensions.restapi.RestView; import com.google.gerrit.extensions.webui.UiCommand; @@ -71,7 +71,7 @@ public class UiCommands { } public static Iterable from( - ChildCollection collection, + RestCollection collection, R resource, EnumSet places) { return from(collection.views(), resource, places);