Skip to content

fix(invite): pre-fill org dialog from user profile when invite has none#1034

Open
andrest50 wants to merge 12 commits into
mainfrom
fix/LFXV2-2528-prefill-org-from-profile
Open

fix(invite): pre-fill org dialog from user profile when invite has none#1034
andrest50 wants to merge 12 commits into
mainfrom
fix/LFXV2-2528-prefill-org-from-profile

Conversation

@andrest50

@andrest50 andrest50 commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

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/utils so both flows use the same employer-selection logic. Prefers an entry without an endDate (current job), falls back to most-recent by startDate using a Safari-safe "MMM YYYY" parser. Unit tests added in invitation.utils.spec.ts.

  • OrganizationSearchComponent: fixed the parent→search-input sync effect() so it reacts to programmatic patchValue() 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

  • Accept a pending committee invite that requires org and has no pre-set org — org dialog should open pre-filled with current employer from CDP profile, or blank if no work history
  • Accept a pending committee invite that already carries an org — dialog skips CDP fetch, shows the invite org
  • Add member to an org-requiring committee: search for a user with known work history, select them — Organization field should auto-populate; hint text mentions auto-populate
  • Add member: selecting a user with no CDP work history leaves the org field blank (no error)
  • Add member: org field is blank before selecting a user; after selecting, auto-populates; manually typing in the field is still possible and overrides the auto-fill
  • GET /api/profile/work-experiences (plural) — confirm endpoint exists and returns work experiences for the authenticated user
  • GET /api/search/users/:lfid/work-experiences — confirm returns only { organization, organizationId, startDate, endDate } per entry (no job title, source, or verification fields)

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>
@andrest50 andrest50 requested a review from a team as a code owner June 26, 2026 19:38
Copilot AI review requested due to automatic review settings June 26, 2026 19:38
@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Invitation 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.

Changes

Invitation organization prefill

Layer / File(s) Summary
Employer derivation utility
packages/shared/src/utils/invitation.utils.ts
currentEmployerFromWorkExperiences selects a current or most recent organization from work-experience entries and maps it to a committee organization reference.
Work-experience lookup API
apps/lfx-one/src/server/controllers/search.controller.ts, apps/lfx-one/src/server/routes/search.route.ts, apps/lfx-one/src/app/shared/services/search.service.ts
SearchController and SearchService add a user work-experience endpoint and client method that return work-experience entries for an LFID.
Invitation dialog prefill
apps/lfx-one/src/app/shared/services/invitation-accept-flow.service.ts
InvitationAcceptFlowService.accept() resolves a missing organization from profile work experiences before opening the confirmation dialog and accepting the invitation.
Member form organization fill
apps/lfx-one/src/app/modules/committees/components/add-member-dialog/add-member-dialog.component.ts
addEmail fills the member form organization field from the selected user's work experiences when the field is visible and blank.

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[]
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately describes the main change: pre-filling the invite org dialog from the user profile.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The description matches the invite prefill, add-member autofill, shared utility, and search endpoint changes in the PR.
✨ 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/LFXV2-2528-prefill-org-from-profile

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

@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

🤖 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

📥 Commits

Reviewing files that changed from the base of the PR and between 3a728ad and 8919acc.

📒 Files selected for processing (1)
  • apps/lfx-one/src/app/shared/services/invitation-accept-flow.service.ts

Comment thread apps/lfx-one/src/app/shared/services/invitation-accept-flow.service.ts Outdated
Comment thread apps/lfx-one/src/app/shared/services/invitation-accept-flow.service.ts Outdated
@github-actions

Copy link
Copy Markdown

🚀 Deployment Status

Your branch has been deployed to: https://ui-pr-1034.dev.v2.cluster.linuxfound.info

Deployment Details:

  • Environment: Development
  • Namespace: ui-pr-1034
  • ArgoCD App: ui-pr-1034

The deployment will be automatically removed when this PR is closed.

Copilot AI 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.

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 InvitationAcceptFlowService to 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.organization field so the user can still edit/clear before confirming.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread apps/lfx-one/src/app/shared/services/invitation-accept-flow.service.ts Outdated
- 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>
Copilot AI review requested due to automatic review settings June 26, 2026 20:12

@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: 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

📥 Commits

Reviewing files that changed from the base of the PR and between ce2e3f0 and 87eff8f.

📒 Files selected for processing (1)
  • apps/lfx-one/src/app/modules/committees/components/add-member-dialog/add-member-dialog.component.ts

Copilot AI 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.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

Comment thread apps/lfx-one/src/app/shared/services/invitation-accept-flow.service.ts Outdated
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>
@andrest50

Copy link
Copy Markdown
Contributor Author

Review Feedback Addressed

Commit: 5ebf4ff

Changes Made

  • add-member-dialog.component.ts: Removed organization_url from the auto-fill patchValue. Pre-filling the URL alongside the name could leave a stale URL in the submit payload if the inviter subsequently changes the org name. The URL is now resolved by the OrganizationSearchComponent when the inviter confirms the org. (per coderabbitai[bot])
  • invitation-accept-flow.service.ts: Replaced new Date(startDate) date sort with a deterministic monthYearToOrdinal() helper that parses the "MMM YYYY" format produced by the BFF. new Date() on non-ISO strings is spec-undefined and fails silently in Safari. (per copilot-pull-request-reviewer[bot])

Threads Resolved

2 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>
Copilot AI review requested due to automatic review settings June 26, 2026 20:42

Copilot AI 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.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Comment thread apps/lfx-one/src/server/controllers/search.controller.ts
Comment thread packages/shared/src/utils/invitation.utils.ts
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>
Copilot AI review requested due to automatic review settings June 26, 2026 21:16
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>

Copilot AI 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.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 4 comments.

Comment thread packages/shared/src/utils/invitation.utils.ts
Comment thread apps/lfx-one/src/server/controllers/search.controller.ts
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>
Copilot AI review requested due to automatic review settings June 26, 2026 21:23

Copilot AI 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.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 6 comments.

Comment thread packages/shared/src/utils/invitation.utils.ts
Comment thread apps/lfx-one/src/server/controllers/search.controller.ts
Comment thread apps/lfx-one/src/server/routes/search.route.ts
Copilot AI review requested due to automatic review settings June 26, 2026 21:28

Copilot AI 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.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 5 comments.

Comment thread apps/lfx-one/src/server/controllers/search.controller.ts
Comment thread apps/lfx-one/src/server/controllers/search.controller.ts
Comment thread packages/shared/src/utils/invitation.utils.ts
- 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 MRashad26 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.

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 + monthYearToOrdinal are clean and well-tested (7 cases). Safari-safe MMM YYYY parser avoids new Date() on non-ISO strings. ✅
  • invitation-accept-flow.service.ts CDP pre-fill uses catchError(() => of(context)) so the flow never blocks on a CDP failure — correct fail-open approach for a UX convenience. ✅
  • add-member-dialog double-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) => {

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.

🟡 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> {

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.

🟡 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants