Add reviewer.enableByEmail to ProjectConfig

This change adds a setting to enable reviewers and CCs by email to the
ProjectConfig. It also adds tests, docs, API and UI support.

Bug: Issue 4134
Change-Id: Ibe6ccb80f9f71f2a430afcd6e12b3f27cf8fdbd6
This commit is contained in:
Patrick Hiesel
2017-03-21 09:40:03 +01:00
parent 1d31699e03
commit 6db5afdf59
9 changed files with 89 additions and 8 deletions

View File

@@ -291,6 +291,18 @@ Branches not listed in this section will not be included in the mergeability
check. If the `branchOrder` section is not defined then the mergeability of a
change into other branches will not be done.
[[reviewer-section]]
=== reviewer section
Defines config options to adjust a project's reviewer workflow such as enabling
reviewers and CCs by email.
[[reviewer.enableByEmail]]reviewer.enableByEmail::
+
A boolean indicating if reviewers and CCs that do not currently have a Gerrit
account can be added to a change by providing their email address.
Defaults to `false`.
[[file-groups]]
== The file +groups+

View File

@@ -31,6 +31,7 @@ public class ConfigInfo {
public InheritedBooleanInfo enableSignedPush;
public InheritedBooleanInfo requireSignedPush;
public InheritedBooleanInfo rejectImplicitMerges;
public InheritedBooleanInfo enableReviewerByEmail;
public MaxObjectSizeLimitInfo maxObjectSizeLimit;
public SubmitType submitType;
public ProjectState state;

View File

@@ -86,6 +86,7 @@ public class ProjectInfoScreen extends ProjectScreen {
private ListBox enableSignedPush;
private ListBox requireSignedPush;
private ListBox rejectImplicitMerges;
private ListBox enableReviewerByEmail;
private NpTextBox maxObjectSizeLimit;
private Label effectiveMaxObjectSizeLimit;
private Map<String, Map<String, HasEnabled>> pluginConfigWidgets;
@@ -191,6 +192,7 @@ public class ProjectInfoScreen extends ProjectScreen {
requireChangeID.setEnabled(isOwner);
rejectImplicitMerges.setEnabled(isOwner);
maxObjectSizeLimit.setEnabled(isOwner);
enableReviewerByEmail.setEnabled(isOwner);
if (pluginConfigWidgets != null) {
for (Map<String, HasEnabled> widgetMap : pluginConfigWidgets.values()) {
@@ -264,6 +266,10 @@ public class ProjectInfoScreen extends ProjectScreen {
saveEnabler.listenTo(rejectImplicitMerges);
grid.addHtml(AdminConstants.I.rejectImplicitMerges(), rejectImplicitMerges);
enableReviewerByEmail = newInheritedBooleanBox();
saveEnabler.listenTo(enableReviewerByEmail);
grid.addHtml(AdminConstants.I.rejectImplicitMerges(), enableReviewerByEmail);
maxObjectSizeLimit = new NpTextBox();
saveEnabler.listenTo(maxObjectSizeLimit);
effectiveMaxObjectSizeLimit = new Label();
@@ -395,6 +401,7 @@ public class ProjectInfoScreen extends ProjectScreen {
setBool(requireSignedPush, result.requireSignedPush());
}
setBool(rejectImplicitMerges, result.rejectImplicitMerges());
setBool(enableReviewerByEmail, result.enableReviewerByEmail());
setSubmitType(result.submitType());
setState(result.state());
maxObjectSizeLimit.setText(result.maxObjectSizeLimit().configuredValue());

View File

@@ -57,6 +57,9 @@ public class ConfigInfo extends JavaScriptObject {
public final native InheritedBooleanInfo rejectImplicitMerges()
/*-{ return this.reject_implicit_merges; }-*/ ;
public final native InheritedBooleanInfo enableReviewerByEmail()
/*-{ return this.enable_reviewer_by_email; }-*/ ;
public final SubmitType submitType() {
return SubmitType.valueOf(submitTypeRaw());
}

View File

@@ -99,6 +99,8 @@ public final class Project {
protected InheritableBoolean rejectImplicitMerges;
protected InheritableBoolean enableReviewerByEmail;
protected Project() {}
public Project(Project.NameKey nameKey) {
@@ -112,6 +114,7 @@ public final class Project {
createNewChangeForAllNotInTarget = InheritableBoolean.INHERIT;
enableSignedPush = InheritableBoolean.INHERIT;
requireSignedPush = InheritableBoolean.INHERIT;
enableReviewerByEmail = InheritableBoolean.INHERIT;
}
public Project.NameKey getNameKey() {
@@ -154,6 +157,10 @@ public final class Project {
return rejectImplicitMerges;
}
public InheritableBoolean getEnableReviewerByEmail() {
return enableReviewerByEmail;
}
public void setUseContributorAgreements(final InheritableBoolean u) {
useContributorAgreements = u;
}

View File

@@ -155,6 +155,9 @@ public class ProjectConfig extends VersionedMetaData implements ValidationError.
ImmutableSet.of(
"MaxWithBlock", "AnyWithBlock", "MaxNoBlock", "NoBlock", "NoOp", "PatchSetLock");
private static final String REVIEWER = "reviewer";
private static final String KEY_ENABLE_REVIEWER_BY_EMAIL = "enableByEmail";
private static final String LEGACY_PERMISSION_PUSH_TAG = "pushTag";
private static final String LEGACY_PERMISSION_PUSH_SIGNED_TAG = "pushSignedTag";
@@ -182,6 +185,7 @@ public class ProjectConfig extends VersionedMetaData implements ValidationError.
private boolean checkReceivedObjects;
private Set<String> sectionsWithUnknownPermissions;
private boolean hasLegacyPermissions;
private boolean enableReviewerByEmail;
public static ProjectConfig read(MetaDataUpdate update)
throws IOException, ConfigInvalidException {
@@ -435,6 +439,16 @@ public class ProjectConfig extends VersionedMetaData implements ValidationError.
return checkReceivedObjects;
}
/** @return the enableReviewerByEmail for this project, default is false. */
public boolean getEnableReviewerByEmail() {
return enableReviewerByEmail;
}
/** Set enableReviewerByEmail for this project, default is false. */
public void setEnableReviewerByEmail(boolean val) {
enableReviewerByEmail = val;
}
/**
* Check all GroupReferences use current group name, repairing stale ones.
*
@@ -526,6 +540,7 @@ public class ProjectConfig extends VersionedMetaData implements ValidationError.
mimeTypes = new ConfiguredMimeTypes(projectName.get(), rc);
loadPluginSections(rc);
loadReceiveSection(rc);
loadReviewerSection(rc);
}
private void loadAccountsSection(Config rc, Map<String, GroupReference> groupsByName) {
@@ -933,6 +948,10 @@ public class ProjectConfig extends VersionedMetaData implements ValidationError.
maxObjectSizeLimit = rc.getLong(RECEIVE, null, KEY_MAX_OBJECT_SIZE_LIMIT, 0);
}
private void loadReviewerSection(Config rc) {
enableReviewerByEmail = rc.getBoolean(REVIEWER, null, KEY_ENABLE_REVIEWER_BY_EMAIL, false);
}
private void loadPluginSections(Config rc) {
pluginConfigs = new HashMap<>();
for (String plugin : rc.getSubsections(PLUGIN)) {
@@ -1067,6 +1086,7 @@ public class ProjectConfig extends VersionedMetaData implements ValidationError.
saveAccessSections(rc, keepGroups);
saveNotifySections(rc, keepGroups);
savePluginSections(rc, keepGroups);
saveReviewerSection(rc);
groupList.retainUUIDs(keepGroups);
saveLabelSections(rc);
saveSubscribeSections(rc);
@@ -1288,40 +1308,55 @@ public class ProjectConfig extends VersionedMetaData implements ValidationError.
setBooleanConfigKey(
rc,
LABEL,
name,
KEY_ALLOW_POST_SUBMIT,
label.allowPostSubmit(),
LabelType.DEF_ALLOW_POST_SUBMIT);
setBooleanConfigKey(
rc, name, KEY_COPY_MIN_SCORE, label.isCopyMinScore(), LabelType.DEF_COPY_MIN_SCORE);
setBooleanConfigKey(
rc, name, KEY_COPY_MAX_SCORE, label.isCopyMaxScore(), LabelType.DEF_COPY_MAX_SCORE);
rc,
LABEL,
name,
KEY_COPY_MIN_SCORE,
label.isCopyMinScore(),
LabelType.DEF_COPY_MIN_SCORE);
setBooleanConfigKey(
rc,
LABEL,
name,
KEY_COPY_MAX_SCORE,
label.isCopyMaxScore(),
LabelType.DEF_COPY_MAX_SCORE);
setBooleanConfigKey(
rc,
LABEL,
name,
KEY_COPY_ALL_SCORES_ON_TRIVIAL_REBASE,
label.isCopyAllScoresOnTrivialRebase(),
LabelType.DEF_COPY_ALL_SCORES_ON_TRIVIAL_REBASE);
setBooleanConfigKey(
rc,
LABEL,
name,
KEY_COPY_ALL_SCORES_IF_NO_CODE_CHANGE,
label.isCopyAllScoresIfNoCodeChange(),
LabelType.DEF_COPY_ALL_SCORES_IF_NO_CODE_CHANGE);
setBooleanConfigKey(
rc,
LABEL,
name,
KEY_COPY_ALL_SCORES_IF_NO_CHANGE,
label.isCopyAllScoresIfNoChange(),
LabelType.DEF_COPY_ALL_SCORES_IF_NO_CHANGE);
setBooleanConfigKey(
rc,
LABEL,
name,
KEY_COPY_ALL_SCORES_ON_MERGE_FIRST_PARENT_UPDATE,
label.isCopyAllScoresOnMergeFirstParentUpdate(),
LabelType.DEF_COPY_ALL_SCORES_ON_MERGE_FIRST_PARENT_UPDATE);
setBooleanConfigKey(
rc, name, KEY_CAN_OVERRIDE, label.canOverride(), LabelType.DEF_CAN_OVERRIDE);
rc, LABEL, name, KEY_CAN_OVERRIDE, label.canOverride(), LabelType.DEF_CAN_OVERRIDE);
List<String> values = Lists.newArrayListWithCapacity(label.getValues().size());
for (LabelValue value : label.getValues()) {
values.add(value.format());
@@ -1335,11 +1370,11 @@ public class ProjectConfig extends VersionedMetaData implements ValidationError.
}
private static void setBooleanConfigKey(
Config rc, String name, String key, boolean value, boolean defaultValue) {
Config rc, String section, String name, String key, boolean value, boolean defaultValue) {
if (value == defaultValue) {
rc.unset(LABEL, name, key);
rc.unset(section, name, key);
} else {
rc.setBoolean(LABEL, name, key, value);
rc.setBoolean(section, name, key, value);
}
}
@@ -1367,6 +1402,11 @@ public class ProjectConfig extends VersionedMetaData implements ValidationError.
}
}
private void saveReviewerSection(Config rc) {
setBooleanConfigKey(
rc, REVIEWER, null, KEY_ENABLE_REVIEWER_BY_EMAIL, enableReviewerByEmail, false);
}
private void saveGroupList() throws IOException {
saveUTF8(GroupList.FILE_NAME, groupList.asText());
}

View File

@@ -58,6 +58,7 @@ public class ConfigInfoImpl extends ConfigInfo {
InheritedBooleanInfo enableSignedPush = new InheritedBooleanInfo();
InheritedBooleanInfo requireSignedPush = new InheritedBooleanInfo();
InheritedBooleanInfo rejectImplicitMerges = new InheritedBooleanInfo();
InheritedBooleanInfo enableReviewerByEmail = new InheritedBooleanInfo();
useContributorAgreements.value = projectState.isUseContributorAgreements();
useSignedOffBy.value = projectState.isUseSignedOffBy();
@@ -73,6 +74,7 @@ public class ConfigInfoImpl extends ConfigInfo {
enableSignedPush.configuredValue = p.getEnableSignedPush();
requireSignedPush.configuredValue = p.getRequireSignedPush();
rejectImplicitMerges.configuredValue = p.getRejectImplicitMerges();
enableReviewerByEmail.configuredValue = p.getEnableReviewerByEmail();
ProjectState parentState = Iterables.getFirst(projectState.parents(), null);
if (parentState != null) {
@@ -85,6 +87,7 @@ public class ConfigInfoImpl extends ConfigInfo {
enableSignedPush.inheritedValue = projectState.isEnableSignedPush();
requireSignedPush.inheritedValue = projectState.isRequireSignedPush();
rejectImplicitMerges.inheritedValue = projectState.isRejectImplicitMerges();
enableReviewerByEmail.inheritedValue = projectState.isEnableReviewerByEmail();
}
this.useContributorAgreements = useContributorAgreements;

View File

@@ -394,6 +394,10 @@ public class ProjectState {
return getInheritableBoolean(Project::getRejectImplicitMerges);
}
public boolean isEnableReviewerByEmail() {
return getInheritableBoolean(Project::getEnableReviewerByEmail);
}
public LabelTypes getLabelTypes() {
Map<String, LabelType> types = new LinkedHashMap<>();
for (ProjectState s : treeInOrder()) {

View File

@@ -105,7 +105,9 @@ public class ProjectConfigTest extends LocalDiskRepositoryTestCase {
+ " accepted = group Developers\n" //
+ " accepted = group Staff\n" //
+ " autoVerify = group Developers\n" //
+ " agreementUrl = http://www.example.com/agree\n")) //
+ " agreementUrl = http://www.example.com/agree\n" //
+ "[reviewer]\n" //
+ " enableByEmail = true\n")) //
));
ProjectConfig cfg = read(rev);
@@ -132,6 +134,8 @@ public class ProjectConfigTest extends LocalDiskRepositoryTestCase {
assertThat(submit.getExclusiveGroup()).isTrue();
assertThat(read.getExclusiveGroup()).isTrue();
assertThat(push.getExclusiveGroup()).isFalse();
assertThat(cfg.getEnableReviewerByEmail()).isTrue();
}
@Test