krz/domain-dig

an ios app for DNS & SSL analysis

clone: git clone https://gitbay.org/krz/domain-dig.git

8b661d9f36381ee432ece5e77a17d5ae6357d327

unsigned

author: Christian Cleberg <hello@cleberg.net> · 2026-07-20T20:31:51Z

fix: make disabled targets and forced queue processing visible

Closes #8, closes #9.

enqueue(events:) filtered to enabled targets before writing any
DeliveryRecord, so events routed to a disabled integration disappeared
with nothing in the Delivery Log. Disabled targets now record a .skipped
entry with a reason, matching how filter mismatches are already surfaced.

sendTest bypassed the isEnabled check entirely, so a test event
delivered against a target that silently dropped every real event —
exactly the wrong signal when someone is verifying their setup. It now
skips with the same reason.

processQueueNow only restarted the processing task; it never moved
nextAttemptAt, so an item in retry backoff stayed undue and the fresh
task went straight back to sleep. Backoff reaches an hour, so the button
appeared inert for the one case it exists to handle. It now pulls every
queued item forward, and reports when the queue is empty instead of
returning silently.
 DomainDig/IntegrationService.swift | 58 ++++++++++++++++++++++++++++++++++----
 1 file changed, 52 insertions(+), 6 deletions(-)

diff --git a/DomainDig/IntegrationService.swift b/DomainDig/IntegrationService.swift
index 9c9541c..23085d3 100644
--- a/DomainDig/IntegrationService.swift
+++ b/DomainDig/IntegrationService.swift
@@ -100,9 +100,22 @@ final class IntegrationService {
 
     func enqueue(events: [MonitoringEvent]) {
         guard !events.isEmpty else { return }
-        let eligibleTargets = targets.filter(\.isEnabled)
         for event in events {
-            for target in eligibleTargets {
+            for target in targets {
+                guard target.isEnabled else {
+                    appendRecord(
+                        DeliveryRecord(
+                            integrationID: target.id,
+                            eventID: event.id,
+                            status: .skipped,
+                            destination: destinationLabel(for: target),
+                            summary: event.summary,
+                            failureReason: Self.disabledTargetReason
+                        )
+                    )
+                    continue
+                }
+
                 if let reason = filterMismatchReason(for: event, target: target) {
                     appendRecord(
                         DeliveryRecord(
@@ -150,7 +163,7 @@ final class IntegrationService {
     }
 
     func sendTest(for targetID: UUID) {
-        guard targets.contains(where: { $0.id == targetID }) else { return }
+        guard let target = targets.first(where: { $0.id == targetID }) else { return }
         let event = MonitoringEvent(
             type: .test,
             severity: .info,
@@ -161,13 +174,31 @@ final class IntegrationService {
                 "environment": "local-first"
             ]
         )
-        queue.append(QueuedDelivery(integrationID: targetID, event: event))
+
+        // Real events skip a disabled target, so a test event must too —
+        // otherwise a test succeeds against a target that silently drops
+        // everything monitoring sends it.
+        guard target.isEnabled else {
+            appendRecord(
+                DeliveryRecord(
+                    integrationID: target.id,
+                    eventID: event.id,
+                    status: .skipped,
+                    destination: destinationLabel(for: target),
+                    summary: event.summary,
+                    failureReason: Self.disabledTargetReason
+                )
+            )
+            return
+        }
+
+        queue.append(QueuedDelivery(integrationID: target.id, event: event))
         appendRecord(
             DeliveryRecord(
-                integrationID: targetID,
+                integrationID: target.id,
                 eventID: event.id,
                 status: .pending,
-                destination: targets.first(where: { $0.id == targetID }).map(destinationLabel(for:)) ?? "Unknown",
+                destination: destinationLabel(for: target),
                 summary: event.summary
             )
         )
@@ -175,7 +206,20 @@ final class IntegrationService {
         scheduleProcessing()
     }
 
+    /// Restarting the processing task alone leaves any item still in retry
+    /// backoff undue, so the loop would skip it and sleep again. Pulling every
+    /// queued item forward is what makes this button mean "now".
     func processQueueNow() {
+        guard !queue.isEmpty else {
+            statusMessage = "No deliveries are waiting."
+            return
+        }
+
+        let now = Date()
+        for index in queue.indices {
+            queue[index].nextAttemptAt = now
+        }
+        persistQueue()
         scheduleProcessing(force: true)
     }
 
@@ -427,6 +471,8 @@ final class IntegrationService {
         URL(string: string)?.host ?? "Configured"
     }
 
+    private static let disabledTargetReason = "This integration is disabled."
+
     private static func secretReference(for integrationID: UUID, suffix: String) -> String {
         "integration.\(integrationID.uuidString).\(suffix)"
     }