gitbay/Builds/BuildListViewModel.swift

v1.4.0
gitbay-ios/gitbay/Builds/BuildListViewModel.swift history · blame · raw

79 lines · 2605 bytes

 1import Foundation
 2import Observation
 3
 4/// `build list <repo>`, narrowed by `filter`, thirty to a page as the web
 5/// pages it. Rows stay in server order: a client sort would reorder them
 6/// across a page boundary.
 7@Observable
 8@MainActor
 9final class BuildListViewModel {
10
11    let list: PagedListModel<Build>
12    /// A change reloads from the first page. The sheet edits a copy and
13    /// assigns once, so a typed branch does not fire a request per
14    /// keystroke.
15    var filter = BuildFilter() {
16        didSet {
17            guard filter != oldValue else { return }
18            list.argv = ["build", "list", repoPath] + filter.flags()
19            list.emptyMessage = filter.emptyMessage
20            reloadTask?.cancel()
21            reloadTask = Task { await list.reload() }
22        }
23    }
24    private var reloadTask: Task<Void, Never>?
25    /// The jobs a trigger can name. Empty when the repo has no CI
26    /// config — the web hides its trigger form in that case.
27    private(set) var jobs: [CIJob] = []
28    private(set) var actionError: String?
29    private(set) var working = false
30
31    private let client: GitbayClient
32    let repoPath: String
33
34    init(client: GitbayClient, repoPath: String) {
35        self.client = client
36        self.repoPath = repoPath
37        list = PagedListModel(
38            client: client,
39            argv: ["build", "list", repoPath] + BuildFilter().flags(),
40            emptyMessage: BuildFilter().emptyMessage,
41            pageSize: 30
42        )
43    }
44
45    var state: LoadState<[Build]> { list.state }
46
47    func load() async {
48        reloadTask?.cancel()
49        await list.reload()
50    }
51
52    /// The list's `.task`: reads only while nothing is shown yet.
53    func loadIfNeeded() async {
54        guard list.needsLoad else { return }
55        await load()
56    }
57
58    /// `build jobs <owner/name>` — what the picker offers. A repo
59    /// without a CI config answers "not found"; that means no jobs, not
60    /// a failure worth showing.
61    func loadJobs() async {
62        jobs = (try? await client.readList(["build", "jobs", repoPath], of: CIJob.self)) ?? []
63    }
64
65    /// `build trigger <owner/name> <job>` — queue a job now.
66    func trigger(job: String) async {
67        working = true
68        actionError = nil
69        defer { working = false }
70        do {
71            try await client.run(["build", "trigger", repoPath, job])
72            await load()
73        } catch let error as GitbayError {
74            actionError = error.userFacingMessage
75        } catch {
76            actionError = GitbayError.transport(error).userFacingMessage
77        }
78    }
79}