refactor(alerts): drop ACKNOWLEDGED from AlertState, add readAt/deletedAt to AlertInstance
- AlertState: remove ACKNOWLEDGED case (V17 migration already dropped it from DB enum) - AlertInstance: insert readAt + deletedAt Instant fields after lastNotifiedAt; add withReadAt/withDeletedAt withers; update all existing withers to pass both fields positionally - AlertStateTransitions: add null,null for readAt/deletedAt in newInstance ctor call; collapse FIRING,ACKNOWLEDGED switch arm to just FIRING - AlertScopeTest: update AlertState.values() assertion to 3 values; fix stale ConditionKind.hasSize(6) to 7 (JVM_METRIC was added earlier) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -28,7 +28,7 @@ public final class AlertStateTransitions {
|
||||
/**
|
||||
* Apply an EvalResult to the current open AlertInstance.
|
||||
*
|
||||
* @param current the open instance for this rule (PENDING / FIRING / ACKNOWLEDGED), or null if none
|
||||
* @param current the open instance for this rule (PENDING / FIRING), or null if none
|
||||
* @param result the evaluator outcome
|
||||
* @param rule the rule being evaluated
|
||||
* @param now wall-clock instant for the current tick
|
||||
@@ -50,7 +50,7 @@ public final class AlertStateTransitions {
|
||||
private static Optional<AlertInstance> onClear(AlertInstance current, Instant now) {
|
||||
if (current == null) return Optional.empty(); // no open instance — no-op
|
||||
if (current.state() == AlertState.RESOLVED) return Optional.empty(); // already resolved
|
||||
// Any open state (PENDING / FIRING / ACKNOWLEDGED) → RESOLVED
|
||||
// Any open state (PENDING / FIRING) → RESOLVED
|
||||
return Optional.of(current
|
||||
.withState(AlertState.RESOLVED)
|
||||
.withResolvedAt(now));
|
||||
@@ -84,8 +84,8 @@ public final class AlertStateTransitions {
|
||||
// Still within forDuration — stay PENDING, nothing to persist
|
||||
yield Optional.empty();
|
||||
}
|
||||
// FIRING / ACKNOWLEDGED — re-notification cadence handled by the dispatcher
|
||||
case FIRING, ACKNOWLEDGED -> Optional.empty();
|
||||
// FIRING — re-notification cadence handled by the dispatcher
|
||||
case FIRING -> Optional.empty();
|
||||
// RESOLVED should never appear as the "current open" instance, but guard anyway
|
||||
case RESOLVED -> Optional.empty();
|
||||
};
|
||||
@@ -126,6 +126,8 @@ public final class AlertStateTransitions {
|
||||
null, // ackedBy
|
||||
null, // resolvedAt
|
||||
null, // lastNotifiedAt
|
||||
null, // readAt
|
||||
null, // deletedAt
|
||||
false, // silenced
|
||||
f.currentValue(),
|
||||
f.threshold(),
|
||||
|
||||
@@ -17,6 +17,8 @@ public record AlertInstance(
|
||||
String ackedBy,
|
||||
Instant resolvedAt,
|
||||
Instant lastNotifiedAt,
|
||||
Instant readAt, // NEW — global "someone has seen this"
|
||||
Instant deletedAt, // NEW — soft delete
|
||||
boolean silenced,
|
||||
Double currentValue,
|
||||
Double threshold,
|
||||
@@ -39,63 +41,77 @@ public record AlertInstance(
|
||||
|
||||
public AlertInstance withState(AlertState s) {
|
||||
return new AlertInstance(id, ruleId, ruleSnapshot, environmentId,
|
||||
s, severity, firedAt, ackedAt, ackedBy, resolvedAt, lastNotifiedAt, silenced,
|
||||
s, severity, firedAt, ackedAt, ackedBy, resolvedAt, lastNotifiedAt, readAt, deletedAt, silenced,
|
||||
currentValue, threshold, context, title, message,
|
||||
targetUserIds, targetGroupIds, targetRoleNames);
|
||||
}
|
||||
|
||||
public AlertInstance withFiredAt(Instant i) {
|
||||
return new AlertInstance(id, ruleId, ruleSnapshot, environmentId,
|
||||
state, severity, i, ackedAt, ackedBy, resolvedAt, lastNotifiedAt, silenced,
|
||||
state, severity, i, ackedAt, ackedBy, resolvedAt, lastNotifiedAt, readAt, deletedAt, silenced,
|
||||
currentValue, threshold, context, title, message,
|
||||
targetUserIds, targetGroupIds, targetRoleNames);
|
||||
}
|
||||
|
||||
public AlertInstance withResolvedAt(Instant i) {
|
||||
return new AlertInstance(id, ruleId, ruleSnapshot, environmentId,
|
||||
state, severity, firedAt, ackedAt, ackedBy, i, lastNotifiedAt, silenced,
|
||||
state, severity, firedAt, ackedAt, ackedBy, i, lastNotifiedAt, readAt, deletedAt, silenced,
|
||||
currentValue, threshold, context, title, message,
|
||||
targetUserIds, targetGroupIds, targetRoleNames);
|
||||
}
|
||||
|
||||
public AlertInstance withAck(String ackedBy, Instant ackedAt) {
|
||||
return new AlertInstance(id, ruleId, ruleSnapshot, environmentId,
|
||||
state, severity, firedAt, ackedAt, ackedBy, resolvedAt, lastNotifiedAt, silenced,
|
||||
state, severity, firedAt, ackedAt, ackedBy, resolvedAt, lastNotifiedAt, readAt, deletedAt, silenced,
|
||||
currentValue, threshold, context, title, message,
|
||||
targetUserIds, targetGroupIds, targetRoleNames);
|
||||
}
|
||||
|
||||
public AlertInstance withSilenced(boolean silenced) {
|
||||
return new AlertInstance(id, ruleId, ruleSnapshot, environmentId,
|
||||
state, severity, firedAt, ackedAt, ackedBy, resolvedAt, lastNotifiedAt, silenced,
|
||||
state, severity, firedAt, ackedAt, ackedBy, resolvedAt, lastNotifiedAt, readAt, deletedAt, silenced,
|
||||
currentValue, threshold, context, title, message,
|
||||
targetUserIds, targetGroupIds, targetRoleNames);
|
||||
}
|
||||
|
||||
public AlertInstance withTitleMessage(String title, String message) {
|
||||
return new AlertInstance(id, ruleId, ruleSnapshot, environmentId,
|
||||
state, severity, firedAt, ackedAt, ackedBy, resolvedAt, lastNotifiedAt, silenced,
|
||||
state, severity, firedAt, ackedAt, ackedBy, resolvedAt, lastNotifiedAt, readAt, deletedAt, silenced,
|
||||
currentValue, threshold, context, title, message,
|
||||
targetUserIds, targetGroupIds, targetRoleNames);
|
||||
}
|
||||
|
||||
public AlertInstance withLastNotifiedAt(Instant instant) {
|
||||
return new AlertInstance(id, ruleId, ruleSnapshot, environmentId,
|
||||
state, severity, firedAt, ackedAt, ackedBy, resolvedAt, instant, silenced,
|
||||
state, severity, firedAt, ackedAt, ackedBy, resolvedAt, instant, readAt, deletedAt, silenced,
|
||||
currentValue, threshold, context, title, message,
|
||||
targetUserIds, targetGroupIds, targetRoleNames);
|
||||
}
|
||||
|
||||
public AlertInstance withContext(Map<String, Object> context) {
|
||||
return new AlertInstance(id, ruleId, ruleSnapshot, environmentId,
|
||||
state, severity, firedAt, ackedAt, ackedBy, resolvedAt, lastNotifiedAt, silenced,
|
||||
state, severity, firedAt, ackedAt, ackedBy, resolvedAt, lastNotifiedAt, readAt, deletedAt, silenced,
|
||||
currentValue, threshold, context, title, message,
|
||||
targetUserIds, targetGroupIds, targetRoleNames);
|
||||
}
|
||||
|
||||
public AlertInstance withRuleSnapshot(Map<String, Object> snapshot) {
|
||||
return new AlertInstance(id, ruleId, snapshot, environmentId,
|
||||
state, severity, firedAt, ackedAt, ackedBy, resolvedAt, lastNotifiedAt, silenced,
|
||||
state, severity, firedAt, ackedAt, ackedBy, resolvedAt, lastNotifiedAt, readAt, deletedAt, silenced,
|
||||
currentValue, threshold, context, title, message,
|
||||
targetUserIds, targetGroupIds, targetRoleNames);
|
||||
}
|
||||
|
||||
public AlertInstance withReadAt(Instant i) {
|
||||
return new AlertInstance(id, ruleId, ruleSnapshot, environmentId,
|
||||
state, severity, firedAt, ackedAt, ackedBy, resolvedAt, lastNotifiedAt, i, deletedAt, silenced,
|
||||
currentValue, threshold, context, title, message,
|
||||
targetUserIds, targetGroupIds, targetRoleNames);
|
||||
}
|
||||
|
||||
public AlertInstance withDeletedAt(Instant i) {
|
||||
return new AlertInstance(id, ruleId, ruleSnapshot, environmentId,
|
||||
state, severity, firedAt, ackedAt, ackedBy, resolvedAt, lastNotifiedAt, readAt, i, silenced,
|
||||
currentValue, threshold, context, title, message,
|
||||
targetUserIds, targetGroupIds, targetRoleNames);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
package com.cameleer.server.core.alerting;
|
||||
|
||||
public enum AlertState { PENDING, FIRING, ACKNOWLEDGED, RESOLVED }
|
||||
public enum AlertState { PENDING, FIRING, RESOLVED }
|
||||
|
||||
@@ -23,8 +23,8 @@ class AlertScopeTest {
|
||||
assertThat(AlertSeverity.values()).containsExactly(
|
||||
AlertSeverity.CRITICAL, AlertSeverity.WARNING, AlertSeverity.INFO);
|
||||
assertThat(AlertState.values()).containsExactly(
|
||||
AlertState.PENDING, AlertState.FIRING, AlertState.ACKNOWLEDGED, AlertState.RESOLVED);
|
||||
assertThat(ConditionKind.values()).hasSize(6);
|
||||
AlertState.PENDING, AlertState.FIRING, AlertState.RESOLVED);
|
||||
assertThat(ConditionKind.values()).hasSize(7);
|
||||
assertThat(TargetKind.values()).containsExactly(
|
||||
TargetKind.USER, TargetKind.GROUP, TargetKind.ROLE);
|
||||
assertThat(NotificationStatus.values()).containsExactly(
|
||||
|
||||
Reference in New Issue
Block a user