Skip to content

Commit fe1d05f

Browse files
committed
feat(code-review): filter unresolved comments
Generated-By: PostHog Code Task-Id: 199c1147-fca6-415b-9ea1-00a45d9b7267
1 parent 429d1b3 commit fe1d05f

6 files changed

Lines changed: 172 additions & 68 deletions

File tree

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,8 @@ export function CloudReviewPage({ task }: CloudReviewPageProps) {
155155
onCollapseFiles={collapseFiles}
156156
items={items}
157157
itemIndexByFilePath={itemIndexByFilePath}
158-
commentedFilePaths={commentedFilePaths}
158+
commentedFilePaths={commentedFilePaths?.all}
159+
unresolvedCommentedFilePaths={commentedFilePaths?.unresolved}
159160
currentSignatures={currentSignatures}
160161
viewedRecord={viewedRecord}
161162
onToggleViewed={toggleViewed}

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,8 @@ export function ReviewPage({ task }: ReviewPageProps) {
178178
branchSourceAvailable={branchSourceAvailable}
179179
prSourceAvailable={prSourceAvailable}
180180
commentThreads={effectiveCommentThreads}
181-
commentedFilePaths={commentedFilePaths}
181+
commentedFilePaths={commentedFilePaths?.all}
182+
unresolvedCommentedFilePaths={commentedFilePaths?.unresolved}
182183
/>
183184
);
184185
}
@@ -212,7 +213,8 @@ export function ReviewPage({ task }: ReviewPageProps) {
212213
untrackedFiles={untrackedFiles}
213214
stagedPathSet={stagedPathSet}
214215
commentThreads={effectiveCommentThreads}
215-
commentedFilePaths={commentedFilePaths}
216+
commentedFilePaths={commentedFilePaths?.all}
217+
unresolvedCommentedFilePaths={commentedFilePaths?.unresolved}
216218
effectiveSource={effectiveSource}
217219
branchSourceAvailable={branchSourceAvailable}
218220
prSourceAvailable={prSourceAvailable}
@@ -250,6 +252,7 @@ function LocalReviewContent({
250252
stagedPathSet,
251253
commentThreads,
252254
commentedFilePaths,
255+
unresolvedCommentedFilePaths,
253256
effectiveSource,
254257
branchSourceAvailable,
255258
prSourceAvailable,
@@ -283,6 +286,7 @@ function LocalReviewContent({
283286
stagedPathSet: Set<string>;
284287
commentThreads?: Map<number, PrCommentThread>;
285288
commentedFilePaths?: ReadonlySet<string>;
289+
unresolvedCommentedFilePaths?: ReadonlySet<string>;
286290
effectiveSource: ResolvedDiffSource;
287291
branchSourceAvailable: boolean;
288292
prSourceAvailable: boolean;
@@ -447,6 +451,7 @@ function LocalReviewContent({
447451
items={items}
448452
itemIndexByFilePath={itemIndexByFilePath}
449453
commentedFilePaths={commentedFilePaths}
454+
unresolvedCommentedFilePaths={unresolvedCommentedFilePaths}
450455
currentSignatures={currentSignatures}
451456
viewedRecord={viewedRecord}
452457
onToggleViewed={toggleViewed}
@@ -466,6 +471,7 @@ function RemoteReviewPage({
466471
prSourceAvailable,
467472
commentThreads,
468473
commentedFilePaths,
474+
unresolvedCommentedFilePaths,
469475
}: {
470476
task: Task;
471477
repoPath: string | null;
@@ -478,6 +484,7 @@ function RemoteReviewPage({
478484
prSourceAvailable: boolean;
479485
commentThreads?: Map<number, PrCommentThread>;
480486
commentedFilePaths?: ReadonlySet<string>;
487+
unresolvedCommentedFilePaths?: ReadonlySet<string>;
481488
}) {
482489
const taskId = task.id;
483490
const isBranch = effectiveSource === "branch";
@@ -561,6 +568,7 @@ function RemoteReviewPage({
561568
items={items}
562569
itemIndexByFilePath={itemIndexByFilePath}
563570
commentedFilePaths={commentedFilePaths}
571+
unresolvedCommentedFilePaths={unresolvedCommentedFilePaths}
564572
currentSignatures={currentSignatures}
565573
viewedRecord={reviewState.viewedRecord}
566574
onToggleViewed={reviewState.toggleViewed}

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

Lines changed: 46 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import {
3232
import { ReviewViewedContext } from "../reviewViewedContext";
3333
import { useReviewViewedStore } from "../reviewViewedStore";
3434
import { PendingReviewBar } from "./PendingReviewBar";
35-
import { ReviewToolbar } from "./ReviewToolbar";
35+
import { type CommentFileFilter, ReviewToolbar } from "./ReviewToolbar";
3636

3737
// Pure helpers, hooks, types, and presentational sub-components live in
3838
// ../reviewShellParts. Re-exported here so consumers can import everything
@@ -123,6 +123,7 @@ export function ReviewShell({
123123
items,
124124
itemIndexByFilePath,
125125
commentedFilePaths,
126+
unresolvedCommentedFilePaths,
126127
currentSignatures,
127128
viewedRecord,
128129
onToggleViewed,
@@ -145,9 +146,9 @@ export function ReviewShell({
145146
const lastActiveRef = useRef<string | null>(null);
146147
const pendingNavigationRef = useRef<string | null>(null);
147148
const navigationFrameRef = useRef<number | null>(null);
148-
const [showCommentedFilesOnly, setShowCommentedFilesOnly] = useState(false);
149-
const isCommentFilterActive =
150-
showCommentedFilesOnly && commentedFilePaths !== undefined;
149+
const [commentFilter, setCommentFilter] = useState<CommentFileFilter>("none");
150+
const activeCommentFilter =
151+
commentedFilePaths && unresolvedCommentedFilePaths ? commentFilter : "none";
151152

152153
const commentedItems = useMemo(
153154
() =>
@@ -156,7 +157,19 @@ export function ReviewShell({
156157
: [],
157158
[commentedFilePaths, items],
158159
);
159-
const visibleItems = isCommentFilterActive ? commentedItems : items;
160+
const unresolvedCommentedItems = useMemo(
161+
() =>
162+
unresolvedCommentedFilePaths
163+
? filterReviewItemsByFilePaths(items, unresolvedCommentedFilePaths)
164+
: [],
165+
[items, unresolvedCommentedFilePaths],
166+
);
167+
const visibleItems =
168+
activeCommentFilter === "commented"
169+
? commentedItems
170+
: activeCommentFilter === "unresolved"
171+
? unresolvedCommentedItems
172+
: items;
160173
const visibleItemIndexByFilePath = useMemo(
161174
() => buildItemIndex(visibleItems),
162175
[visibleItems],
@@ -165,6 +178,10 @@ export function ReviewShell({
165178
() => commentedItems.filter((item) => item.filePaths).length,
166179
[commentedItems],
167180
);
181+
const unresolvedCommentedFileCount = useMemo(
182+
() => unresolvedCommentedItems.filter((item) => item.filePaths).length,
183+
[unresolvedCommentedItems],
184+
);
168185

169186
const workerFactory = useCallback(
170187
() => reviewHost.diffWorkerFactory(),
@@ -177,20 +194,21 @@ export function ReviewShell({
177194
const isExpanded = reviewMode === "expanded";
178195

179196
const viewedCount = useMemo(() => {
180-
const visibleKeys = isCommentFilterActive
181-
? new Set(
182-
visibleItems.flatMap((item) =>
183-
item.scrollKey ? [item.scrollKey] : [],
184-
),
185-
)
186-
: null;
197+
const visibleKeys =
198+
activeCommentFilter !== "none"
199+
? new Set(
200+
visibleItems.flatMap((item) =>
201+
item.scrollKey ? [item.scrollKey] : [],
202+
),
203+
)
204+
: null;
187205
let count = 0;
188206
for (const [key, sig] of currentSignatures) {
189207
if (visibleKeys && !visibleKeys.has(key)) continue;
190208
if (isFileViewed(viewedRecord[key], sig)) count++;
191209
}
192210
return count;
193-
}, [currentSignatures, isCommentFilterActive, viewedRecord, visibleItems]);
211+
}, [activeCommentFilter, currentSignatures, viewedRecord, visibleItems]);
194212

195213
// Collapse already-viewed files on first open per task (mirrors GitHub).
196214
// Skips on re-opens: seededTaskRef prevents re-collapsing files the user
@@ -259,8 +277,11 @@ export function ReviewShell({
259277
if (!scrollRequest) return;
260278
const targetIndex = visibleItemIndexByFilePath.get(scrollRequest);
261279
if (targetIndex === undefined) {
262-
if (isCommentFilterActive && itemIndexByFilePath.has(scrollRequest)) {
263-
setShowCommentedFilesOnly(false);
280+
if (
281+
activeCommentFilter !== "none" &&
282+
itemIndexByFilePath.has(scrollRequest)
283+
) {
284+
setCommentFilter("none");
264285
}
265286
return;
266287
}
@@ -307,7 +328,7 @@ export function ReviewShell({
307328
onUncollapseFile,
308329
scrollRequest,
309330
setActiveFilePath,
310-
isCommentFilterActive,
331+
activeCommentFilter,
311332
taskId,
312333
visibleItemIndexByFilePath,
313334
viewedRecord,
@@ -349,9 +370,11 @@ export function ReviewShell({
349370
reviewContent = (
350371
<Flex align="center" justify="center" className="min-h-0 flex-1">
351372
<Text color="gray" className="text-sm">
352-
{isCommentFilterActive
373+
{activeCommentFilter === "commented"
353374
? "No files with comments"
354-
: "No file changes to review"}
375+
: activeCommentFilter === "unresolved"
376+
? "No files with unresolved comments"
377+
: "No file changes to review"}
355378
</Text>
356379
</Flex>
357380
);
@@ -405,10 +428,11 @@ export function ReviewShell({
405428
fileCount={fileCount}
406429
viewedCount={viewedCount}
407430
commentedFileCount={commentedFileCount}
408-
showCommentedFilesOnly={isCommentFilterActive}
409-
onToggleCommentedFilesOnly={
410-
commentedFilePaths
411-
? () => setShowCommentedFilesOnly((current) => !current)
431+
unresolvedCommentedFileCount={unresolvedCommentedFileCount}
432+
commentFilter={activeCommentFilter}
433+
onCommentFilterChange={
434+
commentedFilePaths && unresolvedCommentedFilePaths
435+
? setCommentFilter
412436
: undefined
413437
}
414438
linesAdded={linesAdded}

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

Lines changed: 84 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,24 @@
11
import {
22
ArrowCounterClockwise,
33
ArrowsClockwise,
4+
CaretDown,
45
ChatCircle,
56
Columns,
67
Rows,
78
X,
89
} from "@phosphor-icons/react";
910
import type { ResolvedDiffSource } from "@posthog/core/code-review/resolveDiffSource";
10-
import { Button } from "@posthog/quill";
11+
import {
12+
Button,
13+
DropdownMenu,
14+
DropdownMenuContent,
15+
DropdownMenuItem,
16+
DropdownMenuRadioGroup,
17+
DropdownMenuRadioItem,
18+
DropdownMenuSeparator,
19+
DropdownMenuTrigger,
20+
MenuLabel,
21+
} from "@posthog/quill";
1122
import { useDiffViewerStore } from "@posthog/ui/features/code-editor/diffViewerStore";
1223
import {
1324
type ReviewMode,
@@ -25,8 +36,9 @@ interface ReviewToolbarProps {
2536
fileCount: number;
2637
viewedCount: number;
2738
commentedFileCount: number;
28-
showCommentedFilesOnly: boolean;
29-
onToggleCommentedFilesOnly?: () => void;
39+
unresolvedCommentedFileCount: number;
40+
commentFilter: CommentFileFilter;
41+
onCommentFilterChange?: (filter: CommentFileFilter) => void;
3042
linesAdded: number;
3143
linesRemoved: number;
3244
allExpanded: boolean;
@@ -40,6 +52,8 @@ interface ReviewToolbarProps {
4052
defaultBranch?: string | null;
4153
}
4254

55+
export type CommentFileFilter = "none" | "commented" | "unresolved";
56+
4357
function formatFileCount(count: number, suffix: string): string {
4458
const noun = count === 1 ? "file" : "files";
4559
return `${count} ${noun} ${suffix}`;
@@ -50,8 +64,9 @@ export const ReviewToolbar = memo(function ReviewToolbar({
5064
fileCount,
5165
viewedCount,
5266
commentedFileCount,
53-
showCommentedFilesOnly,
54-
onToggleCommentedFilesOnly,
67+
unresolvedCommentedFileCount,
68+
commentFilter,
69+
onCommentFilterChange,
5570
allExpanded,
5671
onExpandAll,
5772
onCollapseAll,
@@ -78,12 +93,21 @@ export const ReviewToolbar = memo(function ReviewToolbar({
7893
setReviewMode(taskId, "closed");
7994
};
8095

81-
const visibleFileCount = showCommentedFilesOnly
82-
? commentedFileCount
83-
: fileCount;
84-
const fileCountLabel = showCommentedFilesOnly
85-
? formatFileCount(commentedFileCount, "with comments")
86-
: formatFileCount(fileCount, "changed");
96+
const visibleFileCount =
97+
commentFilter === "commented"
98+
? commentedFileCount
99+
: commentFilter === "unresolved"
100+
? unresolvedCommentedFileCount
101+
: fileCount;
102+
const fileCountLabel =
103+
commentFilter === "commented"
104+
? formatFileCount(commentedFileCount, "with comments")
105+
: commentFilter === "unresolved"
106+
? formatFileCount(
107+
unresolvedCommentedFileCount,
108+
"with unresolved comments",
109+
)
110+
: formatFileCount(fileCount, "changed");
87111

88112
return (
89113
<Flex
@@ -115,29 +139,56 @@ export const ReviewToolbar = memo(function ReviewToolbar({
115139
</Flex>
116140

117141
<Flex align="center" gap="1" ml="auto">
118-
{onToggleCommentedFilesOnly && (
119-
<Tooltip
120-
content={
121-
showCommentedFilesOnly
122-
? "Show all files"
123-
: `Show only files with comments (${commentedFileCount})`
124-
}
125-
>
126-
<Button
127-
size="icon-sm"
128-
variant={showCommentedFilesOnly ? "primary" : "default"}
129-
onClick={onToggleCommentedFilesOnly}
130-
disabled={commentedFileCount === 0 && !showCommentedFilesOnly}
131-
aria-label="Filter files with comments"
132-
aria-pressed={showCommentedFilesOnly}
133-
className="rounded-xs"
142+
{onCommentFilterChange && (
143+
<DropdownMenu>
144+
<DropdownMenuTrigger
145+
render={
146+
<Button
147+
size="sm"
148+
variant={commentFilter === "none" ? "default" : "primary"}
149+
aria-label="Filter files by review comments"
150+
className="rounded-xs"
151+
>
152+
<ChatCircle
153+
size={14}
154+
weight={commentFilter === "none" ? "regular" : "fill"}
155+
/>
156+
<CaretDown size={10} weight="bold" />
157+
</Button>
158+
}
159+
/>
160+
<DropdownMenuContent
161+
align="end"
162+
side="bottom"
163+
sideOffset={6}
164+
className="min-w-[220px]"
134165
>
135-
<ChatCircle
136-
size={14}
137-
weight={showCommentedFilesOnly ? "fill" : "regular"}
138-
/>
139-
</Button>
140-
</Tooltip>
166+
<MenuLabel>Comment filter</MenuLabel>
167+
<DropdownMenuRadioGroup
168+
value={commentFilter === "none" ? "" : commentFilter}
169+
onValueChange={(value) =>
170+
onCommentFilterChange(value as CommentFileFilter)
171+
}
172+
>
173+
<DropdownMenuRadioItem value="commented">
174+
All comments ({commentedFileCount})
175+
</DropdownMenuRadioItem>
176+
<DropdownMenuRadioItem value="unresolved">
177+
Unresolved comments ({unresolvedCommentedFileCount})
178+
</DropdownMenuRadioItem>
179+
</DropdownMenuRadioGroup>
180+
{commentFilter !== "none" && (
181+
<>
182+
<DropdownMenuSeparator />
183+
<DropdownMenuItem
184+
onClick={() => onCommentFilterChange("none")}
185+
>
186+
Clear comment filter
187+
</DropdownMenuItem>
188+
</>
189+
)}
190+
</DropdownMenuContent>
191+
</DropdownMenu>
141192
)}
142193

143194
{onRefresh && (

0 commit comments

Comments
 (0)