fix(invite): pre-fill org dialog from user profile when invite has none#1034
fix(invite): pre-fill org dialog from user profile when invite has none#1034andrest50 wants to merge 12 commits into
Conversation
When a committee invite requires an organization but carries no pre-filled org (e.g. the inviter didn't specify one), the accept flow now fetches the user's current employer from their work experience and pre-fills the Confirm Organization dialog with it. Behaviour: - Invite already carries an org → unchanged (use invite org as pre-fill) - Invite has no org → fetch /api/profile/work-experience; pick the first entry without an endDate (current employer), falling back to the most recent entry; pre-fill name + organizationId into the dialog - Profile fetch fails or returns empty → dialog opens blank (existing UX) The pre-fill is editable — the user can change or clear it before confirming. The fix lives in InvitationAcceptFlowService so all four callers (invite link, committee-view, committee-invitations, pending-actions) benefit without individual changes. Generated with [Claude Code](https://claude.ai/code) Signed-off-by: Andres Tobon <andrest2455@gmail.com>
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughInvitation acceptance and member-add flows now pre-fill organization data from work-experience lookups. The shared utility derives the organization, the server exposes a user lookup endpoint, and both client flows use the returned work experiences when the organization field is empty. ChangesInvitation organization prefill
Sequence Diagram(s)sequenceDiagram
participant AddMemberDialogComponent
participant InvitationAcceptFlowService
participant SearchService
participant SearchController
participant CdpService
AddMemberDialogComponent->>SearchService: getUserWorkExperiences(lfid)
InvitationAcceptFlowService->>SearchService: get work experiences for current user
SearchService->>SearchController: GET /users/:lfid/work-experiences
SearchController->>CdpService: getWorkExperiencesForUser(req, lfid)
CdpService-->>SearchController: work experiences
SearchController-->>SearchService: JSON array
SearchService-->>AddMemberDialogComponent: WorkExperienceEntry[]
SearchService-->>InvitationAcceptFlowService: WorkExperienceEntry[]
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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/lfx-one/src/app/shared/services/invitation-accept-flow.service.ts`:
- Around line 62-65: The fallback in currentEmployerFromProfile currently uses
experiences[0], which can pick the wrong organization when the API response is
not sorted. Update the logic in invitation-accept-flow.service.ts so that when
no active experience is found, it selects the most recent finished
WorkExperience by date instead of the first array item, while still returning
the current employer when one exists. Keep the change localized to
currentEmployerFromProfile and preserve the CommitteeOrganizationReference shape
with name and organizationId mapping.
- Around line 46-49: The `InvitationAcceptFlowService` currently lets
`userService.getWorkExperience()` errors terminate `contextReady$`, which
prevents the accept dialog from opening at all. Update the observable chain in
`contextReady$` to handle failures from `getWorkExperience()` and recover with
the blank-dialog context instead of erroring, while keeping the normal
`currentEmployerFromProfile` mapping path for successful responses.
🪄 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: f65ff909-64ac-4249-a5fe-4342ad830650
📒 Files selected for processing (1)
apps/lfx-one/src/app/shared/services/invitation-accept-flow.service.ts
🚀 Deployment StatusYour branch has been deployed to: https://ui-pr-1034.dev.v2.cluster.linuxfound.info Deployment Details:
The deployment will be automatically removed when this PR is closed. |
There was a problem hiding this comment.
Pull request overview
This PR aims to improve the committee invite acceptance flow by pre-filling the “Confirm Organization” dialog from the user’s profile when an invite requires an organization but doesn’t include one, reducing duplicate user input across all invite acceptance entry points.
Changes:
- Adds a profile work-experience fetch path in
InvitationAcceptFlowServiceto derive a default organization when the invite has none. - Introduces helper logic to select a “current employer” (no
endDate) as the pre-fill candidate. - Routes dialog pre-fill via the existing
InvitationAcceptContext.organizationfield so the user can still edit/clear before confirming.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Switch profile fetch from getWorkExperience() → direct HttpClient call to /api/profile/work-experiences (plural): the singular endpoint has no BFF route so getWorkExperience() always 404'd and returned [], meaning the pre-fill never fired (per copilot-pull-request-reviewer) - Add catchError(() => of(context)) on the profile fetch so any HTTP error falls back to opening the dialog blank rather than aborting the accept flow entirely (per coderabbitai[bot]) - Sort by startDate descending before taking the fallback entry so the most recent finished experience is used when no current employer exists, rather than relying on API response ordering (per coderabbitai[bot] and copilot-pull-request-reviewer) - Remove UserService injection; HttpClient is sufficient for the one quiet background call this service needs Generated with [Claude Code](https://claude.ai/code) Signed-off-by: Andres Tobon <andrest2455@gmail.com>
When an inviter searches for and selects a user from the add-member dialog's typeahead, if that user has a known organization and the org field is currently blank, auto-populate the organization name and website from the search result. The pre-fill is a convenience only: it leaves the org field editable so the inviter can adjust before sending, and never overwrites a value they have already entered (e.g. from a previous selection or manual input). The field is only shown — and therefore only filled — when the committee requires organization on accept. LFXV2-2531 Generated with [Claude Code](https://claude.ai/code) Signed-off-by: Andres Tobon <andrest2455@gmail.com>
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/lfx-one/src/app/modules/committees/components/add-member-dialog/add-member-dialog.component.ts`:
- Around line 152-157: The organization auto-fill in
add-member-dialog.component.ts can leave organization_url stale when the
organization name is changed or re-resolved. Update the logic around the prefill
in the add-member dialog so that whenever the organization value changes,
organization_url is either cleared or recomputed from the currently resolved
organization, and ensure the submit payload from the component does not carry a
mismatched URL alongside a different organization_id/name.
🪄 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: afe77ad6-00e2-4540-a030-1236dd613daf
📒 Files selected for processing (1)
apps/lfx-one/src/app/modules/committees/components/add-member-dialog/add-member-dialog.component.ts
Address review comments from coderabbitai[bot], copilot-pull-request-reviewer[bot]: - add-member-dialog.component.ts: drop organization_url from the auto-fill patchValue; pre-filling it could leave a stale URL in the submit payload when the inviter later changes the org name (per coderabbitai[bot]) - invitation-accept-flow.service.ts: replace new Date(startDate) sort with deterministic "MMM YYYY" parser; new Date() on non-ISO strings is spec-undefined and unreliable in Safari (per copilot-pull-request-reviewer[bot]) Resolves 2 review threads. Generated with [Claude Code](https://claude.ai/code) Signed-off-by: Andres Tobon <andrest2455@gmail.com>
Review Feedback AddressedCommit: 5ebf4ff Changes Made
Threads Resolved2 of 2 unresolved threads addressed in this iteration. |
The previous pre-fill used org data from the search result, but committee-member search results only carry org when the user previously accepted an org-requiring invite — null for most users in practice. Fix: on user selection fetch their work experiences from CDP via a new BFF endpoint and apply the same current-employer logic as the invite accept flow. - packages/shared: add currentEmployerFromWorkExperiences() utility shared by InvitationAcceptFlowService and the dialog - search.controller + search.route: add GET /api/search/users/:lfid/work-experiences, same CDP call as the profile endpoint but accepts any LFID instead of reading from session - app/search.service: add getUserWorkExperiences(lfid) Angular method - add-member-dialog: call getUserWorkExperiences on user selection, pre-fill org name when the field is currently blank - InvitationAcceptFlowService: switch to the shared utility LFXV2-2531 Generated with [Claude Code](https://claude.ai/code) Signed-off-by: Andres Tobon <andrest2455@gmail.com>
LFXV2-2531 Generated with [Claude Code](https://claude.ai/code) Signed-off-by: Andres Tobon <andrest2455@gmail.com>
The sync effect only tracked form() and nameControl() input signals, which never change after init. Calling patchValue() on the parent control from outside had no effect on the internal autocomplete input. Subscribe to the parent control's valueChanges inside the effect and use onCleanup to tear down the subscription when inputs change. LFXV2-2531 Generated with [Claude Code](https://claude.ai/code) Signed-off-by: Andres Tobon <andrest2455@gmail.com>
Temporary debug logs to diagnose why org auto-populate is not working. Remove before merging. LFXV2-2531 Generated with [Claude Code](https://claude.ai/code) Signed-off-by: Andres Tobon <andrest2455@gmail.com>
Temporary debug logs to diagnose why org auto-populate is not working. Remove before merging. LFXV2-2531 Generated with [Claude Code](https://claude.ai/code) Signed-off-by: Andres Tobon <andrest2455@gmail.com>
LFXV2-2531 Generated with [Claude Code](https://claude.ai/code) Signed-off-by: Andres Tobon <andrest2455@gmail.com>
- search.controller: minimize work-experience response to org-prefill fields only (organization, organizationId, startDate, endDate) — avoids exposing job titles, source, and verification metadata to any authenticated caller (per copilot-pull-request-reviewer) - organization-search: also clear the internal search input when the parent name control is cleared programmatically, not just when set (per copilot-pull-request-reviewer) - add-member-dialog: fix misleading comment — the endpoint is different from the invite accept flow; only the employer-selection logic is shared (per copilot-pull-request-reviewer) - invitation.utils.spec: add unit tests for currentEmployerFromWorkExperiences covering empty array, current-job preference, most-recent fallback, missing org ID, month ordering, and invalid date handling (per copilot-pull-request-reviewer) LFXV2-2531 Generated with [Claude Code](https://claude.ai/code) Signed-off-by: Andres Tobon <andrest2455@gmail.com>
MRashad26
left a comment
There was a problem hiding this comment.
Code Review — PR #1034 fix(invite): pre-fill org dialog from user profile when invite has none
Overview
Three coordinated changes: (1) invite-accept flow now fetches the authenticated user's CDP work experiences to pre-fill the org dialog when the invite carries none; (2) add-member dialog auto-populates the org field from a selected user's CDP work history via a new BFF endpoint; (3) OrganizationSearchComponent effect fixed to subscribe to valueChanges so programmatic patchValue() calls update the search input (enabling both pre-fills to display correctly). currentEmployerFromWorkExperiences extracted to shared utils with unit tests.
Secrets / critical-constants check ✅
No secrets or hardcoded credentials introduced.
Code-standards audit
| # | Severity | Finding |
|---|---|---|
| 1 | 🟡 Warning | OrganizationSearchComponent — pre-existing effect() is being modified; still violates the no-effect() rule |
| 2 | 🟡 Warning | GET /api/search/users/:lfid/work-experiences — returns any user's employment dates to any authenticated user without checking the requestor's relationship with the target |
Code quality notes
currentEmployerFromWorkExperiences+monthYearToOrdinalare clean and well-tested (7 cases). Safari-safeMMM YYYYparser avoidsnew Date()on non-ISO strings. ✅invitation-accept-flow.service.tsCDP pre-fill usescatchError(() => of(context))so the flow never blocks on a CDP failure — correct fail-open approach for a UX convenience. ✅add-member-dialogdouble-guard (!org?.trim()before fetch AND inside subscribe callback) correctly handles the race where the admin types an org between the click and the async response. ✅- BFF response projection strips job titles, source, and verification metadata — minimal data exposure. ✅
Verdict: FAIL ❌ (2 warnings)
Fix the effect() pattern and add an authorization guard on the work-experiences endpoint.
| // Effect to sync the search input with the parent form's name control — both on | ||
| // initial render and whenever the control's value changes programmatically (e.g. patchValue). | ||
| // onCleanup tears down the valueChanges subscription if the form/nameControl inputs change. | ||
| effect((onCleanup) => { |
There was a problem hiding this comment.
🟡 Warning — effect() with subscribe() inside (pre-existing violation being modified)
The effect() now calls nameControl.valueChanges.subscribe() inside its body and relies on onCleanup to unsubscribe. While onCleanup is correctly used, this still violates the project rule that bans effect() entirely.
Suggested pattern — replace the whole block with a signal-reactive observable chain:
toObservable(this.form).pipe(
switchMap((parentForm) => {
const nameControlName = this.nameControl();
if (!parentForm || !nameControlName) return EMPTY;
const ctrl = parentForm.get(nameControlName);
if (!ctrl) return EMPTY;
// Emit current value immediately, then all future changes
return merge(of(ctrl.value as string | null), ctrl.valueChanges);
}),
takeUntilDestroyed(),
).subscribe((value) => {
searchControl.setValue((value ?? '').trim(), { emitEvent: false });
});This eliminates the effect() entirely and uses switchMap to auto-cancel the previous subscription when form() or nameControl() changes.
| * Returns the CDP work-experience list for any user by LFID. | ||
| * Used to pre-fill the organization field in the add-member dialog. | ||
| */ | ||
| public async getUserWorkExperiences(req: Request, res: Response, next: NextFunction): Promise<void> { |
There was a problem hiding this comment.
🟡 Warning — No authorization check on work-experience lookup
Any authenticated user can query any other user's employment history by LFID — including start and end dates which are personally identifying. There is no check that the requestor has a committee-admin relationship with the target user.
In practice the UI only reaches this endpoint when an admin selects a user from the member-search results, but the endpoint itself is unguarded at the BFF layer.
Suggested fix: Add a committee-admin authorization guard (similar to the pattern used on other admin-only write endpoints) so only users with a committee-write relation can query another user's work experiences:
router.get(
'/users/:lfid/work-experiences',
requireProjectAccess('committee_admin'), // or equivalent existing middleware
(req, res, next) => searchController.getUserWorkExperiences(req, res, next)
);At minimum, scope to users who appear in the requester's committee member search results.
Summary
Invite accept flow: when a pending invite requires organization and the invite itself carries no pre-set org, the accept flow now fetches the invitee's current employer from their CDP work-experience profile (
/api/profile/work-experiences) and pre-fills the org dialog instead of opening it blank. Falls back gracefully to blank when CDP returns no data.Add-member dialog: when a committee admin selects an existing user from the search results in the Add Member dialog, the optional Organization field auto-populates from that user's CDP work-experience history. A new BFF endpoint (
GET /api/search/users/:lfid/work-experiences) is added for this purpose; it returns only the org-prefill fields (name, id, startDate, endDate) to minimise data exposure. The hint text under the Organization label is updated to communicate the auto-populate behaviour.Shared utility:
currentEmployerFromWorkExperiences()is extracted into@lfx-one/shared/utilsso both flows use the same employer-selection logic. Prefers an entry without anendDate(current job), falls back to most-recent bystartDateusing a Safari-safe "MMM YYYY" parser. Unit tests added ininvitation.utils.spec.ts.OrganizationSearchComponent: fixed the parent→search-input synceffect()so it reacts to programmaticpatchValue()calls (not just the initial render), and also clears the search input when the parent control is cleared.Ticket
LFXV2-2528 / LFXV2-2531
Test plan
GET /api/profile/work-experiences(plural) — confirm endpoint exists and returns work experiences for the authenticated userGET /api/search/users/:lfid/work-experiences— confirm returns only{ organization, organizationId, startDate, endDate }per entry (no job title, source, or verification fields)