diff options
| author | Christian Cleberg <[email protected]> | 2026-04-13 09:57:23 -0500 |
|---|---|---|
| committer | Christian Cleberg <[email protected]> | 2026-04-13 09:57:23 -0500 |
| commit | 0e461a382257979037e8927e5f6b468635b0a7fe (patch) | |
| tree | 0aa3a19f6f8f966b0e90f4874ed54359a7898107 /Hutch/Views/Builds/BuildDetailViewModel.swift | |
| parent | ee9f2904aa319231b7047fca3cb069f0c07019cd (diff) | |
| download | hutch-0e461a382257979037e8927e5f6b468635b0a7fe.tar.gz hutch-0e461a382257979037e8927e5f6b468635b0a7fe.tar.bz2 hutch-0e461a382257979037e8927e5f6b468635b0a7fe.zip | |
feat: add power user actions
Implements: https://todo.sr.ht/~ccleberg/hutch/52
Implements: https://todo.sr.ht/~ccleberg/hutch/53
Implements: https://todo.sr.ht/~ccleberg/hutch/54
Diffstat (limited to 'Hutch/Views/Builds/BuildDetailViewModel.swift')
| -rw-r--r-- | Hutch/Views/Builds/BuildDetailViewModel.swift | 49 |
1 files changed, 47 insertions, 2 deletions
diff --git a/Hutch/Views/Builds/BuildDetailViewModel.swift b/Hutch/Views/Builds/BuildDetailViewModel.swift index 9566e0d..dcf70a0 100644 --- a/Hutch/Views/Builds/BuildDetailViewModel.swift +++ b/Hutch/Views/Builds/BuildDetailViewModel.swift @@ -28,6 +28,7 @@ private struct SubmittedJob: Decodable, Sendable { @MainActor final class BuildDetailViewModel { private static let autoRefreshInterval: Duration = .seconds(5) + private static func cacheKey(for jobId: Int) -> String { "build.detail.\(jobId)" } let jobId: Int private let client: SRHTClient @@ -44,6 +45,7 @@ final class BuildDetailViewModel { private(set) var isCancelling = false private(set) var isRebuilding = false private(set) var isSubmittingEditedBuild = false + private(set) var rawJobResponse: String? var error: String? /// Transient error shown for action failures (cancel, rebuild, submit). /// Separate from `error` so auto-refresh doesn't immediately clear it. @@ -123,6 +125,7 @@ final class BuildDetailViewModel { guard !isLoading else { return } isLoading = true error = nil + rawJobResponse = nil do { let result = try await client.execute( @@ -149,6 +152,40 @@ final class BuildDetailViewModel { isLoading = false } + func loadJobWithDebugCapture() async { + guard !isLoading else { return } + isLoading = true + error = nil + + do { + let cacheKey = Self.cacheKey(for: jobId) + let result = try await client.executeAndCache( + service: .builds, + query: Self.detailQuery, + variables: ["id": jobId], + responseType: JobDetailResponse.self, + cacheKey: cacheKey + ) + rawJobResponse = client.responseCache.get(forKey: cacheKey) + .flatMap { String(data: $0, encoding: .utf8) } + var loadedJob = result.job + loadedJob.tasks = loadedJob.tasks.enumerated().map { index, task in + task.withOrdinal(index) + } + if job != loadedJob { + job = loadedJob + } + + if loadedJob.status.isTerminal { + stopAutoRefresh() + } + } catch { + self.error = error.userFacingMessage + } + + isLoading = false + } + func loadTaskLog(task: BuildTask) async { let cacheKey = task.logCacheKey let jobIsTerminal = job?.status.isTerminal ?? false @@ -250,7 +287,7 @@ final class BuildDetailViewModel { variables: ["id": jobId], responseType: CancelResponse.self ) - await loadJob() + await reloadJobPreservingDebugState() } catch { // Revert optimistic update on failure. self.job = originalJob @@ -372,6 +409,14 @@ final class BuildDetailViewModel { self.autoRefreshTask = nil } + private func reloadJobPreservingDebugState() async { + if rawJobResponse != nil { + await loadJobWithDebugCapture() + } else { + await loadJob() + } + } + private var shouldAutoRefresh: Bool { guard let job else { return true } return !job.status.isTerminal @@ -385,7 +430,7 @@ final class BuildDetailViewModel { return } - await loadJob() + await reloadJobPreservingDebugState() await loadBuildLog() } } |
