Expose the submit requirements in the Change API

Add requirements in the Change API, allowing PolyGerrit and API
consumers to use this information, and hopefuly improve communication
with the final users (change owner and contributors).
The API is thought to be both human and programs friendly, by exposing
a human readable description, a "type" identifying accurately the
requirement, and additional values in a key-value fashion.

For instance, the PolyGerrit UI might use these informations to display
each requirement with markup depending on its type, and fallback on the
fallbackText if the type is unknown (hence the name).

Change-Id: Ifb9e15a3c08ebaf42d8eb1469257c6e41cc22882
This commit is contained in:
Maxime Guerreiro
2018-03-22 15:29:10 +01:00
parent 9d72988a56
commit d32aedb347
5 changed files with 182 additions and 0 deletions

View File

@@ -5642,6 +5642,9 @@ AccountInfo] entity.
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.
|`requirements` |optional|
List of the link:rest-api-changes.html#requirement[requirements] to be met before this change
can be submitted.
|`labels` |optional|
The labels of the change as a map that maps the label names to
link:#label-info[LabelInfo] entries. +
@@ -6591,6 +6594,32 @@ describing the related changes. Sorted by git commit order, newest to
oldest. Empty if there are no related changes.
|===========================
[[requirement]]
=== Requirement
The `Requirement` entity contains information about a requirement relative to a change.
type:: Alphanumerical (plus hyphens or underscores) string to identify what the requirement is and
why it was triggered. Can be seen as a class: requirements sharing the same type were created for a
similar reason, and the data structure will follow one set of rules.
data:: (Optional) Additional key-value data linked to this requirement. This is used in templates to
render rich status messages.
[options="header",cols="1,^1,5"]
|===========================
|Field Name | |Description
|`status` | | Status of the requirement. Can be either `OK`, `NOT_READY` or `RULE_ERROR`.
|`fallbackText` | | A human readable reason
|`type` | |
Alphanumerical (plus hyphens or underscores) string to identify what the requirement is and why it
was triggered. Can be seen as a class: requirements sharing the same type were created for a similar
reason, and the data structure will follow one set of rules.
|`data` |optional|
Holds custom key-value strings, used in templates to render richer status messages
|===========================
[[restore-input]]
=== RestoreInput
The `RestoreInput` entity contains information for restoring a change.

View File

@@ -70,4 +70,5 @@ public class ChangeInfo {
public List<ProblemInfo> problems;
public List<PluginDefinedInfo> plugins;
public Collection<TrackingIdInfo> trackingIds;
public Collection<SubmitRequirementInfo> requirements;
}

View File

@@ -0,0 +1,53 @@
// Copyright (C) 2018 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.Map;
import java.util.Objects;
public class SubmitRequirementInfo {
public final String status;
public final String fallbackText;
public final String type;
public final Map<String, String> data;
public SubmitRequirementInfo(
String status, String fallbackText, String type, Map<String, String> data) {
this.status = status;
this.fallbackText = fallbackText;
this.type = type;
this.data = data;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof SubmitRequirementInfo)) {
return false;
}
SubmitRequirementInfo that = (SubmitRequirementInfo) o;
return Objects.equals(status, that.status)
&& Objects.equals(fallbackText, that.fallbackText)
&& Objects.equals(type, that.type)
&& Objects.equals(data, that.data);
}
@Override
public int hashCode() {
return Objects.hash(status, fallbackText, type, data);
}
}

View File

@@ -63,6 +63,8 @@ import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.LabelTypes;
import com.google.gerrit.common.data.LabelValue;
import com.google.gerrit.common.data.SubmitRecord;
import com.google.gerrit.common.data.SubmitRecord.Status;
import com.google.gerrit.common.data.SubmitRequirement;
import com.google.gerrit.common.data.SubmitTypeRecord;
import com.google.gerrit.extensions.api.changes.FixInput;
import com.google.gerrit.extensions.client.ListChangesOption;
@@ -78,6 +80,7 @@ import com.google.gerrit.extensions.common.ProblemInfo;
import com.google.gerrit.extensions.common.PushCertificateInfo;
import com.google.gerrit.extensions.common.ReviewerUpdateInfo;
import com.google.gerrit.extensions.common.RevisionInfo;
import com.google.gerrit.extensions.common.SubmitRequirementInfo;
import com.google.gerrit.extensions.common.TrackingIdInfo;
import com.google.gerrit.extensions.common.VotingRangeInfo;
import com.google.gerrit.extensions.common.WebLinkInfo;
@@ -425,6 +428,23 @@ public class ChangeJson {
return out;
}
private static Collection<SubmitRequirementInfo> requirementsFor(ChangeData cd) {
Collection<SubmitRequirementInfo> reqInfos = new ArrayList<>();
for (SubmitRecord submitRecord : cd.submitRecords(SUBMIT_RULE_OPTIONS_STRICT)) {
if (submitRecord.requirements == null) {
continue;
}
for (SubmitRequirement requirement : submitRecord.requirements) {
reqInfos.add(requirementToInfo(requirement, submitRecord.status));
}
}
return reqInfos;
}
private static SubmitRequirementInfo requirementToInfo(SubmitRequirement req, Status status) {
return new SubmitRequirementInfo(status.name(), req.fallbackText(), req.type(), req.data());
}
private void ensureLoaded(Iterable<ChangeData> all) throws OrmException {
if (lazyLoad) {
ChangeData.ensureChangeLoaded(all);
@@ -603,6 +623,7 @@ public class ChangeJson {
}
out.labels = labelsFor(cd, has(LABELS), has(DETAILED_LABELS));
out.requirements = requirementsFor(cd);
if (out.labels != null && has(DETAILED_LABELS)) {
// If limited to specific patch sets but not the current patch set, don't

View File

@@ -0,0 +1,78 @@
// Copyright (C) 2018 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.api.change;
import static com.google.common.truth.Truth.assertThat;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.common.data.SubmitRecord;
import com.google.gerrit.common.data.SubmitRequirement;
import com.google.gerrit.extensions.annotations.Exports;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.SubmitRequirementInfo;
import com.google.gerrit.extensions.config.FactoryModule;
import com.google.gerrit.server.project.SubmitRuleOptions;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.rules.SubmitRule;
import com.google.inject.Module;
import java.util.ArrayList;
import java.util.Collection;
import org.junit.Test;
public class ChangeSubmitRequirementIT extends AbstractDaemonTest {
private static final SubmitRequirement req =
SubmitRequirement.builder()
.setType("custom_rule")
.setFallbackText("Fallback text")
.addCustomValue("key", "value")
.build();
private static final SubmitRequirementInfo reqInfo =
new SubmitRequirementInfo(
"NOT_READY", "Fallback text", "custom_rule", ImmutableMap.of("key", "value"));
@Override
public Module createModule() {
return new FactoryModule() {
@Override
public void configure() {
bind(SubmitRule.class)
.annotatedWith(Exports.named("CustomSubmitRule"))
.to(CustomSubmitRule.class);
}
};
}
@Test
public void checkSubmitRequirementIsPropagated() throws Exception {
PushOneCommit.Result r = createChange();
ChangeInfo result = gApi.changes().id(r.getChangeId()).get();
assertThat(result.requirements).containsExactly(reqInfo);
}
private static class CustomSubmitRule implements SubmitRule {
@Override
public Collection<SubmitRecord> evaluate(ChangeData changeData, SubmitRuleOptions options) {
SubmitRecord record = new SubmitRecord();
record.labels = new ArrayList<>();
record.status = SubmitRecord.Status.NOT_READY;
record.requirements = ImmutableList.of(req);
return ImmutableList.of(record);
}
}
}