Skip to content

Conversation

@dougfabris
Copy link
Member

@dougfabris dougfabris commented Jan 29, 2026

Proposed changes (including videos or screenshots)

Issue(s)

Steps to test or reproduce

Further comments

CORE-1545

Summary by CodeRabbit

  • Bug Fixes

    • Improved forward chat modal to handle transfers internally via API, providing better error handling for scenarios where target departments have no available agents.
  • Tests

    • Added comprehensive test coverage for forward chat modal including rendering, snapshots, and accessibility checks.
    • Added end-to-end tests for chat transfer failure scenarios.

✏️ Tip: You can customize this high-level summary in your review settings.

@dionisio-bot
Copy link
Contributor

dionisio-bot bot commented Jan 29, 2026

Looks like this PR is not ready to merge, because of the following issues:

  • This PR is missing the 'stat: QA assured' label
  • This PR is targeting the wrong base branch. It should target 8.2.0, but it targets 8.1.0

Please fix the issues and try again

If you have any trouble, please check the PR guidelines

@changeset-bot
Copy link

changeset-bot bot commented Jan 29, 2026

⚠️ No Changeset found

Latest commit: cdb0e6f

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@dougfabris dougfabris added this to the 8.2.0 milestone Jan 29, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 29, 2026

Walkthrough

Refactored ForwardChatModal to handle chat forward flow internally via API request instead of a callback prop, replacing Modal with GenericModal and switching form control to react-hook-form. Updated QuickActions hook, added Storybook stories and Jest tests, and introduced E2E test coverage for error scenarios.

Changes

Cohort / File(s) Summary
ForwardChatModal Component Refactor
apps/meteor/client/views/omnichannel/modals/ForwardChatModal.tsx
Removed onForward prop; now handles forward flow internally with POST /v1/livechat/room.forward API call. Replaced Modal with GenericModal, switched to Controller-based react-hook-form, added router navigation and toast error handling. Props changed to { room, onCancel } only.
ForwardChatModal Testing & Documentation
apps/meteor/client/views/omnichannel/modals/ForwardChatModal.spec.tsx, apps/meteor/client/views/omnichannel/modals/ForwardChatModal.stories.tsx
Added Storybook story file with mocked omnichannel room and default story; added Jest spec with render, snapshot, and accessibility (jest-axe) test cases using composeStories.
QuickActions Hook
apps/meteor/client/views/room/Header/Omnichannel/QuickActions/hooks/useQuickActions.tsx
Removed forward transfer functionality and handleForwardChat callback; ForwardChatModal now invoked without onForward handler.
Testing Infrastructure
apps/meteor/tests/e2e/omnichannel/omnichannel-chat-transfers.spec.ts, apps/meteor/tests/e2e/page-objects/omnichannel/omnichannel-contact-center/omnichannel-contact-center-chats.ts, apps/meteor/tests/mocks/data.ts
Added E2E test for failed department transfer error scenario; added createFakeOmnichannelRoom factory function for mock data; updated page object to include search before row interaction.

Sequence Diagram

sequenceDiagram
    actor User
    participant Modal as ForwardChatModal
    participant Form as react-hook-form
    participant API as POST /room.forward
    participant Navigation as Router
    participant Notification as Toast

    User->>Modal: Select department/user & submit
    Modal->>Form: Collect form values (departmentId/userId)
    Form->>Modal: Validate & assemble payload
    Modal->>API: Send forward request
    
    alt Success
        API-->>Modal: 200 OK
        Modal->>Navigation: Navigate away from room
        Modal->>Notification: Show success message
        Note over Modal: Close modal
    else Failure
        API-->>Modal: Error response
        Modal->>Notification: Display error toast
        Note over Modal: Keep modal open, preserve state
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • #38029: Modifies ForwardChatModal.tsx to add accessibility IDs for form inputs—same file with related component updates.
  • #38334: Standardizes modal usage across codebase by switching to GenericModal, affecting ForwardChatModal and other modal consumers.

Suggested labels

stat: ready to merge, stat: QA assured

Suggested reviewers

  • aleksandernsilva
  • ricardogarim

Poem

🐰 A modal so forward, it takes control today,
No callbacks to juggle, just API on display,
Toast errors bloom bright when transfers go wrong,
Forms dance with Controllers, their fields sing along!
The forward flow flows now, so elegant and true. ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: refactoring ForwardChatModal to use GenericModal, which is the primary objective of the changeset.
Linked Issues check ✅ Passed The PR successfully addresses CORE-1545 by refactoring ForwardChatModal to use GenericModal, which moves the modal rendering layer and ensures error messages appear above the modal rather than behind it.
Out of Scope Changes check ✅ Passed All changes are directly related to the ForwardChatModal refactoring: test specs, Storybook stories, modal component update, hook modifications, E2E tests, page objects, and mock data factories are all in-scope.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 refactor/forward-modal

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@NAME-ASHWANIYADAV
Copy link
Contributor

