Skip to content

Commit d3ad1bb

Browse files
bherilameta-codesync[bot]
authored andcommitted
✨ recoil to jotai migration 14/n - version and thread tracking
Summary: Continue the Recoil to Jotai migration by moving version navigation and thread-by-commit atoms. This enables components that track which PR version is being viewed and display comment counts per commit to use the simpler Jotai API. Some thread-related selectors remain in Recoil because they're still consumed by gitHubPullRequestThreadsForCommitFile, which is used by components not yet migrated. Differential Revision: D92246374 fbshipit-source-id: b4834ed28c514dedc16fde2e77ff228f218c5edc
1 parent 67a5676 commit d3ad1bb

12 files changed

Lines changed: 185 additions & 91 deletions

eden/contrib/reviewstack/src/PullRequest.tsx

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,30 +20,20 @@ import {stripStackInfoFromBodyHTML} from './ghstackUtils';
2020
import {
2121
gitHubOrgAndRepoAtom,
2222
gitHubPullRequestAtom,
23-
gitHubPullRequestComparableVersionsAtom,
2423
gitHubPullRequestIDAtom,
2524
gitHubPullRequestVersionDiffAtom,
2625
} from './jotai';
27-
import {
28-
gitHubPullRequest,
29-
gitHubPullRequestComparableVersions,
30-
gitHubPullRequestForParams,
31-
} from './recoil';
26+
import {gitHubPullRequest, gitHubPullRequestForParams} from './recoil';
3227
import {stripStackInfoFromSaplingBodyHTML} from './saplingStack';
3328
import {stackedPullRequest} from './stackState';
3429
import {Box, Text} from '@primer/react';
3530
import {useAtomValue, useSetAtom} from 'jotai';
3631
import {Suspense, useEffect} from 'react';
37-
import {useRecoilValue, useRecoilValueLoadable, useSetRecoilState} from 'recoil';
32+
import {useRecoilValueLoadable, useSetRecoilState} from 'recoil';
3833

