Merge "Export submit records as a new field with ChangeInfo"
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index b609643..d83ef0e 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -6535,6 +6535,9 @@
Actions the caller might be able to perform on this revision. The
information is a map of view name to link:#action-info[ActionInfo]
entities.
+|`submit_records` ||
+List of the link:rest-api-changes.html#submit-record-info[SubmitRecordInfo]
+containing the submit records for the change at the latest patchset.
|`requirements` |optional|
List of the link:rest-api-changes.html#requirement[requirements] to be met before this change
can be submitted. This field is deprecated in favour of `submit_requirements`.
@@ -8176,6 +8179,37 @@
the failure of the rule predicate.
|===========================
+[[submit-record-info]]
+=== SubmitRecordInfo
+The `SubmitRecordInfo` entity describes results from a submit_rule.
+
+[options="header",cols="1,^1,5"]
+|===========================
+|Field Name ||Description
+|`rule_name`||
+The name of the submit rule that created this submit record. The submit rule is
+specified in the form of "$plugin~$rule" where `$plugin` is the plugin name
+and `$rule` is the name of the class that implemented the submit rule.
+|`status`||
+`OK`, the change can be submitted. +
+`NOT_READY`, additional labels are required before submit. +
+`CLOSED`, closed changes cannot be submitted. +
+`FORCED`, the change was submitted bypassing the submit rule. +
+`RULE_ERROR`, rule code failed with an error.
+|`labels`|optional|
+A list of labels, each containing the following fields. +
+ * `label`: the label name. +
+ * `status`: the label status: {`OK`, `REJECT`, `MAY`, `NEED`, `IMPOSSIBLE`}. +
+ * `appliedBy`: the link:rest-api-accounts.html#account-info[AccountInfo]
+ that applied the vote to the label.
+|`requirements`|optional|
+List of the link:rest-api-changes.html#requirement[requirements] to be met
+before this change can be submitted.
+|`error_message`|optional|
+When status is RULE_ERROR this message provides some text describing
+the failure of the rule predicate.
+|===========================
+
[[submit-requirement-expression-info]]
=== SubmitRequirementExpressionInfo
The `SubmitRequirementExpressionInfo` describes the result of evaluating a
diff --git a/java/com/google/gerrit/extensions/common/ChangeInfo.java b/java/com/google/gerrit/extensions/common/ChangeInfo.java
index 6afe8ac..2bb3dd7 100644
--- a/java/com/google/gerrit/extensions/common/ChangeInfo.java
+++ b/java/com/google/gerrit/extensions/common/ChangeInfo.java
@@ -112,6 +112,7 @@
public List<PluginDefinedInfo> plugins;
public Collection<TrackingIdInfo> trackingIds;
public Collection<LegacySubmitRequirementInfo> requirements;
+ public Collection<SubmitRecordInfo> submitRecords;
public Collection<SubmitRequirementResultInfo> submitRequirements;
public ChangeInfo() {}
diff --git a/java/com/google/gerrit/extensions/common/SubmitRecordInfo.java b/java/com/google/gerrit/extensions/common/SubmitRecordInfo.java
new file mode 100644
index 0000000..09c9841
--- /dev/null
+++ b/java/com/google/gerrit/extensions/common/SubmitRecordInfo.java
@@ -0,0 +1,48 @@
+// 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.extensions.common;
+
+import java.util.List;
+
+/** API response containing a {@link com.google.gerrit.entities.SubmitRecord} entity. */
+public class SubmitRecordInfo {
+ public enum Status {
+ OK,
+ NOT_READY,
+ CLOSED,
+ FORCED,
+ RULE_ERROR
+ }
+
+ public static class Label {
+ public enum Status {
+ OK,
+ REJECT,
+ NEED,
+ MAY,
+ IMPOSSIBLE
+ }
+
+ public String label;
+ public Status status;
+ public AccountInfo appliedBy;
+ }
+
+ public String ruleName;
+ public Status status;
+ public List<Label> labels;
+ public List<LegacySubmitRequirementInfo> requirements;
+ public String errorMessage;
+}
diff --git a/java/com/google/gerrit/server/change/ChangeJson.java b/java/com/google/gerrit/server/change/ChangeJson.java
index 5efcf59..db25dc7 100644
--- a/java/com/google/gerrit/server/change/ChangeJson.java
+++ b/java/com/google/gerrit/server/change/ChangeJson.java
@@ -79,6 +79,7 @@
import com.google.gerrit.extensions.common.ProblemInfo;
import com.google.gerrit.extensions.common.ReviewerUpdateInfo;
import com.google.gerrit.extensions.common.RevisionInfo;
+import com.google.gerrit.extensions.common.SubmitRecordInfo;
import com.google.gerrit.extensions.common.SubmitRequirementExpressionInfo;
import com.google.gerrit.extensions.common.SubmitRequirementResultInfo;
import com.google.gerrit.extensions.common.TrackingIdInfo;
@@ -369,6 +370,14 @@
return reqInfos;
}
+ private Collection<SubmitRecordInfo> submitRecordsFor(ChangeData cd) {
+ List<SubmitRecordInfo> submitRecordInfos = new ArrayList<>();
+ for (SubmitRecord record : cd.submitRecords(SUBMIT_RULE_OPTIONS_STRICT)) {
+ submitRecordInfos.add(submitRecordToInfo(record));
+ }
+ return submitRecordInfos;
+ }
+
private static Collection<SubmitRequirementResultInfo> submitRequirementsFor(ChangeData cd) {
Collection<SubmitRequirementResultInfo> reqInfos = new ArrayList<>();
Map<SubmitRequirement, SubmitRequirementResult> requirements = cd.submitRequirements();
@@ -383,6 +392,34 @@
return new LegacySubmitRequirementInfo(status.name(), req.fallbackText(), req.type());
}
+ private SubmitRecordInfo submitRecordToInfo(SubmitRecord record) {
+ SubmitRecordInfo info = new SubmitRecordInfo();
+ if (record.status != null) {
+ info.status = SubmitRecordInfo.Status.valueOf(record.status.name());
+ }
+ info.ruleName = record.ruleName;
+ info.errorMessage = record.errorMessage;
+ if (record.labels != null) {
+ info.labels = new ArrayList<>();
+ for (SubmitRecord.Label label : record.labels) {
+ SubmitRecordInfo.Label labelInfo = new SubmitRecordInfo.Label();
+ labelInfo.label = label.label;
+ if (label.status != null) {
+ labelInfo.status = SubmitRecordInfo.Label.Status.valueOf(label.status.name());
+ }
+ labelInfo.appliedBy = accountLoader.get(label.appliedBy);
+ info.labels.add(labelInfo);
+ }
+ }
+ if (record.requirements != null) {
+ info.requirements = new ArrayList<>();
+ for (LegacySubmitRequirement requirement : record.requirements) {
+ info.requirements.add(requirementToInfo(requirement, record.status));
+ }
+ }
+ return info;
+ }
+
private static SubmitRequirementResultInfo submitRequirementToInfo(
SubmitRequirement req, SubmitRequirementResult result) {
SubmitRequirementResultInfo info = new SubmitRequirementResultInfo();
@@ -662,6 +699,7 @@
out.labels = labelsJson.labelsFor(accountLoader, cd, has(LABELS), has(DETAILED_LABELS));
out.requirements = requirementsFor(cd);
+ out.submitRecords = submitRecordsFor(cd);
if (has(SUBMIT_REQUIREMENTS)) {
out.submitRequirements = submitRequirementsFor(cd);
}
diff --git a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
index 6166f36..59011f6 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
@@ -99,10 +99,12 @@
import com.google.gerrit.entities.LabelFunction;
import com.google.gerrit.entities.LabelId;
import com.google.gerrit.entities.LabelType;
+import com.google.gerrit.entities.LegacySubmitRequirement;
import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.entities.Permission;
import com.google.gerrit.entities.Project;
import com.google.gerrit.entities.RefNames;
+import com.google.gerrit.entities.SubmitRecord;
import com.google.gerrit.entities.SubmitRequirement;
import com.google.gerrit.entities.SubmitRequirementExpression;
import com.google.gerrit.entities.SubmitRequirementExpressionResult;
@@ -148,7 +150,9 @@
import com.google.gerrit.extensions.common.CommitInfo;
import com.google.gerrit.extensions.common.GitPerson;
import com.google.gerrit.extensions.common.LabelInfo;
+import com.google.gerrit.extensions.common.LegacySubmitRequirementInfo;
import com.google.gerrit.extensions.common.RevisionInfo;
+import com.google.gerrit.extensions.common.SubmitRecordInfo;
import com.google.gerrit.extensions.common.SubmitRequirementResultInfo;
import com.google.gerrit.extensions.common.SubmitRequirementResultInfo.Status;
import com.google.gerrit.extensions.common.TrackingIdInfo;
@@ -186,6 +190,7 @@
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.ChangeQueryBuilder.ChangeOperatorFactory;
import com.google.gerrit.server.restapi.change.PostReview;
+import com.google.gerrit.server.rules.SubmitRule;
import com.google.gerrit.server.update.BatchUpdate;
import com.google.gerrit.server.update.BatchUpdateOp;
import com.google.gerrit.server.update.ChangeContext;
@@ -4033,6 +4038,51 @@
}
@Test
+ public void submitRecords() throws Exception {
+ PushOneCommit.Result r = createChange();
+ TestSubmitRule testSubmitRule = new TestSubmitRule();
+ try (Registration registration = extensionRegistry.newRegistration().add(testSubmitRule)) {
+ String changeId = r.getChangeId();
+
+ ChangeInfo change = gApi.changes().id(changeId).get();
+ assertThat(change.submitRecords).hasSize(2);
+ // Check the default submit record for the code-review label
+ SubmitRecordInfo codeReviewRecord = Iterables.get(change.submitRecords, 0);
+ assertThat(codeReviewRecord.ruleName).isEqualTo("gerrit~DefaultSubmitRule");
+ assertThat(codeReviewRecord.status).isEqualTo(SubmitRecordInfo.Status.NOT_READY);
+ assertThat(codeReviewRecord.labels).hasSize(1);
+ SubmitRecordInfo.Label label = Iterables.getOnlyElement(codeReviewRecord.labels);
+ assertThat(label.label).isEqualTo("Code-Review");
+ assertThat(label.status).isEqualTo(SubmitRecordInfo.Label.Status.NEED);
+ assertThat(label.appliedBy).isNull();
+ // Check the custom test record created by the TestSubmitRule
+ SubmitRecordInfo testRecord = Iterables.get(change.submitRecords, 1);
+ assertThat(testRecord.ruleName).isEqualTo("gerrit~TestSubmitRule");
+ assertThat(testRecord.status).isEqualTo(SubmitRecordInfo.Status.OK);
+ assertThat(testRecord.requirements)
+ .containsExactly(new LegacySubmitRequirementInfo("OK", "fallback text", "type"));
+ assertThat(testRecord.labels).hasSize(1);
+ SubmitRecordInfo.Label testLabel = Iterables.getOnlyElement(testRecord.labels);
+ assertThat(testLabel.label).isEqualTo("label");
+ assertThat(testLabel.status).isEqualTo(SubmitRecordInfo.Label.Status.OK);
+ assertThat(testLabel.appliedBy).isNull();
+
+ voteLabel(changeId, "code-review", 2);
+ // Code review record is satisfied after voting +2
+ change = gApi.changes().id(changeId).get();
+ assertThat(change.submitRecords).hasSize(2);
+ codeReviewRecord = Iterables.get(change.submitRecords, 0);
+ assertThat(codeReviewRecord.ruleName).isEqualTo("gerrit~DefaultSubmitRule");
+ assertThat(codeReviewRecord.status).isEqualTo(SubmitRecordInfo.Status.OK);
+ assertThat(codeReviewRecord.labels).hasSize(1);
+ label = Iterables.getOnlyElement(codeReviewRecord.labels);
+ assertThat(label.label).isEqualTo("Code-Review");
+ assertThat(label.status).isEqualTo(SubmitRecordInfo.Label.Status.OK);
+ assertThat(label.appliedBy._accountId).isEqualTo(admin.id().get());
+ }
+ }
+
+ @Test
public void submitRequirement_withLabelEqualsMax() throws Exception {
configSubmitRequirement(
project,
@@ -5183,4 +5233,25 @@
.update();
return project;
}
+
+ /** Returns a hard-coded submit record containing all fields. */
+ private static class TestSubmitRule implements SubmitRule {
+ @Override
+ public Optional<SubmitRecord> evaluate(ChangeData changeData) {
+ SubmitRecord record = new SubmitRecord();
+ record.ruleName = "testSubmitRule";
+ record.status = SubmitRecord.Status.OK;
+ SubmitRecord.Label label = new SubmitRecord.Label();
+ label.label = "label";
+ label.status = SubmitRecord.Label.Status.OK;
+ record.labels = Arrays.asList(label);
+ record.requirements =
+ Arrays.asList(
+ LegacySubmitRequirement.builder()
+ .setType("type")
+ .setFallbackText("fallback text")
+ .build());
+ return Optional.of(record);
+ }
+ }
}