This is an automated email from the ASF dual-hosted git repository.

lizhimins pushed a commit to branch rocketmq-studio
in repository https://gitbox.apache.org/repos/asf/rocketmq-dashboard.git


The following commit(s) were added to refs/heads/rocketmq-studio by this push:
     new ac66865eb fix(ai): page inventory tool results (#3052)
ac66865eb is described below

commit ac66865ebad801b05a52f6426d24880d1972ca1e
Author: xdz997 <[email protected]>
AuthorDate: Fri Sep 4 16:02:44 2026 +0800

    fix(ai): page inventory tool results (#3052)
---
 .../ops/ai/tool/AlertRuleListToolHandler.java      |  28 +-
 .../ops/ai/tool/ConsumerGroupListToolHandler.java  |   7 +-
 .../studio/ops/ai/tool/ToolListPagination.java     |  61 +++++
 .../studio/ops/ai/tool/TopicListToolHandler.java   |   7 +-
 .../src/main/resources/tool-catalog/rmq-tools.yaml | 284 +++++++++++++--------
 .../studio/ops/ai/tool/ToolGatewayServiceTest.java | 102 +++++---
 6 files changed, 312 insertions(+), 177 deletions(-)

diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/ops/ai/tool/AlertRuleListToolHandler.java
 
b/server/src/main/java/org/apache/rocketmq/studio/ops/ai/tool/AlertRuleListToolHandler.java
index f570861a7..b001b5c51 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/ops/ai/tool/AlertRuleListToolHandler.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/ops/ai/tool/AlertRuleListToolHandler.java
@@ -19,11 +19,11 @@ package org.apache.rocketmq.studio.ops.ai.tool;
 import lombok.RequiredArgsConstructor;
 import org.apache.rocketmq.studio.ops.alert.AlertRuleVO;
 import org.apache.rocketmq.studio.ops.alert.AlertService;
+import org.apache.rocketmq.studio.common.domain.PageResult;
 import org.springframework.stereotype.Component;
 
 import java.util.LinkedHashMap;
 import java.util.List;
-import java.util.Locale;
 import java.util.Map;
 
 @Component
@@ -43,29 +43,11 @@ public class AlertRuleListToolHandler implements 
ToolHandler {
     public Object execute(Map<String, Object> input) {
         String search = (String) input.get("search");
         Boolean enabled = (Boolean) input.get("enabled");
-        return alertService.listRules().stream()
-                .filter(rule -> matchesEnabled(rule, enabled))
-                .filter(rule -> matchesSearch(rule, search))
+        PageResult<AlertRuleVO> page = alertService.listRules(
+                search, enabled, ToolListPagination.page(input), 
ToolListPagination.pageSize(input));
+        return ToolListPagination.pagedResult(page, page.getItems().stream()
                 .map(AlertRuleListToolHandler::safeProjection)
-                .toList();
-    }
-
-    private static boolean matchesEnabled(AlertRuleVO rule, Boolean enabled) {
-        return enabled == null || rule.isEnabled() == enabled;
-    }
-
-    private static boolean matchesSearch(AlertRuleVO rule, String search) {
-        if (search == null || search.isBlank()) {
-            return true;
-        }
-        String normalizedSearch = search.trim().toLowerCase(Locale.ROOT);
-        return contains(rule.getName(), normalizedSearch)
-                || contains(rule.getMetric(), normalizedSearch)
-                || contains(rule.getDescription(), normalizedSearch);
-    }
-
-    private static boolean contains(String value, String normalizedSearch) {
-        return value != null && 
value.toLowerCase(Locale.ROOT).contains(normalizedSearch);
+                .toList());
     }
 
     private static Map<String, Object> safeProjection(AlertRuleVO rule) {
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/ops/ai/tool/ConsumerGroupListToolHandler.java
 
b/server/src/main/java/org/apache/rocketmq/studio/ops/ai/tool/ConsumerGroupListToolHandler.java
index db7fd84cd..0caf1e785 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/ops/ai/tool/ConsumerGroupListToolHandler.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/ops/ai/tool/ConsumerGroupListToolHandler.java
@@ -18,6 +18,7 @@ package org.apache.rocketmq.studio.ops.ai.tool;
 
 import org.apache.rocketmq.studio.instance.group.ConsumerGroupVO;
 import org.apache.rocketmq.studio.instance.topic.MetadataService;
+import org.apache.rocketmq.studio.common.domain.PageResult;
 import lombok.RequiredArgsConstructor;
 import org.springframework.stereotype.Component;
 
@@ -42,9 +43,11 @@ public class ConsumerGroupListToolHandler implements 
ToolHandler {
     public Object execute(Map<String, Object> input) {
         String clusterId = (String) input.get("cluster");
         String search = (String) input.get("search");
-        return metadataService.listConsumerGroups(clusterId, search).stream()
+        PageResult<ConsumerGroupVO> page = 
metadataService.listConsumerGroupsPage(
+                clusterId, null, search, ToolListPagination.page(input), 
ToolListPagination.pageSize(input));
+        return ToolListPagination.pagedResult(page, page.getItems().stream()
                 .map(ConsumerGroupListToolHandler::safeProjection)
-                .toList();
+                .toList());
     }
 
     private static Map<String, Object> safeProjection(ConsumerGroupVO group) {
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/ops/ai/tool/ToolListPagination.java
 
b/server/src/main/java/org/apache/rocketmq/studio/ops/ai/tool/ToolListPagination.java
new file mode 100644
index 000000000..70d2930aa
--- /dev/null
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/ops/ai/tool/ToolListPagination.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.rocketmq.studio.ops.ai.tool;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.rocketmq.studio.common.domain.PageResult;
+
+/** Shared pagination helpers for list-style AI tool handlers. */
+final class ToolListPagination {
+
+    static final int DEFAULT_PAGE = 1;
+    static final int DEFAULT_PAGE_SIZE = 20;
+
+    private ToolListPagination() {}
+
+    static Map<String, Object> pagedResult(PageResult<?> page, 
List<Map<String, Object>> items) {
+        Map<String, Object> result = new LinkedHashMap<>();
+        result.put("items", items);
+        result.put("total", page.getTotal());
+        result.put("page", page.getPage());
+        result.put("size", page.getSize());
+        return result;
+    }
+
+    static int page(Map<String, Object> input) {
+        return optionalPositiveInteger(input.get("page"), DEFAULT_PAGE, 
"page");
+    }
+
+    static int pageSize(Map<String, Object> input) {
+        return optionalPositiveInteger(input.get("pageSize"), 
DEFAULT_PAGE_SIZE, "pageSize");
+    }
+
+    private static int optionalPositiveInteger(Object value, int defaultValue, 
String field) {
+        if (value == null) {
+            return defaultValue;
+        }
+        if (value instanceof Number number) {
+            int result = number.intValue();
+            if (result > 0) {
+                return result;
+            }
+        }
+        throw new IllegalArgumentException(field + " must be positive");
+    }
+}
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/ops/ai/tool/TopicListToolHandler.java
 
b/server/src/main/java/org/apache/rocketmq/studio/ops/ai/tool/TopicListToolHandler.java
index 6eb730c27..910cff251 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/ops/ai/tool/TopicListToolHandler.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/ops/ai/tool/TopicListToolHandler.java
@@ -18,6 +18,7 @@ package org.apache.rocketmq.studio.ops.ai.tool;
 
 import org.apache.rocketmq.studio.instance.topic.MetadataService;
 import org.apache.rocketmq.studio.instance.topic.TopicVO;
+import org.apache.rocketmq.studio.common.domain.PageResult;
 import lombok.RequiredArgsConstructor;
 import org.springframework.stereotype.Component;
 
@@ -42,9 +43,11 @@ public class TopicListToolHandler implements ToolHandler {
         String clusterId = (String) input.get("cluster");
         String type = (String) input.get("type");
         String search = (String) input.get("search");
-        return metadataService.listTopics(clusterId, type, search).stream()
+        PageResult<TopicVO> page = metadataService.listTopicsPage(
+                clusterId, null, type, search, ToolListPagination.page(input), 
ToolListPagination.pageSize(input));
+        return ToolListPagination.pagedResult(page, page.getItems().stream()
                 .map(TopicListToolHandler::safeProjection)
-                .toList();
+                .toList());
     }
 
     private static Map<String, Object> safeProjection(TopicVO topic) {
diff --git a/server/src/main/resources/tool-catalog/rmq-tools.yaml 
b/server/src/main/resources/tool-catalog/rmq-tools.yaml
index e66c60426..647467a18 100644
--- a/server/src/main/resources/tool-catalog/rmq-tools.yaml
+++ b/server/src/main/resources/tool-catalog/rmq-tools.yaml
@@ -205,43 +205,65 @@ tools:
         search:
           type: string
           minLength: 1
+        page:
+          type: integer
+          minimum: 1
+        pageSize:
+          type: integer
+          minimum: 1
+          maximum: 100
     outputSchema:
-      type: array
-      items:
-        type: object
-        required:
-          - name
-          - namespace
-          - clusterId
-          - type
-          - writeQueues
-          - readQueues
-          - perm
-          - messageCount
-          - tps
-          - consumerGroupCount
-        additionalProperties: false
-        properties:
-          name:
-            type: string
-          namespace:
-            type: string
-          clusterId:
-            type: string
-          type:
-            type: string
-          writeQueues:
-            type: integer
-          readQueues:
-            type: integer
-          perm:
-            type: string
-          messageCount:
-            type: integer
-          tps:
-            type: number
-          consumerGroupCount:
-            type: integer
+      type: object
+      required:
+        - items
+        - total
+        - page
+        - size
+      additionalProperties: false
+      properties:
+        items:
+          type: array
+          items:
+            type: object
+            required:
+              - name
+              - namespace
+              - clusterId
+              - type
+              - writeQueues
+              - readQueues
+              - perm
+              - messageCount
+              - tps
+              - consumerGroupCount
+            additionalProperties: false
+            properties:
+              name:
+                type: string
+              namespace:
+                type: string
+              clusterId:
+                type: string
+              type:
+                type: string
+              writeQueues:
+                type: integer
+              readQueues:
+                type: integer
+              perm:
+                type: string
+              messageCount:
+                type: integer
+              tps:
+                type: number
+              consumerGroupCount:
+                type: integer
+        total:
+          type: integer
+        page:
+          type: integer
+        size:
+          type: integer
     viewHint: table
     deprecated: false
   - name: rmq.group.list
@@ -265,42 +287,64 @@ tools:
         search:
           type: string
           minLength: 1
+        page:
+          type: integer
+          minimum: 1
+        pageSize:
+          type: integer
+          minimum: 1
+          maximum: 100
     outputSchema:
-      type: array
-      items:
-        type: object
-        required:
-          - name
-          - namespace
-          - clusterId
-          - subscriptionMode
-          - consumeType
-          - onlineInstances
-          - totalLag
-          - subscribedTopics
-          - retryMaxTimes
-        additionalProperties: false
-        properties:
-          name:
-            type: string
-          namespace:
-            type: string
-          clusterId:
-            type: string
-          subscriptionMode:
-            type: string
-          consumeType:
-            type: string
-          onlineInstances:
-            type: integer
-          totalLag:
-            type: integer
-          subscribedTopics:
-            type: array
-            items:
-              type: string
-          retryMaxTimes:
-            type: integer
+      type: object
+      required:
+        - items
+        - total
+        - page
+        - size
+      additionalProperties: false
+      properties:
+        items:
+          type: array
+          items:
+            type: object
+            required:
+              - name
+              - namespace
+              - clusterId
+              - subscriptionMode
+              - consumeType
+              - onlineInstances
+              - totalLag
+              - subscribedTopics
+              - retryMaxTimes
+            additionalProperties: false
+            properties:
+              name:
+                type: string
+              namespace:
+                type: string
+              clusterId:
+                type: string
+              subscriptionMode:
+                type: string
+              consumeType:
+                type: string
+              onlineInstances:
+                type: integer
+              totalLag:
+                type: integer
+              subscribedTopics:
+                type: array
+                items:
+                  type: string
+              retryMaxTimes:
+                type: integer
+        total:
+          type: integer
+        page:
+          type: integer
+        size:
+          type: integer
     viewHint: table
     deprecated: false
   - name: rmq.message.query
@@ -453,45 +497,67 @@ tools:
           minLength: 1
         enabled:
           type: boolean
+        page:
+          type: integer
+          minimum: 1
+        pageSize:
+          type: integer
+          minimum: 1
+          maximum: 100
     outputSchema:
-      type: array
-      items:
-        type: object
-        required:
-          - id
-          - name
-          - metric
-          - operator
-          - threshold
-          - thresholdUnit
-          - duration
-          - channels
-          - enabled
-          - description
-        additionalProperties: false
-        properties:
-          id:
-            type: integer
-          name:
-            type: string
-          metric:
-            type: string
-          operator:
-            type: string
-          threshold:
-            type: number
-          thresholdUnit:
-            type: string
-          duration:
-            type: string
-          channels:
-            type: array
-            items:
-              type: string
-          enabled:
-            type: boolean
-          description:
-            type: string
+      type: object
+      required:
+        - items
+        - total
+        - page
+        - size
+      additionalProperties: false
+      properties:
+        items:
+          type: array
+          items:
+            type: object
+            required:
+              - id
+              - name
+              - metric
+              - operator
+              - threshold
+              - thresholdUnit
+              - duration
+              - channels
+              - enabled
+              - description
+            additionalProperties: false
+            properties:
+              id:
+                type: integer
+              name:
+                type: string
+              metric:
+                type: string
+              operator:
+                type: string
+              threshold:
+                type: number
+              thresholdUnit:
+                type: string
+              duration:
+                type: string
+              channels:
+                type: array
+                items:
+                  type: string
+              enabled:
+                type: boolean
+              description:
+                type: string
+        total:
+          type: integer
+        page:
+          type: integer
+        size:
+          type: integer
     viewHint: table
     deprecated: false
   - name: rmq.nameserver.config.diff
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/ops/ai/tool/ToolGatewayServiceTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/ops/ai/tool/ToolGatewayServiceTest.java
index 69de63e16..1b88dde62 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/ops/ai/tool/ToolGatewayServiceTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/ops/ai/tool/ToolGatewayServiceTest.java
@@ -29,6 +29,7 @@ import 
org.apache.rocketmq.studio.common.domain.enums.SubscriptionMode;
 import org.apache.rocketmq.studio.common.domain.enums.TopicPerm;
 import org.apache.rocketmq.studio.common.domain.enums.TopicType;
 import org.apache.rocketmq.studio.common.exception.BusinessException;
+import org.apache.rocketmq.studio.common.domain.PageResult;
 import org.apache.rocketmq.studio.instance.group.ConsumerGroupVO;
 import org.apache.rocketmq.studio.instance.message.MessageService;
 import org.apache.rocketmq.studio.instance.topic.MetadataService;
@@ -390,25 +391,31 @@ class ToolGatewayServiceTest {
         
when(clusterService.getCluster("cluster-v5")).thenReturn(cluster(ClusterType.V5_PROXY_CLUSTER));
         TopicVO topic = topic();
         topic.setRemark("do-not-expose");
-        when(metadataService.listTopics("cluster-v5", "NORMAL", "order"))
-                .thenReturn(List.of(topic));
+        when(metadataService.listTopicsPage("cluster-v5", null, "NORMAL", 
"order", 2, 20))
+                .thenReturn(PageResult.of(List.of(topic), 101, 2, 20));
 
         Object output = gateway.execute("rmq.topic.list", Map.of(
                 "cluster", "cluster-v5",
                 "type", "NORMAL",
-                "search", "order"));
+                "search", "order",
+                "page", 2,
+                "pageSize", 20));
 
-        assertThat(output).isEqualTo(List.of(Map.of(
-                "name", "order-topic",
-                "namespace", "default",
-                "clusterId", "cluster-v5",
-                "type", "NORMAL",
-                "writeQueues", 8,
-                "readQueues", 8,
-                "perm", "RW",
-                "messageCount", 1200L,
-                "tps", 23.5D,
-                "consumerGroupCount", 3)));
+        assertThat(output).isEqualTo(Map.of(
+                "items", List.of(Map.of(
+                        "name", "order-topic",
+                        "namespace", "default",
+                        "clusterId", "cluster-v5",
+                        "type", "NORMAL",
+                        "writeQueues", 8,
+                        "readQueues", 8,
+                        "perm", "RW",
+                        "messageCount", 1200L,
+                        "tps", 23.5D,
+                        "consumerGroupCount", 3)),
+                "total", 101L,
+                "page", 2,
+                "size", 20));
         assertThat(output.toString()).doesNotContain("do-not-expose");
     }
 
@@ -425,22 +432,29 @@ class ToolGatewayServiceTest {
         
when(clusterService.getCluster("cluster-v5")).thenReturn(cluster(ClusterType.V5_PROXY_CLUSTER));
         ConsumerGroupVO group = consumerGroup();
         group.setDelaySeconds(30);
-        when(metadataService.listConsumerGroups("cluster-v5", 
"order")).thenReturn(List.of(group));
+        when(metadataService.listConsumerGroupsPage("cluster-v5", null, 
"order", 2, 20))
+                .thenReturn(PageResult.of(List.of(group), 101, 2, 20));
 
         Object output = gateway.execute("rmq.group.list", Map.of(
                 "cluster", "cluster-v5",
-                "search", "order"));
+                "search", "order",
+                "page", 2,
+                "pageSize", 20));
 
-        assertThat(output).isEqualTo(List.of(Map.of(
-                "name", "cg-order",
-                "namespace", "default",
-                "clusterId", "cluster-v5",
-                "subscriptionMode", "Push",
-                "consumeType", "CLUSTERING",
-                "onlineInstances", 2,
-                "totalLag", 42L,
-                "subscribedTopics", List.of("order-topic"),
-                "retryMaxTimes", 16)));
+        assertThat(output).isEqualTo(Map.of(
+                "items", List.of(Map.of(
+                        "name", "cg-order",
+                        "namespace", "default",
+                        "clusterId", "cluster-v5",
+                        "subscriptionMode", "Push",
+                        "consumeType", "CLUSTERING",
+                        "onlineInstances", 2,
+                        "totalLag", 42L,
+                        "subscribedTopics", List.of("order-topic"),
+                        "retryMaxTimes", 16)),
+                "total", 101L,
+                "page", 2,
+                "size", 20));
         assertThat(output.toString()).doesNotContain("delaySeconds");
     }
 
@@ -454,26 +468,32 @@ class ToolGatewayServiceTest {
 
     @Test
     void executesAlertRuleListThroughADataMinimizingProjection() {
-        when(alertService.listRules()).thenReturn(List.of(
-                alertRule(1L, "High Lag", "rocketmq_consumer_lag_messages", 
true),
-                alertRule(2L, "Broker Down", "up", false)));
+        when(alertService.listRules("  LAG  ", true, 2, 
20)).thenReturn(PageResult.of(
+                List.of(alertRule(1L, "High Lag", 
"rocketmq_consumer_lag_messages", true)),
+                101, 2, 20));
 
         Object output = gateway.execute("rmq.alert.rule.list", Map.of(
                 "cluster", "cluster-v5",
                 "search", "  LAG  ",
-                "enabled", true));
-
-        assertThat(output).isEqualTo(List.of(Map.of(
-                "id", 1L,
-                "name", "High Lag",
-                "metric", "rocketmq_consumer_lag_messages",
-                "operator", ">",
-                "threshold", 100000D,
-                "thresholdUnit", "messages",
-                "duration", "5m",
-                "channels", List.of("email"),
                 "enabled", true,
-                "description", "Consumer lag is high")));
+                "page", 2,
+                "pageSize", 20));
+
+        assertThat(output).isEqualTo(Map.of(
+                "items", List.of(Map.of(
+                        "id", 1L,
+                        "name", "High Lag",
+                        "metric", "rocketmq_consumer_lag_messages",
+                        "operator", ">",
+                        "threshold", 100000D,
+                        "thresholdUnit", "messages",
+                        "duration", "5m",
+                        "channels", List.of("email"),
+                        "enabled", true,
+                        "description", "Consumer lag is high")),
+                "total", 101L,
+                "page", 2,
+                "size", 20));
         assertThat(output.toString()).doesNotContain("lastTriggered");
     }
 

Reply via email to