diff options
| author | Christian Cleberg <[email protected]> | 2026-04-12 21:17:24 -0500 |
|---|---|---|
| committer | Christian Cleberg <[email protected]> | 2026-04-12 21:17:24 -0500 |
| commit | 7548bfb595d853c682653c0dc21bb3bf17af80d5 (patch) | |
| tree | d5bc8ac6e70a1666439fb551459f3b36d8a566b3 /Hutch | |
| parent | d89f2d52b623aad98df687865b263adeee7f5de9 (diff) | |
| download | hutch-7548bfb595d853c682653c0dc21bb3bf17af80d5.tar.gz hutch-7548bfb595d853c682653c0dc21bb3bf17af80d5.tar.bz2 hutch-7548bfb595d853c682653c0dc21bb3bf17af80d5.zip | |
feat: add build artifact listing
Implements: https://todo.sr.ht/~ccleberg/hutch/31
Diffstat (limited to 'Hutch')
| -rw-r--r-- | Hutch/Models/Builds.swift | 19 | ||||
| -rw-r--r-- | Hutch/Views/Builds/BuildDetailView.swift | 63 | ||||
| -rw-r--r-- | Hutch/Views/Builds/BuildDetailViewModel.swift | 2 |
3 files changed, 84 insertions, 0 deletions
diff --git a/Hutch/Models/Builds.swift b/Hutch/Models/Builds.swift index 8de4235..d666398 100644 --- a/Hutch/Models/Builds.swift +++ b/Hutch/Models/Builds.swift @@ -122,10 +122,29 @@ struct JobDetail: Codable, Sendable, Equatable { let image: String? let manifest: String? var tasks: [BuildTask] + let artifacts: [BuildArtifact] let log: BuildLog? let owner: Entity } +/// A downloadable artifact emitted by a build job. +struct BuildArtifact: Codable, Sendable, Identifiable, Equatable { + let id: Int + let created: Date + let path: String + let size: Int + let url: URL? + + var filename: String { + let pathComponents = path.split(separator: "/") + return pathComponents.last.map(String.init) ?? path + } + + var isDownloadable: Bool { + url != nil + } +} + /// The log associated with a build job. struct BuildLog: Codable, Sendable, Equatable { let fullURL: String diff --git a/Hutch/Views/Builds/BuildDetailView.swift b/Hutch/Views/Builds/BuildDetailView.swift index 2f0bc6a..1ec9f64 100644 --- a/Hutch/Views/Builds/BuildDetailView.swift +++ b/Hutch/Views/Builds/BuildDetailView.swift @@ -4,6 +4,7 @@ struct BuildDetailView: View { let jobId: Int @Environment(AppState.self) private var appState + @Environment(\.openURL) private var openURL @State private var viewModel: BuildDetailViewModel? @State private var rebuiltJobId: Int? @State private var selectedTaskName: String? @@ -181,6 +182,23 @@ struct BuildDetailView: View { } } + if !job.artifacts.isEmpty { + Section { + ForEach(job.artifacts) { artifact in + BuildArtifactRow(artifact: artifact) { + guard let url = artifact.url else { return } + openURL(url) + } + } + } header: { + Text("Artifacts") + } footer: { + if job.artifacts.contains(where: { !$0.isDownloadable }) { + Text("Artifacts without a download URL are no longer available for download.") + } + } + } + // Per-task logs if !job.tasks.isEmpty { ForEach(job.tasks) { task in @@ -285,6 +303,51 @@ struct BuildDetailView: View { } } +private struct BuildArtifactRow: View { + let artifact: BuildArtifact + let onDownload: () -> Void + + var body: some View { + HStack(alignment: .top, spacing: 12) { + VStack(alignment: .leading, spacing: 4) { + Text(artifact.filename) + .font(.subheadline.weight(.medium)) + + if artifact.path != artifact.filename { + Text(artifact.path) + .font(.caption) + .foregroundStyle(.secondary) + .textSelection(.enabled) + } + + Text(metadataText) + .font(.caption) + .foregroundStyle(.secondary) + } + + Spacer(minLength: 12) + + Button { + onDownload() + } label: { + Image(systemName: artifact.isDownloadable ? "arrow.down.circle" : "archivebox") + .imageScale(.large) + } + .disabled(!artifact.isDownloadable) + .accessibilityLabel(artifact.isDownloadable ? "Download \(artifact.filename)" : "\(artifact.filename) is unavailable") + } + } + + private var metadataText: String { + var parts = [artifact.size.formattedByteCount] + parts.append("Created \(artifact.created.relativeDescription)") + if !artifact.isDownloadable { + parts.append("Unavailable") + } + return parts.joined(separator: " • ") + } +} + private struct EditResubmitBuildSheet: View { let viewModel: BuildDetailViewModel let job: JobDetail diff --git a/Hutch/Views/Builds/BuildDetailViewModel.swift b/Hutch/Views/Builds/BuildDetailViewModel.swift index 803ab33..9566e0d 100644 --- a/Hutch/Views/Builds/BuildDetailViewModel.swift +++ b/Hutch/Views/Builds/BuildDetailViewModel.swift @@ -86,6 +86,7 @@ final class BuildDetailViewModel { image manifest tasks { name status log { fullURL } } + artifacts { id created path size url } log { fullURL } owner { canonicalName } } @@ -237,6 +238,7 @@ final class BuildDetailViewModel { status: .cancelled, note: job.note, tags: job.tags, visibility: job.visibility, image: job.image, manifest: job.manifest, tasks: job.tasks, + artifacts: job.artifacts, log: job.log, owner: job.owner ) stopAutoRefresh() |
