Skip to content

Conversation

@asithade
Copy link
Contributor

@asithade asithade commented Oct 1, 2025

Description

This PR implements comprehensive user search functionality for meeting registrants, allowing users to search and select existing registrants or enter details manually.

JIRA Tickets

  • LFXV2-622: Implement backend user search API
  • LFXV2-623: Create reusable user search component
  • LFXV2-624: Enhance autocomplete component with template support
  • LFXV2-625: Integrate user search into registrant form

Changes Made

Backend

  • Created SearchService for querying meeting registrants and committee members
  • Added search controller and routes at /api/search/users
  • Integrated with query microservice for resource fetching
  • Unified response format for both registrant and member types

Frontend Components

  • User Search Component: Reusable autocomplete search with debouncing
  • Autocomplete Enhancements: Added custom item and footer template support
  • Registrant Form Integration: Toggle between search and manual entry modes

Features

  • 🔍 Search for existing users (meeting registrants or committee members)
  • ⚡ Debounced search with minimum 2 characters
  • 📝 Auto-populate form fields on user selection
  • ✏️ Manual entry option always available
  • 🔄 Switch between search and manual modes
  • ✅ Proper email validation on selection

Testing Instructions

  1. Navigate to a meeting's manage page
  2. Go to the Registrants tab
  3. Click "Add Registrant"
  4. Try searching for a user by typing at least 2 characters
  5. Select a user from the dropdown - form should populate
  6. Try "Enter details manually" option from footer
  7. Verify "Back to search" functionality
  8. Test form validation after user selection

Screenshots

  • User search with results displayed
  • Manual entry option in footer
  • Individual fields after selection
  • Styled divider for manual entry link

- LFXV2-622: Implement backend user search API
  - Created SearchService for querying meeting registrants and committee members
  - Added search controller and routes at /api/search/users
  - Integrated with query microservice for resource fetching

- LFXV2-623: Create reusable user search component
  - Built Angular component with autocomplete and debounced search
  - Populates parent form controls on user selection
  - Includes manual entry option in footer

- LFXV2-624: Enhance autocomplete component with template support
  - Added custom item and footer template support
  - Fixed template context passing for proper data binding

- LFXV2-625: Integrate user search into registrant form
  - Added toggle between search and manual entry modes
  - Fixed email validation on user selection
  - Styled with professional divider and transitions

Signed-off-by: Asitha de Silva <[email protected]>
@asithade asithade requested a review from jordane as a code owner October 1, 2025 18:07
Copilot AI review requested due to automatic review settings October 1, 2025 18:07
@coderabbitai
Copy link

coderabbitai bot commented Oct 1, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Out of Scope Changes Check ⚠️ Warning The pull request introduces unrelated changes such as duplicate mounting of the search API router in server.ts and a placeholder text update in meeting-registrants.component.html that are not required by the linked issues, indicating modifications outside the defined scope. Remove the duplicate router mounting in server.ts and revert or isolate the placeholder text change in meeting-registrants.component.html to a separate, focused pull request.
✅ Passed checks (4 passed)
Check name Status Explanation
Title Check ✅ Passed The title uses a clear conventional commit prefix and succinctly describes the main feature of implementing user search for meeting registrants, accurately reflecting the primary change in the pull request.
Linked Issues Check ✅ Passed The pull request implements the backend SearchService, SearchController, and routes with name and type filtering and unified UserSearchResult format as required by LFXV2-622, introduces a standalone UserSearchComponent with debounced autocomplete, multi-control form integration, clear and manual entry options as specified in LFXV2-623, enhances the autocomplete wrapper to support custom item and footer templates meeting LFXV2-624 criteria, and integrates search into the registrant form with mode switching, host state preservation, validation, and reactive signals per LFXV2-625.
Description Check ✅ Passed The description comprehensively outlines the backend and frontend changes for user search, lists relevant JIRA tickets, details new service, controller, component, and UI enhancements, and provides clear testing instructions, demonstrating direct relevance to the pull request contents.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/LFXV2-622

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

Copy link
Contributor

Copilot AI left a comment

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 implements comprehensive user search functionality for meeting registrants, allowing users to search for existing registrants or committee members and auto-populate form fields, with a fallback option for manual entry.

  • Created backend search service and API endpoints for querying users across meeting registrants and committee members
  • Built reusable frontend user search component with debounced autocomplete functionality
  • Enhanced the registrant form to toggle between search mode and manual entry with proper state management

