Merge "Move most of shortcut mixin features into service"
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/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/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/project/ProjectConfig.java b/java/com/google/gerrit/server/project/ProjectConfig.java
index 975ad52..8f0b535 100644
--- a/java/com/google/gerrit/server/project/ProjectConfig.java
+++ b/java/com/google/gerrit/server/project/ProjectConfig.java
@@ -943,8 +943,7 @@
if (lowerNames.containsKey(lower)) {
error(
String.format(
- "Submit requirement \"%s\" conflicts with \"%s\". Skipping the former.",
- name, lowerNames.get(lower)));
+ "Submit requirement '%s' conflicts with '%s'.", name, lowerNames.get(lower)));
continue;
}
lowerNames.put(lower, name);
@@ -958,9 +957,7 @@
if (blockExpr == null) {
error(
String.format(
- "Submit requirement \"%s\" does not define a submittability expression."
- + " Skipping this requirement.",
- name));
+ "Submit requirement '%s' does not define a submittability expression.", name));
continue;
}
diff --git a/java/com/google/gerrit/server/project/SubmitRequirementsEvaluatorImpl.java b/java/com/google/gerrit/server/project/SubmitRequirementsEvaluatorImpl.java
index 9555bf3..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 =
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 9ea6475..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();
@@ -964,12 +968,6 @@
notes().getSubmitRequirementsResult().stream()
.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/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/acceptance/server/project/SubmitRequirementsValidationIT.java b/javatests/com/google/gerrit/acceptance/server/project/SubmitRequirementsValidationIT.java
new file mode 100644
index 0000000..4675bc0
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/server/project/SubmitRequirementsValidationIT.java
@@ -0,0 +1,252 @@
+// 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.acceptance.server.project;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+import static com.google.gerrit.acceptance.GitUtil.fetch;
+import static com.google.gerrit.acceptance.GitUtil.pushHead;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.entities.RefNames;
+import com.google.gerrit.server.project.ProjectConfig;
+import java.util.Locale;
+import java.util.function.Consumer;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevObject;
+import org.eclipse.jgit.transport.PushResult;
+import org.eclipse.jgit.transport.RemoteRefUpdate;
+import org.eclipse.jgit.transport.RemoteRefUpdate.Status;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.util.RawParseUtils;
+import org.junit.Test;
+
+/**
+ * Tests validating submit requirements on upload of {@code project.config} to {@code
+ * refs/meta/config}.
+ */
+public class SubmitRequirementsValidationIT extends AbstractDaemonTest {
+ @Test
+ public void validSubmitRequirementIsAccepted_optionalParametersNotSet() throws Exception {
+ fetchRefsMetaConfig();
+
+ String submitRequirementName = "Code-Review";
+ updateProjectConfig(
+ projectConfig ->
+ projectConfig.setString(
+ ProjectConfig.SUBMIT_REQUIREMENT,
+ /* subsection= */ submitRequirementName,
+ /* name= */ ProjectConfig.KEY_SR_SUBMITTABILITY_EXPRESSION,
+ /* value= */ "label:\"code-review=+2\""));
+
+ PushResult r = pushRefsMetaConfig();
+ assertOkStatus(r);
+ }
+
+ @Test
+ public void validSubmitRequirementIsAccepted_allParametersSet() throws Exception {
+ fetchRefsMetaConfig();
+
+ String submitRequirementName = "Code-Review";
+ updateProjectConfig(
+ projectConfig -> {
+ projectConfig.setString(
+ ProjectConfig.SUBMIT_REQUIREMENT,
+ /* subsection= */ submitRequirementName,
+ /* name= */ ProjectConfig.KEY_SR_DESCRIPTION,
+ /* value= */ "foo bar description");
+ projectConfig.setString(
+ ProjectConfig.SUBMIT_REQUIREMENT,
+ /* subsection= */ submitRequirementName,
+ /* name= */ ProjectConfig.KEY_SR_APPLICABILITY_EXPRESSION,
+ /* value= */ "branch:refs/heads/master");
+ projectConfig.setString(
+ ProjectConfig.SUBMIT_REQUIREMENT,
+ /* subsection= */ submitRequirementName,
+ /* name= */ ProjectConfig.KEY_SR_SUBMITTABILITY_EXPRESSION,
+ /* value= */ "label:\"code-review=+2\"");
+ projectConfig.setString(
+ ProjectConfig.SUBMIT_REQUIREMENT,
+ /* subsection= */ submitRequirementName,
+ /* name= */ ProjectConfig.KEY_SR_OVERRIDE_EXPRESSION,
+ /* value= */ "label:\"override=+1\"");
+ projectConfig.setBoolean(
+ ProjectConfig.SUBMIT_REQUIREMENT,
+ /* subsection= */ submitRequirementName,
+ /* name= */ ProjectConfig.KEY_SR_OVERRIDE_IN_CHILD_PROJECTS,
+ /* value= */ false);
+ });
+
+ PushResult r = pushRefsMetaConfig();
+ assertOkStatus(r);
+ }
+
+ @Test
+ public void conflictingSubmitRequirementsAreRejected() throws Exception {
+ fetchRefsMetaConfig();
+
+ String submitRequirementName = "Code-Review";
+ updateProjectConfig(
+ projectConfig -> {
+ projectConfig.setString(
+ ProjectConfig.SUBMIT_REQUIREMENT,
+ /* subsection= */ submitRequirementName,
+ /* name= */ ProjectConfig.KEY_SR_SUBMITTABILITY_EXPRESSION,
+ /* value= */ "label:\"code-review=+2\"");
+ projectConfig.setString(
+ ProjectConfig.SUBMIT_REQUIREMENT,
+ /* subsection= */ submitRequirementName.toLowerCase(Locale.US),
+ /* name= */ ProjectConfig.KEY_SR_SUBMITTABILITY_EXPRESSION,
+ /* value= */ "label:\"code-review=+2\"");
+ });
+
+ PushResult r = pushRefsMetaConfig();
+ assertErrorStatus(
+ r,
+ "Invalid project configuration",
+ String.format(
+ "project.config: Submit requirement '%s' conflicts with '%s'.",
+ submitRequirementName.toLowerCase(Locale.US), submitRequirementName));
+ }
+
+ @Test
+ public void conflictingSubmitRequirementIsRejected() throws Exception {
+ fetchRefsMetaConfig();
+ String submitRequirementName = "Code-Review";
+ updateProjectConfig(
+ projectConfig ->
+ projectConfig.setString(
+ ProjectConfig.SUBMIT_REQUIREMENT,
+ /* subsection= */ submitRequirementName,
+ /* name= */ ProjectConfig.KEY_SR_SUBMITTABILITY_EXPRESSION,
+ /* value= */ "label:\"code-review=+2\""));
+ PushResult r = pushRefsMetaConfig();
+ assertOkStatus(r);
+
+ updateProjectConfig(
+ projectConfig ->
+ projectConfig.setString(
+ ProjectConfig.SUBMIT_REQUIREMENT,
+ /* subsection= */ submitRequirementName.toLowerCase(Locale.US),
+ /* name= */ ProjectConfig.KEY_SR_SUBMITTABILITY_EXPRESSION,
+ /* value= */ "label:\"code-review=+2\""));
+ r = pushRefsMetaConfig();
+ assertErrorStatus(
+ r,
+ "Invalid project configuration",
+ String.format(
+ "project.config: Submit requirement '%s' conflicts with '%s'.",
+ submitRequirementName.toLowerCase(Locale.US), submitRequirementName));
+ }
+
+ @Test
+ public void submitRequirementWithoutSubmittabilityExpressionIsRejected() throws Exception {
+ fetchRefsMetaConfig();
+
+ String submitRequirementName = "Code-Review";
+ updateProjectConfig(
+ projectConfig ->
+ projectConfig.setString(
+ ProjectConfig.SUBMIT_REQUIREMENT,
+ /* subsection= */ submitRequirementName,
+ /* name= */ ProjectConfig.KEY_SR_DESCRIPTION,
+ /* value= */ "foo bar description"));
+
+ PushResult r = pushRefsMetaConfig();
+ assertErrorStatus(
+ r,
+ "Invalid project configuration",
+ String.format(
+ "project.config: Submit requirement '%s' does not define a submittability expression.",
+ submitRequirementName));
+ }
+
+ private void fetchRefsMetaConfig() throws Exception {
+ fetch(testRepo, RefNames.REFS_CONFIG + ":" + RefNames.REFS_CONFIG);
+ testRepo.reset(RefNames.REFS_CONFIG);
+ }
+
+ private PushResult pushRefsMetaConfig() throws Exception {
+ return pushHead(testRepo, RefNames.REFS_CONFIG);
+ }
+
+ private void updateProjectConfig(Consumer<Config> configUpdater) throws Exception {
+ RevCommit head = getHead(testRepo.getRepository(), RefNames.REFS_CONFIG);
+ Config projectConfig = readProjectConfig(head);
+ configUpdater.accept(projectConfig);
+ RevCommit commit =
+ testRepo.update(
+ RefNames.REFS_CONFIG,
+ testRepo
+ .commit()
+ .parent(head)
+ .message("Update project config")
+ .author(admin.newIdent())
+ .committer(admin.newIdent())
+ .add(ProjectConfig.PROJECT_CONFIG, projectConfig.toText()));
+
+ testRepo.reset(commit);
+ }
+
+ private Config readProjectConfig(RevCommit commit) throws Exception {
+ try (TreeWalk tw =
+ TreeWalk.forPath(
+ testRepo.getRevWalk().getObjectReader(),
+ ProjectConfig.PROJECT_CONFIG,
+ commit.getTree())) {
+ if (tw == null) {
+ throw new IllegalStateException(
+ String.format("%s does not exist", ProjectConfig.PROJECT_CONFIG));
+ }
+ }
+ RevObject blob = testRepo.get(commit.getTree(), ProjectConfig.PROJECT_CONFIG);
+ byte[] data = testRepo.getRepository().open(blob).getCachedBytes(Integer.MAX_VALUE);
+ String content = RawParseUtils.decode(data);
+
+ Config projectConfig = new Config();
+ projectConfig.fromText(content);
+ return projectConfig;
+ }
+
+ public void assertOkStatus(PushResult result) {
+ RemoteRefUpdate refUpdate = result.getRemoteUpdate(RefNames.REFS_CONFIG);
+ assertThat(refUpdate).isNotNull();
+ assertWithMessage(getMessage(result, refUpdate))
+ .that(refUpdate.getStatus())
+ .isEqualTo(Status.OK);
+ }
+
+ public void assertErrorStatus(PushResult result, String... expectedMessages) {
+ RemoteRefUpdate refUpdate = result.getRemoteUpdate(RefNames.REFS_CONFIG);
+ assertThat(refUpdate).isNotNull();
+ assertWithMessage(getMessage(result, refUpdate))
+ .that(refUpdate.getStatus())
+ .isEqualTo(Status.REJECTED_OTHER_REASON);
+ for (String expectedMessage : expectedMessages) {
+ assertThat(result.getMessages()).contains(expectedMessage);
+ }
+ }
+
+ private String getMessage(PushResult result, RemoteRefUpdate refUpdate) {
+ StringBuilder b = new StringBuilder();
+ if (refUpdate.getMessage() != null) {
+ b.append(refUpdate.getMessage());
+ b.append("\n");
+ }
+ b.append(result.getMessages());
+ return b.toString();
+ }
+}
diff --git a/javatests/com/google/gerrit/server/project/ProjectConfigTest.java b/javatests/com/google/gerrit/server/project/ProjectConfigTest.java
index 7f0b685..9df59c2 100644
--- a/javatests/com/google/gerrit/server/project/ProjectConfigTest.java
+++ b/javatests/com/google/gerrit/server/project/ProjectConfigTest.java
@@ -310,9 +310,7 @@
assertThat(cfg.getValidationErrors()).hasSize(1);
assertThat(Iterables.getOnlyElement(cfg.getValidationErrors()).getMessage())
.isEqualTo(
- "project.config: "
- + "Submit requirement \"Code-Review\" conflicts with \"code-review\". "
- + "Skipping the former.");
+ "project.config: Submit requirement 'Code-Review' conflicts with 'code-review'.");
}
@Test
@@ -332,8 +330,8 @@
assertThat(cfg.getValidationErrors()).hasSize(1);
assertThat(Iterables.getOnlyElement(cfg.getValidationErrors()).getMessage())
.isEqualTo(
- "project.config: Submit requirement \"code-review\" does not define a submittability"
- + " expression. Skipping this requirement.");
+ "project.config: Submit requirement 'code-review' does not define a submittability"
+ + " expression.");
}
@Test
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_test.js b/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row_test.js
index b3214c5..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,14 +101,12 @@
const labelsChangedHandler = sinon.stub();
element.addEventListener('labels-changed', labelsChangedHandler);
assert.ok(element.$.labelSelector);
- MockInteractions.tap(element.shadowRoot
- .querySelector('gr-button[data-value="-1"]'));
+ 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');
@@ -120,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', () => {
@@ -174,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 () => {
@@ -259,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': [
@@ -279,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', () => {
@@ -343,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 14ea4f59..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-button[data-value="+1"]');
+ 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 be57ef6..b8932e5 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
@@ -98,9 +98,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 {
@@ -984,7 +984,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 91b8603..5a11f47 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 e302b43..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';
@@ -77,7 +78,8 @@
width: var(--line-height-normal, 20px);
height: var(--line-height-normal, 20px);
}
- iron-icon.check {
+ iron-icon.check,
+ iron-icon.overridden {
color: var(--success-foreground);
}
iron-icon.close {
@@ -130,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-icons/gr-icons.ts b/polygerrit-ui/app/elements/shared/gr-icons/gr-icons.ts
index da1a782..1a6239f 100644
--- a/polygerrit-ui/app/elements/shared/gr-icons/gr-icons.ts
+++ b/polygerrit-ui/app/elements/shared/gr-icons/gr-icons.ts
@@ -160,6 +160,8 @@
<g id="feedback"><path d="M0 0h24v24H0z" fill="none"/><path d="M20 2H4c-1.1 0-1.99.9-1.99 2L2 22l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-7 12h-2v-2h2v2zm0-4h-2V6h2v4z"/></g>
<!-- This SVG is a copy from material.io https://fonts.google.com/icons?selected=Material+Icons&icon.query=description -->
<g id="description"><path xmlns="http://www.w3.org/2000/svg" d="M0 0h24v24H0V0z" fill="none"/><path xmlns="http://www.w3.org/2000/svg" d="M8 16h8v2H8zm0-4h8v2H8zm6-10H6c-1.1 0-2 .9-2 2v16c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6zm4 18H6V4h7v5h5v11z"/></g>
+ <!-- This SVG is a copy from material.io https://fonts.google.com/icons?selected=Material+Icons&icon.query=settings_backup_restore and 0.65 scale and 4 translate https://fonts.google.com/icons?selected=Material+Icons&icon.query=done-->
+ <g id="overridden"><path xmlns="http://www.w3.org/2000/svg" d="M0 0h24v24H0V0z" fill="none"/><path xmlns="http://www.w3.org/2000/svg" d="M12 15 zM2 4v6h6V8H5.09C6.47 5.61 9.04 4 12 4c4.42 0 8 3.58 8 8s-3.58 8-8 8-8-3.58-8-8H2c0 5.52 4.48 10 10.01 10C17.53 22 22 17.52 22 12S17.53 2 12.01 2C8.73 2 5.83 3.58 4 6.01V4H2z"/><path xmlns="http://www.w3.org/2000/svg" d="M9.85 14.53 7.12 11.8l-.91.91L9.85 16.35 17.65 8.55l-.91-.91L9.85 14.53z"/></g>
</defs>
</svg>
</iron-iconset-svg>`;
diff --git a/polygerrit-ui/app/utils/label-util.ts b/polygerrit-ui/app/utils/label-util.ts
index 918d2ab..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];
}
}
@@ -219,10 +227,31 @@
case SubmitRequirementStatus.UNSATISFIED:
return 'close';
case SubmitRequirementStatus.OVERRIDDEN:
- return 'warning';
+ return 'overridden';
case SubmitRequirementStatus.NOT_APPLICABLE:
return 'info';
default:
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);
+}