-
Notifications
You must be signed in to change notification settings - Fork 13.1k
refactor: standardize ForwardChatModal using GenericModal
#38424
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Conversation
|
Looks like this PR is not ready to merge, because of the following issues:
Please fix the issues and try again If you have any trouble, please check the PR guidelines |
|
WalkthroughRefactored 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
Sequence DiagramsequenceDiagram
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
|
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 My PR covers:
Since your implementation for Would you mind taking a quick look at #38384 when you have a moment? Your feedback would be really valuable! 🙏 Thanks! |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ 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
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
20f752d to
2fadb5b
Compare
e8c2d60 to
971618d
Compare
There was a problem hiding this 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
There was a problem hiding this 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.
| 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(); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
Proposed changes (including videos or screenshots)
Issue(s)
Steps to test or reproduce
Further comments
CORE-1545
Summary by CodeRabbit
Bug Fixes
Tests
✏️ Tip: You can customize this high-level summary in your review settings.