Reviewed Changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
packages/shared/src/utils/form.utils.ts Added utility functions for form control validation updates with configurable parameters
packages/shared/src/interfaces/search.interface.ts Defined TypeScript interfaces for user search functionality including request/response structures
apps/lfx-one/src/server/services/search.service.ts Implemented backend search service to query and transform user data from microservices
apps/lfx-one/src/server/server.ts Registered new search routes in the Express application
apps/lfx-one/src/server/routes/search.route.ts Created API routing for user search endpoints
apps/lfx-one/src/server/controllers/search.controller.ts Implemented search controller with input validation and error handling
apps/lfx-one/src/server/controllers/public-meeting.controller.ts Enhanced logging and error handling for meeting data retrieval
apps/lfx-one/src/app/shared/services/search.service.ts Created frontend service to interact with user search API endpoints
apps/lfx-one/src/app/shared/components/user-search/user-search.component.ts Built reusable user search component with debouncing and form integration
apps/lfx-one/src/app/shared/components/user-search/user-search.component.html Template for user search autocomplete with custom item and footer templates
apps/lfx-one/src/app/shared/components/organization-search/organization-search.component.ts Added debouncing to existing organization search for consistency
apps/lfx-one/src/app/shared/components/autocomplete/autocomplete.component.ts Enhanced autocomplete component to support custom item and footer templates
apps/lfx-one/src/app/shared/components/autocomplete/autocomplete.component.html Added template support for custom item and footer rendering
apps/lfx-one/src/app/modules/project/meetings/components/registrant-form/registrant-form.component.ts Integrated user search functionality with toggle between search and manual entry modes
apps/lfx-one/src/app/modules/project/meetings/components/registrant-form/registrant-form.component.html Updated template to support both search interface and individual form fields

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

@github-actions
Copy link

github-actions bot commented Oct 1, 2025

✅ E2E Tests Passed

Browser: chromium
Status: passed

All E2E tests passed successfully.

Test Configuration

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (14)
packages/shared/src/utils/form.utils.ts (1)

21-29: Consider triggering form-level validators and handling nested controls

  • After updating each control, call
    form.updateValueAndValidity({ onlySelf, emitEvent })
    to run any FormGroup-level validators (e.g. { validators: futureDateTimeValidator() } in meeting-manage.component.ts).
  • Current logic only iterates one level down—nested FormArrays/FormGroups (e.g. new FormArray([]) in meeting-manage.component.ts) won’t have their child controls updated. Introduce a recursive traversal if you need to validate those too.
apps/lfx-one/src/app/shared/components/user-search/user-search.component.html (1)

19-44: Add data-testid attributes to template sections.

Consider adding data-testid attributes to the main container divs in the item, empty, and footer templates for better test coverage and component verification.

As per coding guidelines.

Example additions:

   <ng-template #item let-user>
-    <div class="w-full flex justify-between items-center gap-2">
+    <div class="w-full flex justify-between items-center gap-2" data-testid="user-search-item">
       <div class="flex flex-col">
   <ng-template #empty>
-    <div class="p-3 text-center">
+    <div class="p-3 text-center" data-testid="user-search-empty">
       <p class="text-sm text-gray-600">No users found</p>
   <ng-template #footer>
-    <div class="p-2 border-t border-gray-200 text-center">
+    <div class="p-2 border-t border-gray-200 text-center" data-testid="user-search-footer">
       <button type="button" class="text-sm text-primary hover:underline" data-testid="user-search-manual-entry-button" (click)="onEnterManually()">Enter details manually</button>
apps/lfx-one/src/app/shared/services/search.service.ts (2)

18-32: Consider surfacing errors instead of silently returning empty arrays.

The current implementation catches errors and returns an empty array, making it impossible for callers to distinguish between "no results found" and "an error occurred." This can mask issues like network failures or authorization problems.

Consider either:

  1. Re-throwing errors after logging so callers can handle them appropriately
  2. Returning a discriminated union or result type that includes error state
  3. Using a centralized logging service instead of console.error

Example discriminated union approach:

// Option 1: Let errors propagate
public searchUsers(name: string, type: 'committee_member' | 'meeting_registrant'): Observable<UserSearchResult[]> {
  if (!name || !type) {
    return of([]);
  }

  const params = new HttpParams().set('name', name).set('type', type).set('limit', '20');

  return this.http.get<UserSearchResponse>('/api/search/users', { params }).pipe(
    map((response) => response.results || [])
    // Let error propagate to caller
  );
}

37-50: Same error handling concern applies here.

