Add --subscribe option to event stream command

This change introduces a new --subscribe (alias -s) option to the event
stream command, enabling users to filter the real-time event output by
one or more specific event types. The option allows more efficient
monitoring and scripting, especially for clients interested in a subset
of events.

Release-Notes: Adds support for filtering streamed events by type
Change-Id: Iafb774a48cb07096c4d48167ee56a6cf02150125
diff --git a/src/main/java/com/googlesource/gerrit/plugins/events/StreamEvents.java b/src/main/java/com/googlesource/gerrit/plugins/events/StreamEvents.java
index 45549aa..c7fcc54 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/events/StreamEvents.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/events/StreamEvents.java
@@ -32,6 +32,8 @@
 import com.google.inject.Inject;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.Future;
 import java.util.concurrent.ScheduledThreadPoolExecutor;
 import org.apache.sshd.server.Environment;
@@ -78,6 +80,13 @@
   @Option(name = "--ids", usage = "add ids to events (useful for resuming after a disconnect)")
   protected boolean includeIds = false;
 
+  @Option(
+      name = "--subscribe",
+      aliases = {"-s"},
+      metaVar = "SUBSCRIBE",
+      usage = "subscribe to specific events")
+  protected List<String> subscribedEventTypes = new ArrayList<>();
+
   @Inject @StreamCommandExecutor protected ScheduledThreadPoolExecutor threadPool;
 
   @Inject protected EventStore events;
@@ -232,7 +241,15 @@
   protected void flush(String uuid, long number, String json) {
     if (json != null) {
       JsonElement el = JsonParser.parseString(json);
-      if (perms.isVisibleTo(el, currentUser)) {
+      boolean subscribed = true;
+      if (!subscribedEventTypes.isEmpty()) {
+        String eventType = null;
+        if (el.isJsonObject() && el.getAsJsonObject().has("type")) {
+          eventType = el.getAsJsonObject().get("type").getAsString();
+        }
+        subscribed = eventType != null && subscribedEventTypes.contains(eventType);
+      }
+      if (subscribed && perms.isVisibleTo(el, currentUser)) {
         if (includeIds) {
           el.getAsJsonObject().addProperty("id", uuid + ":" + number);
           json = gson.toJson(el);
diff --git a/src/main/resources/Documentation/cmd-stream.md b/src/main/resources/Documentation/cmd-stream.md
index 74e6a79..0e1b73e 100644
--- a/src/main/resources/Documentation/cmd-stream.md
+++ b/src/main/resources/Documentation/cmd-stream.md
@@ -11,6 +11,7 @@
 ssh -p @SSH_PORT@ @SSH_HOST@ @PLUGIN@ stream
    [--ids]
    [--resume-after <RESUME_AFTER>]
+   [--subscribe <EVENT_TYPE>]
 ```
 
 DESCRIPTION
@@ -41,6 +42,18 @@
 
 : event id after which to resume playing events on connection.
 
+**--subscribe**
+
+: Only stream events of the specified type(s). Multiple `--subscribe` (or `-s`) options can be provided to listen for multiple event types.
+  For example, to receive only change-abandoned and change-merged events:
+  ```
+  ssh -p @SSH_PORT@ @SSH_HOST@ @PLUGIN@ stream --subscribe change-abandoned --subscribe change-merged
+  ```
+  or using the alias:
+  ```
+  ssh -p @SSH_PORT@ @SSH_HOST@ @PLUGIN@ stream -s change-abandoned -s change-merged
+  ```
+  If not specified, all event types will be streamed.
 
 ACCESS
 ------
diff --git a/test/test_events_plugin.sh b/test/test_events_plugin.sh
index 406b351..01ef210 100755
--- a/test/test_events_plugin.sh
+++ b/test/test_events_plugin.sh
@@ -366,4 +366,29 @@
 result_type "$GROUP" "comment-added" 1
 result_type "$GROUP" "ref-updated" 2
 
+# ------------------------- Subscribe -------------------------
+FILTERED=""
+set_filter_rules # No rules
+GROUP=subscribe
+type=change-abandoned
+
+PLUGIN_CMD+=(-s "$type")
+
+ch1=$(create_change "$REF_BRANCH" "$FILE_A") || exit
+
+capture_events_for plugin $(add_meta_ref_updates 1 0)
+capture_events_for core $(add_meta_ref_updates 1 1)
+review "$ch1,1" --abandon
+result_type "$GROUP" "$type" 1
+
+type=change-restored
+capture_events $(add_meta_ref_updates 1 2)
+review "$ch1,1" --restore
+# Plugin is expected to emit zero change-restored events (filtered out).
+# The follow-on abandon regenerates change activity,
+# ensuring event capture completion and test progress.
+review "$ch1,1" --abandon
+result_type_for plugin "$GROUP" "$type" 0
+result_type_for core "$GROUP" "$type" 1
+
 exit $RESULT