summaryrefslogtreecommitdiff
path: root/Hutch/Views/Tickets/TicketDetailViewModel.swift
diff options
context:
space:
mode:
authorChristian Cleberg <[email protected]>2026-04-13 09:57:23 -0500
committerChristian Cleberg <[email protected]>2026-04-13 09:57:23 -0500
commit0e461a382257979037e8927e5f6b468635b0a7fe (patch)
tree0aa3a19f6f8f966b0e90f4874ed54359a7898107 /Hutch/Views/Tickets/TicketDetailViewModel.swift
parentee9f2904aa319231b7047fca3cb069f0c07019cd (diff)
downloadhutch-0e461a382257979037e8927e5f6b468635b0a7fe.tar.gz
hutch-0e461a382257979037e8927e5f6b468635b0a7fe.tar.bz2
hutch-0e461a382257979037e8927e5f6b468635b0a7fe.zip
feat: add power user actions
Implements: https://todo.sr.ht/~ccleberg/hutch/52 Implements: https://todo.sr.ht/~ccleberg/hutch/53 Implements: https://todo.sr.ht/~ccleberg/hutch/54
Diffstat (limited to 'Hutch/Views/Tickets/TicketDetailViewModel.swift')
-rw-r--r--Hutch/Views/Tickets/TicketDetailViewModel.swift66
1 files changed, 60 insertions, 6 deletions
diff --git a/Hutch/Views/Tickets/TicketDetailViewModel.swift b/Hutch/Views/Tickets/TicketDetailViewModel.swift
index 999374b..483f749 100644
--- a/Hutch/Views/Tickets/TicketDetailViewModel.swift
+++ b/Hutch/Views/Tickets/TicketDetailViewModel.swift
@@ -97,6 +97,9 @@ private struct LabelsPage: Decodable, Sendable {
@Observable
@MainActor
final class TicketDetailViewModel {
+ private static func cacheKey(ownerUsername: String, trackerRid: String, ticketId: Int) -> String {
+ "ticket.detail.\(ownerUsername).\(trackerRid).\(ticketId)"
+ }
let ownerUsername: String
let trackerName: String
@@ -110,6 +113,7 @@ final class TicketDetailViewModel {
private(set) var isSubmitting = false
private(set) var isPerformingAction = false
private(set) var trackerLabels: [TicketLabel] = []
+ private(set) var rawTicketResponse: String?
var commentText = ""
var error: String?
@@ -284,6 +288,7 @@ final class TicketDetailViewModel {
guard !isLoading else { return }
isLoading = true
error = nil
+ rawTicketResponse = nil
do {
let result = try await client.execute(
@@ -317,6 +322,47 @@ final class TicketDetailViewModel {
isLoading = false
}
+ func loadTicketWithDebugCapture() async {
+ guard !isLoading else { return }
+ isLoading = true
+ error = nil
+
+ do {
+ let cacheKey = Self.cacheKey(ownerUsername: ownerUsername, trackerRid: trackerRid, ticketId: ticketId)
+ let result = try await client.executeAndCache(
+ service: .todo,
+ query: Self.detailQuery,
+ variables: [
+ "rid": trackerRid,
+ "ticketId": ticketId
+ ],
+ responseType: TicketDetailResponse.self,
+ cacheKey: cacheKey
+ )
+ rawTicketResponse = client.responseCache.get(forKey: cacheKey)
+ .flatMap { String(data: $0, encoding: .utf8) }
+ let payload = result.tracker.ticket
+ ticket = TicketDetail(
+ id: payload.id,
+ created: payload.created,
+ updated: payload.updated,
+ title: payload.title,
+ description: payload.description,
+ status: payload.status,
+ resolution: payload.resolution,
+ authenticity: payload.authenticity,
+ submitter: payload.submitter,
+ assignees: payload.assignees,
+ labels: payload.labels
+ )
+ events = payload.events.results.sorted(by: Self.timelineOrder)
+ } catch {
+ self.error = error.userFacingMessage
+ }
+
+ isLoading = false
+ }
+
func submitComment() async {
let text = commentText.trimmingCharacters(in: .whitespacesAndNewlines)
guard !text.isEmpty, !isSubmitting else { return }
@@ -378,7 +424,7 @@ final class TicketDetailViewModel {
responseType: UpdateStatusResponse.self
)
// Re-fetch the ticket to get updated status/resolution
- await loadTicket()
+ await reloadTicketPreservingDebugState()
} catch {
self.error = error.userFacingMessage
}
@@ -412,7 +458,7 @@ final class TicketDetailViewModel {
responseType: AssignUserResponse.self
)
// Reload to reflect the change
- await loadTicket()
+ await reloadTicketPreservingDebugState()
} catch {
self.error = error.userFacingMessage
}
@@ -457,7 +503,7 @@ final class TicketDetailViewModel {
],
responseType: AssignUserResponse.self
)
- await loadTicket()
+ await reloadTicketPreservingDebugState()
} catch {
ticket = TicketDetail(
id: currentTicket.id,
@@ -505,7 +551,7 @@ final class TicketDetailViewModel {
responseType: UnassignUserResponse.self
)
// Reload to reflect the change
- await loadTicket()
+ await reloadTicketPreservingDebugState()
} catch {
self.error = error.userFacingMessage
}
@@ -529,7 +575,7 @@ final class TicketDetailViewModel {
],
responseType: LabelTicketResponse.self
)
- await loadTicket()
+ await reloadTicketPreservingDebugState()
} catch {
self.error = error.userFacingMessage
}
@@ -553,7 +599,7 @@ final class TicketDetailViewModel {
],
responseType: UnlabelTicketResponse.self
)
- await loadTicket()
+ await reloadTicketPreservingDebugState()
} catch {
self.error = error.userFacingMessage
}
@@ -600,6 +646,14 @@ final class TicketDetailViewModel {
isPerformingAction = false
}
+ private func reloadTicketPreservingDebugState() async {
+ if rawTicketResponse != nil {
+ await loadTicketWithDebugCapture()
+ } else {
+ await loadTicket()
+ }
+ }
+
static func matchesAssignee(_ entity: Entity, user: User) -> Bool {
let assigneeCanonical = normalizedCanonicalName(entity.canonicalName)
let userCanonical = normalizedCanonicalName(user.canonicalName)