krz/hutch

an ios client for sourcehut

clone: git clone https://gitbay.org/krz/hutch.git

v2.16.0: Hutch/Models/Builds.swift · raw

  1import Foundation
  2
  3// MARK: - Enums
  4
  5/// Status of a build job.
  6enum JobStatus: String, Codable, Sendable {
  7    case pending = "PENDING"
  8    case queued = "QUEUED"
  9    case running = "RUNNING"
 10    case success = "SUCCESS"
 11    case failed = "FAILED"
 12    case cancelled = "CANCELLED"
 13    case timeout = "TIMEOUT"
 14
 15    /// Whether the job can be cancelled.
 16    var isCancellable: Bool {
 17        switch self {
 18        case .pending, .queued, .running: true
 19        default: false
 20        }
 21    }
 22
 23    /// Whether the job has reached a terminal state and no longer changes.
 24    var isTerminal: Bool {
 25        switch self {
 26        case .success, .failed, .cancelled, .timeout: true
 27        case .pending, .queued, .running: false
 28        }
 29    }
 30}
 31
 32/// Status of a single build task within a job.
 33enum TaskStatus: String, Codable, Sendable {
 34    case pending = "PENDING"
 35    case running = "RUNNING"
 36    case success = "SUCCESS"
 37    case failed = "FAILED"
 38    case skipped = "SKIPPED"
 39}
 40
 41// MARK: - Build Task
 42
 43/// A single task within a build job.
 44struct BuildTask: Codable, Sendable, Identifiable, Equatable {
 45    private(set) var ordinal: Int?
 46    let name: String
 47    let status: TaskStatus
 48    let log: BuildLog?
 49
 50    var id: String {
 51        if let ordinal {
 52            return "\(ordinal):\(name)"
 53        }
 54        return [name, log?.fullURL, status.rawValue]
 55            .compactMap { $0 }
 56            .joined(separator: "::")
 57    }
 58
 59    var logCacheKey: String {
 60        id
 61    }
 62
 63    func withOrdinal(_ ordinal: Int) -> BuildTask {
 64        var task = self
 65        task.ordinal = ordinal
 66        return task
 67    }
 68
 69    private enum CodingKeys: String, CodingKey {
 70        case name, status, log
 71    }
 72}
 73
 74// MARK: - Job Summary (for list view)
 75
 76/// Lightweight job model matching the fields returned by the jobs list query.
 77struct JobSummary: Codable, Sendable, Identifiable, Hashable {
 78    let id: Int
 79    let created: Date
 80    let updated: Date
 81    let status: JobStatus
 82    let note: String?
 83    let tags: [String]
 84    let visibility: Visibility?
 85    let image: String?
 86    let tasks: [JobTaskSummary]
 87
 88    /// Number of completed (success) tasks.
 89    var completedTaskCount: Int {
 90        tasks.filter { $0.status == .success }.count
 91    }
 92
 93    /// Display label: note if available, otherwise tags joined.
 94    var displayLabel: String {
 95        if let note, !note.isEmpty {
 96            return note
 97        }
 98        if !tags.isEmpty {
 99            return tags.joined(separator: ", ")
100        }
101        return "Job #\(id)"
102    }
103}
104
105/// Minimal task info for the list query.
106struct JobTaskSummary: Codable, Sendable, Hashable {
107    let name: String
108    let status: TaskStatus
109}
110
111// MARK: - Job Detail (for detail view)
112
113/// Full job model with all fields for the detail view.
114struct JobDetail: Codable, Sendable, Equatable {
115    let id: Int
116    let created: Date
117    let updated: Date
118    let status: JobStatus
119    let note: String?
120    let tags: [String]
121    let visibility: Visibility?
122    let image: String?
123    let manifest: String?
124    var tasks: [BuildTask]
125    let log: BuildLog?
126    let owner: Entity
127}
128
129/// The log associated with a build job.
130struct BuildLog: Codable, Sendable, Equatable {
131    let fullURL: String
132}
133
134// MARK: - Job Group
135
136/// A group of related build jobs.
137struct JobGroup: Codable, Sendable, Identifiable {
138    let id: Int
139    let created: Date
140    let note: String?
141    let jobs: [JobSummary]
142}