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.
|
* 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 result the evaluator outcome
|
||||||
* @param rule the rule being evaluated
|
* @param rule the rule being evaluated
|
||||||
* @param now wall-clock instant for the current tick
|
* @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) {
|
private static Optional<AlertInstance> onClear(AlertInstance current, Instant now) {
|
||||||
if (current == null) return Optional.empty(); // no open instance — no-op
|
if (current == null) return Optional.empty(); // no open instance — no-op
|
||||||
if (current.state() == AlertState.RESOLVED) return Optional.empty(); // already resolved
|
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
|
return Optional.of(current
|
||||||
.withState(AlertState.RESOLVED)
|
.withState(AlertState.RESOLVED)
|
||||||
.withResolvedAt(now));
|
.withResolvedAt(now));
|
||||||
@@ -84,8 +84,8 @@ public final class AlertStateTransitions {
|
|||||||
// Still within forDuration — stay PENDING, nothing to persist
|
// Still within forDuration — stay PENDING, nothing to persist
|
||||||
yield Optional.empty();
|
yield Optional.empty();
|
||||||
}
|
}
|
||||||
// FIRING / ACKNOWLEDGED — re-notification cadence handled by the dispatcher
|
// FIRING — re-notification cadence handled by the dispatcher
|
||||||
case FIRING, ACKNOWLEDGED -> Optional.empty();
|
case FIRING -> Optional.empty();
|
||||||
// RESOLVED should never appear as the "current open" instance, but guard anyway
|
// RESOLVED should never appear as the "current open" instance, but guard anyway
|
||||||
case RESOLVED -> Optional.empty();
|
case RESOLVED -> Optional.empty();
|
||||||
};
|
};
|
||||||
@@ -126,6 +126,8 @@ public final class AlertStateTransitions {
|
|||||||
null, // ackedBy
|
null, // ackedBy
|
||||||
null, // resolvedAt
|
null, // resolvedAt
|
||||||
null, // lastNotifiedAt
|
null, // lastNotifiedAt
|
||||||
|
null, // readAt
|
||||||
|
null, // deletedAt
|
||||||
false, // silenced
|
false, // silenced
|
||||||
f.currentValue(),
|
f.currentValue(),
|
||||||
f.threshold(),
|
f.threshold(),
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ public record AlertInstance(
|
|||||||
String ackedBy,
|
String ackedBy,
|
||||||
Instant resolvedAt,
|
Instant resolvedAt,
|
||||||
Instant lastNotifiedAt,
|
Instant lastNotifiedAt,
|
||||||
|
Instant readAt, // NEW — global "someone has seen this"
|
||||||
|
Instant deletedAt, // NEW — soft delete
|
||||||
boolean silenced,
|
boolean silenced,
|
||||||
Double currentValue,
|
Double currentValue,
|
||||||
Double threshold,
|
Double threshold,
|
||||||
@@ -39,63 +41,77 @@ public record AlertInstance(
|
|||||||
|
|
||||||
public AlertInstance withState(AlertState s) {
|
public AlertInstance withState(AlertState s) {
|
||||||
return new AlertInstance(id, ruleId, ruleSnapshot, environmentId,
|
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,
|
currentValue, threshold, context, title, message,
|
||||||
targetUserIds, targetGroupIds, targetRoleNames);
|
targetUserIds, targetGroupIds, targetRoleNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AlertInstance withFiredAt(Instant i) {
|
public AlertInstance withFiredAt(Instant i) {
|
||||||
return new AlertInstance(id, ruleId, ruleSnapshot, environmentId,
|
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,
|
currentValue, threshold, context, title, message,
|
||||||
targetUserIds, targetGroupIds, targetRoleNames);
|
targetUserIds, targetGroupIds, targetRoleNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AlertInstance withResolvedAt(Instant i) {
|
public AlertInstance withResolvedAt(Instant i) {
|
||||||
return new AlertInstance(id, ruleId, ruleSnapshot, environmentId,
|
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,
|
currentValue, threshold, context, title, message,
|
||||||
targetUserIds, targetGroupIds, targetRoleNames);
|
targetUserIds, targetGroupIds, targetRoleNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AlertInstance withAck(String ackedBy, Instant ackedAt) {
|
public AlertInstance withAck(String ackedBy, Instant ackedAt) {
|
||||||
return new AlertInstance(id, ruleId, ruleSnapshot, environmentId,
|
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,
|
currentValue, threshold, context, title, message,
|
||||||
targetUserIds, targetGroupIds, targetRoleNames);
|
targetUserIds, targetGroupIds, targetRoleNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AlertInstance withSilenced(boolean silenced) {
|
public AlertInstance withSilenced(boolean silenced) {
|
||||||
return new AlertInstance(id, ruleId, ruleSnapshot, environmentId,
|
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,
|
currentValue, threshold, context, title, message,
|
||||||
targetUserIds, targetGroupIds, targetRoleNames);
|
targetUserIds, targetGroupIds, targetRoleNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AlertInstance withTitleMessage(String title, String message) {
|
public AlertInstance withTitleMessage(String title, String message) {
|
||||||
return new AlertInstance(id, ruleId, ruleSnapshot, environmentId,
|
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,
|
currentValue, threshold, context, title, message,
|
||||||
targetUserIds, targetGroupIds, targetRoleNames);
|
targetUserIds, targetGroupIds, targetRoleNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AlertInstance withLastNotifiedAt(Instant instant) {
|
public AlertInstance withLastNotifiedAt(Instant instant) {
|
||||||
return new AlertInstance(id, ruleId, ruleSnapshot, environmentId,
|
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,
|
currentValue, threshold, context, title, message,
|
||||||
targetUserIds, targetGroupIds, targetRoleNames);
|
targetUserIds, targetGroupIds, targetRoleNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AlertInstance withContext(Map<String, Object> context) {
|
public AlertInstance withContext(Map<String, Object> context) {
|
||||||
return new AlertInstance(id, ruleId, ruleSnapshot, environmentId,
|
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,
|
currentValue, threshold, context, title, message,
|
||||||
targetUserIds, targetGroupIds, targetRoleNames);
|
targetUserIds, targetGroupIds, targetRoleNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AlertInstance withRuleSnapshot(Map<String, Object> snapshot) {
|
public AlertInstance withRuleSnapshot(Map<String, Object> snapshot) {
|
||||||
return new AlertInstance(id, ruleId, snapshot, environmentId,
|
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,
|
currentValue, threshold, context, title, message,
|
||||||
targetUserIds, targetGroupIds, targetRoleNames);
|
targetUserIds, targetGroupIds, targetRoleNames);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
package com.cameleer.server.core.alerting;
|
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(
|
assertThat(AlertSeverity.values()).containsExactly(
|
||||||
AlertSeverity.CRITICAL, AlertSeverity.WARNING, AlertSeverity.INFO);
|
AlertSeverity.CRITICAL, AlertSeverity.WARNING, AlertSeverity.INFO);
|
||||||
assertThat(AlertState.values()).containsExactly(
|
assertThat(AlertState.values()).containsExactly(
|
||||||
AlertState.PENDING, AlertState.FIRING, AlertState.ACKNOWLEDGED, AlertState.RESOLVED);
|
AlertState.PENDING, AlertState.FIRING, AlertState.RESOLVED);
|
||||||
assertThat(ConditionKind.values()).hasSize(6);
|
assertThat(ConditionKind.values()).hasSize(7);
|
||||||
assertThat(TargetKind.values()).containsExactly(
|
assertThat(TargetKind.values()).containsExactly(
|
||||||
TargetKind.USER, TargetKind.GROUP, TargetKind.ROLE);
|
TargetKind.USER, TargetKind.GROUP, TargetKind.ROLE);
|
||||||
assertThat(NotificationStatus.values()).containsExactly(
|
assertThat(NotificationStatus.values()).containsExactly(
|
||||||
|
|||||||
Reference in New Issue
Block a user