feat(integrations): GitHub issue browser + multi-select import UI (#564)#608
Conversation
Adds the issue-import browser that lets users select open issues from a connected GitHub repo (the import action itself lands in #565). Backend: - core/github_issues_service.py: headless list_issues() — open issues only, PRs filtered out, label filter, /search/issues fallback for free-text, Link-header pagination, typed error mapping (401/403/410). - GET /api/v2/integrations/github/issues on the existing #563 router, with a 60s in-process TTL cache and a 409 when no repo is connected. Frontend: - GitHubIssueImportModal: paginated list, debounced search, label filter, multi-select that persists across pages, select-all-on-page (indeterminate), selected-count badge, Import Selected gated on >=1 selection. - integrationsApi.getIssues + GitHub issue types. - TaskBoardView: 'Import from GitHub' button gated on connection status. Also fixes an unanchored .gitignore 'tasks/' rule that silently ignored web-ui/src/components/tasks/ source files. Tests: 9 service + 8 router (backend), 9 modal + 2 board (frontend). All green.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
WalkthroughAdds a complete GitHub issue browser: backend service to list/search issues, a TTL-cached HTTP endpoint, frontend types and API client, a React modal for paginated multi-select import, TaskBoardView wiring, tests, and small repo/config updates. ChangesGitHub Issue Browser Import Feature
Sequence DiagramsequenceDiagram
participant User
participant Modal
participant SWR_integAPI
participant BackendRouter
participant list_issues
participant GitHubAPI
User->>Modal: open modal (workspace, repo)
Modal->>SWR_integAPI: useSWR(key={workspace, page, per_page, search, label})
SWR_integAPI->>BackendRouter: GET /api/v2/integrations/github/issues?workspace_path=...
BackendRouter->>list_issues: list_issues(pat, repo, page, per_page, search, label)
list_issues->>GitHubAPI: GET /search/issues or /repos/{owner}/{repo}/issues
GitHubAPI-->>list_issues: issues[], Link header / total_count
list_issues-->>BackendRouter: (items[], total)
BackendRouter-->>SWR_integAPI: {items, total}
SWR_integAPI-->>Modal: data / isLoading / error
User->>Modal: select items / click Import Selected
Modal-->>User: onImport(selectedIssues[])
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
|
deleted |
|
Code Review — Phase 5.5 GitHub Issue Browser (564): Well-scoped implementation, architecture compliant. Two items to fix before merge: (1) Add workspace path to issue cache key to prevent cross-workspace data leakage; (2) Strip double-quotes from label param to prevent GitHub search qualifier manipulation. Full review details to follow. |
|
deleted test |
|
deleted |
|
test plain string |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 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 `@codeframe/core/github_issues_service.py`:
- Line 25: The module codeframe/core/github_issues_service.py imports httpx but
httpx is not declared in project dependencies; add "httpx" to the project's
dependency list (e.g., under [project] dependencies in pyproject.toml or the
appropriate [project.optional-dependencies] group if intended optional) so
installs include it, then update the lockfile (poetry lock / pip-tools /
pip-compile) and CI/setup workflows to install the new dependency; locate the
missing import by checking the import line in github_issues_service.py to verify
the package name.
In `@web-ui/src/components/tasks/GitHubIssueImportModal.tsx`:
- Around line 314-331: The formatAge function mangles phrases like "less than a
minute" and leaves "almost " intact; update formatAge to special-case the exact
"less than a minute" result (return "<1m" or "less than 1m") before doing other
replacements, strip both "about " and "almost " prefixes, and perform the unit
replacements using word-boundary or plural-aware patterns (e.g. replace
/months?/ -> "mo", /days?/ -> "d", /years?/ -> "y", /hours?/ -> "h", /minutes?/
-> "m") so you don't accidentally alter words inside other phrases; keep these
changes inside the formatAge function that calls formatDistanceToNow.
In `@web-ui/src/components/tasks/TaskBoardView.tsx`:
- Line 13: The import of GithubIcon from '`@hugeicons/react`' in TaskBoardView.tsx
is incorrect because '`@hugeicons/react`' exports a renderer (HugeiconsIcon) while
individual icons live in separate packs; update the TaskBoardView.tsx usage to
import the actual icon (e.g., GithubIcon) from the icon pack (such as
'`@hugeicons/core-free-icons`') and render it via the HugeiconsIcon renderer using
the icon prop (or alternatively import and use the renderer and pass the core
icon to its icon prop where GithubIcon is currently expected), ensuring
Cancel01Icon and Task01Icon imports are similarly validated or replaced with the
correct source.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: d84bf8d0-3a24-4e2b-acd2-c1aef953d78f
📒 Files selected for processing (12)
.gitignorecodeframe/core/github_issues_service.pycodeframe/ui/routers/github_integrations_v2.pytests/core/test_github_issues_service.pytests/ui/test_github_integrations_v2.pyweb-ui/__mocks__/@hugeicons/react.jsweb-ui/src/__tests__/components/tasks/GitHubIssueImportModal.test.tsxweb-ui/src/__tests__/components/tasks/TaskBoardView.githubImport.test.tsxweb-ui/src/components/tasks/GitHubIssueImportModal.tsxweb-ui/src/components/tasks/TaskBoardView.tsxweb-ui/src/lib/api.tsweb-ui/src/types/index.ts
| import { BulkActionConfirmDialog, type BulkActionType } from './BulkActionConfirmDialog'; | ||
| import { Cancel01Icon, Task01Icon } from '@hugeicons/react'; | ||
| import { GitHubIssueImportModal } from './GitHubIssueImportModal'; | ||
| import { Cancel01Icon, Task01Icon, GithubIcon } from '@hugeicons/react'; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# 1) Show prior usage of any Github*Icon in the repo (is this an established import?)
rg -nP 'Github\w*Icon' -g '!**/__mocks__/**'
echo "----- hugeicons mock -----"
cat web-ui/__mocks__/@hugeicons/react.js
echo "----- installed export names (if available) -----"
fd -t f -p 'node_modules/@hugeicons/react' --exec sh -c 'grep -l "GithubIcon" "$1" 2>/dev/null' _ {} \; 2>/dev/null | headRepository: frankbria/codeframe
Length of output: 3616
🌐 Web query:
@hugeicons/react 0.3.0 GithubIcon export name
💡 Result:
In the @hugeicons/react library, individual icons are not exported directly from the @hugeicons/react package. Instead, @hugeicons/react serves as a rendering library that provides the HugeiconsIcon component, while the icons themselves are exported from separate icon packages, such as @hugeicons/core-free-icons [1][2][3]. To use the GitHub icon, you should install the icon pack and import it from that package: 1. Install the required packages: npm install @hugeicons/react @hugeicons/core-free-icons [4][5]. 2. Import the icon and the renderer: import { HugeiconsIcon } from '@hugeicons/react'; import { GithubIcon } from '@hugeicons/core-free-icons'; 3. Use the component: Note that older versions of the library (such as the deprecated hugeicons-react package) followed a different structure, but the current official approach requires importing icons from the dedicated icon packs [4][3][5].
Citations:
- 1: https://github.com/hugeicons/react/tree/2856d183a49575a902c48e8d8dcd7cbe81c5d728
- 2: https://www.npmjs.com/package/@hugeicons/react
- 3: https://github.com/hugeicons/react
- 4: https://github.com/hugeicons/hugeicons-react
- 5: https://github.com/hugeicons/hugeicons-react/blob/main/README.md
🏁 Script executed:
#!/bin/bash
set -euo pipefail
node - <<'NODE'
const path = require('path');
function tryRequire(mod) {
try {
const pkg = require(path.join(mod, 'package.json'));
const m = require(mod);
const keys = m && typeof m === 'object' ? Object.keys(m) : [];
const githubKeys = keys.filter(k => /github/i.test(k));
console.log(`== ${mod} ==`);
console.log(`version: ${pkg.version}`);
console.log(`export keys: ${githubKeys.length ? githubKeys.join(', ') : '(none matched /github/i)'}`);
console.log(`has GithubIcon export: ${'GithubIcon' in m}`);
} catch (e) {
console.log(`== ${mod} ==`);
console.log(String(e));
}
}
tryRequire('`@hugeicons/react`');
tryRequire('`@hugeicons/core-free-icons`');
NODE
# Fallback: show if GithubIcon string exists in the installed package entrypoints
if [ -d "node_modules/@hugeicons/react" ]; then
rg -n "GithubIcon" node_modules/@hugeicons/react --hidden || true
fiRepository: frankbria/codeframe
Length of output: 333
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 1) Check which hugeicons packages/versions are declared
ls -la web-ui | head
rg -n '"`@hugeicons/`' web-ui/package.json web-ui/package-lock.json web-ui/yarn.lock web-ui/pnpm-lock.yaml 2>/dev/null || true
# 2) Locate all Hugeicons/Github icon imports
rg -n 'from\s+[\'"]`@hugeicons/`(react|core-free-icons|[^\'"]+)[\'"]' web-ui/src --glob='*.{ts,tsx}' || true
rg -n 'GithubIcon' web-ui/src --glob='*.{ts,tsx}' || true
rg -n 'HugeiconsIcon' web-ui/src --glob='*.{ts,tsx}' || true
# 3) Check existing pattern for icons without numeric suffix
rg -n 'from\s+[\'"]`@hugeicons/react`[\'"]' web-ui/src/components --glob='*.{ts,tsx}' || trueRepository: frankbria/codeframe
Length of output: 830
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# package + lock (dependency versions / presence)
echo "== web-ui/package.json ==" && cat web-ui/package.json
echo "== web-ui/package-lock.json (hugeicons deps only) =="
rg -n '"`@hugeicons/`(react|core-free-icons)[^"]*"' web-ui/package-lock.json || true
rg -n '"`@hugeicons/`' web-ui/package-lock.json | head -n 80
# search import patterns
echo "== Imports from `@hugeicons/react` in web-ui/src =="
rg -n "from\s+['\"]`@hugeicons/react`['\"]" web-ui/src --glob='*.{ts,tsx}' || true
echo "== GithubIcon usage in web-ui/src (any import/source) =="
rg -n "GithubIcon" web-ui/src --glob='*.{ts,tsx}' || true
echo "== HugeiconsIcon usage in web-ui/src (renderer pattern) =="
rg -n "HugeiconsIcon" web-ui/src --glob='*.{ts,tsx}' || trueRepository: frankbria/codeframe
Length of output: 8777
🏁 Script executed:
#!/bin/bash
set -euo pipefail
if [ -d "web-ui/node_modules/@hugeicons/react" ]; then
echo "Found web-ui/node_modules/@hugeicons/react"
node - <<'NODE'
try {
const m = require('./web-ui/node_modules/@hugeicons/react');
const keys = Object.keys(m || {});
console.log('version:', require('./web-ui/node_modules/@hugeicons/react/package.json').version);
console.log('has GithubIcon:', 'GithubIcon' in m);
console.log('github-like keys:', keys.filter(k => /github/i.test(k)).join(', ') || '(none)');
console.log('type of GithubIcon:', typeof m.GithubIcon);
} catch (e) {
console.log(String(e));
}
NODE
echo "Searching installed package for 'GithubIcon'..."
rg -n "GithubIcon" web-ui/node_modules/@hugeicons/react -S || true
else
echo "web-ui/node_modules/@hugeicons/react not present in sandbox"
fi
echo "Also check whether any `@hugeicons/react` is present anywhere else..."
ls -ლა web-ui/node_modules/@hugeicons 2>/dev/null || trueRepository: frankbria/codeframe
Length of output: 191
🌐 Web query:
Find in @hugeicons/react@0.3.0 source/index exports: does it export GithubIcon? (use unpkg/jsdelivr/npm view or GitHub tag)
💡 Result:
The @hugeicons/react package (version 0.3.0) is a rendering library designed specifically for displaying icons [1][2]. It does not export individual icon components like GithubIcon directly from its root [1][2]. Instead, the library provides a single HugeiconsIcon component [1][2]. To display an icon, you must install a separate icon pack (such as @hugeicons/core-free-icons) and pass the desired icon object to the icon prop of the HugeiconsIcon component [1][3]. Example usage: import { HugeiconsIcon } from '@hugeicons/react'; import { GithubIcon } from '@hugeicons/core-free-icons'; function App { return ; }
Citations:
- 1: https://github.com/hugeicons/react/tree/2856d183a49575a902c48e8d8dcd7cbe81c5d728
- 2: https://www.npmjs.com/package/@hugeicons/react
- 3: https://github.com/hugeicons/hugeicons-react
🌐 Web query:
unpkg @hugeicons/react@0.3.0 entry file exports HugeiconsIcon; does it contain export { GithubIcon } or named exports for icons?
💡 Result:
unpkg @hugeicons/react@0.3.0 (root entry) exports only the HugeiconsIcon React component (named export). It does not export individual icon components like GithubIcon from the entry point. Evidence: the package documentation describes @hugeicons/react as a “rendering library” that provides the HugeiconsIcon component, while the actual icons come from separate icon packages such as @hugeicons/core-free-icons / @hugeicons-pro/core-* [1][2]. Example usage imports HugeiconsIcon from @hugeicons/react and SearchIcon (an icon component) from @hugeicons/core-free-icons [1][2]. Therefore, a statement like export { GithubIcon } or other named exports for icons at the root entry is not part of the intended API. How to import a specific icon: import the icon component (e.g., GithubIcon) from the appropriate icon pack package, then pass it to HugeiconsIcon as the icon prop. [1][2] (Note: your question mentions unpkg @hugeicons/react@0.3.0 entry file exports HugeiconsIcon; that matches the known API pattern for this library, but I did not find evidence of any GithubIcon root export in the cited docs.)
Citations:
Fix GithubIcon import from @hugeicons/react
web-ui/src/components/tasks/TaskBoardView.tsx imports GithubIcon (along with other *Icon named exports) from @hugeicons/react. In @hugeicons/react@0.3.0, the package is a renderer that exports HugeiconsIcon, while individual icons like GithubIcon are provided by separate icon packs (e.g. @hugeicons/core-free-icons) and must be passed via the icon prop; relying on GithubIcon being a root named export will likely be undefined at runtime (tests can be masked by web-ui/__mocks__/@hugeicons/react.js).
🤖 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 `@web-ui/src/components/tasks/TaskBoardView.tsx` at line 13, The import of
GithubIcon from '`@hugeicons/react`' in TaskBoardView.tsx is incorrect because
'`@hugeicons/react`' exports a renderer (HugeiconsIcon) while individual icons
live in separate packs; update the TaskBoardView.tsx usage to import the actual
icon (e.g., GithubIcon) from the icon pack (such as
'`@hugeicons/core-free-icons`') and render it via the HugeiconsIcon renderer using
the icon prop (or alternatively import and use the renderer and pass the core
icon to its icon prop where GithubIcon is currently expected), ensuring
Cancel01Icon and Task01Icon imports are similarly validated or replaced with the
correct source.
Line 1line 2 with no hash |
|
deleted stdin test |
|
deleted |
|
deleted proc test |
|
deleted |
- Declare httpx as an explicit dependency in pyproject.toml. It was only available transitively (via anthropic/openai/etc.); this module imports it directly, so make the dependency explicit (Major). - Fix formatAge mangling: 'less than a minute' became 'less than am' and 'almost 2 years' became 'almost 2y'. Switch to formatDistanceToNowStrict (no about/almost/less-than prefixes) + clean unit abbreviation (Minor).
|
Thanks @coderabbitai — triaged all three:
|
|
Tip For best results, initiate chat on the files or code changes.
🐇 ✨ |
|
deleted |
3 similar comments
|
deleted |
|
deleted |
|
deleted |
|
Follow-up Review - Phase 5.5 GitHub Issue Browser (#564) This follows up on my earlier summary comment. The httpx explicit dependency (flagged by coderabbitai) was correctly added in 80012d3. Here is where things stand on the two items I flagged. Item 1: Cache key workspace scope - closing this out My earlier concern about cross-workspace cache leakage is less critical than I stated. Since the PAT is machine-wide (CredentialProvider.GIT_GITHUB), all workspaces share the same credentials, and the cache key already includes repo which disambiguates different repositories. The only edge case - two workspaces pointing at the same repo - would share a cache entry, but with the same PAT that is correct behavior. No action needed. Item 2: Label injection in search qualifier - still present In codeframe/core/github_issues_service.py around line 231: If the user-supplied label contains a double-quote, the assembled q string will break out of the quoted qualifier. In this single-user context the user can only corrupt their own search, but the fix is one line: Low severity but trivial to fix before merge. Additional observation: _headers is a private symbol imported across modules github_issues_service.py imports _headers (underscore-prefixed) from github_connect_service. This is a minor coupling smell - if github_connect_service ever refactors its internals, this import breaks silently. Worth considering whether to expose it as a public helper or consolidate the shared HTTP utilities. Not a blocker. Overall Implementation is clean and architecture-compliant: core service is headless, router layer is a thin adapter, frontend modal handles multi-select state correctly across pages, and test coverage is solid (9+8 backend, 9+2 frontend). Recommended: address the label double-quote stripping before merge (five-minute fix), then this is good to go. |
Code Review — Phase 5.5 GitHub Issue Browser (#564)This is a well-scoped and well-tested implementation. Architecture choices align correctly with project conventions (headless core, thin router layer, reuse of #563's credential/config infrastructure). Findings organized by severity below. Security / CorrectnessCache key missing workspace isolation ( The cache key is keyed only on query parameters, not on the workspace path: If the server process handles multiple workspaces connecting to the same repo, workspace A's cached response will be served to workspace B. The PAT is also excluded from the key, so two tokens with different effective permissions would receive the same cached data. In the current single-user product model this is low-risk, but it is a latent bug as multi-workspace usage grows. Recommend adding Label search injection into GitHub query ( A label value containing a double-quote character (e.g. Code QualityImporting a private function from another module (
Pagination total slightly off after PR filtering (
Unbounded cache growth ( The module-level Frontend
The series of Import button spinner conflates fetch and action state (
What's Done Well
SummaryTwo items worth addressing before merge:
The remaining items are polish/future-work level. The PR is otherwise in good shape. |
Code Review — GitHub Issue Browser (#564)Overall: Well-executed PR. The architecture rules are followed cleanly: core is headless, the PAT never leaks into responses, typed errors are reused, and the 60s cache lives in the router layer where it belongs. Tests are comprehensive and meaningful. Three items below warrant attention before merge. Issues1. Unbounded
# github_integrations_v2.py
from functools import lru_cache
# Or simply add a max-size guard:
_ISSUE_CACHE_MAX_ENTRIES = 500
def _issue_cache_set(key: str, payload: Any) -> None:
if len(_ISSUE_CACHE) >= _ISSUE_CACHE_MAX_ENTRIES:
# Drop the oldest (insertion order in Python 3.7+)
_ISSUE_CACHE.pop(next(iter(_ISSUE_CACHE)))
_ISSUE_CACHE[key] = (time.monotonic() + _ISSUE_CACHE_TTL_SECONDS, payload)2. Label filter input is not debounced
3. GitHub 429 (rate-limit) not mapped to a distinct error
Minor observations (non-blocking)
What's solid
The two cache issues (#1 and #2) are the ones I'd resolve before merging; #3 is a polish item. Otherwise this is merge-ready. |
Closes #564.
What
Phase 5.5 — the GitHub issue browser that lets users select open issues from a connected repo before importing. The actual import action is out of scope here (lands in #565); selecting + confirming currently just closes the modal.
Plan adaptation
The Traycer plan on the issue targeted a superseded architecture (a new
integrations_v2.pyrouter readingGITHUB_TOKEN/GITHUB_REPOenv vars viaget_global_config()). The shipped #563 integration is different, so this was rewired to the real code:github_integrations_v2.pyrouter (prefix already/api/v2/integrations/github) instead of adding a new one.CredentialManager(CredentialProvider.GIT_GITHUB); repo from.codeframe/github_integration.json(load_github_integration_config)./statusendpoint +integrationsApi.getStatusto gate the button.Backend
core/github_issues_service.py— headlesslist_issues():/issuesendpoint returns PRs too).labels=; free-text search routes to/search/issueswithrepo:+is:issue+is:openand readstotal_count.Linkheader'srel="last"page.InvalidTokenError, 403→InsufficientScopeError, 410 issues-disabled → empty).GET /api/v2/integrations/github/issues?page&per_page&search&labelon the existing router:per_pageclamped to 1..100.Frontend
GitHubIssueImportModal— paginated list (25/page), debounced (300ms) title search, label filter, multi-select that persists across pages, select-all-on-page (indeterminate state), selected-count badge, skeleton loading, error banner. Import Selected disabled until ≥1 selected.integrationsApi.getIssues+GitHubIssue/GitHubIssuesResponsetypes.TaskBoardView— Import from GitHub button shown only when a repo is connected; opens the modal.Drive-by fix
.gitignore's unanchoredtasks/rule also matchedweb-ui/src/components/tasks/, silently ignoring new source files there. Anchored to/tasks/.Acceptance criteria
npm testpassesTesting
tests/core/test_github_issues_service.py(9),tests/ui/test_github_integrations_v2.py(+8).uv run ruff check .clean.GitHubIssueImportModal.test.tsx(9),TaskBoardView.githubImport.test.tsx(2). Full suite 945 passing;npm run buildsucceeds.Known limitations
totalfrom the list endpoint is GitHub's standard last-page×per_page upper-bound estimate (exact only on the search path).Summary by CodeRabbit