| 1 | import { |
| 2 | EnvironmentId, |
| 3 | ProjectId, |
| 4 | type PullRequestDiffResult, |
| 5 | type PullRequestListEntry, |
| 6 | } from "@t3tools/contracts"; |
| 7 | import { useAtomRefresh } from "@effect/atom-react"; |
| 8 | import { useCallback, useMemo, useState } from "react"; |
| 9 | |
| 10 | import { composerPullRequests } from "../../state/pull-requests"; |
| 11 | import { useEnvironmentQuery } from "../../state/query"; |
| 12 | import { usePullToRefresh } from "../../state/use-pull-to-refresh"; |
| 13 | import type { ReviewParsedDiff } from "../review/reviewModel"; |
| 14 | import { |
| 15 | combinePullRequestDiffPages, |
| 16 | retainPullRequestDiffPage, |
| 17 | type PullRequestDiffPage, |
| 18 | } from "./pullRequestDiffPages"; |
| 19 | |
| 20 | export type PullRequestParams = { |
| 21 | readonly environmentId: string; |
| 22 | readonly projectId: string; |
| 23 | readonly repository: string; |
| 24 | readonly number: number; |
| 25 | readonly host?: string; |
| 26 | readonly initialSummary?: PullRequestListEntry; |
| 27 | }; |
| 28 | |
| 29 | export type PullRequestDiffParams = PullRequestParams & { readonly commit?: string }; |
| 30 | |
| 31 | export function pullRequestTarget(params: PullRequestParams) { |
| 32 | return { |
| 33 | environmentId: EnvironmentId.make(params.environmentId), |
| 34 | input: { |
| 35 | projectId: ProjectId.make(params.projectId), |
| 36 | repository: params.repository, |
| 37 | number: params.number, |
| 38 | ...(params.host ? { host: params.host } : {}), |
| 39 | }, |
| 40 | }; |
| 41 | } |
| 42 | |
| 43 | const NO_PAGES: readonly PullRequestDiffPage[] = []; |
| 44 | |
| 45 | export function usePullRequestDiff(params: PullRequestDiffParams) { |
| 46 | const reference = pullRequestTarget(params); |
| 47 | const target = { |
| 48 | ...reference, |
| 49 | input: { ...reference.input, ...(params.commit ? { commit: params.commit } : {}) }, |
| 50 | }; |
| 51 | const key = JSON.stringify(target); |
| 52 | const firstPageAtom = composerPullRequests.diff(target); |
| 53 | const refreshFirstPage = useAtomRefresh(firstPageAtom); |
| 54 | const { refreshing, onRefresh } = usePullToRefresh(firstPageAtom); |
| 55 | const [state, setState] = useState<{ |
| 56 | key: string; |
| 57 | cursor: string | null; |
| 58 | pages: readonly PullRequestDiffPage[]; |
| 59 | }>({ key, cursor: null, pages: NO_PAGES }); |
| 60 | const cursor = state.key === key ? state.cursor : null; |
| 61 | const query = useEnvironmentQuery( |
| 62 | composerPullRequests.diff({ |
| 63 | ...target, |
| 64 | input: { ...target.input, ...(cursor ? { cursor } : {}) }, |
| 65 | }), |
| 66 | ); |
| 67 | const pages = useMemo(() => { |
| 68 | const loaded = state.key === key ? state.pages : NO_PAGES; |
| 69 | return query.data ? retainPullRequestDiffPage(loaded, cursor, query.data) : loaded; |
| 70 | }, [cursor, key, query.data, state.key, state.pages]); |
| 71 | if (state.key !== key || state.pages !== pages) setState({ key, cursor, pages }); |
| 72 | |
| 73 | const [parseCache] = useState(() => new WeakMap<PullRequestDiffResult, ReviewParsedDiff>()); |
| 74 | const parsed = useMemo( |
| 75 | () => combinePullRequestDiffPages(pages, parseCache, key), |
| 76 | [key, pages, parseCache], |
| 77 | ); |
| 78 | const patch = useMemo(() => pages.map((page) => page.result.patch).join("\n"), [pages]); |
| 79 | const nextCursor = pages.at(-1)?.result.nextCursor ?? null; |
| 80 | const canLoadMore = |
| 81 | nextCursor !== null && nextCursor !== cursor && !query.isPending && query.error === null; |
| 82 | const loadMore = useCallback(() => { |
| 83 | if (!canLoadMore) return; |
| 84 | setState({ key, cursor: nextCursor, pages }); |
| 85 | }, [canLoadMore, key, nextCursor, pages, setState]); |
| 86 | const refresh = useCallback(() => { |
| 87 | setState({ key, cursor: null, pages: NO_PAGES }); |
| 88 | refreshFirstPage(); |
| 89 | }, [key, refreshFirstPage, setState]); |
| 90 | const onPullRefresh = useCallback(() => { |
| 91 | setState({ key, cursor: null, pages: NO_PAGES }); |
| 92 | onRefresh(); |
| 93 | }, [key, onRefresh, setState]); |
| 94 | return { |
| 95 | ...query, |
| 96 | refresh, |
| 97 | parsed, |
| 98 | patch, |
| 99 | nextCursor, |
| 100 | canLoadMore, |
| 101 | loadMore, |
| 102 | refreshing, |
| 103 | onPullRefresh, |
| 104 | }; |
| 105 | } |