Skip to content

fix: separate personal and organization forks#211

Merged
AmintaCCCP merged 2 commits into
mainfrom
fix/fork-owner-selector
Jun 7, 2026
Merged

fix: separate personal and organization forks#211
AmintaCCCP merged 2 commits into
mainfrom
fix/fork-owner-selector

Conversation

@AmintaCCCP

@AmintaCCCP AmintaCCCP commented Jun 7, 2026

Copy link
Copy Markdown
Owner

Summary

  • Add GitHub organization discovery and organization fork fetching APIs.
  • Add a Fork page owner selector that defaults to the personal account and can switch to organization-owned forks.
  • Scope Fork refresh, cached rendering, counts, empty states, search, and pagination by selected owner.
  • Add focused ForkTimeline tests for personal/org filtering, refresh scoping, and organization-load failure UX.

Fixes #196

Testing

  • ✅ npm run test:run -- src/components/ForkTimeline.test.tsx
  • ✅ npm run build
  • ⚠️ npx eslint src/components/ForkTimeline.tsx src/components/ForkTimeline.test.tsx src/services/githubApi.ts src/types/index.ts still reports pre-existing lint errors in src/services/githubApi.ts lines 646, 653, 733, 748, and 852.
  • ⚠️ npm run test:run still fails in pre-existing src/components/MarkdownRenderer.test.tsx line 176 (line-number selector assertion), unrelated to this Fork page change.

Notes

  • Organization owner loading failure now shows a warning toast while keeping the personal Fork view usable.
  • Cached fork owners are included in the selector so existing org Forks remain reachable even if /user/orgs cannot list every organization.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • View and manage forks owned by organizations as well as personal accounts.
    • Owner selector to filter forks by account (personal, orgs, cached owners).
    • Owner-scoped refresh that updates forks for the selected account.
  • UX Improvements

    • Pagination and "Showing…" text updated for clearer ranges.
    • Empty-state messages now reflect active owner and search context; prompt to refresh or switch owners.
    • Clearer warnings when organization loading fails.
  • Tests

    • Added tests covering owner-based filtering, refresh behavior, and error handling.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7e427929-b74f-4932-a5df-ae815c7da1d4

📥 Commits

Reviewing files that changed from the base of the PR and between f0ae460 and 1951ae0.

📒 Files selected for processing (2)
  • src/components/ForkTimeline.test.tsx
  • src/components/ForkTimeline.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/components/ForkTimeline.test.tsx
  • src/components/ForkTimeline.tsx

📝 Walkthrough

Walkthrough

This PR adds organization support and owner-scoped fork management: a GitHubOrganization type and paginated API helpers, ForkTimeline state for selected owner/org loading, owner-scoped refresh/merge/sync logic, updated pagination/empty-state UI, and tests for owner filtering and failure handling.

Changes

Multi-owner fork filtering and management

Layer / File(s) Summary
GitHub organization type and API service
src/types/index.ts, src/services/githubApi.ts
GitHubOrganization interface; getUserOrganizations() added; private pagination helpers consolidate paging for forks/orgs and refactor getUserForks/getOrganizationForks.
ForkTimeline state and owner derivations
src/components/ForkTimeline.tsx
Adds selectedForkOwner, organizations, isLoadingOrganizations, loadedForkOwners; derives ownerForks, currentOwnerLabel, and owner selector options; loads orgs when available.
Pagination display and empty-state handling
src/components/ForkTimeline.tsx
Clamped page index, computes displayStart/displayEnd, removes early no-forks return, and centralizes empty-state messaging based on search and active owner.
Owner-scoped fork loading, merge, and sync
src/components/ForkTimeline.tsx
loadForksForOwner(ownerLogin) validates prerequisites, fetches user/org forks, filters to owner-owned forks, merges into store preserving metadata, records loaded owners/last refresh, and runs sync pre-checks for refreshed owner.
Owner selector, header, and results UI
src/components/ForkTimeline.tsx
Adds owner <select> wired to forkOwnerOptions with loading/disabled states; header shows active owner and count; results-range text updated to use display indices.
ForkTimeline tests for owner-scoped behavior
src/components/ForkTimeline.test.tsx
Vitest tests mock store/API/dialogs and verify default personal view, switching to org owner, owner-filtered refresh persistence, and toast on org-load failure.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hopped through orgs and personal nests,
Collected forks, then gave them rests.
A selector here, a refresh there,
Owners sorted with careful care.
🍃✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding owner-scoped fork filtering to separate personal account forks from organization-owned forks.
Linked Issues check ✅ Passed All coding requirements from issue #196 are met: organization discovery via getUserOrganizations(), owner-scoped fork fetching/filtering, UI owner selector, graceful error handling for failed org loading, and cached owner persistence.
Out of Scope Changes check ✅ Passed All changes directly support the core objective of separating personal and organization forks; no unrelated modifications detected beyond scope.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/fork-owner-selector

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@AmintaCCCP AmintaCCCP marked this pull request as ready for review June 7, 2026 15:06

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
src/services/githubApi.ts (1)