This method has the same silent error swallowing issue as searchUsers. Consider the same refactoring approaches mentioned in the previous comment.

Additionally, consider extracting the default limit (20) and offset (0) as class constants to avoid duplication.

private readonly DEFAULT_LIMIT = 20;
private readonly DEFAULT_OFFSET = 0;

public searchUsersAdvanced(params: UserSearchParams): Observable<UserSearchResponse> {
  const httpParams = new HttpParams()
    .set('name', params.name)
    .set('type', params.type)
    .set('limit', (params.limit || this.DEFAULT_LIMIT).toString())
    .set('offset', (params.offset || this.DEFAULT_OFFSET).toString());

  return this.http.get<UserSearchResponse>('/api/search/users', { params: httpParams });
  // Let errors propagate or handle explicitly
}
apps/lfx-one/src/server/services/search.service.ts (2)

23-29: Consider aligning default limit with client service.

The server-side default limit is 50, while the client-side service (apps/lfx-one/src/app/shared/services/search.service.ts lines 23, 41) uses a default of 20. This inconsistency could cause confusion.

Since the client always sends an explicit limit, the server default won't typically be used, but aligning the defaults would make the API contract clearer.

Consider using the same default value (20) on both client and server:

 const queryParams = {
   v: 1,
   name: params.name,
   type: params.type,
-  limit: params.limit || 50,
+  limit: params.limit || 20,
   offset: params.offset || 0,
 };

84-89: Consider clarifying "total" field semantics in documentation.

The total field is set to results.length (the count of returned results), not the total count of matching records in the database. This is consistent with the UserSearchResponse interface, but callers might expect total to represent all matching records across all pages.

The current implementation is acceptable since the interface documentation describes it as "Total number of matching users" (which could mean "in this response"). However, if confusion arises, consider:

  1. Updating interface documentation to clarify this means "results in this response"
  2. Or implementing true total count if the backend supports it
apps/lfx-one/src/app/modules/project/meetings/components/registrant-form/registrant-form.component.ts (4)

29-40: Initialize manual mode if registrant is already set pre-subscription

Guard for the case where the parent sets a non-null registrant before the subscription attaches.

   public constructor() {
+    // Handle initial value eagerly
+    if (this.registrant()) {
+      this.showIndividualFields.set(true);
+    }
     // If we have an existing registrant, show individual fields immediately
     // Using toObservable to specifically track only registrant changes

47-52: Run validators after selection

After the child populates controls, ensure validators run and UI reflects state.

   public handleUserSelection(_user: UserSearchResult): void {
     // The search component has already populated the form controls
     // Now show the individual input fields
     this.showIndividualFields.set(true);
-    this.form().markAsDirty();
+    const form = this.form();
+    form.markAsDirty();
+    form.markAllAsTouched();
+    form.updateValueAndValidity({ emitEvent: false });
   }

57-65: Reset preserving host without duplicate writes; reset state cleanly

Use FormGroup.reset with value map to avoid a second setValue; also reset pristine/touched for a clean manual-entry start.

   public switchToManualEntry(): void {
     // Clear the form for manual entry
-    const hostValue = this.form().get('host')?.value; // Preserve host checkbox state
-    this.form().reset();
-    this.form().get('host')?.setValue(hostValue);
+    const hostValue = this.form().get('host')?.value ?? false; // Preserve host checkbox state
+    this.form().reset({ host: hostValue }, { emitEvent: false });
+    this.form().markAsPristine();
+    this.form().markAsUntouched();
 
     // Show individual fields for manual entry
     this.showIndividualFields.set(true);
   }

70-78: DRY the reset logic and keep form state consistent when returning to search

Mirror the improvements from manual-entry and consider extracting a small helper.

   public backToSearch(): void {
     // Reset form but preserve host checkbox
-    const hostValue = this.form().get('host')?.value;
-    this.form().reset();
-    this.form().get('host')?.setValue(hostValue);
+    const hostValue = this.form().get('host')?.value ?? false;
+    this.form().reset({ host: hostValue }, { emitEvent: false });
+    this.form().markAsPristine();
+    this.form().markAsUntouched();
 
     // Switch back to search mode
     this.showIndividualFields.set(false);
   }
apps/lfx-one/src/app/shared/components/user-search/user-search.component.ts (4)

24-26: Reuse central type for searchType

Avoid duplicating the union; tie it to the shared interface to keep types in sync. As per coding guidelines.

-  public searchType = input.required<'committee_member' | 'meeting_registrant'>();
+  public searchType = input.required<UserSearchResult['type']>();

91-103: Guard against redundant setValue in effect

Avoid unnecessary writes by checking current value before setting.

-        if (emailControlValue && emailControlValue.trim()) {
-          this.userSearchForm.get('userSearch')?.setValue(emailControlValue, { emitEvent: false });
-        }
+        if (emailControlValue && emailControlValue.trim()) {
+          const ctrl = this.userSearchForm.get('userSearch');
+          if (ctrl?.value !== emailControlValue) {
+            ctrl?.setValue(emailControlValue, { emitEvent: false });
+          }
+        }

165-185: Reset form state on clear

Also reset pristine/touched and validators so UX returns to a clean slate.

   public onSearchClear(): void {
     this.userSearchForm.get('userSearch')?.setValue('');
 
     // Clear all form controls if they are specified
     const parentForm = this.form();
@@
       if (controlName && parentForm.get(controlName)) {
         parentForm.get(controlName)?.setValue(null);
       }
     });
