Skip to content

Commit 3b4c69f

Browse files
committed
refactor(code-review): extract comment file filtering
Generated-By: PostHog Code Task-Id: 199c1147-fca6-415b-9ea1-00a45d9b7267
1 parent ce33385 commit 3b4c69f

9 files changed

Lines changed: 184 additions & 109 deletions

File tree

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import type { PrCommentThread } from "@posthog/core/code-review/types";
2+
import type { ReactNode } from "react";
3+
4+
export type CommentFileFilter = "none" | "commented" | "unresolved";
5+
6+
export interface ReviewListItem {
7+
key: string;
8+
scrollKey?: string;
9+
filePaths?: string[];
10+
node: ReactNode;
11+
}
12+
13+
interface CommentFileFilterState {
14+
activeFilter: CommentFileFilter;
15+
visibleItems: ReviewListItem[];
16+
commentedFileCount: number;
17+
unresolvedCommentedFileCount: number;
18+
}
19+
20+
interface DeriveCommentFileFilterStateArgs {
21+
items: ReviewListItem[];
22+
requestedFilter: CommentFileFilter;
23+
commentedFilePaths?: ReadonlySet<string>;
24+
unresolvedCommentedFilePaths?: ReadonlySet<string>;
25+
}
26+
27+
export function getCommentedFilePaths(threads: Map<number, PrCommentThread>): {
28+
all: Set<string>;
29+
unresolved: Set<string>;
30+
} {
31+
const all = new Set<string>();
32+
const unresolved = new Set<string>();
33+
34+
for (const thread of threads.values()) {
35+
if (thread.comments.length === 0) continue;
36+
all.add(thread.filePath);
37+
if (!thread.isResolved) unresolved.add(thread.filePath);
38+
}
39+
40+
return { all, unresolved };
41+
}
42+
43+
export function filterReviewItemsByFilePaths(
44+
items: ReviewListItem[],
45+
filePaths: ReadonlySet<string>,
46+
): ReviewListItem[] {
47+
const filteredItems: ReviewListItem[] = [];
48+
let pendingSectionItems: ReviewListItem[] = [];
49+
50+
for (const item of items) {
51+
if (!item.filePaths) {
52+
pendingSectionItems = [item];
53+
continue;
54+
}
55+
56+
if (!item.filePaths.some((filePath) => filePaths.has(filePath))) continue;
57+
58+
filteredItems.push(...pendingSectionItems, item);
59+
pendingSectionItems = [];
60+
}
61+
62+
return filteredItems;
63+
}
64+
65+
export function deriveCommentFileFilterState({
66+
items,
67+
requestedFilter,
68+
commentedFilePaths,
69+
unresolvedCommentedFilePaths,
70+
}: DeriveCommentFileFilterStateArgs): CommentFileFilterState {
71+
if (!commentedFilePaths || !unresolvedCommentedFilePaths) {
72+
return {
73+
activeFilter: "none",
74+
visibleItems: items,
75+
commentedFileCount: 0,
76+
unresolvedCommentedFileCount: 0,
77+
};
78+
}
79+
80+
const commentedItems = filterReviewItemsByFilePaths(
81+
items,
82+
commentedFilePaths,
83+
);
84+
const unresolvedCommentedItems = filterReviewItemsByFilePaths(
85+
items,
86+
unresolvedCommentedFilePaths,
87+
);
88+
89+
let visibleItems: ReviewListItem[];
90+
switch (requestedFilter) {
91+
case "commented":
92+
visibleItems = commentedItems;
93+
break;
94+
case "unresolved":
95+
visibleItems = unresolvedCommentedItems;
96+
break;
97+
case "none":
98+
visibleItems = items;
99+
break;
100+
}
101+
102+
return {
103+
activeFilter: requestedFilter,
104+
visibleItems,
105+
commentedFileCount: commentedItems.filter((item) => item.filePaths).length,
106+
unresolvedCommentedFileCount: unresolvedCommentedItems.filter(
107+
(item) => item.filePaths,
108+
).length,
109+
};
110+
}
111+
112+
export function getEmptyReviewMessage(
113+
commentFilter: CommentFileFilter,
114+
): string {
115+
switch (commentFilter) {
116+
case "commented":
117+
return "No files with comments";
118+
case "unresolved":
119+
return "No files with unresolved comments";
120+
case "none":
121+
return "No file changes to review";
122+
}
123+
}

packages/ui/src/features/code-review/components/CloudReviewPage.tsx

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,13 @@ import { useMemo } from "react";
77
import { useDiffViewerStore } from "../../code-editor/diffViewerStore";
88
import { usePrDetails } from "../../git-interaction/usePrDetails";
99
import { useCloudChangedFiles } from "../../task-detail/hooks/useCloudChangedFiles";
10-
import { useReviewNavigationStore } from "../reviewNavigationStore";
11-
import { PatchedFileDiff } from "./PatchedFileDiff";
1210
import {
13-
buildItemIndex,
1411
getCommentedFilePaths,
1512
type ReviewListItem,
16-
ReviewShell,
17-
useReviewState,
18-
} from "./ReviewShell";
13+
} from "../commentFileFilter";
14+
import { useReviewNavigationStore } from "../reviewNavigationStore";
15+
import { PatchedFileDiff } from "./PatchedFileDiff";
16+
import { buildItemIndex, ReviewShell, useReviewState } from "./ReviewShell";
1917
import { changedFileSignature } from "./reviewItemBuilders";
2018

