Allow plugins to define custom data in SubmitRequirement

Replace the raw short/long reasons of SubmitRequirement with a dynamic
type and map, allowing for more powerful implementations later.
Also remove the label field, superseeded by the data Map.

Use AutoValues for the SubmitRequirement class, as it is marked
GwtIncompatible.

The idea behind this change is to provide a future proof API that
both plugins and end users will enjoy. A user interface is being
worked on, and will benefit of these changes.

Change-Id: I030609cd164d308f2231a2abba2eb16b09524b7f
This commit is contained in:
Maxime Guerreiro
2018-04-03 17:26:28 +00:00
parent 3817100d4a
commit 36c9725f3f
8 changed files with 106 additions and 78 deletions

View File

@@ -185,14 +185,16 @@ in order for the change to be submittable.
[[requirement]] [[requirement]]
== requirement == requirement
Information about a requirement (not met) in order to submit a change. Information about a requirement in order to submit a change.
shortReason:: A short description of the requirement (a hint). fallbackText:: A human readable description of the requirement.
fullReason:: A longer and descriptive message explaining what needs to type:: Alphanumerical (plus hyphens or underscores) string to identify what the requirement is and
be changed to meet the requirement. why it was triggered. Can be seen as a class: requirements sharing the same type were created for a
similar reason, and the data structure will follow one set of rules.
label:: (Optional) The name of the linked label, if set by a pre-submit rule. data:: (Optional) Additional key-value data linked to this requirement. This is used in templates to
render rich status messages.
[[label]] [[label]]
== label == label

View File

@@ -22,6 +22,7 @@ gwt_module(
"//lib:guava", "//lib:guava",
"//lib:gwtorm_client", "//lib:gwtorm_client",
"//lib:servlet-api-3_1", "//lib:servlet-api-3_1",
"//lib/auto:auto-value",
"//lib/jgit/org.eclipse.jgit:jgit", "//lib/jgit/org.eclipse.jgit:jgit",
"//lib/log:api", "//lib/log:api",
], ],
@@ -46,6 +47,7 @@ java_library(
"//lib:gwtjsonrpc", "//lib:gwtjsonrpc",
"//lib:gwtorm", "//lib:gwtorm",
"//lib:servlet-api-3_1", "//lib:servlet-api-3_1",
"//lib/auto:auto-value",
"//lib/jgit/org.eclipse.jgit:jgit", "//lib/jgit/org.eclipse.jgit:jgit",
"//lib/log:api", "//lib/log:api",
], ],

View File

