Filter auto-routed review requests (CODEOWNERS-style)#44
Merged
Conversation
…ests Adds a `requestedDirectly` signal — set from `requested_reviewers` on the PR — and a Settings toggle to hide reviews where the user was only attached via team membership. Closes the noisy-CODEOWNERS case from #42 at the cost of also hiding manual team requests (the API doesn't distinguish them).
There was a problem hiding this comment.
Pull request overview
Adds a requestedDirectly flag to review-request items so the UI can optionally hide review requests that were routed via team membership (common with CODEOWNERS), reducing noise while keeping explicitly requested reviews visible.
Changes:
- Server: compute and return
requestedDirectlyinfetchReviews()based on whether the authenticated user is inrequested_reviewers. - Web: persist a new “Hide requests routed via a team” setting and apply it when rendering the review list.
- Tests/demo: add integration tests for
fetchReviews()and update demo cache data withrequestedDirectly.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| packages/web/src/types.ts | Extends ReviewRequest with optional requestedDirectly. |
| packages/web/src/filters.ts | Adds hideTeamReviewRequestsAtom persisted via atomWithStorage. |
| packages/web/src/components/SettingsModal.tsx | Adds a settings toggle UI for hiding team-routed review requests. |
| packages/web/src/App.tsx | Applies the new filter when building the review-request list. |
| packages/server/src/fetchers.ts | Computes and returns requestedDirectly in fetchReviews(). |
| packages/server/src/fetchers.integration.test.ts | Adds integration tests validating requestedDirectly behavior. |
| .cache-demo/data.json | Updates demo review-request entries to include requestedDirectly. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Move the "show team-routed reviews" toggle out of the settings modal into the Reviews column header, between the label and sort dimensions. Default on; off filters CODEOWNERS-style auto-requests. Also: - `f` keyboard shortcut to toggle, gated to the Reviews column. - Rename columns: "My PRs" → "My work", "Review Requests" → "Reviews". - "My work" count shows open PRs only (Last-7-days list excluded from the badge; keyboard nav still spans both).
…label SortControl now renders only the active dimension (label + direction arrow); s still cycles, click flips direction. Trims the visual noise now that the column header carries more controls. Also: switch reads "Auto requests" rather than "Show auto requests".
The previous heuristic conflated "user in requested_reviewers" with "asked of me directly" — it filtered out anyone routed via team membership, including manual team requests. Replace it with a timeline-based check that targets only automatic attachments: - bot actor (login ends in [bot] or actor type is Bot), OR - PR author as actor AND event fires within 2s of PR creation. Manual requests (whether by the author themselves later, or by any non-author at any time) stay visible. API shape changes: - field `requestedDirectly` -> `autoAssigned` (flipped meaning) - atom `showTeamReviewRequestsAtom` -> `showCodeOwnerRequestsAtom` (new localStorage key, default true — same UX) - switch label "Auto requests" -> "Code owners", tooltip rewritten Adds one timeline API call per review-requested PR (in parallel with the existing CI/reviews/PR fetches). Verified against real PRs in sana-labs/sana-ai: github-actions[bot] and PR-author-at-creation patterns both detected; manual late requests stay visible. Tests cover bot actor (both type and [bot]-suffix detection), timing boundaries (1.5s / 3s), team and direct paths, multi-event scenarios, and fallback when the timeline fetch fails.
The timeline fetch is the one new API call this branch introduced.
Each review-requested PR triggers it on every fresh fetch even though
the auto/manual classification rarely changes — CODEOWNERS attaches at
PR creation, and the only way it'd change is a push touching newly-
owned files. Both kinds of activity bump the PR's updated_at, so we
can safely cache the result and refetch only when updated_at moves.
Cache value is {value, updatedAt}; on hit we return the cached value
and skip the timeline fetch entirely (Promise.resolve(null) inside the
parallel batch so the rest of the data still fetches in parallel).
We don't persist when timeline fetch fails — a transient 404 / throttle
shouldn't burn a "false" into the cache.
Adds 4 tests covering hit, miss, miss-after-failure, and miss-then-populate.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.
Comments suppressed due to low confidence (1)
packages/server/src/fetchers.ts:388
autoAssignedteam-path detection treats anyreview_requestedevent that targets any team as relevant (requested_team != null). On PRs that have multiple requested teams, this can mark a review as auto-assigned due to an unrelated team’s event and incorrectly hide a manual request. Consider restricting matches to teams that are currently requested on the PR (e.g., compareev.requested_team.slugagainstprData.requested_teams) before applyingisAutoActor.
const isAutoActor = (
actor: { login?: string; type?: string } | null | undefined,
eventCreatedAt?: string,
) => {
if (!actor?.login) return false;
if (actor.type === "Bot" || actor.login.endsWith("[bot]"))
Comment on lines
+376
to
+382
| const timelineEvents = (timelineRes?.data ?? []) as Array<{ | ||
| event?: string; | ||
| created_at?: string; | ||
| actor?: { login?: string; type?: string } | null; | ||
| requested_reviewer?: { login?: string } | null; | ||
| requested_team?: { slug?: string } | null; | ||
| }>; |
| @@ -12,39 +11,26 @@ export function SortControl<F extends string>({ | |||
| value, | |||
| onChange, | |||
| }: Props<F>) { | |||
| ["a", "Approve PR"], | ||
| ["c", "Close PR"], | ||
| ["e", "Dismiss review / notification"], | ||
| ["f", "Toggle CODEOWNERS-attached reviews"], |
2 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Hide CODEOWNERS auto-attached review requests when the "Code owners" toggle in the Reviews column is off. Manual user and manual team requests stay visible.
Detection
A
review_requestedevent counts as automatic when either:type === "Bot"or login ends in[bot]), orThis was reached empirically. Real timelines in
sana-labs/sana-aishow two patterns:actor=github-actions[bot](any timing).actor=<PR author>, event timestamp exactly matches PRcreated_at.Manual requests by the PR author (clicked later, including 13s after open) and any request by a non-author land in the "manual" bucket and stay visible.
Known limitation
CODEOWNERS can also re-evaluate on a push that adds owned files. The resulting event fires at push time (not PR creation), so we'd miss those (false negative). Detecting them would require pairing the request event with a nearby push event — not worth the complexity for now.
Caching
Each fresh fetch would otherwise add one timeline API call per review-requested PR.
autoAssignedonly changes when the PR changes (push, comment, review — all bumpupdated_at), so we cache{value, updatedAt}per(instance, repo, prNumber)and skip the timeline fetch when the cachedupdatedAtstill matches. Transient timeline failures don't poison the cache.Changes
fetchers.ts): each review gets anautoAssignedflag (replaces the priorrequestedDirectly). Adds one timeline fetch per review-requested PR, in parallel with the existing CI/reviews/PR calls. Cached per PR keyed onupdated_at.filters.ts): atom renamed toshowCodeOwnerRequestsAtom, new localStorage key, defaulttrue(same UX).App.tsx): filter predicate updated; switch label "Code owners" lives between the column header and sort dimensions.fkeyboard shortcut toggles it (Reviews column only).SortControlrenders just the active dimension.Tests
Server suite covers the autoAssigned branches and the cache end-to-end (
fetchers.integration.test.ts):[bot]-suffix actor without type, mixed events, empty timelinecreated_at107 server tests pass (was 91 on
main).Follow-up
Surfaced #46 — keyboard-shortcut scope is implicit today and the help overlay doesn't communicate it.
Test plan
pnpm typecheck,pnpm test,pnpm lint,pnpm fmt:checkfshortcut fires only when Reviews column is focusedtrue(existing users see all reviews unchanged)sstill cycles sort dimensions