gitbay/Builds/BuildModels.swift

v1.12.0
gitbay-ios/gitbay/Builds/BuildModels.swift history · blame · raw

87 lines · 2838 bytes

 1import Foundation
 2import SwiftUI
 3
 4/// One row of `build list`.
 5nonisolated struct Build: Decodable, Sendable, Hashable, Identifiable {
 6    let number: Int64
 7    let job: String
 8    let status: String
 9    let sha: String
10    let ref: String
11    let createdAt: Date
12    let finishedAt: Date?
13    /// The built commit's subject, which `build list` resolves since
14    /// gitbay v1.33.0. Absent when the commit is gone.
15    let subject: String?
16    /// `build show` only (krz/gitbay#266). `failedStep` is 1-based and
17    /// absent when the runner named none.
18    var durationS: Int64? = nil
19    var steps: [String]? = nil
20    var failedStep: Int? = nil
21    var failedReason: String? = nil
22
23    enum CodingKeys: String, CodingKey {
24        case number, job, status, sha, ref, subject, steps
25        case createdAt = "created_at"
26        case finishedAt = "finished_at"
27        case durationS = "duration_s"
28        case failedStep = "failed_step"
29        case failedReason = "failed_reason"
30    }
31
32    var id: Int64 { number }
33    var shortSHA: String { String(sha.prefix(10)) }
34    /// What a row leads with. A sha alone does not say what was built
35    /// (krz/gitbay#241).
36    var title: String {
37        if let subject, !subject.isEmpty { return "#\(number) \(subject)" }
38        return "#\(number) \(shortSHA)"
39    }
40}
41
42/// `build jobs` — what a trigger can name. A repo with no CI config has
43/// none, which is an empty state rather than an error.
44nonisolated struct CIJob: Decodable, Sendable, Hashable, Identifiable {
45    let name: String
46    let schedule: String?
47    let tags: String?
48
49    var id: String { name }
50
51    /// Why this job runs, in the words the server uses.
52    var trigger: String {
53        if let schedule, !schedule.isEmpty { return "schedule \(schedule)" }
54        if let tags, !tags.isEmpty { return "tags \(tags)" }
55        return "on push"
56    }
57}
58
59extension Build {
60    /// The list row and the detail header must agree on what a status
61    /// looks like; the strings come from the registry.
62    var statusIcon: String {
63        switch status {
64        case "success": "checkmark.circle.fill"
65        case "failure", "error": "xmark.circle.fill"
66        case "running": "circle.dotted"
67        case "queued", "pending": "clock"
68        default: "questionmark.circle"
69        }
70    }
71
72    /// How long a build ran, as the web's "ran" puts it.
73    static func ran(seconds: Int64?) -> String? {
74        guard let seconds, seconds > 0 else { return nil }
75        return Duration.seconds(seconds)
76            .formatted(.units(allowed: [.hours, .minutes, .seconds], width: .narrow))
77    }
78
79    var statusColor: Color {
80        switch status {
81        case "success": .gbOK
82        case "failure", "error": .gbBad
83        case "running", "queued", "pending": .gbWarn
84        default: .secondary
85        }
86    }
87}