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 15167dc5d fix(studio): normalize the audit search term and require an 
alert metric (#4682)
15167dc5d is described below

commit 15167dc5da4538a46ca1e207adc301dabda71ddb
Author: btlqql <[email protected]>
AuthorDate: Mon Sep 21 20:17:08 2026 +0800

    fix(studio): normalize the audit search term and require an alert metric 
(#4682)
    
    Two service-input validation fixes from the same author, consolidated into 
one change.
    
    1. `AuditService` passed the raw `search` term to the repository, so a term 
of only spaces became a LIKE pattern that matched nothing while the UI showed 
an unfiltered-looking empty result. A new `normalizeSearch` trims the term and 
maps blank to null across the list, summary and export entry points — the 
behaviour `AlertService` and `AuthService` already had.
    2. `NativeAlertRuleTestService.test` dereferenced the metric of a rule that 
has none and answered 500; it now rejects the run with `BusinessException(400, 
"metric is required")`.
    
    Consolidates #4682 and #4711 (same author, both service-layer input 
validation).
---
 .../ops/alert/NativeAlertRuleTestService.java      |  6 +++
 .../rocketmq/studio/ops/audit/AuditService.java    | 16 +++++++-
 .../ops/alert/NativeAlertRuleTestServiceTest.java  | 20 +++++++++
 .../studio/ops/audit/AuditServiceTest.java         | 47 ++++++++++++++++++++++
 4 files changed, 87 insertions(+), 2 deletions(-)

diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/ops/alert/NativeAlertRuleTestService.java
 
b/server/src/main/java/org/apache/rocketmq/studio/ops/alert/NativeAlertRuleTestService.java
index 8c3095a41..dbe556b78 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/ops/alert/NativeAlertRuleTestService.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/ops/alert/NativeAlertRuleTestService.java
@@ -25,6 +25,7 @@ import 
org.apache.rocketmq.studio.common.exception.BusinessException;
 import org.apache.rocketmq.studio.instance.InstanceRepository;
 import org.apache.rocketmq.studio.instance.InstanceVO;
 import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -41,6 +42,11 @@ public class NativeAlertRuleTestService {
 
     public AlertRuleTestResultVO test(AlertRuleVO rule) {
         normalizeRule(rule);
+        if (!StringUtils.hasText(rule.getMetric())) {
+            // A test run without a metric used to reach the sample filter 
below and fail with a
+            // NullPointerException, which the request layer reports as an 
unhandled server error.
+            throw new BusinessException(400, "metric is required");
+        }
         NativeAlertRulePolicy.validate(rule);
         InstanceVO instance = 
instanceRepository.findByIdentifier(rule.getInstanceId())
                 .orElseThrow(() -> new BusinessException(404, "Instance not 
found: " + rule.getInstanceId()));
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditService.java 
b/server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditService.java
index a514d4fe8..563bb98fa 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditService.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/ops/audit/AuditService.java
@@ -23,6 +23,7 @@ import org.apache.rocketmq.studio.common.util.CsvUtil;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
 
 import java.time.LocalDate;
 import java.time.LocalDateTime;
@@ -67,7 +68,8 @@ public class AuditService {
     public AuditSummaryVO summarize(String search, String operationType, 
String resourceType,
                                     String clusterId, String startDate, String 
endDate, String result) {
         DateRange range = parseDateRange(startDate, endDate);
-        return auditRepository.summarize(search, operationType, resourceType, 
clusterId,
+        String normalizedSearch = normalizeSearch(search);
+        return auditRepository.summarize(normalizedSearch, operationType, 
resourceType, clusterId,
                 range.start(), range.end(), result);
     }
 
@@ -165,7 +167,8 @@ public class AuditService {
                                                String startDate, String 
endDate,
                                                String result, int page, int 
pageSize) {
         DateRange range = parseDateRange(startDate, endDate);
-        return auditRepository.findPage(search, operationType, resourceType, 
target, clusterId,
+        String normalizedSearch = normalizeSearch(search);
+        return auditRepository.findPage(normalizedSearch, operationType, 
resourceType, target, clusterId,
                 clusterIdMissing,
                 range.start(), range.end(), result, page, pageSize);
     }
@@ -191,6 +194,15 @@ public class AuditService {
         }
     }
 
+    /**
+     * Trims the free-text search term so a term pasted with surrounding 
whitespace still matches, and
+     * treats a whitespace-only term as no filter at all. Normalized once on 
the way into the repository
+     * so the paged list, the summary aggregates and the CSV export all filter 
on the same term.
+     */
+    private static String normalizeSearch(String search) {
+        return StringUtils.hasText(search) ? search.trim() : null;
+    }
+
     private record DateRange(LocalDateTime start, LocalDateTime end) {
     }
 }
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/ops/alert/NativeAlertRuleTestServiceTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/ops/alert/NativeAlertRuleTestServiceTest.java
index 49a1f4559..12cf45bfc 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/ops/alert/NativeAlertRuleTestServiceTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/ops/alert/NativeAlertRuleTestServiceTest.java
@@ -17,6 +17,7 @@
 package org.apache.rocketmq.studio.ops.alert;
 
 import org.apache.rocketmq.studio.cluster.metrics.BusinessMetricsCollector;
+import org.apache.rocketmq.studio.common.exception.BusinessException;
 import org.apache.rocketmq.studio.cluster.metrics.MetricAvailability;
 import org.apache.rocketmq.studio.cluster.metrics.MetricSample;
 import org.apache.rocketmq.studio.instance.InstanceRepository;
@@ -29,6 +30,7 @@ import java.util.Map;
 import java.util.Optional;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
@@ -123,6 +125,24 @@ class NativeAlertRuleTestServiceTest {
                 .satisfies(sample -> 
assertThat(sample.currentValue()).isEqualTo(20));
     }
 
+    @Test
+    void 
rejectsARuleWithoutMetricInsteadOfFailingWithANullPointerExceptionTest() {
+        InstanceRepository instances = mock(InstanceRepository.class);
+        BusinessMetricsCollector collector = 
mock(BusinessMetricsCollector.class);
+        InstanceVO instance = InstanceVO.builder().name("local").build();
+        
when(instances.findByIdentifier("local")).thenReturn(Optional.of(instance));
+        when(collector.supports(instance)).thenReturn(true);
+        when(collector.collect(instance)).thenReturn(List.of(sample("orders", 
20)));
+        AlertRuleVO rule = 
AlertRuleVO.builder().domain(AlertDomain.BUSINESS).instanceId("local")
+                .operator(">").threshold(10).build();
+
+        assertThatThrownBy(() -> new NativeAlertRuleTestService(instances, 
List.of(), List.of(collector),
+                new AlertRuleEvaluator()).test(rule))
+                .isInstanceOf(BusinessException.class)
+                .hasMessage("metric is required")
+                .satisfies(error -> assertThat(((BusinessException) 
error).getCode()).isEqualTo(400));
+    }
+
     private static MetricSample sample(String group, double value) {
         return sample("consumer.lag.total", group, value);
     }
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/ops/audit/AuditServiceTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/ops/audit/AuditServiceTest.java
index cfbeaaa59..02ac20a9f 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/ops/audit/AuditServiceTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/ops/audit/AuditServiceTest.java
@@ -34,6 +34,8 @@ import java.util.TimeZone;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.verify;
@@ -265,4 +267,49 @@ class AuditServiceTest {
         assertThat(deleted).isEqualTo(500);
         verify(auditRepository).deleteBefore(any(LocalDateTime.class), 
eq(500), eq(20));
     }
+
+    @Test
+    void queryLogsShouldTrimSearchTermBeforeDelegating() {
+        auditService.queryLogs(1, 10, "  ops  ", null, null, null, null, false,
+                null, null, null);
+
+        ArgumentCaptor<String> search = ArgumentCaptor.forClass(String.class);
+        verify(auditRepository).findPage(search.capture(), isNull(), isNull(), 
isNull(),
+                isNull(), eq(false), isNull(), isNull(), isNull(), eq(1), 
eq(10));
+        assertThat(search.getValue()).isEqualTo("ops");
+    }
+
+    @Test
+    void queryLogsShouldTreatWhitespaceOnlySearchAsAbsent() {
+        auditService.queryLogs(1, 10, "   ", null, null, null, null, false,
+                null, null, null);
+
+        ArgumentCaptor<String> search = ArgumentCaptor.forClass(String.class);
+        verify(auditRepository).findPage(search.capture(), isNull(), isNull(), 
isNull(),
+                isNull(), eq(false), isNull(), isNull(), isNull(), eq(1), 
eq(10));
+        assertThat(search.getValue()).isNull();
+    }
+
+    @Test
+    void exportLogsShouldTrimSearchTermBeforeDelegating() {
+        when(auditRepository.findPage(any(), any(), any(), any(), any(), 
anyBoolean(),
+                any(), any(), any(), anyInt(), 
anyInt())).thenReturn(PageResult.empty(1, 10));
+
+        auditService.exportLogs("  50% off  ", null, null, null, null, false, 
null, null, null);
+
+        ArgumentCaptor<String> search = ArgumentCaptor.forClass(String.class);
+        verify(auditRepository).findPage(search.capture(), any(), any(), 
any(), any(), anyBoolean(),
+                any(), any(), any(), anyInt(), anyInt());
+        assertThat(search.getValue()).isEqualTo("50% off");
+    }
+
+    @Test
+    void summarizeShouldTrimSearchTermBeforeDelegating() {
+        auditService.summarize("  ops  ", null, null, null, null, null, null);
+
+        ArgumentCaptor<String> search = ArgumentCaptor.forClass(String.class);
+        verify(auditRepository).summarize(search.capture(), isNull(), 
isNull(), isNull(),
+                isNull(), isNull(), isNull());
+        assertThat(search.getValue()).isEqualTo("ops");
+    }
 }

Reply via email to