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 9b0763cc fix: escape special characters in exported alert rules (#722)
9b0763cc is described below

commit 9b0763cc0987616ce0da1c19d2286b9d6d2062f4
Author: yx9o <[email protected]>
AuthorDate: Mon Aug 3 11:13:39 2026 +0800

    fix: escape special characters in exported alert rules (#722)
---
 .../rocketmq/studio/ops/alert/AlertService.java    | 34 +++++++++++++---
 .../studio/ops/alert/AlertServiceTest.java         | 46 ++++++++++++++++++++++
 2 files changed, 74 insertions(+), 6 deletions(-)

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 59df2845..418a7fc0 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
@@ -58,8 +58,9 @@ public class AlertService {
             yaml.append("          severity: 
").append(rule.severity()).append('\n');
             yaml.append("          team: ").append(rule.team()).append('\n');
             yaml.append("        annotations:\n");
-            yaml.append("          summary: 
\"").append(escapeYaml(rule.summary())).append("\"\n");
-            yaml.append("          description: 
\"").append(escapeYaml(rule.description())).append("\"\n");
+            yaml.append("          summary: 
\"").append(escapeDoubleQuotedValue(rule.summary())).append("\"\n");
+            yaml.append("          description: 
\"").append(escapeDoubleQuotedValue(rule.description()))
+                    .append("\"\n");
         }
         return yaml.toString();
     }
@@ -188,7 +189,8 @@ public class AlertService {
     }
 
     private String alertName(AlertRuleVO rule) {
-        return hasText(rule.getName()) ? 
rule.getName().replaceAll("[^A-Za-z0-9_]", "") : "RocketMQAlert";
+        String alertName = hasText(rule.getName()) ? 
rule.getName().replaceAll("[^A-Za-z0-9_]", "") : "";
+        return alertName.isEmpty() ? "RocketMQAlert" : alertName;
     }
 
     private String expression(AlertRuleVO rule) {
@@ -211,7 +213,7 @@ public class AlertService {
         if (!selector.isEmpty()) {
             selector.append(',');
         }
-        selector.append(label).append("=\"").append(value.trim()).append('"');
+        
selector.append(label).append("=\"").append(escapeDoubleQuotedValue(value.trim())).append('"');
     }
 
     private String severity(AlertRuleVO rule) {
@@ -271,8 +273,28 @@ public class AlertService {
         return hasText(description) ? description : "RocketMQ alert condition 
matched.";
     }
 
-    private String escapeYaml(String value) {
-        return value.replace("\\", "\\\\").replace("\"", "\\\"");
+    private String escapeDoubleQuotedValue(String value) {
+        StringBuilder escaped = new StringBuilder(value.length());
+        for (int index = 0; index < value.length(); index++) {
+            char character = value.charAt(index);
+            switch (character) {
+                case '\\' -> escaped.append("\\\\");
+                case '"' -> escaped.append("\\\"");
+                case '\b' -> escaped.append("\\b");
+                case '\f' -> escaped.append("\\f");
+                case '\n' -> escaped.append("\\n");
+                case '\r' -> escaped.append("\\r");
+                case '\t' -> escaped.append("\\t");
+                default -> {
+                    if (character < 0x20) {
+                        escaped.append(String.format("\\u%04x", (int) 
character));
+                    } else {
+                        escaped.append(character);
+                    }
+                }
+            }
+        }
+        return escaped.toString();
     }
 
     private boolean hasText(String value) {
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 4c110c51..0e717c3f 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
@@ -16,6 +16,9 @@
  */
 package org.apache.rocketmq.studio.ops.alert;
 
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
 import org.apache.rocketmq.studio.common.domain.enums.AlertLevel;
 import org.apache.rocketmq.studio.common.exception.BusinessException;
 import org.junit.jupiter.api.Test;
@@ -152,6 +155,49 @@ class AlertServiceTest {
                 .contains("severity: warning");
     }
 
+    @Test
+    void exportPrometheusRulesYamlShouldEscapeSpecialCharacters() throws 
Exception {
+        AlertRuleVO rule = AlertRuleVO.builder()
+                .name("Scoped rule")
+                .metric("rocketmq_consumer_lag_messages")
+                .operator(">")
+                .threshold(1)
+                .clusterName("prod\"east\\dc\nline")
+                .brokerName("broker\tone")
+                .description("Summary \"quoted\" \\ path\nnext - 
Details\twith\rline")
+                .build();
+        when(alertRepository.findAllRules()).thenReturn(List.of(rule));
+
+        String result = alertService.exportPrometheusRulesYaml();
+
+        assertThat(result).contains(
+                "expr: 
rocketmq_consumer_lag_messages{cluster=\"prod\\\"east\\\\dc\\nline\","
+                        + "broker=\"broker\\tone\"} > 1");
+        JsonNode exportedRule = new ObjectMapper(new 
YAMLFactory()).readTree(result)
+                .path("groups").get(0).path("rules").get(0);
+        assertThat(exportedRule.path("annotations").path("summary").asText())
+                .isEqualTo("Summary \"quoted\" \\ path\nnext");
+        
assertThat(exportedRule.path("annotations").path("description").asText())
+                .isEqualTo("Details\twith\rline");
+    }
+
+    @Test
+    void 
exportPrometheusRulesYamlShouldUseFallbackWhenAlertNameHasNoValidCharacters() 
throws Exception {
+        AlertRuleVO rule = AlertRuleVO.builder()
+                .name(" - !")
+                .metric("rocketmq_consumer_lag_messages")
+                .operator(">")
+                .threshold(1)
+                .build();
+        when(alertRepository.findAllRules()).thenReturn(List.of(rule));
+
+        String result = alertService.exportPrometheusRulesYaml();
+
+        JsonNode exportedRule = new ObjectMapper(new 
YAMLFactory()).readTree(result)
+                .path("groups").get(0).path("rules").get(0);
+        
assertThat(exportedRule.path("alert").asText()).isEqualTo("RocketMQAlert");
+    }
+
     @Test
     void createRuleShouldPreserveReplicationScopeFields() {
         AlertRuleVO input = AlertRuleVO.builder()

Reply via email to