+    parentForm.markAsPristine();
+    parentForm.markAsUntouched();
+    parentForm.updateValueAndValidity({ emitEvent: false });
   }

56-72: Unify debouncing to avoid double‐latency (user-search.component.ts)
The wrapper <lfx-autocomplete> defaults to [delay]=300ms and [minLength]=1 while your valueChanges pipeline also does debounceTime(300) and filters out <2 chars, stacking ~600ms before any request. Choose one:

  • Option A: drop the Rx debounceTime(300) in user-search.component.ts and rely on the Autocomplete’s delay/minLength.
  • Option B: disable the PrimeNG delay by calling <lfx-autocomplete [delay]="0" [minLength]="2"> and keep your Rx debounceTime(300)/threshold.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between d81d0d0 and 07f3568.

📒 Files selected for processing (15)
  • apps/lfx-one/src/app/modules/project/meetings/components/registrant-form/registrant-form.component.html (1 hunks)
  • apps/lfx-one/src/app/modules/project/meetings/components/registrant-form/registrant-form.component.ts (1 hunks)
  • apps/lfx-one/src/app/shared/components/autocomplete/autocomplete.component.html (1 hunks)
  • apps/lfx-one/src/app/shared/components/autocomplete/autocomplete.component.ts (1 hunks)
  • apps/lfx-one/src/app/shared/components/organization-search/organization-search.component.ts (2 hunks)
  • apps/lfx-one/src/app/shared/components/user-search/user-search.component.html (1 hunks)
  • apps/lfx-one/src/app/shared/components/user-search/user-search.component.ts (1 hunks)
  • apps/lfx-one/src/app/shared/services/search.service.ts (1 hunks)
  • apps/lfx-one/src/server/controllers/public-meeting.controller.ts (1 hunks)
  • apps/lfx-one/src/server/controllers/search.controller.ts (1 hunks)
  • apps/lfx-one/src/server/routes/search.route.ts (1 hunks)
  • apps/lfx-one/src/server/server.ts (2 hunks)
  • apps/lfx-one/src/server/services/search.service.ts (1 hunks)
  • packages/shared/src/interfaces/search.interface.ts (1 hunks)
  • packages/shared/src/utils/form.utils.ts (1 hunks)
🔥 Files not summarized due to errors (2)
  • apps/lfx-one/src/app/shared/components/organization-search/organization-search.component.ts: Error: Server error: no LLM provider could handle the message
  • apps/lfx-one/src/app/shared/components/autocomplete/autocomplete.component.ts: Error: Server error: no LLM provider could handle the message
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Use TypeScript interfaces instead of union types for better maintainability
When defining PrimeNG-related types, reference the official PrimeNG component interfaces

Files:

  • apps/lfx-one/src/server/routes/search.route.ts
  • apps/lfx-one/src/server/controllers/search.controller.ts
  • apps/lfx-one/src/server/controllers/public-meeting.controller.ts
  • apps/lfx-one/src/app/shared/components/organization-search/organization-search.component.ts
  • apps/lfx-one/src/app/shared/components/autocomplete/autocomplete.component.ts
  • apps/lfx-one/src/app/shared/services/search.service.ts
  • packages/shared/src/interfaces/search.interface.ts
  • apps/lfx-one/src/server/services/search.service.ts
  • apps/lfx-one/src/app/modules/project/meetings/components/registrant-form/registrant-form.component.ts
  • apps/lfx-one/src/server/server.ts
  • packages/shared/src/utils/form.utils.ts
  • apps/lfx-one/src/app/shared/components/user-search/user-search.component.ts
