From 5ea7a99d5c575eaea0598b2edc5c5ee619673c82 Mon Sep 17 00:00:00 2001 From: James Maes Date: Fri, 16 Oct 2026 15:30:40 -0500 Subject: [PATCH 1/2] fix(alerts): name the plan in alert titles, keep info alerts out of the bell Per-plan alert titles were constants, so two plans looked identical in the notification bell. Titles now carry the plan's display name; dedupe keys are unchanged. STORAGE_DEBRIS and RESTORE_COMPLETED are informational, so they are declared alert-only: after the first raise the adapter switches notify off on the Alerts qbit's alert type row (core alert type metadata has no such setting). Co-Authored-By: Claude Opus 5.5 --- .../backups/alerts/AlertsQBitAlertSink.java | 66 +++++++++++++++++++ .../qbits/backups/alerts/BackupAlerts.java | 15 ++++- .../qbits/backups/sla/SlaChecker.java | 12 ++-- .../qbits/backups/verify/BackupVerifier.java | 2 +- .../alerts/AlertsQBitAlertSinkTest.java | 37 +++++++++++ .../backups/alerts/BackupAlertsTest.java | 12 ++++ .../qbits/backups/sla/SlaCheckerTest.java | 1 + 7 files changed, 135 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/kingsrook/qbits/backups/alerts/AlertsQBitAlertSink.java b/src/main/java/com/kingsrook/qbits/backups/alerts/AlertsQBitAlertSink.java index b4d426d..bd96351 100644 --- a/src/main/java/com/kingsrook/qbits/backups/alerts/AlertsQBitAlertSink.java +++ b/src/main/java/com/kingsrook/qbits/backups/alerts/AlertsQBitAlertSink.java @@ -17,7 +17,10 @@ package com.kingsrook.qbits.backups.alerts; +import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; import com.kingsrook.qbits.backups.BackupsRuntime; import com.kingsrook.qqq.backend.core.alerts.AlertSeverity; @@ -25,8 +28,17 @@ import com.kingsrook.qqq.backend.core.alerts.AlertSignal; import com.kingsrook.qqq.backend.core.alerts.AlertsInstanceMetaData; import com.kingsrook.qqq.backend.core.alerts.QAlertTypeMetaData; import com.kingsrook.qqq.backend.core.alerts.QAlerts; +import com.kingsrook.qqq.backend.core.actions.tables.QueryAction; +import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction; import com.kingsrook.qqq.backend.core.context.QContext; +import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.logging.QLogger; +import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator; +import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria; +import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter; +import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput; +import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput; +import com.kingsrook.qqq.backend.core.model.data.QRecord; import com.kingsrook.qqq.backend.core.model.metadata.QInstance; @@ -40,8 +52,13 @@ public class AlertsQBitAlertSink implements BackupAlertSink { private static final QLogger LOG = QLogger.getLogger(AlertsQBitAlertSink.class); + /** The Alerts qbit's alert type table, whose notify switch decides what reaches the bell. */ + private static final String ALERTS_TYPE_TABLE = "alerts_type"; + private boolean warned = false; + private final Set silenced = ConcurrentHashMap.newKeySet(); + /******************************************************************************* @@ -114,6 +131,7 @@ public class AlertsQBitAlertSink implements BackupAlertSink detail.forEach(signal::withAttribute); } QAlerts.raise(signal); + keepAlertOnly(type); } catch(RuntimeException | LinkageError e) { @@ -123,6 +141,54 @@ public class AlertsQBitAlertSink implements BackupAlertSink + /******************************************************************************* + ** The core alert type metadata has no notify setting; the Alerts qbit keeps it + ** on the alert type row (created with notify on, then left to admins). For a + ** type declared alert-only, switch notify off once per adapter, after the + ** first raise has created the row, so these never reach the notification bell. + ** Without the Alerts qbit's type table this does nothing. + *******************************************************************************/ + private void keepAlertOnly(String type) + { + if(type == null || silenced.contains(type) + || BackupAlerts.alertTypes().stream().noneMatch(t -> t.name().equals(type) && !t.bell())) + { + return; + } + + try + { + if(QContext.getQInstance().getTable(ALERTS_TYPE_TABLE) == null) + { + return; + } + + QueryInput queryInput = new QueryInput(ALERTS_TYPE_TABLE); + queryInput.setFilter(new QQueryFilter(new QFilterCriteria("name", QCriteriaOperator.EQUALS, type))); + queryInput.withPermissionBypass("backups alert-only types"); + List rows = new QueryAction().execute(queryInput).getRecords(); + if(rows.isEmpty()) + { + return; + } + + if(!Boolean.FALSE.equals(rows.get(0).getValueBoolean("notify"))) + { + UpdateInput updateInput = new UpdateInput(ALERTS_TYPE_TABLE); + updateInput.setRecords(List.of(new QRecord().withValue("id", rows.get(0).getValue("id")).withValue("notify", false))); + updateInput.withPermissionBypass("backups alert-only types"); + new UpdateAction().execute(updateInput); + } + silenced.add(type); + } + catch(QException | RuntimeException e) + { + warnOnce("Could not mark the alert type " + type + " alert-only", e); + } + } + + + /******************************************************************************* ** {@inheritDoc} *******************************************************************************/ diff --git a/src/main/java/com/kingsrook/qbits/backups/alerts/BackupAlerts.java b/src/main/java/com/kingsrook/qbits/backups/alerts/BackupAlerts.java index 4f0fc31..36bd102 100644 --- a/src/main/java/com/kingsrook/qbits/backups/alerts/BackupAlerts.java +++ b/src/main/java/com/kingsrook/qbits/backups/alerts/BackupAlerts.java @@ -56,9 +56,18 @@ public final class BackupAlerts ** @param label a short label ** @param defaultSeverity the severity when a signal does not say ** @param howToFix one sentence for the person looking at the alert + ** @param bell whether the alert also goes to the notification bell; false + ** keeps it alert-only (listed in the Alerts qbit, never pushed) ***************************************************************************/ - public record AlertType(String name, String label, BackupAlertSink.Severity defaultSeverity, String howToFix) + public record AlertType(String name, String label, BackupAlertSink.Severity defaultSeverity, String howToFix, boolean bell) { + /******************************************************************************* + ** An alert type that notifies (the default). + *******************************************************************************/ + public AlertType(String name, String label, BackupAlertSink.Severity defaultSeverity, String howToFix) + { + this(name, label, defaultSeverity, howToFix, true); + } } @@ -93,9 +102,9 @@ public final class BackupAlerts new AlertType(PLAN_OWNER_INVALID, "Backup plan owner is not valid", BackupAlertSink.Severity.WARNING, "Set an owner who exists and holds backups.admin on the plan."), new AlertType(STORAGE_DEBRIS, "Backup storage has debris or orphans", BackupAlertSink.Severity.INFO, - "Prune clears debris. Orphan prefixes can be imported into the catalog or deleted."), + "Prune clears debris. Orphan prefixes can be imported into the catalog or deleted.", false), new AlertType(RESTORE_COMPLETED, "A restore finished", BackupAlertSink.Severity.INFO, - "Nothing to fix unless the restore failed. Open the restore run."))); + "Nothing to fix unless the restore failed. Open the restore run.", false))); } diff --git a/src/main/java/com/kingsrook/qbits/backups/sla/SlaChecker.java b/src/main/java/com/kingsrook/qbits/backups/sla/SlaChecker.java index 94b9978..b8a5b41 100644 --- a/src/main/java/com/kingsrook/qbits/backups/sla/SlaChecker.java +++ b/src/main/java/com/kingsrook/qbits/backups/sla/SlaChecker.java @@ -262,7 +262,7 @@ public class SlaChecker if(missing) { - BackupAlerts.raise(BackupAlerts.RUN_MISSING, planKey, BackupAlertSink.Severity.CRITICAL, "The latest backup is missing from storage", + BackupAlerts.raise(BackupAlerts.RUN_MISSING, planKey, BackupAlertSink.Severity.CRITICAL, "The latest backup of \"" + name + "\" is missing from storage", "plan", planKey, "planName", name); } else @@ -273,7 +273,7 @@ public class SlaChecker if(late) { BackupAlerts.raise(BackupAlerts.PLAN_MISSED, planKey, critical ? BackupAlertSink.Severity.CRITICAL : BackupAlertSink.Severity.ERROR, - "A backup did not run in time", "plan", planKey, "planName", name, "slaHours", String.valueOf(slaHours(plan))); + "Backup \"" + name + "\" did not run in time", "plan", planKey, "planName", name, "slaHours", String.valueOf(slaHours(plan))); } else { @@ -283,7 +283,7 @@ public class SlaChecker if(latestFailed || abandoned) { BackupAlerts.raise(BackupAlerts.RUN_FAILED, planKey, failing ? BackupAlertSink.Severity.ERROR : BackupAlertSink.Severity.WARNING, - failing ? "Backups have failed twice in a row" : "A backup run failed", "plan", planKey, "planName", name); + failing ? "Backup \"" + name + "\" has failed twice in a row" : "Backup \"" + name + "\" run failed", "plan", planKey, "planName", name); } else if(latestFinished != null) { @@ -292,7 +292,7 @@ public class SlaChecker if(ownerBad) { - BackupAlerts.raise(BackupAlerts.PLAN_OWNER_INVALID, planKey, BackupAlertSink.Severity.WARNING, "The backup plan owner is not valid", + BackupAlerts.raise(BackupAlerts.PLAN_OWNER_INVALID, planKey, BackupAlertSink.Severity.WARNING, "The owner of backup plan \"" + name + "\" is not valid", "plan", planKey, "planName", name); } else @@ -302,7 +302,7 @@ public class SlaChecker if(unverified) { - BackupAlerts.raise(BackupAlerts.VERIFY_OVERDUE, planKey, BackupAlertSink.Severity.WARNING, "No backup has been verified recently", + BackupAlerts.raise(BackupAlerts.VERIFY_OVERDUE, planKey, BackupAlertSink.Severity.WARNING, "No backup of \"" + name + "\" has been verified recently", "plan", planKey, "planName", name, "verifySlaDays", String.valueOf(plan.getValueInteger("verifySlaDays"))); } else @@ -507,7 +507,7 @@ public class SlaChecker LAST_AUDIT_AT.put(auditKey, now); if(orphans + debris > 0) { - BackupAlerts.raise(BackupAlerts.STORAGE_DEBRIS, planKey, BackupAlertSink.Severity.INFO, "Backup storage has orphans or debris", + BackupAlerts.raise(BackupAlerts.STORAGE_DEBRIS, planKey, BackupAlertSink.Severity.INFO, "Backup storage of \"" + planKey + "\" has orphans or debris", "plan", planKey, "orphans", String.valueOf(orphans), "debris", String.valueOf(debris)); } else diff --git a/src/main/java/com/kingsrook/qbits/backups/verify/BackupVerifier.java b/src/main/java/com/kingsrook/qbits/backups/verify/BackupVerifier.java index 6cef352..2830560 100644 --- a/src/main/java/com/kingsrook/qbits/backups/verify/BackupVerifier.java +++ b/src/main/java/com/kingsrook/qbits/backups/verify/BackupVerifier.java @@ -639,7 +639,7 @@ public class BackupVerifier detail.put("level", outcome.level()); detail.put("mismatches", String.valueOf(outcome.mismatches())); BackupAlerts.raiseWithKey(BackupAlerts.VERIFY_FAILED, BackupAlerts.verifyKey(planKey, outcome.level()), BackupAlertSink.Severity.ERROR, - "Backup verification failed (" + outcome.level() + ")", detail); + "Verification of backup \"" + (plan.getValueString("name") == null ? planKey : plan.getValueString("name")) + "\" failed (" + outcome.level() + ")", detail); } } } diff --git a/src/test/java/com/kingsrook/qbits/backups/alerts/AlertsQBitAlertSinkTest.java b/src/test/java/com/kingsrook/qbits/backups/alerts/AlertsQBitAlertSinkTest.java index 6717bc3..20b1d04 100644 --- a/src/test/java/com/kingsrook/qbits/backups/alerts/AlertsQBitAlertSinkTest.java +++ b/src/test/java/com/kingsrook/qbits/backups/alerts/AlertsQBitAlertSinkTest.java @@ -24,6 +24,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import com.kingsrook.qbits.backups.BackupsRuntime; +import com.kingsrook.qbits.backups.BackupsTestUtils; import com.kingsrook.qbits.backups.VerifyTestSupport; import com.kingsrook.qbits.backups.sla.SlaChecker; import com.kingsrook.qqq.backend.core.alerts.AlertResolution; @@ -31,11 +32,18 @@ import com.kingsrook.qqq.backend.core.alerts.AlertSignal; import com.kingsrook.qqq.backend.core.alerts.AlertSinkInterface; import com.kingsrook.qqq.backend.core.alerts.AlertsInstanceMetaData; import com.kingsrook.qqq.backend.core.alerts.QAlerts; +import com.kingsrook.qqq.backend.core.actions.tables.InsertAction; +import com.kingsrook.qqq.backend.core.actions.tables.QueryAction; import com.kingsrook.qqq.backend.core.context.QContext; import com.kingsrook.qqq.backend.core.instances.QInstanceValidator; +import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput; +import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput; import com.kingsrook.qqq.backend.core.model.data.QRecord; import com.kingsrook.qqq.backend.core.model.metadata.QInstance; import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference; +import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType; +import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -208,6 +216,35 @@ class AlertsQBitAlertSinkTest + /******************************************************************************* + ** Alert-only types get notify switched off on the Alerts qbit's type row (once + ** the row exists); types that notify are left alone. + *******************************************************************************/ + @Test + void testAlertOnlyTypesAreNotSentToTheBell() throws Exception + { + QContext.getQInstance().addTable(new QTableMetaData().withName("alerts_type").withBackendName(BackupsTestUtils.BACKEND).withPrimaryKeyField("id") + .withField(new QFieldMetaData("id", QFieldType.INTEGER)) + .withField(new QFieldMetaData("name", QFieldType.STRING)) + .withField(new QFieldMetaData("notify", QFieldType.BOOLEAN))); + InsertInput insert = new InsertInput("alerts_type"); + insert.setRecords(List.of( + new QRecord().withValue("name", BackupAlerts.STORAGE_DEBRIS).withValue("notify", true), + new QRecord().withValue("name", BackupAlerts.PLAN_MISSED).withValue("notify", true))); + new InsertAction().execute(insert); + + AlertsQBitAlertSink sink = new AlertsQBitAlertSink(); + sink.raise(BackupAlerts.PLAN_MISSED, "k", BackupAlertSink.Severity.ERROR, "x", Map.of()); + sink.raise(BackupAlerts.STORAGE_DEBRIS, "k", BackupAlertSink.Severity.INFO, "x", Map.of()); + + QueryInput query = new QueryInput("alerts_type"); + Map notify = new LinkedHashMap<>(); + new QueryAction().execute(query).getRecords().forEach(r -> notify.put(r.getValueString("name"), r.getValueBoolean("notify"))); + assertThat(notify).containsEntry(BackupAlerts.STORAGE_DEBRIS, false).containsEntry(BackupAlerts.PLAN_MISSED, true); + } + + + /******************************************************************************* ** Default binding preserves a host-provided backup sink. *******************************************************************************/ diff --git a/src/test/java/com/kingsrook/qbits/backups/alerts/BackupAlertsTest.java b/src/test/java/com/kingsrook/qbits/backups/alerts/BackupAlertsTest.java index 005cca5..077aa2d 100644 --- a/src/test/java/com/kingsrook/qbits/backups/alerts/BackupAlertsTest.java +++ b/src/test/java/com/kingsrook/qbits/backups/alerts/BackupAlertsTest.java @@ -79,6 +79,18 @@ class BackupAlertsTest + /******************************************************************************* + ** Only the two informational types are alert-only; everything else notifies. + *******************************************************************************/ + @Test + void testOnlyInformationalTypesAreAlertOnly() + { + Set alertOnly = BackupAlerts.alertTypes().stream().filter(t -> !t.bell()).map(BackupAlerts.AlertType::name).collect(Collectors.toSet()); + assertThat(alertOnly).containsExactlyInAnyOrder(BackupAlerts.STORAGE_DEBRIS, BackupAlerts.RESTORE_COMPLETED); + } + + + /******************************************************************************* ** Dedupe keys are the type plus the plan (or plan and level), never a value. *******************************************************************************/ diff --git a/src/test/java/com/kingsrook/qbits/backups/sla/SlaCheckerTest.java b/src/test/java/com/kingsrook/qbits/backups/sla/SlaCheckerTest.java index 9763668..d28f883 100644 --- a/src/test/java/com/kingsrook/qbits/backups/sla/SlaCheckerTest.java +++ b/src/test/java/com/kingsrook/qbits/backups/sla/SlaCheckerTest.java @@ -87,6 +87,7 @@ class SlaCheckerTest assertThat(late.previousStatus()).isEqualTo(SlaChecker.UNKNOWN); assertThat(reload(plan).getValueString("slaStatus")).isEqualTo("LATE"); BackupAlertsAssert.assertOpen("backups.plan.missed:nightly", BackupAlertSink.Severity.CRITICAL); + assertThat(sink.open.get("backups.plan.missed:nightly").title()).isEqualTo("Backup \"Nightly\" did not run in time"); } -- 2.54.0 From 186bfabc342249e5c663697ef192e4ac91cf43af Mon Sep 17 00:00:00 2001 From: James Maes Date: Fri, 16 Oct 2026 15:39:00 -0500 Subject: [PATCH 2/2] revert(alerts): drop the runtime alert-only mechanism Setting alerts_type.notify=false from the adapter overrides an admin's choice on every restart. Making STORAGE_DEBRIS and RESTORE_COMPLETED alert-only waits on a proper notify flag in the alert type metadata (core and qbit-alerts). The plan-named titles stay. Co-Authored-By: Claude Opus 5.5 --- .../backups/alerts/AlertsQBitAlertSink.java | 66 ------------------- .../qbits/backups/alerts/BackupAlerts.java | 15 +---- .../alerts/AlertsQBitAlertSinkTest.java | 37 ----------- .../backups/alerts/BackupAlertsTest.java | 12 ---- 4 files changed, 3 insertions(+), 127 deletions(-) diff --git a/src/main/java/com/kingsrook/qbits/backups/alerts/AlertsQBitAlertSink.java b/src/main/java/com/kingsrook/qbits/backups/alerts/AlertsQBitAlertSink.java index bd96351..b4d426d 100644 --- a/src/main/java/com/kingsrook/qbits/backups/alerts/AlertsQBitAlertSink.java +++ b/src/main/java/com/kingsrook/qbits/backups/alerts/AlertsQBitAlertSink.java @@ -17,10 +17,7 @@ package com.kingsrook.qbits.backups.alerts; -import java.util.List; import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; import com.kingsrook.qbits.backups.BackupsRuntime; import com.kingsrook.qqq.backend.core.alerts.AlertSeverity; @@ -28,17 +25,8 @@ import com.kingsrook.qqq.backend.core.alerts.AlertSignal; import com.kingsrook.qqq.backend.core.alerts.AlertsInstanceMetaData; import com.kingsrook.qqq.backend.core.alerts.QAlertTypeMetaData; import com.kingsrook.qqq.backend.core.alerts.QAlerts; -import com.kingsrook.qqq.backend.core.actions.tables.QueryAction; -import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction; import com.kingsrook.qqq.backend.core.context.QContext; -import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.logging.QLogger; -import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator; -import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria; -import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter; -import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput; -import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput; -import com.kingsrook.qqq.backend.core.model.data.QRecord; import com.kingsrook.qqq.backend.core.model.metadata.QInstance; @@ -52,13 +40,8 @@ public class AlertsQBitAlertSink implements BackupAlertSink { private static final QLogger LOG = QLogger.getLogger(AlertsQBitAlertSink.class); - /** The Alerts qbit's alert type table, whose notify switch decides what reaches the bell. */ - private static final String ALERTS_TYPE_TABLE = "alerts_type"; - private boolean warned = false; - private final Set silenced = ConcurrentHashMap.newKeySet(); - /******************************************************************************* @@ -131,7 +114,6 @@ public class AlertsQBitAlertSink implements BackupAlertSink detail.forEach(signal::withAttribute); } QAlerts.raise(signal); - keepAlertOnly(type); } catch(RuntimeException | LinkageError e) { @@ -141,54 +123,6 @@ public class AlertsQBitAlertSink implements BackupAlertSink - /******************************************************************************* - ** The core alert type metadata has no notify setting; the Alerts qbit keeps it - ** on the alert type row (created with notify on, then left to admins). For a - ** type declared alert-only, switch notify off once per adapter, after the - ** first raise has created the row, so these never reach the notification bell. - ** Without the Alerts qbit's type table this does nothing. - *******************************************************************************/ - private void keepAlertOnly(String type) - { - if(type == null || silenced.contains(type) - || BackupAlerts.alertTypes().stream().noneMatch(t -> t.name().equals(type) && !t.bell())) - { - return; - } - - try - { - if(QContext.getQInstance().getTable(ALERTS_TYPE_TABLE) == null) - { - return; - } - - QueryInput queryInput = new QueryInput(ALERTS_TYPE_TABLE); - queryInput.setFilter(new QQueryFilter(new QFilterCriteria("name", QCriteriaOperator.EQUALS, type))); - queryInput.withPermissionBypass("backups alert-only types"); - List rows = new QueryAction().execute(queryInput).getRecords(); - if(rows.isEmpty()) - { - return; - } - - if(!Boolean.FALSE.equals(rows.get(0).getValueBoolean("notify"))) - { - UpdateInput updateInput = new UpdateInput(ALERTS_TYPE_TABLE); - updateInput.setRecords(List.of(new QRecord().withValue("id", rows.get(0).getValue("id")).withValue("notify", false))); - updateInput.withPermissionBypass("backups alert-only types"); - new UpdateAction().execute(updateInput); - } - silenced.add(type); - } - catch(QException | RuntimeException e) - { - warnOnce("Could not mark the alert type " + type + " alert-only", e); - } - } - - - /******************************************************************************* ** {@inheritDoc} *******************************************************************************/ diff --git a/src/main/java/com/kingsrook/qbits/backups/alerts/BackupAlerts.java b/src/main/java/com/kingsrook/qbits/backups/alerts/BackupAlerts.java index 36bd102..4f0fc31 100644 --- a/src/main/java/com/kingsrook/qbits/backups/alerts/BackupAlerts.java +++ b/src/main/java/com/kingsrook/qbits/backups/alerts/BackupAlerts.java @@ -56,18 +56,9 @@ public final class BackupAlerts ** @param label a short label ** @param defaultSeverity the severity when a signal does not say ** @param howToFix one sentence for the person looking at the alert - ** @param bell whether the alert also goes to the notification bell; false - ** keeps it alert-only (listed in the Alerts qbit, never pushed) ***************************************************************************/ - public record AlertType(String name, String label, BackupAlertSink.Severity defaultSeverity, String howToFix, boolean bell) + public record AlertType(String name, String label, BackupAlertSink.Severity defaultSeverity, String howToFix) { - /******************************************************************************* - ** An alert type that notifies (the default). - *******************************************************************************/ - public AlertType(String name, String label, BackupAlertSink.Severity defaultSeverity, String howToFix) - { - this(name, label, defaultSeverity, howToFix, true); - } } @@ -102,9 +93,9 @@ public final class BackupAlerts new AlertType(PLAN_OWNER_INVALID, "Backup plan owner is not valid", BackupAlertSink.Severity.WARNING, "Set an owner who exists and holds backups.admin on the plan."), new AlertType(STORAGE_DEBRIS, "Backup storage has debris or orphans", BackupAlertSink.Severity.INFO, - "Prune clears debris. Orphan prefixes can be imported into the catalog or deleted.", false), + "Prune clears debris. Orphan prefixes can be imported into the catalog or deleted."), new AlertType(RESTORE_COMPLETED, "A restore finished", BackupAlertSink.Severity.INFO, - "Nothing to fix unless the restore failed. Open the restore run.", false))); + "Nothing to fix unless the restore failed. Open the restore run."))); } diff --git a/src/test/java/com/kingsrook/qbits/backups/alerts/AlertsQBitAlertSinkTest.java b/src/test/java/com/kingsrook/qbits/backups/alerts/AlertsQBitAlertSinkTest.java index 20b1d04..6717bc3 100644 --- a/src/test/java/com/kingsrook/qbits/backups/alerts/AlertsQBitAlertSinkTest.java +++ b/src/test/java/com/kingsrook/qbits/backups/alerts/AlertsQBitAlertSinkTest.java @@ -24,7 +24,6 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import com.kingsrook.qbits.backups.BackupsRuntime; -import com.kingsrook.qbits.backups.BackupsTestUtils; import com.kingsrook.qbits.backups.VerifyTestSupport; import com.kingsrook.qbits.backups.sla.SlaChecker; import com.kingsrook.qqq.backend.core.alerts.AlertResolution; @@ -32,18 +31,11 @@ import com.kingsrook.qqq.backend.core.alerts.AlertSignal; import com.kingsrook.qqq.backend.core.alerts.AlertSinkInterface; import com.kingsrook.qqq.backend.core.alerts.AlertsInstanceMetaData; import com.kingsrook.qqq.backend.core.alerts.QAlerts; -import com.kingsrook.qqq.backend.core.actions.tables.InsertAction; -import com.kingsrook.qqq.backend.core.actions.tables.QueryAction; import com.kingsrook.qqq.backend.core.context.QContext; import com.kingsrook.qqq.backend.core.instances.QInstanceValidator; -import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput; -import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput; import com.kingsrook.qqq.backend.core.model.data.QRecord; import com.kingsrook.qqq.backend.core.model.metadata.QInstance; import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference; -import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; -import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType; -import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -216,35 +208,6 @@ class AlertsQBitAlertSinkTest - /******************************************************************************* - ** Alert-only types get notify switched off on the Alerts qbit's type row (once - ** the row exists); types that notify are left alone. - *******************************************************************************/ - @Test - void testAlertOnlyTypesAreNotSentToTheBell() throws Exception - { - QContext.getQInstance().addTable(new QTableMetaData().withName("alerts_type").withBackendName(BackupsTestUtils.BACKEND).withPrimaryKeyField("id") - .withField(new QFieldMetaData("id", QFieldType.INTEGER)) - .withField(new QFieldMetaData("name", QFieldType.STRING)) - .withField(new QFieldMetaData("notify", QFieldType.BOOLEAN))); - InsertInput insert = new InsertInput("alerts_type"); - insert.setRecords(List.of( - new QRecord().withValue("name", BackupAlerts.STORAGE_DEBRIS).withValue("notify", true), - new QRecord().withValue("name", BackupAlerts.PLAN_MISSED).withValue("notify", true))); - new InsertAction().execute(insert); - - AlertsQBitAlertSink sink = new AlertsQBitAlertSink(); - sink.raise(BackupAlerts.PLAN_MISSED, "k", BackupAlertSink.Severity.ERROR, "x", Map.of()); - sink.raise(BackupAlerts.STORAGE_DEBRIS, "k", BackupAlertSink.Severity.INFO, "x", Map.of()); - - QueryInput query = new QueryInput("alerts_type"); - Map notify = new LinkedHashMap<>(); - new QueryAction().execute(query).getRecords().forEach(r -> notify.put(r.getValueString("name"), r.getValueBoolean("notify"))); - assertThat(notify).containsEntry(BackupAlerts.STORAGE_DEBRIS, false).containsEntry(BackupAlerts.PLAN_MISSED, true); - } - - - /******************************************************************************* ** Default binding preserves a host-provided backup sink. *******************************************************************************/ diff --git a/src/test/java/com/kingsrook/qbits/backups/alerts/BackupAlertsTest.java b/src/test/java/com/kingsrook/qbits/backups/alerts/BackupAlertsTest.java index 077aa2d..005cca5 100644 --- a/src/test/java/com/kingsrook/qbits/backups/alerts/BackupAlertsTest.java +++ b/src/test/java/com/kingsrook/qbits/backups/alerts/BackupAlertsTest.java @@ -79,18 +79,6 @@ class BackupAlertsTest - /******************************************************************************* - ** Only the two informational types are alert-only; everything else notifies. - *******************************************************************************/ - @Test - void testOnlyInformationalTypesAreAlertOnly() - { - Set alertOnly = BackupAlerts.alertTypes().stream().filter(t -> !t.bell()).map(BackupAlerts.AlertType::name).collect(Collectors.toSet()); - assertThat(alertOnly).containsExactlyInAnyOrder(BackupAlerts.STORAGE_DEBRIS, BackupAlerts.RESTORE_COMPLETED); - } - - - /******************************************************************************* ** Dedupe keys are the type plus the plan (or plan and level), never a value. *******************************************************************************/ -- 2.54.0