Hi @dougfabris

Great work on this PR! I noticed there's some overlap with my PR #38384 where I'm also standardizing Omnichannel modals to use GenericModal.

My PR covers:

  • CloseChatModal
  • ReturnChatQueueModal
  • EnterpriseDepartmentsModal
  • ForwardChatModal

Since your implementation for ForwardChatModal looks more comprehensive (with tests and loading state feature), I'm happy to remove the ForwardChatModal changes from my PR to avoid duplication.

Would you mind taking a quick look at #38384 when you have a moment? Your feedback would be really valuable! 🙏

Thanks!

@codecov
Copy link

codecov bot commented Jan 29, 2026

Codecov Report

❌ Patch coverage is 69.27374% with 55 lines in your changes missing coverage. Please review.
✅ Project coverage is 70.45%. Comparing base (a75e1f1) to head (cdb0e6f).
⚠️ Report is 1 commits behind head on develop.

Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##           develop   #38424      +/-   ##
===========================================
+ Coverage    70.41%   70.45%   +0.03%     
===========================================
  Files         3161     3162       +1     
  Lines       110151   110339     +188     
  Branches     19861    19853       -8     
===========================================
+ Hits         77561    77735     +174     
- Misses       30559    30575      +16     
+ Partials      2031     2029       -2     
Flag Coverage Δ
e2e 60.35% <93.10%> (+0.03%) ⬆️
e2e-api 48.83% <ø> (+1.06%) ⬆️
unit 71.46% <60.67%> (+0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions
Copy link
Contributor

github-actions bot commented Jan 29, 2026

📦 Docker Image Size Report

➡️ Changes

Service Current Baseline Change Percent
sum of all images 0B 0B 0B
account-service 0B 0B 0B
authorization-service 0B 0B 0B
ddp-streamer-service 0B 0B 0B
omnichannel-transcript-service 0B 0B 0B
presence-service 0B 0B 0B
queue-worker-service 0B 0B 0B
rocketchat 0B 0B 0B

📊 Historical Trend

---
config:
  theme: "dark"
  xyChart:
    width: 900
    height: 400
---
xychart
  title "Image Size Evolution by Service (Last 30 Days + This PR)"
  x-axis ["11/18 22:53", "11/19 23:02", "11/21 16:49", "11/24 17:34", "11/27 22:32", "11/28 19:05", "12/01 23:01", "12/02 21:57", "12/03 21:00", "12/04 18:17", "12/05 21:56", "12/08 20:15", "12/09 22:17", "12/10 23:26", "12/11 21:56", "12/12 22:45", "12/13 01:34", "12/15 22:31", "12/16 22:18", "12/17 21:04", "12/18 23:12", "12/19 23:27", "12/20 21:03", "12/22 18:54", "12/23 16:16", "12/24 19:38", "12/25 17:51", "12/26 13:18", "12/29 19:01", "12/30 20:52", "01/30 20:31 (PR)"]
  y-axis "Size (GB)" 0 --> 0.5
  line "account-service" [0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.00]
  line "authorization-service" [0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.00]
  line "ddp-streamer-service" [0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.00]
  line "omnichannel-transcript-service" [0.14, 0.14, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.00]
  line "presence-service" [0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.00]
  line "queue-worker-service" [0.14, 0.14, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.00]
  line "rocketchat" [0.35, 0.35, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.00]
Loading

Statistics (last 30 days):

  • 📊 Average: 1.5GiB
  • ⬇️ Minimum: 1.4GiB
  • ⬆️ Maximum: 1.6GiB
  • 🎯 Current PR: 0B
ℹ️ About this report

This report compares Docker image sizes from this build against the develop baseline.

  • Tag: pr-38424
  • Baseline: develop
  • Timestamp: 2026-01-30 20:31:33 UTC
  • Historical data points: 30

Updated: Fri, 30 Jan 2026 20:31:34 GMT

@dougfabris dougfabris marked this pull request as ready for review January 30, 2026 19:48
@dougfabris dougfabris requested a review from a team as a code owner January 30, 2026 19:48
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

No issues found across 9 files

Copy link
Contributor

@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

🤖 Fix all issues with AI agents
In `@apps/meteor/client/views/omnichannel/modals/ForwardChatModal.tsx`:
- Around line 53-95: The current handleForwardChat performs getUserData before
the try/catch, so failures escape the error handler and onCancel; move the
username lookup into the try block (or wrap the entire function body in
try/catch) so any error from getUserData is caught, call dispatchToastMessage({
type: 'error', message: ... }) with the error and always execute onCancel in
finally, preserving the current payload assembly logic (room._id, departmentId,
userId, comment, clientAction) and keeping forwardChat, router.navigate,
LegacyRoomManager.close and onCancel behavior intact.
🧹 Nitpick comments (2)
apps/meteor/tests/e2e/page-objects/omnichannel/omnichannel-contact-center/omnichannel-contact-center-chats.ts (1)

150-155: Wait for the filtered row before clicking to reduce flakiness.

fill() can debounce table filtering; clicking immediately risks a stale row. Waiting for the row makes the page-object more stable.

Suggested update
 async openChat(name: string) {
 	await this.inputSearch.fill(name);
-	await this.table.findRowByName(name).click();
+	const row = this.table.findRowByName(name);
+	await row.waitFor();
+	await row.click();
 	await this.conversation.openChat();
 	await this.page.locator('#main-content').waitFor();
 }
apps/meteor/tests/e2e/omnichannel/omnichannel-chat-transfers.spec.ts (1)

129-153: Ensure the temporary department is always deleted (even on failure).

If a step fails, emptyDepartment.delete() won’t run, leaving test data behind.

Suggested update
 test(`OC - Chat transfers [Monitor role] - Transfer to department with no online agents should fail`, async ({ api }) => {
 	const [roomA] = conversations.map(({ data }) => data.room);
 	const [, agentB] = sessions;

 	const emptyDepartment = await createDepartment(api);
-
-	await test.step('expect to open forward chat modal', async () => {
-		await poOmnichannel.chats.openChat(roomA.fname);
-		await poOmnichannel.quickActionsRoomToolbar.forwardChat();
-	});
-
-	await test.step('expect transfer to department with no online agents to fail', async () => {
-		await poOmnichannel.content.forwardChatModal.selectDepartment(emptyDepartment.data.name);
-		await poOmnichannel.content.forwardChatModal.inputComment.type('transfer_attempt');
-		await expect(poOmnichannel.content.forwardChatModal.btnForward).toBeEnabled();
-		await poOmnichannel.content.forwardChatModal.btnForward.click();
-		await poOmnichannel.toastMessage.waitForDisplay({ type: 'error' });
-	});
-
-	await test.step('expect conversation to remain with original agent', async () => {
-		await expect(agentB.poHomeOmnichannel.sidebar.getSidebarItemByName(roomA.fname)).not.toBeVisible();
-	});
-
-	await emptyDepartment.delete();
+	try {
+		await test.step('expect to open forward chat modal', async () => {
+			await poOmnichannel.chats.openChat(roomA.fname);
+			await poOmnichannel.quickActionsRoomToolbar.forwardChat();
+		});
+
+		await test.step('expect transfer to department with no online agents to fail', async () => {
+			await poOmnichannel.content.forwardChatModal.selectDepartment(emptyDepartment.data.name);
+			await poOmnichannel.content.forwardChatModal.inputComment.type('transfer_attempt');
+			await expect(poOmnichannel.content.forwardChatModal.btnForward).toBeEnabled();
+			await poOmnichannel.content.forwardChatModal.btnForward.click();
+			await poOmnichannel.toastMessage.waitForDisplay({ type: 'error' });
+		});
+
+		await test.step('expect conversation to remain with original agent', async () => {
+			await expect(agentB.poHomeOmnichannel.sidebar.getSidebarItemByName(roomA.fname)).not.toBeVisible();
+		});
+	} finally {
+		await emptyDepartment.delete();
+	}
 });

As per coding guidelines: Ensure clean state for each test execution in Playwright tests.

Comment on lines 53 to 95
const handleForwardChat = useCallback(
async ({ department: departmentId, username, comment }: ForwardChatModalFormData) => {
let uid;
let userId;

if (username) {
const { user } = await getUserData({ username });
uid = user?._id;
userId = user?._id;
}

await onForward(departmentId, uid, comment);
if (departmentId && userId) {
return;
}

const payload: {
roomId: string;
departmentId?: string;
userId?: string;
comment?: string;
clientAction: boolean;
} = {
roomId: room._id,
comment,
clientAction: true,
};

if (departmentId) {
payload.departmentId = departmentId;
}

if (userId) {
payload.userId = userId;
}

try {
await forwardChat(payload);
dispatchToastMessage({ type: 'success', message: t('Transferred') });
router.navigate('/home');
LegacyRoomManager.close(room.t + room._id);
} catch (error) {
dispatchToastMessage({ type: 'error', message: error });
} finally {
onCancel();
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Handle user-lookup failures inside the error boundary.

getUserData runs before the try/catch, so lookup failures can bypass the toast and onCancel, leaving the modal open with no clear feedback. Wrap the entire flow so any failure is surfaced consistently.

🔧 Suggested fix
 const handleForwardChat = useCallback(
 	async ({ department: departmentId, username, comment }: ForwardChatModalFormData) => {
-		let userId;
-
-		if (username) {
-			const { user } = await getUserData({ username });
-			userId = user?._id;
-		}
-
-		if (departmentId && userId) {
-			return;
-		}
-
-		const payload: {
-			roomId: string;
-			departmentId?: string;
-			userId?: string;
-			comment?: string;
-			clientAction: boolean;
-		} = {
-			roomId: room._id,
-			comment,
-			clientAction: true,
-		};
-
-		if (departmentId) {
-			payload.departmentId = departmentId;
-		}
-
-		if (userId) {
-			payload.userId = userId;
-		}
-
-		try {
-			await forwardChat(payload);
-			dispatchToastMessage({ type: 'success', message: t('Transferred') });
-			router.navigate('/home');
-			LegacyRoomManager.close(room.t + room._id);
-		} catch (error) {
-			dispatchToastMessage({ type: 'error', message: error });
-		} finally {
-			onCancel();
-		}
+		try {
+			let userId;
+
+			if (username) {
+				const { user } = await getUserData({ username });
+				userId = user?._id;
+			}
+
+			if (departmentId && userId) {
+				return;
+			}
+
+			const payload: {
+				roomId: string;
+				departmentId?: string;
+				userId?: string;
+				comment?: string;
+				clientAction: boolean;
+			} = {
+				roomId: room._id,
+				comment,
+				clientAction: true,
+			};
+
+			if (departmentId) {
+				payload.departmentId = departmentId;
+			}
+
+			if (userId) {
+				payload.userId = userId;
+			}
+
+			await forwardChat(payload);
+			dispatchToastMessage({ type: 'success', message: t('Transferred') });
+			router.navigate('/home');
+			LegacyRoomManager.close(room.t + room._id);
+		} catch (error) {
+			dispatchToastMessage({ type: 'error', message: error });
+		} finally {
+			onCancel();
+		}
 	},
 	[room._id, room.t, getUserData, forwardChat, dispatchToastMessage, t, router, onCancel],
 );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleForwardChat = useCallback(
async ({ department: departmentId, username, comment }: ForwardChatModalFormData) => {
let uid;
let userId;
if (username) {
const { user } = await getUserData({ username });
uid = user?._id;
userId = user?._id;
}
await onForward(departmentId, uid, comment);
if (departmentId && userId) {
return;
}
const payload: {
roomId: string;
departmentId?: string;
userId?: string;
comment?: string;
clientAction: boolean;
} = {
roomId: room._id,
comment,
clientAction: true,
};
if (departmentId) {
payload.departmentId = departmentId;
}
if (userId) {
payload.userId = userId;
}
try {
await forwardChat(payload);
dispatchToastMessage({ type: 'success', message: t('Transferred') });
router.navigate('/home');
LegacyRoomManager.close(room.t + room._id);
} catch (error) {
dispatchToastMessage({ type: 'error', message: error });
} finally {
onCancel();
}
const handleForwardChat = useCallback(
async ({ department: departmentId, username, comment }: ForwardChatModalFormData) => {
try {
let userId;
if (username) {
const { user } = await getUserData({ username });
userId = user?._id;
}
if (departmentId && userId) {
return;
}
const payload: {
roomId: string;
departmentId?: string;
userId?: string;
comment?: string;
clientAction: boolean;
} = {
roomId: room._id,
comment,
clientAction: true,
};
if (departmentId) {
payload.departmentId = departmentId;
}
if (userId) {
payload.userId = userId;
}
await forwardChat(payload);
dispatchToastMessage({ type: 'success', message: t('Transferred') });
router.navigate('/home');
LegacyRoomManager.close(room.t + room._id);
} catch (error) {
dispatchToastMessage({ type: 'error', message: error });
} finally {
onCancel();
}
},
[room._id, room.t, getUserData, forwardChat, dispatchToastMessage, t, router, onCancel],
);
🤖 Prompt for AI Agents
In `@apps/meteor/client/views/omnichannel/modals/ForwardChatModal.tsx` around
lines 53 - 95, The current handleForwardChat performs getUserData before the
try/catch, so failures escape the error handler and onCancel; move the username
lookup into the try block (or wrap the entire function body in try/catch) so any
error from getUserData is caught, call dispatchToastMessage({ type: 'error',
message: ... }) with the error and always execute onCancel in finally,
preserving the current payload assembly logic (room._id, departmentId, userId,
comment, clientAction) and keeping forwardChat, router.navigate,
LegacyRoomManager.close and onCancel behavior intact.

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.

4 participants