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);