@@ -14,6 +14,7 @@
package com.google.gerrit.common.data; package com.google.gerrit.common.data;
import com.google.common.annotations.GwtIncompatible;
import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.Account;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
@@ -60,7 +61,7 @@ public class SubmitRecord {
public Status status; public Status status;
public List<Label> labels; public List<Label> labels;
public List<SubmitRequirement> requirements; @GwtIncompatible public List<SubmitRequirement> requirements;
public String errorMessage; public String errorMessage;
public static class Label { public static class Label {
@@ -131,6 +132,7 @@ public class SubmitRecord {
} }
} }
@GwtIncompatible
@Override @Override
public String toString() { public String toString() {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
@@ -158,6 +160,7 @@ public class SubmitRecord {
return sb.toString(); return sb.toString();
} }
@GwtIncompatible
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (o instanceof SubmitRecord) { if (o instanceof SubmitRecord) {
@@ -170,6 +173,7 @@ public class SubmitRecord {
return false; return false;
} }
@GwtIncompatible
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(status, labels, errorMessage, requirements); return Objects.hash(status, labels, errorMessage, requirements);

View File

@@ -14,67 +14,65 @@
package com.google.gerrit.common.data; package com.google.gerrit.common.data;
import static java.util.Objects.requireNonNull; import com.google.auto.value.AutoValue;
import com.google.common.annotations.GwtIncompatible;
import com.google.gerrit.common.Nullable; import com.google.common.base.CharMatcher;
import java.util.Objects; import com.google.common.base.Preconditions;
import java.util.Optional; import com.google.common.collect.ImmutableMap;
import java.util.Map;
/** Describes a requirement to submit a change. */ /** Describes a requirement to submit a change. */
public final class SubmitRequirement { @GwtIncompatible
private final String shortReason; @AutoValue
private final String fullReason; @AutoValue.CopyAnnotations
@Nullable private final String label; public abstract class SubmitRequirement {
private static final CharMatcher TYPE_MATCHER =
CharMatcher.inRange('a', 'z')
.or(CharMatcher.inRange('A', 'Z'))
.or(CharMatcher.inRange('0', '9'))
.or(CharMatcher.anyOf("-_"));
public SubmitRequirement(String shortReason, String fullReason, @Nullable String label) { @AutoValue.Builder
this.shortReason = requireNonNull(shortReason); public abstract static class Builder {
this.fullReason = requireNonNull(fullReason); public abstract Builder setType(String value);
this.label = label;
}
public String shortReason() { public abstract Builder setFallbackText(String value);
return shortReason;
}
public String fullReason() { public Builder setData(Map<String, String> value) {
return fullReason; return setData(ImmutableMap.copyOf(value));
}
public Optional<String> label() {
return Optional.ofNullable(label);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
} }
if (o instanceof SubmitRequirement) {
SubmitRequirement that = (SubmitRequirement) o; public Builder addCustomValue(String key, String value) {
return Objects.equals(shortReason, that.shortReason) dataBuilder().put(key, value);
&& Objects.equals(fullReason, that.fullReason) return this;
&& Objects.equals(label, that.label);
} }
return false;
public SubmitRequirement build() {
SubmitRequirement requirement = autoBuild();
Preconditions.checkState(
validateType(requirement.type()),
"SubmitRequirement's type contains non alphanumerical symbols.");
return requirement;
}
abstract Builder setData(ImmutableMap<String, String> value);
abstract ImmutableMap.Builder<String, String> dataBuilder();
abstract SubmitRequirement autoBuild();
} }
@Override public abstract String fallbackText();
public int hashCode() {
return Objects.hash(shortReason, fullReason, label); public abstract String type();
public abstract ImmutableMap<String, String> data();
public static Builder builder() {
return new AutoValue_SubmitRequirement.Builder();
} }
@Override private static boolean validateType(String type) {
public String toString() { return TYPE_MATCHER.matchesAllOf(type);
return "SubmitRequirement{"
+ "shortReason='"
+ shortReason
+ '\''
+ ", fullReason='"
+ fullReason
+ '\''
+ ", label='"
+ label
+ '\''
+ '}';
} }
} }

View File

@@ -14,12 +14,14 @@
package com.google.gerrit.server.data; package com.google.gerrit.server.data;
import java.util.Map;
/** /**
* Represents a {@link com.google.gerrit.common.data.SubmitRequirement} that does not depend on * Represents a {@link com.google.gerrit.common.data.SubmitRequirement} that does not depend on
* Gerrit internal classes, to be serialized * Gerrit internal classes, to be serialized
*/ */
public class SubmitRequirementAttribute { public class SubmitRequirementAttribute {
public String shortReason; public Map<String, String> data;
public String fullReason; public String type;
public String label; public String fallbackText;
} }

View File

@@ -261,9 +261,9 @@ public class EventFactory {
sa.requirements = new ArrayList<>(); sa.requirements = new ArrayList<>();
for (SubmitRequirement req : submitRecord.requirements) { for (SubmitRequirement req : submitRecord.requirements) {
SubmitRequirementAttribute re = new SubmitRequirementAttribute(); SubmitRequirementAttribute re = new SubmitRequirementAttribute();
re.shortReason = req.shortReason(); re.fallbackText = req.fallbackText();
re.fullReason = req.fullReason(); re.type = req.type();
re.label = req.label().orElse(null); re.data = req.data();
sa.requirements.add(re); sa.requirements.add(re);
} }
} }

