Allow defining a default dashboard for projects

It is now possible to define a default dashboard for a project in the
project.config file in the refs/meta/config branch.

Example:
[dashboard]
  default = refs/meta/dashboards/main:default

When it will be possible to inherit dashboards from parent projects, it
will be important to differentiate between a default dashboard that
is be used for this project and the dashboard that will be inherited
by the child projects as default dashboard.

This is why we allow overwriting the default dashboard by explicitly
setting a local default dashboard, which will only be used by this
project.

Example:
[dashboard]
  default = refs/meta/dashboards/main:default
  local-default = refs/meta/dashboards/main:local

Via the /dashboards/ REST endpoint it is now possible to either
retrieve all dashboards of the project or only the default dashboard
of the project.

Change-Id: I119408968bc86c3fd112c186638d3176235ca28e
Signed-off-by: Edwin Kempin <edwin.kempin@sap.com>
This commit is contained in:
Edwin Kempin
2012-11-14 20:46:58 +01:00
parent b2efe218a5
commit 488534349a
7 changed files with 217 additions and 34 deletions

View File

@@ -420,7 +420,32 @@ List all dashboards for the `myProject` project:
"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"
"parameters": "title\u003dMyDashboard\u0026Open+Changes\u003dstatus:open project:myProject limit:15\u0026Merged+Changes\u003dstatus:merged project:myProject limit:15",
"is_default": true
}
}
----
To retrieve only the default dashboard of a project set the parameter `default`.
----
GET /dashboards/project/myProject?default&format=JSON&d HTTP/1.0
HTTP/1.1 200 OK
Content-Disposition: attachment
Content-Type: application/json;charset=UTF-8
)]}'
{
"MyProject Dashboard": {
"kind": "gerritcodereview#dashboard",
"id" : "refs/meta/dashboards/main:MyDashboard",
"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",
"is_default": true
}
}
----

View File

@@ -77,6 +77,29 @@ section.<name>.query::
The change query that should be used to populate the section with the
given name.
Project Default Dashboard
-------------------------
It is possible to define a default dashboard for a project in the
projects `project.config` file in the `refs/meta/config` branch:
----
[dashboard]
default = refs/meta/dashboards/main:default
----
The dashboard set as the default dashboard will be inherited as the
default dashboard by child projects if they do not define their own
default dashboard. The `local-default` entry makes it possible to
define a different default dashboard that is only used by this project
but not inherited to the child projects.
----
[dashboard]
default = refs/meta/dashboards/main:default
local-default = refs/meta/dashboards/main:local
----
GERRIT
------
Part of link:index.html[Gerrit Code Review]

View File

@@ -23,6 +23,7 @@ public class DashboardInfo extends JavaScriptObject {
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; }-*/;
public final native boolean isDefault() /*-{ return this.isDefault ? true : false; }-*/;
protected DashboardInfo() {
}

View File

@@ -28,6 +28,13 @@ public class DashboardMap extends NativeMap<DashboardInfo> {
.send(NativeMap.copyKeysIntoChildren(callback));
}
public static void projectDefault(Project.NameKey project, boolean mine,
AsyncCallback<DashboardMap> callback) {
new RestApi("/dashboards/project/" + URL.encode(project.get()).replaceAll("[?]", "%3F"))
.addParameterTrue("default")
.send(NativeMap.copyKeysIntoChildren(callback));
}
protected DashboardMap() {
}
}

View File

