diff options
| author | Christian Cleberg <[email protected]> | 2026-03-18 19:34:59 -0500 |
|---|---|---|
| committer | Christian Cleberg <[email protected]> | 2026-03-18 19:34:59 -0500 |
| commit | 430741f0236ab21583acda8e2774d95e6e0e4ffe (patch) | |
| tree | e8ef38d574939a0c47f2a92e14db60950b824151 | |
| parent | 796c39d8ff08829bd695bc4e7497a147640eb79a (diff) | |
| download | hutch-430741f0236ab21583acda8e2774d95e6e0e4ffe.tar.gz hutch-430741f0236ab21583acda8e2774d95e6e0e4ffe.tar.bz2 hutch-430741f0236ab21583acda8e2774d95e6e0e4ffe.zip | |
stabilize build task identity and log cache keys
| -rw-r--r-- | Hutch/Models/Builds.swift | 27 | ||||
| -rw-r--r-- | Hutch/Views/Builds/BuildDetailView.swift | 4 | ||||
| -rw-r--r-- | Hutch/Views/Builds/BuildDetailViewModel.swift | 17 | ||||
| -rw-r--r-- | HutchTests/BuildTaskTests.swift | 27 |
4 files changed, 65 insertions, 10 deletions
diff --git a/Hutch/Models/Builds.swift b/Hutch/Models/Builds.swift index 6694c35..b0c0e8d 100644 --- a/Hutch/Models/Builds.swift +++ b/Hutch/Models/Builds.swift @@ -34,10 +34,33 @@ enum TaskStatus: String, Codable, Sendable { /// A single task within a build job. struct BuildTask: Codable, Sendable, Identifiable { - var id: String { name } + private(set) var ordinal: Int? let name: String let status: TaskStatus let log: BuildLog? + + var id: String { + if let ordinal { + return "\(ordinal):\(name)" + } + return [name, log?.fullURL, status.rawValue] + .compactMap { $0 } + .joined(separator: "::") + } + + var logCacheKey: String { + id + } + + func withOrdinal(_ ordinal: Int) -> BuildTask { + var task = self + task.ordinal = ordinal + return task + } + + private enum CodingKeys: String, CodingKey { + case name, status, log + } } // MARK: - Job Summary (for list view) @@ -90,7 +113,7 @@ struct JobDetail: Codable, Sendable { let visibility: Visibility? let image: String? let manifest: String? - let tasks: [BuildTask] + var tasks: [BuildTask] let log: BuildLog? let owner: Entity } diff --git a/Hutch/Views/Builds/BuildDetailView.swift b/Hutch/Views/Builds/BuildDetailView.swift index 977cd87..0479f76 100644 --- a/Hutch/Views/Builds/BuildDetailView.swift +++ b/Hutch/Views/Builds/BuildDetailView.swift @@ -312,13 +312,13 @@ private struct TaskLogSection: View { var body: some View { DisclosureGroup(isExpanded: $isExpanded) { - if viewModel.loadingTaskLogs.contains(task.name) { + if viewModel.loadingTaskLogs.contains(task.logCacheKey) { HStack { Spacer() ProgressView("Loading log…") Spacer() } - } else if let logText = viewModel.taskLogs[task.name] { + } else if let logText = viewModel.taskLogs[task.logCacheKey] { ScrollView(.horizontal, showsIndicators: false) { Text(logText) .font(.caption2.monospaced()) diff --git a/Hutch/Views/Builds/BuildDetailViewModel.swift b/Hutch/Views/Builds/BuildDetailViewModel.swift index c14e47b..1768e47 100644 --- a/Hutch/Views/Builds/BuildDetailViewModel.swift +++ b/Hutch/Views/Builds/BuildDetailViewModel.swift @@ -104,7 +104,11 @@ final class BuildDetailViewModel { variables: ["id": jobId], responseType: JobDetailResponse.self ) - job = result.job + var loadedJob = result.job + loadedJob.tasks = loadedJob.tasks.enumerated().map { index, task in + task.withOrdinal(index) + } + job = loadedJob } catch { self.error = error.localizedDescription } @@ -113,19 +117,20 @@ final class BuildDetailViewModel { } func loadTaskLog(task: BuildTask) async { + let cacheKey = task.logCacheKey guard let log = task.log, let logURL = URL(string: log.fullURL), - !loadingTaskLogs.contains(task.name), - taskLogs[task.name] == nil else { return } - loadingTaskLogs.insert(task.name) + !loadingTaskLogs.contains(cacheKey), + taskLogs[cacheKey] == nil else { return } + loadingTaskLogs.insert(cacheKey) do { - taskLogs[task.name] = try await client.fetchText(url: logURL) + taskLogs[cacheKey] = try await client.fetchText(url: logURL) } catch { self.error = error.localizedDescription } - loadingTaskLogs.remove(task.name) + loadingTaskLogs.remove(cacheKey) } func cancelJob() async { diff --git a/HutchTests/BuildTaskTests.swift b/HutchTests/BuildTaskTests.swift new file mode 100644 index 0000000..2e2a1e0 --- /dev/null +++ b/HutchTests/BuildTaskTests.swift @@ -0,0 +1,27 @@ +import Foundation +import Testing +@testable import Hutch + +struct BuildTaskTests { + + @Test + @MainActor + func duplicateTaskNamesProduceDistinctIDsAndLogCacheKeys() { + let firstTask = BuildTask( + name: "test", + status: .failed, + log: BuildLog(fullURL: "https://builds.sr.ht/job/1/task/1") + ).withOrdinal(0) + + let secondTask = BuildTask( + name: "test", + status: .failed, + log: BuildLog(fullURL: "https://builds.sr.ht/job/1/task/2") + ).withOrdinal(1) + + #expect(firstTask.id == "0:test") + #expect(secondTask.id == "1:test") + #expect(firstTask.id != secondTask.id) + #expect(firstTask.logCacheKey != secondTask.logCacheKey) + } +} |
