Skip to content

Conversation

@andrest50
Copy link
Contributor

@andrest50 andrest50 commented Oct 21, 2025

Summary

  • Display meeting recordings from Zoom with play button that opens recording in new tab
  • Show AI-generated meeting summaries with view, edit, and approve functionality
  • Allow users to edit summaries with real-time updates
  • Implement approval workflow for summaries before public viewing

Changes

Recording Display (LFXV2-667)

  • Added recording button to past meeting cards
  • Fetches largest session recording URL from backend
  • Opens recording in new tab when clicked
  • Shows only when recording is available

Summary Features (LFXV2-668)

  • Display AI-generated summaries in modal view
  • Edit summary content with textarea
  • Approve summaries for public viewing
  • Prioritize edited_content over original content
  • Keep modal open after save for further edits
  • ETag concurrency control for updates

Technical Details

  • Backend PUT endpoint for summary updates with ETag
  • Client-side service methods for update and approve
  • Signal-based state management in Angular components
  • Proper ngModel binding with signals

Demo

Screen.Recording.2025-10-21.at.2.43.53.PM.mov

Tickets

🤖 Generated with Claude Code

- Add PastMeetingRecording interfaces to shared package
- Implement OpenSearch query for past meeting recordings
- Create recording modal with URL copy and view functionality
- Add recording icon to past meeting cards
- Select session with largest total_size for share URL
- Add View Recording button to open URL directly in new tab

[LFXV2-667]

