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 a008d71a9 fix(observability): deduplicate bundled assets (#2224)
a008d71a9 is described below

commit a008d71a96c2898c3b721ba02b9227ffc1053bdd
Author: yyqdbngt <[email protected]>
AuthorDate: Wed Aug 19 14:26:12 2026 +0800

    fix(observability): deduplicate bundled assets (#2224)
---
 .../metrics/grafana/GrafanaDashboardService.java   | 16 +++++++++++--
 .../studio/ops/alert/AlertRuleAssetService.java    | 19 ++++++++++++---
 .../grafana/GrafanaDashboardServiceTest.java       | 27 ++++++++++++++++++++++
 .../ops/alert/AlertRuleAssetServiceTest.java       | 25 ++++++++++++++++++++
 4 files changed, 82 insertions(+), 5 deletions(-)

diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/cluster/metrics/grafana/GrafanaDashboardService.java
 
b/server/src/main/java/org/apache/rocketmq/studio/cluster/metrics/grafana/GrafanaDashboardService.java
index c28bb5888..f20043e1f 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/cluster/metrics/grafana/GrafanaDashboardService.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/cluster/metrics/grafana/GrafanaDashboardService.java
@@ -31,6 +31,9 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.zip.ZipEntry;
@@ -64,7 +67,7 @@ public class GrafanaDashboardService {
      */
     public List<GrafanaDashboardInfo> listDashboards() {
         List<GrafanaDashboardInfo> infos = new ArrayList<>();
-        for (Resource resource : resolveResources()) {
+        for (Resource resource : resolveUniqueResources()) {
             String uid = uidOf(resource);
             if (uid == null) {
                 continue;
@@ -151,7 +154,7 @@ public class GrafanaDashboardService {
     }
 
     private Resource findResource(String uid) {
-        for (Resource resource : resolveResources()) {
+        for (Resource resource : resolveUniqueResources()) {
             if (uid.equals(uidOf(resource))) {
                 return resource;
             }
@@ -159,6 +162,15 @@ public class GrafanaDashboardService {
         return null;
     }
 
+    private List<Resource> resolveUniqueResources() {
+        Map<String, Resource> resourcesByUid = new LinkedHashMap<>();
+        Arrays.stream(resolveResources())
+                .filter(resource -> uidOf(resource) != null)
+                .sorted(Comparator.comparing(Resource::getDescription))
+                .forEach(resource -> 
resourcesByUid.putIfAbsent(uidOf(resource), resource));
+        return new ArrayList<>(resourcesByUid.values());
+    }
+
     protected Resource[] resolveResources() {
         try {
             return resourceResolver.getResources(LOCATION_PATTERN);
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/ops/alert/AlertRuleAssetService.java
 
b/server/src/main/java/org/apache/rocketmq/studio/ops/alert/AlertRuleAssetService.java
index 85e0a5296..749918f5c 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/ops/alert/AlertRuleAssetService.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/ops/alert/AlertRuleAssetService.java
@@ -30,8 +30,12 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 /**
@@ -61,7 +65,7 @@ public class AlertRuleAssetService {
      */
     public List<AlertRuleAssetInfo> listAssets() {
         List<AlertRuleAssetInfo> infos = new ArrayList<>();
-        for (Resource resource : resolveResources()) {
+        for (Resource resource : resolveUniqueResources()) {
             String name = nameOf(resource);
             if (name == null) {
                 continue;
@@ -107,7 +111,7 @@ public class AlertRuleAssetService {
      */
     public List<PrometheusAlertRule> loadDefaultRules() {
         List<PrometheusAlertRule> rules = new ArrayList<>();
-        for (Resource resource : resolveResources()) {
+        for (Resource resource : resolveUniqueResources()) {
             if (nameOf(resource) == null) {
                 continue;
             }
@@ -162,7 +166,7 @@ public class AlertRuleAssetService {
     }
 
     private Resource findResource(String name) {
-        for (Resource resource : resolveResources()) {
+        for (Resource resource : resolveUniqueResources()) {
             if (name.equals(nameOf(resource))) {
                 return resource;
             }
@@ -170,6 +174,15 @@ public class AlertRuleAssetService {
         return null;
     }
 
+    private List<Resource> resolveUniqueResources() {
+        Map<String, Resource> resourcesByName = new LinkedHashMap<>();
+        Arrays.stream(resolveResources())
+                .filter(resource -> nameOf(resource) != null)
+                .sorted(Comparator.comparing(Resource::getDescription))
+                .forEach(resource -> 
resourcesByName.putIfAbsent(nameOf(resource), resource));
+        return new ArrayList<>(resourcesByName.values());
+    }
+
     protected Resource[] resolveResources() {
         try {
             return resourceResolver.getResources(LOCATION_PATTERN);
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/cluster/metrics/grafana/GrafanaDashboardServiceTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/cluster/metrics/grafana/GrafanaDashboardServiceTest.java
index b28644d5f..71554c556 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/cluster/metrics/grafana/GrafanaDashboardServiceTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/cluster/metrics/grafana/GrafanaDashboardServiceTest.java
@@ -161,6 +161,24 @@ class GrafanaDashboardServiceTest {
         assertTrue(exception.getMessage().contains("resolve bundled Grafana 
dashboards"));
     }
 
+    @Test
+    void dashboardOperationsShouldDeterministicallyDeduplicateUid() throws 
Exception {
+        GrafanaDashboardService service = serviceWithResources(
+                resource("duplicate.json", "z-location", 
"{\"title\":\"Second\"}"),
+                resource("duplicate.json", "a-location", 
"{\"title\":\"First\"}"));
+
+        assertEquals(List.of(new GrafanaDashboardInfo("duplicate", "First", 
"", List.of())),
+                service.listDashboards());
+        assertEquals("First", service.getDashboard("duplicate").get("title"));
+
+        try (ZipInputStream zip = new ZipInputStream(
+                new ByteArrayInputStream(service.getDashboardsArchive()), 
StandardCharsets.UTF_8)) {
+            assertEquals("duplicate.json", zip.getNextEntry().getName());
+            assertTrue(new String(zip.readAllBytes(), 
StandardCharsets.UTF_8).contains("First"));
+            assertNull(zip.getNextEntry());
+        }
+    }
+
     private static GrafanaDashboardService serviceWithResources(Resource... 
resources) {
         return new GrafanaDashboardService(new ObjectMapper()) {
             @Override
@@ -171,11 +189,20 @@ class GrafanaDashboardServiceTest {
     }
 
     private static Resource resource(String filename, String content) {
+        return resource(filename, filename, content);
+    }
+
+    private static Resource resource(String filename, String description, 
String content) {
         return new ByteArrayResource(content.getBytes(StandardCharsets.UTF_8)) 
{
             @Override
             public String getFilename() {
                 return filename;
             }
+
+            @Override
+            public String getDescription() {
+                return description;
+            }
         };
     }
 }
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/ops/alert/AlertRuleAssetServiceTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/ops/alert/AlertRuleAssetServiceTest.java
index f60aae581..664bbe800 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/ops/alert/AlertRuleAssetServiceTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/ops/alert/AlertRuleAssetServiceTest.java
@@ -132,6 +132,22 @@ class AlertRuleAssetServiceTest {
                 "warning", "broker", "BrokerDown", "")), 
service.loadDefaultRules());
     }
 
+    @Test
+    void assetOperationsShouldDeterministicallyDeduplicateName() {
+        AlertRuleAssetService service = serviceWithResources(
+                resource("duplicate.yaml", "z-location", "groups:\n  - name: 
second\n    rules:\n"
+                        + "      - alert: SecondRule\n        expr: up == 
2\n"),
+                resource("duplicate.yaml", "a-location", "groups:\n  - name: 
first\n    rules:\n"
+                        + "      - alert: FirstRule\n        expr: up == 
1\n"));
+
+        assertEquals(List.of(new AlertRuleAssetInfo("duplicate", "first", 1, 
List.of("warning"))),
+                service.listAssets());
+        assertEquals(List.of("FirstRule"), service.loadDefaultRules().stream()
+                .map(PrometheusAlertRule::alert)
+                .toList());
+        assertTrue(service.getAssetYaml("duplicate").contains("FirstRule"));
+    }
+
     private static AlertRuleAssetService serviceWithResources(Resource... 
resources) {
         return new AlertRuleAssetService() {
             @Override
@@ -142,11 +158,20 @@ class AlertRuleAssetServiceTest {
     }
 
     private static Resource resource(String filename, String content) {
+        return resource(filename, filename, content);
+    }
+
+    private static Resource resource(String filename, String description, 
String content) {
         return new ByteArrayResource(content.getBytes(StandardCharsets.UTF_8)) 
{
             @Override
             public String getFilename() {
                 return filename;
             }
+
+            @Override
+            public String getDescription() {
+                return description;
+            }
         };
     }
 }

Reply via email to