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 9776f3f11 fix(alert): retain runtime state for equivalent duration 
edits (#6446)
9776f3f11 is described below

commit 9776f3f11c693782c41141c1c88f61e3c08f11ee
Author: Loyal-Young <[email protected]>
AuthorDate: Thu Oct 15 18:11:39 2026 +0800

    fix(alert): retain runtime state for equivalent duration edits (#6446)
---
 .../ops/alert/AlertRuleSemanticFingerprint.java    | 19 ++++++++++++-
 .../rocketmq/studio/ops/alert/AlertService.java    |  4 +--
 .../alert/AlertRuleSemanticFingerprintTest.java    | 22 +++++++++++++++
 .../studio/ops/alert/AlertServiceTest.java         | 31 ++++++++++++++++++++++
 4 files changed, 73 insertions(+), 3 deletions(-)

diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/ops/alert/AlertRuleSemanticFingerprint.java
 
b/server/src/main/java/org/apache/rocketmq/studio/ops/alert/AlertRuleSemanticFingerprint.java
index a0748ed04..ee88ac4eb 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/ops/alert/AlertRuleSemanticFingerprint.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/ops/alert/AlertRuleSemanticFingerprint.java
@@ -6,6 +6,8 @@
  */
 package org.apache.rocketmq.studio.ops.alert;
 
+import org.apache.rocketmq.studio.common.exception.BusinessException;
+
 import java.math.BigDecimal;
 import java.nio.charset.StandardCharsets;
 import java.security.MessageDigest;
@@ -23,13 +25,28 @@ final class AlertRuleSemanticFingerprint {
     }
 
     static String of(AlertRuleVO rule) {
+        return of(rule, normalizeLower(rule.getDuration()));
+    }
+
+    /** Runtime equivalence only; keep the persisted unique fingerprint's 
existing format. */
+    static String evaluationIdentity(AlertRuleVO rule) {
+        String duration = normalizeLower(rule.getDuration());
+        try {
+            duration = AlertRuleDuration.parse(duration).toString();
+        } catch (BusinessException invalidLegacyDuration) {
+            // Legacy external rules may contain invalid text; comparing 
identities must still work.
+        }
+        return of(rule, duration);
+    }
+
+    private static String of(AlertRuleVO rule, String duration) {
         StringBuilder value = new StringBuilder();
         append(value, rule.getDomain() == null ? AlertDomain.BUSINESS.name() : 
rule.getDomain().name());
         append(value, normalize(rule.getInstanceId()));
         append(value, normalize(rule.getMetric()));
         append(value, normalizeUpper(rule.getOperator()));
         append(value, normalizeThreshold(rule));
-        append(value, normalizeLower(rule.getDuration()));
+        append(value, duration);
         append(value, normalizeUpper(defaultIfBlank(rule.getAggregation(), 
"LAST")));
         append(value, Integer.toString(Math.max(0, rule.getWindowSeconds())));
         append(value, normalize(rule.getBrokerName()));
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/ops/alert/AlertService.java 
b/server/src/main/java/org/apache/rocketmq/studio/ops/alert/AlertService.java
index cf6b95a17..0c26bfec7 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/ops/alert/AlertService.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/ops/alert/AlertService.java
@@ -200,7 +200,7 @@ public class AlertService {
         log.info("Updating alert rule: {}", id);
         validateRuleId(id);
         String previousSemanticFingerprint = alertRepository.findRuleById(id)
-                .map(AlertRuleSemanticFingerprint::of)
+                .map(AlertRuleSemanticFingerprint::evaluationIdentity)
                 .orElse(null);
         NativeAlertRulePolicy.validate(rule);
         rejectDuplicateSemanticRule(rule, id);
@@ -209,7 +209,7 @@ public class AlertService {
         }
         // Disabled rules are excluded from evaluation/reconciliation; their 
runtime must be purged.
         if (!rule.isEnabled() || previousSemanticFingerprint == null
-                || 
!previousSemanticFingerprint.equals(AlertRuleSemanticFingerprint.of(rule))) {
+                || 
!previousSemanticFingerprint.equals(AlertRuleSemanticFingerprint.evaluationIdentity(rule)))
 {
             alertStateRepository.deleteByRuleId(id);
         }
         auditRule("UPDATE_ALERT_RULE", rule, null);
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/ops/alert/AlertRuleSemanticFingerprintTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/ops/alert/AlertRuleSemanticFingerprintTest.java
index a52012cc2..39ac438f5 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/ops/alert/AlertRuleSemanticFingerprintTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/ops/alert/AlertRuleSemanticFingerprintTest.java
@@ -13,6 +13,28 @@ import static org.assertj.core.api.Assertions.assertThat;
 
 class AlertRuleSemanticFingerprintTest {
 
+    @Test
+    void runtimeDurationEquivalenceKeepsPersistedIdentityFormatTest() {
+        AlertRuleVO seconds = AlertRuleVO.builder().duration("60s").build();
+        AlertRuleVO minutes = AlertRuleVO.builder().duration("1m").build();
+
+        assertThat(AlertRuleSemanticFingerprint.evaluationIdentity(seconds))
+                
.isEqualTo(AlertRuleSemanticFingerprint.evaluationIdentity(minutes));
+        assertThat(AlertRuleSemanticFingerprint.of(seconds))
+                .isNotEqualTo(AlertRuleSemanticFingerprint.of(minutes));
+    }
+
+    @Test
+    void runtimeIdentityRetainsMalformedLegacyDurationsTest() {
+        AlertRuleVO first = AlertRuleVO.builder().duration(" invalid 
").build();
+        AlertRuleVO same = AlertRuleVO.builder().duration("INVALID").build();
+        AlertRuleVO different = 
AlertRuleVO.builder().duration("other-invalid").build();
+
+        assertThat(AlertRuleSemanticFingerprint.evaluationIdentity(first))
+                
.isEqualTo(AlertRuleSemanticFingerprint.evaluationIdentity(same))
+                
.isNotEqualTo(AlertRuleSemanticFingerprint.evaluationIdentity(different));
+    }
+
     @Test
     void ignoresPresentationAndDeliverySettingsTest() {
         AlertRuleVO first = AlertRuleVO.builder()
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/ops/alert/AlertServiceTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/ops/alert/AlertServiceTest.java
index 5bcc10b4f..82e4d1a89 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/ops/alert/AlertServiceTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/ops/alert/AlertServiceTest.java
@@ -1078,6 +1078,37 @@ class AlertServiceTest {
         verify(alertRepository).insertRule(distinct);
     }
 
+    @org.junit.jupiter.params.ParameterizedTest
+    @org.junit.jupiter.params.provider.CsvSource({"60s,1m", "1h30m,90m", 
"1000ms,1s", "0s,''"})
+    void equivalentDurationEditKeepsEvaluationStateTest(String original, 
String replacement) {
+        AlertRuleVO existing = durationRule(1L, original);
+        AlertRuleVO update = durationRule(1L, replacement);
+        
when(alertRepository.findRuleById(1L)).thenReturn(java.util.Optional.of(existing));
+        when(alertRepository.replaceRule(update)).thenReturn(true);
+
+        alertService.updateRule(update);
+
+        verify(alertStateRepository, never()).deleteByRuleId(1L);
+    }
+
+    @Test
+    void changedDurationStillResetsEvaluationStateTest() {
+        AlertRuleVO existing = durationRule(1L, "60s");
+        AlertRuleVO update = durationRule(1L, "2m");
+        
when(alertRepository.findRuleById(1L)).thenReturn(java.util.Optional.of(existing));
+        when(alertRepository.replaceRule(update)).thenReturn(true);
+
+        alertService.updateRule(update);
+
+        verify(alertStateRepository).deleteByRuleId(1L);
+    }
+
+    private static AlertRuleVO durationRule(Long id, String duration) {
+        return AlertRuleVO.builder().id(id).name("Disk 
usage").domain(AlertDomain.CLUSTER)
+                
.instanceId("local").metric("broker.disk.usage_ratio").operator(">=").threshold(0.85)
+                .duration(duration).enabled(true).build();
+    }
+
     @Test
     void updateRuleShouldRejectAnotherRulesEvaluationConditionsTest() {
         AlertRuleVO existing = AlertRuleVO.builder()

Reply via email to