Support to edit project plugin configuration parameters in UI
Plugins can define project configuration parameters that project owners are allowed to edit through the ProjectInfoScreen. This makes it much easier for project owners to change the project specific plugin configuration. With this change only string parameters without inheritance are supported. Support for inheritance and other parameter types is added by follow-up changes. Change-Id: Iab3bc2ea2eb6dd6d0508f0049f43f7b9b21f58c8 Signed-off-by: Edwin Kempin <edwin.kempin@sap.com>
This commit is contained in:
@@ -679,6 +679,31 @@ Project owners can edit the project configuration by fetching the
|
||||
`refs/meta/config` branch, editing the `project.config` file and
|
||||
pushing the commit back.
|
||||
|
||||
Plugin configuration values that are stored in the `project.config`
|
||||
file can be exposed in the ProjectInfoScreen to allow project owners
|
||||
to see and edit them from the UI.
|
||||
|
||||
For this an instance of `ProjectConfigEntry` needs to be bound for each
|
||||
parameter. The export name must be a valid Git variable name. The
|
||||
variable name is case-insensitive, allows only alphanumeric characters
|
||||
and '-', and must start with an alphabetic character.
|
||||
|
||||
The example below shows how the parameter `plugin.helloworld.language`
|
||||
is bound to be editable from the WebUI. "Preferred Language" is
|
||||
provided as display name and "en" is set as default value.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
class Module extends AbstractModule {
|
||||
@Override
|
||||
protected void configure() {
|
||||
bind(ProjectConfigEntry.class)
|
||||
.annotatedWith(Exports.named("language"))
|
||||
.toInstance(new ProjectConfigEntry("Preferred Language", "en"));
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
[[project-specific-configuration]]
|
||||
== Project Specific Configuration in own config file
|
||||
|
||||
|
||||
@@ -460,6 +460,15 @@ read access to `refs/meta/config`.
|
||||
"submit_type": "MERGE_IF_NECESSARY",
|
||||
"state": "ACTIVE",
|
||||
"commentlinks": {},
|
||||
"plugin_config": {
|
||||
"helloworld": {
|
||||
"language": {
|
||||
"display_name": "Preferred Language",
|
||||
"type": "STRING",
|
||||
"value": "en"
|
||||
}
|
||||
}
|
||||
},
|
||||
"actions": {
|
||||
"cookbook~hello-project": {
|
||||
"method": "POST",
|
||||
@@ -1211,6 +1220,10 @@ commentlink section] of `gerrit.config`.
|
||||
|`theme` |optional|
|
||||
The theme that is configured for the project as a link:#theme-info[
|
||||
ThemeInfo] entity.
|
||||
|`plugin_config` |optional|
|
||||
Plugin configuration as map which maps the plugin name to a map of
|
||||
parameter names to link:#config-parameter-info[ConfigParameterInfo]
|
||||
entities.
|
||||
|`actions` |optional|
|
||||
Actions the caller might be able to perform on this project. The
|
||||
information is a map of view names to
|
||||
@@ -1265,8 +1278,28 @@ If not set, the submit type is not updated.
|
||||
The state of the project, can be `ACTIVE`, `READ_ONLY` or `HIDDEN`. +
|
||||
Not set if the project state is `ACTIVE`. +
|
||||
If not set, the project state is not updated.
|
||||
|`plugin_config_values` |optional|
|
||||
Plugin configuration values as map which maps the plugin name to a map
|
||||
of parameter names to values.
|
||||
|=========================================
|
||||
|
||||
[[config-parameter-info]]
|
||||
ConfigParameterInfo
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
The `ConfigParameterInfo` entity describes a project configuration
|
||||
parameter.
|
||||
|
||||
[options="header",width="50%",cols="1,^2,4"]
|
||||
|===============================
|
||||
|Field Name ||Description
|
||||
|`display_name` |optional|
|
||||
The display name of the configuration parameter.
|
||||
|`type` ||
|
||||
The type of the configuration parameter, can be `STRING`.
|
||||
|`value` |optional|
|
||||
The value of the configuration parameter as string.
|
||||
|===============================
|
||||
|
||||
[[dashboard-info]]
|
||||
=== DashboardInfo
|
||||
The `DashboardInfo` entity contains information about a project
|
||||
|
||||
@@ -28,4 +28,5 @@ public interface AdminMessages extends Messages {
|
||||
|
||||
String effectiveMaxObjectSizeLimit(String effectiveMaxObjectSizeLimit);
|
||||
String globalMaxObjectSizeLimit(String globalMaxObjectSizeLimit);
|
||||
String pluginProjectOptionsTitle(String pluginName);
|
||||
}
|
||||
|
||||
@@ -7,3 +7,4 @@ deletedReference = Reference {0} was deleted
|
||||
deletedSection = Section {0} was deleted
|
||||
effectiveMaxObjectSizeLimit = effective: {0}
|
||||
globalMaxObjectSizeLimit = The global max object size limit is set to {0}. The limit cannot be increased on project level.
|
||||
pluginProjectOptionsTitle = {0} Plugin Options
|
||||
|
||||
@@ -22,11 +22,13 @@ import com.google.gerrit.client.actions.ActionInfo;
|
||||
import com.google.gerrit.client.change.Resources;
|
||||
import com.google.gerrit.client.download.DownloadPanel;
|
||||
import com.google.gerrit.client.projects.ConfigInfo;
|
||||
import com.google.gerrit.client.projects.ConfigInfo.ConfigParameterInfo;
|
||||
import com.google.gerrit.client.projects.ConfigInfo.InheritedBooleanInfo;
|
||||
import com.google.gerrit.client.projects.ProjectApi;
|
||||
import com.google.gerrit.client.rpc.CallbackGroup;
|
||||
import com.google.gerrit.client.rpc.GerritCallback;
|
||||
import com.google.gerrit.client.rpc.NativeMap;
|
||||
import com.google.gerrit.client.rpc.Natives;
|
||||
import com.google.gerrit.client.rpc.ScreenLoadCallback;
|
||||
import com.google.gerrit.client.ui.OnEditEnabler;
|
||||
import com.google.gerrit.client.ui.SmallHeading;
|
||||
@@ -41,18 +43,26 @@ import com.google.gwt.event.dom.client.ClickHandler;
|
||||
import com.google.gwt.user.client.ui.Button;
|
||||
import com.google.gwt.user.client.ui.FlexTable;
|
||||
import com.google.gwt.user.client.ui.FlowPanel;
|
||||
import com.google.gwt.user.client.ui.FocusWidget;
|
||||
import com.google.gwt.user.client.ui.HorizontalPanel;
|
||||
import com.google.gwt.user.client.ui.Label;
|
||||
import com.google.gwt.user.client.ui.ListBox;
|
||||
import com.google.gwt.user.client.ui.Panel;
|
||||
import com.google.gwt.user.client.ui.TextBox;
|
||||
import com.google.gwt.user.client.ui.VerticalPanel;
|
||||
import com.google.gwt.user.client.ui.Widget;
|
||||
import com.google.gwtexpui.globalkey.client.NpTextArea;
|
||||
import com.google.gwtexpui.globalkey.client.NpTextBox;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
public class ProjectInfoScreen extends ProjectScreen {
|
||||
private boolean isOwner;
|
||||
|
||||
private LabeledWidgetsGrid grid;
|
||||
private Panel pluginOptionsPanel;
|
||||
private LabeledWidgetsGrid actionsGrid;
|
||||
|
||||
// Section: Project Options
|
||||
@@ -62,6 +72,7 @@ public class ProjectInfoScreen extends ProjectScreen {
|
||||
private ListBox contentMerge;
|
||||
private NpTextBox maxObjectSizeLimit;
|
||||
private Label effectiveMaxObjectSizeLimit;
|
||||
private Map<String, Map<String, FocusWidget>> pluginConfigWidgets;
|
||||
|
||||
// Section: Contributor Agreements
|
||||
private ListBox contributorAgreements;
|
||||
@@ -93,10 +104,12 @@ public class ProjectInfoScreen extends ProjectScreen {
|
||||
|
||||
initDescription();
|
||||
grid = new LabeledWidgetsGrid();
|
||||
pluginOptionsPanel = new FlowPanel();
|
||||
actionsGrid = new LabeledWidgetsGrid();
|
||||
initProjectOptions();
|
||||
initAgreements();
|
||||
add(grid);
|
||||
add(pluginOptionsPanel);
|
||||
add(saveProject);
|
||||
add(actionsGrid);
|
||||
}
|
||||
@@ -140,6 +153,14 @@ public class ProjectInfoScreen extends ProjectScreen {
|
||||
signedOffBy.setEnabled(isOwner);
|
||||
requireChangeID.setEnabled(isOwner);
|
||||
maxObjectSizeLimit.setEnabled(isOwner);
|
||||
|
||||
if (pluginConfigWidgets != null) {
|
||||
for (Map<String, FocusWidget> widgetMap : pluginConfigWidgets.values()) {
|
||||
for (FocusWidget widget : widgetMap.values()) {
|
||||
widget.setEnabled(isOwner);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void initDescription() {
|
||||
@@ -323,9 +344,46 @@ public class ProjectInfoScreen extends ProjectScreen {
|
||||
}
|
||||
|
||||
saveProject.setEnabled(false);
|
||||
initPluginOptions(result);
|
||||
initProjectActions(result);
|
||||
}
|
||||
|
||||
private void initPluginOptions(ConfigInfo info) {
|
||||
pluginOptionsPanel.clear();
|
||||
pluginConfigWidgets = new HashMap<String, Map<String, FocusWidget>>();
|
||||
|
||||
for (String pluginName : info.pluginConfig().keySet()) {
|
||||
Map<String, FocusWidget> widgetMap = new HashMap<String, FocusWidget>();
|
||||
pluginConfigWidgets.put(pluginName, widgetMap);
|
||||
LabeledWidgetsGrid g = new LabeledWidgetsGrid();
|
||||
g.addHeader(new SmallHeading(Util.M.pluginProjectOptionsTitle(pluginName)));
|
||||
pluginOptionsPanel.add(g);
|
||||
NativeMap<ConfigParameterInfo> pluginConfig =
|
||||
info.pluginConfig(pluginName);
|
||||
pluginConfig.copyKeysIntoChildren("name");
|
||||
for (ConfigParameterInfo param : Natives.asList(pluginConfig.values())) {
|
||||
FocusWidget w;
|
||||
if ("STRING".equals(param.type())) {
|
||||
w = renderTextBox(g, param);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
widgetMap.put(param.name(), w);
|
||||
}
|
||||
}
|
||||
|
||||
enableForm();
|
||||
}
|
||||
|
||||
private TextBox renderTextBox(LabeledWidgetsGrid g, ConfigParameterInfo param) {
|
||||
NpTextBox textBox = new NpTextBox();
|
||||
textBox.setValue(param.value());
|
||||
g.add(param.displayName() != null
|
||||
? param.displayName() : param.name(), textBox);
|
||||
saveEnabler.listenTo(textBox);
|
||||
return textBox;
|
||||
}
|
||||
|
||||
private void initProjectActions(ConfigInfo info) {
|
||||
actionsGrid.clear(true);
|
||||
actionsGrid.removeAllRows();
|
||||
@@ -355,7 +413,7 @@ public class ProjectInfoScreen extends ProjectScreen {
|
||||
maxObjectSizeLimit.getText().trim(),
|
||||
Project.SubmitType.valueOf(submitType.getValue(submitType.getSelectedIndex())),
|
||||
Project.State.valueOf(state.getValue(state.getSelectedIndex())),
|
||||
new GerritCallback<ConfigInfo>() {
|
||||
getPluginConfigValues(), new GerritCallback<ConfigInfo>() {
|
||||
@Override
|
||||
public void onSuccess(ConfigInfo result) {
|
||||
enableForm();
|
||||
@@ -370,6 +428,23 @@ public class ProjectInfoScreen extends ProjectScreen {
|
||||
});
|
||||
}
|
||||
|
||||
private Map<String, Map<String, String>> getPluginConfigValues() {
|
||||
Map<String, Map<String, String>> pluginConfigValues =
|
||||
new HashMap<String, Map<String, String>>(pluginConfigWidgets.size());
|
||||
for (Entry<String, Map<String, FocusWidget>> e : pluginConfigWidgets.entrySet()) {
|
||||
Map<String, String> values =
|
||||
new HashMap<String, String>(e.getValue().size());
|
||||
pluginConfigValues.put(e.getKey(), values);
|
||||
for (Entry<String, FocusWidget> e2 : e.getValue().entrySet()) {
|
||||
FocusWidget widget = e2.getValue();
|
||||
if (widget instanceof TextBox) {
|
||||
values.put(e2.getKey(), ((TextBox) widget).getValue().trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
return pluginConfigValues;
|
||||
}
|
||||
|
||||
public class ProjectDownloadPanel extends DownloadPanel {
|
||||
public ProjectDownloadPanel(String project, boolean isAllowsAnonymous) {
|
||||
super(project, null, isAllowsAnonymous);
|
||||
|
||||
@@ -49,6 +49,12 @@ public class ConfigInfo extends JavaScriptObject {
|
||||
return SubmitType.valueOf(submit_typeRaw());
|
||||
}
|
||||
|
||||
public final native NativeMap<NativeMap<ConfigParameterInfo>> pluginConfig()
|
||||
/*-{ return this.plugin_config || {}; }-*/;
|
||||
|
||||
public final native NativeMap<ConfigParameterInfo> pluginConfig(String p)
|
||||
/*-{ return this.plugin_config[p]; }-*/;
|
||||
|
||||
public final native NativeMap<ActionInfo> actions()
|
||||
/*-{ return this.actions; }-*/;
|
||||
|
||||
@@ -138,4 +144,14 @@ public class ConfigInfo extends JavaScriptObject {
|
||||
protected MaxObjectSizeLimitInfo() {
|
||||
}
|
||||
}
|
||||
|
||||
public static class ConfigParameterInfo extends JavaScriptObject {
|
||||
public final native String name() /*-{ return this.name; }-*/;
|
||||
public final native String displayName() /*-{ return this.display_name; }-*/;
|
||||
public final native String type() /*-{ return this.type; }-*/;
|
||||
public final native String value() /*-{ return this.value; }-*/;
|
||||
|
||||
protected ConfigParameterInfo() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ package com.google.gerrit.client.projects;
|
||||
|
||||
import com.google.gerrit.client.VoidResult;
|
||||
import com.google.gerrit.client.rpc.CallbackGroup;
|
||||
import com.google.gerrit.client.rpc.NativeMap;
|
||||
import com.google.gerrit.client.rpc.NativeString;
|
||||
import com.google.gerrit.client.rpc.RestApi;
|
||||
import com.google.gerrit.reviewdb.client.Project;
|
||||
@@ -24,6 +25,8 @@ import com.google.gwt.core.client.JavaScriptObject;
|
||||
import com.google.gwt.core.client.JsArray;
|
||||
import com.google.gwt.user.client.rpc.AsyncCallback;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
public class ProjectApi {
|
||||
@@ -81,7 +84,9 @@ public class ProjectApi {
|
||||
InheritableBoolean useContributorAgreements,
|
||||
InheritableBoolean useContentMerge, InheritableBoolean useSignedOffBy,
|
||||
InheritableBoolean requireChangeId, String maxObjectSizeLimit,
|
||||
SubmitType submitType, Project.State state, AsyncCallback<ConfigInfo> cb) {
|
||||
SubmitType submitType, Project.State state,
|
||||
Map<String, Map<String, String>> pluginConfigValues,
|
||||
AsyncCallback<ConfigInfo> cb) {
|
||||
ConfigInput in = ConfigInput.create();
|
||||
in.setDescription(description);
|
||||
in.setUseContributorAgreements(useContributorAgreements);
|
||||
@@ -91,6 +96,8 @@ public class ProjectApi {
|
||||
in.setMaxObjectSizeLimit(maxObjectSizeLimit);
|
||||
in.setSubmitType(submitType);
|
||||
in.setState(state);
|
||||
in.setPluginConfigValues(pluginConfigValues);
|
||||
|
||||
project(name).view("config").put(in, cb);
|
||||
}
|
||||
|
||||
@@ -214,6 +221,33 @@ public class ProjectApi {
|
||||
}
|
||||
private final native void setStateRaw(String s)
|
||||
/*-{ if(s)this.state=s; }-*/;
|
||||
|
||||
final void setPluginConfigValues(Map<String, Map<String, String>> pluginConfigValues) {
|
||||
if (!pluginConfigValues.isEmpty()) {
|
||||
NativeMap<StringMap> configValues = NativeMap.create().cast();
|
||||
for (Entry<String, Map<String, String>> e : pluginConfigValues.entrySet()) {
|
||||
StringMap values = StringMap.create();
|
||||
configValues.put(e.getKey(), values);
|
||||
for (Entry<String, String> e2 : e.getValue().entrySet()) {
|
||||
values.put(e2.getKey(), e2.getValue());
|
||||
}
|
||||
}
|
||||
setPluginConfigValuesRaw(configValues);
|
||||
}
|
||||
}
|
||||
private final native void setPluginConfigValuesRaw(NativeMap<StringMap> v)
|
||||
/*-{ this.plugin_config_values=v; }-*/;
|
||||
}
|
||||
|
||||
private static class StringMap extends JavaScriptObject {
|
||||
static StringMap create() {
|
||||
return (StringMap) createObject();
|
||||
}
|
||||
|
||||
protected StringMap() {
|
||||
}
|
||||
|
||||
public final native void put(String n, String v) /*-{ this[n] = v; }-*/;
|
||||
}
|
||||
|
||||
private static class BranchInput extends JavaScriptObject {
|
||||
|
||||
@@ -269,6 +269,7 @@ public class GerritGlobalModule extends FactoryModule {
|
||||
DynamicSet.setOf(binder(), MessageOfTheDay.class);
|
||||
DynamicMap.mapOf(binder(), DownloadScheme.class);
|
||||
DynamicMap.mapOf(binder(), DownloadCommand.class);
|
||||
DynamicMap.mapOf(binder(), ProjectConfigEntry.class);
|
||||
|
||||
bind(AnonymousUser.class);
|
||||
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
// 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.server.config;
|
||||
|
||||
import com.google.gerrit.extensions.annotations.ExtensionPoint;
|
||||
|
||||
@ExtensionPoint
|
||||
public class ProjectConfigEntry {
|
||||
public enum Type {
|
||||
STRING
|
||||
}
|
||||
|
||||
private final String displayName;
|
||||
private final String defaultValue;
|
||||
private final Type type;
|
||||
|
||||
public ProjectConfigEntry(String displayName, String defaultValue) {
|
||||
this.displayName = displayName;
|
||||
this.defaultValue = defaultValue;
|
||||
this.type = Type.STRING;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public String getDefaultValue() {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public Type getType() {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
@@ -18,17 +18,22 @@ import com.google.common.base.Strings;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.gerrit.extensions.registration.DynamicMap;
|
||||
import com.google.gerrit.extensions.registration.DynamicMap.Entry;
|
||||
import com.google.gerrit.extensions.restapi.RestView;
|
||||
import com.google.gerrit.extensions.webui.UiAction;
|
||||
import com.google.gerrit.reviewdb.client.Project;
|
||||
import com.google.gerrit.reviewdb.client.Project.InheritableBoolean;
|
||||
import com.google.gerrit.reviewdb.client.Project.SubmitType;
|
||||
import com.google.gerrit.server.actions.ActionInfo;
|
||||
import com.google.gerrit.server.config.PluginConfig;
|
||||
import com.google.gerrit.server.config.PluginConfigFactory;
|
||||
import com.google.gerrit.server.config.ProjectConfigEntry;
|
||||
import com.google.gerrit.server.extensions.webui.UiActions;
|
||||
import com.google.gerrit.server.git.TransferConfig;
|
||||
import com.google.inject.util.Providers;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
public class ConfigInfo {
|
||||
public final String kind = "gerritcodereview#project_config";
|
||||
@@ -41,6 +46,7 @@ public class ConfigInfo {
|
||||
public MaxObjectSizeLimitInfo maxObjectSizeLimit;
|
||||
public SubmitType submitType;
|
||||
public Project.State state;
|
||||
public Map<String, Map<String, ConfigParameterInfo>> pluginConfig;
|
||||
public Map<String, ActionInfo> actions;
|
||||
|
||||
public Map<String, CommentLinkInfo> commentlinks;
|
||||
@@ -48,6 +54,8 @@ public class ConfigInfo {
|
||||
|
||||
public ConfigInfo(ProjectControl control,
|
||||
TransferConfig config,
|
||||
DynamicMap<ProjectConfigEntry> pluginConfigEntries,
|
||||
PluginConfigFactory cfgFactory,
|
||||
DynamicMap<RestView<ProjectResource>> views) {
|
||||
ProjectState projectState = control.getProjectState();
|
||||
Project p = control.getProject();
|
||||
@@ -103,6 +111,9 @@ public class ConfigInfo {
|
||||
this.commentlinks.put(cl.name, cl);
|
||||
}
|
||||
|
||||
pluginConfig =
|
||||
getPluginConfig(control.getProjectState(), pluginConfigEntries, cfgFactory);
|
||||
|
||||
actions = Maps.newTreeMap();
|
||||
for (UiAction.Description d : UiActions.from(
|
||||
views, new ProjectResource(control),
|
||||
@@ -112,6 +123,28 @@ public class ConfigInfo {
|
||||
this.theme = projectState.getTheme();
|
||||
}
|
||||
|
||||
private Map<String, Map<String, ConfigParameterInfo>> getPluginConfig(
|
||||
ProjectState project, DynamicMap<ProjectConfigEntry> pluginConfigEntries,
|
||||
PluginConfigFactory cfgFactory) {
|
||||
TreeMap<String, Map<String, ConfigParameterInfo>> pluginConfig = new TreeMap<>();
|
||||
for (Entry<ProjectConfigEntry> e : pluginConfigEntries) {
|
||||
PluginConfig cfg =
|
||||
cfgFactory.getFromProjectConfig(project, e.getPluginName());
|
||||
ProjectConfigEntry configEntry = e.getProvider().get();
|
||||
ConfigParameterInfo p = new ConfigParameterInfo();
|
||||
p.displayName = configEntry.getDisplayName();
|
||||
p.type = configEntry.getType();
|
||||
p.value = cfg.getString(e.getExportName(), configEntry.getDefaultValue());
|
||||
Map<String, ConfigParameterInfo> pc = pluginConfig.get(e.getPluginName());
|
||||
if (pc == null) {
|
||||
pc = new TreeMap<>();
|
||||
pluginConfig.put(e.getPluginName(), pc);
|
||||
}
|
||||
pc.put(e.getExportName(), p);
|
||||
}
|
||||
return !pluginConfig.isEmpty() ? pluginConfig : null;
|
||||
}
|
||||
|
||||
public static class InheritedBooleanInfo {
|
||||
public Boolean value;
|
||||
public InheritableBoolean configuredValue;
|
||||
@@ -123,4 +156,10 @@ public class ConfigInfo {
|
||||
public String configuredValue;
|
||||
public String inheritedValue;
|
||||
}
|
||||
|
||||
public static class ConfigParameterInfo {
|
||||
public String displayName;
|
||||
public ProjectConfigEntry.Type type;
|
||||
public String value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ import com.google.gerrit.extensions.registration.DynamicMap;
|
||||
import com.google.gerrit.extensions.restapi.RestReadView;
|
||||
import com.google.gerrit.extensions.restapi.RestView;
|
||||
import com.google.gerrit.server.CurrentUser;
|
||||
import com.google.gerrit.server.config.PluginConfigFactory;
|
||||
import com.google.gerrit.server.config.ProjectConfigEntry;
|
||||
import com.google.gerrit.server.git.TransferConfig;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
@@ -25,18 +27,25 @@ import com.google.inject.Provider;
|
||||
public class GetConfig implements RestReadView<ProjectResource> {
|
||||
|
||||
private final TransferConfig config;
|
||||
private final DynamicMap<ProjectConfigEntry> pluginConfigEntries;
|
||||
private final PluginConfigFactory cfgFactory;
|
||||
private final DynamicMap<RestView<ProjectResource>> views;
|
||||
|
||||
@Inject
|
||||
public GetConfig(TransferConfig config,
|
||||
DynamicMap<ProjectConfigEntry> pluginConfigEntries,
|
||||
PluginConfigFactory cfgFactory,
|
||||
DynamicMap<RestView<ProjectResource>> views,
|
||||
Provider<CurrentUser> currentUser) {
|
||||
this.config = config;
|
||||
this.pluginConfigEntries = pluginConfigEntries;
|
||||
this.cfgFactory = cfgFactory;
|
||||
this.views = views;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigInfo apply(ProjectResource resource) {
|
||||
return new ConfigInfo(resource.getControl(), config, views);
|
||||
return new ConfigInfo(resource.getControl(), config,
|
||||
pluginConfigEntries, cfgFactory, views);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
package com.google.gerrit.server.project;
|
||||
|
||||
import com.google.common.base.CharMatcher;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.gerrit.extensions.registration.DynamicMap;
|
||||
import com.google.gerrit.extensions.restapi.BadRequestException;
|
||||
@@ -25,6 +26,9 @@ import com.google.gerrit.reviewdb.client.Project;
|
||||
import com.google.gerrit.reviewdb.client.Project.InheritableBoolean;
|
||||
import com.google.gerrit.reviewdb.client.Project.SubmitType;
|
||||
import com.google.gerrit.server.CurrentUser;
|
||||
import com.google.gerrit.server.config.PluginConfig;
|
||||
import com.google.gerrit.server.config.PluginConfigFactory;
|
||||
import com.google.gerrit.server.config.ProjectConfigEntry;
|
||||
import com.google.gerrit.server.git.MetaDataUpdate;
|
||||
import com.google.gerrit.server.git.ProjectConfig;
|
||||
import com.google.gerrit.server.git.TransferConfig;
|
||||
@@ -34,10 +38,16 @@ import com.google.inject.Provider;
|
||||
|
||||
import org.eclipse.jgit.errors.ConfigInvalidException;
|
||||
import org.eclipse.jgit.errors.RepositoryNotFoundException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
public class PutConfig implements RestModifyView<ProjectResource, Input> {
|
||||
private static final Logger log = LoggerFactory.getLogger(PutConfig.class);
|
||||
|
||||
public static class Input {
|
||||
public String description;
|
||||
public InheritableBoolean useContributorAgreements;
|
||||
@@ -47,6 +57,7 @@ public class PutConfig implements RestModifyView<ProjectResource, Input> {
|
||||
public String maxObjectSizeLimit;
|
||||
public SubmitType submitType;
|
||||
public Project.State state;
|
||||
public Map<String, Map<String, String>> pluginConfigValues;
|
||||
}
|
||||
|
||||
private final MetaDataUpdate.User metaDataUpdateFactory;
|
||||
@@ -54,6 +65,8 @@ public class PutConfig implements RestModifyView<ProjectResource, Input> {
|
||||
private final Provider<CurrentUser> self;
|
||||
private final ProjectState.Factory projectStateFactory;
|
||||
private final TransferConfig config;
|
||||
private final DynamicMap<ProjectConfigEntry> pluginConfigEntries;
|
||||
private final PluginConfigFactory cfgFactory;
|
||||
private final DynamicMap<RestView<ProjectResource>> views;
|
||||
private final Provider<CurrentUser> currentUser;
|
||||
|
||||
@@ -63,6 +76,8 @@ public class PutConfig implements RestModifyView<ProjectResource, Input> {
|
||||
Provider<CurrentUser> self,
|
||||
ProjectState.Factory projectStateFactory,
|
||||
TransferConfig config,
|
||||
DynamicMap<ProjectConfigEntry> pluginConfigEntries,
|
||||
PluginConfigFactory cfgFactory,
|
||||
DynamicMap<RestView<ProjectResource>> views,
|
||||
Provider<CurrentUser> currentUser) {
|
||||
this.metaDataUpdateFactory = metaDataUpdateFactory;
|
||||
@@ -70,6 +85,8 @@ public class PutConfig implements RestModifyView<ProjectResource, Input> {
|
||||
this.self = self;
|
||||
this.projectStateFactory = projectStateFactory;
|
||||
this.config = config;
|
||||
this.pluginConfigEntries = pluginConfigEntries;
|
||||
this.cfgFactory = cfgFactory;
|
||||
this.views = views;
|
||||
this.currentUser = currentUser;
|
||||
}
|
||||
@@ -126,6 +143,10 @@ public class PutConfig implements RestModifyView<ProjectResource, Input> {
|
||||
p.setState(input.state);
|
||||
}
|
||||
|
||||
if (input.pluginConfigValues != null) {
|
||||
setPluginConfigValues(projectConfig, input.pluginConfigValues);
|
||||
}
|
||||
|
||||
md.setMessage("Modified project settings\n");
|
||||
try {
|
||||
projectConfig.commit(md);
|
||||
@@ -143,7 +164,7 @@ public class PutConfig implements RestModifyView<ProjectResource, Input> {
|
||||
ProjectState state = projectStateFactory.create(projectConfig);
|
||||
return new ConfigInfo(
|
||||
state.controlFor(currentUser.get()),
|
||||
config, views);
|
||||
config, pluginConfigEntries, cfgFactory, views);
|
||||
} catch (ConfigInvalidException err) {
|
||||
throw new ResourceConflictException("Cannot read project " + projectName, err);
|
||||
} catch (IOException err) {
|
||||
@@ -152,4 +173,39 @@ public class PutConfig implements RestModifyView<ProjectResource, Input> {
|
||||
md.close();
|
||||
}
|
||||
}
|
||||
|
||||
private void setPluginConfigValues(ProjectConfig projectConfig,
|
||||
Map<String, Map<String, String>> pluginConfigValues)
|
||||
throws BadRequestException {
|
||||
for (Entry<String, Map<String, String>> e : pluginConfigValues.entrySet()) {
|
||||
String pluginName = e.getKey();
|
||||
PluginConfig cfg = projectConfig.getPluginConfig(pluginName);
|
||||
for (Entry<String, String> v : e.getValue().entrySet()) {
|
||||
ProjectConfigEntry projectConfigEntry =
|
||||
pluginConfigEntries.get(pluginName, v.getKey());
|
||||
if (projectConfigEntry != null) {
|
||||
if (!isValidParameterName(v.getKey())) {
|
||||
log.warn(String.format(
|
||||
"Parameter name '%s' must match '^[a-zA-Z0-9]+[a-zA-Z0-9-]*$'", v.getKey()));
|
||||
continue;
|
||||
}
|
||||
if (v.getValue() != null) {
|
||||
cfg.setString(v.getKey(), v.getValue());
|
||||
} else {
|
||||
cfg.unset(v.getKey());
|
||||
}
|
||||
} else {
|
||||
throw new BadRequestException(String.format(
|
||||
"The config parameter '%s' of plugin '%s' does not exist.",
|
||||
v.getKey(), pluginName));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isValidParameterName(String name) {
|
||||
return CharMatcher.JAVA_LETTER_OR_DIGIT
|
||||
.or(CharMatcher.is('-'))
|
||||
.matchesAllOf(name) && !name.startsWith("-");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user