summaryrefslogtreecommitdiff
path: root/Hutch/Models/Builds.swift
blob: 6694c35130b2067b72ebafb82e7d243d8bce99a1 (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
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]
}