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 29a7d4abe fix(alert): audit exhausted deliveries with the shared 
FAILED vocabulary (#4657)
29a7d4abe is described below

commit 29a7d4abeac8193ce786d10a8601f9e59a51871e
Author: 烤化の初雪 <[email protected]>
AuthorDate: Mon Sep 21 20:18:59 2026 +0800

    fix(alert): audit exhausted deliveries with the shared FAILED vocabulary 
(#4657)
    
    `NotificationOutboxService` audited the retry-exhausted branch with the 
literal `FAILURE`, while every other audit writer in the repo uses `FAILED`. 
`MybatisPlusAuditRepository` counts the row into the total but not into any 
result bucket, so exhausted notification deliveries silently disappeared from 
the per-result breakdown. The branch now uses the shared `FAILED` vocabulary, 
and the legacy value stays readable for rows already stored.
    
    Note: `"RETRYING"` in the same file has the same total-vs-bucket gap and is 
left for a follow-up, since fixing it means deciding the bucket vocabulary 
rather than renaming one literal.
---
 .../ops/alert/NotificationOutboxService.java       |  4 ++-
 .../ops/alert/NotificationOutboxServiceTest.java   | 38 ++++++++++++++++++++++
 2 files changed, 41 insertions(+), 1 deletion(-)

diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/ops/alert/NotificationOutboxService.java
 
b/server/src/main/java/org/apache/rocketmq/studio/ops/alert/NotificationOutboxService.java
index b94fe069f..ae48d1f37 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/ops/alert/NotificationOutboxService.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/ops/alert/NotificationOutboxService.java
@@ -516,8 +516,10 @@ public class NotificationOutboxService {
                 .set("last_error", abbreviate(error)).set("claim_token", 
null))) {
             return;
         }
+        // "FAILURE" is outside the shared audit result vocabulary 
(SUCCESS/FAILED/PARTIAL), so
+        // exhausted deliveries never landed in the audit summary's failed 
bucket.
         recordDeliverySafely(row, exhausted ? "FAIL_ALERT_NOTIFICATION" : 
"RETRY_ALERT_NOTIFICATION",
-                exhausted ? "FAILURE" : "RETRYING", abbreviate(error));
+                exhausted ? "FAILED" : "RETRYING", abbreviate(error));
         log.warn("Alert notification {} for event {}: {}", exhausted ? 
"failed" : "will retry", row.getAlertId(), error);
     }
 
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/ops/alert/NotificationOutboxServiceTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/ops/alert/NotificationOutboxServiceTest.java
index 541d5b2d2..3b949ddd6 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/ops/alert/NotificationOutboxServiceTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/ops/alert/NotificationOutboxServiceTest.java
@@ -716,6 +716,44 @@ class NotificationOutboxServiceTest {
         verify(heartbeatFuture).cancel(false);
     }
 
+    @Test
+    void auditsAnExhaustedDeliveryWithTheSharedFailedVocabularyTest() {
+        RmqAlertNotificationOutboxMapper mapper = 
mock(RmqAlertNotificationOutboxMapper.class);
+        SettingsRepository settings = mock(SettingsRepository.class);
+        AlertRepository alerts = mock(AlertRepository.class);
+        OperationAuditService audit = mock(OperationAuditService.class);
+        RmqAlertNotificationOutbox row = new RmqAlertNotificationOutbox();
+        row.setId(8L);
+        row.setAlertId(9L);
+        row.setChannel("dingtalk");
+        row.setStatus("RETRY_WAIT");
+        // The last allowed attempt: the row enters the terminal FAILED state 
after this dispatch.
+        row.setAttemptCount(4);
+        when(mapper.findDispatchable(any(LocalDateTime.class), 
any(LocalDateTime.class), any(Integer.class)))
+                .thenReturn(List.of(row));
+        when(mapper.claimForDispatch(any(), any(LocalDateTime.class), 
any(LocalDateTime.class),
+                any(LocalDateTime.class), anyString())).thenReturn(1);
+        when(mapper.update(any(), any())).thenReturn(1);
+        
when(alerts.findAlertById(9L)).thenReturn(Optional.of(SystemAlertVO.builder().id(9L)
+                
.level(AlertLevel.warning).title("Lag").description("high").instanceId("local").build()));
+        
when(settings.loadGeneralSettings()).thenReturn(GeneralSettingsVO.builder()
+                .dingtalkWebhook("https://example.com/hook";).build());
+
+        RestTemplate client = new RestTemplate();
+        MockRestServiceServer server = 
MockRestServiceServer.bindTo(client).build();
+        server.expect(once(), requestTo("https://example.com/hook";))
+                
.andRespond(withSuccess("{\"errcode\":310000,\"errmsg\":\"keywords not in 
content\"}",
+                        MediaType.APPLICATION_JSON));
+
+        NotificationOutboxService service = new 
NotificationOutboxService(mapper, settings,
+                mock(AlertSilenceService.class), alerts, audit, client);
+        service.dispatch();
+
+        server.verify();
+        verify(audit).record("FAIL_ALERT_NOTIFICATION", "ALERT_NOTIFICATION", 
"8", null,
+                "alertId=9, channel=dingtalk", "FAILED", "DingTalk rejected 
webhook: keywords not in content");
+    }
+
     @Test
     void doesNotRetryWhenDeliveryStateWriteFailsAfterExternalSuccessTest() {
         RmqAlertNotificationOutboxMapper mapper = 
mock(RmqAlertNotificationOutboxMapper.class);

Reply via email to