1115-1126: ⚡ Quick win

Avoid repeated array reallocation in paginated accumulation.

allItems = [...allItems, ...items] copies the full accumulator on every page. For larger org/repo sets this turns the loop into avoidable O(n²) copying. Use push(...items) to keep it linear.

Proposed change
-    let allItems: T[] = [];
+    const allItems: T[] = [];
@@
-      allItems = [...allItems, ...items];
+      allItems.push(...items);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/githubApi.ts` around lines 1115 - 1126, The loop accumulates
paginated results into allItems using repeated spread copies (allItems =
[...allItems, ...items]) causing O(n²) behavior; change the accumulation to
mutate the existing array instead (use allItems.push(...items)) after the
makeRequest call and remove the spread reassignment so the loop runs in linear
time; keep the rest of the pagination logic (perPage, page, break condition) and
the makeRequest(...) usage unchanged.
src/components/ForkTimeline.test.tsx (2)

166-172: ⚡ Quick win

Avoid coupling to setForks call order in refresh assertions.

Using setForks.mock.calls[0] is brittle if mount-time or intermediate updates introduce additional calls. Assert against the last call (or a call matching owner/login) to keep this test stable.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/ForkTimeline.test.tsx` around lines 166 - 172, The test
assumes the refreshed forks are in setForks.mock.calls[0], which is brittle;
update the assertion to read the last call (or locate the call containing the
expected owner) instead: when inspecting the mocked setForks calls (symbol:
setForks) extract the final invocation's argument (previously assigned to
refreshedForks) or search calls for an array containing an entry with
owner.login 'tamina' and then assert its length and that refreshedForks[0]
matches the expected ForkRepo fields (referenced symbols: refreshedForks,
ForkRepo, personalFork).

175-184: ⚡ Quick win

Assert the fallback UI remains usable after organization-load failure.

This test checks the warning toast, but it doesn’t verify the key requirement that the personal Fork view still works after /user/orgs fails. Add an assertion that personal content is still rendered (and actionable) after the failure path.