View File

@@ -36,7 +36,6 @@ import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Table; import com.google.common.collect.Table;
import com.google.common.primitives.Longs; import com.google.common.primitives.Longs;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.SubmitRecord; import com.google.gerrit.common.data.SubmitRecord;
import com.google.gerrit.common.data.SubmitRequirement; import com.google.gerrit.common.data.SubmitRequirement;
import com.google.gerrit.index.FieldDef; import com.google.gerrit.index.FieldDef;
@@ -77,6 +76,7 @@ import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
@@ -657,9 +657,9 @@ public class ChangeField {
} }
static class StoredRequirement { static class StoredRequirement {
String shortReason; String fallbackText;
String fullReason; String type;
@Nullable String label; Map<String, String> data;
} }
SubmitRecord.Status status; SubmitRecord.Status status;
@@ -684,9 +684,9 @@ public class ChangeField {
this.requirements = new ArrayList<>(rec.requirements.size()); this.requirements = new ArrayList<>(rec.requirements.size());
for (SubmitRequirement requirement : rec.requirements) { for (SubmitRequirement requirement : rec.requirements) {
StoredRequirement sr = new StoredRequirement(); StoredRequirement sr = new StoredRequirement();
sr.shortReason = requirement.shortReason(); sr.type = requirement.type();
sr.fullReason = requirement.fullReason(); sr.fallbackText = requirement.fallbackText();
sr.label = requirement.label().orElse(null); sr.data = requirement.data();
this.requirements.add(sr); this.requirements.add(sr);
} }
} }
@@ -708,10 +708,13 @@ public class ChangeField {
} }
if (requirements != null) { if (requirements != null) {
rec.requirements = new ArrayList<>(requirements.size()); rec.requirements = new ArrayList<>(requirements.size());
for (StoredRequirement requirement : requirements) { for (StoredRequirement req : requirements) {
SubmitRequirement sr = SubmitRequirement sr =
new SubmitRequirement( SubmitRequirement.builder()
requirement.shortReason, requirement.fullReason, requirement.label); .setType(req.type)
.setFallbackText(req.fallbackText)
.setData(req.data)
.build();
rec.requirements.add(sr); rec.requirements.add(sr);
} }
} }

View File

@@ -93,7 +93,7 @@ public class ChangeFieldTest extends GerritBaseTests {
} }
@Test @Test
public void storedSubmitRecordsWithRequirements() { public void storedSubmitRecordsWithRequirement() {
SubmitRecord r = SubmitRecord r =
record( record(
SubmitRecord.Status.OK, SubmitRecord.Status.OK,
@@ -101,10 +101,27 @@ public class ChangeFieldTest extends GerritBaseTests {
label(SubmitRecord.Label.Status.OK, "Label-2", 1)); label(SubmitRecord.Label.Status.OK, "Label-2", 1));
SubmitRequirement sr = SubmitRequirement sr =
new SubmitRequirement( SubmitRequirement.builder()
"short reason", .setType("short_type")
"Full reason can be a long string with special symbols like < > \\ / ; :", .setFallbackText("Fallback text may contain special symbols like < > \\ / ; :")
null); .addCustomValue("custom_data", "my value")
.build();
r.requirements = Collections.singletonList(sr);
assertStoredRecordRoundTrip(r);
}
@Test
public void storedSubmitRequirementWithoutCustomData() {
SubmitRecord r =
record(
SubmitRecord.Status.OK,
label(SubmitRecord.Label.Status.MAY, "Label-1", null),
label(SubmitRecord.Label.Status.OK, "Label-2", 1));
// Doesn't have any custom data value
SubmitRequirement sr =
SubmitRequirement.builder().setFallbackText("short_type").setType("ci_status").build();
r.requirements = Collections.singletonList(sr); r.requirements = Collections.singletonList(sr);
assertStoredRecordRoundTrip(r); assertStoredRecordRoundTrip(r);