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()