@@ -109,6 +109,10 @@ public final class Project {
protected InheritedBoolean useContentMerge;
protected String defaultDashboardId;
protected String localDefaultDashboardId;
protected Project() {
}
@@ -186,6 +190,22 @@ public final class Project {
state = newState;
}
public String getDefaultDashboard() {
return defaultDashboardId;
}
public void setDefaultDashboard(final String defaultDashboardId) {
this.defaultDashboardId = defaultDashboardId;
}
public String getLocalDefaultDashboard() {
return localDefaultDashboardId;
}
public void setLocalDefaultDashboard(final String localDefaultDashboardId) {
this.localDefaultDashboardId = localDefaultDashboardId;
}
public void copySettingsFrom(final Project update) {
description = update.description;
useContributorAgreements = update.useContributorAgreements;

View File

@@ -34,6 +34,7 @@ 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.eclipse.jgit.treewalk.filter.PathFilter;
import org.kohsuke.args4j.Option;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -47,6 +48,7 @@ import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
/** List projects visible to the calling user. */
public class ListDashboards {
@@ -64,6 +66,9 @@ public class ListDashboards {
@Option(name = "--format", metaVar = "FMT", usage = "Output display format")
private OutputFormat format = OutputFormat.JSON;
@Option(name = "--default", usage = "only the projects default dashboard is returned")
private boolean defaultDashboard;
private Level level;
private String entityName;
@@ -111,7 +116,14 @@ public class ListDashboards {
if (level != null) {
switch (level) {
case PROJECT:
dashboards = projectDashboards(new Project.NameKey(entityName));
final Project.NameKey projectName = new Project.NameKey(entityName);
if (defaultDashboard) {
dashboards = Maps.newTreeMap();
final DashboardInfo info = loadProjectDefaultDashboard(projectName);
dashboards.put(info.id, info);
} else {
dashboards = projectDashboards(projectName);
}
break;
default:
throw new IllegalStateException("unsupported dashboard level: " + level);
@@ -145,7 +157,8 @@ public class ListDashboards {
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));
dashboards.putAll(loadDashboards(projectControl.getProject(), repo,
revWalk, ref));
}
}
} catch (IOException e) {
@@ -163,8 +176,8 @@ public class ListDashboards {
}
private Map<String, DashboardInfo> loadDashboards(
final Project.NameKey projectName, final Repository repo,
final RevWalk revWalk, final Ref ref) {
final Project project, final Repository repo,
final RevWalk revWalk, final Ref ref) throws IOException {
final Map<String, DashboardInfo> dashboards = Maps.newTreeMap();
TreeWalk treeWalk = new TreeWalk(repo);
try {
@@ -173,39 +186,18 @@ public class ListDashboards {
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();
final DashboardInfo info =
loadDashboard(project, ref.getName(), treeWalk.getPathString(),
loader);
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()
log.warn("Failed to load dashboards of project " + project.getName()
+ " from ref " + ref.getName(), e);
} catch (IOException e) {
log.warn("Failed to load dashboards of project " + project.getName()
+ " from ref " + ref.getName(), e);
} finally {
treeWalk.release();
@@ -213,6 +205,110 @@ public class ListDashboards {
return dashboards;
}
private DashboardInfo loadProjectDefaultDashboard(final Project.NameKey projectName) {
final ProjectState projectState = projectCache.get(projectName);
final ProjectControl projectControl = projectState.controlFor(currentUser);
if (projectState == null || !projectControl.isVisible()) {
return null;
}
final Project project = projectControl.getProject();
final String defaultDashboardId =
project.getLocalDefaultDashboard() != null ? project
.getLocalDefaultDashboard() : project.getDefaultDashboard();
return loadDashboard(projectControl, defaultDashboardId);
}
private DashboardInfo loadDashboard(final ProjectControl projectControl,
final String dashboardId) {
StringTokenizer t = new StringTokenizer(dashboardId);
if (t.countTokens() != 2) {
throw new IllegalStateException("failed to load dashboard, invalid dashboard id: " + dashboardId);
}
final String refName = t.nextToken();
final String path = t.nextToken();
Repository repo = null;
RevWalk revWalk = null;
TreeWalk treeWalk = null;
try {
repo =
repoManager.openRepository(projectControl.getProject().getNameKey());
final Ref ref = repo.getRef(refName);
if (ref == null) {
return null;
}
if (!projectControl.controlForRef(ref.getName()).canRead()) {
return null;
}
revWalk = new RevWalk(repo);
final RevCommit commit = revWalk.parseCommit(ref.getObjectId());
treeWalk = new TreeWalk(repo);
treeWalk.addTree(commit.getTree());
treeWalk.setRecursive(true);
treeWalk.setFilter(PathFilter.create(path));
if (!treeWalk.next()) {
return null;
}
final ObjectLoader loader = repo.open(treeWalk.getObjectId(0));
return loadDashboard(projectControl.getProject(), refName, path, loader);
} catch (IOException e) {
log.warn("Failed to load default dashboard", e);
} catch (ConfigInvalidException e) {
log.warn("Failed to load dashboards of project "
+ projectControl.getProject().getName() + " from ref " + refName, e);
} finally {
if (treeWalk != null) {
treeWalk.release();
}
if (revWalk != null) {
revWalk.release();
}
if (repo != null) {
repo.close();
}
}
return null;
}
private DashboardInfo loadDashboard(final Project project,
final String refName, final String path, final ObjectLoader loader)
throws IOException, ConfigInvalidException {
DashboardInfo info = new DashboardInfo();
info.dashboardName = path;
info.refName = refName;
info.projectName = project.getName();
info.id = createId(info.refName, info.dashboardName);
final String defaultDashboardId =
project.getLocalDefaultDashboard() != null ? project
.getLocalDefaultDashboard() : project.getDefaultDashboard();
info.isDefault = info.id.equals(defaultDashboardId);
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();
return info;
}
private static String createId(final String refName,
final String dashboardName) {
return refName + ":" + dashboardName;
@@ -227,5 +323,6 @@ public class ListDashboards {
String projectName;
String description;
String parameters;
boolean isDefault;
}
}

View File

@@ -94,6 +94,10 @@ public class ProjectConfig extends VersionedMetaData {
private static final String KEY_MERGE_CONTENT = "mergeContent";
private static final String KEY_STATE = "state";
private static final String DASHBOARD = "dashboard";
private static final String KEY_DEFAULT = "default";
private static final String KEY_LOCAL_DEFAULT = "local-default";
private static final SubmitType defaultSubmitAction =
SubmitType.MERGE_IF_NECESSARY;
private static final State defaultStateValue =
@@ -291,6 +295,9 @@ public class ProjectConfig extends VersionedMetaData {
p.setUseContentMerge(getEnum(rc, SUBMIT, null, KEY_MERGE_CONTENT, Project.InheritedBoolean.INHERIT));
p.setState(getEnum(rc, PROJECT, null, KEY_STATE, defaultStateValue));
p.setDefaultDashboard(rc.getString(DASHBOARD, null, KEY_DEFAULT));
p.setLocalDefaultDashboard(rc.getString(DASHBOARD, null, KEY_LOCAL_DEFAULT));
loadAccountsSection(rc, groupsByName);
loadContributorAgreements(rc, groupsByName);
loadAccessSections(rc, groupsByName);
@@ -538,6 +545,9 @@ public class ProjectConfig extends VersionedMetaData {
set(rc, PROJECT, null, KEY_STATE, p.getState(), null);
set(rc, DASHBOARD, null, KEY_DEFAULT, p.getDefaultDashboard());
set(rc, DASHBOARD, null, KEY_LOCAL_DEFAULT, p.getLocalDefaultDashboard());
Set<AccountGroup.UUID> keepGroups = new HashSet<AccountGroup.UUID>();
saveAccountsSection(rc, keepGroups);
saveContributorAgreements(rc, keepGroups);