Generated with [Claude Code](https://claude.ai/code)

Signed-off-by: Andres Tobon <andrest2455@gmail.com>
- Add PastMeetingSummary interfaces to shared package
- Implement OpenSearch query for past meeting summaries
- Create summary modal with HTML content rendering
- Add summary icon to past meeting cards
- Display AI-generated summary with proper formatting
- Fix recording modal URL display to show beginning of URL
- Replace input with div element to control scroll position
- Add click-to-select functionality for URL copying

[LFXV2-667]

Generated with [Claude Code](https://claude.ai/code)

Signed-off-by: Andres Tobon <andrest2455@gmail.com>
Allow users to edit AI-generated meeting summaries and approve them for viewing by others. Summaries can be edited independently of approval status, and approval is a separate action requiring explicit user confirmation.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Signed-off-by: Andres Tobon <andrest2455@gmail.com>
Copilot AI review requested due to automatic review settings October 21, 2025 21:11
@andrest50 andrest50 requested a review from jordane as a code owner October 21, 2025 21:11
@coderabbitai
Copy link

coderabbitai bot commented Oct 21, 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.

Walkthrough

Adds past-meeting recording and AI-summary features: UI buttons and modals, MeetingCard signals/effects to fetch/manage data, client MeetingService endpoints, server routes/controllers/services with ETag update logic, and new shared TypeScript interfaces for recordings and summaries.

Changes

Cohort / File(s) Summary
Meeting Card UI
apps/lfx-one/src/app/modules/project/meetings/components/meeting-card/meeting-card.component.html
Adds conditional "View Recording" and "View Summary" buttons for past meetings that call modal open methods.
Meeting Card Logic
apps/lfx-one/src/app/modules/project/meetings/components/meeting-card/meeting-card.component.ts
Adds signals/state for recording and summary (recording, recordingShareUrl, hasRecording, summary, summaryContent, summaryUid, summaryApproved, hasSummary); initializes/fetches data; adds openRecordingModal() and openSummaryModal() and helper to derive largest share URL.
Recording Modal (UI + Logic)
apps/lfx-one/src/app/modules/project/meetings/components/recording-modal/recording-modal.component.html, apps/lfx-one/src/app/modules/project/meetings/components/recording-modal/recording-modal.component.ts
New standalone modal component showing meeting title, share URL, copy-to-clipboard action, open-in-new-tab, and close behavior; uses dialog config inputs and MessageService/Clipboard.
Summary Modal (UI + Logic)
apps/lfx-one/src/app/modules/project/meetings/components/summary-modal/summary-modal.component.html, apps/lfx-one/src/app/modules/project/meetings/components/summary-modal/summary-modal.component.ts
New standalone modal with view and edit modes for AI-generated summaries; supports edit/save (with loading), approve, sanitized display, and returns updated/approved data on close; integrates with MeetingService and MessageService.
Client Meeting Service
apps/lfx-one/src/app/shared/services/meeting.service.ts
Adds client API methods: getPastMeetingRecording(), getPastMeetingSummary(), updatePastMeetingSummary(), approvePastMeetingSummary() with 404→null handling and error propagation.
Backend Controller
apps/lfx-one/src/server/controllers/past-meeting.controller.ts
Adds controller endpoints: getPastMeetingRecording(), getPastMeetingSummary(), updatePastMeetingSummary() with request validation, logging, 404 handling, and error forwarding.
Backend Routes
apps/lfx-one/src/server/routes/past-meetings.route.ts
Registers new routes: GET /:uid/recording, GET /:uid/summary, PUT /:uid/summary/:summaryUid.
Backend Service
apps/lfx-one/src/server/services/meeting.service.ts
Implements service methods: getPastMeetingRecording(), getPastMeetingSummary() (returning null when missing), and updatePastMeetingSummary() using ETag-based concurrency and sanitization; adds logging and error propagation.
Shared Interfaces
packages/shared/src/interfaces/meeting.interface.ts
Adds types: RecordingSession, RecordingFile, PastMeetingRecording, SummaryData, ZoomSummaryConfig, PastMeetingSummary, UpdatePastMeetingSummaryRequest.
Meeting list IDs
apps/lfx-one/src/app/modules/project/meetings/meeting-dashboard/meeting-dashboard.component.html
Adds id attribute binding [attr.id]="'meeting-' + meeting.uid" to each meeting card.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Card as MeetingCard
    participant Client as Client MeetingService
    participant Server as Backend API
    participant RModal as RecordingModal
    participant SModal as SummaryModal

    Note over User,Card: View Recording (client-side)
    User->>Card: Click "View Recording"
    Card->>Card: openRecordingModal()
    Card->>RModal: open with shareUrl, meetingTitle
    RModal->>User: display URL / Copy / Open

    Note over User,Server: View/Edit/Approve Summary (round-trip)
    User->>Card: Click "View Summary"
    Card->>Client: getPastMeetingSummary(pastMeetingUid)
    Client->>Server: GET /past-meetings/:uid/summary
    Server-->>Client: PastMeetingSummary or 404/null
    Client-->>Card: summary data
    Card->>SModal: open with summary data
    alt Edit & Save
        User->>SModal: Edit -> Save
        SModal->>Client: updatePastMeetingSummary(uid, summaryUid, edited)
        Client->>Server: PUT /past-meetings/:uid/summary/:summaryUid (with ETag)
        Server-->>Client: Updated summary
        Client-->>SModal: updated summary -> SModal closes with updated payload
    end
    alt Approve
        User->>SModal: Approve
        SModal->>Client: approvePastMeetingSummary(uid, summaryUid)
        Client->>Server: PUT /past-meetings/:uid/summary/:summaryUid {approved:true}
        Server-->>Client: Approved summary
        Client-->>SModal: updated approved state
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Out of Scope Changes Check ⚠️ Warning While most changes directly support the stated objectives of LFXV2-667 and LFXV2-668, the modification to meeting-dashboard.component.html appears to be out of scope. This change adds an id attribute binding ([attr.id]='meeting-' + meeting.uid) to meeting cards, which does not relate to the core objectives of displaying recordings or implementing summary modals with edit and approval workflows. Although this is a minimal, non-breaking change likely intended for testing purposes, it falls outside the explicitly stated requirements. Consider removing the meeting-dashboard.component.html change (the id attribute binding) from this PR or explicitly document its necessity as a supporting change for e2e testing or identification purposes. If it is critical for testing the new recording and summary features, it could be included with a brief explanation in the PR description to clarify its relationship to the scope.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The pull request title "feat(meetings): add recording and summary features for past meetings" is specific, concise, and directly reflects the main changes in the changeset. It accurately summarizes the primary focus of the PR—adding recording display and AI-generated summary management for past meetings. The title follows conventional commit format and is clear enough for a reviewer to understand the scope without examining the code.
Linked Issues Check ✅ Passed The PR successfully addresses all requirements from both linked issues. LFXV2-667 objectives are met through recording modal display with share URL and copy functionality, recording button on past meeting cards, and backend endpoints querying the past_meeting_recording object type LFXV2-667. LFXV2-668 objectives are satisfied via the summary modal component with view, edit, and approve capabilities, accessible from the past meeting card, with proper ETag concurrency control for updates [LFXV2-668]. The implementation includes both frontend signal-based state management and backend service methods for fetching and updating recordings and summaries.
Description Check ✅ Passed The pull request description is comprehensive and directly related to the changeset. It clearly outlines the two main features (Recording Display and Summary Features), references the associated tickets (LFXV2-667 and LFXV2-668), describes technical implementation details (signal-based state management, ETag concurrency control, backend endpoints), and even includes a demo video. The description accurately reflects the code changes across both frontend and backend components.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch andrest50/display-meeting-recordings

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

Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Conventional Commits FTW!

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 adds recording and summary features for past meetings, implementing functionality to view Zoom recordings and AI-generated meeting summaries with editing and approval capabilities.

Key Changes:

  • Added backend and frontend support for fetching and displaying meeting recordings from past meetings
  • Implemented summary viewing, editing, and approval workflow with ETag-based concurrency control
  • Created modal components for recording and summary display with appropriate user interactions

Reviewed Changes

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

Show a summary per file
File Description
packages/shared/src/interfaces/meeting.interface.ts Added TypeScript interfaces for recording sessions, files, summaries, and update requests
apps/lfx-one/src/server/services/meeting.service.ts Implemented service methods to fetch recordings, summaries, and update summaries with ETag support
apps/lfx-one/src/server/routes/past-meetings.route.ts Added API routes for recording, summary retrieval, and summary updates
apps/lfx-one/src/server/controllers/past-meeting.controller.ts Created controller methods for handling recording and summary requests with validation
apps/lfx-one/src/app/shared/services/meeting.service.ts Added client-side service methods for fetching and updating recordings and summaries
apps/lfx-one/src/app/modules/project/meetings/components/summary-modal/summary-modal.component.ts Created modal component for viewing, editing, and approving meeting summaries
apps/lfx-one/src/app/modules/project/meetings/components/summary-modal/summary-modal.component.html Template for summary modal with edit mode and approval UI
apps/lfx-one/src/app/modules/project/meetings/components/recording-modal/recording-modal.component.ts Created modal component for displaying and sharing recording URLs
apps/lfx-one/src/app/modules/project/meetings/components/recording-modal/recording-modal.component.html Template for recording modal with URL display and copy functionality
apps/lfx-one/src/app/modules/project/meetings/components/meeting-card/meeting-card.component.ts Integrated recording and summary features into meeting cards with fetch and display logic
apps/lfx-one/src/app/modules/project/meetings/components/meeting-card/meeting-card.component.html Added UI buttons for viewing recordings and summaries on past meeting cards

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

@andrest50 andrest50 changed the title [LFXV2-667][LFXV2-668] feat(meetings): add recording and summary features for past meetings feat(meetings): add recording and summary features for past meetings Oct 21, 2025
@github-actions
Copy link

github-actions bot commented Oct 21, 2025

🚀 Deployment Status

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

Deployment Details:

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

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

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

🧹 Nitpick comments (10)
apps/lfx-one/src/app/modules/project/meetings/components/meeting-card/meeting-card.component.html (1)

52-73: Icon-only buttons: ensure accessible labels.

Buttons are icon-only; add ariaLabel (or equivalent on lfx-button) so screen readers announce the action. Keep current data-testid naming for local consistency.

- <lfx-button
+ <lfx-button
+   ariaLabel="View recording"
    icon="fa-light fa-video"
...
- <lfx-button
+ <lfx-button
+   ariaLabel="View summary"
    icon="fa-light fa-file-lines"
apps/lfx-one/src/app/modules/project/meetings/components/recording-modal/recording-modal.component.html (1)

11-23: Make share URL field focusable and properly labeled.

Use a readonly input tied to the label for keyboard/a11y and simpler selection.

-  <label class="block text-sm font-medium text-gray-700 mb-2">Recording Share URL</label>
-  <div class="flex items-center gap-2">
-    <div
-      class="flex-1 px-3 py-2 text-sm border border-gray-300 rounded-md bg-white text-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500 overflow-x-auto whitespace-nowrap cursor-text select-all"
-      data-testid="share-url-input"
-      (click)="selectUrl($event)">
-      {{ shareUrl }}
-    </div>
+  <label for="share-url" class="block text-sm font-medium text-gray-700 mb-2">Recording Share URL</label>
+  <div class="flex items-center gap-2">
+    <input
+      id="share-url"
+      type="text"
+      readonly
+      [value]="shareUrl"
+      class="flex-1 px-3 py-2 text-sm border border-gray-300 rounded-md bg-white text-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500"
+      data-testid="share-url-input"
+      (focus)="$event.target.select()"
+    />
apps/lfx-one/src/app/modules/project/meetings/components/recording-modal/recording-modal.component.ts (1)

28-35: Harden selection logic (type correctness).

event.target can be a Text node. Type it as Node and use selectAllChildren for robustness.

-  public selectUrl(event: MouseEvent): void {
-    const target = event.target as HTMLElement;
-    const selection = window.getSelection();
-    const range = document.createRange();
-    range.selectNodeContents(target);
-    selection?.removeAllRanges();
-    selection?.addRange(range);
-  }
+  public selectUrl(event: MouseEvent): void {
+    const target = (event.target as Node) ?? this.config?.data?.shareUrl;
+    const selection = window.getSelection();
+    if (!target || !selection) return;
+    const range = document.createRange();
+    range.selectNodeContents(target);
+    selection.removeAllRanges();
+    selection.addRange(range);
+  }
apps/lfx-one/src/app/modules/project/meetings/components/summary-modal/summary-modal.component.ts (3)

79-88: Handle ETag/precondition conflicts distinctly.

On 412/409, prompt the user to reload or merge changes instead of a generic error.

-        error: (error: unknown) => {
-          this.messageService.add({
-            severity: 'error',
-            summary: 'Error',
-            detail: 'Failed to update summary',
-          });
+        error: (error: any) => {
+          const status = error?.status;
+          const conflict = status === 412 || status === 409;
+          this.messageService.add({
+            severity: 'error',
+            summary: conflict ? 'Edit conflict' : 'Error',
+            detail: conflict
+              ? 'Summary was updated elsewhere. Please refresh and retry.'
+              : 'Failed to update summary',
+          });

98-107: Avoid console noise; rely on toast/logging service.

Remove console.error or gate behind env flag to keep logs clean in production.

-          console.error('Failed to update summary:', error);
+          // Optional: route to a central logging service if needed
...
-          console.error('Failed to approve summary:', error);
+          // Optional: route to a central logging service if needed

Also applies to: 109-116


34-41: Minor UX: prevent no-op saves and return richer close payload.

Disable Save when content unchanged/empty to avoid unnecessary calls; include updatedAt timestamp on close if available.

-  public readonly isSaving: WritableSignal<boolean> = signal(false);
+  public readonly isSaving: WritableSignal<boolean> = signal(false);
+  public readonly canSave: Signal<boolean> = computed(() =>
+    (this.editedContent().trim() !== '' && this.editedContent() !== this.originalContent()) && !this.isSaving()
+  );

And in template (summary-modal.component.html):

-<lfx-button label="Save" size="small" severity="primary" [loading]="isSaving()" data-testid="save-edit-button" (click)="saveEdit()"></lfx-button>
+<lfx-button label="Save" size="small" severity="primary" [disabled]="!canSave()" [loading]="isSaving()" data-testid="save-edit-button" (click)="saveEdit()"></lfx-button>

Also applies to: 120-127

packages/shared/src/interfaces/meeting.interface.ts (1)

651-676: Avoid leaking sensitive fields by default.

PastMeetingSummary includes password. If the client does not strictly need it, consider omitting this field from responses or marking optional to discourage accidental use on the frontend.

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

170-181: Set no-store caching for sensitive artifacts.

Recording share URLs and summaries may be sensitive/ephemeral. Add Cache-Control headers to prevent caching by intermediaries.

       // Send the recording data to the client
-      res.json(recording);
+      res.set('Cache-Control', 'private, no-store, must-revalidate');
+      res.json(recording);
       // Send the summary data to the client
-      res.json(summary);
+      res.set('Cache-Control', 'private, no-store, must-revalidate');
+      res.json(summary);

Also applies to: 244-246

apps/lfx-one/src/app/shared/services/meeting.service.ts (1)

325-335: Only coerce 404 to null; surface other errors to UI.

Swallowing all non-404 errors as null hides 401/403/5xx. Prefer rethrow for non-404 so callers can display proper error states.

   return this.http.get<PastMeetingRecording>(`/api/past-meetings/${pastMeetingUid}/recording`).pipe(
     catchError((error) => {
       if (error.status === 404) {
         return of(null);
       }
       console.error(`Failed to load recording for past meeting ${pastMeetingUid}:`, error);
-      return of(null);
+      return throwError(() => error);
     })
   );
   return this.http.get<PastMeetingSummary>(`/api/past-meetings/${pastMeetingUid}/summary`).pipe(
     catchError((error) => {
       if (error.status === 404) {
         return of(null);
       }
       console.error(`Failed to load summary for past meeting ${pastMeetingUid}:`, error);
-      return of(null);
+      return throwError(() => error);
     })
   );

Also applies to: 337-347

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

460-474: Constrain query for determinism and efficiency.

You take the first resource; add limit=1 and an order to ensure it’s the latest and reduce payload.

-      const params = {
+      const params = {
         type: 'past_meeting_recording',
         tags: `past_meeting_uid:${pastMeetingUid}`,
+        limit: 1,
+        order: 'updated_at.desc',
       };
-      const params = {
+      const params = {
         type: 'past_meeting_summary',
         tags: `past_meeting_uid:${pastMeetingUid}`,
+        limit: 1,
+        order: 'updated_at.desc',
       };

Also applies to: 517-529

📜 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 8e5f0c3 and 04e720a.

📒 Files selected for processing (11)
  • apps/lfx-one/src/app/modules/project/meetings/components/meeting-card/meeting-card.component.html (1 hunks)
  • apps/lfx-one/src/app/modules/project/meetings/components/meeting-card/meeting-card.component.ts (5 hunks)
  • apps/lfx-one/src/app/modules/project/meetings/components/recording-modal/recording-modal.component.html (1 hunks)
  • apps/lfx-one/src/app/modules/project/meetings/components/recording-modal/recording-modal.component.ts (1 hunks)
  • apps/lfx-one/src/app/modules/project/meetings/components/summary-modal/summary-modal.component.html (1 hunks)
  • apps/lfx-one/src/app/modules/project/meetings/components/summary-modal/summary-modal.component.ts (1 hunks)
  • apps/lfx-one/src/app/shared/services/meeting.service.ts (2 hunks)
  • apps/lfx-one/src/server/controllers/past-meeting.controller.ts (2 hunks)
  • apps/lfx-one/src/server/routes/past-meetings.route.ts (1 hunks)
  • apps/lfx-one/src/server/services/meeting.service.ts (2 hunks)
  • packages/shared/src/interfaces/meeting.interface.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
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/meeting-card/meeting-card.component.html
  • apps/lfx-one/src/app/modules/project/meetings/components/recording-modal/recording-modal.component.html
  • apps/lfx-one/src/app/modules/project/meetings/components/summary-modal/summary-modal.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/meeting-card/meeting-card.component.html
  • apps/lfx-one/src/server/routes/past-meetings.route.ts
  • apps/lfx-one/src/app/modules/project/meetings/components/recording-modal/recording-modal.component.html
  • apps/lfx-one/src/app/modules/project/meetings/components/recording-modal/recording-modal.component.ts
  • apps/lfx-one/src/app/shared/services/meeting.service.ts
  • apps/lfx-one/src/server/services/meeting.service.ts
  • apps/lfx-one/src/server/controllers/past-meeting.controller.ts
  • apps/lfx-one/src/app/modules/project/meetings/components/summary-modal/summary-modal.component.html
  • packages/shared/src/interfaces/meeting.interface.ts
  • apps/lfx-one/src/app/modules/project/meetings/components/summary-modal/summary-modal.component.ts
  • apps/lfx-one/src/app/modules/project/meetings/components/meeting-card/meeting-card.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/routes/past-meetings.route.ts
  • apps/lfx-one/src/app/modules/project/meetings/components/recording-modal/recording-modal.component.ts
  • apps/lfx-one/src/app/shared/services/meeting.service.ts
  • apps/lfx-one/src/server/services/meeting.service.ts
  • apps/lfx-one/src/server/controllers/past-meeting.controller.ts
  • packages/shared/src/interfaces/meeting.interface.ts
  • apps/lfx-one/src/app/modules/project/meetings/components/summary-modal/summary-modal.component.ts
  • apps/lfx-one/src/app/modules/project/meetings/components/meeting-card/meeting-card.component.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Do not nest ternary expressions

Files:

  • apps/lfx-one/src/server/routes/past-meetings.route.ts
  • apps/lfx-one/src/app/modules/project/meetings/components/recording-modal/recording-modal.component.ts
  • apps/lfx-one/src/app/shared/services/meeting.service.ts
  • apps/lfx-one/src/server/services/meeting.service.ts
  • apps/lfx-one/src/server/controllers/past-meeting.controller.ts
  • packages/shared/src/interfaces/meeting.interface.ts
  • apps/lfx-one/src/app/modules/project/meetings/components/summary-modal/summary-modal.component.ts
  • apps/lfx-one/src/app/modules/project/meetings/components/meeting-card/meeting-card.component.ts
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/meeting.interface.ts
🧬 Code graph analysis (6)
apps/lfx-one/src/app/modules/project/meetings/components/recording-modal/recording-modal.component.ts (1)
apps/lfx-one/src/app/modules/project/meetings/components/meeting-card/meeting-card.component.ts (1)
  • Component (46-772)
apps/lfx-one/src/app/shared/services/meeting.service.ts (1)
packages/shared/src/interfaces/meeting.interface.ts (3)
  • PastMeetingRecording (594-615)
  • PastMeetingSummary (651-676)
  • UpdatePastMeetingSummaryRequest (682-687)
apps/lfx-one/src/server/services/meeting.service.ts (2)
packages/shared/src/interfaces/meeting.interface.ts (3)
  • PastMeetingRecording (594-615)
  • PastMeetingSummary (651-676)
  • UpdatePastMeetingSummaryRequest (682-687)
packages/shared/src/interfaces/api.interface.ts (1)
  • QueryServiceResponse (58-61)
apps/lfx-one/src/server/controllers/past-meeting.controller.ts (1)
packages/shared/src/interfaces/meeting.interface.ts (3)
  • PastMeetingRecording (594-615)
  • PastMeetingSummary (651-676)
  • UpdatePastMeetingSummaryRequest (682-687)
apps/lfx-one/src/app/modules/project/meetings/components/summary-modal/summary-modal.component.ts (1)
apps/lfx-one/src/app/modules/project/meetings/components/meeting-card/meeting-card.component.ts (1)
  • Component (46-772)
apps/lfx-one/src/app/modules/project/meetings/components/meeting-card/meeting-card.component.ts (1)
packages/shared/src/interfaces/meeting.interface.ts (2)
  • PastMeetingRecording (594-615)
  • PastMeetingSummary (651-676)
🔇 Additional comments (8)
apps/lfx-one/src/server/routes/past-meetings.route.ts (1)

26-28: Endpoint parity confirmed—no action required.

The client's approvePastMeetingSummary calls updatePastMeetingSummary with { approved: true }, which correctly maps to the PUT /:uid/summary/:summaryUid route on the server. Approval is handled via the payload flag, not a separate endpoint—this is proper parity.

packages/shared/src/interfaces/meeting.interface.ts (2)

621-635: Confirm “edited_content can be empty” contract across layers.

Interfaces state edited_content can be empty. Ensure controllers/UI treat empty string as a valid, intentional override (not falsy). See controller and component comments.


594-616: LGTM on recording model additions.

Shapes and field docs look solid and map cleanly to Zoom concepts.

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

149-201: Endpoint behavior and logging look solid.

UID validation, 404 handling, and structured logs are consistent.

Also applies to: 203-255

apps/lfx-one/src/app/shared/services/meeting.service.ts (1)

349-356: LGTM on update/approve surface.

Client delegates ETag handling to the server; API is straightforward and matches controller routes.

apps/lfx-one/src/app/modules/project/meetings/components/meeting-card/meeting-card.component.ts (2)

314-339: Recording URL derivation looks good.

Largest session by total_size is selected; null-safety on sessions is handled.


267-313: Modal wiring and state sync look correct.

Guards, dialog data, and onClose updates are clean.

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

579-599: LGTM on ETag-based update flow.

Fetch-with-ETag + update-with-ETag pattern is consistent with existing services.

@github-actions
Copy link

github-actions bot commented Oct 21, 2025

✅ E2E Tests Passed

Browser: chromium
Status: passed

All E2E tests passed successfully.

Test Configuration

@andrest50 andrest50 force-pushed the andrest50/display-meeting-recordings branch from cf3909c to 04e720a Compare October 21, 2025 21:34
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: 0

🧹 Nitpick comments (1)
apps/lfx-one/src/server/controllers/past-meeting.controller.ts (1)

287-293: Validation fix correctly applied; consider adding runtime type guards.

The field presence check using the in operator is now correct and allows empty strings for edited_content. The issue raised in the previous review has been properly resolved.

However, consider adding runtime type validation for additional safety when called by external clients:

// Validate field presence
if (!('edited_content' in body) && !('approved' in body)) {
  res.status(400).json({
    error: 'Bad Request',
    message: 'Either edited_content or approved must be provided',
  });
  return;
}

// Validate types
if ('edited_content' in body && typeof body.edited_content !== 'string') {
  res.status(400).json({
    error: 'Bad Request',
    message: 'edited_content must be a string',
  });
  return;
}

if ('approved' in body && typeof body.approved !== 'boolean') {
  res.status(400).json({
    error: 'Bad Request',
    message: 'approved must be a boolean',
  });
  return;
}
📜 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 04e720a and cf3909c.

📒 Files selected for processing (1)
  • apps/lfx-one/src/server/controllers/past-meeting.controller.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{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/controllers/past-meeting.controller.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/controllers/past-meeting.controller.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Do not nest ternary expressions

Files:

  • apps/lfx-one/src/server/controllers/past-meeting.controller.ts
🧬 Code graph analysis (1)
apps/lfx-one/src/server/controllers/past-meeting.controller.ts (1)
packages/shared/src/interfaces/meeting.interface.ts (3)
  • PastMeetingRecording (594-615)
  • PastMeetingSummary (651-676)
  • UpdatePastMeetingSummaryRequest (682-687)
⏰ 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: build-and-push
🔇 Additional comments (3)
apps/lfx-one/src/server/controllers/past-meeting.controller.ts (3)

6-6: LGTM: Imports correctly include new shared interfaces.

The new imports for PastMeetingRecording, PastMeetingSummary, and UpdatePastMeetingSummaryRequest are properly typed and align with the shared interface definitions.


149-201: LGTM: Well-structured endpoint following established patterns.

The getPastMeetingRecording method correctly validates the UID, fetches the recording, returns 404 when not found, and logs appropriately. The implementation is consistent with other controller methods.


203-255: LGTM: Summary retrieval endpoint correctly implemented.

The getPastMeetingSummary method follows the same reliable pattern as getPastMeetingRecording, with appropriate logging of approval-related metadata.

Signed-off-by: Andres Tobon <andrest2455@gmail.com>
…king

Add id attribute to meeting cards using meeting UID to enable deep linking
to specific meetings via URL hash fragments.

Generated with [Claude Code](https://claude.ai/code)

Signed-off-by: Andres Tobon <andrest2455@gmail.com>
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: 0

🧹 Nitpick comments (1)
apps/lfx-one/src/app/modules/project/meetings/meeting-dashboard/meeting-dashboard.component.html (1)

76-76: Use data-testid instead of id for test targeting.

Per coding guidelines, Angular components should use data-testid attributes for reliable test targeting with the naming convention [section]-[component]-[element].

Apply this diff to follow the guideline:

-                    [attr.id]="'meeting-' + meeting.uid"
+                    [attr.data-testid]="'meeting-list-card-' + meeting.uid"

As per coding guidelines

📜 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 cf3909c and e3be355.

📒 Files selected for processing (3)
  • apps/lfx-one/src/app/modules/project/meetings/meeting-dashboard/meeting-dashboard.component.html (1 hunks)
  • apps/lfx-one/src/server/services/meeting.service.ts (2 hunks)
  • packages/shared/src/interfaces/meeting.interface.ts (1 hunks)
🧰 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/services/meeting.service.ts
  • packages/shared/src/interfaces/meeting.interface.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/services/meeting.service.ts
  • apps/lfx-one/src/app/modules/project/meetings/meeting-dashboard/meeting-dashboard.component.html
  • packages/shared/src/interfaces/meeting.interface.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Do not nest ternary expressions

Files:

  • apps/lfx-one/src/server/services/meeting.service.ts
  • packages/shared/src/interfaces/meeting.interface.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/modules/project/meetings/meeting-dashboard/meeting-dashboard.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/meeting.interface.ts
🧬 Code graph analysis (1)
apps/lfx-one/src/server/services/meeting.service.ts (2)
packages/shared/src/interfaces/meeting.interface.ts (3)
  • PastMeetingRecording (596-617)
  • PastMeetingSummary (653-678)
  • UpdatePastMeetingSummaryRequest (684-689)
packages/shared/src/interfaces/api.interface.ts (1)
  • QueryServiceResponse (58-61)
🔇 Additional comments (5)
packages/shared/src/interfaces/meeting.interface.ts (1)

550-688: LGTM!

The new interfaces are well-structured and properly documented. They correctly model past meeting recordings and summaries with appropriate type safety, consistent naming conventions, and thorough JSDoc comments. The placement in the shared package follows coding guidelines.

apps/lfx-one/src/server/services/meeting.service.ts (4)

11-12: LGTM!

The new interface imports are correctly sourced from the shared package and properly support the new recording and summary methods.

Also applies to: 14-14, 17-17


464-518: LGTM!

The method correctly implements recording retrieval with proper error handling and logging. The pattern follows existing methods in the service (like getPastMeetingParticipants), returning null for missing recordings and gracefully handling errors.


520-574: LGTM!

The method correctly implements summary retrieval with consistent error handling and logging patterns. The implementation mirrors getPastMeetingRecording, ensuring consistency across the service.


576-631: LGTM!

The method correctly implements ETag-based optimistic locking for summary updates, following the established pattern used in other update methods (e.g., updateMeetingRegistrant). Error handling, logging, and sanitization are properly implemented.

Comment on lines 140 to 152
// Fetch recording for past meetings
effect(() => {
if (this.pastMeeting() && this.meeting().uid) {
this.fetchRecording();
}
});

// Fetch summary for past meetings
effect(() => {
if (this.pastMeeting() && this.meeting().uid) {
this.fetchSummary();
}
});
Copy link
Contributor

Choose a reason for hiding this comment

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

To get the summary and recording, we should be using "toSignal" so that it's automatically loaded when the element is displayed.

The current implementation reloads the recording and summary anytime there is a signal that is updated which could be a problem.

});
}

private fetchRecording(): void {
Copy link
Contributor

Choose a reason for hiding this comment

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

We generally do not subscribe for get calls and instead use toSignal to load it async

return largestSession.share_url || null;
}

private fetchSummary(): void {
Copy link
Contributor

Choose a reason for hiding this comment

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

We generally do not subscribe for get calls and instead use toSignal to load it async

// Injected services
private readonly clipboard = inject(Clipboard);
private readonly messageService = inject(MessageService);
private readonly ref = inject(DynamicDialogRef);
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you rename this to dialogRef just so it's clear what this is when used. It's a little vague for a name.

private readonly clipboard = inject(Clipboard);
private readonly messageService = inject(MessageService);
private readonly ref = inject(DynamicDialogRef);
private readonly config = inject(DynamicDialogConfig);
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here

}
}

public openRecording(): void {
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove

@if (isEditMode()) {
<div class="bg-gray-50 rounded-lg p-4 border border-gray-200" data-testid="summary-edit-container">
<label class="block text-sm font-medium text-gray-700 mb-2">Edit Summary</label>
<textarea
Copy link
Contributor

Choose a reason for hiding this comment

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

We should be using the shared lfx-textarea component and reactive forms here.

Comment on lines 22 to 23
private readonly ref = inject(DynamicDialogRef);
private readonly config = inject(DynamicDialogConfig);
Copy link
Contributor

Choose a reason for hiding this comment

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

Rename to be more precise: dialogRef, dialogConfig

Comment on lines 327 to 333
catchError((error) => {
if (error.status === 404) {
return of(null);
}
console.error(`Failed to load recording for past meeting ${pastMeetingUid}:`, error);
return of(null);
})
Copy link
Contributor

Choose a reason for hiding this comment

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

Move these to the callers instead of the service. Have the callers handle errors

Comment on lines 339 to 344
catchError((error) => {
if (error.status === 404) {
return of(null);
}
console.error(`Failed to load summary for past meeting ${pastMeetingUid}:`, error);
return of(null);
Copy link
Contributor

Choose a reason for hiding this comment

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

Move this logic to the caller for pipes

…ary features

- Use toSignal instead of effect for recording/summary data loading
- Move recording/summary initialization to ngOnInit lifecycle hook
- Replace click handler with native href for recording links
- Convert summary modal to use reactive forms with lfx-textarea
- Move error handling from service to component callers

Generated with [Claude Code](https://claude.ai/code)

Signed-off-by: Andres Tobon <andrest2455@gmail.com>
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

🧹 Nitpick comments (1)
apps/lfx-one/src/app/shared/services/meeting.service.ts (1)

325-339: Add error handling for consistency with existing service methods.

These new methods lack error handling, which is inconsistent with similar methods in this service (e.g., getMeetings, getPastMeetings, getMeetingAttachments all include catchError). While a past review suggested moving error handling to callers, the established pattern in this service is to handle errors at the service level.

Consider adding error handling to match the existing pattern:

 public getPastMeetingRecording(pastMeetingUid: string): Observable<PastMeetingRecording> {
-  return this.http.get<PastMeetingRecording>(`/api/past-meetings/${pastMeetingUid}/recording`);
+  return this.http.get<PastMeetingRecording>(`/api/past-meetings/${pastMeetingUid}/recording`).pipe(
+    catchError((error) => {
+      console.error(`Failed to load recording for past meeting ${pastMeetingUid}:`, error);
+      return throwError(() => error);
+    })
+  );
 }

 public getPastMeetingSummary(pastMeetingUid: string): Observable<PastMeetingSummary> {
-  return this.http.get<PastMeetingSummary>(`/api/past-meetings/${pastMeetingUid}/summary`);
+  return this.http.get<PastMeetingSummary>(`/api/past-meetings/${pastMeetingUid}/summary`).pipe(
+    catchError((error) => {
+      console.error(`Failed to load summary for past meeting ${pastMeetingUid}:`, error);
+      return throwError(() => error);
+    })
+  );
 }

 public updatePastMeetingSummary(pastMeetingUid: string, summaryUid: string, updateData: UpdatePastMeetingSummaryRequest): Observable<PastMeetingSummary> {
-  return this.http.put<PastMeetingSummary>(`/api/past-meetings/${pastMeetingUid}/summary/${summaryUid}`, updateData);
+  return this.http.put<PastMeetingSummary>(`/api/past-meetings/${pastMeetingUid}/summary/${summaryUid}`, updateData).pipe(
+    take(1),
+    catchError((error) => {
+      console.error(`Failed to update summary ${summaryUid} for past meeting ${pastMeetingUid}:`, error);
+      return throwError(() => error);
+    })
+  );
 }
📜 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 e3be355 and 4bea0ca.

📒 Files selected for processing (6)
  • apps/lfx-one/src/app/modules/project/meetings/components/meeting-card/meeting-card.component.ts (6 hunks)
  • apps/lfx-one/src/app/modules/project/meetings/components/recording-modal/recording-modal.component.html (1 hunks)
  • apps/lfx-one/src/app/modules/project/meetings/components/recording-modal/recording-modal.component.ts (1 hunks)
  • apps/lfx-one/src/app/modules/project/meetings/components/summary-modal/summary-modal.component.html (1 hunks)
  • apps/lfx-one/src/app/modules/project/meetings/components/summary-modal/summary-modal.component.ts (1 hunks)
  • apps/lfx-one/src/app/shared/services/meeting.service.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • apps/lfx-one/src/app/modules/project/meetings/components/recording-modal/recording-modal.component.ts
  • apps/lfx-one/src/app/modules/project/meetings/components/summary-modal/summary-modal.component.html
  • apps/lfx-one/src/app/modules/project/meetings/components/summary-modal/summary-modal.component.ts
  • apps/lfx-one/src/app/modules/project/meetings/components/recording-modal/recording-modal.component.html
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{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/app/shared/services/meeting.service.ts
  • apps/lfx-one/src/app/modules/project/meetings/components/meeting-card/meeting-card.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/app/shared/services/meeting.service.ts
  • apps/lfx-one/src/app/modules/project/meetings/components/meeting-card/meeting-card.component.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Do not nest ternary expressions

Files:

  • apps/lfx-one/src/app/shared/services/meeting.service.ts
  • apps/lfx-one/src/app/modules/project/meetings/components/meeting-card/meeting-card.component.ts
🧠 Learnings (1)
📚 Learning: 2025-10-21T21:19:13.599Z
Learnt from: andrest50
PR: linuxfoundation/lfx-v2-ui#125
File: apps/lfx-one/src/app/modules/project/meetings/components/meeting-card/meeting-card.component.ts:345-350
Timestamp: 2025-10-21T21:19:13.599Z
Learning: In the Angular meeting card component (apps/lfx-one/src/app/modules/project/meetings/components/meeting-card/meeting-card.component.ts), when selecting between `summary.summary_data.edited_content` and `summary.summary_data.content`, the logical OR operator (`||`) is intentionally used instead of nullish coalescing (`??`) because empty string edited_content should fall back to the original content rather than being displayed as empty.

Applied to files:

  • apps/lfx-one/src/app/modules/project/meetings/components/meeting-card/meeting-card.component.ts
🧬 Code graph analysis (2)
apps/lfx-one/src/app/shared/services/meeting.service.ts (1)
packages/shared/src/interfaces/meeting.interface.ts (3)
  • PastMeetingRecording (596-617)
  • PastMeetingSummary (653-678)
  • UpdatePastMeetingSummaryRequest (684-689)
apps/lfx-one/src/app/modules/project/meetings/components/meeting-card/meeting-card.component.ts (1)
packages/shared/src/interfaces/meeting.interface.ts (2)
  • PastMeetingRecording (596-617)
  • PastMeetingSummary (653-678)
⏰ 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: build-and-push

@asithade asithade merged commit adaaa66 into main Oct 27, 2025
5 checks passed
@asithade asithade deleted the andrest50/display-meeting-recordings branch October 27, 2025 18:05
@github-actions
Copy link

🧹 Deployment Removed

The deployment for PR #125 has been removed.

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