**/*.{ts,tsx,js,jsx,mjs,cjs,html,css,scss}

📄 CodeRabbit inference engine (CLAUDE.md)

Include required license headers on all source files

Files:

  • apps/lfx-one/src/server/routes/search.route.ts
  • apps/lfx-one/src/server/controllers/search.controller.ts
  • apps/lfx-one/src/app/shared/components/user-search/user-search.component.html
  • apps/lfx-one/src/app/shared/components/autocomplete/autocomplete.component.html
  • apps/lfx-one/src/server/controllers/public-meeting.controller.ts
  • apps/lfx-one/src/app/shared/components/organization-search/organization-search.component.ts
  • apps/lfx-one/src/app/shared/components/autocomplete/autocomplete.component.ts
  • apps/lfx-one/src/app/shared/services/search.service.ts
  • packages/shared/src/interfaces/search.interface.ts
  • apps/lfx-one/src/server/services/search.service.ts
  • apps/lfx-one/src/app/modules/project/meetings/components/registrant-form/registrant-form.component.html
  • apps/lfx-one/src/app/modules/project/meetings/components/registrant-form/registrant-form.component.ts
  • apps/lfx-one/src/server/server.ts
  • packages/shared/src/utils/form.utils.ts
  • apps/lfx-one/src/app/shared/components/user-search/user-search.component.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Do not nest ternary expressions

Files:

  • apps/lfx-one/src/server/routes/search.route.ts
  • apps/lfx-one/src/server/controllers/search.controller.ts
  • apps/lfx-one/src/server/controllers/public-meeting.controller.ts
  • apps/lfx-one/src/app/shared/components/organization-search/organization-search.component.ts
  • apps/lfx-one/src/app/shared/components/autocomplete/autocomplete.component.ts
  • apps/lfx-one/src/app/shared/services/search.service.ts
  • packages/shared/src/interfaces/search.interface.ts
  • apps/lfx-one/src/server/services/search.service.ts
  • apps/lfx-one/src/app/modules/project/meetings/components/registrant-form/registrant-form.component.ts
  • apps/lfx-one/src/server/server.ts
  • packages/shared/src/utils/form.utils.ts
  • apps/lfx-one/src/app/shared/components/user-search/user-search.component.ts
apps/lfx-one/src/**/*.html

📄 CodeRabbit inference engine (CLAUDE.md)

apps/lfx-one/src/**/*.html: Always add data-testid attributes when creating new Angular components for reliable test targeting
Use data-testid naming convention [section]-[component]-[element]

Files:

  • apps/lfx-one/src/app/shared/components/user-search/user-search.component.html
  • apps/lfx-one/src/app/shared/components/autocomplete/autocomplete.component.html
  • apps/lfx-one/src/app/modules/project/meetings/components/registrant-form/registrant-form.component.html
packages/shared/src/interfaces/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Place all TypeScript interfaces in the shared package at packages/shared/src/interfaces

Files:

  • packages/shared/src/interfaces/search.interface.ts
🧬 Code graph analysis (6)
apps/lfx-one/src/server/routes/search.route.ts (1)
apps/lfx-one/src/server/controllers/search.controller.ts (1)
  • SearchController (14-101)
apps/lfx-one/src/server/controllers/search.controller.ts (2)
apps/lfx-one/src/server/services/search.service.ts (1)
  • SearchService (12-90)
packages/shared/src/interfaces/search.interface.ts (1)
  • UserSearchParams (83-92)
apps/lfx-one/src/app/shared/services/search.service.ts (2)
apps/lfx-one/src/server/services/search.service.ts (1)
  • SearchService (12-90)
packages/shared/src/interfaces/search.interface.ts (3)
  • UserSearchResult (48-77)
  • UserSearchResponse (98-105)
  • UserSearchParams (83-92)
apps/lfx-one/src/server/services/search.service.ts (4)
packages/shared/src/interfaces/search.interface.ts (3)
  • UserSearchParams (83-92)
  • UserSearchResponse (98-105)
  • UserSearchResult (48-77)
packages/shared/src/interfaces/api.interface.ts (1)
  • QueryServiceResponse (58-61)
packages/shared/src/interfaces/meeting.interface.ts (1)
  • MeetingRegistrant (260-301)
packages/shared/src/interfaces/member.interface.ts (1)
  • CommitteeMember (10-64)
apps/lfx-one/src/app/modules/project/meetings/components/registrant-form/registrant-form.component.ts (4)
apps/lfx-one/src/app/shared/components/organization-search/organization-search.component.ts (1)
  • Component (16-161)
