Enable storing of custom dashboards for projects
Custom dashboards can now be stored in the projects `refs/meta/dashboards/*` branches. A REST endpoint was added to retrieve the custom dashboards for a project. Change-Id: I1be4c4b8856f4edd279e752d5b4004f9a548bd2a Signed-off-by: Edwin Kempin <edwin.kempin@sap.com>
This commit is contained in:
@@ -396,6 +396,35 @@ default. Optional fields are:
|
||||
]
|
||||
----
|
||||
|
||||
[[dashboards]]
|
||||
/dashboards/project/ (List Dashboards)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Lists custom dashboards for a project.
|
||||
|
||||
The `/dashboards/project/` URL expects the project name as part of the
|
||||
URL.
|
||||
List all dashboards for the `myProject` project:
|
||||
----
|
||||
GET /dashboards/project/myProject?format=JSON&d HTTP/1.0
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Content-Disposition: attachment
|
||||
Content-Type: application/json;charset=UTF-8
|
||||
|
||||
)]}'
|
||||
{
|
||||
"refs/meta/dashboards/main:MyDashboard": {
|
||||
"kind": "gerritcodereview#dashboard",
|
||||
"id" : "refs/meta/dashboards/main:MyDashboard",
|
||||
"dashboard_name": "MyDashboard",
|
||||
"ref_name": "refs/meta/dashboards/main",
|
||||
"project_name": "myProject",
|
||||
"description": "Most recent open and merged changes.",
|
||||
"parameters": "title\u003dMyDashboard\u0026Open+Changes\u003dstatus:open project:myProject limit:15\u0026Merged+Changes\u003dstatus:merged project:myProject limit:15"
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
GERRIT
|
||||
------
|
||||
|
@@ -43,6 +43,40 @@ Parameters may be separated from each other using any of the following
|
||||
characters, as some users may find one more readable than another:
|
||||
`&` or `;` or `,`
|
||||
|
||||
Project Dashboards
|
||||
------------------
|
||||
|
||||
It is possible to share custom dashboards at a project level. To do
|
||||
this define the dashboards in a `refs/meta/dashboards/*` branch of the
|
||||
project. For each dashboard create a config file. The file name will be
|
||||
used as name for the dashboard.
|
||||
|
||||
Example dashboard config file `MyProject Dashboard`:
|
||||
|
||||
----
|
||||
[main]
|
||||
description = Most recent open and merged changes.
|
||||
[section "Open Changes"]
|
||||
query = status:open project:myProject limit:15
|
||||
[section "Merged Changes"]
|
||||
query = status:merged project:myProject limit:15
|
||||
----
|
||||
|
||||
Section main
|
||||
~~~~~~~~~~~~
|
||||
|
||||
main.description::
|
||||
+
|
||||
The description of the dashboard.
|
||||
|
||||
Section section
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
section.<name>.query::
|
||||
+
|
||||
The change query that should be used to populate the section with the
|
||||
given name.
|
||||
|
||||
GERRIT
|
||||
------
|
||||
Part of link:index.html[Gerrit Code Review]
|
||||
|
@@ -0,0 +1,29 @@
|
||||
// Copyright (C) 2012 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.dashboards;
|
||||
|
||||
import com.google.gwt.core.client.JavaScriptObject;
|
||||
|
||||
public class DashboardInfo extends JavaScriptObject {
|
||||
public final native String id() /*-{ return this.id; }-*/;
|
||||
public final native String name() /*-{ return this.dashboard_name; }-*/;
|
||||
public final native String refName() /*-{ return this.ref_name; }-*/;
|
||||
public final native String projectName() /*-{ return this.project_name; }-*/;
|
||||
public final native String description() /*-{ return this.description; }-*/;
|
||||
public final native String parameters() /*-{ return this.parameters; }-*/;
|
||||
|
||||
protected DashboardInfo() {
|
||||
}
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
// Copyright (C) 2012 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.dashboards;
|
||||
|
||||
import com.google.gerrit.client.rpc.NativeMap;
|
||||
import com.google.gerrit.client.rpc.RestApi;
|
||||
import com.google.gerrit.reviewdb.client.Project;
|
||||
import com.google.gwtjsonrpc.common.AsyncCallback;
|
||||
import com.google.gwt.http.client.URL;
|
||||
|
||||
/** Dashboards available from {@code /dashboards/}. */
|
||||
public class DashboardMap extends NativeMap<DashboardInfo> {
|
||||
public static void allOnProject(Project.NameKey project,
|
||||
AsyncCallback<DashboardMap> callback) {
|
||||
new RestApi("/dashboards/project/" + URL.encode(project.get()).replaceAll("[?]", "%3F"))
|
||||
.send(NativeMap.copyKeysIntoChildren(callback));
|
||||
}
|
||||
|
||||
protected DashboardMap() {
|
||||
}
|
||||
}
|
@@ -27,6 +27,7 @@ import com.google.gerrit.httpd.raw.ToolServlet;
|
||||
import com.google.gerrit.httpd.rpc.account.AccountCapabilitiesServlet;
|
||||
import com.google.gerrit.httpd.rpc.change.DeprecatedChangeQueryServlet;
|
||||
import com.google.gerrit.httpd.rpc.change.ListChangesServlet;
|
||||
import com.google.gerrit.httpd.rpc.dashboard.ListDashboardsServlet;
|
||||
import com.google.gerrit.httpd.rpc.project.ListProjectsServlet;
|
||||
import com.google.gerrit.reviewdb.client.Change;
|
||||
import com.google.gerrit.reviewdb.client.Project;
|
||||
@@ -97,6 +98,7 @@ class UrlModule extends ServletModule {
|
||||
serveRegex("^/(?:a/)?accounts/self/capabilities$").with(AccountCapabilitiesServlet.class);
|
||||
serveRegex("^/(?:a/)?changes/$").with(ListChangesServlet.class);
|
||||
serveRegex("^/(?:a/)?projects/(.*)?$").with(ListProjectsServlet.class);
|
||||
serveRegex("^/(?:a/)?dashboards/(.*)?$").with(ListDashboardsServlet.class);
|
||||
|
||||
if (cfg.deprecatedQuery) {
|
||||
serve("/query").with(DeprecatedChangeQueryServlet.class);
|
||||
|
@@ -0,0 +1,75 @@
|
||||
// Copyright (C) 2012 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.httpd.rpc.dashboard;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.gerrit.httpd.RestApiServlet;
|
||||
import com.google.gerrit.server.CurrentUser;
|
||||
import com.google.gerrit.server.OutputFormat;
|
||||
import com.google.gerrit.server.dashboard.ListDashboards;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.URLDecoder;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
@Singleton
|
||||
public class ListDashboardsServlet extends RestApiServlet {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final String PROJECT_LEVEL_PREFIX = "project/";
|
||||
private final ParameterParser paramParser;
|
||||
private final Provider<ListDashboards> factory;
|
||||
|
||||
@Inject
|
||||
ListDashboardsServlet(final Provider<CurrentUser> currentUser,
|
||||
ParameterParser paramParser, Provider<ListDashboards> ls) {
|
||||
super(currentUser);
|
||||
this.paramParser = paramParser;
|
||||
this.factory = ls;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse res)
|
||||
throws IOException {
|
||||
ListDashboards impl = factory.get();
|
||||
if (!Strings.isNullOrEmpty(req.getPathInfo())) {
|
||||
final String path = URLDecoder.decode(req.getPathInfo(), "UTF-8");
|
||||
if (path.startsWith(PROJECT_LEVEL_PREFIX)) {
|
||||
impl.setLevel(ListDashboards.Level.PROJECT);
|
||||
impl.setEntityName(path.substring(PROJECT_LEVEL_PREFIX.length()));
|
||||
}
|
||||
}
|
||||
if (acceptsJson(req)) {
|
||||
impl.setFormat(OutputFormat.JSON_COMPACT);
|
||||
}
|
||||
if (paramParser.parse(impl, req, res)) {
|
||||
ByteArrayOutputStream buf = new ByteArrayOutputStream();
|
||||
if (impl.getFormat().isJson()) {
|
||||
res.setContentType(JSON_TYPE);
|
||||
buf.write(JSON_MAGIC);
|
||||
} else {
|
||||
res.setContentType("text/plain");
|
||||
}
|
||||
impl.display(buf);
|
||||
res.setCharacterEncoding("UTF-8");
|
||||
send(req, res, buf.toByteArray());
|
||||
}
|
||||
}
|
||||
}
|
@@ -31,6 +31,7 @@ import com.google.gerrit.server.changedetail.DeleteDraftPatchSet;
|
||||
import com.google.gerrit.server.changedetail.PublishDraft;
|
||||
import com.google.gerrit.server.changedetail.RebaseChange;
|
||||
import com.google.gerrit.server.changedetail.Submit;
|
||||
import com.google.gerrit.server.dashboard.ListDashboards;
|
||||
import com.google.gerrit.server.git.AsyncReceiveCommits;
|
||||
import com.google.gerrit.server.git.BanCommit;
|
||||
import com.google.gerrit.server.git.CreateCodeReviewNotes;
|
||||
@@ -73,6 +74,7 @@ public class GerritRequestModule extends FactoryModule {
|
||||
bind(AccountResolver.class);
|
||||
bind(ChangeQueryRewriter.class);
|
||||
bind(ListProjects.class);
|
||||
bind(ListDashboards.class);
|
||||
bind(ApprovalsUtil.class);
|
||||
|
||||
bind(PerRequestProjectControlCache.class).in(RequestScoped.class);
|
||||
|
@@ -0,0 +1,231 @@
|
||||
// Copyright (C) 2012 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.server.dashboard;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.gerrit.reviewdb.client.Project;
|
||||
import com.google.gerrit.server.CurrentUser;
|
||||
import com.google.gerrit.server.OutputFormat;
|
||||
import com.google.gerrit.server.git.GitRepositoryManager;
|
||||
import com.google.gerrit.server.project.ProjectCache;
|
||||
import com.google.gerrit.server.project.ProjectControl;
|
||||
import com.google.gerrit.server.project.ProjectState;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import org.eclipse.jgit.errors.ConfigInvalidException;
|
||||
import org.eclipse.jgit.lib.Config;
|
||||
import org.eclipse.jgit.lib.ObjectLoader;
|
||||
import org.eclipse.jgit.lib.Ref;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.eclipse.jgit.revwalk.RevCommit;
|
||||
import org.eclipse.jgit.revwalk.RevTree;
|
||||
import org.eclipse.jgit.revwalk.RevWalk;
|
||||
import org.eclipse.jgit.treewalk.TreeWalk;
|
||||
import org.kohsuke.args4j.Option;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/** List projects visible to the calling user. */
|
||||
public class ListDashboards {
|
||||
private static final Logger log = LoggerFactory.getLogger(ListDashboards.class);
|
||||
private static String REFS_DASHBOARDS = "refs/meta/dashboards/";
|
||||
|
||||
public static enum Level {
|
||||
PROJECT
|
||||
};
|
||||
|
||||
private final CurrentUser currentUser;
|
||||
private final ProjectCache projectCache;
|
||||
private final GitRepositoryManager repoManager;
|
||||
|
||||
@Option(name = "--format", metaVar = "FMT", usage = "Output display format")
|
||||
private OutputFormat format = OutputFormat.JSON;
|
||||
|
||||
private Level level;
|
||||
private String entityName;
|
||||
|
||||
@Inject
|
||||
protected ListDashboards(CurrentUser currentUser, ProjectCache projectCache,
|
||||
GitRepositoryManager repoManager) {
|
||||
this.currentUser = currentUser;
|
||||
this.projectCache = projectCache;
|
||||
this.repoManager = repoManager;
|
||||
}
|
||||
|
||||
public OutputFormat getFormat() {
|
||||
return format;
|
||||
}
|
||||
|
||||
public ListDashboards setFormat(OutputFormat fmt) {
|
||||
if (!format.isJson()) {
|
||||
throw new IllegalArgumentException(format.name() + " not supported");
|
||||
}
|
||||
this.format = fmt;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ListDashboards setLevel(Level level) {
|
||||
this.level = level;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ListDashboards setEntityName(String entityName) {
|
||||
this.entityName = entityName;
|
||||
return this;
|
||||
}
|
||||
|
||||
public void display(OutputStream out) {
|
||||
final PrintWriter stdout;
|
||||
try {
|
||||
stdout = new PrintWriter(new BufferedWriter(new OutputStreamWriter(out, "UTF-8")));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
// Our encoding is required by the specifications for the runtime.
|
||||
throw new RuntimeException("JVM lacks UTF-8 encoding", e);
|
||||
}
|
||||
|
||||
try {
|
||||
final Map<String, DashboardInfo> dashboards;
|
||||
if (level != null) {
|
||||
switch (level) {
|
||||
case PROJECT:
|
||||
dashboards = projectDashboards(new Project.NameKey(entityName));
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("unsupported dashboard level: " + level);
|
||||
}
|
||||
} else {
|
||||
dashboards = Maps.newTreeMap();
|
||||
}
|
||||
|
||||
format.newGson().toJson(dashboards,
|
||||
new TypeToken<Map<String, DashboardInfo>>() {}.getType(), stdout);
|
||||
stdout.print('\n');
|
||||
} finally {
|
||||
stdout.flush();
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, DashboardInfo> projectDashboards(final Project.NameKey projectName) {
|
||||
final Map<String, DashboardInfo> dashboards = Maps.newTreeMap();
|
||||
|
||||
final ProjectState projectState = projectCache.get(projectName);
|
||||
final ProjectControl projectControl = projectState.controlFor(currentUser);
|
||||
if (projectState == null || !projectControl.isVisible()) {
|
||||
return dashboards;
|
||||
}
|
||||
|
||||
Repository repo = null;
|
||||
RevWalk revWalk = null;
|
||||
try {
|
||||
repo = repoManager.openRepository(projectName);
|
||||
revWalk = new RevWalk(repo);
|
||||
final Map<String, Ref> refs = repo.getRefDatabase().getRefs(REFS_DASHBOARDS);
|
||||
for (final Ref ref : refs.values()) {
|
||||
if (projectControl.controlForRef(ref.getName()).canRead()) {
|
||||
dashboards.putAll(loadDashboards(projectName, repo, revWalk, ref));
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.warn("Failed to load dashboards of project " + projectName.get(), e);
|
||||
} finally {
|
||||
if (revWalk != null) {
|
||||
revWalk.release();
|
||||
}
|
||||
if (repo != null) {
|
||||
repo.close();
|
||||
}
|
||||
}
|
||||
|
||||
return dashboards;
|
||||
}
|
||||
|
||||
private Map<String, DashboardInfo> loadDashboards(
|
||||
final Project.NameKey projectName, final Repository repo,
|
||||
final RevWalk revWalk, final Ref ref) {
|
||||
final Map<String, DashboardInfo> dashboards = Maps.newTreeMap();
|
||||
TreeWalk treeWalk = new TreeWalk(repo);
|
||||
try {
|
||||
final RevCommit commit = revWalk.parseCommit(ref.getObjectId());
|
||||
final RevTree tree = commit.getTree();
|
||||
treeWalk.addTree(tree);
|
||||
treeWalk.setRecursive(true);
|
||||
while (treeWalk.next()) {
|
||||
DashboardInfo info = new DashboardInfo();
|
||||
info.dashboardName = treeWalk.getPathString();
|
||||
info.refName = ref.getName();
|
||||
info.projectName = projectName.get();
|
||||
info.id = createId(info.refName, info.dashboardName);
|
||||
final ObjectLoader loader = repo.open(treeWalk.getObjectId(0));
|
||||
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
loader.copyTo(out);
|
||||
Config dashboardConfig = new Config();
|
||||
dashboardConfig.fromText(new String(out.toByteArray(), "UTF-8"));
|
||||
|
||||
info.description = dashboardConfig.getString("main", null, "description");
|
||||
|
||||
final StringBuilder query = new StringBuilder();
|
||||
query.append("title=");
|
||||
query.append(info.dashboardName.replaceAll(" ", "+"));
|
||||
final Set<String> sections = dashboardConfig.getSubsections("section");
|
||||
for (final String section : sections) {
|
||||
query.append("&");
|
||||
query.append(section.replaceAll(" ", "+"));
|
||||
query.append("=");
|
||||
query.append(dashboardConfig.getString("section", section, "query"));
|
||||
}
|
||||
info.parameters = query.toString();
|
||||
|
||||
dashboards.put(info.id, info);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.warn("Failed to load dashboards of project " + projectName.get()
|
||||
+ " from ref " + ref.getName(), e);
|
||||
} catch (ConfigInvalidException e) {
|
||||
log.warn("Failed to load dashboards of project " + projectName.get()
|
||||
+ " from ref " + ref.getName(), e);
|
||||
} finally {
|
||||
treeWalk.release();
|
||||
}
|
||||
return dashboards;
|
||||
}
|
||||
|
||||
private static String createId(final String refName,
|
||||
final String dashboardName) {
|
||||
return refName + ":" + dashboardName;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static class DashboardInfo {
|
||||
final String kind = "gerritcodereview#dashboard";
|
||||
String id;
|
||||
String dashboardName;
|
||||
String refName;
|
||||
String projectName;
|
||||
String description;
|
||||
String parameters;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user