Apply restricted voting range
If a specific PatchSet imposes a voting range on ChatGPT Gerrit user
that is more restrictive than the default bounds set by `votingMinScore`
and `votingMaxScore`, this narrower range takes precedence.
Jira-Id: IT-103
Change-Id: Iba796b79d39cc88a1e4567c685b16172fe7073d4
Signed-off-by: Patrizio <patrizio.gelosi@amarulasolutions.com>
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/PatchSetReviewer.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/PatchSetReviewer.java
index e2d7755..63027b0 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/PatchSetReviewer.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/PatchSetReviewer.java
@@ -5,8 +5,10 @@
import com.googlesource.gerrit.plugins.chatgpt.client.*;
import com.googlesource.gerrit.plugins.chatgpt.client.chatgpt.ChatGptClient;
import com.googlesource.gerrit.plugins.chatgpt.client.gerrit.GerritClient;
+import com.googlesource.gerrit.plugins.chatgpt.client.gerrit.GerritClientDetail;
import com.googlesource.gerrit.plugins.chatgpt.client.model.chatGpt.ChatGptReplyItem;
import com.googlesource.gerrit.plugins.chatgpt.client.model.chatGpt.ChatGptResponseContent;
+import com.googlesource.gerrit.plugins.chatgpt.client.model.gerrit.GerritPatchSetDetail;
import com.googlesource.gerrit.plugins.chatgpt.client.model.gerrit.GerritCodeRange;
import com.googlesource.gerrit.plugins.chatgpt.client.model.gerrit.GerritComment;
import com.googlesource.gerrit.plugins.chatgpt.client.model.ReviewBatch;
@@ -45,9 +47,7 @@
log.info("No file to review has been found in the PatchSet");
return;
}
- config.configureDynamically(Configuration.KEY_GPT_REQUEST_USER_PROMPT,
- gerritClient.getUserRequests(fullChangeId));
- config.configureDynamically(Configuration.KEY_COMMENT_PROPERTIES_SIZE, commentProperties.size());
+ updateDynamicConfiguration(config, fullChangeId);
String reviewReply = getReviewReply(config, fullChangeId, patchSet);
log.debug("ChatGPT response: {}", reviewReply);
@@ -57,6 +57,28 @@
gerritClient.setReview(fullChangeId, reviewBatches, reviewJson.getScore());
}
+ private void updateDynamicConfiguration(Configuration config, String fullChangeId) {
+ config.configureDynamically(Configuration.KEY_GPT_REQUEST_USER_PROMPT,
+ gerritClient.getUserRequests(fullChangeId));
+ config.configureDynamically(Configuration.KEY_COMMENT_PROPERTIES_SIZE, commentProperties.size());
+ if (config.isVotingEnabled() && !isCommentEvent) {
+ GerritClientDetail gerritClientDetail = new GerritClientDetail(config,
+ gerritClient.getGptAccountId(fullChangeId));
+ GerritPatchSetDetail.PermittedVotingRange permittedVotingRange = gerritClientDetail.getPermittedVotingRange(
+ fullChangeId);
+ if (permittedVotingRange != null) {
+ if (permittedVotingRange.getMin() > config.getVotingMinScore()) {
+ log.debug("Minimum ChatGPT voting score set to {}", permittedVotingRange.getMin());
+ config.configureDynamically(Configuration.KEY_VOTING_MIN_SCORE, permittedVotingRange.getMin());
+ }
+ if (permittedVotingRange.getMax() < config.getVotingMaxScore()) {
+ log.debug("Maximum ChatGPT voting score set to {}", permittedVotingRange.getMax());
+ config.configureDynamically(Configuration.KEY_VOTING_MAX_SCORE, permittedVotingRange.getMax());
+ }
+ }
+ }
+ }
+
private void addReviewBatch(Integer batchID, String batch) {
ReviewBatch batchMap = new ReviewBatch();
batchMap.setContent(batch);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/UriResourceLocator.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/UriResourceLocator.java
index aff6657..4273038 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/UriResourceLocator.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/UriResourceLocator.java
@@ -47,6 +47,10 @@
return gerritSetChangesUri(fullChangeId, "/revisions/current/review");
}
+ public static String gerritGetPatchSetDetailUri(String fullChangeId) {
+ return gerritSetChangesUri(fullChangeId, "/detail");
+ }
+
public static String chatCompletionsUri() {
return "/v1/chat/completions";
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/gerrit/GerritClient.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/gerrit/GerritClient.java
index ccbcdaa..b710afc 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/gerrit/GerritClient.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/gerrit/GerritClient.java
@@ -67,6 +67,11 @@
return gerritClientPatchSet.getFileDiffsProcessed();
}
+ public Integer getGptAccountId(String fullChangeId) {
+ updateGerritClient(GerritClientType.COMMENTS, fullChangeId);
+ return gerritClientComments.getGptAccountId();
+ }
+
public List<GerritComment> getCommentProperties(String fullChangeId) {
updateGerritClient(GerritClientType.COMMENTS, fullChangeId);
return gerritClientComments.getCommentProperties();
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/gerrit/GerritClientComments.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/gerrit/GerritClientComments.java
index da35b89..772d598 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/gerrit/GerritClientComments.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/gerrit/GerritClientComments.java
@@ -29,7 +29,8 @@
private static final Integer MAX_SECS_GAP_BETWEEN_EVENT_AND_COMMENT = 2;
private final Gson gson = new Gson();
- private final int gptAccountId;
+ @Getter
+ private final Integer gptAccountId;
private final HashMap<String, GerritComment> commentMap;
private long commentsStartTimestamp;
private String authorUsername;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/gerrit/GerritClientDetail.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/gerrit/GerritClientDetail.java
new file mode 100644
index 0000000..55dbeae
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/gerrit/GerritClientDetail.java
@@ -0,0 +1,54 @@
+package com.googlesource.gerrit.plugins.chatgpt.client.gerrit;
+
+import com.google.gson.Gson;
+import com.googlesource.gerrit.plugins.chatgpt.client.UriResourceLocator;
+import com.googlesource.gerrit.plugins.chatgpt.client.model.gerrit.GerritPatchSetDetail;
+import com.googlesource.gerrit.plugins.chatgpt.config.Configuration;
+import lombok.extern.slf4j.Slf4j;
+
+import java.net.URI;
+import java.util.List;
+
+@Slf4j
+public class GerritClientDetail extends GerritClientAccount {
+
+ private final Gson gson = new Gson();
+ private final Integer gptAccountId;
+
+ public GerritClientDetail(Configuration config, Integer gptAccountId) {
+ super(config);
+ this.gptAccountId = gptAccountId;
+ }
+
+
+ public GerritPatchSetDetail.PermittedVotingRange getPermittedVotingRange(String fullChangeId) {
+ GerritPatchSetDetail gerritPatchSetDetail;
+ try {
+ gerritPatchSetDetail = getReviewDetail(fullChangeId);
+ }
+ catch (Exception e) {
+ log.debug("Error retrieving PatchSet details", e);
+ return null;
+ }
+ List<GerritPatchSetDetail.Permission> permissions = gerritPatchSetDetail.getLabels().getCodeReview().getAll();
+ if (permissions == null) {
+ log.debug("No limitations on the ChatGPT voting range were detected");
+ return null;
+ }
+ for (GerritPatchSetDetail.Permission permission : permissions) {
+ if (permission.getAccountId() == gptAccountId) {
+ log.debug("PatchSet voting range detected for ChatGPT user: {}", permission.getPermittedVotingRange());
+ return permission.getPermittedVotingRange();
+ }
+ }
+ return null;
+ }
+
+ private GerritPatchSetDetail getReviewDetail(String fullChangeId) throws Exception {
+ URI uri = URI.create(config.getGerritAuthBaseUrl()
+ + UriResourceLocator.gerritGetPatchSetDetailUri(fullChangeId));
+ String responseBody = forwardGetRequest(uri);
+ return gson.fromJson(responseBody, GerritPatchSetDetail.class);
+ }
+
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/model/gerrit/GerritPatchSetDetail.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/model/gerrit/GerritPatchSetDetail.java
new file mode 100644
index 0000000..2570f4c
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/model/gerrit/GerritPatchSetDetail.java
@@ -0,0 +1,39 @@
+package com.googlesource.gerrit.plugins.chatgpt.client.model.gerrit;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class GerritPatchSetDetail {
+ private Labels labels;
+
+ @Data
+ public static class Labels {
+ @SerializedName("Code-Review")
+ private CodeReview codeReview;
+ }
+
+ @Data
+ public static class CodeReview {
+ private List<Permission> all;
+ }
+
+ @Data
+ public static class Permission {
+ private Integer value;
+ private String date;
+ @SerializedName("permitted_voting_range")
+ private PermittedVotingRange permittedVotingRange;
+ @SerializedName("_account_id")
+ private int accountId;
+ }
+
+ @Data
+ public static class PermittedVotingRange {
+ private int min;
+ private int max;
+ }
+
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/config/Configuration.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/config/Configuration.java
index f953df9..92c4e9e 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/config/Configuration.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/config/Configuration.java
@@ -77,6 +77,8 @@
public static final String KEY_GPT_SYSTEM_PROMPT = "gptSystemPrompt";
public static final String KEY_GPT_REQUEST_USER_PROMPT = "gptRequestUserPrompt";
public static final String KEY_COMMENT_PROPERTIES_SIZE = "commentPropertiesSize";
+ public static final String KEY_VOTING_MIN_SCORE = "votingMinScore";
+ public static final String KEY_VOTING_MAX_SCORE = "votingMaxScore";
private static final String KEY_GPT_TOKEN = "gptToken";
private static final String KEY_GERRIT_AUTH_BASE_URL = "gerritAuthBaseUrl";
private static final String KEY_GERRIT_USERNAME = "gerritUserName";
@@ -101,8 +103,6 @@
private static final String KEY_MAX_REVIEW_FILE_SIZE = "maxReviewFileSize";
private static final String KEY_ENABLED_FILE_EXTENSIONS = "enabledFileExtensions";
private static final String KEY_ENABLED_VOTING = "enabledVoting";
- private static final String KEY_VOTING_MIN_SCORE = "votingMinScore";
- private static final String KEY_VOTING_MAX_SCORE = "votingMaxScore";
// Prompt constants loaded from JSON file
public static String DEFAULT_GPT_SYSTEM_PROMPT;
diff --git a/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewTest.java b/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewTest.java
index a0feb6a..0f40ec4 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewTest.java
@@ -168,6 +168,13 @@
.withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString())
.withBody("{\"revisions\":{\"aa5be5ebb80846475ec4dfe43e0799eb73c6415a\":{}}}")));
+ // Mock the behavior of the gerritGetPatchSetDetailUri request
+ WireMock.stubFor(WireMock.get(gerritGetPatchSetDetailUri(fullChangeId))
+ .willReturn(WireMock.aResponse()
+ .withStatus(HTTP_OK)
+ .withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString())
+ .withBodyFile("gerritPatchSetDetail.json")));
+
// Mock the behavior of the gerritPatchSetFiles request
WireMock.stubFor(WireMock.get(gerritPatchSetFilesUri(fullChangeId))
.willReturn(WireMock.aResponse()
diff --git a/src/test/resources/__files/gerritPatchSetDetail.json b/src/test/resources/__files/gerritPatchSetDetail.json
new file mode 100644
index 0000000..8c4ca4d
--- /dev/null
+++ b/src/test/resources/__files/gerritPatchSetDetail.json
@@ -0,0 +1,39 @@
+{
+ "labels": {
+ "Code-Review": {
+ "all": [
+ {
+ "value": 1,
+ "date": "2024-02-06 07:38:58.000000000",
+ "permitted_voting_range": {
+ "min": -1,
+ "max": 1
+ },
+ "_account_id": 1000001,
+ "name": "ChatGPT",
+ "display_name": "ChatGPT",
+ "username": "gpt"
+ }
+ ],
+ "values": {
+ "-2": "This shall not be submitted",
+ "-1": "I would prefer this is not submitted as is",
+ " 0": "No score",
+ "+1": "Looks good to me, but someone else must approve",
+ "+2": "Looks good to me, approved"
+ },
+ "description": "",
+ "value": 1,
+ "default_value": 0
+ }
+ },
+ "permitted_labels": {
+ "Code-Review": [
+ "-2",
+ "-1",
+ " 0",
+ "+1",
+ "+2"
+ ]
+ }
+}
\ No newline at end of file