Skip to content
Source
Sign inSign up
Open History to see the last commit

use-thread-pr.ts

126 lines4.5 KB

1import { useAtomValue } from "@effect/atom-react";
2import type { EnvironmentThreadShell } from "@t3tools/client-runtime/state/shell";
3import {
4 createLinkedPullRequestSummaryAtomFamily,
5 pullRequestDetailToVcsStatus,
6} from "@t3tools/client-runtime/state/pull-requests";
7import type { EnvironmentId, PullRequestSummary } from "@t3tools/contracts";
8import * as Effect from "effect/Effect";
9import { Atom } from "effect/reactivity";
10import { use, useMemo } from "react";
11​
12import { connectionAtomRuntime } from "../connection/runtime";
13import { appAtomRegistry } from "./atom-registry";
14import { serverEnvironment } from "./server";
15import { environmentThreadShells } from "./threads";
16import { useEnvironmentQuery } from "./query";
17import { ThreadPrDemandContext } from "./thread-pr-demand";
18import {
19 knownPullRequestSummary,
20 rememberBounded,
21 resolveThreadPrSource,
22 presentThreadPr,
23 type ThreadPrPresentation,
24} from "./thread-pr-presentation";
25​
26const MAX_PULL_REQUEST_SNAPSHOTS = 500;
27​
28// One bounded cache survives row virtualization (the summary query is dropped once idle) without
29// retaining one live atom for every pull request ever seen.
30const pullRequestSnapshotsAtom = Atom.make<ReadonlyMap<string, PullRequestSummary>>(new Map()).pipe(
31 Atom.keepAlive,
32 Atom.withLabel("mobile:thread-pr-snapshots"),
33);
34​
35function snapshotKey(
36 environmentId: EnvironmentId,
37 reference: { readonly projectId: string; readonly repository: string; readonly number: number },
38): string {
39 return JSON.stringify([
40 environmentId,
41 reference.projectId,
42 reference.repository,
43 reference.number,
44 ]);
46​
47const pullRequestSummaryAtom = createLinkedPullRequestSummaryAtomFamily(
48 connectionAtomRuntime,
49 undefined,
50 ({ environmentId, input }, summary, registry) =>
51 Effect.sync(() =>
52 registry.update(pullRequestSnapshotsAtom, (current) =>
53 rememberBounded(
54 current,
55 snapshotKey(environmentId, input),
56 summary,
57 MAX_PULL_REQUEST_SNAPSHOTS,
58 ),
59 ),
60 ),
61);
62​
63export { type ThreadPr, type ThreadPrPresentation } from "./thread-pr-presentation";
64​
65/** Seeds a pull request page with a summary already in memory, never another pull request's. */
66export function withKnownPullRequestSummary<
67 Params extends { readonly host?: string; readonly repository: string; readonly number: number },
68>(params: Params) {
69 const initialSummary =
70 params.host === undefined
71 ? undefined
72 : knownPullRequestSummary(
73 { host: params.host, repository: params.repository, number: params.number },
74 appAtomRegistry.get(environmentThreadShells.threadShellsAtom),
75 appAtomRegistry.get(pullRequestSnapshotsAtom).values(),
76 );
77 return initialSummary === undefined ? params : { ...params, initialSummary };
79​
80export function useThreadPr(thread: EnvironmentThreadShell): ThreadPrPresentation | null {
81 const demand = use(ThreadPrDemandContext);
82 const supportsLinks = useAtomValue(
83 serverEnvironment.configValueAtom(thread.environmentId),
84 (config) => config !== null,
85 );
86 const { linkedPresentation, pullRequestRef } = useMemo(
87 () =>
88 resolveThreadPrSource(
89 {
90 pullRequests: thread.pullRequests,
91 linkedPullRequest: thread.linkedPullRequest,
92 branchPullRequest: thread.branchPullRequest,
93 },
94 supportsLinks,
95 ),
96 [thread.pullRequests, thread.linkedPullRequest, thread.branchPullRequest, supportsLinks],
97 );
98 const key = pullRequestRef === null ? null : snapshotKey(thread.environmentId, pullRequestRef);
99 // Select this row's entry so answers for other pull requests do not re-render it.
100 const snapshot = useAtomValue(pullRequestSnapshotsAtom, (current) =>
101 key === null ? undefined : current.get(key),
102 );
103 const pullRequestSummary = useEnvironmentQuery(
104 pullRequestRef === null || !demand
105 ? null
106 : pullRequestSummaryAtom({
107 environmentId: thread.environmentId,
108 input: {
109 projectId: pullRequestRef.projectId,
110 repository: pullRequestRef.repository,
111 number: pullRequestRef.number,
112 },
113 }),
114 );
115 const summary = pullRequestRef === null ? undefined : (pullRequestSummary.data ?? snapshot);
116 // Rows pass `pr` into callbacks; a fresh object per render would invalidate them.
117 const summaryPresentation =
118 summary === undefined
119 ? null
120 : presentThreadPr(pullRequestDetailToVcsStatus(summary), {
121 kind: summary.provider,
122 name: summary.provider,
123 baseUrl: "",
124 });
125 return linkedPresentation ?? summaryPresentation;