From 8b661d9f36381ee432ece5e77a17d5ae6357d327 Mon Sep 17 00:00:00 2001 From: Christian Cleberg Date: Mon, 20 Jul 2026 15:31:51 -0500 Subject: fix: make disabled targets and forced queue processing visible MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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)" } -- cgit v1.2.3