gitbay/Builds/BuildListViewModel.swift
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}