Add candidate search dropdown#384
Conversation
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_26bef785-67fb-4623-acbc-8112dce2f356) |
📝 WalkthroughWalkthroughThe gig detail flow now searches CRM-linked people records, lets users select a candidate, and submits the selected CRM profile when adding an application. Playwright coverage mocks query-based people search and verifies the Devon candidate selection flow. ChangesCandidate search and selection
Estimated code review effort: 4 (Complex) | ~45 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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 `@apps/admin_dashboard/src/main.tsx`:
- Around line 6049-6063: Update the error handling in the candidate search
promise within the debounced request callback to recognize aborts wrapped by
requestJson in ApiRequestError, not only direct DOMException AbortError values.
Preserve the early return for cancellations, and only clear matches and set
candidateSearchError for genuine request failures.
🪄 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: aac15f7c-4da2-4618-b269-2e0897ce0868
📒 Files selected for processing (2)
apps/admin_dashboard/src/main.tsxtests/integration/test_dashboard_playwright.py
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 603fd03145
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| value={candidateQuery} | ||
| autoComplete="off" | ||
| placeholder="Search by name or email" | ||
| aria-label="Search candidates to add" |
There was a problem hiding this comment.
Commit the rebuilt dashboard bundle
In this repo the FastAPI app serves the checked-in bundle from apps/api/src/five08/backend/static/dashboard; I checked the committed index-LL0oAOC_.js and it still has CRM profile for candidate/Paste a CRM, not this new search label. With only the source updated, production and test_dashboard_playwright.py (which starts the FastAPI app, not Vite) continue to run the old UI, so the new search-select flow is unavailable and the updated Playwright step cannot pass until the generated static assets are committed.
Useful? React with 👍 / 👎.
| const controller = new AbortController() | ||
| const timer = window.setTimeout(() => { | ||
| const params = new URLSearchParams({ limit: "8", query }) | ||
| void requestJson<Person[]>(`/dashboard/api/people?${params.toString()}`, { |
There was a problem hiding this comment.
Preserve candidate adds for gig-only members
For Discord Member sessions, /dashboard/api/me grants gigs:read/gigs:write but not people:read, and dashboard_people_handler requires people:read. Since this new search always calls /dashboard/api/people and the form disables submit until a result is selected, gig owners who could previously paste a CRM URL and pass dashboard_add_gig_application_handler can no longer add candidates; the search returns 403 and no candidateProfile is ever set. Gate the search UI on people:read with a fallback or use a gigs-write candidate lookup.
Useful? React with 👍 / 👎.
| onChange={(event) => { | ||
| setCandidateQuery(event.target.value) | ||
| setSelectedCandidate(null) | ||
| }} |
There was a problem hiding this comment.
Clear stale candidate matches when the query changes
When a user edits the search after a previous query returned matches, the old candidateMatches remain until the debounced request completes. If the old result set had exactly one entry and the user presses Enter (or clicks the still-visible stale option) before the new search returns, chooseCandidate(candidateMatches[0]) selects and enables adding the previous person even though the input contains a different query. Clear matches/errors on query changes or tie matches to the query they were fetched for.
Useful? React with 👍 / 👎.
| setCandidateSearchError("") | ||
| }) | ||
| .catch((error) => { | ||
| if (error instanceof DOMException && error.name === "AbortError") return |
There was a problem hiding this comment.
Ignore aborts from requestJson-wrapped searches
When a user changes the candidate query after a debounced fetch has already started, the cleanup aborts that fetch, but requestJson catches fetch failures and rethrows an ApiRequestError, so this DOMException check never matches. Normal typing can therefore surface an abort as a candidate search error and clear matches for the active query until the next request wins; check the controller signal or the wrapped error instead of only DOMException.
Useful? React with 👍 / 👎.
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_898f79b7-854e-4ee3-b6ca-963dd5094f5a) |
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_8fa36829-536d-4f16-ba68-191d85acc7b7) |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7715160ffa
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const candidateProfile = selectedCandidateId | ||
| ? crmContactUrl(selectedCandidateId) || selectedCandidateId | ||
| : "" | ||
| const profileForAdd = canSearchCandidates ? candidateProfile : crmProfile.trim() |
There was a problem hiding this comment.
Keep manual CRM entry for unsynced contacts
Users with people:read now get profileForAdd only from selectedCandidate, so the CRM URL input is hidden for admins. When a CRM contact is new or otherwise not yet in the local people cache, /dashboard/api/people cannot return it because the lookup selects FROM people (apps/api/src/five08/backend/api.py:2204), even though dashboard_add_gig_application_handler still accepts a crm_profile and verifies it directly against Espo (apps/api/src/five08/backend/api.py:7082, apps/api/src/five08/backend/api.py:7099). In that unsynced-contact case an admin can no longer add a valid candidate; keep a manual CRM profile fallback alongside search.
Useful? React with 👍 / 👎.
| .then((people) => { | ||
| setCandidateMatches(people.filter((person) => Boolean(person.crm_contact_id))) |
There was a problem hiding this comment.
Guard search results against stale queries
Fresh evidence: although the updated onChange clears matches, this fulfillment handler still commits whichever request resolves without checking controller.signal.aborted or that its query is still the current input. If a prior search response for one query completes after the user has typed another query, stale candidates can reappear under the new input and be selected/added before the newer request wins.
Useful? React with 👍 / 👎.
|
|
||
| const controller = new AbortController() | ||
| const timer = window.setTimeout(() => { | ||
| const params = new URLSearchParams({ limit: "8", query }) |
There was a problem hiding this comment.
Filter inactive people out of candidate search
When the people cache contains missing_in_crm or conflict rows for a matching name, this typeahead omits the endpoint's sync_status=active filter, so stale CRM IDs can be offered as addable candidates; selecting one then reaches the add handler's live Espo lookup and fails with crm_profile_not_found (apps/api/src/five08/backend/api.py:7099). If enough stale rows match, they can also consume the 8-result limit before the valid active contact is shown, so pass the active filter for this picker.
Useful? React with 👍 / 👎.
Summary
Why
Adding a candidate required copying and pasting a CRM profile URL, which made a common dashboard action slower and error-prone. The dashboard already has a permission-checked people search endpoint, so the UI can select an existing CRM candidate directly.
Validation
bun run check(admin dashboard)uv run pytest tests/integration/test_dashboard_playwright.py -qNote
Medium Risk
Changes who can add candidates how and what value is sent as
crm_profile, but reuses the existing applications API; mis-selection or permission gaps could add the wrong CRM contact.Overview
Gig detail — add candidate now branches on
people:read: users with that permission get a debounced search against/dashboard/api/people(min 2 chars, dropdown, explicit selection) and submit the resolved CRM contact URL/ID via the existingonAddApplication/crm_profilePOST. Users without search permission still use the manual CRM profile input.Wiring adds
canSearchCandidatesfrom the gigs view intoGigDetailPage, resets add-candidate UI state after success, setskey={gig.id}on the detail page, and updates the empty-selection toast to “Choose a candidate first.” Built dashboard assets are refreshed in the API static manifest.Reviewed by Cursor Bugbot for commit 7715160. Bugbot is set up for automated code reviews on this repo. Configure here.
Summary by CodeRabbit
New Features
Bug Fixes