Support project specific plugin list parameters for edit in UI

Change-Id: I22a383fa0944681078b327cc62c215d08e2c9bc6
Signed-off-by: Edwin Kempin <edwin.kempin@sap.com>
This commit is contained in:
Edwin Kempin
2013-11-28 19:56:15 +01:00
parent a6c1c45f48
commit 20f256fb89
10 changed files with 153 additions and 40 deletions

View File

@@ -1295,10 +1295,12 @@ parameter.
|`display_name` |optional| |`display_name` |optional|
The display name of the configuration parameter. The display name of the configuration parameter.
|`type` || |`type` ||
The type of the configuration parameter, can be `STRING`, `INT`, `LONG` The type of the configuration parameter, can be `STRING`, `INT`,
or `BOOLEAN`. `LONG`, `BOOLEAN` or `LIST`.
|`value` |optional| |`value` |optional|
The value of the configuration parameter as string. The value of the configuration parameter as string.
|`permitted_values`|optional|
The list of permitted values, only set if the `type` is `LIST`.
|=============================== |===============================
[[dashboard-info]] [[dashboard-info]]

View File

@@ -371,6 +371,9 @@ public class ProjectInfoScreen extends ProjectScreen {
w = renderTextBox(g, param, true); w = renderTextBox(g, param, true);
} else if ("BOOLEAN".equals(param.type())) { } else if ("BOOLEAN".equals(param.type())) {
w = renderCheckBox(g, param); w = renderCheckBox(g, param);
} else if ("LIST".equals(param.type())
&& param.permittedValues() != null) {
w = renderListBox(g, param);
} else { } else {
continue; continue;
} }
@@ -399,6 +402,21 @@ public class ProjectInfoScreen extends ProjectScreen {
return checkBox; return checkBox;
} }
private ListBox renderListBox(LabeledWidgetsGrid g,
ConfigParameterInfo param) {
ListBox listBox = new ListBox();
for (int i = 0; i < param.permittedValues().length(); i++) {
String sv = param.permittedValues().get(i);
listBox.addItem(sv);
if (sv.equals(param.value())) {
listBox.setSelectedIndex(i);
}
}
g.add(getDisplayName(param), listBox);
saveEnabler.listenTo(listBox);
return listBox;
}
private String getDisplayName(ConfigParameterInfo param) { private String getDisplayName(ConfigParameterInfo param) {
return param.displayName() != null ? param.displayName() : param.name(); return param.displayName() != null ? param.displayName() : param.name();
} }
@@ -460,6 +478,10 @@ public class ProjectInfoScreen extends ProjectScreen {
values.put(e2.getKey(), ((TextBox) widget).getValue().trim()); values.put(e2.getKey(), ((TextBox) widget).getValue().trim());
} else if (widget instanceof CheckBox) { } else if (widget instanceof CheckBox) {
values.put(e2.getKey(), Boolean.toString(((CheckBox) widget).getValue())); values.put(e2.getKey(), Boolean.toString(((CheckBox) widget).getValue()));
} else if (widget instanceof ListBox) {
ListBox listBox = (ListBox) widget;
String value = listBox.getValue(listBox.getSelectedIndex());
values.put(e2.getKey(), value);
} }
} }
} }

View File

