Skip to content

Commit 1fbcc5a

Browse files
committed
feat(code-review): filter files with comments
Generated-By: PostHog Code Task-Id: 199c1147-fca6-415b-9ea1-00a45d9b7267
1 parent 13f7ab6 commit 1fbcc5a

7 files changed

Lines changed: 287 additions & 43 deletions

File tree

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { useReviewNavigationStore } from "../reviewNavigationStore";
1111
import { PatchedFileDiff } from "./PatchedFileDiff";
1212
import {
1313
buildItemIndex,
14+
getCommentedFilePaths,
1415
type ReviewListItem,
1516
ReviewShell,
1617
useReviewState,
@@ -37,8 +38,12 @@ export function CloudReviewPage({ task }: CloudReviewPageProps) {
3738
isLoading,
3839
} = useCloudChangedFiles(taskId, task, isReviewOpen);
3940
const { commentThreads } = usePrDetails(prUrl, {
40-
includeComments: isReviewOpen && showReviewComments,
41+
includeComments: isReviewOpen,
4142
});
43+
const commentedFilePaths = useMemo(
44+
() => (prUrl ? getCommentedFilePaths(commentThreads) : undefined),
45+
[commentThreads, prUrl],
46+
);
4247

4348
const allPaths = useMemo(() => reviewFiles.map((f) => f.path), [reviewFiles]);
4449

@@ -83,6 +88,9 @@ export function CloudReviewPage({ task }: CloudReviewPageProps) {
8388
return {
8489
key: file.path,
8590
scrollKey: file.path,
91+
filePaths: [file.path, file.originalPath].filter(
92+
(path): path is string => !!path,
93+
),
8694
node: (
8795
<PatchedFileDiff
8896
file={file}
@@ -147,6 +155,7 @@ export function CloudReviewPage({ task }: CloudReviewPageProps) {
147155
onCollapseFiles={collapseFiles}
148156
items={items}
149157
itemIndexByFilePath={itemIndexByFilePath}
158+
commentedFilePaths={commentedFilePaths}
150159
currentSignatures={currentSignatures}
151160
viewedRecord={viewedRecord}
152161
onToggleViewed={toggleViewed}

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

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { useReviewNavigationStore } from "../reviewNavigationStore";
2525
import type { DiffOptions } from "../types";
2626
import {
2727
buildItemIndex,
28+
getCommentedFilePaths,
2829
type ReviewListItem,
2930
ReviewShell,
3031
useReviewState,
@@ -110,11 +111,15 @@ export function ReviewPage({ task }: ReviewPageProps) {
110111

111112
const showReviewComments = useDiffViewerStore((s) => s.showReviewComments);
112113
const { commentThreads } = usePrDetails(prUrl, {
113-
includeComments: isReviewOpen && showReviewComments,
114+
includeComments: isReviewOpen,
114115
});
115116
const effectiveCommentThreads = showReviewComments
116117
? commentThreads
117118
: undefined;
119+
const commentedFilePaths = useMemo(
120+
() => (prUrl ? getCommentedFilePaths(commentThreads) : undefined),
121+
[commentThreads, prUrl],
122+
);
118123

119124
const isLocalActive = isReviewOpen && effectiveSource === "local";
120125

@@ -173,6 +178,7 @@ export function ReviewPage({ task }: ReviewPageProps) {
173178
branchSourceAvailable={branchSourceAvailable}
174179
prSourceAvailable={prSourceAvailable}
175180
commentThreads={effectiveCommentThreads}
181+
commentedFilePaths={commentedFilePaths}
176182
/>
177183
);
178184
}
@@ -206,6 +212,7 @@ export function ReviewPage({ task }: ReviewPageProps) {
206212
untrackedFiles={untrackedFiles}
207213
stagedPathSet={stagedPathSet}
208214
commentThreads={effectiveCommentThreads}
215+
commentedFilePaths={commentedFilePaths}
209216
effectiveSource={effectiveSource}
210217
branchSourceAvailable={branchSourceAvailable}
211218
prSourceAvailable={prSourceAvailable}
@@ -242,6 +249,7 @@ function LocalReviewContent({
242249
untrackedFiles,
243250
stagedPathSet,
244251
commentThreads,
252+
commentedFilePaths,
245253
effectiveSource,
246254
branchSourceAvailable,
247255
prSourceAvailable,
@@ -274,6 +282,7 @@ function LocalReviewContent({
274282
untrackedFiles: ChangedFile[];
275283
stagedPathSet: Set<string>;
276284
commentThreads?: Map<number, PrCommentThread>;
285+
commentedFilePaths?: ReadonlySet<string>;
277286
effectiveSource: ResolvedDiffSource;
278287
branchSourceAvailable: boolean;
279288
prSourceAvailable: boolean;
@@ -437,6 +446,7 @@ function LocalReviewContent({
437446
defaultBranch={defaultBranch}
438447
items={items}
439448
itemIndexByFilePath={itemIndexByFilePath}
449+
commentedFilePaths={commentedFilePaths}
440450
currentSignatures={currentSignatures}
441451
viewedRecord={viewedRecord}
442452
onToggleViewed={toggleViewed}
@@ -455,6 +465,7 @@ function RemoteReviewPage({
455465
branchSourceAvailable,
456466
prSourceAvailable,
457467
commentThreads,
468+
commentedFilePaths,
458469
}: {
459470
task: Task;
460471
repoPath: string | null;
@@ -466,6 +477,7 @@ function RemoteReviewPage({
466477
branchSourceAvailable: boolean;
467478
prSourceAvailable: boolean;
468479
commentThreads?: Map<number, PrCommentThread>;
480+
commentedFilePaths?: ReadonlySet<string>;
469481
}) {
470482
const taskId = task.id;
471483
const isBranch = effectiveSource === "branch";
@@ -548,6 +560,7 @@ function RemoteReviewPage({
548560
defaultBranch={defaultBranch}
549561
items={items}
550562
itemIndexByFilePath={itemIndexByFilePath}
563+
commentedFilePaths={commentedFilePaths}
551564
currentSignatures={currentSignatures}
552565
viewedRecord={reviewState.viewedRecord}
553566
onToggleViewed={reviewState.toggleViewed}

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

Lines changed: 91 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,14 @@ import { useArchivedTaskIds } from "@posthog/ui/features/archive/useArchivedTask
55
import { useCloudPrUrl } from "@posthog/ui/features/git-interaction/useCloudPrUrl";
66
import { useTaskPrStatus } from "@posthog/ui/features/sidebar/useTaskPrStatus";
77
import { Flex, Spinner, Text } from "@radix-ui/themes";
8-
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
8+
import {
9+
type ReactNode,
10+
useCallback,
11+
useEffect,
12+
useMemo,
13+
useRef,
14+
useState,
15+
} from "react";
916
import { VList, type VListHandle } from "virtua";
1017
import {
1118
REVIEW_LIST_BUFFER_PX,
@@ -16,6 +23,8 @@ import { REVIEW_HOST, type ReviewHost } from "../reviewHost";
1623
import { useReviewNavigationStore } from "../reviewNavigationStore";
1724
import type { ReviewListItem, ReviewShellProps } from "../reviewShellParts";
1825
import {
26+
buildItemIndex,
27+
filterReviewItemsByFilePaths,
1928
findActiveScrollKey,
2029
findRenderedScrollAnchor,
2130
isFileViewed,
@@ -113,6 +122,7 @@ export function ReviewShell({
113122
isEmpty,
114123
items,
115124
itemIndexByFilePath,
125+
commentedFilePaths,
116126
currentSignatures,
117127
viewedRecord,
118128
onToggleViewed,
@@ -135,6 +145,26 @@ export function ReviewShell({
135145
const lastActiveRef = useRef<string | null>(null);
136146
const pendingNavigationRef = useRef<string | null>(null);
137147
const navigationFrameRef = useRef<number | null>(null);
148+
const [showCommentedFilesOnly, setShowCommentedFilesOnly] = useState(false);
149+
const isCommentFilterActive =
150+
showCommentedFilesOnly && commentedFilePaths !== undefined;
151+
152+
const commentedItems = useMemo(
153+
() =>
154+
commentedFilePaths
155+
? filterReviewItemsByFilePaths(items, commentedFilePaths)
156+
: [],
157+
[commentedFilePaths, items],
158+
);
159+
const visibleItems = isCommentFilterActive ? commentedItems : items;
160+
const visibleItemIndexByFilePath = useMemo(
161+
() => buildItemIndex(visibleItems),
162+
[visibleItems],
163+
);
164+
const commentedFileCount = useMemo(
165+
() => commentedItems.filter((item) => item.filePaths).length,
166+
[commentedItems],
167+
);
138168

139169
const workerFactory = useCallback(
140170
() => reviewHost.diffWorkerFactory(),
@@ -147,12 +177,20 @@ export function ReviewShell({
147177
const isExpanded = reviewMode === "expanded";
148178

149179
const viewedCount = useMemo(() => {
180+
const visibleKeys = isCommentFilterActive
181+
? new Set(
182+
visibleItems.flatMap((item) =>
183+
item.scrollKey ? [item.scrollKey] : [],
184+
),
185+
)
186+
: null;
150187
let count = 0;
151188
for (const [key, sig] of currentSignatures) {
189+
if (visibleKeys && !visibleKeys.has(key)) continue;
152190
if (isFileViewed(viewedRecord[key], sig)) count++;
153191
}
154192
return count;
155-
}, [currentSignatures, viewedRecord]);
193+
}, [currentSignatures, isCommentFilterActive, viewedRecord, visibleItems]);
156194

157195
// Collapse already-viewed files on first open per task (mirrors GitHub).
158196
// Skips on re-opens: seededTaskRef prevents re-collapsing files the user
@@ -219,8 +257,13 @@ export function ReviewShell({
219257

220258
useEffect(() => {
221259
if (!scrollRequest) return;
222-
const targetIndex = itemIndexByFilePath.get(scrollRequest);
223-
if (targetIndex === undefined) return;
260+
const targetIndex = visibleItemIndexByFilePath.get(scrollRequest);
261+
if (targetIndex === undefined) {
262+
if (isCommentFilterActive && itemIndexByFilePath.has(scrollRequest)) {
263+
setShowCommentedFilesOnly(false);
264+
}
265+
return;
266+
}
224267

225268
const currentSignature = currentSignatures.get(scrollRequest);
226269
const viewed =
@@ -264,7 +307,9 @@ export function ReviewShell({
264307
onUncollapseFile,
265308
scrollRequest,
266309
setActiveFilePath,
310+
isCommentFilterActive,
267311
taskId,
312+
visibleItemIndexByFilePath,
268313
viewedRecord,
269314
]);
270315

@@ -293,6 +338,40 @@ export function ReviewShell({
293338
[],
294339
);
295340

341+
let reviewContent: ReactNode;
342+
if (isLoading) {
343+
reviewContent = (
344+
<Flex align="center" justify="center" className="min-h-0 flex-1">
345+
<Spinner size="2" />
346+
</Flex>
347+
);
348+
} else if (isEmpty || visibleItems.length === 0) {
349+
reviewContent = (
350+
<Flex align="center" justify="center" className="min-h-0 flex-1">
351+
<Text color="gray" className="text-sm">
352+
{isCommentFilterActive
353+
? "No files with comments"
354+
: "No file changes to review"}
355+
</Text>
356+
</Flex>
357+
);
358+
} else {
359+
reviewContent = (
360+
<VList
361+
ref={listRef}
362+
bufferSize={REVIEW_LIST_BUFFER_PX}
363+
itemSize={REVIEW_LIST_ESTIMATED_ITEM_SIZE}
364+
className="pierre-scroll-root scrollbar-overlay-y min-h-0 flex-1 overflow-auto bg-(--gray-2)"
365+
shift={false}
366+
style={{ scrollbarGutter: "stable" }}
367+
onScroll={handleScroll}
368+
data={visibleItems}
369+
>
370+
{renderItem}
371+
</VList>
372+
);
373+
}
374+
296375
return (
297376
<WorkerPoolContextProvider
298377
// poolSize: each highlighter worker is a full V8 isolate with shiki
@@ -325,6 +404,13 @@ export function ReviewShell({
325404
taskId={taskId}
326405
fileCount={fileCount}
327406
viewedCount={viewedCount}
407+
commentedFileCount={commentedFileCount}
408+
showCommentedFilesOnly={isCommentFilterActive}
409+
onToggleCommentedFilesOnly={
410+
commentedFilePaths
411+
? () => setShowCommentedFilesOnly((current) => !current)
412+
: undefined
413+
}
328414
linesAdded={linesAdded}
329415
linesRemoved={linesRemoved}
330416
allExpanded={allExpanded}
@@ -343,38 +429,7 @@ export function ReviewShell({
343429
direction="column"
344430
className="min-w-0 flex-1"
345431
>
346-
{isLoading ? (
347-
<Flex
348-
align="center"
349-
justify="center"
350-
className="min-h-0 flex-1"
351-
>
352-
<Spinner size="2" />
353-
</Flex>
354-
) : isEmpty ? (
355-
<Flex
356-
align="center"
357-
justify="center"
358-
className="min-h-0 flex-1"
359-
>
360-
<Text color="gray" className="text-sm">
361-
No file changes to review
362-
</Text>
363-
</Flex>
364-
) : (
365-
<VList
366-
ref={listRef}
367-
bufferSize={REVIEW_LIST_BUFFER_PX}
368-
itemSize={REVIEW_LIST_ESTIMATED_ITEM_SIZE}
369-
className="pierre-scroll-root scrollbar-overlay-y min-h-0 flex-1 overflow-auto bg-(--gray-2)"
370-
shift={false}
371-
style={{ scrollbarGutter: "stable" }}
372-
onScroll={handleScroll}
373-
data={items}
374-
>
375-
{renderItem}
376-
</VList>
377-
)}
432+
{reviewContent}
378433
<PendingReviewBar taskId={taskId} />
379434
</Flex>
380435

0 commit comments

Comments
 (0)