From b7efaa567c57da6c04d15399b3b75e34faf1a16f Mon Sep 17 00:00:00 2001 From: Sam Chau Date: Sun, 28 Dec 2025 19:30:50 +1030 Subject: [PATCH] feat(upgrades): enhance upgrade manager with detailed notifications for success and failure --- .../server/jobs/definitions/upgradeManager.ts | 73 ++++++++++++++++++- src/lib/server/jobs/logic/upgradeManager.ts | 10 +++ src/lib/shared/notificationTypes.ts | 20 +++++ src/routes/arr/[id]/upgrades/+page.server.ts | 41 +++++++++++ 4 files changed, 142 insertions(+), 2 deletions(-) diff --git a/src/lib/server/jobs/definitions/upgradeManager.ts b/src/lib/server/jobs/definitions/upgradeManager.ts index d7c9826..35b2881 100644 --- a/src/lib/server/jobs/definitions/upgradeManager.ts +++ b/src/lib/server/jobs/definitions/upgradeManager.ts @@ -1,5 +1,6 @@ import { logger } from '$logger/logger.ts'; import { runUpgradeManager } from '../logic/upgradeManager.ts'; +import { notificationManager } from '$notifications/NotificationManager.ts'; import type { JobDefinition, JobResult } from '../types.ts'; /** @@ -52,6 +53,65 @@ export const upgradeManagerJob: JobDefinition = { } } + // Send notification summary (only if something was processed, excluding skipped) + const processedCount = result.successCount + result.failureCount; + if (processedCount > 0) { + const successfulInstances = result.instances.filter((i) => i.success); + const failedInstances = result.instances.filter((i) => !i.success && i.error && !i.error.includes('disabled') && !i.error.includes('not yet supported')); + const hasDryRun = result.instances.some((i) => i.dryRun); + + // Build message lines for each successful instance + const messageLines: string[] = []; + for (const inst of successfulInstances) { + const dryRunLabel = inst.dryRun ? ' [DRY RUN]' : ''; + messageLines.push(`**${inst.instanceName}: ${inst.filterName}${dryRunLabel}**`); + messageLines.push(`Filter: ${inst.matchedCount} matched → ${inst.afterCooldown} after cooldown`); + messageLines.push(`Selection: ${inst.itemsSearched}/${inst.itemsRequested} items`); + if (inst.items && inst.items.length > 0) { + messageLines.push(`Items: ${inst.items.join(', ')}`); + } + messageLines.push(''); + } + + // Add failed instances + for (const inst of failedInstances) { + messageLines.push(`**${inst.instanceName}: Failed**`); + messageLines.push(`Error: ${inst.error}`); + messageLines.push(''); + } + + let notificationType: string; + let title: string; + + if (result.failureCount === 0) { + notificationType = 'upgrade.success'; + title = hasDryRun ? 'Upgrade Completed (Dry Run)' : 'Upgrade Completed'; + } else if (result.successCount === 0) { + notificationType = 'upgrade.failed'; + title = 'Upgrade Failed'; + } else { + notificationType = 'upgrade.partial'; + title = 'Upgrade Partially Completed'; + } + + await notificationManager.notify({ + type: notificationType, + title, + message: messageLines.join('\n').trim(), + metadata: { + successCount: result.successCount, + failureCount: result.failureCount, + dryRun: hasDryRun, + instances: result.instances.filter((i) => i.success).map((i) => ({ + name: i.instanceName, + filter: i.filterName, + searched: i.itemsSearched, + items: i.items + })) + } + }); + } + // Consider job failed only if all configs failed if (result.failureCount > 0 && result.successCount === 0) { return { @@ -65,14 +125,23 @@ export const upgradeManagerJob: JobDefinition = { output: message }; } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + await logger.error('Upgrade manager job failed', { source: 'UpgradeManagerJob', - meta: { error: error instanceof Error ? error.message : String(error) } + meta: { error: errorMessage } + }); + + await notificationManager.notify({ + type: 'upgrade.failed', + title: 'Upgrade Failed', + message: `Upgrade manager encountered an error: ${errorMessage}`, + metadata: { error: errorMessage } }); return { success: false, - error: error instanceof Error ? error.message : String(error) + error: errorMessage }; } } diff --git a/src/lib/server/jobs/logic/upgradeManager.ts b/src/lib/server/jobs/logic/upgradeManager.ts index 9a79464..e7e70dd 100644 --- a/src/lib/server/jobs/logic/upgradeManager.ts +++ b/src/lib/server/jobs/logic/upgradeManager.ts @@ -15,6 +15,11 @@ export interface UpgradeInstanceStatus { success: boolean; filterName?: string; itemsSearched?: number; + itemsRequested?: number; + matchedCount?: number; + afterCooldown?: number; + items?: string[]; + dryRun?: boolean; error?: string; } @@ -79,6 +84,11 @@ async function processConfig(config: UpgradeConfig): Promise i.title), + dryRun: config.dryRun, error: log.status === 'failed' ? log.results.errors.join('; ') : undefined }; } catch (error) { diff --git a/src/lib/shared/notificationTypes.ts b/src/lib/shared/notificationTypes.ts index 901d123..9dc0cd9 100644 --- a/src/lib/shared/notificationTypes.ts +++ b/src/lib/shared/notificationTypes.ts @@ -84,6 +84,26 @@ export const notificationTypes: NotificationType[] = [ label: 'Database Sync (Failed)', category: 'Databases', description: 'Notification when database sync fails' + }, + + // Upgrades + { + id: 'upgrade.success', + label: 'Upgrade Completed (Success)', + category: 'Upgrades', + description: 'Notification when all upgrade searches complete successfully' + }, + { + id: 'upgrade.partial', + label: 'Upgrade Completed (Partial)', + category: 'Upgrades', + description: 'Notification when some upgrade searches succeed and some fail' + }, + { + id: 'upgrade.failed', + label: 'Upgrade Failed', + category: 'Upgrades', + description: 'Notification when all upgrade searches fail' } ]; diff --git a/src/routes/arr/[id]/upgrades/+page.server.ts b/src/routes/arr/[id]/upgrades/+page.server.ts index f0f5e8d..c3ca3de 100644 --- a/src/routes/arr/[id]/upgrades/+page.server.ts +++ b/src/routes/arr/[id]/upgrades/+page.server.ts @@ -3,6 +3,7 @@ import type { Actions, ServerLoad } from '@sveltejs/kit'; import { arrInstancesQueries } from '$db/queries/arrInstances.ts'; import { upgradeConfigsQueries } from '$db/queries/upgradeConfigs.ts'; import { logger } from '$logger/logger.ts'; +import { notificationManager } from '$notifications/NotificationManager.ts'; import type { FilterConfig, FilterMode } from '$lib/shared/filters.ts'; import { processUpgradeConfig } from '$lib/server/upgrades/processor.ts'; @@ -221,6 +222,31 @@ export const actions: Actions = { upgradeConfigsQueries.incrementFilterIndex(id); } + // Send notification + const isSuccess = result.status === 'success' || result.status === 'partial'; + const dryRunLabel = result.config.dryRun ? ' [DRY RUN]' : ''; + const itemsList = result.selection.items.map((i) => i.title).join(', '); + + await notificationManager.notify({ + type: isSuccess ? 'upgrade.success' : 'upgrade.failed', + title: `${instance.name}: ${result.config.selectedFilter}${dryRunLabel}`, + message: [ + `Filter: ${result.filter.matchedCount} matched → ${result.filter.afterCooldown} after cooldown`, + `Selection: ${result.selection.actualCount}/${result.selection.requestedCount} items`, + `Results: ${result.results.searchesTriggered} searches, ${result.results.successful} successful`, + itemsList ? `Items: ${itemsList}` : null + ].filter(Boolean).join('\n'), + metadata: { + instanceId: id, + instanceName: instance.name, + filterName: result.config.selectedFilter, + itemsSearched: result.selection.actualCount, + matchedCount: result.filter.matchedCount, + dryRun: result.config.dryRun, + items: result.selection.items.map((i) => i.title) + } + }); + return { success: true, runResult: { @@ -234,10 +260,25 @@ export const actions: Actions = { } }; } catch (err) { + const errorMessage = err instanceof Error ? err.message : 'Unknown error'; + await logger.error('Manual upgrade run failed', { source: 'upgrades', meta: { instanceId: id, error: err } }); + + await notificationManager.notify({ + type: 'upgrade.failed', + title: 'Upgrade Failed', + message: `${instance.name}: ${errorMessage}`, + metadata: { + instanceId: id, + instanceName: instance.name, + error: errorMessage, + dryRun: true + } + }); + return fail(500, { error: 'Upgrade run failed. Check logs for details.' }); } }