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 10b43e1f0 fix(alerts): omit reminder timestamps for non-reminding
states (#5907)
10b43e1f0 is described below
commit 10b43e1f0150b9747209116b7fe31db7a2effc53
Author: 健康宝 <[email protected]>
AuthorDate: Mon Oct 12 15:19:50 2026 +0800
fix(alerts): omit reminder timestamps for non-reminding states (#5907)
`MybatisPlusAlertStateRepository.toRuntime` projected `nextReminderAt` for
any state that had ever notified (`:135-136`), while `AlertStateMachine` only
emits REMINDER while the state is FIRING with a non-zero interval — the ACKED
branch returns `NONE` outright (`AlertStateMachine.java:67-70`) and a RESOLVED
state's `lastNotifiedAt` is purely historical. A rule with reminders switched
off was worst off: `AlertRuleDuration.parse` returns `Duration.ZERO` for a
blank or `0s` interval (`: [...]
`runtimeReminderIsProjectedOnlyForRemindingStatesTest` asserts all four
rows in one `containsExactly`: ACKED, RESOLVED and FIRING-with-`0s` must be
null, FIRING-with-`30m` must be `lastNotifiedAt + 30m`. Reverting the
projection turns the three nulls back into timestamps; the fourth row is the
anchor that fails if the projection is instead widened to always return null.
---
.../ops/alert/MybatisPlusAlertStateRepository.java | 14 +++++++++--
.../alert/MybatisPlusAlertStateRepositoryTest.java | 29 ++++++++++++++++++++++
2 files changed, 41 insertions(+), 2 deletions(-)
diff --git
a/server/src/main/java/org/apache/rocketmq/studio/ops/alert/MybatisPlusAlertStateRepository.java
b/server/src/main/java/org/apache/rocketmq/studio/ops/alert/MybatisPlusAlertStateRepository.java
index 3d07e690d..bc5a99181 100644
---
a/server/src/main/java/org/apache/rocketmq/studio/ops/alert/MybatisPlusAlertStateRepository.java
+++
b/server/src/main/java/org/apache/rocketmq/studio/ops/alert/MybatisPlusAlertStateRepository.java
@@ -29,6 +29,7 @@ import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Repository;
import org.springframework.util.StringUtils;
+import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
@@ -132,8 +133,17 @@ public class MybatisPlusAlertStateRepository implements
AlertStateRepository {
}
private static AlertRuleRuntimeVO toRuntime(RmqAlertState entity,
AlertRuleVO rule) {
- LocalDateTime next = entity.getLastNotifiedAt() == null || rule ==
null ? null
- :
entity.getLastNotifiedAt().plus(AlertRuleDuration.parse(rule.getReminderInterval()));
+ // The state machine only emits REMINDER while FIRING with a positive
interval; ACKED and
+ // RESOLVED states keep lastNotifiedAt without ever reminding again.
Report nextReminderAt
+ // only for those states, instead of fabricating one for every state
that ever notified.
+ LocalDateTime next = null;
+ if (rule != null && entity.getLastNotifiedAt() != null
+ && AlertStateStatus.FIRING.name().equals(entity.getStatus())) {
+ Duration interval =
AlertRuleDuration.parse(rule.getReminderInterval());
+ if (!interval.isZero() && !interval.isNegative()) {
+ next = entity.getLastNotifiedAt().plus(interval);
+ }
+ }
return
AlertRuleRuntimeVO.builder().ruleId(entity.getRuleId()).fingerprint(entity.getFingerprint())
.status(AlertStateStatus.valueOf(entity.getStatus())).consecutiveHits(entity.getConsecutiveHits()
== null ? 0 : entity.getConsecutiveHits())
.currentValue(entity.getCurrentValue()).lastNotifiedAt(entity.getLastNotifiedAt()).nextReminderAt(next).build();
diff --git
a/server/src/test/java/org/apache/rocketmq/studio/ops/alert/MybatisPlusAlertStateRepositoryTest.java
b/server/src/test/java/org/apache/rocketmq/studio/ops/alert/MybatisPlusAlertStateRepositoryTest.java
index 086a40b9b..a13f635f7 100644
---
a/server/src/test/java/org/apache/rocketmq/studio/ops/alert/MybatisPlusAlertStateRepositoryTest.java
+++
b/server/src/test/java/org/apache/rocketmq/studio/ops/alert/MybatisPlusAlertStateRepositoryTest.java
@@ -182,6 +182,35 @@ class MybatisPlusAlertStateRepositoryTest {
verify(alertMapper, never()).selectOne(any());
}
+ @Test
+ void runtimeReminderIsProjectedOnlyForRemindingStatesTest() {
+ RmqAlertStateMapper mapper = mock(RmqAlertStateMapper.class);
+ LocalDateTime notifiedAt = LocalDateTime.of(2026, 10, 1, 12, 0);
+ RmqAlertState acked = activeState(1L, "f1", AlertStateStatus.ACKED);
+ acked.setLastNotifiedAt(notifiedAt);
+ RmqAlertState resolved = activeState(1L, "f2",
AlertStateStatus.RESOLVED);
+ resolved.setLastNotifiedAt(notifiedAt);
+ RmqAlertState firingWithoutReminder = activeState(2L, "f3",
AlertStateStatus.FIRING);
+ firingWithoutReminder.setLastNotifiedAt(notifiedAt);
+ RmqAlertState firing = activeState(1L, "f4", AlertStateStatus.FIRING);
+ firing.setLastNotifiedAt(notifiedAt);
+ when(mapper.selectList(any()))
+ .thenReturn(List.of(acked, resolved, firingWithoutReminder,
firing));
+ MybatisPlusAlertStateRepository repository = new
MybatisPlusAlertStateRepository(mapper,
+ mock(RmqSystemAlertMapper.class));
+
+ List<AlertRuleRuntimeVO> runtimes =
repository.findRuntimeByRuleIds(List.of(
+ AlertRuleVO.builder().id(1L).reminderInterval("30m").build(),
+ AlertRuleVO.builder().id(2L).reminderInterval("0s").build()));
+
+ assertThat(runtimes).extracting(AlertRuleRuntimeVO::getFingerprint,
AlertRuleRuntimeVO::getNextReminderAt)
+ .containsExactly(
+ org.assertj.core.groups.Tuple.tuple("f1", null),
+ org.assertj.core.groups.Tuple.tuple("f2", null),
+ org.assertj.core.groups.Tuple.tuple("f3", null),
+ org.assertj.core.groups.Tuple.tuple("f4",
notifiedAt.plusMinutes(30)));
+ }
+
private static RmqAlertState activeState(Long ruleId, String fingerprint,
AlertStateStatus status) {
RmqAlertState state = new RmqAlertState();
state.setRuleId(ruleId);