apps/lfx-one/src/app/shared/components/user-search/user-search.component.ts (1)
  • Component (15-198)
packages/shared/src/interfaces/meeting.interface.ts (1)
  • MeetingRegistrant (260-301)
packages/shared/src/interfaces/search.interface.ts (1)
  • UserSearchResult (48-77)
apps/lfx-one/src/app/shared/components/user-search/user-search.component.ts (4)
apps/lfx-one/src/app/modules/project/meetings/components/registrant-form/registrant-form.component.ts (1)
  • Component (15-79)
apps/lfx-one/src/app/shared/components/autocomplete/autocomplete.component.ts (1)
  • Component (9-48)
apps/lfx-one/src/app/shared/components/organization-search/organization-search.component.ts (1)
  • Component (16-161)
packages/shared/src/interfaces/search.interface.ts (1)
  • UserSearchResult (48-77)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: E2E Tests / Playwright E2E Tests
🔇 Additional comments (16)
apps/lfx-one/src/app/shared/components/organization-search/organization-search.component.ts (1)

50-53: Nice debounce addition.

This 300 ms debounce smooths out keystrokes and trims redundant service calls while keeping the existing flow intact.

apps/lfx-one/src/app/shared/components/autocomplete/autocomplete.component.ts (2)

17-18: LGTM! Template support properly implemented.

The new itemTemplate and footerTemplate ContentChild declarations follow the same pattern as the existing emptyTemplate, ensuring consistency and proper content projection.


27-27: Good addition for debounced search.

The delay input with a default of 300ms provides sensible debouncing for search operations, aligning with the PR's requirement for debounced autocomplete.

apps/lfx-one/src/app/shared/components/autocomplete/autocomplete.component.html (2)

29-32: LGTM! Item template context properly configured.

The item template correctly uses { $implicit: item } context, enabling consumers to access the item via let-item syntax in their templates.


34-37: LGTM! Footer template properly implemented.

The footer template follows the same conditional rendering pattern as the other templates and enables persistent actions in the autocomplete dropdown.

packages/shared/src/interfaces/search.interface.ts (3)

48-77: LGTM! Well-structured unified user interface.

The UserSearchResult interface properly combines fields from both MeetingRegistrant and CommitteeMember with appropriate nullable types and clear documentation. The discriminated union using the type literal enables type-safe handling of different user sources.


83-92: LGTM! Clean search parameters interface.

The UserSearchParams interface provides clear search criteria with optional pagination support.


98-105: LGTM! Standard API response wrapper.

The UserSearchResponse interface follows common API pagination patterns with results array and optional metadata fields.

apps/lfx-one/src/server/server.ts (2)

27-27: LGTM! Search router properly imported.

The import follows the established pattern for other route modules.


207-207: LGTM! Search route properly mounted in protected API section.

The search router is correctly mounted at /api/search after authentication middleware and before the error handler, placing it in the protected API routes section. This ensures search functionality requires authentication.

apps/lfx-one/src/app/shared/components/user-search/user-search.component.html (2)

19-31: LGTM! Clear user item presentation.

The item template effectively displays user information with name, optional organization, and email in a well-structured layout.


33-37: LGTM! Clear empty state message.

The empty template provides appropriate user feedback when no results are found.

apps/lfx-one/src/app/modules/project/meetings/components/registrant-form/registrant-form.component.html (1)

44-132: LGTM! Individual fields section is well-structured.

The individual fields section properly:

  • Uses conditional rendering for the back button based on registrant state
  • Includes all required form fields with proper bindings
  • Shows validation messages conditionally
  • Follows the data-testid naming convention as per coding guidelines
  • Maintains good accessibility with labels
apps/lfx-one/src/server/services/search.service.ts (1)

39-82: LGTM! Resource transformation is well-implemented.

The transformation logic properly:

  • Handles both meeting_registrant and committee_member types
  • Maps fields correctly to the UserSearchResult interface
  • Uses appropriate null handling for optional fields (job_title, organization, username)
  • Sets type-specific fields correctly (committee only for committee_member)
apps/lfx-one/src/server/routes/search.route.ts (1)

1-14: No duplicate mounting of searchRouter in server.ts
The searchRouter is imported once and registered exactly once at /api/search (line 207), so no duplicate route handlers exist.

apps/lfx-one/src/app/shared/components/user-search/user-search.component.ts (1)

1-3: Headers present and correct

License and SPDX headers look good.

