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 2a428b84 fix: fail closed when LiteTopic is unavailable (#932)
2a428b84 is described below

commit 2a428b84535a12615aa3c422ff801d239f45638f
Author: aias00 <[email protected]>
AuthorDate: Tue Aug 4 03:05:02 2026 -0700

    fix: fail closed when LiteTopic is unavailable (#932)
    
    * [ISSUE #793] Stop returning sample LiteTopic data
    
    * [ISSUE #795] Fail closed on LiteTopic capability errors
---
 .../studio/instance/topic/LiteTopicService.java    | 90 ++--------------------
 .../instance/topic/LiteTopicControllerTest.java    | 19 +++++
 .../instance/topic/LiteTopicServiceTest.java       | 50 +++++++-----
 web/src/pages/studio/LiteTopic.tsx                 |  7 +-
 web/src/pages/studio/__tests__/LiteTopic.test.tsx  | 12 +++
 5 files changed, 74 insertions(+), 104 deletions(-)

diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/instance/topic/LiteTopicService.java
 
b/server/src/main/java/org/apache/rocketmq/studio/instance/topic/LiteTopicService.java
index af8129fd..f7715617 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/instance/topic/LiteTopicService.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/instance/topic/LiteTopicService.java
@@ -17,43 +17,22 @@
 
 package org.apache.rocketmq.studio.instance.topic;
 
+import org.apache.rocketmq.studio.common.exception.BusinessException;
 import org.springframework.stereotype.Service;
 
 import java.util.List;
-import java.util.Locale;
 
 @Service
 public class LiteTopicService {
+    private static final String PROVIDER_UNAVAILABLE_MESSAGE = "LiteTopic 
provider integration is not available";
+    private static final int NOT_IMPLEMENTED = 501;
 
     public List<LiteTopicItemVO> listLiteTopics(String pattern, String 
namespace) {
-        return sampleItems().stream()
-                .filter(item -> matchesPattern(pattern, 
item.getTopicPattern()))
-                .filter(item -> matchesNamespace(namespace, 
item.getNamespace()))
-                .toList();
+        return List.of();
     }
 
     public LiteTopicSessionVO getSession(String sessionId) {
-        long now = System.currentTimeMillis();
-        return LiteTopicSessionVO.builder()
-                .sessionId(sessionId)
-                .clientId("grpc-client-" + sessionId)
-                .clientAddress("192.168.1.10:8081")
-                .parentTopic("chat/{sessionId}")
-                .consumerGroup("cg-chat-session")
-                .createTime(now - 3_600_000L)
-                .lastActiveTime(now - 30_000L)
-                .ttl(3_600_000L)
-                .ttlRemaining(1_800_000L)
-                .status("ACTIVE")
-                .totalMessages(5_000L)
-                .consumedMessages(4_800L)
-                .pendingMessages(200L)
-                .popProgress(96)
-                .liteTopicCreationCount(2)
-                .liteTopics(List.of(
-                        new LiteTopicSessionVO.SessionLiteTopic("chat/" + 
sessionId, "ACTIVE", 1_800_000L),
-                        new LiteTopicSessionVO.SessionLiteTopic("agent/" + 
sessionId, "ACTIVE", 1_200_000L)))
-                .build();
+        throw new BusinessException(NOT_IMPLEMENTED, 
PROVIDER_UNAVAILABLE_MESSAGE);
     }
 
     public void extendTTL(String topicPattern, Long newTTL) {
@@ -63,67 +42,14 @@ public class LiteTopicService {
         if (newTTL == null || newTTL <= 0) {
             throw new IllegalArgumentException("newTTL must be positive");
         }
+        throw new BusinessException(NOT_IMPLEMENTED, 
PROVIDER_UNAVAILABLE_MESSAGE);
     }
 
     public LiteTopicQuotaVO getQuota(String namespace) {
-        return LiteTopicQuotaVO.builder()
-                .currentTopicCount(128)
-                .maxTopicCount(1_000_000)
-                .currentSessionCount(32)
-                .maxSessionCount(100_000)
-                .currentCreationRate(12)
-                .maxCreationRate(1_000)
-                .usageRate(0.000128)
-                .sessionUsageRate(0.00032)
-                .defaultTTL(3_600_000L)
-                .maxTTL(86_400_000L)
-                .remainingQuota(999_872)
-                .consumerDensity(0.25)
-                .build();
+        throw new BusinessException(NOT_IMPLEMENTED, 
PROVIDER_UNAVAILABLE_MESSAGE);
     }
 
     public LiteTopicCapabilityVO getCapability() {
-        return new LiteTopicCapabilityVO(true);
-    }
-
-    private List<LiteTopicItemVO> sampleItems() {
-        long now = System.currentTimeMillis();
-        return List.of(
-                LiteTopicItemVO.builder()
-                        .topicPattern("chat/{sessionId}")
-                        .namespace("default")
-                        .topicCount(96)
-                        .consumerCount(12)
-                        .totalBacklog(1_200L)
-                        .averageTTL(3_600_000L)
-                        .ttlStatus("ACTIVE")
-                        .lastActiveTime(now - 60_000L)
-                        .sessionIds(List.of("sess-001", "sess-002"))
-                        .build(),
-                LiteTopicItemVO.builder()
-                        .topicPattern("agent/{sessionId}")
-                        .namespace("ai")
-                        .topicCount(32)
-                        .consumerCount(4)
-                        .totalBacklog(180L)
-                        .averageTTL(1_800_000L)
-                        .ttlStatus("EXPIRING_SOON")
-                        .lastActiveTime(now - 120_000L)
-                        .sessionIds(List.of("agent-001"))
-                        .build());
-    }
-
-    private boolean matchesPattern(String filter, String value) {
-        if (filter == null || filter.isBlank()) {
-            return true;
-        }
-        return value != null && 
value.toLowerCase(Locale.ROOT).contains(filter.toLowerCase(Locale.ROOT));
-    }
-
-    private boolean matchesNamespace(String namespace, String value) {
-        if (namespace == null || namespace.isBlank()) {
-            return true;
-        }
-        return value != null && value.equalsIgnoreCase(namespace.trim());
+        return new LiteTopicCapabilityVO(false);
     }
 }
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/instance/topic/LiteTopicControllerTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/instance/topic/LiteTopicControllerTest.java
index 8c1609f4..c58d6e31 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/instance/topic/LiteTopicControllerTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/instance/topic/LiteTopicControllerTest.java
@@ -18,6 +18,7 @@
 package org.apache.rocketmq.studio.instance.topic;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.rocketmq.studio.common.exception.BusinessException;
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 import 
org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
@@ -29,6 +30,7 @@ import org.springframework.test.web.servlet.MockMvc;
 import java.util.List;
 
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoInteractions;
 import static org.mockito.Mockito.when;
@@ -126,6 +128,23 @@ class LiteTopicControllerTest {
         verify(liteTopicService).extendTTL(eq("chat/{sessionId}"), 
eq(7_200_000L));
     }
 
+    @Test
+    void extendTTLShouldReturnUnsupportedWhenProviderIsUnavailable() throws 
Exception {
+        LiteTopicTTLUpdateDTO request = new LiteTopicTTLUpdateDTO();
+        request.setTopicPattern("chat/{sessionId}");
+        request.setNewTTL(7_200_000L);
+        doThrow(new BusinessException(501, "LiteTopic provider integration is 
not available"))
+                .when(liteTopicService)
+                .extendTTL(eq("chat/{sessionId}"), eq(7_200_000L));
+
+        mockMvc.perform(post("/api/liteTopic/extendTTL")
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content(objectMapper.writeValueAsString(request)))
+                .andExpect(status().isNotImplemented())
+                .andExpect(jsonPath("$.code").value(501))
+                .andExpect(jsonPath("$.message").value("LiteTopic provider 
integration is not available"));
+    }
+
     @Test
     void extendTTLShouldRejectMissingTopicPattern() throws Exception {
         LiteTopicTTLUpdateDTO request = new LiteTopicTTLUpdateDTO();
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/instance/topic/LiteTopicServiceTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/instance/topic/LiteTopicServiceTest.java
index d2677d11..a6aafd50 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/instance/topic/LiteTopicServiceTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/instance/topic/LiteTopicServiceTest.java
@@ -17,6 +17,7 @@
 
 package org.apache.rocketmq.studio.instance.topic;
 
+import org.apache.rocketmq.studio.common.exception.BusinessException;
 import org.junit.jupiter.api.Test;
 
 import java.util.List;
@@ -29,35 +30,28 @@ class LiteTopicServiceTest {
     private final LiteTopicService liteTopicService = new LiteTopicService();
 
     @Test
-    void listLiteTopicsShouldFilterByPatternAndNamespace() {
+    void listLiteTopicsShouldNotReturnSampleData() {
         List<LiteTopicItemVO> result = liteTopicService.listLiteTopics("hat", 
" DEFAULT ");
 
-        assertThat(result).hasSize(1);
-        
assertThat(result.get(0).getTopicPattern()).isEqualTo("chat/{sessionId}");
-        assertThat(result.get(0).getNamespace()).isEqualTo("default");
+        assertThat(result).isEmpty();
     }
 
     @Test
-    void listLiteTopicsShouldMatchNamespaceExactly() {
-        assertThat(liteTopicService.listLiteTopics(null, "def")).isEmpty();
-        assertThat(liteTopicService.listLiteTopics(null, "  ")).hasSize(2);
+    void getQuotaShouldReturnUnsupportedWhenProviderIsUnavailable() {
+        assertThatThrownBy(() -> liteTopicService.getQuota("default"))
+                .isInstanceOfSatisfying(BusinessException.class, ex -> {
+                    assertThat(ex.getCode()).isEqualTo(501);
+                    assertThat(ex.getMessage()).isEqualTo("LiteTopic provider 
integration is not available");
+                });
     }
 
     @Test
-    void getQuotaShouldReturnDefaultLimits() {
-        LiteTopicQuotaVO quota = liteTopicService.getQuota("default");
-
-        assertThat(quota.getCurrentTopicCount()).isEqualTo(128);
-        assertThat(quota.getMaxTopicCount()).isEqualTo(1_000_000);
-        assertThat(quota.getRemainingQuota()).isEqualTo(999_872);
-    }
-
-    @Test
-    void getSessionShouldReturnRequestedSessionId() {
-        LiteTopicSessionVO session = liteTopicService.getSession("sess-001");
-
-        assertThat(session.getSessionId()).isEqualTo("sess-001");
-        
assertThat(session.getLiteTopics()).extracting("topicName").contains("chat/sess-001");
+    void getSessionShouldReturnUnsupportedWhenProviderIsUnavailable() {
+        assertThatThrownBy(() -> liteTopicService.getSession("sess-001"))
+                .isInstanceOfSatisfying(BusinessException.class, ex -> {
+                    assertThat(ex.getCode()).isEqualTo(501);
+                    assertThat(ex.getMessage()).isEqualTo("LiteTopic provider 
integration is not available");
+                });
     }
 
     @Test
@@ -69,4 +63,18 @@ class LiteTopicServiceTest {
                 .isInstanceOf(IllegalArgumentException.class)
                 .hasMessage("newTTL must be positive");
     }
+
+    @Test
+    void extendTTLShouldReturnUnsupportedWhenProviderIsUnavailable() {
+        assertThatThrownBy(() -> 
liteTopicService.extendTTL("chat/{sessionId}", 7_200_000L))
+                .isInstanceOfSatisfying(BusinessException.class, ex -> {
+                    assertThat(ex.getCode()).isEqualTo(501);
+                    assertThat(ex.getMessage()).isEqualTo("LiteTopic provider 
integration is not available");
+                });
+    }
+
+    @Test
+    void getCapabilityShouldReportUnsupportedByDefault() {
+        assertThat(liteTopicService.getCapability().isSupported()).isFalse();
+    }
 }
diff --git a/web/src/pages/studio/LiteTopic.tsx 
b/web/src/pages/studio/LiteTopic.tsx
index 90f765b9..332f5620 100644
--- a/web/src/pages/studio/LiteTopic.tsx
+++ b/web/src/pages/studio/LiteTopic.tsx
@@ -204,7 +204,12 @@ const LiteTopicPage: React.FC = () => {
         }
       } catch {
         if (!bootstrapIsActive()) return;
-        // Assume supported when capability detection is unavailable.
+        ++displayCounter.current;
+        setCapabilitySupported(false);
+        setTopicList([]);
+        setQuota(null);
+        setLoading(false);
+        return;
       }
 
       if (displayCounter.current === initialDisplayRequestId) {
diff --git a/web/src/pages/studio/__tests__/LiteTopic.test.tsx 
b/web/src/pages/studio/__tests__/LiteTopic.test.tsx
index 905692ec..7113e8fa 100644
--- a/web/src/pages/studio/__tests__/LiteTopic.test.tsx
+++ b/web/src/pages/studio/__tests__/LiteTopic.test.tsx
@@ -328,6 +328,18 @@ describe('LiteTopic Page', () => {
     expect(screen.queryByText('late-result-*')).not.toBeInTheDocument();
   });
 
+  it('fails closed when capability detection is unavailable', async () => {
+    apiMocks.queryLiteTopicCapability.mockRejectedValue(new Error('capability 
unavailable'));
+
+    renderPage();
+
+    expect(
+      await screen.findByText('当前集群不支持 LiteTopic,请升级到 RocketMQ 5.x'),
+    ).toBeInTheDocument();
+    expect(apiMocks.queryLiteTopicList).not.toHaveBeenCalled();
+    expect(apiMocks.queryLiteTopicQuota).not.toHaveBeenCalled();
+  });
+
   it('ignores stale list and quota responses when namespaces change quickly', 
async () => {
     const alphaList = createDeferred<LiteTopicItem[]>();
     const betaList = createDeferred<LiteTopicItem[]>();

Reply via email to