Enable dynamic configuration via command
Plugin configuration can now be dynamically modified through messages
addressed to the ChatGPT user for testing/debugging purposes. This
functionality is activated when the `enableMessageDebugging`
configuration setting is set to true.
The command syntax is as follows:
- `/configure` shows the changed settings and their dynamically changed
values in a reply message.
- `/configure --<CONFIG_KEY_1>=<CONFIG_VALUE_1> [...
--<CONFIG_KEY_N>=<CONFIG_VALUE_N>]` allows assigning new value(s) to one
or more configuration keys.
Change-Id: Ia74f83a2b2cb3e8c36745e7d320fb65d4ff1eee4
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 f94323b..b752f28 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/PatchSetReviewer.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/PatchSetReviewer.java
@@ -71,7 +71,7 @@
}
ChangeSetDataHandler.update(config, change, gerritClient, changeSetData, localizer);
- if (changeSetData.getReviewSystemMessage() == null) {
+ if (changeSetData.shouldRequestChatGptReview()) {
ChatGptResponseContent reviewReply = getReviewReply(change, patchSet);
log.debug("ChatGPT response: {}", reviewReply);
@@ -121,11 +121,10 @@
if (changeSetData.getReplyFilterEnabled() && isHidden) {
continue;
}
- if (changeSetData.getDebugMode()) {
+ if (changeSetData.getDebugReviewMode()) {
reply += debugCodeBlocksReview.getDebugCodeBlock(replyItem, isHidden);
}
- ReviewBatch batchMap = new ReviewBatch();
- batchMap.setContent(reply);
+ ReviewBatch batchMap = new ReviewBatch(reply);
if (change.getIsCommentEvent() && replyItem.getId() != null) {
setCommentBatchMap(batchMap, replyItem.getId());
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/config/ConfigCreator.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/config/ConfigCreator.java
index c3169ef..9cebdde 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/config/ConfigCreator.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/config/ConfigCreator.java
@@ -1,6 +1,7 @@
package com.googlesource.gerrit.plugins.chatgpt.config;
import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.annotations.PluginName;
import com.google.gerrit.server.account.AccountCache;
@@ -12,9 +13,17 @@
import com.google.gerrit.server.util.OneOffRequestContext;
import com.google.inject.Inject;
import com.google.inject.Singleton;
+import com.googlesource.gerrit.plugins.chatgpt.data.PluginDataHandlerBaseProvider;
+import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
+import org.eclipse.jgit.lib.Config;
+import java.util.HashMap;
+import java.util.Map;
import java.util.Optional;
+import java.util.Set;
+
+import static com.googlesource.gerrit.plugins.chatgpt.config.DynamicConfiguration.KEY_DYNAMIC_CONFIG;
@Singleton
@Slf4j
@@ -26,17 +35,26 @@
private final OneOffRequestContext context;
private final GerritApi gerritApi;
+ private final PluginDataHandlerBaseProvider pluginDataHandlerBaseProvider;
@Inject
- ConfigCreator(@PluginName String pluginName, AccountCache accountCache, PluginConfigFactory configFactory, OneOffRequestContext context, GerritApi gerritApi) {
+ ConfigCreator(
+ @PluginName String pluginName,
+ AccountCache accountCache,
+ PluginConfigFactory configFactory,
+ OneOffRequestContext context,
+ GerritApi gerritApi,
+ PluginDataHandlerBaseProvider pluginDataHandlerBaseProvider
+ ) {
this.pluginName = pluginName;
this.accountCache = accountCache;
this.configFactory = configFactory;
this.context = context;
this.gerritApi = gerritApi;
+ this.pluginDataHandlerBaseProvider = pluginDataHandlerBaseProvider;
}
- public Configuration createConfig(Project.NameKey projectName) throws NoSuchProjectException {
+ public Configuration createConfig(Project.NameKey projectName, Change.Key changeKey) throws NoSuchProjectException {
PluginConfig globalConfig = configFactory.getFromGerritConfig(pluginName);
log.debug(
"These configuration items have been set in the global configuration: {}",
@@ -45,6 +63,14 @@
log.debug(
"These configuration items have been set in the project configuration: {}",
projectConfig.getNames());
+ // `PluginDataHandlerProvider` cannot be injected because `GerritChange` is not initialized at this stage:
+ // instead of using `PluginDataHandlerProvider.getChangeScope`, `PluginDataHandlerBaseProvider.get` is employed
+ Map<String, String> dynamicConfig = pluginDataHandlerBaseProvider.get(changeKey.toString())
+ .getJsonValue(KEY_DYNAMIC_CONFIG, String.class);
+ if (dynamicConfig != null && !dynamicConfig.isEmpty()) {
+ log.info("DynamicConfig found for change '{}': {}", changeKey, dynamicConfig);
+ projectConfig = updateDynamicConfig(projectConfig, pluginName, dynamicConfig);
+ }
Optional<AccountState> gptAccount = getAccount(globalConfig);
String email = gptAccount.map(a -> a.account().preferredEmail()).orElse("");
Account.Id accountId =
@@ -63,4 +89,36 @@
String gptUser = globalConfig.getString(Configuration.KEY_GERRIT_USERNAME);
return accountCache.getByUsername(gptUser);
}
+
+ private PluginConfig updateDynamicConfig(
+ PluginConfig projectConfig,
+ String pluginName,
+ Map<String, String> dynamicConfig
+ ) {
+ // Retrieve all current configuration values
+ Set<String> keys = projectConfig.getNames();
+ Map<String, String> currentConfigValues = new HashMap<>();
+ for (String key : keys) {
+ currentConfigValues.put(key, projectConfig.getString(key));
+ }
+ // Merge current config with new values from dynamicConfig
+ currentConfigValues.putAll(dynamicConfig);
+ PluginConfig.Update configUpdater = getProjectUpdate(pluginName, currentConfigValues);
+
+ return configUpdater.asPluginConfig();
+ }
+
+ private PluginConfig.@NonNull Update getProjectUpdate(String pluginName, Map<String, String> currentConfigValues) {
+ // Use PluginConfig.Update to apply merged configuration
+ PluginConfig.Update configUpdater = new PluginConfig.Update(
+ pluginName,
+ new Config(),
+ Optional.empty()
+ );
+ // Set all merged values
+ for (Map.Entry<String, String> entry : currentConfigValues.entrySet()) {
+ configUpdater.setString(entry.getKey(), entry.getValue());
+ }
+ return configUpdater;
+ }
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/config/DynamicConfiguration.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/config/DynamicConfiguration.java
new file mode 100644
index 0000000..c0ea2bf
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/config/DynamicConfiguration.java
@@ -0,0 +1,34 @@
+package com.googlesource.gerrit.plugins.chatgpt.config;
+
+import com.googlesource.gerrit.plugins.chatgpt.data.PluginDataHandler;
+import com.googlesource.gerrit.plugins.chatgpt.data.PluginDataHandlerProvider;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+
+@Slf4j
+public class DynamicConfiguration {
+ public static final String KEY_DYNAMIC_CONFIG = "dynamicConfig";
+
+ private final PluginDataHandler pluginDataHandler;
+ private final Map<String, String> dynamicConfig;
+
+ public DynamicConfiguration(PluginDataHandlerProvider pluginDataHandlerProvider) {
+ this.pluginDataHandler = pluginDataHandlerProvider.getChangeScope();
+ dynamicConfig = Optional.ofNullable(pluginDataHandler.getJsonValue(KEY_DYNAMIC_CONFIG, String.class))
+ .orElse(new HashMap<>());
+ }
+
+ public void setConfig(String key, String value) {
+ dynamicConfig.put(key, value);
+ }
+
+ public void updateConfiguration() {
+ if (dynamicConfig != null && !dynamicConfig.isEmpty()) {
+ log.info("Updating dynamic configuration with {}", dynamicConfig);
+ pluginDataHandler.setJsonValue(KEY_DYNAMIC_CONFIG, dynamicConfig);
+ }
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/data/ChangeSetDataHandler.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/data/ChangeSetDataHandler.java
index 878202f..f4dd638 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/data/ChangeSetDataHandler.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/data/ChangeSetDataHandler.java
@@ -31,6 +31,7 @@
changeSetData.setCommentPropertiesSize(gerritClientData.getCommentProperties().size());
changeSetData.setDirectives(new HashSet<>());
+ changeSetData.setReviewSystemMessage(null);
changeSetData.setGptRequestUserPrompt(chatGptUserPrompt.buildPrompt());
if (config.isVotingEnabled() && !change.getIsCommentEvent()) {
GerritPermittedVotingRange permittedVotingRange = gerritClient.getPermittedVotingRange(change);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/data/PluginDataHandler.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/data/PluginDataHandler.java
index c3f92dc..44c1817 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/data/PluginDataHandler.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/data/PluginDataHandler.java
@@ -1,13 +1,18 @@
package com.googlesource.gerrit.plugins.chatgpt.data;
+import com.google.gson.reflect.TypeToken;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.io.IOException;
+import java.lang.reflect.Type;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.util.Map;
import java.util.Properties;
+import static com.googlesource.gerrit.plugins.chatgpt.utils.GsonUtils.getGson;
+
@Singleton
public class PluginDataHandler {
private final Path configFile;
@@ -28,10 +33,21 @@
storeProperties();
}
+ public synchronized void setJsonValue(String key, Object value) {
+ setValue(key, getGson().toJson(value));
+ }
+
public String getValue(String key) {
return configProperties.getProperty(key);
}
+ public <T> Map<String, T> getJsonValue(String key, Class<T> clazz) {
+ String value = getValue(key);
+ if (value == null || value.isEmpty()) return null;
+ Type typeOfMap = TypeToken.getParameterized(Map.class, String.class, clazz).getType();
+ return getGson().fromJson(value, typeOfMap);
+ }
+
public synchronized void removeValue(String key) {
if (configProperties.containsKey(key)) {
configProperties.remove(key);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/data/PluginDataHandlerProvider.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/data/PluginDataHandlerProvider.java
index ca52d51..54ba288 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/data/PluginDataHandlerProvider.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/data/PluginDataHandlerProvider.java
@@ -10,6 +10,7 @@
@Singleton
public class PluginDataHandlerProvider extends PluginDataHandlerBaseProvider implements Provider<PluginDataHandler> {
private final String projectName;
+ private final String changeKey;
@Inject
public PluginDataHandlerProvider(
@@ -18,6 +19,7 @@
) {
super(defaultPluginDataPath);
projectName = change.getProjectName();
+ changeKey = change.getChangeKey().toString();
}
public PluginDataHandler getGlobalScope() {
@@ -27,4 +29,8 @@
public PluginDataHandler getProjectScope() {
return super.get(projectName);
}
+
+ public PluginDataHandler getChangeScope() {
+ return super.get(changeKey);
+ }
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/listener/EventHandlerTask.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/listener/EventHandlerTask.java
index b1877bf..872b308 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/listener/EventHandlerTask.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/listener/EventHandlerTask.java
@@ -90,7 +90,7 @@
boolean isCommentEvent = EVENT_COMMENT_MAP.get(eventType);
if (isCommentEvent) {
if (!gerritClient.retrieveLastComments(change)) {
- if (changeSetData.getForcedReview()) {
+ if (changeSetData.getForcedReview() || changeSetData.getForceDisplaySystemMessage()) {
isCommentEvent = false;
} else {
log.info("No comments found for review");
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/listener/GerritListener.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/listener/GerritListener.java
index a86dd4d..2e60049 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/listener/GerritListener.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/listener/GerritListener.java
@@ -1,6 +1,7 @@
package com.googlesource.gerrit.plugins.chatgpt.listener;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.Project;
import com.google.gerrit.server.config.GerritInstanceId;
import com.google.gerrit.server.events.PatchSetEvent;
@@ -47,9 +48,10 @@
log.info("Processing event: {}", event);
PatchSetEvent patchSetEvent = (PatchSetEvent) event;
Project.NameKey projectNameKey = patchSetEvent.getProjectNameKey();
+ Change.Key changeKey = patchSetEvent.getChangeKey();
try {
- Configuration config = configCreator.createConfig(projectNameKey);
+ Configuration config = configCreator.createConfig(projectNameKey, changeKey);
evenHandlerExecutor.execute(config, patchSetEvent);
} catch (NoSuchProjectException e) {
log.error("Project not found: {}", projectNameKey, e);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/api/gerrit/GerritClientComments.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/api/gerrit/GerritClientComments.java
index f9cc867..96f9854 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/api/gerrit/GerritClientComments.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/api/gerrit/GerritClientComments.java
@@ -7,6 +7,7 @@
import com.google.gerrit.server.util.ManualRequestContext;
import com.google.inject.Inject;
import com.googlesource.gerrit.plugins.chatgpt.config.Configuration;
+import com.googlesource.gerrit.plugins.chatgpt.data.PluginDataHandlerProvider;
import com.googlesource.gerrit.plugins.chatgpt.localization.Localizer;
import com.googlesource.gerrit.plugins.chatgpt.mode.common.client.messages.ClientMessage;
import com.googlesource.gerrit.plugins.chatgpt.mode.common.model.api.gerrit.GerritCodeRange;
@@ -32,6 +33,7 @@
private final ChangeSetData changeSetData;
private final HashMap<String, GerritComment> commentMap;
private final HashMap<String, GerritComment> patchSetCommentMap;
+ private final PluginDataHandlerProvider pluginDataHandlerProvider;
private final Localizer localizer;
private String authorUsername;
@@ -44,10 +46,12 @@
Configuration config,
AccountCache accountCache,
ChangeSetData changeSetData,
+ PluginDataHandlerProvider pluginDataHandlerProvider,
Localizer localizer
) {
super(config, accountCache);
this.changeSetData = changeSetData;
+ this.pluginDataHandlerProvider = pluginDataHandlerProvider;
this.localizer = localizer;
commentProperties = new ArrayList<>();
commentMap = new HashMap<>();
@@ -145,7 +149,7 @@
}
private void addLastComments(GerritChange change) {
- ClientMessage clientMessage = new ClientMessage(config, changeSetData, localizer);
+ ClientMessage clientMessage = new ClientMessage(config, changeSetData, pluginDataHandlerProvider, localizer);
try {
List<GerritComment> latestComments = retrieveComments(change);
if (latestComments == null) {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/api/gerrit/GerritClientReview.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/api/gerrit/GerritClientReview.java
index c3c539e..5e707dc 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/api/gerrit/GerritClientReview.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/api/gerrit/GerritClientReview.java
@@ -11,7 +11,10 @@
import com.google.gerrit.server.util.ManualRequestContext;
import com.google.inject.Inject;
import com.googlesource.gerrit.plugins.chatgpt.config.Configuration;
+import com.googlesource.gerrit.plugins.chatgpt.data.PluginDataHandler;
+import com.googlesource.gerrit.plugins.chatgpt.data.PluginDataHandlerProvider;
import com.googlesource.gerrit.plugins.chatgpt.localization.Localizer;
+import com.googlesource.gerrit.plugins.chatgpt.mode.common.client.messages.DebugCodeBlocksDynamicSettings;
import com.googlesource.gerrit.plugins.chatgpt.mode.common.model.data.ChangeSetData;
import com.googlesource.gerrit.plugins.chatgpt.mode.common.model.review.ReviewBatch;
import lombok.extern.slf4j.Slf4j;
@@ -22,17 +25,28 @@
import java.util.Map;
import java.util.Optional;
+import static com.googlesource.gerrit.plugins.chatgpt.config.DynamicConfiguration.KEY_DYNAMIC_CONFIG;
import static com.googlesource.gerrit.plugins.chatgpt.mode.common.client.prompt.MessageSanitizer.sanitizeChatGptMessage;
+import static com.googlesource.gerrit.plugins.chatgpt.utils.TextUtils.joinWithDoubleNewLine;
@Slf4j
public class GerritClientReview extends GerritClientAccount {
+ private final PluginDataHandler pluginDataHandler;
private final Localizer localizer;
+ private final DebugCodeBlocksDynamicSettings debugCodeBlocksDynamicSettings;
@VisibleForTesting
@Inject
- public GerritClientReview(Configuration config, AccountCache accountCache, Localizer localizer) {
+ public GerritClientReview(
+ Configuration config,
+ AccountCache accountCache,
+ PluginDataHandlerProvider pluginDataHandlerProvider,
+ Localizer localizer
+ ) {
super(config, accountCache);
+ this.pluginDataHandler = pluginDataHandlerProvider.getChangeScope();
this.localizer = localizer;
+ debugCodeBlocksDynamicSettings = new DebugCodeBlocksDynamicSettings(localizer);
}
public void setReview(
@@ -77,21 +91,33 @@
if (changeSetData.getReviewSystemMessage() != null) {
systemMessage = changeSetData.getReviewSystemMessage();
}
- else {
+ else if (!changeSetData.shouldHideChatGptReview()) {
comments = getReviewComments(reviewBatches);
if (reviewScore != null) {
reviewInput.label(LabelId.CODE_REVIEW, reviewScore);
}
}
- if (comments.isEmpty()) {
- reviewInput.message(localizer.getText("system.message.prefix") + ' ' + systemMessage);
- }
- else {
+ updateSystemMessage(reviewInput, comments.isEmpty(), systemMessage);
+ if (!comments.isEmpty()) {
reviewInput.comments = comments;
}
return reviewInput;
}
+ private void updateSystemMessage(ReviewInput reviewInput, boolean emptyComments, String systemMessage) {
+ List<String> messages = new ArrayList<>();
+ Map<String, String> dynamicConfig = pluginDataHandler.getJsonValue(KEY_DYNAMIC_CONFIG, String.class);
+ if (dynamicConfig != null && !dynamicConfig.isEmpty()) {
+ messages.add(debugCodeBlocksDynamicSettings.getDebugCodeBlock(dynamicConfig));
+ }
+ if (emptyComments) {
+ messages.add(localizer.getText("system.message.prefix") + ' ' + systemMessage);
+ }
+ if (!messages.isEmpty()) {
+ reviewInput.message(joinWithDoubleNewLine(messages));
+ }
+ }
+
private Map<String, List<CommentInput>> getReviewComments(List<ReviewBatch> reviewBatches) {
Map<String, List<CommentInput>> comments = new HashMap<>();
for (ReviewBatch reviewBatch : reviewBatches) {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/commands/ClientCommands.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/commands/ClientCommands.java
index 85b2d3e..f3ad30f 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/commands/ClientCommands.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/commands/ClientCommands.java
@@ -1,6 +1,8 @@
package com.googlesource.gerrit.plugins.chatgpt.mode.common.client.commands;
import com.googlesource.gerrit.plugins.chatgpt.config.Configuration;
+import com.googlesource.gerrit.plugins.chatgpt.config.DynamicConfiguration;
+import com.googlesource.gerrit.plugins.chatgpt.data.PluginDataHandlerProvider;
import com.googlesource.gerrit.plugins.chatgpt.localization.Localizer;
import com.googlesource.gerrit.plugins.chatgpt.mode.common.client.ClientBase;
import com.googlesource.gerrit.plugins.chatgpt.mode.common.client.prompt.Directives;
@@ -20,9 +22,10 @@
private enum COMMAND_SET {
REVIEW,
REVIEW_LAST,
- DIRECTIVE
+ DIRECTIVE,
+ CONFIGURE
}
- private enum OPTION_SET {
+ private enum REVIEW_OPTION_SET {
FILTER,
DEBUG
}
@@ -30,11 +33,12 @@
private static final Map<String, COMMAND_SET> COMMAND_MAP = Map.of(
"review", COMMAND_SET.REVIEW,
"review_last", COMMAND_SET.REVIEW_LAST,
- "directive", COMMAND_SET.DIRECTIVE
+ "directive", COMMAND_SET.DIRECTIVE,
+ "configure", COMMAND_SET.CONFIGURE
);
- private static final Map<String, OPTION_SET> OPTION_MAP = Map.of(
- "filter", OPTION_SET.FILTER,
- "debug", OPTION_SET.DEBUG
+ private static final Map<String, REVIEW_OPTION_SET> REVIEW_OPTION_MAP = Map.of(
+ "filter", REVIEW_OPTION_SET.FILTER,
+ "debug", REVIEW_OPTION_SET.DEBUG
);
private static final List<COMMAND_SET> REVIEW_COMMANDS = new ArrayList<>(List.of(
COMMAND_SET.REVIEW,
@@ -44,22 +48,30 @@
COMMAND_SET.DIRECTIVE
));
private static final Pattern COMMAND_PATTERN = Pattern.compile("/(" + String.join("|",
- COMMAND_MAP.keySet()) + ")\\b((?:\\s+--\\w+(?:=\\w+)?)+)?");
- private static final Pattern OPTIONS_PATTERN = Pattern.compile("--(\\w+)(?:=(\\w+))?");
+ COMMAND_MAP.keySet()) + ")\\b((?:\\s+--\\w+(?:=\\S+)?)+)?");
+ private static final Pattern OPTIONS_PATTERN = Pattern.compile("--(\\w+)(?:=(\\S+))?");
private final ChangeSetData changeSetData;
- @Getter
private final Directives directives;
private final Localizer localizer;
- @Getter
+ private DynamicConfiguration dynamicConfiguration;
private boolean containingHistoryCommand;
- public ClientCommands(Configuration config, ChangeSetData changeSetData, Localizer localizer) {
+ public ClientCommands(
+ Configuration config,
+ ChangeSetData changeSetData,
+ PluginDataHandlerProvider pluginDataHandlerProvider,
+ Localizer localizer
+ ) {
super(config);
this.localizer = localizer;
this.changeSetData = changeSetData;
directives = new Directives(changeSetData);
+ // The `dynamicConfiguration` instance is utilized only for parsing current client messages, not the history
+ if (pluginDataHandlerProvider != null) {
+ dynamicConfiguration = new DynamicConfiguration(pluginDataHandlerProvider);
+ }
containingHistoryCommand = false;
}
@@ -67,10 +79,9 @@
boolean commandFound = false;
Matcher reviewCommandMatcher = COMMAND_PATTERN.matcher(comment);
while (reviewCommandMatcher.find()) {
- parseCommand(comment, reviewCommandMatcher.group(1), isNotHistory);
- if (reviewCommandMatcher.group(2) != null) {
- parseOption(reviewCommandMatcher, isNotHistory);
- }
+ COMMAND_SET command = COMMAND_MAP.get(reviewCommandMatcher.group(1));
+ parseOptions(command, reviewCommandMatcher, isNotHistory);
+ parseCommand(command, comment, isNotHistory);
commandFound = true;
}
return commandFound;
@@ -88,52 +99,77 @@
return reviewCommandMatcher.replaceAll("");
}
- private void parseCommand(String comment, String commandString, boolean isNotHistory) {
- COMMAND_SET command = COMMAND_MAP.get(commandString);
- if (isNotHistory && REVIEW_COMMANDS.contains(command)) {
- changeSetData.setForcedReview(true);
- if (command == COMMAND_SET.REVIEW_LAST) {
- log.info("Forced review command applied to the last Patch Set");
- changeSetData.setForcedReviewLastPatchSet(true);
+ private void parseCommand(COMMAND_SET command, String comment, boolean isNotHistory) {
+ if (isNotHistory) {
+ if (REVIEW_COMMANDS.contains(command)) {
+ changeSetData.setForcedReview(true);
+ if (command == COMMAND_SET.REVIEW_LAST) {
+ log.info("Forced review command applied to the last Patch Set");
+ changeSetData.setForcedReviewLastPatchSet(true);
+ }
+ else {
+ log.info("Forced review command applied to the entire Change Set");
+ }
}
- else {
- log.info("Forced review command applied to the entire Change Set");
+ else if (command == COMMAND_SET.CONFIGURE) {
+ if (config.getEnableMessageDebugging()) {
+ changeSetData.setHideChatGptReview(true);
+ changeSetData.setForceDisplaySystemMessage(true);
+ dynamicConfiguration.updateConfiguration();
+ }
+ else {
+ changeSetData.setReviewSystemMessage(localizer.getText(
+ "message.configure.from.messages.disabled"
+ ));
+ log.debug("Unable to change configuration from messages: `enableMessageDebugging` config must" +
+ "be set to true");
+ }
}
}
if (HISTORY_COMMANDS.contains(command)) {
containingHistoryCommand = true;
- directives.addDirective(removeCommands(comment));
+ if (command == COMMAND_SET.DIRECTIVE) {
+ directives.addDirective(removeCommands(comment));
+ }
}
}
- private void parseOption(Matcher reviewCommandMatcher, boolean isNotHistory) {
- COMMAND_SET command = COMMAND_MAP.get(reviewCommandMatcher.group(1));
+ private void parseOptions(COMMAND_SET command, Matcher reviewCommandMatcher, boolean isNotHistory) {
+ // Command options need to be parsed only when processing the current message, not the message history
+ if (reviewCommandMatcher.group(2) == null || !isNotHistory) return;
Matcher reviewOptionsMatcher = OPTIONS_PATTERN.matcher(reviewCommandMatcher.group(2));
while (reviewOptionsMatcher.find()) {
- OPTION_SET option = OPTION_MAP.get(reviewOptionsMatcher.group(1));
- if (isNotHistory && REVIEW_COMMANDS.contains(command)) {
- switch (option) {
- case FILTER:
- boolean value = Boolean.parseBoolean(reviewOptionsMatcher.group(2));
- log.debug("Option 'replyFilterEnabled' set to {}", value);
- changeSetData.setReplyFilterEnabled(value);
- break;
- case DEBUG:
- if (config.getEnableMessageDebugging()) {
- log.debug("Response Mode set to Debug");
- changeSetData.setDebugMode(true);
- changeSetData.setReplyFilterEnabled(false);
- }
- else {
- changeSetData.setReviewSystemMessage(localizer.getText(
- "message.debugging.functionalities.disabled"
- ));
- log.debug("Unable to set Response Mode to Debug: `enableMessageDebugging` config must be " +
- "set to true");
- }
- break;
- }
+ parseSingleOption(command, reviewOptionsMatcher);
+ }
+ }
+
+ private void parseSingleOption(COMMAND_SET command, Matcher reviewOptionsMatcher) {
+ String optionKey = reviewOptionsMatcher.group(1);
+ String optionValue = reviewOptionsMatcher.group(2);
+ if (REVIEW_COMMANDS.contains(command)) {
+ switch (REVIEW_OPTION_MAP.get(optionKey)) {
+ case FILTER:
+ boolean value = Boolean.parseBoolean(optionValue);
+ log.debug("Option 'replyFilterEnabled' set to {}", value);
+ changeSetData.setReplyFilterEnabled(value);
+ break;
+ case DEBUG:
+ if (config.getEnableMessageDebugging()) {
+ log.debug("Response Mode set to Debug");
+ changeSetData.setDebugReviewMode(true);
+ changeSetData.setReplyFilterEnabled(false);
+ } else {
+ changeSetData.setReviewSystemMessage(localizer.getText(
+ "message.debugging.review.disabled"
+ ));
+ log.debug("Unable to set Response Mode to Debug: `enableMessageDebugging` config " +
+ "must be set to true");
+ }
+ break;
}
+ } else if (command == COMMAND_SET.CONFIGURE && config.getEnableMessageDebugging()) {
+ log.debug("Updating configuration setting '{}' to '{}'", optionKey, optionValue);
+ dynamicConfiguration.setConfig(optionKey, optionValue);
}
}
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/messages/ClientMessage.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/messages/ClientMessage.java
index 1d545f5..eac9527 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/messages/ClientMessage.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/messages/ClientMessage.java
@@ -1,6 +1,7 @@
package com.googlesource.gerrit.plugins.chatgpt.mode.common.client.messages;
import com.googlesource.gerrit.plugins.chatgpt.config.Configuration;
+import com.googlesource.gerrit.plugins.chatgpt.data.PluginDataHandlerProvider;
import com.googlesource.gerrit.plugins.chatgpt.localization.Localizer;
import com.googlesource.gerrit.plugins.chatgpt.mode.common.client.ClientBase;
import com.googlesource.gerrit.plugins.chatgpt.mode.common.client.commands.ClientCommands;
@@ -19,19 +20,26 @@
private final Pattern botMentionPattern;
private final ClientCommands clientCommands;
private final DebugCodeBlocksReview debugCodeBlocksReview;
+ private final DebugCodeBlocksDynamicSettings debugCodeBlocksDynamicSettings;
@Getter
private String message;
- public ClientMessage(Configuration config, ChangeSetData changeSetData, Localizer localizer) {
+ public ClientMessage(
+ Configuration config,
+ ChangeSetData changeSetData,
+ PluginDataHandlerProvider pluginDataHandlerProvider,
+ Localizer localizer
+ ) {
super(config);
botMentionPattern = getBotMentionPattern();
- clientCommands = new ClientCommands(config, changeSetData, localizer);
+ clientCommands = new ClientCommands(config, changeSetData, pluginDataHandlerProvider, localizer);
debugCodeBlocksReview = new DebugCodeBlocksReview(localizer);
+ debugCodeBlocksDynamicSettings = new DebugCodeBlocksDynamicSettings(localizer);
}
public ClientMessage(Configuration config, ChangeSetData changeSetData, String message, Localizer localizer) {
- this(config, changeSetData, localizer);
+ this(config, changeSetData, (PluginDataHandlerProvider) null, localizer);
this.message = message;
}
@@ -62,11 +70,16 @@
return this;
}
- public ClientMessage removeDebugCodeBlocks() {
+ public ClientMessage removeDebugCodeBlocksReview() {
message = debugCodeBlocksReview.removeDebugCodeBlocks(message);
return this;
}
+ public ClientMessage removeDebugCodeBlocksDynamicSettings() {
+ message = debugCodeBlocksDynamicSettings.removeDebugCodeBlocks(message);
+ return this;
+ }
+
public boolean isContainingHistoryCommand() {
return clientCommands.isContainingHistoryCommand();
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/messages/DebugCodeBlocksDynamicSettings.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/messages/DebugCodeBlocksDynamicSettings.java
new file mode 100644
index 0000000..cb3829c
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/messages/DebugCodeBlocksDynamicSettings.java
@@ -0,0 +1,20 @@
+package com.googlesource.gerrit.plugins.chatgpt.mode.common.client.messages;
+
+import com.googlesource.gerrit.plugins.chatgpt.localization.Localizer;
+
+import java.util.List;
+import java.util.Map;
+
+import static com.googlesource.gerrit.plugins.chatgpt.utils.TextUtils.prettyStringifyMap;
+
+public class DebugCodeBlocksDynamicSettings extends DebugCodeBlocks {
+ public DebugCodeBlocksDynamicSettings(Localizer localizer) {
+ super(localizer.getText("message.dynamic.configuration.title"));
+ }
+
+ public String getDebugCodeBlock(Map<String, String> dynamicConfig) {
+ return super.getDebugCodeBlock(List.of(
+ prettyStringifyMap(dynamicConfig)
+ ));
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/prompt/ChatGptComment.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/prompt/ChatGptComment.java
index fccc1a9..ebddc85 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/prompt/ChatGptComment.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/prompt/ChatGptComment.java
@@ -24,7 +24,7 @@
protected String getCleanedMessage(GerritComment commentProperty) {
commentMessage = new ClientMessage(config, changeSetData, commentProperty.getMessage(), localizer);
if (isFromAssistant(commentProperty)) {
- commentMessage.removeDebugCodeBlocks();
+ commentMessage.removeDebugCodeBlocksReview().removeDebugCodeBlocksDynamicSettings();
}
else {
commentMessage.removeMentions().parseRemoveCommands();
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/prompt/Directives.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/prompt/Directives.java
index c3ccdaf..6cdeb98 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/prompt/Directives.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/prompt/Directives.java
@@ -15,7 +15,7 @@
}
public void copyDirectiveToSettings() {
- if (!directive.isEmpty()) {
+ if (directive != null && !directive.isEmpty()) {
changeSetData.getDirectives().add(directive);
}
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/model/data/ChangeSetData.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/model/data/ChangeSetData.java
index af99c68..ab5cd23 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/model/data/ChangeSetData.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/model/data/ChangeSetData.java
@@ -24,8 +24,18 @@
// Command variables
private Boolean forcedReview = false;
private Boolean forcedReviewLastPatchSet = false;
+ private Boolean forceDisplaySystemMessage = false;
private Boolean replyFilterEnabled = true;
- private Boolean debugMode = false;
+ private Boolean debugReviewMode = false;
+ private Boolean hideChatGptReview = false;
private Set<String> directives = new HashSet<>();
private String reviewSystemMessage;
+
+ public Boolean shouldHideChatGptReview() {
+ return hideChatGptReview && !forcedReview;
+ }
+
+ public Boolean shouldRequestChatGptReview() {
+ return reviewSystemMessage == null && !shouldHideChatGptReview();
+ }
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/model/review/ReviewBatch.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/model/review/ReviewBatch.java
index ef8743f..73196d5 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/model/review/ReviewBatch.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/model/review/ReviewBatch.java
@@ -2,12 +2,16 @@
import com.googlesource.gerrit.plugins.chatgpt.mode.common.model.api.gerrit.GerritCodeRange;
import lombok.Data;
+import lombok.NonNull;
+import lombok.RequiredArgsConstructor;
import static com.googlesource.gerrit.plugins.chatgpt.settings.Settings.GERRIT_PATCH_SET_FILENAME;
@Data
+@RequiredArgsConstructor
public class ReviewBatch {
private String id;
+ @NonNull
private String content;
private String filename;
private Integer line;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/utils/TextUtils.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/utils/TextUtils.java
index 499df4f..b64c5a4 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/utils/TextUtils.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/utils/TextUtils.java
@@ -3,10 +3,7 @@
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.Field;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Set;
+import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
@@ -46,6 +43,10 @@
return String.join("\n", components);
}
+ public static String joinWithDoubleNewLine(List<String> components) {
+ return String.join("\n\n", components);
+ }
+
public static String joinWithComma(Set<String> components) {
return String.join(COMMA, components);
}
@@ -76,4 +77,12 @@
}
return joinWithNewLine(lines);
}
+
+ public static String prettyStringifyMap(Map<String, String> map) {
+ return joinWithNewLine(
+ map.entrySet().stream()
+ .map(entry -> entry.getKey() + ": " + entry.getValue())
+ .collect(Collectors.toList())
+ );
+ }
}
diff --git a/src/main/resources/localization/localTexts.properties b/src/main/resources/localization/localTexts.properties
index 00c5f06..433a0cf 100644
--- a/src/main/resources/localization/localTexts.properties
+++ b/src/main/resources/localization/localTexts.properties
@@ -1,4 +1,6 @@
system.message.prefix=SYSTEM MESSAGE:
message.empty.review=No update to show for this Change Set
-message.debugging.functionalities.disabled=Message Debugging functionalities are disabled
+message.debugging.review.disabled=Unable to set Response Mode to Debug: Message Debugging functionalities are disabled
+message.configure.from.messages.disabled=Unable to change configuration from messages: Message Debugging functionalities are disabled
message.debugging.review.title=DEBUGGING DETAILS
+message.dynamic.configuration.title=CONFIGURATION SETTINGS DYNAMICALLY MODIFIED
diff --git a/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewStatefulTest.java b/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewStatefulTest.java
index 267f968..e6fbcda 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewStatefulTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewStatefulTest.java
@@ -8,7 +8,6 @@
import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gson.JsonObject;
-import com.googlesource.gerrit.plugins.chatgpt.data.PluginDataHandler;
import com.googlesource.gerrit.plugins.chatgpt.mode.common.model.api.chatgpt.ChatGptResponseContent;
import com.googlesource.gerrit.plugins.chatgpt.mode.stateful.client.api.UriResourceLocatorStateful;
import com.googlesource.gerrit.plugins.chatgpt.mode.stateful.client.prompt.ChatGptPromptStateful;
@@ -20,7 +19,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.junit.MockitoJUnitRunner;
@@ -51,9 +49,6 @@
private ChatGptPromptStateful chatGptPromptStateful;
private JsonObject threadMessage;
- @Mock
- protected PluginDataHandler pluginDataHandler;
-
public ChatGptReviewStatefulTest() {
MockitoAnnotations.openMocks(this);
}
@@ -65,7 +60,7 @@
when(globalConfig.getString(Mockito.eq("gptMode"), Mockito.anyString()))
.thenReturn(MODES.stateful.name());
- // Mock the pluginDataHandlerProvider to return the mocked pluginDataHandler
+ // Mock the pluginDataHandlerProvider to return the mocked project pluginDataHandler
when(pluginDataHandlerProvider.getProjectScope()).thenReturn(pluginDataHandler);
}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewTestBase.java b/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewTestBase.java
index b91fddc..34d5d35 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewTestBase.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewTestBase.java
@@ -30,6 +30,7 @@
import com.google.inject.util.Providers;
import com.googlesource.gerrit.plugins.chatgpt.config.ConfigCreator;
import com.googlesource.gerrit.plugins.chatgpt.config.Configuration;
+import com.googlesource.gerrit.plugins.chatgpt.data.PluginDataHandler;
import com.googlesource.gerrit.plugins.chatgpt.data.PluginDataHandlerProvider;
import com.googlesource.gerrit.plugins.chatgpt.listener.EventHandlerTask;
import com.googlesource.gerrit.plugins.chatgpt.localization.Localizer;
@@ -97,6 +98,9 @@
protected PluginDataHandlerProvider pluginDataHandlerProvider;
@Mock
+ protected PluginDataHandler pluginDataHandler;
+
+ @Mock
protected OneOffRequestContext context;
@Mock
protected GerritApi gerritApi;
@@ -157,7 +161,14 @@
}
protected void initConfig() {
- config = new Configuration(context, gerritApi, globalConfig, projectConfig, "gpt@email.com", Account.id(1000000));
+ config = new Configuration(
+ context,
+ gerritApi,
+ globalConfig,
+ projectConfig,
+ "gpt@email.com",
+ Account.id(1000000)
+ );
}
protected void setupMockRequests() throws RestApiException {
@@ -184,6 +195,9 @@
// Mock the GerritApi's revision API
when(changeApiMock.current()).thenReturn(revisionApiMock);
+
+ // Mock the pluginDataHandlerProvider to return the mocked Change pluginDataHandler
+ when(pluginDataHandlerProvider.getChangeScope()).thenReturn(pluginDataHandler);
}
private Accounts mockGerritAccountsRestEndpoint() {
@@ -297,14 +311,20 @@
new GerritClientFacade(
config,
changeSetData,
- new GerritClientComments(config, accountCacheMock, changeSetData, localizer),
+ new GerritClientComments(
+ config,
+ accountCacheMock,
+ changeSetData,
+ pluginDataHandlerProvider,
+ localizer
+ ),
getGerritClientPatchSet()));
patchSetReviewer =
new PatchSetReviewer(
gerritClient,
config,
changeSetData,
- Providers.of(new GerritClientReview(config, accountCacheMock, localizer)),
+ Providers.of(new GerritClientReview(config, accountCacheMock, pluginDataHandlerProvider, localizer)),
getChatGptClient(),
localizer
);
diff --git a/src/test/java/com/googlesource/gerrit/plugins/chatgpt/integration/CodeReviewPluginIT.java b/src/test/java/com/googlesource/gerrit/plugins/chatgpt/integration/CodeReviewPluginIT.java
index 2f4c452..e61846a 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/chatgpt/integration/CodeReviewPluginIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/chatgpt/integration/CodeReviewPluginIT.java
@@ -2,6 +2,7 @@
import com.google.gerrit.server.account.AccountCache;
import com.googlesource.gerrit.plugins.chatgpt.config.Configuration;
+import com.googlesource.gerrit.plugins.chatgpt.data.PluginDataHandlerProvider;
import com.googlesource.gerrit.plugins.chatgpt.localization.Localizer;
import com.googlesource.gerrit.plugins.chatgpt.mode.common.client.api.gerrit.GerritChange;
import com.googlesource.gerrit.plugins.chatgpt.mode.common.client.api.gerrit.GerritClient;
@@ -33,6 +34,9 @@
@Mock
private Configuration config;
+ @Mock
+ protected PluginDataHandlerProvider pluginDataHandlerProvider;
+
@InjectMocks
private GerritClient gerritClient;
@@ -72,10 +76,9 @@
when(config.getGerritUserName()).thenReturn("Your Gerrit username");
List<ReviewBatch> reviewBatches = new ArrayList<>();
- reviewBatches.add(new ReviewBatch());
- reviewBatches.get(0).setContent("message");
+ reviewBatches.add(new ReviewBatch("message"));
- GerritClientReview gerritClientReview = new GerritClientReview(config, accountCache, localizer);
+ GerritClientReview gerritClientReview = new GerritClientReview(config, accountCache, pluginDataHandlerProvider, localizer);
gerritClientReview.setReview(new GerritChange("Your changeId"), reviewBatches, changeSetData);
}
}