2119
interface CloudReviewPageProps {

packages/ui/src/features/code-review/components/DiffSettingsMenu.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@ import {
1313
DropdownMenuTrigger,
1414
} from "@posthog/quill";
1515
import { useDiffViewerStore } from "@posthog/ui/features/code-editor/diffViewerStore";
16-
17-
export type CommentFileFilter = "none" | "commented" | "unresolved";
16+
import type { CommentFileFilter } from "../commentFileFilter";
1817

1918
interface DiffSettingsMenuProps {
2019
commentedFileCount: number;

packages/ui/src/features/code-review/components/ReviewPage.tsx

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,16 @@ import { useCwd } from "../../sidebar/useCwd";
1818
import { useDiscardAllChanges } from "../../task-detail/hooks/useDiscardAllChanges";
1919
import { useDiscardFile } from "../../task-detail/hooks/useDiscardFile";
2020
import { useStageToggle } from "../../task-detail/hooks/useStageToggle";
21+
import {
22+
getCommentedFilePaths,
23+
type ReviewListItem,
24+
} from "../commentFileFilter";
2125
import { REVIEW_FILE_CACHE_TIME_MS, REVIEW_MAX_FILE_LINES } from "../constants";
2226
import { useEffectiveDiffSource } from "../hooks/useEffectiveDiffSource";
2327
import { useReviewDiffs } from "../hooks/useReviewDiffs";
2428
import { useReviewNavigationStore } from "../reviewNavigationStore";
2529
import type { DiffOptions } from "../types";
26-
import {
27-
buildItemIndex,
28-
getCommentedFilePaths,
29-
type ReviewListItem,
30-
ReviewShell,
31-
useReviewState,
32-
} from "./ReviewShell";
30+
import { buildItemIndex, ReviewShell, useReviewState } from "./ReviewShell";
3331
import {
3432
buildPatchReviewItems,
3533
buildRemoteReviewItems,

packages/ui/src/features/code-review/components/ReviewShell.tsx

Lines changed: 20 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -14,24 +14,28 @@ import {
1414
useState,
1515
} from "react";
1616
import { VList, type VListHandle } from "virtua";
17+
import {
18+
type CommentFileFilter,
19+
deriveCommentFileFilterState,
20+
getEmptyReviewMessage,
21+
type ReviewListItem,
22+
} from "../commentFileFilter";
1723
import {
1824
REVIEW_LIST_BUFFER_PX,
1925
REVIEW_LIST_ESTIMATED_ITEM_SIZE,
2026
} from "../constants";
2127
import { useReviewDraftsStore } from "../reviewDraftsStore";
2228
import { REVIEW_HOST, type ReviewHost } from "../reviewHost";
2329
import { useReviewNavigationStore } from "../reviewNavigationStore";
24-
import type { ReviewListItem, ReviewShellProps } from "../reviewShellParts";
30+
import type { ReviewShellProps } from "../reviewShellParts";
2531
import {
2632
buildItemIndex,
27-
filterReviewItemsByFilePaths,
2833
findActiveScrollKey,
2934
findRenderedScrollAnchor,
3035
isFileViewed,
3136
} from "../reviewShellParts";
3237
import { ReviewViewedContext } from "../reviewViewedContext";
3338
import { useReviewViewedStore } from "../reviewViewedStore";
34-
import type { CommentFileFilter } from "./DiffSettingsMenu";
3539
import { PendingReviewBar } from "./PendingReviewBar";
3640
import { ReviewToolbar } from "./ReviewToolbar";
3741

@@ -45,17 +49,6 @@ const SIDEBAR_MIN_WIDTH = 200;
4549
const SIDEBAR_MAX_WIDTH = 500;
4650
const SIDEBAR_DEFAULT_WIDTH = 280;
4751

48-
function getEmptyReviewMessage(commentFilter: CommentFileFilter): string {
49-
switch (commentFilter) {
50-
case "commented":
51-
return "No files with comments";
52-
case "unresolved":
53-
return "No files with unresolved comments";
54-
case "none":
55-
return "No file changes to review";
56-
}
57-
}
58-
5952
function ExpandedSidebar({ task }: { task: Task }) {
6053
const reviewHost = useService<ReviewHost>(REVIEW_HOST);
6154
const [width, setWidth] = useState(SIDEBAR_DEFAULT_WIDTH);
@@ -159,41 +152,25 @@ export function ReviewShell({
159152
const pendingNavigationRef = useRef<string | null>(null);
160153
const navigationFrameRef = useRef<number | null>(null);
161154
const [commentFilter, setCommentFilter] = useState<CommentFileFilter>("none");
162-
const activeCommentFilter =
163-
commentedFilePaths && unresolvedCommentedFilePaths ? commentFilter : "none";
164-
165-
const commentedItems = useMemo(
155+
const {
156+
activeFilter: activeCommentFilter,
157+
visibleItems,
158+
commentedFileCount,
159+
unresolvedCommentedFileCount,
160+
} = useMemo(
166161
() =>
167-
commentedFilePaths
168-
? filterReviewItemsByFilePaths(items, commentedFilePaths)
169-
: [],
170-
[commentedFilePaths, items],
162+
deriveCommentFileFilterState({
163+
items,
164+
requestedFilter: commentFilter,
165+
commentedFilePaths,
166+
unresolvedCommentedFilePaths,
167+
}),
168+
[commentFilter, commentedFilePaths, items, unresolvedCommentedFilePaths],
171169
);
172-
const unresolvedCommentedItems = useMemo(
173-
() =>
174-
unresolvedCommentedFilePaths
175-
? filterReviewItemsByFilePaths(items, unresolvedCommentedFilePaths)
176-
: [],
177-
[items, unresolvedCommentedFilePaths],
178-
);
179-
const visibleItems =
180-
activeCommentFilter === "commented"
181-
? commentedItems
182-
: activeCommentFilter === "unresolved"
183-
? unresolvedCommentedItems
184-
: items;
185170
const visibleItemIndexByFilePath = useMemo(
186171
() => buildItemIndex(visibleItems),
187172
[visibleItems],
188173
);
189-
const commentedFileCount = useMemo(
190-
() => commentedItems.filter((item) => item.filePaths).length,
191-
[commentedItems],
192-
);
193-
const unresolvedCommentedFileCount = useMemo(
194-
() => unresolvedCommentedItems.filter((item) => item.filePaths).length,
195-
[unresolvedCommentedItems],
196-
);
197174

198175
const workerFactory = useCallback(
199176
() => reviewHost.diffWorkerFactory(),

packages/ui/src/features/code-review/components/ReviewToolbar.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ import { Tooltip } from "@posthog/ui/primitives/Tooltip";
1616
import { Flex, Separator, Text } from "@radix-ui/themes";
1717
import { FoldVertical, Maximize, Minimize, UnfoldVertical } from "lucide-react";
1818
import { memo } from "react";
19-
import { type CommentFileFilter, DiffSettingsMenu } from "./DiffSettingsMenu";
19+
import type { CommentFileFilter } from "../commentFileFilter";
20+
import { DiffSettingsMenu } from "./DiffSettingsMenu";
2021
import { DiffSourceSelector } from "./DiffSourceSelector";
2122

2223
interface ReviewToolbarProps {

packages/ui/src/features/code-review/components/reviewItemBuilders.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
import type { PrCommentThread } from "@posthog/core/code-review/types";
88
import type { ChangedFile } from "@posthog/shared/domain-types";
99
import { makeFileKey } from "../../git-interaction/utils/fileKey";
10-
import type { ReviewListItem } from "../reviewShellParts";
10+
import type { ReviewListItem } from "../commentFileFilter";
1111
import type { DiffOptions } from "../types";
1212
import { PatchRow, RemoteRow, UntrackedRow } from "./ReviewRows";
1313

packages/ui/src/features/code-review/reviewShellParts.test.tsx

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,17 @@ vi.mock("../../primitives/FileIcon", () => ({
1414
FileIcon: () => <span data-testid="file-icon" />,
1515
}));
1616

17+
import {
18+
deriveCommentFileFilterState,
19+
filterReviewItemsByFilePaths,
20+
getCommentedFilePaths,
21+
type ReviewListItem,
22+
} from "./commentFileFilter";
1723
import {
1824
DeferredDiffPlaceholder,
1925
DiffFileHeader,
20-
filterReviewItemsByFilePaths,
2126
findActiveScrollKey,
2227
findRenderedScrollAnchor,
23-
getCommentedFilePaths,
24-
type ReviewListItem,
2528
} from "./reviewShellParts";
2629

2730
type FileDiffMetadata = import("@pierre/diffs/react").FileDiffMetadata;
@@ -257,4 +260,24 @@ describe("commented file filtering", () => {
257260
),
258261
).toEqual(["section:changes", "unstaged:b.ts"]);
259262
});
263+
264+
it("derives visible items and counts for the selected filter", () => {
265+
const items: ReviewListItem[] = [
266+
{ key: "a.ts", filePaths: ["a.ts"], node: <span>A</span> },
267+
{ key: "b.ts", filePaths: ["b.ts"], node: <span>B</span> },
268+
{ key: "c.ts", filePaths: ["c.ts"], node: <span>C</span> },
269+
];
270+
271+
const state = deriveCommentFileFilterState({
272+
items,
273+
requestedFilter: "unresolved",
274+
commentedFilePaths: new Set(["a.ts", "b.ts"]),
275+
unresolvedCommentedFilePaths: new Set(["b.ts"]),
276+
});
277+
278+
expect(state.activeFilter).toBe("unresolved");
279+
expect(state.visibleItems.map((item) => item.key)).toEqual(["b.ts"]);
280+
expect(state.commentedFileCount).toBe(2);
281+
expect(state.unresolvedCommentedFileCount).toBe(1);
282+
});
260283
});

0 commit comments

Comments
 (0)