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
commit 8bbddf73d1ed4a51ce0288eb5028d6aac165d85c Author: yyqdbngt <[email protected]> AuthorDate: Mon Aug 10 11:52:37 2026 +0800 fix: batch of small backend validation and export fixes (#1103, #1105, #1233, #1241, #1242) * fix(lite-topic): return 400 for invalid extendTTL input * fix(grafana): use attachment disposition type for dashboard export * fix(metrics): pass through upstream 504 and guard null result elements * fix(topic): require newTTL on TTL update * fix(metrics): validate nested query in datasource queries --------- --- .../metrics/AbstractPrometheusCompatibleMetricsSource.java | 12 ++++++++++-- .../cluster/metrics/grafana/GrafanaDashboardController.java | 4 +++- .../rocketmq/studio/instance/topic/LiteTopicService.java | 4 ++-- .../studio/instance/topic/LiteTopicTTLUpdateDTO.java | 2 ++ .../studio/model/request/MetricsDataSourceQueryRequest.java | 4 ++++ .../metrics/grafana/GrafanaDashboardControllerTest.java | 2 +- .../rocketmq/studio/instance/topic/LiteTopicServiceTest.java | 10 ++++++---- 7 files changed, 28 insertions(+), 10 deletions(-) diff --git a/server/src/main/java/org/apache/rocketmq/studio/cluster/metrics/AbstractPrometheusCompatibleMetricsSource.java b/server/src/main/java/org/apache/rocketmq/studio/cluster/metrics/AbstractPrometheusCompatibleMetricsSource.java index 3af76c5a..a0ce125d 100644 --- a/server/src/main/java/org/apache/rocketmq/studio/cluster/metrics/AbstractPrometheusCompatibleMetricsSource.java +++ b/server/src/main/java/org/apache/rocketmq/studio/cluster/metrics/AbstractPrometheusCompatibleMetricsSource.java @@ -211,6 +211,10 @@ public abstract class AbstractPrometheusCompatibleMetricsSource implements Metri } private MetricDataVO.MetricSeriesVO parseSeries(JsonNode seriesNode) { + if (seriesNode == null) { + throw new PrometheusException(HttpStatus.BAD_GATEWAY.value(), + backendLabel() + " returned a malformed time series"); + } JsonNode metric = seriesNode.path("metric"); JsonNode values = seriesNode.path("values"); JsonNode histograms = seriesNode.path("histograms"); @@ -254,7 +258,8 @@ public abstract class AbstractPrometheusCompatibleMetricsSource implements Metri } private MetricDataVO.MetricSampleVO parseSample(JsonNode sampleNode) { - if (!sampleNode.isArray() || sampleNode.size() != 2 || !sampleNode.get(0).isNumber()) { + if (sampleNode == null || !sampleNode.isArray() || sampleNode.size() != 2 + || !sampleNode.get(0).isNumber()) { throw new PrometheusException(HttpStatus.BAD_GATEWAY.value(), backendLabel() + " returned a malformed sample"); } @@ -307,6 +312,9 @@ public abstract class AbstractPrometheusCompatibleMetricsSource implements Metri } long totalSamples = 0; for (JsonNode series : result) { + if (series == null) { + continue; + } totalSamples += series.path("values").size(); totalSamples += series.path("histograms").size(); if (totalSamples > MAX_TOTAL_SAMPLES) { @@ -319,7 +327,7 @@ public abstract class AbstractPrometheusCompatibleMetricsSource implements Metri private int responseStatus(HttpStatusCode statusCode) { int upstreamStatus = statusCode.value(); return switch (upstreamStatus) { - case 400, 422, 503 -> upstreamStatus; + case 400, 422, 503, 504 -> upstreamStatus; default -> HttpStatus.BAD_GATEWAY.value(); }; } diff --git a/server/src/main/java/org/apache/rocketmq/studio/cluster/metrics/grafana/GrafanaDashboardController.java b/server/src/main/java/org/apache/rocketmq/studio/cluster/metrics/grafana/GrafanaDashboardController.java index fd8c9197..c288e86b 100644 --- a/server/src/main/java/org/apache/rocketmq/studio/cluster/metrics/grafana/GrafanaDashboardController.java +++ b/server/src/main/java/org/apache/rocketmq/studio/cluster/metrics/grafana/GrafanaDashboardController.java @@ -20,6 +20,7 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import lombok.RequiredArgsConstructor; import org.apache.rocketmq.studio.common.domain.Result; +import org.springframework.http.ContentDisposition; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; @@ -66,7 +67,8 @@ public class GrafanaDashboardController { String json = grafanaDashboardService.getDashboardJson(uid); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); - headers.setContentDispositionFormData("attachment", uid + ".json"); + headers.setContentDisposition( + ContentDisposition.attachment().filename(uid + ".json").build()); return new ResponseEntity<>(json.getBytes(StandardCharsets.UTF_8), headers, HttpStatus.OK); } } 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 39040ae6..6d70fb4c 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 @@ -37,10 +37,10 @@ public class LiteTopicService { public void extendTTL(String topicPattern, Long newTTL) { if (topicPattern == null || topicPattern.isBlank()) { - throw new IllegalArgumentException("topicPattern is required"); + throw new BusinessException(400, "topicPattern is required"); } if (newTTL == null || newTTL <= 0) { - throw new IllegalArgumentException("newTTL must be positive"); + throw new BusinessException(400, "newTTL must be positive"); } throw new BusinessException(NOT_IMPLEMENTED, PROVIDER_UNAVAILABLE_MESSAGE); } diff --git a/server/src/main/java/org/apache/rocketmq/studio/instance/topic/LiteTopicTTLUpdateDTO.java b/server/src/main/java/org/apache/rocketmq/studio/instance/topic/LiteTopicTTLUpdateDTO.java index 206948f4..2e37c68e 100644 --- a/server/src/main/java/org/apache/rocketmq/studio/instance/topic/LiteTopicTTLUpdateDTO.java +++ b/server/src/main/java/org/apache/rocketmq/studio/instance/topic/LiteTopicTTLUpdateDTO.java @@ -18,6 +18,7 @@ package org.apache.rocketmq.studio.instance.topic; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Positive; import lombok.Data; @@ -25,6 +26,7 @@ import lombok.Data; public class LiteTopicTTLUpdateDTO { @NotBlank(message = "topicPattern is required") private String topicPattern; + @NotNull(message = "newTTL is required") @Positive(message = "newTTL must be positive") private Long newTTL; } diff --git a/server/src/main/java/org/apache/rocketmq/studio/model/request/MetricsDataSourceQueryRequest.java b/server/src/main/java/org/apache/rocketmq/studio/model/request/MetricsDataSourceQueryRequest.java index 5c4468a0..d910af80 100644 --- a/server/src/main/java/org/apache/rocketmq/studio/model/request/MetricsDataSourceQueryRequest.java +++ b/server/src/main/java/org/apache/rocketmq/studio/model/request/MetricsDataSourceQueryRequest.java @@ -16,6 +16,8 @@ */ package org.apache.rocketmq.studio.model.request; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; import org.apache.rocketmq.studio.cluster.metrics.MetricQueryDTO; import lombok.Data; @@ -30,6 +32,8 @@ import lombok.Data; @Data public class MetricsDataSourceQueryRequest { + @Valid + @NotNull(message = "query is required") private MetricQueryDTO query; private String username; diff --git a/server/src/test/java/org/apache/rocketmq/studio/cluster/metrics/grafana/GrafanaDashboardControllerTest.java b/server/src/test/java/org/apache/rocketmq/studio/cluster/metrics/grafana/GrafanaDashboardControllerTest.java index 09e828b8..e97c4781 100644 --- a/server/src/test/java/org/apache/rocketmq/studio/cluster/metrics/grafana/GrafanaDashboardControllerTest.java +++ b/server/src/test/java/org/apache/rocketmq/studio/cluster/metrics/grafana/GrafanaDashboardControllerTest.java @@ -81,7 +81,7 @@ class GrafanaDashboardControllerTest { .andExpect(status().isOk()) .andExpect(header().string("Content-Type", "application/json")) .andExpect(header().string("Content-Disposition", - "form-data; name=\"attachment\"; filename=\"rocketmq-overview.json\"")) + "attachment; filename=\"rocketmq-overview.json\"")) .andExpect(content().json("{\"uid\":\"rocketmq-overview\"}")); } } 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 1c921338..e1d6baf8 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 @@ -57,11 +57,13 @@ class LiteTopicServiceTest { @Test void extendTTLShouldRejectInvalidInput() { assertThatThrownBy(() -> liteTopicService.extendTTL("", 1L)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("topicPattern is required"); + .isInstanceOf(BusinessException.class) + .hasMessage("topicPattern is required") + .satisfies(ex -> assertThat(((BusinessException) ex).getCode()).isEqualTo(400)); assertThatThrownBy(() -> liteTopicService.extendTTL("chat/{sessionId}", 0L)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("newTTL must be positive"); + .isInstanceOf(BusinessException.class) + .hasMessage("newTTL must be positive") + .satisfies(ex -> assertThat(((BusinessException) ex).getCode()).isEqualTo(400)); } @Test
