From e7a90426770fbb41d47d2e4b44f9c4f255287d25 Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Sun, 19 Apr 2026 18:43:03 +0200 Subject: [PATCH] feat(alerting): core domain records (rule, instance, silence, notification) --- .../server/core/alerting/AlertInstance.java | 37 ++++++++++++++++++ .../core/alerting/AlertNotification.java | 26 +++++++++++++ .../server/core/alerting/AlertRule.java | 38 +++++++++++++++++++ .../server/core/alerting/AlertRuleTarget.java | 5 +++ .../server/core/alerting/AlertSilence.java | 14 +++++++ .../server/core/alerting/SilenceMatcher.java | 11 ++++++ .../server/core/alerting/WebhookBinding.java | 15 ++++++++ .../core/alerting/AlertDomainRecordsTest.java | 37 ++++++++++++++++++ 8 files changed, 183 insertions(+) create mode 100644 cameleer-server-core/src/main/java/com/cameleer/server/core/alerting/AlertInstance.java create mode 100644 cameleer-server-core/src/main/java/com/cameleer/server/core/alerting/AlertNotification.java create mode 100644 cameleer-server-core/src/main/java/com/cameleer/server/core/alerting/AlertRule.java create mode 100644 cameleer-server-core/src/main/java/com/cameleer/server/core/alerting/AlertRuleTarget.java create mode 100644 cameleer-server-core/src/main/java/com/cameleer/server/core/alerting/AlertSilence.java create mode 100644 cameleer-server-core/src/main/java/com/cameleer/server/core/alerting/SilenceMatcher.java create mode 100644 cameleer-server-core/src/main/java/com/cameleer/server/core/alerting/WebhookBinding.java create mode 100644 cameleer-server-core/src/test/java/com/cameleer/server/core/alerting/AlertDomainRecordsTest.java diff --git a/cameleer-server-core/src/main/java/com/cameleer/server/core/alerting/AlertInstance.java b/cameleer-server-core/src/main/java/com/cameleer/server/core/alerting/AlertInstance.java new file mode 100644 index 00000000..4f59060e --- /dev/null +++ b/cameleer-server-core/src/main/java/com/cameleer/server/core/alerting/AlertInstance.java @@ -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 ruleSnapshot, + UUID environmentId, + AlertState state, + AlertSeverity severity, + Instant firedAt, + Instant ackedAt, + String ackedBy, + Instant resolvedAt, + Instant lastNotifiedAt, + boolean silenced, + Double currentValue, + Double threshold, + Map context, + String title, + String message, + List targetUserIds, + List targetGroupIds, + List 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); + } +} diff --git a/cameleer-server-core/src/main/java/com/cameleer/server/core/alerting/AlertNotification.java b/cameleer-server-core/src/main/java/com/cameleer/server/core/alerting/AlertNotification.java new file mode 100644 index 00000000..9a80a736 --- /dev/null +++ b/cameleer-server-core/src/main/java/com/cameleer/server/core/alerting/AlertNotification.java @@ -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 payload, + Instant deliveredAt, + Instant createdAt) { + + public AlertNotification { + payload = payload == null ? Map.of() : Map.copyOf(payload); + } +} diff --git a/cameleer-server-core/src/main/java/com/cameleer/server/core/alerting/AlertRule.java b/cameleer-server-core/src/main/java/com/cameleer/server/core/alerting/AlertRule.java new file mode 100644 index 00000000..55b530c2 --- /dev/null +++ b/cameleer-server-core/src/main/java/com/cameleer/server/core/alerting/AlertRule.java @@ -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 webhooks, + List targets, + Instant nextEvaluationAt, + String claimedBy, + Instant claimedUntil, + Map 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); + } +} diff --git a/cameleer-server-core/src/main/java/com/cameleer/server/core/alerting/AlertRuleTarget.java b/cameleer-server-core/src/main/java/com/cameleer/server/core/alerting/AlertRuleTarget.java new file mode 100644 index 00000000..e772266f --- /dev/null +++ b/cameleer-server-core/src/main/java/com/cameleer/server/core/alerting/AlertRuleTarget.java @@ -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) {} diff --git a/cameleer-server-core/src/main/java/com/cameleer/server/core/alerting/AlertSilence.java b/cameleer-server-core/src/main/java/com/cameleer/server/core/alerting/AlertSilence.java new file mode 100644 index 00000000..75d69a2a --- /dev/null +++ b/cameleer-server-core/src/main/java/com/cameleer/server/core/alerting/AlertSilence.java @@ -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) {} diff --git a/cameleer-server-core/src/main/java/com/cameleer/server/core/alerting/SilenceMatcher.java b/cameleer-server-core/src/main/java/com/cameleer/server/core/alerting/SilenceMatcher.java new file mode 100644 index 00000000..b6c512f1 --- /dev/null +++ b/cameleer-server-core/src/main/java/com/cameleer/server/core/alerting/SilenceMatcher.java @@ -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; + } +} diff --git a/cameleer-server-core/src/main/java/com/cameleer/server/core/alerting/WebhookBinding.java b/cameleer-server-core/src/main/java/com/cameleer/server/core/alerting/WebhookBinding.java new file mode 100644 index 00000000..b0174143 --- /dev/null +++ b/cameleer-server-core/src/main/java/com/cameleer/server/core/alerting/WebhookBinding.java @@ -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 headerOverrides) { + + public WebhookBinding { + headerOverrides = headerOverrides == null ? Map.of() : Map.copyOf(headerOverrides); + } +} diff --git a/cameleer-server-core/src/test/java/com/cameleer/server/core/alerting/AlertDomainRecordsTest.java b/cameleer-server-core/src/test/java/com/cameleer/server/core/alerting/AlertDomainRecordsTest.java new file mode 100644 index 00000000..ceca31b6 --- /dev/null +++ b/cameleer-server-core/src/test/java/com/cameleer/server/core/alerting/AlertDomainRecordsTest.java @@ -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(); + 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 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"); + } +}