3934
export default function PullRequest() {
40-
const setComparableVersions = useSetAtom(gitHubPullRequestComparableVersionsAtom);
41-
// Get the default value from Recoil (computed from versions)
42-
const recoilComparableVersions = useRecoilValue(gitHubPullRequestComparableVersions);
43-
// Initialize the Jotai atom with the Recoil-computed default on page load.
44-
useEffect(() => {
45-
setComparableVersions(recoilComparableVersions);
46-
}, [setComparableVersions, recoilComparableVersions]);
35+
// Note: comparableVersions sync is handled by JotaiRecoilSync component
36+
// which properly waits for valid data before syncing
4737

4838
return (
4939
<Suspense fallback={<CenteredSpinner />}>

eden/contrib/reviewstack/src/PullRequestLatestVersionLink.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,20 @@
77

88
import {
99
gitHubPullRequestComparableVersionsAtom,
10+
gitHubPullRequestIsViewingLatestAtom,
1011
gitHubPullRequestSelectedVersionIndexAtom,
1112
gitHubPullRequestVersionsAtom,
1213
} from './jotai';
13-
import {gitHubPullRequestIsViewingLatest} from './recoil';
1414
import {ArrowLeftIcon} from '@primer/octicons-react';
1515
import {Link, Text} from '@primer/react';
1616
import {useAtomValue, useSetAtom} from 'jotai';
1717
import {useCallback} from 'react';
18-
import {useRecoilValue} from 'recoil';
1918

2019
export default function PullRequestLatestVersionLink(): React.ReactElement | null {
2120
const versions = useAtomValue(gitHubPullRequestVersionsAtom);
2221
const setSelectedVersionIndex = useSetAtom(gitHubPullRequestSelectedVersionIndexAtom);
2322
const setComparableVersions = useSetAtom(gitHubPullRequestComparableVersionsAtom);
24-
const isViewingLatest = useRecoilValue(gitHubPullRequestIsViewingLatest);
23+
const isViewingLatest = useAtomValue(gitHubPullRequestIsViewingLatestAtom);
2524

2625
const onClick = useCallback(() => {
2726
// Reset to latest version

eden/contrib/reviewstack/src/PullRequestNewCommentInput.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@
55
* LICENSE file in the root directory of this source tree.
66
*/
77

8+
import type {ComparableVersions} from './jotai';
9+
810
import PullRequestCommentInput from './PullRequestCommentInput';
911
import {DiffSide} from './generated/graphql';
10-
import {gitHubClientAtom, gitHubPullRequestAtom} from './jotai';
11-
import type {ComparableVersions} from './jotai';
12-
import {gitHubPullRequestComparableVersionsAtom} from './jotai';
12+
import {gitHubClientAtom, gitHubPullRequestAtom,gitHubPullRequestComparableVersionsAtom} from './jotai';
1313
import {gitHubPullRequestNewCommentInputCell, gitHubPullRequestPositionForLine} from './recoil';
1414
import useRefreshPullRequest from './useRefreshPullRequest';
1515
import {Box, Text} from '@primer/react';

eden/contrib/reviewstack/src/PullRequestReviewCommentLineNumber.tsx

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,15 @@
77

88
import type {GitObjectID, ID} from './github/types';
99

10-
import {gitHubPullRequestComparableVersions} from './recoil';
1110
import {
11+
gitHubPullRequestComparableVersionsAtom,
1212
gitHubPullRequestJumpToCommentIDAtom,
1313
gitHubPullRequestSelectedVersionIndexAtom,
14-
} from './jotai/atoms';
15-
import {gitHubPullRequestVersionIndexForCommit} from './recoil';
14+
gitHubPullRequestVersionIndexForCommitAtom,
15+
} from './jotai';
1616
import {Box, Link, Text} from '@primer/react';
17-
import {useSetAtom} from 'jotai';
17+
import {useAtomValue, useSetAtom} from 'jotai';
1818
import React, {useCallback} from 'react';
19-
import {useRecoilValue, useSetRecoilState} from 'recoil';
2019

2120
type Props = {
2221
commentID: ID;
@@ -30,10 +29,10 @@ export default React.memo(function PullRequestReviewCommentLineNumber({
3029
commit,
3130
lineNumber,
3231
}: Props): React.ReactElement {
33-
const versionIndex = useRecoilValue(gitHubPullRequestVersionIndexForCommit(commit));
32+
const versionIndex = useAtomValue(gitHubPullRequestVersionIndexForCommitAtom(commit));
3433
const setJumpToCommentID = useSetAtom(gitHubPullRequestJumpToCommentIDAtom(commentID));
3534
const setSelectedVersionIndex = useSetAtom(gitHubPullRequestSelectedVersionIndexAtom);
36-
const setComparableVersions = useSetRecoilState(gitHubPullRequestComparableVersions);
35+
const setComparableVersions = useSetAtom(gitHubPullRequestComparableVersionsAtom);
3736

3837
const onClick = useCallback(() => {
3938
if (versionIndex != null) {

eden/contrib/reviewstack/src/PullRequestVersionCommitSelectorItem.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,11 @@ import BulletItems from './BulletItems';
1111
import CommentCount from './CommentCount';
1212
import CommitLink from './CommitLink';
1313
import ToggleButton from './ToggleButton';
14-
import {gitHubPullRequestComparableVersionsAtom} from './jotai';
15-
import {gitHubPullRequestThreadsForCommit} from './recoil';
14+
import {gitHubPullRequestComparableVersionsAtom, gitHubPullRequestThreadsForCommitAtom} from './jotai';
1615
import {countCommentsForThreads, formatISODate} from './utils';
1716
import {ActionList, Box, Text} from '@primer/react';
18-
import {useSetAtom} from 'jotai';
17+
import {useAtomValue, useSetAtom} from 'jotai';
1918
import React, {useCallback} from 'react';
20-
import {useRecoilValue} from 'recoil';
2119

2220
const TOGGLE_BUTTON_WIDTH = 70;
2321

@@ -52,7 +50,7 @@ export default React.memo(function PullRequestVersionCommitSelectorItem({
5250
repo: string;
5351
}): React.ReactElement {
5452
const setComparableVersions = useSetAtom(gitHubPullRequestComparableVersionsAtom);
55-
const reviewThreadsForCommit = useRecoilValue(gitHubPullRequestThreadsForCommit(commit));
53+
const reviewThreadsForCommit = useAtomValue(gitHubPullRequestThreadsForCommitAtom(commit));
5654
const commentCount = countCommentsForThreads(reviewThreadsForCommit);
5755

5856
const onClickBefore = useCallback(() => {

eden/contrib/reviewstack/src/PullRequestVersionSelectorItem.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,12 @@ import CommitLink from './CommitLink';
1313
import {
1414
gitHubPullRequestComparableVersionsAtom,
1515
gitHubPullRequestSelectedVersionIndexAtom,
16+
gitHubPullRequestThreadsByCommitAtom,
1617
} from './jotai';
17-
import {gitHubPullRequestThreadsByCommit} from './recoil';
1818
import {countCommentsForThreads, formatISODate, versionLabel} from './utils';
1919
import {ActionList, Box, Text} from '@primer/react';
20-
import {useAtom, useSetAtom} from 'jotai';
20+
import {useAtom, useAtomValue, useSetAtom} from 'jotai';
2121
import React, {useCallback} from 'react';
22-
import {useRecoilValue} from 'recoil';
2322

2423
// eslint-disable-next-line prefer-arrow-callback
2524
export default React.memo(function PullRequestVersionSelectorItem({
@@ -43,7 +42,7 @@ export default React.memo(function PullRequestVersionSelectorItem({
4342
gitHubPullRequestSelectedVersionIndexAtom,
4443
);
4544
const setComparableVersions = useSetAtom(gitHubPullRequestComparableVersionsAtom);
46-
const reviewThreadsByCommit = useRecoilValue(gitHubPullRequestThreadsByCommit);
45+
const reviewThreadsByCommit = useAtomValue(gitHubPullRequestThreadsByCommitAtom);
4746
const commentCount = commits.reduce((acc, commit) => {
4847
const reviewThreadsForCommit = reviewThreadsByCommit.get(commit.commit);
4948
if (reviewThreadsForCommit == null) {

eden/contrib/reviewstack/src/jotai/JotaiRecoilSync.tsx

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,15 @@ import {
99
gitHubCommitID,
1010
gitHubOrgAndRepo,
1111
gitHubPullRequest,
12+
gitHubPullRequestComparableVersions,
1213
gitHubPullRequestSelectedVersionIndex,
1314
gitHubPullRequestVersions,
1415
} from '../recoil';
1516
import {
1617
gitHubCommitIDAtom,
1718
gitHubOrgAndRepoAtom,
1819
gitHubPullRequestAtom,
20+
gitHubPullRequestComparableVersionsAtom,
1921
gitHubPullRequestSelectedVersionIndexAtom,
2022
gitHubPullRequestVersionsAtom,
2123
} from './atoms';
@@ -96,5 +98,34 @@ export function JotaiRecoilSync(): null {
9698
setSelectedVersionIndexRecoil(jotaiSelectedVersionIndex);
9799
}, [jotaiSelectedVersionIndex, setSelectedVersionIndexRecoil]);
98100

101+
// Bidirectional sync for comparableVersions
102+
// - Recoil -> Jotai: When versions load, Recoil computes the default (based on latest version)
103+
// - Jotai -> Recoil: When user changes comparison, Jotai updates and some Recoil selectors
104+
// may still depend on it
105+
const recoilComparableVersionsLoadable = useRecoilValueLoadable(
106+
gitHubPullRequestComparableVersions,
107+
);
108+
const jotaiComparableVersions = useAtomValue(gitHubPullRequestComparableVersionsAtom);
109+
const setComparableVersionsAtom = useSetAtom(gitHubPullRequestComparableVersionsAtom);
110+
const setComparableVersionsRecoil = useSetRecoilState(gitHubPullRequestComparableVersions);
111+
112+
useEffect(() => {
113+
// Sync Recoil -> Jotai for initial default value
114+
// Only sync if we have a valid afterCommitID (not empty string from loading state)
115+
if (recoilComparableVersionsLoadable.state === 'hasValue') {
116+
const contents = recoilComparableVersionsLoadable.contents;
117+
if (contents.afterCommitID !== '') {
118+
setComparableVersionsAtom(contents);
119+
}
120+
}
121+
}, [recoilComparableVersionsLoadable, setComparableVersionsAtom]);
122+
123+
useEffect(() => {
124+
// Sync Jotai -> Recoil when user changes comparison
125+
if (jotaiComparableVersions != null) {
126+
setComparableVersionsRecoil(jotaiComparableVersions);
127+
}
128+
}, [jotaiComparableVersions, setComparableVersionsRecoil]);
129+
99130
return null;
100131
}

eden/contrib/reviewstack/src/jotai/atoms.ts

Lines changed: 111 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,15 @@ import type {
2626
PullRequestReviewComment,
2727
} from '../github/pullRequestTimelineTypes';
2828
import type {PullsQueryInput, PullsWithPageInfo} from '../github/pullsTypes';
29-
import type {Blob, Commit, DateTime, GitObjectID, ID, Version, VersionCommit} from '../github/types';
29+
import type {
30+
Blob,
31+
Commit,
32+
DateTime,
33+
GitObjectID,
34+
ID,
35+
Version,
36+
VersionCommit,
37+
} from '../github/types';
3038

3139
import {UserHomePageQuery} from '../generated/graphql';
3240
import CachingGitHubClient, {openDatabase} from '../github/CachingGitHubClient';
@@ -260,9 +268,7 @@ export const gitHubPullRequestViewerDidAuthorAtom = atom<boolean>(get => {
260268
export const gitHubCurrentCommitAtom = atom<Promise<Commit | null>>(async get => {
261269
const client = await get(gitHubClientAtom);
262270
const oid = get(gitHubCommitIDAtom);
263-
return (client != null && oid != null) ?
264-
client.getCommit(oid) : null;
265-
271+
return client != null && oid != null ? client.getCommit(oid) : null;
266272
});
267273

268274
/**
@@ -538,6 +544,107 @@ export const gitHubPullRequestSelectedVersionCommitsAtom = atom<VersionCommit[]>
538544
return versions[selectedVersionIndex]?.commits ?? [];
539545
});
540546

547+
/**
548+
* Migrated from: gitHubPullRequestIsViewingLatest selector in recoil.ts
549+
*
550+
* Determines if the user is viewing the latest version of the PR.
551+
* Used to show/hide the "Back to Latest" link.
552+
*/
553+
export const gitHubPullRequestIsViewingLatestAtom = atom<boolean>(get => {
554+
const versions = get(gitHubPullRequestVersionsAtom);
555+
if (versions.length === 0) {
556+
return true; // Default to true during loading
557+
}
558+
559+
const selectedVersionIndex = get(gitHubPullRequestSelectedVersionIndexAtom);
560+
if (selectedVersionIndex !== versions.length - 1) {
561+
return false; // Not on the latest version
562+
}
563+
564+
const comparableVersions = get(gitHubPullRequestComparableVersionsAtom);
565+
if (comparableVersions == null) {
566+
return true; // No explicit comparison selected, assume latest
567+
}
568+
569+
// Viewing latest means: no explicit "before" selected, and "after" is the head commit
570+
const latestVersion = versions[versions.length - 1];
571+
return (
572+
comparableVersions.beforeCommitID == null &&
573+
comparableVersions.afterCommitID === latestVersion.headCommit
574+
);
575+
});
576+
577+
/**
578+
* Migrated from: gitHubPullRequestVersionIndexesByCommit selector in recoil.ts
579+
*
580+
* Internal atom that indexes versions by commit ID.
581+
*/
582+
const gitHubPullRequestVersionIndexesByCommitAtom = atom<Map<GitObjectID, number>>(get => {
583+
const versions = get(gitHubPullRequestVersionsAtom);
584+
const versionIndexByCommit = new Map<GitObjectID, number>();
585+
versions.forEach(({commits}, index) => {
586+
commits.forEach(commit => {
587+
versionIndexByCommit.set(commit.commit, index);
588+
});
589+
});
590+
return versionIndexByCommit;
591+
});
592+
593+
/**
594+
* Migrated from: gitHubPullRequestVersionIndexForCommit selectorFamily in recoil.ts
595+
*
596+
* Looks up the version index for a given commit.
597+
* Used to navigate to the version containing a specific commit.
598+
*/
599+
export const gitHubPullRequestVersionIndexForCommitAtom = atomFamily(
600+
(commit: GitObjectID) =>
601+
atom<number | null>(get => {
602+
const versionIndexesByCommit = get(gitHubPullRequestVersionIndexesByCommitAtom);
603+
return versionIndexesByCommit.get(commit) ?? null;
604+
}),
605+
(a, b) => a === b,
606+
);
607+
608+
/**
609+
* Migrated from: gitHubPullRequestThreadsByCommit selector in recoil.ts
610+
*
611+
* Groups review threads by commit ID.
612+
* Used to count comments per version.
613+
*/
614+
export const gitHubPullRequestThreadsByCommitAtom = atom<
615+
Map<GitObjectID, GitHubPullRequestReviewThread[]>
616+
>(get => {
617+
const reviewThreads = get(gitHubPullRequestReviewThreadsAtom);
618+
const threadsByCommit = new Map<GitObjectID, GitHubPullRequestReviewThread[]>();
619+
620+
reviewThreads.forEach(thread => {
621+
// All comments in the thread should have the same original commit as the first
622+
const firstComment = thread.comments[0];
623+
const commitOid = firstComment?.originalCommit?.oid;
624+
if (commitOid != null) {
625+
const existing = threadsByCommit.get(commitOid) ?? [];
626+
existing.push(thread);
627+
threadsByCommit.set(commitOid, existing);
628+
}
629+
});
630+
631+
return threadsByCommit;
632+
});
633+
634+
/**
635+
* Migrated from: gitHubPullRequestThreadsForCommit selectorFamily in recoil.ts
636+
*
637+
* Gets review threads for a specific commit.
638+
*/
639+
export const gitHubPullRequestThreadsForCommitAtom = atomFamily(
640+
(commitID: GitObjectID) =>
641+
atom<GitHubPullRequestReviewThread[]>(get => {
642+
const reviewThreadsByCommit = get(gitHubPullRequestThreadsByCommitAtom);
643+
return reviewThreadsByCommit.get(commitID) ?? [];
644+
}),
645+
(a, b) => a === b,
646+
);
647+
541648
// =============================================================================
542649
// Pull Request Review Threads
543650
// =============================================================================

eden/contrib/reviewstack/src/jotai/hooks/useSplitDiffViewData.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,15 @@ export function useSplitDiffViewData(
113113
if (recoilLoadable.state === 'loading' || commitIDsLoadable.state === 'loading') {
114114
return {state: 'loading'};
115115
}
116-
if (isPullRequest && versions.length === 0) {
117-
// Versions haven't been synced yet from Recoil
116+
117+
// PR case: versions haven't been synced yet from Recoil, or commitIDs aren't yet available.
118+
// This handles the race condition where gitHubPullRequestComparableVersionsAtom
119+
// hasn't been synced from Recoil yet, causing gitHubDiffCommitIDsAtom to return null.
120+
if (
121+
isPullRequest &&
122+
(versions.length === 0 ||
123+
(commitIDsLoadable.state === 'hasData' && commitIDsLoadable.data == null))
124+
) {
118125
return {state: 'loading'};
119126
}
120127

eden/contrib/reviewstack/src/jotai/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ export {
3838
gitHubPullRequestVersionsAtom,
3939
gitHubPullRequestSelectedVersionIndexAtom,
4040
gitHubPullRequestSelectedVersionCommitsAtom,
41+
gitHubPullRequestIsViewingLatestAtom,
42+
gitHubPullRequestVersionIndexForCommitAtom,
43+
gitHubPullRequestThreadsByCommitAtom,
44+
gitHubPullRequestThreadsForCommitAtom,
4145
gitHubThreadsForDiffFileAtom,
4246
gitHubPullRequestLineToPositionForFileAtom,
4347
gitHubPullRequestCheckRunsAtom,

0 commit comments

Comments
 (0)