- Add email-based search using tags parameter
- Support searching by email format detection
- Simplify registrant form handleUserSelection method
- Update placeholder text for clarity
- Enhance search controller validation for name or tags
- Remove unused imports and parameters
- Clean up unused searchUsersAdvanced method

LFXV2-622 LFXV2-623 LFXV2-625

Signed-off-by: Asitha de Silva <[email protected]>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (3)
apps/lfx-one/src/app/modules/project/meetings/components/registrant-form/registrant-form.component.html (1)

14-28: Fix searchType to target meeting registrants. The registrant form must query the meeting registrant dataset, but searchType="committee_member" still points to committee members and breaks the flow—switch it to "meeting_registrant" so the search returns the correct people.

apps/lfx-one/src/server/controllers/search.controller.ts (1)

78-85: Validate limit/offset query params before building search params. parseInt can yield NaN; we still pass that through to searchParams. Guard both fields (ensure finite, non-negative numbers) before assignment, otherwise treat them as undefined or reject the request.

apps/lfx-one/src/server/services/search.service.ts (1)

63-82: LGTM!

Committee member transformation is correct. The code after line 62 implicitly handles the else case (when params.type !== 'meeting_registrant'). While the past review suggested an explicit else clause, it's unnecessary here since params.type is constrained to two values by the interface, making the control flow clear without it.

🧹 Nitpick comments (1)
apps/lfx-one/src/server/services/search.service.ts (1)

15-17: Consider dependency injection for better testability.

The constructor directly instantiates MicroserviceProxyService, which makes unit testing harder (requires mocking the constructor). Consider accepting the proxy service as a constructor parameter to enable easier mocking and follow dependency injection patterns.

Example refactor:

