feat(alerting): core domain records (rule, instance, silence, notification)

This commit is contained in:
hsiegeln
2026-04-19 18:43:03 +02:00
parent 56a7b6de7d
commit e7a9042677
8 changed files with 183 additions and 0 deletions

View File

@@ -0,0 +1,37 @@
package com.cameleer.server.core.alerting;
import java.time.Instant;
import java.util.List;
import java.util.Map;
import java.util.UUID;
public record AlertInstance(
UUID id,
UUID ruleId, // nullable after rule deletion
Map<String, Object> ruleSnapshot,
UUID environmentId,
AlertState state,
AlertSeverity severity,
Instant firedAt,
Instant ackedAt,
String ackedBy,
Instant resolvedAt,
Instant lastNotifiedAt,
boolean silenced,
Double currentValue,
Double threshold,
Map<String, Object> context,
String title,
String message,
List<String> targetUserIds,
List<UUID> targetGroupIds,
List<String> targetRoleNames) {
public AlertInstance {
ruleSnapshot = ruleSnapshot == null ? Map.of() : Map.copyOf(ruleSnapshot);
context = context == null ? Map.of() : Map.copyOf(context);
targetUserIds = targetUserIds == null ? List.of() : List.copyOf(targetUserIds);
targetGroupIds = targetGroupIds == null ? List.of() : List.copyOf(targetGroupIds);
targetRoleNames = targetRoleNames == null ? List.of() : List.copyOf(targetRoleNames);
}
}

View File

@@ -0,0 +1,26 @@
package com.cameleer.server.core.alerting;
import java.time.Instant;
import java.util.Map;
import java.util.UUID;
public record AlertNotification(
UUID id,
UUID alertInstanceId,
UUID webhookId,
UUID outboundConnectionId,
NotificationStatus status,
int attempts,
Instant nextAttemptAt,
String claimedBy,
Instant claimedUntil,
Integer lastResponseStatus,
String lastResponseSnippet,
Map<String, Object> payload,
Instant deliveredAt,
Instant createdAt) {
public AlertNotification {
payload = payload == null ? Map.of() : Map.copyOf(payload);
}
}

View File

@@ -0,0 +1,38 @@
package com.cameleer.server.core.alerting;
import java.time.Instant;
import java.util.List;
import java.util.Map;
import java.util.UUID;
public record AlertRule(
UUID id,
UUID environmentId,
String name,
String description,
AlertSeverity severity,
boolean enabled,
ConditionKind conditionKind,
AlertCondition condition,
int evaluationIntervalSeconds,
int forDurationSeconds,
int reNotifyMinutes,
String notificationTitleTmpl,
String notificationMessageTmpl,
List<WebhookBinding> webhooks,
List<AlertRuleTarget> targets,
Instant nextEvaluationAt,
String claimedBy,
Instant claimedUntil,
Map<String, Object> evalState,
Instant createdAt,
String createdBy,
Instant updatedAt,
String updatedBy) {
public AlertRule {
webhooks = webhooks == null ? List.of() : List.copyOf(webhooks);
targets = targets == null ? List.of() : List.copyOf(targets);
evalState = evalState == null ? Map.of() : Map.copyOf(evalState);
}
}

View File

@@ -0,0 +1,5 @@
package com.cameleer.server.core.alerting;
import java.util.UUID;
public record AlertRuleTarget(UUID id, UUID ruleId, TargetKind kind, String targetId) {}

View File

@@ -0,0 +1,14 @@
package com.cameleer.server.core.alerting;
import java.time.Instant;
import java.util.UUID;
public record AlertSilence(
UUID id,
UUID environmentId,
SilenceMatcher matcher,
String reason,
Instant startsAt,
Instant endsAt,
String createdBy,
Instant createdAt) {}

View File

@@ -0,0 +1,11 @@
package com.cameleer.server.core.alerting;
import java.util.UUID;
public record SilenceMatcher(
UUID ruleId, String appSlug, String routeId, String agentId, AlertSeverity severity) {
public boolean isWildcard() {
return ruleId == null && appSlug == null && routeId == null && agentId == null && severity == null;
}
}

View File

@@ -0,0 +1,15 @@
package com.cameleer.server.core.alerting;
import java.util.Map;
import java.util.UUID;
public record WebhookBinding(
UUID id,
UUID outboundConnectionId,
String bodyOverride,
Map<String, String> headerOverrides) {
public WebhookBinding {
headerOverrides = headerOverrides == null ? Map.of() : Map.copyOf(headerOverrides);
}
}

View File

@@ -0,0 +1,37 @@
package com.cameleer.server.core.alerting;
import org.junit.jupiter.api.Test;
import java.time.Instant;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
class AlertDomainRecordsTest {
@Test
void alertRuleDefensiveCopy() {
var webhooks = new java.util.ArrayList<WebhookBinding>();
webhooks.add(new WebhookBinding(UUID.randomUUID(), UUID.randomUUID(), null, null));
var r = newRule(webhooks);
webhooks.clear();
assertThat(r.webhooks()).hasSize(1);
}
@Test
void silenceMatcherAllFieldsNullMatchesEverything() {
var m = new SilenceMatcher(null, null, null, null, null);
assertThat(m.isWildcard()).isTrue();
}
private AlertRule newRule(List<WebhookBinding> wh) {
return new AlertRule(
UUID.randomUUID(), UUID.randomUUID(), "r", null,
AlertSeverity.WARNING, true, ConditionKind.AGENT_STATE,
new AgentStateCondition(new AlertScope(null,null,null), "DEAD", 60),
60, 0, 60, "t", "m", wh, List.of(),
Instant.now(), null, null, Map.of(),
Instant.now(), "u1", Instant.now(), "u1");
}
}