summaryrefslogtreecommitdiff
path: root/Hutch/Models/Builds.swift
blob: b0c0e8dff74ec61fdc9f9646a8c21c52f9d0d9a2 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
import Foundation

// MARK: - Enums

/// Status of a build job.
enum JobStatus: String, Codable, Sendable {
    case pending = "PENDING"
    case queued = "QUEUED"
    case running = "RUNNING"
    case success = "SUCCESS"
    case failed = "FAILED"
    case cancelled = "CANCELLED"
    case timeout = "TIMEOUT"

    /// Whether the job can be cancelled.
    var isCancellable: Bool {
        switch self {
        case .pending, .queued, .running: true
        default: false
        }
    }
}

/// Status of a single build task within a job.
enum TaskStatus: String, Codable, Sendable {
    case pending = "PENDING"
    case running = "RUNNING"
    case success = "SUCCESS"
    case failed = "FAILED"
    case skipped = "SKIPPED"
}

// MARK: - Build Task

/// A single task within a build job.
struct BuildTask: Codable, Sendable, Identifiable {
    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)

/// Lightweight job model matching the fields returned by the jobs list query.
struct JobSummary: Codable, Sendable, Identifiable, Hashable {
    let id: Int
    let created: Date
    let updated: Date
    let status: JobStatus
    let note: String?
    let tags: [String]
    let visibility: Visibility?
    let image: String?
    let tasks: [JobTaskSummary]

    /// Number of completed (success) tasks.
    var completedTaskCount: Int {
        tasks.filter { $0.status == .success }.count
    }

    /// Display label: note if available, otherwise tags joined.
    var displayLabel: String {
        if let note, !note.isEmpty {
            return note
        }
        if !tags.isEmpty {
            return tags.joined(separator: ", ")
        }
        return "Job #\(id)"
    }
}

/// Minimal task info for the list query.
struct JobTaskSummary: Codable, Sendable, Hashable {
    let name: String
    let status: TaskStatus
}

// MARK: - Job Detail (for detail view)

/// Full job model with all fields for the detail view.
struct JobDetail: Codable, Sendable {
    let id: Int
    let created: Date
    let updated: Date
    let status: JobStatus
    let note: String?
    let tags: [String]
    let visibility: Visibility?
    let image: String?
    let manifest: String?
    var tasks: [BuildTask]
    let log: BuildLog?
    let owner: Entity
}

/// The log associated with a build job.
struct BuildLog: Codable, Sendable {
    let fullURL: String
}

// MARK: - Job Group

/// A group of related build jobs.
struct JobGroup: Codable, Sendable, Identifiable {
    let id: Int
    let created: Date
    let note: String?
    let jobs: [JobSummary]
}