Showing the changes to apps/web/src/components/pullRequest/pullRequestStackSnapshot.ts (1 of 53 files). Show all
Unified · Split
| @@ -0,0 +1,73 @@ | |||
| 1 | import type { PullRequestRef, PullRequestStack, ThreadPullRequestLink } from "@t3tools/contracts"; | ||
| 2 | |||
| 3 | /** Saved native membership is enough for navigation, but never supplies action head SHAs. */ | ||
| 4 | export function savedPullRequestStack( | ||
| 5 | links: ReadonlyArray<ThreadPullRequestLink>, | ||
| 6 | reference: PullRequestRef, | ||
| 7 | ): PullRequestStack | null { | ||
| 8 | const host = reference.host?.toLowerCase(); | ||
| 9 | if (!host) return null; | ||
| 10 | const matching = links.filter( | ||
| 11 | (link) => | ||
| 12 | link.host.toLowerCase() === host && | ||
| 13 | link.repository.toLowerCase() === reference.repository.toLowerCase(), | ||
| 14 | ); | ||
| 15 | const exact = matching.filter((link) => link.number === reference.number); | ||
| 16 | const candidates = | ||
| 17 | exact.length > 0 | ||
| 18 | ? exact | ||
| 19 | : matching.filter((link) => | ||
| 20 | link.stack?.layers.some((layer) => layer.number === reference.number), | ||
| 21 | ); | ||
| 22 | const newest = candidates.toSorted( | ||
| 23 | (a, b) => | ||
| 24 | Date.parse(b.snapshot?.syncedAt ?? b.linkedAt) - | ||
| 25 | Date.parse(a.snapshot?.syncedAt ?? a.linkedAt), | ||
| 26 | )[0]; | ||
| 27 | const stack = newest?.stack; | ||
| 28 | if (!stack || !stack.layers.some((layer) => layer.number === reference.number)) return null; | ||
| 29 | return { | ||
| 30 | id: stack.id, | ||
| 31 | number: stack.number, | ||
| 32 | url: stack.url, | ||
| 33 | base: stack.base, | ||
| 34 | layers: stack.layers.map((layer) => { | ||
| 35 | const snapshot = matching | ||
| 36 | .filter((link) => link.number === layer.number) | ||
| 37 | .toSorted( | ||
| 38 | (a, b) => | ||
| 39 | Date.parse(b.snapshot?.syncedAt ?? b.linkedAt) - | ||
| 40 | Date.parse(a.snapshot?.syncedAt ?? a.linkedAt), | ||
| 41 | )[0]?.snapshot; | ||
| 42 | return { | ||
| 43 | ...layer, | ||
| 44 | ...(snapshot ? { title: snapshot.title, isDraft: snapshot.isDraft } : {}), | ||
| 45 | }; | ||
| 46 | }), | ||
| 47 | }; | ||
| 48 | } | ||
| 49 | |||
| 50 | /** A fresh absence overrides saved membership; failed refreshes preserve available navigation. */ | ||
| 51 | export function pullRequestStackView( | ||
| 52 | query: { | ||
| 53 | data: PullRequestStack | null; | ||
| 54 | isSuccess: boolean; | ||
| 55 | isPending: boolean; | ||
| 56 | error: string | null; | ||
| 57 | }, | ||
| 58 | saved: PullRequestStack | null, | ||
| 59 | ) { | ||
| 60 | const data = query.isSuccess ? query.data : (query.data ?? saved); | ||
| 61 | return { | ||
| 62 | data, | ||
| 63 | isFresh: query.isSuccess && !query.isPending, | ||
| 64 | notice: | ||
| 65 | data === null | ||
| 66 | ? null | ||
| 67 | : query.error | ||
| 68 | ? "Stack data may be stale. We couldn’t refresh it." | ||
| 69 | : !query.isSuccess || query.isPending | ||
| 70 | ? "Refreshing stack… Showing saved data." | ||
| 71 | : null, | ||
| 72 | }; | ||
| 73 | } | ||