-  public constructor() {
-    this.microserviceProxy = new MicroserviceProxyService();
+  public constructor(microserviceProxy?: MicroserviceProxyService) {
+    this.microserviceProxy = microserviceProxy || new MicroserviceProxyService();
   }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 07f3568 and 71d44cd.

📒 Files selected for processing (8)
  • apps/lfx-one/src/app/modules/project/meetings/components/meeting-registrants/meeting-registrants.component.html (1 hunks)
  • apps/lfx-one/src/app/modules/project/meetings/components/registrant-form/registrant-form.component.html (1 hunks)
  • apps/lfx-one/src/app/modules/project/meetings/components/registrant-form/registrant-form.component.ts (1 hunks)
  • apps/lfx-one/src/app/shared/components/user-search/user-search.component.ts (1 hunks)
  • apps/lfx-one/src/app/shared/services/search.service.ts (1 hunks)
  • apps/lfx-one/src/server/controllers/search.controller.ts (1 hunks)
  • apps/lfx-one/src/server/services/search.service.ts (1 hunks)
  • packages/shared/src/interfaces/search.interface.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • apps/lfx-one/src/app/modules/project/meetings/components/meeting-registrants/meeting-registrants.component.html
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/shared/src/interfaces/search.interface.ts
  • apps/lfx-one/src/app/shared/components/user-search/user-search.component.ts
  • apps/lfx-one/src/app/shared/services/search.service.ts
🧰 Additional context used
📓 Path-based instructions (4)
apps/lfx-one/src/**/*.html

📄 CodeRabbit inference engine (CLAUDE.md)

apps/lfx-one/src/**/*.html: Always add data-testid attributes when creating new Angular components for reliable test targeting
Use data-testid naming convention [section]-[component]-[element]

Files:

  • apps/lfx-one/src/app/modules/project/meetings/components/registrant-form/registrant-form.component.html
**/*.{ts,tsx,js,jsx,mjs,cjs,html,css,scss}

📄 CodeRabbit inference engine (CLAUDE.md)

Include required license headers on all source files

Files:

  • apps/lfx-one/src/app/modules/project/meetings/components/registrant-form/registrant-form.component.html
  • apps/lfx-one/src/server/services/search.service.ts
  • apps/lfx-one/src/server/controllers/search.controller.ts
  • apps/lfx-one/src/app/modules/project/meetings/components/registrant-form/registrant-form.component.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Use TypeScript interfaces instead of union types for better maintainability
When defining PrimeNG-related types, reference the official PrimeNG component interfaces

Files:

  • apps/lfx-one/src/server/services/search.service.ts
  • apps/lfx-one/src/server/controllers/search.controller.ts
  • apps/lfx-one/src/app/modules/project/meetings/components/registrant-form/registrant-form.component.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Do not nest ternary expressions

Files:

  • apps/lfx-one/src/server/services/search.service.ts
  • apps/lfx-one/src/server/controllers/search.controller.ts
  • apps/lfx-one/src/app/modules/project/meetings/components/registrant-form/registrant-form.component.ts
🧬 Code graph analysis (3)
apps/lfx-one/src/server/services/search.service.ts (4)
packages/shared/src/interfaces/search.interface.ts (3)
  • UserSearchParams (83-94)
  • UserSearchResponse (100-107)
  • UserSearchResult (48-77)
packages/shared/src/interfaces/api.interface.ts (1)
  • QueryServiceResponse (58-61)
packages/shared/src/interfaces/meeting.interface.ts (1)
  • MeetingRegistrant (260-301)
packages/shared/src/interfaces/member.interface.ts (1)
  • CommitteeMember (10-64)
apps/lfx-one/src/server/controllers/search.controller.ts (2)
apps/lfx-one/src/server/services/search.service.ts (1)
  • SearchService (12-91)
packages/shared/src/interfaces/search.interface.ts (1)
  • UserSearchParams (83-94)
apps/lfx-one/src/app/modules/project/meetings/components/registrant-form/registrant-form.component.ts (4)
apps/lfx-one/src/app/shared/components/user-search/user-search.component.ts (1)
  • Component (15-198)
apps/lfx-one/src/app/shared/components/autocomplete/autocomplete.component.ts (1)
  • Component (9-48)
apps/lfx-one/src/app/shared/components/organization-search/organization-search.component.ts (1)
  • Component (16-161)
packages/shared/src/interfaces/meeting.interface.ts (1)
  • MeetingRegistrant (260-301)
🔇 Additional comments (5)
apps/lfx-one/src/server/services/search.service.ts (5)

1-2: LGTM!

License header is present and correctly formatted.


4-7: LGTM!

Imports are clean and all appear necessary for the service functionality.


22-30: LGTM!

Query parameter construction is clean with appropriate defaults. The hardcoded v: 1 is acceptable but consider extracting to a constant if the API version needs to be managed centrally.


32-38: LGTM!

Microservice proxy call is correctly typed and structured.


41-62: LGTM!

Meeting registrant transformation logic correctly maps all fields to the unified UserSearchResult format with appropriate null handling.

Comment on lines +85 to +89
return {
results,
total: results.length,
has_more: results.length === queryParams.limit,
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Incorrect total calculation breaks pagination semantics.

Line 87 sets total: results.length, but according to the interface definition (packages/shared/src/interfaces/search.interface.ts), total should represent "Total number of matching users" across all pages, not just the current page size. This breaks pagination UIs that need to show "X of Y results" or calculate page counts.

The query microservice response (QueryServiceResponse) does not appear to include a total count field. You need to either:

  1. Request the total count from the backend (if available via a separate field or query), or
  2. Document that total is not available and use undefined instead of results.length.

Additionally, consider whether has_more can be reliably determined without a true total count.

Verify the query microservice response structure:


🏁 Script executed:

#!/bin/bash
# Check if QueryServiceResponse includes total count or metadata fields
ast-grep --pattern $'export interface QueryServiceResponse$_$ {
  $$$
}'

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Search for QueryServiceResponse definition
rg -n "export interface QueryServiceResponse" -t ts

Length of output: 103


🏁 Script executed:

#!/bin/bash
# Show full QueryServiceResponse fields
rg -n -C3 "export interface QueryServiceResponse" packages/shared/src/interfaces/api.interface.ts

Length of output: 236


Supply a real total count instead of using the page length.

QueryServiceResponse only provides resources (no total‐count field), so total: results.length misrepresents the overall result set. Update the backend call (or issue a separate count query) to return the true total before setting total, and continue using has_more as a page‐full heuristic.

🤖 Prompt for AI Agents
In apps/lfx-one/src/server/services/search.service.ts around lines 85 to 89, the
code sets total to results.length which only reflects the page size rather than
the overall matching count; change the implementation to obtain the real total
from the search backend (extend the QueryServiceResponse to include a totalCount
or call the backend’s count endpoint) or run a separate count query before
returning, then set total to that returned totalCount while keeping has_more as
(results.length === queryParams.limit) as a page-full heuristic; update any
types/interfaces to reflect the new total field and use that value in the return
object.

@asithade asithade merged commit 8aecaec into main Oct 1, 2025
8 checks passed
@asithade asithade deleted the feat/LFXV2-622 branch October 1, 2025 19:32
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.

3 participants