summaryrefslogtreecommitdiff
path: root/Hutch/Views/Tickets/TicketDetailViewModel.swift
diff options
context:
space:
mode:
Diffstat (limited to 'Hutch/Views/Tickets/TicketDetailViewModel.swift')
-rw-r--r--Hutch/Views/Tickets/TicketDetailViewModel.swift120
1 files changed, 84 insertions, 36 deletions
diff --git a/Hutch/Views/Tickets/TicketDetailViewModel.swift b/Hutch/Views/Tickets/TicketDetailViewModel.swift
index 483f749..df2fd46 100644
--- a/Hutch/Views/Tickets/TicketDetailViewModel.swift
+++ b/Hutch/Views/Tickets/TicketDetailViewModel.swift
@@ -98,7 +98,7 @@ private struct LabelsPage: Decodable, Sendable {
@MainActor
final class TicketDetailViewModel {
private static func cacheKey(ownerUsername: String, trackerRid: String, ticketId: Int) -> String {
- "ticket.detail.\(ownerUsername).\(trackerRid).\(ticketId)"
+ APICacheKeys.ticketDetail(owner: ownerUsername, trackerRid: trackerRid, ticketId: ticketId)
}
let ownerUsername: String
@@ -114,6 +114,8 @@ final class TicketDetailViewModel {
private(set) var isPerformingAction = false
private(set) var trackerLabels: [TicketLabel] = []
private(set) var rawTicketResponse: String?
+ private(set) var cacheMetadata: CacheEntryMetadata?
+ private(set) var isRefreshingCachedData = false
var commentText = ""
var error: String?
@@ -291,30 +293,25 @@ final class TicketDetailViewModel {
rawTicketResponse = nil
do {
- let result = try await client.execute(
+ let result = try await client.executeCached(
service: .todo,
query: Self.detailQuery,
variables: [
"rid": trackerRid,
"ticketId": ticketId
],
- responseType: TicketDetailResponse.self
- )
- 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
+ responseType: TicketDetailResponse.self,
+ cacheKey: Self.cacheKey(ownerUsername: ownerUsername, trackerRid: trackerRid, ticketId: ticketId),
+ resourceType: .ticketDetail,
+ ttl: APICacheTTLs.ticketDetail,
+ policy: .cacheFirstThenRefresh
)
- events = payload.events.results.sorted(by: Self.timelineOrder)
+ apply(result.value, metadata: result.metadata)
+ if result.isFromCache {
+ isLoading = false
+ await refreshTicketInBackground()
+ return
+ }
} catch {
self.error = error.userFacingMessage
}
@@ -329,7 +326,7 @@ final class TicketDetailViewModel {
do {
let cacheKey = Self.cacheKey(ownerUsername: ownerUsername, trackerRid: trackerRid, ticketId: ticketId)
- let result = try await client.executeAndCache(
+ let result = try await client.executeCached(
service: .todo,
query: Self.detailQuery,
variables: [
@@ -337,25 +334,14 @@ final class TicketDetailViewModel {
"ticketId": ticketId
],
responseType: TicketDetailResponse.self,
- cacheKey: cacheKey
+ cacheKey: cacheKey,
+ resourceType: .ticketDetail,
+ ttl: APICacheTTLs.ticketDetail,
+ policy: .refreshIgnoringCache
)
- rawTicketResponse = client.responseCache.get(forKey: cacheKey)
+ rawTicketResponse = await client.cachedPayload(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)
+ apply(result.value, metadata: result.metadata)
} catch {
self.error = error.userFacingMessage
}
@@ -391,6 +377,7 @@ final class TicketDetailViewModel {
events.append(event)
events.sort(by: Self.timelineOrder)
commentText = ""
+ await invalidateAfterMutation()
} catch {
self.error = error.userFacingMessage
}
@@ -423,6 +410,7 @@ final class TicketDetailViewModel {
],
responseType: UpdateStatusResponse.self
)
+ await invalidateAfterMutation()
// Re-fetch the ticket to get updated status/resolution
await reloadTicketPreservingDebugState()
} catch {
@@ -457,6 +445,7 @@ final class TicketDetailViewModel {
],
responseType: AssignUserResponse.self
)
+ await invalidateAfterMutation()
// Reload to reflect the change
await reloadTicketPreservingDebugState()
} catch {
@@ -503,6 +492,7 @@ final class TicketDetailViewModel {
],
responseType: AssignUserResponse.self
)
+ await invalidateAfterMutation()
await reloadTicketPreservingDebugState()
} catch {
ticket = TicketDetail(
@@ -550,6 +540,7 @@ final class TicketDetailViewModel {
],
responseType: UnassignUserResponse.self
)
+ await invalidateAfterMutation()
// Reload to reflect the change
await reloadTicketPreservingDebugState()
} catch {
@@ -575,6 +566,7 @@ final class TicketDetailViewModel {
],
responseType: LabelTicketResponse.self
)
+ await invalidateAfterMutation()
await reloadTicketPreservingDebugState()
} catch {
self.error = error.userFacingMessage
@@ -599,6 +591,7 @@ final class TicketDetailViewModel {
],
responseType: UnlabelTicketResponse.self
)
+ await invalidateAfterMutation()
await reloadTicketPreservingDebugState()
} catch {
self.error = error.userFacingMessage
@@ -654,6 +647,61 @@ final class TicketDetailViewModel {
}
}
+ private func refreshTicketInBackground() async {
+ guard !isRefreshingCachedData else { return }
+ isRefreshingCachedData = true
+ defer { isRefreshingCachedData = false }
+
+ do {
+ let result = try await client.executeCached(
+ service: .todo,
+ query: Self.detailQuery,
+ variables: [
+ "rid": trackerRid,
+ "ticketId": ticketId
+ ],
+ responseType: TicketDetailResponse.self,
+ cacheKey: Self.cacheKey(ownerUsername: ownerUsername, trackerRid: trackerRid, ticketId: ticketId),
+ resourceType: .ticketDetail,
+ ttl: APICacheTTLs.ticketDetail,
+ policy: .refreshIgnoringCache
+ )
+ apply(result.value, metadata: result.metadata)
+ } catch {
+ if ticket == nil {
+ self.error = error.userFacingMessage
+ }
+ }
+ }
+
+ private func apply(_ response: TicketDetailResponse, metadata: CacheEntryMetadata?) {
+ cacheMetadata = metadata
+ let payload = response.tracker.ticket
+ let updatedTicket = 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
+ )
+ ticket = updatedTicket
+ let updatedEvents = payload.events.results.sorted(by: Self.timelineOrder)
+ events = updatedEvents
+ }
+
+ private func invalidateAfterMutation() async {
+ await client.invalidateCache(prefix: APICacheKeys.prefix(SRHTService.todo.rawValue, "ticket"))
+ await client.invalidateCache(prefix: APICacheKeys.prefix(SRHTService.todo.rawValue, "tickets"))
+ await client.invalidateCache(prefix: APICacheKeys.prefix(SRHTService.todo.rawValue, "tracker"))
+ await client.invalidateCache(prefix: APICacheKeys.prefix("home"))
+ }
+
static func matchesAssignee(_ entity: Entity, user: User) -> Bool {
let assigneeCanonical = normalizedCanonicalName(entity.canonicalName)
let userCanonical = normalizedCanonicalName(user.canonicalName)