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");
}