Suggested test strengthening
   it('warns when organization owners cannot be loaded', async () => {
     MockGitHubApiService.mockImplementation(() => ({
       getUserOrganizations: vi.fn().mockRejectedValue(new Error('missing scope')),
     } as unknown as GitHubApiService));

     render(<ForkTimeline />);

     await waitFor(() => {
       expect(toastMock).toHaveBeenCalledWith('组织列表加载失败,请检查 GitHub token 权限。', 'warning');
     });
+    expect(screen.getByText('personal-fork')).toBeInTheDocument();
+    expect(screen.getByRole('button', { name: '刷新' })).toBeInTheDocument();
   });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/ForkTimeline.test.tsx` around lines 175 - 184, The test
currently stubs MockGitHubApiService.getUserOrganizations to reject and asserts
toastMock was called, but it must also verify the personal Fork view remains
usable: after rendering <ForkTimeline /> and waiting for the toast, assert that
the personal/fallback UI is rendered and actionable (for example, look up the
personal tab or a known personal-view marker and assert it's in the document
and/or simulate a click to ensure it responds). Update the test that uses
MockGitHubApiService and getUserOrganizations to add an expectation that the
personal content (the element/text/button used to show personal forks in
ForkTimeline) is present and interactive after the failure, in addition to the
existing toastMock assertion.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/ForkTimeline.tsx`:
- Around line 44-45: The cached Set in loadedForkOwners must be cleared whenever
the signed-in personal owner/account changes; add a useEffect in the
ForkTimeline component that watches the authenticated personal owner identifier
(the prop/state that represents the current session owner, e.g.,
authenticatedPersonalOwner or currentOwner) and calls setLoadedForkOwners(new
Set()) when it changes so the load gating logic that references loadedForkOwners
(used around the load gating at lines where loading is checked) will re-run for
the new account.
- Around line 286-290: The code currently overwrites forks using stale snapshots
(setForks([...currentForks.filter(...), ...updatedForks])) which causes
last-write-wins when concurrent owner refreshes overlap; change both places that
call setForks to use the functional updater form (setForks(prev => { ... })) so
you operate on the current state when removing the specific owner's forks and
merging updatedForks, and change setLoadedForkOwners(prev => new
Set(prev).add(ownerLogin)) to return a new Set (const s = new Set(prev);
s.add(ownerLogin); return s) inside its functional updater to avoid mutating the
previous Set; target the calls using the identifiers setForks, currentForks,
updatedForks, and setLoadedForkOwners.

---

Nitpick comments:
In `@src/components/ForkTimeline.test.tsx`:
- Around line 166-172: The test assumes the refreshed forks are in
setForks.mock.calls[0], which is brittle; update the assertion to read the last
call (or locate the call containing the expected owner) instead: when inspecting
the mocked setForks calls (symbol: setForks) extract the final invocation's
argument (previously assigned to refreshedForks) or search calls for an array
containing an entry with owner.login 'tamina' and then assert its length and
that refreshedForks[0] matches the expected ForkRepo fields (referenced symbols:
refreshedForks, ForkRepo, personalFork).
- Around line 175-184: The test currently stubs
MockGitHubApiService.getUserOrganizations to reject and asserts toastMock was
called, but it must also verify the personal Fork view remains usable: after
rendering <ForkTimeline /> and waiting for the toast, assert that the
personal/fallback UI is rendered and actionable (for example, look up the
personal tab or a known personal-view marker and assert it's in the document
and/or simulate a click to ensure it responds). Update the test that uses
MockGitHubApiService and getUserOrganizations to add an expectation that the
personal content (the element/text/button used to show personal forks in
ForkTimeline) is present and interactive after the failure, in addition to the
existing toastMock assertion.

In `@src/services/githubApi.ts`:
- Around line 1115-1126: The loop accumulates paginated results into allItems
using repeated spread copies (allItems = [...allItems, ...items]) causing O(n²)
behavior; change the accumulation to mutate the existing array instead (use
allItems.push(...items)) after the makeRequest call and remove the spread
reassignment so the loop runs in linear time; keep the rest of the pagination
logic (perPage, page, break condition) and the makeRequest(...) usage unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c8e21a7d-f007-4b5e-81c3-1d3af523adfb

📥 Commits

Reviewing files that changed from the base of the PR and between 6356798 and f0ae460.

📒 Files selected for processing (4)
  • src/components/ForkTimeline.test.tsx
  • src/components/ForkTimeline.tsx
  • src/services/githubApi.ts
  • src/types/index.ts

Comment thread src/components/ForkTimeline.tsx
Comment thread src/components/ForkTimeline.tsx Outdated
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@AmintaCCCP AmintaCCCP merged commit 9a4c4ed into main Jun 7, 2026
5 checks passed
@AmintaCCCP AmintaCCCP deleted the fix/fork-owner-selector branch June 7, 2026 15:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fork页应考虑区分账号与组织等

1 participant