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
|
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 {
var id: String { name }
let name: String
let status: TaskStatus
let log: BuildLog?
}
// 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?
let 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]
}
|