Merge "Submit Requirements - overridden icon"
diff --git a/Documentation/user-search.txt b/Documentation/user-search.txt
index 3977278..cc8d813 100644
--- a/Documentation/user-search.txt
+++ b/Documentation/user-search.txt
@@ -136,6 +136,19 @@
+
Changes originally submitted by a user in 'GROUP'.
+[[uploader]]
+uploader:'USER'::
++
+Changes where the latest patch set was uploaded by 'USER'.
+The special case of `uploader:self` will find changes uploaded
+by the caller.
+
+[[uploaderin]]
+uploaderin:'GROUP'::
++
+Changes where the latest patch set was uploaded by a user in
+'GROUP'.
+
[[query]]
query:'[name=]NAME[,user=USER]'::
+
@@ -466,6 +479,12 @@
True on any change where the current user is the change owner.
Same as `owner:self`.
+is:uploader::
++
+True on any change where the current user is the uploader of
+the latest patch set.
+Same as `uploader:self`.
+
is:reviewer::
+
True on any change where the current user is a reviewer.
diff --git a/java/com/google/gerrit/entities/SubmitRequirementResult.java b/java/com/google/gerrit/entities/SubmitRequirementResult.java
index b7fa398..13625c1 100644
--- a/java/com/google/gerrit/entities/SubmitRequirementResult.java
+++ b/java/com/google/gerrit/entities/SubmitRequirementResult.java
@@ -44,8 +44,14 @@
/**
* Whether this result was created from a legacy {@link SubmitRecord}, or by evaluating a {@link
* SubmitRequirement}.
+ *
+ * <p>If equals {@link Optional#empty()}, we treat the result as non-legacy (false).
*/
- public abstract boolean legacy();
+ public abstract Optional<Boolean> legacy();
+
+ public boolean isLegacy() {
+ return legacy().orElse(false);
+ }
@Memoized
public Status status() {
@@ -122,7 +128,7 @@
public abstract Builder patchSetCommitId(ObjectId value);
- public abstract Builder legacy(boolean value);
+ public abstract Builder legacy(Optional<Boolean> value);
public abstract SubmitRequirementResult build();
}
diff --git a/java/com/google/gerrit/server/change/ChangeJson.java b/java/com/google/gerrit/server/change/ChangeJson.java
index 9a94d93..328c5de 100644
--- a/java/com/google/gerrit/server/change/ChangeJson.java
+++ b/java/com/google/gerrit/server/change/ChangeJson.java
@@ -99,6 +99,8 @@
import com.google.gerrit.server.cancellation.RequestCancelledException;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.TrackingFooters;
+import com.google.gerrit.server.experiments.ExperimentFeatures;
+import com.google.gerrit.server.experiments.ExperimentFeaturesConstants;
import com.google.gerrit.server.index.change.ChangeField;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.ReviewerStateInternal;
@@ -253,6 +255,7 @@
TrackingFooters trackingFooters,
Metrics metrics,
RevisionJson.Factory revisionJsonFactory,
+ ExperimentFeatures experimentFeatures,
@GerritServerConfig Config cfg,
@Assisted Iterable<ListChangesOption> options,
@Assisted Optional<PluginDefinedInfosFactory> pluginDefinedInfosFactory) {
@@ -271,7 +274,9 @@
this.revisionJson = revisionJsonFactory.create(options);
this.options = Sets.immutableEnumSet(options);
this.includeMergeable = MergeabilityComputationBehavior.fromConfig(cfg).includeInApi();
- this.lazyLoad = containsAnyOf(this.options, REQUIRE_LAZY_LOAD);
+ this.lazyLoad =
+ containsAnyOf(this.options, REQUIRE_LAZY_LOAD)
+ || lazyloadSubmitRequirements(this.options, experimentFeatures);
this.pluginDefinedInfosFactory = pluginDefinedInfosFactory;
logger.atFine().log("options = %s", options);
@@ -938,4 +943,20 @@
}
return ImmutableListMultimap.of();
}
+
+ private static boolean lazyloadSubmitRequirements(
+ Set<ListChangesOption> changeOptions, ExperimentFeatures experimentFeatures) {
+ // TODO(ghareeb,hiesel): Remove this method.
+ // We are testing the new submit requirements with users in lieu of upgrading the change index
+ // to a version that supports the new requirements.
+ // Upgrading now, before the feature is finalized would be counter productive, because the index
+ // format might change while we iterate over the feature.
+ // Allowing changes to lazyload parameters will slow down dashboards for users who have this
+ // feature enabled, but will backfill submit requirements that weren't loaded from the index by
+ // simply computing them.
+ return changeOptions.contains(SUBMIT_REQUIREMENTS)
+ && experimentFeatures.isFeatureEnabled(
+ ExperimentFeaturesConstants
+ .GERRIT_BACKEND_REQUEST_FEATURE_ENABLE_SUBMIT_REQUIREMENTS_BACKFILLING_ON_DASHBOARD);
+ }
}
diff --git a/java/com/google/gerrit/server/change/SubmitRequirementsJson.java b/java/com/google/gerrit/server/change/SubmitRequirementsJson.java
index ebb0790..8eeec62 100644
--- a/java/com/google/gerrit/server/change/SubmitRequirementsJson.java
+++ b/java/com/google/gerrit/server/change/SubmitRequirementsJson.java
@@ -47,7 +47,7 @@
submitRequirementExpressionToInfo(
req.submittabilityExpression(), result.submittabilityExpressionResult());
info.status = SubmitRequirementResultInfo.Status.valueOf(result.status().toString());
- info.isLegacy = result.legacy();
+ info.isLegacy = result.isLegacy();
return info;
}
diff --git a/java/com/google/gerrit/server/experiments/ExperimentFeaturesConstants.java b/java/com/google/gerrit/server/experiments/ExperimentFeaturesConstants.java
index b060d3e..1486559 100644
--- a/java/com/google/gerrit/server/experiments/ExperimentFeaturesConstants.java
+++ b/java/com/google/gerrit/server/experiments/ExperimentFeaturesConstants.java
@@ -25,13 +25,25 @@
public static String GERRIT_BACKEND_REQUEST_FEATURE_REMOVE_REVISION_ETAG =
"GerritBackendRequestFeature__remove_revision_etag";
+ /** Enable storing submit requirements in NoteDb when the change is merged. */
+ public static final String GERRIT_BACKEND_REQUEST_FEATURE_STORE_SUBMIT_REQUIREMENTS_ON_MERGE =
+ "GerritBackendRequestFeature__store_submit_requirements_on_merge";
+
/**
* Allow legacy {@link com.google.gerrit.entities.SubmitRecord}s to be converted and returned as
* submit requirements by the {@link
* com.google.gerrit.server.project.SubmitRequirementsEvaluator}.
*/
- public static final String GERRIT_BACKEND_REQUEST_FEATURE_ENABLE_LEGACY_SUBMIT_REQUIREMENTS =
- "GerritBackendRequestFeature__enable_legacy_submit_requirements";
+ public static final String GERRIT_BACKEND_REQUEST_FEATURE_ENABLE_SUBMIT_REQUIREMENTS =
+ "GerritBackendRequestFeature__enable_submit_requirements";
+
+ /**
+ * Allow SubmitRequirements to be computed freshly on dashboards irrespective of the value we
+ * retrieved from the change index.
+ */
+ public static final String
+ GERRIT_BACKEND_REQUEST_FEATURE_ENABLE_SUBMIT_REQUIREMENTS_BACKFILLING_ON_DASHBOARD =
+ "GerritBackendRequestFeature__enable_submit_requirements_backfilling_on_dashboard";
/** Features, enabled by default in the current release. */
public static final ImmutableSet<String> DEFAULT_ENABLED_FEATURES =
diff --git a/java/com/google/gerrit/server/index/change/ChangeField.java b/java/com/google/gerrit/server/index/change/ChangeField.java
index bfe1ee1..b9569e4 100644
--- a/java/com/google/gerrit/server/index/change/ChangeField.java
+++ b/java/com/google/gerrit/server/index/change/ChangeField.java
@@ -294,6 +294,10 @@
public static final FieldDef<ChangeData, Integer> OWNER =
integer(ChangeQueryBuilder.FIELD_OWNER).build(changeGetter(c -> c.getOwner().get()));
+ /** Uploader of the latest patch set. */
+ public static final FieldDef<ChangeData, Integer> UPLOADER =
+ integer(ChangeQueryBuilder.FIELD_UPLOADER).build(cd -> cd.currentPatchSet().uploader().get());
+
/** References the source change number that this change was cherry-picked from. */
public static final FieldDef<ChangeData, Integer> CHERRY_PICK_OF_CHANGE =
integer(ChangeQueryBuilder.FIELD_CHERRY_PICK_OF_CHANGE)
diff --git a/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java b/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java
index 30ab6e6a..9339d62 100644
--- a/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java
+++ b/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java
@@ -178,9 +178,14 @@
new Schema.Builder<ChangeData>().add(V68).add(ChangeField.CHERRY_PICK).build();
/** Added new field {@link ChangeField#ATTENTION_SET_USERS_COUNT}. */
+ @Deprecated
static final Schema<ChangeData> V70 =
new Schema.Builder<ChangeData>().add(V69).add(ChangeField.ATTENTION_SET_USERS_COUNT).build();
+ /** Added new field {@link ChangeField#UPLOADER}. */
+ static final Schema<ChangeData> V71 =
+ new Schema.Builder<ChangeData>().add(V70).add(ChangeField.UPLOADER).build();
+
/**
* Name of the change index to be used when contacting index backends or loading configurations.
*/
diff --git a/java/com/google/gerrit/server/notedb/CommitRewriter.java b/java/com/google/gerrit/server/notedb/CommitRewriter.java
index eabee65..338b984 100644
--- a/java/com/google/gerrit/server/notedb/CommitRewriter.java
+++ b/java/com/google/gerrit/server/notedb/CommitRewriter.java
@@ -53,6 +53,7 @@
import com.google.inject.Singleton;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
+import java.io.Serializable;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
@@ -108,7 +109,7 @@
@Singleton
public class CommitRewriter {
/** Options to run {@link #backfillProject}. */
- public static class RunOptions {
+ public static class RunOptions implements Serializable {
/** Whether to rewrite the commit history or only find refs that need to be fixed. */
public boolean dryRun = true;
/**
diff --git a/java/com/google/gerrit/server/notedb/StoreSubmitRequirementsOp.java b/java/com/google/gerrit/server/notedb/StoreSubmitRequirementsOp.java
index 1a7d5af..d128633 100644
--- a/java/com/google/gerrit/server/notedb/StoreSubmitRequirementsOp.java
+++ b/java/com/google/gerrit/server/notedb/StoreSubmitRequirementsOp.java
@@ -14,6 +14,8 @@
package com.google.gerrit.server.notedb;
+import com.google.gerrit.server.experiments.ExperimentFeatures;
+import com.google.gerrit.server.experiments.ExperimentFeaturesConstants;
import com.google.gerrit.server.project.SubmitRequirementsEvaluator;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.update.BatchUpdateOp;
@@ -24,6 +26,7 @@
public class StoreSubmitRequirementsOp implements BatchUpdateOp {
private final ChangeData.Factory changeDataFactory;
private final SubmitRequirementsEvaluator evaluator;
+ private final boolean storeRequirementsInNoteDb;
public interface Factory {
StoreSubmitRequirementsOp create();
@@ -31,13 +34,23 @@
@Inject
public StoreSubmitRequirementsOp(
- ChangeData.Factory changeDataFactory, SubmitRequirementsEvaluator evaluator) {
+ ChangeData.Factory changeDataFactory,
+ ExperimentFeatures experimentFeatures,
+ SubmitRequirementsEvaluator evaluator) {
this.changeDataFactory = changeDataFactory;
this.evaluator = evaluator;
+ this.storeRequirementsInNoteDb =
+ experimentFeatures.isFeatureEnabled(
+ ExperimentFeaturesConstants
+ .GERRIT_BACKEND_REQUEST_FEATURE_STORE_SUBMIT_REQUIREMENTS_ON_MERGE);
}
@Override
public boolean updateChange(ChangeContext ctx) throws Exception {
+ if (!storeRequirementsInNoteDb) {
+ // Temporarily stop storing submit requirements in NoteDb when the change is merged.
+ return false;
+ }
// Create ChangeData using the project/change IDs instead of ctx.getChange(). We do that because
// for changes requiring a rebase before submission (e.g. if submit type = RebaseAlways), the
// RebaseOp inserts a new patchset that is visible here (via Change#getCurrentPatchset). If we
diff --git a/java/com/google/gerrit/server/notedb/SubmitRequirementProtoConverter.java b/java/com/google/gerrit/server/notedb/SubmitRequirementProtoConverter.java
index 9bf56d8..3caa4d4 100644
--- a/java/com/google/gerrit/server/notedb/SubmitRequirementProtoConverter.java
+++ b/java/com/google/gerrit/server/notedb/SubmitRequirementProtoConverter.java
@@ -34,14 +34,18 @@
SubmitRequirementResultProto.getDescriptor().findFieldByNumber(2);
private static final FieldDescriptor SR_OVERRIDE_EXPR_RESULT_FIELD =
SubmitRequirementResultProto.getDescriptor().findFieldByNumber(4);
+ private static final FieldDescriptor SR_LEGACY_FIELD =
+ SubmitRequirementResultProto.getDescriptor().findFieldByNumber(6);
@Override
public SubmitRequirementResultProto toProto(SubmitRequirementResult r) {
SubmitRequirementResultProto.Builder builder = SubmitRequirementResultProto.newBuilder();
builder
.setSubmitRequirement(SubmitRequirementSerializer.serialize(r.submitRequirement()))
- .setLegacy(r.legacy())
.setCommit(ObjectIdConverter.create().toByteString(r.patchSetCommitId()));
+ if (r.legacy().isPresent()) {
+ builder.setLegacy(r.legacy().get());
+ }
if (r.applicabilityExpressionResult().isPresent()) {
builder.setApplicabilityExpressionResult(
SubmitRequirementExpressionResultSerializer.serialize(
@@ -61,10 +65,12 @@
public SubmitRequirementResult fromProto(SubmitRequirementResultProto proto) {
SubmitRequirementResult.Builder builder =
SubmitRequirementResult.builder()
- .legacy(proto.getLegacy())
.patchSetCommitId(ObjectIdConverter.create().fromByteString(proto.getCommit()))
.submitRequirement(
SubmitRequirementSerializer.deserialize(proto.getSubmitRequirement()));
+ if (proto.hasField(SR_LEGACY_FIELD)) {
+ builder.legacy(Optional.of(proto.getLegacy()));
+ }
if (proto.hasField(SR_APPLICABILITY_EXPR_RESULT_FIELD)) {
builder.applicabilityExpressionResult(
Optional.of(
diff --git a/java/com/google/gerrit/server/project/ProjectConfig.java b/java/com/google/gerrit/server/project/ProjectConfig.java
index 513aeed..975ad52 100644
--- a/java/com/google/gerrit/server/project/ProjectConfig.java
+++ b/java/com/google/gerrit/server/project/ProjectConfig.java
@@ -125,7 +125,6 @@
public static final String KEY_BRANCH = "branch";
public static final String SUBMIT_REQUIREMENT = "submit-requirement";
- public static final String KEY_SR_NAME = "name";
public static final String KEY_SR_DESCRIPTION = "description";
public static final String KEY_SR_APPLICABILITY_EXPRESSION = "applicableIf";
public static final String KEY_SR_SUBMITTABILITY_EXPRESSION = "submittableIf";
@@ -643,7 +642,7 @@
if (rc.getStringList(ACCESS, null, KEY_INHERIT_FROM).length > 1) {
// The config must not contain more than one parent to inherit from
// as there is no guarantee which of the parents would be used then.
- error(ValidationError.create(PROJECT_CONFIG, "Cannot inherit from multiple projects"));
+ error("Cannot inherit from multiple projects");
}
p.setParent(rc.getString(ACCESS, null, KEY_INHERIT_FROM));
@@ -696,10 +695,8 @@
String lower = name.toLowerCase();
if (lowerNames.containsKey(lower)) {
error(
- ValidationError.create(
- PROJECT_CONFIG,
- String.format(
- "Extension Panels \"%s\" conflicts with \"%s\"", name, lowerNames.get(lower))));
+ String.format(
+ "Extension Panels \"%s\" conflicts with \"%s\"", name, lowerNames.get(lower)));
}
lowerNames.put(lower, name);
extensionPanelSections.put(
@@ -725,26 +722,14 @@
ca.setAutoVerify(null);
} else if (rules.size() > 1) {
error(
- ValidationError.create(
- PROJECT_CONFIG,
- "Invalid rule in "
- + CONTRIBUTOR_AGREEMENT
- + "."
- + name
- + "."
- + KEY_AUTO_VERIFY
- + ": at most one group may be set"));
+ String.format(
+ "Invalid rule in %s.%s.%s: at most one group may be set",
+ CONTRIBUTOR_AGREEMENT, name, KEY_AUTO_VERIFY));
} else if (rules.get(0).getAction() != Action.ALLOW) {
error(
- ValidationError.create(
- PROJECT_CONFIG,
- "Invalid rule in "
- + CONTRIBUTOR_AGREEMENT
- + "."
- + name
- + "."
- + KEY_AUTO_VERIFY
- + ": the group must be allowed"));
+ String.format(
+ "Invalid rule in %s.%s.%s: the group must be allowed",
+ CONTRIBUTOR_AGREEMENT, name, KEY_AUTO_VERIFY));
} else {
ca.setAutoVerify(rules.get(0).getGroup());
}
@@ -792,21 +777,16 @@
if (ref.getUUID() != null) {
n.addGroup(ref);
} else {
- error(
- ValidationError.create(
- PROJECT_CONFIG,
- "group \"" + ref.getName() + "\" not in " + GroupList.FILE_NAME));
+ error(String.format("group \"%s\" not in %s", ref.getName(), GroupList.FILE_NAME));
}
} else if (dst.startsWith("user ")) {
- error(ValidationError.create(PROJECT_CONFIG, dst + " not supported"));
+ error(String.format("%s not supported", dst));
} else {
try {
n.addAddress(Address.parse(dst));
} catch (IllegalArgumentException err) {
error(
- ValidationError.create(
- PROJECT_CONFIG,
- "notify section \"" + sectionName + "\" has invalid email \"" + dst + "\""));
+ String.format("notify section \"%s\" has invalid email \"%s\"", sectionName, dst));
}
}
}
@@ -867,7 +847,7 @@
try {
RefPattern.validateRegExp(refPattern);
} catch (InvalidNameException e) {
- error(ValidationError.create(PROJECT_CONFIG, "Invalid ref name: " + e.getMessage()));
+ error(String.format("Invalid ref name: %s", e.getMessage()));
return false;
}
return true;
@@ -895,9 +875,7 @@
// to fail fast if any of the patterns are invalid.
patterns.add(Pattern.compile(patternString).pattern());
} catch (PatternSyntaxException e) {
- error(
- ValidationError.create(
- PROJECT_CONFIG, "Invalid regular expression: " + e.getMessage()));
+ error(String.format("Invalid regular expression: %s", e.getMessage()));
continue;
}
}
@@ -924,15 +902,11 @@
rule = PermissionRule.fromString(ruleString, useRange);
} catch (IllegalArgumentException notRule) {
error(
- ValidationError.create(
- PROJECT_CONFIG,
- "Invalid rule in "
- + section
- + (subsection != null ? "." + subsection : "")
- + "."
- + varName
- + ": "
- + notRule.getMessage()));
+ String.format(
+ "Invalid rule in %s.%s: %s",
+ section + (subsection != null ? "." + subsection : ""),
+ varName,
+ notRule.getMessage()));
continue;
}
@@ -943,9 +917,7 @@
// all rules in the same file share the same GroupReference.
//
ref = groupList.resolve(rule.getGroup());
- error(
- ValidationError.create(
- PROJECT_CONFIG, "group \"" + ref.getName() + "\" not in " + GroupList.FILE_NAME));
+ error(String.format("group \"%s\" not in %s", ref.getName(), GroupList.FILE_NAME));
}
perm.add(rule.toBuilder().setGroup(ref));
@@ -970,11 +942,9 @@
String lower = name.toLowerCase();
if (lowerNames.containsKey(lower)) {
error(
- ValidationError.create(
- PROJECT_CONFIG,
- String.format(
- "Submit requirement \"%s\" conflicts with \"%s\". Skipping the former.",
- name, lowerNames.get(lower))));
+ String.format(
+ "Submit requirement \"%s\" conflicts with \"%s\". Skipping the former.",
+ name, lowerNames.get(lower)));
continue;
}
lowerNames.put(lower, name);
@@ -987,12 +957,10 @@
if (blockExpr == null) {
error(
- ValidationError.create(
- PROJECT_CONFIG,
- (String.format(
- "Submit requirement \"%s\" does not define a submittability expression."
- + " Skipping this requirement.",
- name))));
+ String.format(
+ "Submit requirement \"%s\" does not define a submittability expression."
+ + " Skipping this requirement.",
+ name));
continue;
}
@@ -1019,10 +987,7 @@
for (String name : rc.getSubsections(LABEL)) {
String lower = name.toLowerCase();
if (lowerNames.containsKey(lower)) {
- error(
- ValidationError.create(
- PROJECT_CONFIG,
- String.format("Label \"%s\" conflicts with \"%s\"", name, lowerNames.get(lower))));
+ error(String.format("Label \"%s\" conflicts with \"%s\"", name, lowerNames.get(lower)));
}
lowerNames.put(lower, name);
@@ -1034,18 +999,13 @@
if (allValues.add(labelValue.getValue())) {
values.add(labelValue);
} else {
- error(
- ValidationError.create(
- PROJECT_CONFIG,
- String.format("Duplicate %s \"%s\" for label \"%s\"", KEY_VALUE, value, name)));
+ error(String.format("Duplicate %s \"%s\" for label \"%s\"", KEY_VALUE, value, name));
}
} catch (IllegalArgumentException notValue) {
error(
- ValidationError.create(
- PROJECT_CONFIG,
- String.format(
- "Invalid %s \"%s\" for label \"%s\": %s",
- KEY_VALUE, value, name, notValue.getMessage())));
+ String.format(
+ "Invalid %s \"%s\" for label \"%s\": %s",
+ KEY_VALUE, value, name, notValue.getMessage()));
}
}
@@ -1053,7 +1013,7 @@
try {
label = LabelType.builder(name, values);
} catch (IllegalArgumentException badName) {
- error(ValidationError.create(PROJECT_CONFIG, String.format("Invalid label \"%s\"", name)));
+ error(String.format("Invalid label \"%s\"", name));
continue;
}
@@ -1064,11 +1024,9 @@
: Optional.of(LabelFunction.MAX_WITH_BLOCK);
if (!function.isPresent()) {
error(
- ValidationError.create(
- PROJECT_CONFIG,
- String.format(
- "Invalid %s for label \"%s\". Valid names are: %s",
- KEY_FUNCTION, name, Joiner.on(", ").join(LabelFunction.ALL.keySet()))));
+ String.format(
+ "Invalid %s for label \"%s\". Valid names are: %s",
+ KEY_FUNCTION, name, Joiner.on(", ").join(LabelFunction.ALL.keySet())));
}
label.setFunction(function.orElse(null));
label.setCopyCondition(rc.getString(LABEL, name, KEY_COPY_CONDITION));
@@ -1078,11 +1036,7 @@
if (isInRange(dv, values)) {
label.setDefaultValue(dv);
} else {
- error(
- ValidationError.create(
- PROJECT_CONFIG,
- String.format(
- "Invalid %s \"%s\" for label \"%s\"", KEY_DEFAULT_VALUE, dv, name)));
+ error(String.format("Invalid %s \"%s\" for label \"%s\"", KEY_DEFAULT_VALUE, dv, name));
}
}
label.setAllowPostSubmit(
@@ -1131,18 +1085,13 @@
short copyValue = Shorts.checkedCast(PermissionRule.parseInt(value));
if (!copyValues.add(copyValue)) {
error(
- ValidationError.create(
- PROJECT_CONFIG,
- String.format(
- "Duplicate %s \"%s\" for label \"%s\"", KEY_COPY_VALUE, value, name)));
+ String.format("Duplicate %s \"%s\" for label \"%s\"", KEY_COPY_VALUE, value, name));
}
} catch (IllegalArgumentException notValue) {
error(
- ValidationError.create(
- PROJECT_CONFIG,
- String.format(
- "Invalid %s \"%s\" for label \"%s\": %s",
- KEY_COPY_VALUE, value, name, notValue.getMessage())));
+ String.format(
+ "Invalid %s \"%s\" for label \"%s\": %s",
+ KEY_COPY_VALUE, value, name, notValue.getMessage()));
}
}
label.setCopyValues(copyValues);
@@ -1177,18 +1126,14 @@
commentLinkSections.put(name, buildCommentLink(rc, name, false));
} catch (PatternSyntaxException e) {
error(
- ValidationError.create(
- PROJECT_CONFIG,
- String.format(
- "Invalid pattern \"%s\" in commentlink.%s.match: %s",
- rc.getString(COMMENTLINK, name, KEY_MATCH), name, e.getMessage())));
+ String.format(
+ "Invalid pattern \"%s\" in commentlink.%s.match: %s",
+ rc.getString(COMMENTLINK, name, KEY_MATCH), name, e.getMessage()));
} catch (IllegalArgumentException e) {
error(
- ValidationError.create(
- PROJECT_CONFIG,
- String.format(
- "Error in pattern \"%s\" in commentlink.%s.match: %s",
- rc.getString(COMMENTLINK, name, KEY_MATCH), name, e.getMessage())));
+ String.format(
+ "Error in pattern \"%s\" in commentlink.%s.match: %s",
+ rc.getString(COMMENTLINK, name, KEY_MATCH), name, e.getMessage()));
}
}
}
@@ -1230,9 +1175,7 @@
if (groupName != null) {
GroupReference ref = groupList.byName(groupName);
if (ref == null) {
- error(
- ValidationError.create(
- PROJECT_CONFIG, "group \"" + groupName + "\" not in " + GroupList.FILE_NAME));
+ error(String.format("group \"%s\" not in %s", groupName, GroupList.FILE_NAME));
}
rc.setString(PLUGIN, plugin, name, value);
}
@@ -1782,11 +1725,15 @@
try {
return rc.getEnum(section, subsection, name, defaultValue);
} catch (IllegalArgumentException err) {
- error(ValidationError.create(PROJECT_CONFIG, err.getMessage()));
+ error(err.getMessage());
return defaultValue;
}
}
+ private void error(String errorMessage) {
+ error(ValidationError.create(PROJECT_CONFIG, errorMessage));
+ }
+
@Override
public void error(ValidationError error) {
if (validationErrors == null) {
diff --git a/java/com/google/gerrit/server/project/SubmitRequirementsAdapter.java b/java/com/google/gerrit/server/project/SubmitRequirementsAdapter.java
index 7252fe9..539edc1 100644
--- a/java/com/google/gerrit/server/project/SubmitRequirementsAdapter.java
+++ b/java/com/google/gerrit/server/project/SubmitRequirementsAdapter.java
@@ -29,6 +29,7 @@
import com.google.gerrit.server.query.change.ChangeQueryBuilder;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.eclipse.jgit.lib.ObjectId;
@@ -88,7 +89,7 @@
.setAllowOverrideInChildProjects(labelType.isCanOverride());
result.add(
SubmitRequirementResult.builder()
- .legacy(true)
+ .legacy(Optional.of(true))
.submitRequirement(req.build())
.submittabilityExpressionResult(
createExpressionResult(toExpression(atoms), mapStatus(label), atoms))
@@ -111,7 +112,7 @@
.build();
return ImmutableList.of(
SubmitRequirementResult.builder()
- .legacy(true)
+ .legacy(Optional.of(true))
.submitRequirement(sr)
.submittabilityExpressionResult(
createExpressionResult(
@@ -130,7 +131,7 @@
.build();
result.add(
SubmitRequirementResult.builder()
- .legacy(true)
+ .legacy(Optional.of(true))
.submitRequirement(sr)
.submittabilityExpressionResult(
createExpressionResult(
diff --git a/java/com/google/gerrit/server/project/SubmitRequirementsEvaluatorImpl.java b/java/com/google/gerrit/server/project/SubmitRequirementsEvaluatorImpl.java
index 2caa9dd..cc2c805 100644
--- a/java/com/google/gerrit/server/project/SubmitRequirementsEvaluatorImpl.java
+++ b/java/com/google/gerrit/server/project/SubmitRequirementsEvaluatorImpl.java
@@ -24,8 +24,6 @@
import com.google.gerrit.entities.SubmitRequirementResult;
import com.google.gerrit.index.query.Predicate;
import com.google.gerrit.index.query.QueryParseException;
-import com.google.gerrit.server.experiments.ExperimentFeatures;
-import com.google.gerrit.server.experiments.ExperimentFeaturesConstants;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.SubmitRequirementChangeQueryBuilder;
import com.google.inject.AbstractModule;
@@ -43,7 +41,6 @@
private final Provider<SubmitRequirementChangeQueryBuilder> queryBuilder;
private final ProjectCache projectCache;
private final SubmitRuleEvaluator.Factory legacyEvaluator;
- private final ExperimentFeatures experimentFeatures;
public static Module module() {
return new AbstractModule() {
@@ -60,12 +57,10 @@
private SubmitRequirementsEvaluatorImpl(
Provider<SubmitRequirementChangeQueryBuilder> queryBuilder,
ProjectCache projectCache,
- SubmitRuleEvaluator.Factory legacyEvaluator,
- ExperimentFeatures experimentFeatures) {
+ SubmitRuleEvaluator.Factory legacyEvaluator) {
this.queryBuilder = queryBuilder;
this.projectCache = projectCache;
this.legacyEvaluator = legacyEvaluator;
- this.experimentFeatures = experimentFeatures;
}
@Override
@@ -79,10 +74,7 @@
ChangeData cd, boolean includeLegacy) {
Map<SubmitRequirement, SubmitRequirementResult> projectConfigRequirements = getRequirements(cd);
Map<SubmitRequirement, SubmitRequirementResult> result = projectConfigRequirements;
- if (includeLegacy
- && experimentFeatures.isFeatureEnabled(
- ExperimentFeaturesConstants
- .GERRIT_BACKEND_REQUEST_FEATURE_ENABLE_LEGACY_SUBMIT_REQUIREMENTS)) {
+ if (includeLegacy) {
Map<SubmitRequirement, SubmitRequirementResult> legacyReqs =
SubmitRequirementsAdapter.getLegacyRequirements(legacyEvaluator, cd);
result =
@@ -108,7 +100,7 @@
: Optional.empty();
return SubmitRequirementResult.builder()
- .legacy(false)
+ .legacy(Optional.of(false))
.submitRequirement(sr)
.patchSetCommitId(cd.currentPatchSet().commitId())
.submittabilityExpressionResult(blockingResult)
diff --git a/java/com/google/gerrit/server/project/SubmitRequirementsUtil.java b/java/com/google/gerrit/server/project/SubmitRequirementsUtil.java
index 2e43eac..102d3f2 100644
--- a/java/com/google/gerrit/server/project/SubmitRequirementsUtil.java
+++ b/java/com/google/gerrit/server/project/SubmitRequirementsUtil.java
@@ -50,10 +50,10 @@
result.putAll(projectConfigRequirements);
Map<String, SubmitRequirementResult> requirementsByName =
projectConfigRequirements.entrySet().stream()
- .collect(Collectors.toMap(sr -> sr.getKey().name(), sr -> sr.getValue()));
+ .collect(Collectors.toMap(sr -> sr.getKey().name().toLowerCase(), sr -> sr.getValue()));
for (Map.Entry<SubmitRequirement, SubmitRequirementResult> legacy :
legacyRequirements.entrySet()) {
- String name = legacy.getKey().name();
+ String name = legacy.getKey().name().toLowerCase();
SubmitRequirementResult projectConfigResult = requirementsByName.get(name);
SubmitRequirementResult legacyResult = legacy.getValue();
if (projectConfigResult != null && matchByStatus(projectConfigResult, legacyResult)) {
diff --git a/java/com/google/gerrit/server/query/change/ChangeData.java b/java/com/google/gerrit/server/query/change/ChangeData.java
index b8c8c07..9961519 100644
--- a/java/com/google/gerrit/server/query/change/ChangeData.java
+++ b/java/com/google/gerrit/server/query/change/ChangeData.java
@@ -948,6 +948,10 @@
* com.google.gerrit.server.index.change.ChangeField#STORED_SUBMIT_REQUIREMENTS}.
*/
public Map<SubmitRequirement, SubmitRequirementResult> submitRequirements() {
+ if (!experimentFeatures.isFeatureEnabled(
+ ExperimentFeaturesConstants.GERRIT_BACKEND_REQUEST_FEATURE_ENABLE_SUBMIT_REQUIREMENTS)) {
+ return Collections.emptyMap();
+ }
if (submitRequirements == null) {
if (!lazyload()) {
return Collections.emptyMap();
@@ -962,14 +966,8 @@
// Closed changes: Load submit requirement results from NoteDb.
Map<SubmitRequirement, SubmitRequirementResult> projectConfigRequirements =
notes().getSubmitRequirementsResult().stream()
- .filter(r -> !r.legacy())
+ .filter(r -> !r.isLegacy())
.collect(Collectors.toMap(r -> r.submitRequirement(), Function.identity()));
- if (!experimentFeatures.isFeatureEnabled(
- ExperimentFeaturesConstants
- .GERRIT_BACKEND_REQUEST_FEATURE_ENABLE_LEGACY_SUBMIT_REQUIREMENTS)) {
- submitRequirements = projectConfigRequirements;
- return submitRequirements;
- }
Map<SubmitRequirement, SubmitRequirementResult> legacyRequirements =
SubmitRequirementsAdapter.getLegacyRequirements(submitRuleEvaluatorFactory, this);
submitRequirements =
@@ -981,7 +979,15 @@
public void setSubmitRequirements(
Map<SubmitRequirement, SubmitRequirementResult> submitRequirements) {
- this.submitRequirements = submitRequirements;
+ if (!experimentFeatures.isFeatureEnabled(
+ ExperimentFeaturesConstants
+ .GERRIT_BACKEND_REQUEST_FEATURE_ENABLE_SUBMIT_REQUIREMENTS_BACKFILLING_ON_DASHBOARD)) {
+ // Only set back values from the index if the experiment is not active. While the experiment
+ // is active, we want
+ // to compute SRs from scratch to ensure fresh results.
+ // TODO(ghareeb, hiesel): Remove this.
+ this.submitRequirements = submitRequirements;
+ }
}
public List<SubmitRecord> submitRecords(SubmitRuleOptions options) {
diff --git a/java/com/google/gerrit/server/query/change/ChangePredicates.java b/java/com/google/gerrit/server/query/change/ChangePredicates.java
index 4e638df..043b37d 100644
--- a/java/com/google/gerrit/server/query/change/ChangePredicates.java
+++ b/java/com/google/gerrit/server/query/change/ChangePredicates.java
@@ -125,6 +125,14 @@
}
/**
+ * Returns a predicate that matches changes where the latest patch set was uploaded by the
+ * provided {@link com.google.gerrit.entities.Account.Id}.
+ */
+ public static Predicate<ChangeData> uploader(Account.Id id) {
+ return new ChangeIndexPredicate(ChangeField.UPLOADER, id.toString());
+ }
+
+ /**
* Returns a predicate that matches changes that are a cherry pick of the provided {@link
* com.google.gerrit.entities.Change.Id}.
*/
diff --git a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
index f1fe520..da36633 100644
--- a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
+++ b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -195,6 +195,8 @@
public static final String FIELD_SUBMISSIONID = "submissionid";
public static final String FIELD_TR = "tr";
public static final String FIELD_UNRESOLVED_COMMENT_COUNT = "unresolved";
+ public static final String FIELD_UPLOADER = "uploader";
+ public static final String FIELD_UPLOADERIN = "uploaderin";
public static final String FIELD_VISIBLETO = "visibleto";
public static final String FIELD_WATCHEDBY = "watchedby";
public static final String FIELD_WIP = "wip";
@@ -661,6 +663,14 @@
return ChangePredicates.owner(self());
}
+ if ("uploader".equalsIgnoreCase(value)) {
+ if (!args.getSchema().hasField(ChangeField.UPLOADER)) {
+ throw new QueryParseException(
+ "'is:uploader' operator is not supported by change index version");
+ }
+ return ChangePredicates.uploader(self());
+ }
+
if ("reviewer".equalsIgnoreCase(value)) {
if (args.getSchema().hasField(ChangeField.WIP)) {
return Predicate.and(
@@ -1163,6 +1173,23 @@
}
@Operator
+ public Predicate<ChangeData> uploader(String who)
+ throws QueryParseException, IOException, ConfigInvalidException {
+ if (!args.getSchema().hasField(ChangeField.UPLOADER)) {
+ throw new QueryParseException("'uploader' operator is not supported by change index version");
+ }
+ return uploader(parseAccount(who, (AccountState s) -> true));
+ }
+
+ private Predicate<ChangeData> uploader(Set<Account.Id> who) {
+ List<Predicate<ChangeData>> p = Lists.newArrayListWithCapacity(who.size());
+ for (Account.Id id : who) {
+ p.add(ChangePredicates.uploader(id));
+ }
+ return Predicate.or(p);
+ }
+
+ @Operator
public Predicate<ChangeData> attention(String who)
throws QueryParseException, IOException, ConfigInvalidException {
if (!args.index.getSchema().hasField(ChangeField.ATTENTION_SET_USERS)) {
@@ -1212,6 +1239,31 @@
}
@Operator
+ public Predicate<ChangeData> uploaderin(String group) throws QueryParseException, IOException {
+ if (!args.getSchema().hasField(ChangeField.UPLOADER)) {
+ throw new QueryParseException("'uploader' operator is not supported by change index version");
+ }
+
+ GroupReference g = GroupBackends.findBestSuggestion(args.groupBackend, group);
+ if (g == null) {
+ throw error("Group " + group + " not found");
+ }
+
+ AccountGroup.UUID groupId = g.getUUID();
+ GroupDescription.Basic groupDescription = args.groupBackend.get(groupId);
+ if (!(groupDescription instanceof GroupDescription.Internal)) {
+ return new UploaderinPredicate(args.userFactory, groupId);
+ }
+
+ Set<Account.Id> accounts = getMembers(groupId);
+ List<Predicate<ChangeData>> p = Lists.newArrayListWithCapacity(accounts.size());
+ for (Account.Id id : accounts) {
+ p.add(ChangePredicates.uploader(id));
+ }
+ return Predicate.or(p);
+ }
+
+ @Operator
public Predicate<ChangeData> r(String who)
throws QueryParseException, IOException, ConfigInvalidException {
return reviewer(who);
diff --git a/java/com/google/gerrit/server/query/change/UploaderinPredicate.java b/java/com/google/gerrit/server/query/change/UploaderinPredicate.java
new file mode 100644
index 0000000..f44c0b5
--- /dev/null
+++ b/java/com/google/gerrit/server/query/change/UploaderinPredicate.java
@@ -0,0 +1,50 @@
+// Copyright (C) 2021 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.query.change;
+
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.index.query.PostFilterPredicate;
+import com.google.gerrit.server.IdentifiedUser;
+
+/**
+ * Predicate that matches changes where the latest patch set was uploaded by a user in the provided
+ * group.
+ */
+public class UploaderinPredicate extends PostFilterPredicate<ChangeData> {
+ protected final IdentifiedUser.GenericFactory userFactory;
+ protected final AccountGroup.UUID uuid;
+
+ public UploaderinPredicate(IdentifiedUser.GenericFactory userFactory, AccountGroup.UUID uuid) {
+ super(ChangeQueryBuilder.FIELD_UPLOADERIN, uuid.get());
+ this.userFactory = userFactory;
+ this.uuid = uuid;
+ }
+
+ @Override
+ public boolean match(ChangeData cd) {
+ PatchSet latestPatchSet = cd.currentPatchSet();
+ if (latestPatchSet == null) {
+ return false;
+ }
+ IdentifiedUser uploader = userFactory.create(latestPatchSet.uploader());
+ return uploader.getEffectiveGroups().contains(uuid);
+ }
+
+ @Override
+ public int getCost() {
+ return 2;
+ }
+}
diff --git a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
index e657c89..0d7210f 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
@@ -4159,6 +4159,9 @@
}
@Test
+ @GerritConfig(
+ name = "experiments.enabled",
+ value = ExperimentFeaturesConstants.GERRIT_BACKEND_REQUEST_FEATURE_ENABLE_SUBMIT_REQUIREMENTS)
public void submitRequirement_withLabelEqualsMax() throws Exception {
configSubmitRequirement(
project,
@@ -4185,6 +4188,9 @@
}
@Test
+ @GerritConfig(
+ name = "experiments.enabled",
+ value = ExperimentFeaturesConstants.GERRIT_BACKEND_REQUEST_FEATURE_ENABLE_SUBMIT_REQUIREMENTS)
public void submitRequirement_withLabelEqualsMax_fromNonUploader() throws Exception {
configLabel("my-label", LabelFunction.NO_OP); // label function has no effect
projectOperations
@@ -4204,14 +4210,15 @@
PushOneCommit.Result r = createChange();
String changeId = r.getChangeId();
ChangeInfo change = gApi.changes().id(changeId).get();
- assertThat(change.submitRequirements).hasSize(1);
+ // The second requirement is coming from the legacy code-review label function
+ assertThat(change.submitRequirements).hasSize(2);
assertSubmitRequirementStatus(
change.submitRequirements, "my-label", Status.UNSATISFIED, /* isLegacy= */ false);
// Voting with a max vote as the uploader will not satisfy the submit requirement.
voteLabel(changeId, "my-label", 1);
change = gApi.changes().id(changeId).get();
- assertThat(change.submitRequirements).hasSize(1);
+ assertThat(change.submitRequirements).hasSize(2);
assertSubmitRequirementStatus(
change.submitRequirements, "my-label", Status.UNSATISFIED, /* isLegacy= */ false);
@@ -4219,12 +4226,15 @@
requestScopeOperations.setApiUser(user.id());
voteLabel(changeId, "my-label", 1);
change = gApi.changes().id(changeId).get();
- assertThat(change.submitRequirements).hasSize(1);
+ assertThat(change.submitRequirements).hasSize(2);
assertSubmitRequirementStatus(
change.submitRequirements, "my-label", Status.SATISFIED, /* isLegacy= */ false);
}
@Test
+ @GerritConfig(
+ name = "experiments.enabled",
+ value = ExperimentFeaturesConstants.GERRIT_BACKEND_REQUEST_FEATURE_ENABLE_SUBMIT_REQUIREMENTS)
public void submitRequirement_withLabelEqualsMinBlockingSubmission() throws Exception {
configSubmitRequirement(
project,
@@ -4239,17 +4249,23 @@
String changeId = r.getChangeId();
ChangeInfo change = gApi.changes().id(changeId).get();
- assertThat(change.submitRequirements).hasSize(1);
+ assertThat(change.submitRequirements).hasSize(2);
// Requirement is satisfied because there are no votes
assertSubmitRequirementStatus(
change.submitRequirements, "code-review", Status.SATISFIED, /* isLegacy= */ false);
+ // Legacy requirement (coming from the label function definition) is not satisfied. We return
+ // both legacy and non-legacy requirements in this case since their statuses are not identical.
+ assertSubmitRequirementStatus(
+ change.submitRequirements, "Code-Review", Status.UNSATISFIED, /* isLegacy= */ true);
voteLabel(changeId, "code-review", -1);
change = gApi.changes().id(changeId).get();
- assertThat(change.submitRequirements).hasSize(1);
+ assertThat(change.submitRequirements).hasSize(2);
// Requirement is still satisfied because -1 is not the max negative value
assertSubmitRequirementStatus(
change.submitRequirements, "code-review", Status.SATISFIED, /* isLegacy= */ false);
+ assertSubmitRequirementStatus(
+ change.submitRequirements, "Code-Review", Status.UNSATISFIED, /* isLegacy= */ true);
voteLabel(changeId, "code-review", -2);
change = gApi.changes().id(changeId).get();
@@ -4260,6 +4276,9 @@
}
@Test
+ @GerritConfig(
+ name = "experiments.enabled",
+ value = ExperimentFeaturesConstants.GERRIT_BACKEND_REQUEST_FEATURE_ENABLE_SUBMIT_REQUIREMENTS)
public void submitRequirement_withMaxWithBlock_ignoringSelfApproval() throws Exception {
configLabel("my-label", LabelFunction.MAX_WITH_BLOCK);
projectOperations
@@ -4286,7 +4305,8 @@
// Admin (a.k.a uploader) adds a -1 min vote. This is going to block submission.
voteLabel(changeId, "my-label", -1);
ChangeInfo change = gApi.changes().id(changeId).get();
- assertThat(change.submitRequirements).hasSize(1);
+ // The other requirement is coming from the code-review label function
+ assertThat(change.submitRequirements).hasSize(2);
assertSubmitRequirementStatus(
change.submitRequirements, "my-label", Status.UNSATISFIED, /* isLegacy= */ false);
@@ -4294,7 +4314,7 @@
requestScopeOperations.setApiUser(user.id());
voteLabel(changeId, "my-label", 1);
change = gApi.changes().id(changeId).get();
- assertThat(change.submitRequirements).hasSize(1);
+ assertThat(change.submitRequirements).hasSize(2);
assertSubmitRequirementStatus(
change.submitRequirements, "my-label", Status.UNSATISFIED, /* isLegacy= */ false);
@@ -4302,12 +4322,15 @@
requestScopeOperations.setApiUser(admin.id());
voteLabel(changeId, "my-label", 0);
change = gApi.changes().id(changeId).get();
- assertThat(change.submitRequirements).hasSize(1);
+ assertThat(change.submitRequirements).hasSize(2);
assertSubmitRequirementStatus(
change.submitRequirements, "my-label", Status.SATISFIED, /* isLegacy= */ false);
}
@Test
+ @GerritConfig(
+ name = "experiments.enabled",
+ value = ExperimentFeaturesConstants.GERRIT_BACKEND_REQUEST_FEATURE_ENABLE_SUBMIT_REQUIREMENTS)
public void submitRequirement_withLabelEqualsAny() throws Exception {
configSubmitRequirement(
project,
@@ -4328,12 +4351,18 @@
voteLabel(changeId, "code-review", 1);
change = gApi.changes().id(changeId).get();
- assertThat(change.submitRequirements).hasSize(1);
+ assertThat(change.submitRequirements).hasSize(2);
+ // Legacy and non-legacy requirements have mismatching status. Both are returned from the API.
assertSubmitRequirementStatus(
change.submitRequirements, "code-review", Status.SATISFIED, /* isLegacy= */ false);
+ assertSubmitRequirementStatus(
+ change.submitRequirements, "Code-Review", Status.UNSATISFIED, /* isLegacy= */ true);
}
@Test
+ @GerritConfig(
+ name = "experiments.enabled",
+ value = ExperimentFeaturesConstants.GERRIT_BACKEND_REQUEST_FEATURE_ENABLE_SUBMIT_REQUIREMENTS)
public void submitRequirementIsSatisfied_whenSubmittabilityExpressionIsFulfilled()
throws Exception {
configSubmitRequirement(
@@ -4372,6 +4401,9 @@
}
@Test
+ @GerritConfig(
+ name = "experiments.enabled",
+ value = ExperimentFeaturesConstants.GERRIT_BACKEND_REQUEST_FEATURE_ENABLE_SUBMIT_REQUIREMENTS)
public void submitRequirementIsNotApplicable_whenApplicabilityExpressionIsNotFulfilled()
throws Exception {
configSubmitRequirement(
@@ -4387,14 +4419,19 @@
String changeId = r.getChangeId();
ChangeInfo change = gApi.changes().id(changeId).get();
- assertThat(change.submitRequirements).hasSize(1);
+ assertThat(change.submitRequirements).hasSize(2);
assertSubmitRequirementStatus(
change.submitRequirements, "code-review", Status.NOT_APPLICABLE, /* isLegacy= */ false);
+ assertSubmitRequirementStatus(
+ change.submitRequirements, "Code-Review", Status.UNSATISFIED, /* isLegacy= */ true);
}
@Test
+ @GerritConfig(
+ name = "experiments.enabled",
+ value = ExperimentFeaturesConstants.GERRIT_BACKEND_REQUEST_FEATURE_ENABLE_SUBMIT_REQUIREMENTS)
public void submitRequirementIsOverridden_whenOverrideExpressionIsFulfilled() throws Exception {
- configLabel("build-cop-override", LabelFunction.MAX_WITH_BLOCK);
+ configLabel("build-cop-override", LabelFunction.NO_BLOCK);
projectOperations
.project(project)
.forUpdate()
@@ -4424,9 +4461,11 @@
voteLabel(changeId, "build-cop-override", 1);
change = gApi.changes().id(changeId).get();
- assertThat(change.submitRequirements).hasSize(1);
+ assertThat(change.submitRequirements).hasSize(2);
assertSubmitRequirementStatus(
change.submitRequirements, "code-review", Status.OVERRIDDEN, /* isLegacy= */ false);
+ assertSubmitRequirementStatus(
+ change.submitRequirements, "Code-Review", Status.UNSATISFIED, /* isLegacy= */ true);
}
@Test
@@ -4472,6 +4511,9 @@
}
@Test
+ @GerritConfig(
+ name = "experiments.enabled",
+ value = ExperimentFeaturesConstants.GERRIT_BACKEND_REQUEST_FEATURE_ENABLE_SUBMIT_REQUIREMENTS)
public void submitRequirement_inheritedFromParentProject() throws Exception {
configSubmitRequirement(
allProjects,
@@ -4491,12 +4533,18 @@
voteLabel(changeId, "code-review", 1);
change = gApi.changes().id(changeId).get();
- assertThat(change.submitRequirements).hasSize(1);
+ assertThat(change.submitRequirements).hasSize(2);
assertSubmitRequirementStatus(
change.submitRequirements, "code-review", Status.SATISFIED, /* isLegacy= */ false);
+ // Legacy requirement is coming from the label MaxWithBlock function. Still unsatisfied.
+ assertSubmitRequirementStatus(
+ change.submitRequirements, "Code-Review", Status.UNSATISFIED, /* isLegacy= */ true);
}
@Test
+ @GerritConfig(
+ name = "experiments.enabled",
+ value = ExperimentFeaturesConstants.GERRIT_BACKEND_REQUEST_FEATURE_ENABLE_SUBMIT_REQUIREMENTS)
public void submitRequirement_ignoredInChildProject_ifParentDoesNotAllowOverride()
throws Exception {
configSubmitRequirement(
@@ -4528,13 +4576,23 @@
voteLabel(changeId, "code-review", 1);
change = gApi.changes().id(changeId).get();
- assertThat(change.submitRequirements).hasSize(1);
+ assertThat(change.submitRequirements).hasSize(2);
// +1 was enough to fulfill the requirement: override in child project was ignored
assertSubmitRequirementStatus(
change.submitRequirements, "code-review", Status.SATISFIED, /* isLegacy= */ false);
+ // Legacy requirement is coming from the label MaxWithBlock function. Still unsatisfied.
+ assertSubmitRequirementStatus(
+ change.submitRequirements, "Code-Review", Status.UNSATISFIED, /* isLegacy= */ true);
}
@Test
+ @GerritConfig(
+ name = "experiments.enabled",
+ values = {
+ ExperimentFeaturesConstants.GERRIT_BACKEND_REQUEST_FEATURE_ENABLE_SUBMIT_REQUIREMENTS,
+ ExperimentFeaturesConstants
+ .GERRIT_BACKEND_REQUEST_FEATURE_STORE_SUBMIT_REQUIREMENTS_ON_MERGE
+ })
public void submitRequirement_storedForClosedChanges() throws Exception {
for (SubmitType submitType : SubmitType.values()) {
Project.NameKey project = createProjectForPush(submitType);
@@ -4576,6 +4634,13 @@
}
@Test
+ @GerritConfig(
+ name = "experiments.enabled",
+ values = {
+ ExperimentFeaturesConstants.GERRIT_BACKEND_REQUEST_FEATURE_ENABLE_SUBMIT_REQUIREMENTS,
+ ExperimentFeaturesConstants
+ .GERRIT_BACKEND_REQUEST_FEATURE_STORE_SUBMIT_REQUIREMENTS_ON_MERGE
+ })
public void submitRequirement_retrievedFromNoteDbForClosedChanges() throws Exception {
configSubmitRequirement(
project,
@@ -4621,9 +4686,11 @@
@Test
@GerritConfig(
name = "experiments.enabled",
- value =
- ExperimentFeaturesConstants
- .GERRIT_BACKEND_REQUEST_FEATURE_ENABLE_LEGACY_SUBMIT_REQUIREMENTS)
+ values = {
+ ExperimentFeaturesConstants.GERRIT_BACKEND_REQUEST_FEATURE_ENABLE_SUBMIT_REQUIREMENTS,
+ ExperimentFeaturesConstants
+ .GERRIT_BACKEND_REQUEST_FEATURE_STORE_SUBMIT_REQUIREMENTS_ON_MERGE
+ })
public void
submitRequirements_returnOneEntryForMatchingLegacyAndNonLegacyResultsWithTheSameName_ifLegacySubmitRecordsAreEnabled()
throws Exception {
@@ -4686,9 +4753,7 @@
@Test
@GerritConfig(
name = "experiments.enabled",
- value =
- ExperimentFeaturesConstants
- .GERRIT_BACKEND_REQUEST_FEATURE_ENABLE_LEGACY_SUBMIT_REQUIREMENTS)
+ value = ExperimentFeaturesConstants.GERRIT_BACKEND_REQUEST_FEATURE_ENABLE_SUBMIT_REQUIREMENTS)
public void
submitRequirements_returnTwoEntriesForMismatchingLegacyAndNonLegacyResultsWithTheSameName_ifLegacySubmitRecordsAreEnabled()
throws Exception {
@@ -4744,9 +4809,7 @@
@Test
@GerritConfig(
name = "experiments.enabled",
- value =
- ExperimentFeaturesConstants
- .GERRIT_BACKEND_REQUEST_FEATURE_ENABLE_LEGACY_SUBMIT_REQUIREMENTS)
+ value = ExperimentFeaturesConstants.GERRIT_BACKEND_REQUEST_FEATURE_ENABLE_SUBMIT_REQUIREMENTS)
public void submitRequirements_returnForLegacySubmitRecords_ifEnabled() throws Exception {
configLabel("build-cop-override", LabelFunction.MAX_WITH_BLOCK);
projectOperations
@@ -4799,6 +4862,9 @@
}
@Test
+ @GerritConfig(
+ name = "experiments.enabled",
+ value = ExperimentFeaturesConstants.GERRIT_BACKEND_REQUEST_FEATURE_ENABLE_SUBMIT_REQUIREMENTS)
public void submitRequirement_backFilledFromIndexForActiveChanges() throws Exception {
configSubmitRequirement(
project,
@@ -4829,6 +4895,13 @@
}
@Test
+ @GerritConfig(
+ name = "experiments.enabled",
+ values = {
+ ExperimentFeaturesConstants.GERRIT_BACKEND_REQUEST_FEATURE_ENABLE_SUBMIT_REQUIREMENTS,
+ ExperimentFeaturesConstants
+ .GERRIT_BACKEND_REQUEST_FEATURE_STORE_SUBMIT_REQUIREMENTS_ON_MERGE
+ })
public void submitRequirement_backFilledFromIndexForClosedChanges() throws Exception {
configSubmitRequirement(
project,
@@ -4860,6 +4933,35 @@
}
@Test
+ public void submitRequirements_notServedIfExperimentNotEnabled() throws Exception {
+ configSubmitRequirement(
+ project,
+ SubmitRequirement.builder()
+ .setName("code-review")
+ .setSubmittabilityExpression(SubmitRequirementExpression.create("label:code-review=+2"))
+ .setAllowOverrideInChildProjects(false)
+ .build());
+
+ PushOneCommit.Result r = createChange();
+ String changeId = r.getChangeId();
+
+ ChangeInfo change = gApi.changes().id(changeId).get();
+ assertThat(change.submitRequirements).isEmpty();
+
+ voteLabel(changeId, "code-review", -1);
+ change = gApi.changes().id(changeId).get();
+ assertThat(change.submitRequirements).isEmpty();
+
+ voteLabel(changeId, "code-review", 2);
+ change = gApi.changes().id(changeId).get();
+ assertThat(change.submitRequirements).isEmpty();
+
+ gApi.changes().id(changeId).current().submit();
+ change = gApi.changes().id(changeId).get();
+ assertThat(change.submitRequirements).isEmpty();
+ }
+
+ @Test
public void fourByteEmoji() throws Exception {
// U+1F601 GRINNING FACE WITH SMILING EYES
String smile = new String(Character.toChars(0x1f601));
diff --git a/javatests/com/google/gerrit/acceptance/api/change/ChangeSubmitRequirementIT.java b/javatests/com/google/gerrit/acceptance/api/change/ChangeSubmitRequirementIT.java
index c6b57da..96db71a 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/ChangeSubmitRequirementIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/ChangeSubmitRequirementIT.java
@@ -19,6 +19,7 @@
import com.google.common.collect.ImmutableList;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.config.GerritConfig;
import com.google.gerrit.entities.LegacySubmitRequirement;
import com.google.gerrit.entities.SubmitRecord;
import com.google.gerrit.extensions.annotations.Exports;
@@ -28,6 +29,7 @@
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.LegacySubmitRequirementInfo;
import com.google.gerrit.extensions.config.FactoryModule;
+import com.google.gerrit.server.experiments.ExperimentFeaturesConstants;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.rules.SubmitRule;
import com.google.inject.Inject;
@@ -201,6 +203,37 @@
assertThat(rule.numberOfEvaluations.get()).isEqualTo(0);
}
+ @Test
+ @GerritConfig(
+ name = "experiments.enabled",
+ values = {
+ ExperimentFeaturesConstants
+ .GERRIT_BACKEND_REQUEST_FEATURE_ENABLE_SUBMIT_REQUIREMENTS_BACKFILLING_ON_DASHBOARD,
+ ExperimentFeaturesConstants.GERRIT_BACKEND_REQUEST_FEATURE_ENABLE_SUBMIT_REQUIREMENTS
+ })
+ public void submitRuleIsInvokedWhenQueryingChangeWithExperiment() throws Exception {
+ PushOneCommit.Result r = createChange("Some Change", "foo.txt", "some content");
+ String changeId = r.getChangeId();
+
+ rule.numberOfEvaluations.set(0);
+ gApi.changes().query(changeId).withOptions(ListChangesOption.SUBMIT_REQUIREMENTS).get();
+
+ // Submit rules are invoked
+ assertThat(rule.numberOfEvaluations.get()).isEqualTo(1);
+ }
+
+ @Test
+ public void submitRuleIsNotInvokedWhenQueryingChangeWithoutExperiment() throws Exception {
+ PushOneCommit.Result r = createChange("Some Change", "foo.txt", "some content");
+ String changeId = r.getChangeId();
+
+ rule.numberOfEvaluations.set(0);
+ gApi.changes().query(changeId).withOptions(ListChangesOption.SUBMIT_REQUIREMENTS).get();
+
+ // Submit rules are not invoked
+ assertThat(rule.numberOfEvaluations.get()).isEqualTo(0);
+ }
+
private List<ChangeInfo> queryIsSubmittable() throws Exception {
return gApi.changes().query("is:submittable project:" + project.get()).get();
}
diff --git a/javatests/com/google/gerrit/server/notedb/ChangeNotesStateTest.java b/javatests/com/google/gerrit/server/notedb/ChangeNotesStateTest.java
index 52eaf11..61002f9 100644
--- a/javatests/com/google/gerrit/server/notedb/ChangeNotesStateTest.java
+++ b/javatests/com/google/gerrit/server/notedb/ChangeNotesStateTest.java
@@ -687,7 +687,7 @@
.submitRequirementsResult(
ImmutableList.of(
SubmitRequirementResult.builder()
- .legacy(true)
+ .legacy(Optional.of(true))
.patchSetCommitId(
ObjectId.fromString("26e50c7d315a33a13e5cc00902781fa876bc36cd"))
.submitRequirement(
diff --git a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
index 2c6dd66..332548a 100644
--- a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
+++ b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
@@ -601,6 +601,29 @@
}
@Test
+ public void byUploader() throws Exception {
+ assume().that(getSchema().hasField(ChangeField.UPLOADER)).isTrue();
+ Account.Id user2 =
+ accountManager.authenticate(authRequestFactory.createForUser("anotheruser")).getAccountId();
+ CurrentUser user2CurrentUser = userFactory.create(user2);
+
+ TestRepository<Repo> repo = createProject("repo");
+ Change change1 = insert(repo, newChange(repo), userId);
+ assertQuery("is:uploader", change1);
+ assertQuery("uploader:" + userId.get(), change1);
+ change1 = newPatchSet(repo, change1, user2CurrentUser);
+ // Uploader has changed
+ assertQuery("uploader:" + userId.get());
+ assertQuery("uploader:" + user2.get(), change1);
+
+ requestContext.setContext(newRequestContext(user2));
+ assertQuery("is:uploader", change1); // self (user2)
+
+ String nameEmail = user2CurrentUser.asIdentifiedUser().getNameEmail();
+ assertQuery("uploader: \"" + nameEmail + "\"", change1);
+ }
+
+ @Test
public void byAuthorExact() throws Exception {
assume().that(getSchema().hasField(ChangeField.EXACT_AUTHOR)).isTrue();
byAuthorOrCommitterExact("author:");
@@ -716,6 +739,20 @@
}
@Test
+ public void byUploaderIn() throws Exception {
+ assume().that(getSchema().hasField(ChangeField.UPLOADER)).isTrue();
+ TestRepository<Repo> repo = createProject("repo");
+ Change change1 = insert(repo, newChange(repo), userId);
+ assertQuery("uploaderin:Administrators", change1);
+
+ Account.Id user2 =
+ accountManager.authenticate(authRequestFactory.createForUser("anotheruser")).getAccountId();
+ CurrentUser user2CurrentUser = userFactory.create(user2);
+ newPatchSet(repo, change1, user2CurrentUser);
+ assertQuery("uploaderin:Administrators");
+ }
+
+ @Test
public void byProject() throws Exception {
TestRepository<Repo> repo1 = createProject("repo1");
TestRepository<Repo> repo2 = createProject("repo2");
@@ -2585,7 +2622,7 @@
gApi.changes().id(change2.getId().get()).current().review(new ReviewInput().message("comment"));
PatchSet.Id ps3_1 = change3.currentPatchSetId();
- change3 = newPatchSet(repo, change3);
+ change3 = newPatchSet(repo, change3, user);
assertThat(change3.currentPatchSetId()).isNotEqualTo(ps3_1);
// Response to previous patch set still counts as reviewing.
gApi.changes()
@@ -3891,7 +3928,8 @@
}
}
- protected Change newPatchSet(TestRepository<Repo> repo, Change c) throws Exception {
+ protected Change newPatchSet(TestRepository<Repo> repo, Change c, CurrentUser user)
+ throws Exception {
// Add a new file so the patch set is not a trivial rebase, to avoid default
// Code-Review label copying.
int n = c.currentPatchSetId().get() + 1;
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts
index 891482d..b10c17e 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts
@@ -95,9 +95,9 @@
} from '../../shared/gr-js-api-interface/gr-change-actions-js-api';
import {fireAlert, fireEvent, fireReload} from '../../../utils/event-util';
import {
- CODE_REVIEW,
getApprovalInfo,
getVotingRange,
+ StandardLabels,
} from '../../../utils/label-util';
import {CommentThread} from '../../../utils/comment-util';
import {ShowAlertEventDetail} from '../../../types/events';
@@ -975,8 +975,9 @@
// Allow the user to use quick approve to vote the max score on code review
// even if it is already granted by someone else. Does not apply if the
// user owns the change or has already granted the max score themselves.
- const codeReviewLabel = this.change.labels[CODE_REVIEW];
- const codeReviewPermittedValues = this.change.permitted_labels[CODE_REVIEW];
+ const codeReviewLabel = this.change.labels[StandardLabels.CODE_REVIEW];
+ const codeReviewPermittedValues =
+ this.change.permitted_labels[StandardLabels.CODE_REVIEW];
if (
!result &&
codeReviewLabel &&
@@ -988,7 +989,7 @@
getApprovalInfo(codeReviewLabel, this.account)?.value !==
getVotingRange(codeReviewLabel)?.max
) {
- result = CODE_REVIEW;
+ result = StandardLabels.CODE_REVIEW;
}
if (result) {
diff --git a/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row_html.ts b/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row_html.ts
index e21584e..e57a216 100644
--- a/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row_html.ts
@@ -53,29 +53,31 @@
);
padding: 0 var(--spacing-m);
}
- gr-tooltip-content.iron-selected > gr-button[vote='max'] {
+ gr-button[vote='max'].iron-selected {
--button-background-color: var(--vote-color-approved);
}
- gr-tooltip-content.iron-selected > gr-button[vote='positive'] {
+ gr-button[vote='positive'].iron-selected {
--button-background-color: var(--vote-color-recommended);
}
- gr-tooltip-content.iron-selected > gr-button[vote='min'] {
+ gr-button[vote='min'].iron-selected {
--button-background-color: var(--vote-color-rejected);
}
- gr-tooltip-content.iron-selected > gr-button[vote='negative'] {
+ gr-button[vote='negative'].iron-selected {
--button-background-color: var(--vote-color-disliked);
}
- gr-tooltip-content.iron-selected > gr-button[vote='neutral'] {
+ gr-button[vote='neutral'].iron-selected {
--button-background-color: var(--vote-color-neutral);
}
- gr-tooltip-content.iron-selected
- > gr-button[vote='positive']::part(paper-button) {
+ gr-button[vote='positive'].iron-selected::part(paper-button) {
border-color: var(--vote-outline-recommended);
}
- gr-tooltip-content.iron-selected
- > gr-button[vote='negative']::part(paper-button) {
+ gr-button[vote='negative'].iron-selected::part(paper-button) {
border-color: var(--vote-outline-disliked);
}
+ gr-button > gr-tooltip-content {
+ margin: 0px -10px;
+ padding: 0px 10px;
+ }
.placeholder {
display: inline-block;
width: 42px;
@@ -118,20 +120,22 @@
aria-labelledby="labelName"
>
<template is="dom-repeat" items="[[_items]]" as="value">
- <gr-tooltip-content
- has-tooltip
+ <gr-button
+ role="radio"
+ vote$="[[_computeVoteAttribute(value, index, _items.length)]]"
title$="[[_computeLabelValueTitle(labels, label.name, value)]]"
data-name$="[[label.name]]"
data-value$="[[value]]"
+ aria-label$="[[value]]"
+ voteChip
>
- <gr-button
- role="radio"
- vote$="[[_computeVoteAttribute(value, index, _items.length)]]"
- voteChip
+ <gr-tooltip-content
+ has-tooltip
+ title$="[[_computeLabelValueTitle(labels, label.name, value)]]"
>
[[value]]
- </gr-button>
- </gr-tooltip-content>
+ </gr-tooltip-content>
+ </gr-button>
</template>
</iron-selector>
<template
diff --git a/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row_test.js b/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row_test.js
index 34e959b..51d76b2 100644
--- a/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row_test.js
+++ b/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row_test.js
@@ -101,15 +101,12 @@
const labelsChangedHandler = sinon.stub();
element.addEventListener('labels-changed', labelsChangedHandler);
assert.ok(element.$.labelSelector);
- MockInteractions.tap(element.shadowRoot
- .querySelector(
- 'gr-tooltip-content[data-value="-1"] > gr-button'));
+ MockInteractions.tap(
+ element.shadowRoot.querySelector('gr-button[data-value="-1"]'));
await flush();
assert.strictEqual(element.selectedValue, '-1');
- assert.strictEqual(element.selectedItem
- .textContent.trim(), '-1');
- assert.strictEqual(
- element.$.selectedValueLabel.textContent.trim(), 'bad');
+ assert.strictEqual(element.selectedItem.textContent.trim(), '-1');
+ assert.strictEqual(element.$.selectedValueLabel.textContent.trim(), 'bad');
const detail = labelsChangedHandler.args[0][0].detail;
assert.equal(detail.name, 'Verified');
assert.equal(detail.value, '-1');
@@ -121,49 +118,48 @@
let index = 0;
const totalItems = 5;
// positive and first position
- assert.equal(element._computeVoteAttribute(value, index,
- totalItems), 'positive');
+ assert.equal(
+ element._computeVoteAttribute(value, index, totalItems), 'positive');
// negative and first position
value = -1;
- assert.equal(element._computeVoteAttribute(value, index,
- totalItems), 'min');
+ assert.equal(
+ element._computeVoteAttribute(value, index, totalItems), 'min');
// negative but not first position
index = 1;
- assert.equal(element._computeVoteAttribute(value, index,
- totalItems), 'negative');
+ assert.equal(
+ element._computeVoteAttribute(value, index, totalItems), 'negative');
// neutral
value = 0;
- assert.equal(element._computeVoteAttribute(value, index,
- totalItems), 'neutral');
+ assert.equal(
+ element._computeVoteAttribute(value, index, totalItems), 'neutral');
// positive but not last position
value = 1;
- assert.equal(element._computeVoteAttribute(value, index,
- totalItems), 'positive');
+ assert.equal(
+ element._computeVoteAttribute(value, index, totalItems), 'positive');
// positive and last position
index = 4;
- assert.equal(element._computeVoteAttribute(value, index,
- totalItems), 'max');
+ assert.equal(
+ element._computeVoteAttribute(value, index, totalItems), 'max');
// negative and last position
value = -1;
- assert.equal(element._computeVoteAttribute(value, index,
- totalItems), 'negative');
+ assert.equal(
+ element._computeVoteAttribute(value, index, totalItems), 'negative');
});
test('correct item is selected', () => {
// 1 should be the value of the selected item
assert.strictEqual(element.$.labelSelector.selected, '+1');
assert.strictEqual(
- element.$.labelSelector.selectedItem
- .textContent.trim(), '+1');
- assert.strictEqual(
- element.$.selectedValueLabel.textContent.trim(), 'good');
+ element.$.labelSelector.selectedItem.textContent.trim(), '+1');
+ assert.strictEqual(element.$.selectedValueLabel.textContent.trim(), 'good');
checkAriaCheckedValid();
});
test('_computeLabelValue', () => {
- assert.strictEqual(element._computeLabelValue(element.labels,
- element.permittedLabels,
- element.label), '+1');
+ assert.strictEqual(
+ element._computeLabelValue(
+ element.labels, element.permittedLabels, element.label),
+ '+1');
});
test('_computeBlankItems', () => {
@@ -175,18 +171,21 @@
'2': 4,
};
- assert.strictEqual(element._computeBlankItems(element.permittedLabels,
- 'Code-Review').length, 0);
+ assert.strictEqual(
+ element._computeBlankItems(element.permittedLabels, 'Code-Review')
+ .length,
+ 0);
- assert.strictEqual(element._computeBlankItems(element.permittedLabels,
- 'Verified').length, 1);
+ assert.strictEqual(
+ element._computeBlankItems(element.permittedLabels, 'Verified').length,
+ 1);
});
test('labelValues returns no keys', () => {
element.labelValues = {};
- assert.deepEqual(element._computeBlankItems(element.permittedLabels,
- 'Code-Review'), []);
+ assert.deepEqual(
+ element._computeBlankItems(element.permittedLabels, 'Code-Review'), []);
});
test('changes in label score are reflected in the DOM', async () => {
@@ -260,11 +259,8 @@
],
};
await flush();
- assert.strictEqual(element.$.labelSelector
- .items.length, 2);
- assert.strictEqual(
- element.root.querySelectorAll('.placeholder').length,
- 3);
+ assert.strictEqual(element.$.labelSelector.items.length, 2);
+ assert.strictEqual(element.root.querySelectorAll('.placeholder').length, 3);
element.permittedLabels = {
'Code-Review': [
@@ -280,11 +276,8 @@
],
};
await flush();
- assert.strictEqual(element.$.labelSelector
- .items.length, 5);
- assert.strictEqual(
- element.root.querySelectorAll('.placeholder').length,
- 0);
+ assert.strictEqual(element.$.labelSelector.items.length, 5);
+ assert.strictEqual(element.root.querySelectorAll('.placeholder').length, 0);
});
test('default_value', () => {
@@ -344,4 +337,3 @@
assert.isNull(element.selectedValue);
});
});
-
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog-it_test.js b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog-it_test.js
index b8c9319..486f37a 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog-it_test.js
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog-it_test.js
@@ -16,10 +16,11 @@
*/
import '../../../test/common-test-setup-karma.js';
-import {queryAndAssert, resetPlugins, stubRestApi} from '../../../test/test-utils.js';
import './gr-reply-dialog.js';
-import {getPluginLoader} from '../../shared/gr-js-api-interface/gr-plugin-loader.js';
+
+import {queryAndAssert, resetPlugins, stubRestApi} from '../../../test/test-utils.js';
import {_testOnly_initGerritPluginApi} from '../../shared/gr-js-api-interface/gr-gerrit.js';
+import {getPluginLoader} from '../../shared/gr-js-api-interface/gr-plugin-loader.js';
const basicFixture = fixtureFromElement('gr-reply-dialog');
const pluginApi = _testOnly_initGerritPluginApi();
@@ -89,14 +90,12 @@
const sendStub = sinon.stub(element, 'send').returns(Promise.resolve());
element.$.ccs.$.entry.setText('test');
- MockInteractions.tap(element.shadowRoot
- .querySelector('gr-button.send'));
+ MockInteractions.tap(element.shadowRoot.querySelector('gr-button.send'));
assert.isFalse(sendStub.called);
flush();
element.$.ccs.$.entry.setText('test@test.test');
- MockInteractions.tap(element.shadowRoot
- .querySelector('gr-button.send'));
+ MockInteractions.tap(element.shadowRoot.querySelector('gr-button.send'));
assert.isTrue(sendStub.called);
});
@@ -107,9 +106,7 @@
replyApi.addReplyTextChangedCallback(text => {
const label = 'Code-Review';
const labelValue = replyApi.getLabelValue(label);
- if (labelValue &&
- labelValue === ' 0' &&
- text.indexOf('LGTM') === 0) {
+ if (labelValue && labelValue === ' 0' && text.indexOf('LGTM') === 0) {
replyApi.setLabelValue(label, '+1');
}
});
@@ -121,13 +118,13 @@
await flush();
const textarea = queryAndAssert(element, 'gr-textarea').getNativeTextarea();
textarea.value = 'LGTM';
- textarea.dispatchEvent(new CustomEvent(
- 'input', {bubbles: true, composed: true}));
+ textarea.dispatchEvent(
+ new CustomEvent('input', {bubbles: true, composed: true}));
await flush();
- const labelScoreRows = element.getLabelScores().shadowRoot
- .querySelector('gr-label-score-row[name="Code-Review"]');
- const selectedBtn = labelScoreRows.shadowRoot
- .querySelector('gr-tooltip-content[data-value="+1"] > gr-button');
+ const labelScoreRows = element.getLabelScores().shadowRoot.querySelector(
+ 'gr-label-score-row[name="Code-Review"]');
+ const selectedBtn =
+ labelScoreRows.shadowRoot.querySelector('gr-button[data-value="+1"]');
assert.isOk(selectedBtn);
});
});
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts
index 17036e6..10e04f7 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts
@@ -99,9 +99,9 @@
import {GrAccountChip} from '../../shared/gr-account-chip/gr-account-chip';
import {GrOverlay} from '../../shared/gr-overlay/gr-overlay';
import {
- CODE_REVIEW,
getApprovalInfo,
getMaxAccounts,
+ StandardLabels,
} from '../../../utils/label-util';
import {pluralize} from '../../../utils/string-util';
import {
@@ -986,7 +986,7 @@
}
_computeCommentAccounts(threads: CommentThread[]) {
- const crLabel = this.change?.labels?.[CODE_REVIEW];
+ const crLabel = this.change?.labels?.[StandardLabels.CODE_REVIEW];
const maxCrVoteAccountIds = getMaxAccounts(crLabel).map(a => a._account_id);
const accountIds = new Set<AccountId>();
threads.forEach(thread => {
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.ts b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.ts
index e57ffc7..c4bff57 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.ts
@@ -32,7 +32,7 @@
import {addListenerForTest} from '../../../test/test-utils';
import {stubRestApi} from '../../../test/test-utils';
import {JSON_PREFIX} from '../../shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper';
-import {CODE_REVIEW} from '../../../utils/label-util';
+import {StandardLabels} from '../../../utils/label-util';
import {
createAccountWithId,
createChange,
@@ -2116,9 +2116,9 @@
test('_computeSendButtonDisabled_existingVote', async () => {
const account = createAccountWithId();
- (element.change!.labels![CODE_REVIEW]! as DetailedLabelInfo).all = [
- account,
- ];
+ (
+ element.change!.labels![StandardLabels.CODE_REVIEW]! as DetailedLabelInfo
+ ).all = [account];
await flush();
// User has already voted.
diff --git a/polygerrit-ui/app/elements/change/gr-submit-requirements/gr-submit-requirements.ts b/polygerrit-ui/app/elements/change/gr-submit-requirements/gr-submit-requirements.ts
index 78de838..c493be4 100644
--- a/polygerrit-ui/app/elements/change/gr-submit-requirements/gr-submit-requirements.ts
+++ b/polygerrit-ui/app/elements/change/gr-submit-requirements/gr-submit-requirements.ts
@@ -36,6 +36,7 @@
hasNeutralStatus,
hasVotes,
iconForStatus,
+ orderSubmitRequirements,
} from '../../../utils/label-util';
import {fontStyles} from '../../../styles/gr-font-styles';
import {charsOnly, pluralize} from '../../../utils/string-util';
@@ -131,9 +132,10 @@
}
override render() {
- const submit_requirements = (this.change?.submit_requirements ?? []).filter(
- req => req.status !== SubmitRequirementStatus.NOT_APPLICABLE
- );
+ const submit_requirements = orderSubmitRequirements(
+ this.change?.submit_requirements ?? []
+ ).filter(req => req.status !== SubmitRequirementStatus.NOT_APPLICABLE);
+
return html` <h3
class="metadata-title heading-3"
id="submit-requirements-caption"
diff --git a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
index 0d4ad8c..53b3bb9 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
+++ b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
@@ -682,7 +682,7 @@
* comment triggered a recomputation of comments and the text written by
* the user was lost.
*/
- if (!this._messageText) this._messageText = message || '';
+ if (!this._messageText || !this.editing) this._messageText = message || '';
}
_messageTextChanged(_: string, oldValue: string) {
diff --git a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.ts b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.ts
index b963d4b..53e62ff 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.ts
@@ -240,6 +240,29 @@
assert.equal(element._messageText, 'hello world');
});
+ test('comment message sets messageText when not edited', () => {
+ element.changeNum = 1 as NumericChangeId;
+ element.patchNum = 1 as PatchSetNum;
+ element._messageText = 'Some text';
+ element.comment = {
+ author: {
+ name: 'Mr. Peanutbutter',
+ email: 'tenn1sballchaser@aol.com' as EmailAddress,
+ },
+ line: 5,
+ path: 'test',
+ __editing: false,
+ __draft: true,
+ message: 'hello world',
+ };
+ // messageText was empty so overwrite the message now
+ assert.equal(element._messageText, 'hello world');
+
+ element.comment!.message = 'new message';
+ // messageText was already set so do not overwrite it
+ assert.equal(element._messageText, 'hello world');
+ });
+
test('_getPatchNum', () => {
element.side = 'PARENT';
element.patchNum = 1 as PatchSetNum;
diff --git a/polygerrit-ui/app/utils/label-util.ts b/polygerrit-ui/app/utils/label-util.ts
index 0e89500..c50bbe2 100644
--- a/polygerrit-ui/app/utils/label-util.ts
+++ b/polygerrit-ui/app/utils/label-util.ts
@@ -31,7 +31,11 @@
import {assertNever, unique} from './common-util';
// Name of the standard Code-Review label.
-export const CODE_REVIEW = 'Code-Review';
+export enum StandardLabels {
+ CODE_REVIEW = 'Code-Review',
+ CODE_OWNERS = 'Code Owners',
+ PRESUBMIT_VERIFIED = 'Presubmit-Verified',
+}
export enum LabelStatus {
APPROVED = 'APPROVED',
@@ -178,9 +182,13 @@
}
export function labelCompare(labelName1: string, labelName2: string) {
- if (labelName1 === CODE_REVIEW && labelName2 === CODE_REVIEW) return 0;
- if (labelName1 === CODE_REVIEW) return -1;
- if (labelName2 === CODE_REVIEW) return 1;
+ if (
+ labelName1 === StandardLabels.CODE_REVIEW &&
+ labelName2 === StandardLabels.CODE_REVIEW
+ )
+ return 0;
+ if (labelName1 === StandardLabels.CODE_REVIEW) return -1;
+ if (labelName2 === StandardLabels.CODE_REVIEW) return 1;
return labelName1.localeCompare(labelName2);
}
@@ -189,7 +197,7 @@
labels: LabelNameToInfoMap
): LabelInfo | undefined {
for (const label of Object.keys(labels)) {
- if (label === CODE_REVIEW) {
+ if (label === StandardLabels.CODE_REVIEW) {
return labels[label];
}
}
@@ -226,3 +234,24 @@
assertNever(status, `Unsupported status: ${status}`);
}
}
+
+// TODO(milutin): This may be temporary for demo purposes
+const PRIORITY_REQUIREMENTS_ORDER: string[] = [
+ StandardLabels.CODE_REVIEW,
+ StandardLabels.CODE_OWNERS,
+ StandardLabels.PRESUBMIT_VERIFIED,
+];
+export function orderSubmitRequirements(
+ requirements: SubmitRequirementResultInfo[]
+) {
+ let priorityRequirementList: SubmitRequirementResultInfo[] = [];
+ for (const label of PRIORITY_REQUIREMENTS_ORDER) {
+ const priorityRequirement = requirements.filter(r => r.name === label);
+ priorityRequirementList =
+ priorityRequirementList.concat(priorityRequirement);
+ }
+ const nonPriorityRequirements = requirements.filter(
+ r => !PRIORITY_REQUIREMENTS_ORDER.includes(r.name)
+ );
+ return priorityRequirementList.concat(nonPriorityRequirements);
+}