@@ -21,6 +21,7 @@ import com.google.gerrit.reviewdb.client.Project.InheritableBoolean;
import com.google.gerrit.reviewdb.client.Project.SubmitType; import com.google.gerrit.reviewdb.client.Project.SubmitType;
import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArray; import com.google.gwt.core.client.JsArray;
import com.google.gwt.core.client.JsArrayString;
import com.google.gwtexpui.safehtml.client.FindReplace; import com.google.gwtexpui.safehtml.client.FindReplace;
import com.google.gwtexpui.safehtml.client.LinkFindReplace; import com.google.gwtexpui.safehtml.client.LinkFindReplace;
import com.google.gwtexpui.safehtml.client.RawFindReplace; import com.google.gwtexpui.safehtml.client.RawFindReplace;
@@ -150,6 +151,7 @@ public class ConfigInfo extends JavaScriptObject {
public final native String displayName() /*-{ return this.display_name; }-*/; public final native String displayName() /*-{ return this.display_name; }-*/;
public final native String type() /*-{ return this.type; }-*/; public final native String type() /*-{ return this.type; }-*/;
public final native String value() /*-{ return this.value; }-*/; public final native String value() /*-{ return this.value; }-*/;
public final native JsArrayString permittedValues() /*-{ return this.permitted_values; }-*/;
protected ConfigParameterInfo() { protected ConfigParameterInfo() {
} }

View File

@@ -14,38 +14,63 @@
package com.google.gerrit.server.config; package com.google.gerrit.server.config;
import com.google.common.base.Function;
import com.google.common.collect.Lists;
import com.google.gerrit.extensions.annotations.ExtensionPoint; import com.google.gerrit.extensions.annotations.ExtensionPoint;
import java.util.Arrays;
import java.util.List;
@ExtensionPoint @ExtensionPoint
public class ProjectConfigEntry { public class ProjectConfigEntry {
public enum Type { public enum Type {
STRING, INT, LONG, BOOLEAN STRING, INT, LONG, BOOLEAN, LIST
} }
private final String displayName; private final String displayName;
private final String defaultValue; private final String defaultValue;
private final Type type; private final Type type;
private final List<String> permittedValues;
public ProjectConfigEntry(String displayName, String defaultValue) { public ProjectConfigEntry(String displayName, String defaultValue) {
this(displayName, defaultValue, Type.STRING); this(displayName, defaultValue, Type.STRING, null);
} }
public ProjectConfigEntry(String displayName, int defaultValue) { public ProjectConfigEntry(String displayName, int defaultValue) {
this(displayName, Integer.toString(defaultValue), Type.INT); this(displayName, Integer.toString(defaultValue), Type.INT, null);
} }
public ProjectConfigEntry(String displayName, long defaultValue) { public ProjectConfigEntry(String displayName, long defaultValue) {
this(displayName, Long.toString(defaultValue), Type.LONG); this(displayName, Long.toString(defaultValue), Type.LONG, null);
} }
public ProjectConfigEntry(String displayName, boolean defaultValue) { public ProjectConfigEntry(String displayName, boolean defaultValue) {
this(displayName, Boolean.toString(defaultValue), Type.BOOLEAN); this(displayName, Boolean.toString(defaultValue), Type.BOOLEAN, null);
} }
private ProjectConfigEntry(String displayName, String defaultValue, Type type) { public ProjectConfigEntry(String displayName, String defaultValue,
List<String> permittedValues) {
this(displayName, defaultValue, Type.LIST, permittedValues);
}
public <T extends Enum<?>> ProjectConfigEntry(String displayName,
T defaultValue, Class<T> permittedValues) {
this(displayName, defaultValue.name(), Type.LIST, Lists.transform(
Arrays.asList(permittedValues.getEnumConstants()),
new Function<Enum<?>, String>() {
@Override
public String apply(Enum<?> e) {
return e.name();
}
}));
}
private ProjectConfigEntry(String displayName, String defaultValue,
Type type, List<String> permittedValues) {
this.displayName = displayName; this.displayName = displayName;
this.defaultValue = defaultValue; this.defaultValue = defaultValue;
this.type = type; this.type = type;
this.permittedValues = permittedValues;
} }
public String getDisplayName() { public String getDisplayName() {
@@ -59,4 +84,8 @@ public class ProjectConfigEntry {
public Type getType() { public Type getType() {
return type; return type;
} }
public List<String> getPermittedValues() {
return permittedValues;
}
} }

View File

@@ -67,6 +67,11 @@ public enum CommitMergeStatus {
/** */ /** */
INVALID_PROJECT_CONFIGURATION("Change contains an invalid project configuration."), INVALID_PROJECT_CONFIGURATION("Change contains an invalid project configuration."),
/** */
INVALID_PROJECT_CONFIGURATION_PLUGIN_VALUE_NOT_PERMITTED(
"Change contains an invalid project configuration:\n"
+ "One of the plugin configuration parameters has a value that is not permitted."),
/** */ /** */
INVALID_PROJECT_CONFIGURATION_PARENT_PROJECT_NOT_FOUND( INVALID_PROJECT_CONFIGURATION_PARENT_PROJECT_NOT_FOUND(
"Change contains an invalid project configuration:\n" "Change contains an invalid project configuration:\n"

View File

@@ -686,6 +686,7 @@ public class MergeOp {
case CANNOT_CHERRY_PICK_ROOT: case CANNOT_CHERRY_PICK_ROOT:
case NOT_FAST_FORWARD: case NOT_FAST_FORWARD:
case INVALID_PROJECT_CONFIGURATION: case INVALID_PROJECT_CONFIGURATION:
case INVALID_PROJECT_CONFIGURATION_PLUGIN_VALUE_NOT_PERMITTED:
case INVALID_PROJECT_CONFIGURATION_PARENT_PROJECT_NOT_FOUND: case INVALID_PROJECT_CONFIGURATION_PARENT_PROJECT_NOT_FOUND:
case INVALID_PROJECT_CONFIGURATION_ROOT_PROJECT_CANNOT_HAVE_PARENT: case INVALID_PROJECT_CONFIGURATION_ROOT_PROJECT_CANNOT_HAVE_PARENT:
case SETTING_PARENT_PROJECT_ONLY_ALLOWED_BY_ADMIN: case SETTING_PARENT_PROJECT_ONLY_ALLOWED_BY_ADMIN:

View File

@@ -51,6 +51,8 @@ import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.Capable; import com.google.gerrit.common.data.Capable;
import com.google.gerrit.common.data.LabelTypes; import com.google.gerrit.common.data.LabelTypes;
import com.google.gerrit.common.data.PermissionRule; import com.google.gerrit.common.data.PermissionRule;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.registration.DynamicMap.Entry;
import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Branch; import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Change; import com.google.gerrit.reviewdb.client.Change;
@@ -75,6 +77,8 @@ import com.google.gerrit.server.change.RevisionResource;
import com.google.gerrit.server.change.Submit; import com.google.gerrit.server.change.Submit;
import com.google.gerrit.server.config.AllProjectsName; import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.CanonicalWebUrl; import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.config.PluginConfig;
import com.google.gerrit.server.config.ProjectConfigEntry;
import com.google.gerrit.server.events.CommitReceivedEvent; import com.google.gerrit.server.events.CommitReceivedEvent;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated; import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.MultiProgressMonitor.Task; import com.google.gerrit.server.git.MultiProgressMonitor.Task;
@@ -305,6 +309,7 @@ public class ReceiveCommits {
private final SubmoduleOp.Factory subOpFactory; private final SubmoduleOp.Factory subOpFactory;
private final Provider<Submit> submitProvider; private final Provider<Submit> submitProvider;
private final MergeQueue mergeQueue; private final MergeQueue mergeQueue;
private final DynamicMap<ProjectConfigEntry> pluginConfigEntries;
private final List<CommitValidationMessage> messages = new ArrayList<CommitValidationMessage>(); private final List<CommitValidationMessage> messages = new ArrayList<CommitValidationMessage>();
private ListMultimap<Error, String> errors = LinkedListMultimap.create(); private ListMultimap<Error, String> errors = LinkedListMultimap.create();
@@ -352,7 +357,8 @@ public class ReceiveCommits {
@Assisted final Repository repo, @Assisted final Repository repo,
final SubmoduleOp.Factory subOpFactory, final SubmoduleOp.Factory subOpFactory,
final Provider<Submit> submitProvider, final Provider<Submit> submitProvider,
final MergeQueue mergeQueue) throws IOException { final MergeQueue mergeQueue,
final DynamicMap<ProjectConfigEntry> pluginConfigEntries) throws IOException {
this.currentUser = (IdentifiedUser) projectControl.getCurrentUser(); this.currentUser = (IdentifiedUser) projectControl.getCurrentUser();
this.db = db; this.db = db;
this.changeDataFactory = changeDataFactory; this.changeDataFactory = changeDataFactory;
@@ -395,6 +401,7 @@ public class ReceiveCommits {
this.subOpFactory = subOpFactory; this.subOpFactory = subOpFactory;
this.submitProvider = submitProvider; this.submitProvider = submitProvider;
this.mergeQueue = mergeQueue; this.mergeQueue = mergeQueue;
this.pluginConfigEntries = pluginConfigEntries;
this.messageSender = new ReceivePackMessageSender(); this.messageSender = new ReceivePackMessageSender();
@@ -871,6 +878,20 @@ public class ReceiveCommits {
continue; continue;
} }
} }
for (Entry<ProjectConfigEntry> e : pluginConfigEntries) {
ProjectConfigEntry configEntry = e.getProvider().get();
if (ProjectConfigEntry.Type.LIST.equals(configEntry.getType())) {
PluginConfig pluginCfg = cfg.getPluginConfig(e.getPluginName());
String value = pluginCfg.getString(e.getExportName());
if (value != null && !configEntry.getPermittedValues().contains(value)) {
reject(cmd, String.format(
"invalid project configuration: The value '%s' is "
+ "not permitted for parameter '%s' of plugin '%s'.",
value, e.getExportName(), e.getPluginName()));
}
}
}
} catch (Exception e) { } catch (Exception e) {
reject(cmd, "invalid project configuration"); reject(cmd, "invalid project configuration");
log.error("User " + currentUser.getUserName() log.error("User " + currentUser.getUserName()

View File

@@ -15,7 +15,9 @@
package com.google.gerrit.server.git.validators; package com.google.gerrit.server.git.validators;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.registration.DynamicSet; import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.extensions.registration.DynamicMap.Entry;
import com.google.gerrit.reviewdb.client.Branch; import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.PatchSet; import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.PatchSetApproval; import com.google.gerrit.reviewdb.client.PatchSetApproval;
@@ -25,6 +27,8 @@ import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ApprovalsUtil; import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.config.AllProjectsName; import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.PluginConfig;
import com.google.gerrit.server.config.ProjectConfigEntry;
import com.google.gerrit.server.git.CodeReviewCommit; import com.google.gerrit.server.git.CodeReviewCommit;
import com.google.gerrit.server.git.CommitMergeStatus; import com.google.gerrit.server.git.CommitMergeStatus;
import com.google.gerrit.server.git.ProjectConfig; import com.google.gerrit.server.git.ProjectConfig;
@@ -32,8 +36,10 @@ import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState; import com.google.gerrit.server.project.ProjectState;
import com.google.inject.Inject; import com.google.inject.Inject;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.Repository;
import java.io.IOException;
import java.util.List; import java.util.List;
public class MergeValidators { public class MergeValidators {
@@ -74,6 +80,7 @@ public class MergeValidators {
private final ProjectCache projectCache; private final ProjectCache projectCache;
private final IdentifiedUser.GenericFactory identifiedUserFactory; private final IdentifiedUser.GenericFactory identifiedUserFactory;
private final ApprovalsUtil approvalsUtil; private final ApprovalsUtil approvalsUtil;
private final DynamicMap<ProjectConfigEntry> pluginConfigEntries;
public interface Factory { public interface Factory {
ProjectConfigValidator create(); ProjectConfigValidator create();
@@ -83,12 +90,14 @@ public class MergeValidators {
public ProjectConfigValidator(AllProjectsName allProjectsName, public ProjectConfigValidator(AllProjectsName allProjectsName,
ReviewDb db, ProjectCache projectCache, ReviewDb db, ProjectCache projectCache,
IdentifiedUser.GenericFactory iuf, IdentifiedUser.GenericFactory iuf,
ApprovalsUtil approvalsUtil) { ApprovalsUtil approvalsUtil,
DynamicMap<ProjectConfigEntry> pluginConfigEntries) {
this.allProjectsName = allProjectsName; this.allProjectsName = allProjectsName;
this.db = db; this.db = db;
this.projectCache = projectCache; this.projectCache = projectCache;
this.identifiedUserFactory = iuf; this.identifiedUserFactory = iuf;
this.approvalsUtil = approvalsUtil; this.approvalsUtil = approvalsUtil;
this.pluginConfigEntries = pluginConfigEntries;
} }
@Override @Override
@@ -105,39 +114,51 @@ public class MergeValidators {
new ProjectConfig(destProject.getProject().getNameKey()); new ProjectConfig(destProject.getProject().getNameKey());
cfg.load(repo, commit); cfg.load(repo, commit);
newParent = cfg.getProject().getParent(allProjectsName); newParent = cfg.getProject().getParent(allProjectsName);
} catch (Exception e) { final Project.NameKey oldParent =
destProject.getProject().getParent(allProjectsName);
if (oldParent == null) {
// update of the 'All-Projects' project
if (newParent != null) {
throw new MergeValidationException(CommitMergeStatus.
INVALID_PROJECT_CONFIGURATION_ROOT_PROJECT_CANNOT_HAVE_PARENT);
}
} else {
if (!oldParent.equals(newParent)) {
PatchSetApproval psa =
approvalsUtil.getSubmitter(db, commit.notes, patchSetId);
if (psa == null) {
throw new MergeValidationException(CommitMergeStatus.
SETTING_PARENT_PROJECT_ONLY_ALLOWED_BY_ADMIN);
}
final IdentifiedUser submitter =
identifiedUserFactory.create(psa.getAccountId());
if (!submitter.getCapabilities().canAdministrateServer()) {
throw new MergeValidationException(CommitMergeStatus.
SETTING_PARENT_PROJECT_ONLY_ALLOWED_BY_ADMIN);
}
if (projectCache.get(newParent) == null) {
throw new MergeValidationException(CommitMergeStatus.
INVALID_PROJECT_CONFIGURATION_PARENT_PROJECT_NOT_FOUND);
}
}
}
for (Entry<ProjectConfigEntry> e : pluginConfigEntries) {
ProjectConfigEntry configEntry = e.getProvider().get();
if (ProjectConfigEntry.Type.LIST.equals(configEntry.getType())) {
PluginConfig pluginCfg = cfg.getPluginConfig(e.getPluginName());
String value = pluginCfg.getString(e.getExportName());
if (value != null && !configEntry.getPermittedValues().contains(value)) {
throw new MergeValidationException(CommitMergeStatus.
INVALID_PROJECT_CONFIGURATION_PLUGIN_VALUE_NOT_PERMITTED);
}
}
}
} catch (ConfigInvalidException | IOException e) {
throw new MergeValidationException(CommitMergeStatus. throw new MergeValidationException(CommitMergeStatus.
INVALID_PROJECT_CONFIGURATION); INVALID_PROJECT_CONFIGURATION);
} }
final Project.NameKey oldParent =
destProject.getProject().getParent(allProjectsName);
if (oldParent == null) {
// update of the 'All-Projects' project
if (newParent != null) {
throw new MergeValidationException(CommitMergeStatus.
INVALID_PROJECT_CONFIGURATION_ROOT_PROJECT_CANNOT_HAVE_PARENT);
}
} else {
if (!oldParent.equals(newParent)) {
PatchSetApproval psa =
approvalsUtil.getSubmitter(db, commit.notes, patchSetId);
if (psa == null) {
throw new MergeValidationException(CommitMergeStatus.
SETTING_PARENT_PROJECT_ONLY_ALLOWED_BY_ADMIN);
}
final IdentifiedUser submitter =
identifiedUserFactory.create(psa.getAccountId());
if (!submitter.getCapabilities().canAdministrateServer()) {
throw new MergeValidationException(CommitMergeStatus.
SETTING_PARENT_PROJECT_ONLY_ALLOWED_BY_ADMIN);
}
if (projectCache.get(newParent) == null) {
throw new MergeValidationException(CommitMergeStatus.
INVALID_PROJECT_CONFIGURATION_PARENT_PROJECT_NOT_FOUND);
}
}
}
} }
} }
} }

View File

@@ -32,6 +32,7 @@ import com.google.gerrit.server.extensions.webui.UiActions;
import com.google.gerrit.server.git.TransferConfig; import com.google.gerrit.server.git.TransferConfig;
import com.google.inject.util.Providers; import com.google.inject.util.Providers;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.TreeMap; import java.util.TreeMap;
@@ -135,6 +136,7 @@ public class ConfigInfo {
p.displayName = configEntry.getDisplayName(); p.displayName = configEntry.getDisplayName();
p.type = configEntry.getType(); p.type = configEntry.getType();
p.value = cfg.getString(e.getExportName(), configEntry.getDefaultValue()); p.value = cfg.getString(e.getExportName(), configEntry.getDefaultValue());
p.permittedValues = configEntry.getPermittedValues();
Map<String, ConfigParameterInfo> pc = pluginConfig.get(e.getPluginName()); Map<String, ConfigParameterInfo> pc = pluginConfig.get(e.getPluginName());
if (pc == null) { if (pc == null) {
pc = new TreeMap<>(); pc = new TreeMap<>();
@@ -161,5 +163,6 @@ public class ConfigInfo {
public String displayName; public String displayName;
public ProjectConfigEntry.Type type; public ProjectConfigEntry.Type type;
public String value; public String value;
public List<String> permittedValues;
} }
} }

View File

@@ -201,6 +201,13 @@ public class PutConfig implements RestModifyView<ProjectResource, Input> {
case LONG: case LONG:
cfg.setLong(v.getKey(), Long.parseLong(v.getValue())); cfg.setLong(v.getKey(), Long.parseLong(v.getValue()));
break; break;
case LIST:
if (!projectConfigEntry.getPermittedValues()
.contains(v.getValue())) {
throw new BadRequestException(String.format(
"The value '%s' is not permitted for parameter '%s' of plugin '"
+ pluginName + "'", v.getValue(), v.getKey()));
}
case STRING: case STRING:
cfg.setString(v.getKey(), v.getValue()); cfg.setString(v.getKey(), v.getValue());
break; break;