Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/contact-center/store/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"test:styles": "eslint"
},
"dependencies": {
"@webex/contact-center": "3.10.0-next.8",
"@webex/contact-center": "3.10.0-next.17",
"mobx": "6.13.5",
"typescript": "5.6.3"
},
Expand Down Expand Up @@ -59,4 +59,4 @@
"webpack-cli": "5.1.4",
"webpack-merge": "6.0.1"
}
}
}
1 change: 1 addition & 0 deletions packages/contact-center/store/src/store.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ enum TASK_EVENTS {
TASK_PARTICIPANT_LEFT_FAILED = 'task:participantLeftFailed',
TASK_MERGED = 'task:merged',
TASK_POST_CALL_ACTIVITY = 'task:postCallActivity',
TASK_OUTDIAL_FAILED = 'task:outdialFailed',
} // TODO: remove this once cc sdk exports this enum

// Events that are received on the contact center SDK
Expand Down
15 changes: 15 additions & 0 deletions packages/contact-center/store/src/storeEventsWrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class StoreWrapper implements IStoreWrapper {
store: IStore;
onIncomingTask: ({task}: {task: ITask}) => void;
onTaskRejected?: (task: ITask, reason: string) => void;
onOutdialFailed?: (reason: string) => void;
onTaskAssigned?: (task: ITask) => void;
onTaskSelected?: (task: ITask, isClicked: boolean) => void;
onErrorCallback?: (widgetName: string, error: Error) => void;
Expand Down Expand Up @@ -313,6 +314,10 @@ class StoreWrapper implements IStoreWrapper {
this.onTaskRejected = callback;
};

setOutdialFailed = (callback: ((reason: string) => void) | undefined): void => {
this.onOutdialFailed = callback;
};

setTaskAssigned = (callback: ((task: ITask) => void) | undefined): void => {
this.onTaskAssigned = callback;
};
Expand Down Expand Up @@ -381,6 +386,7 @@ class StoreWrapper implements IStoreWrapper {
taskToRemove.off(TASK_EVENTS.TASK_ASSIGNED, this.handleTaskAssigned);
taskToRemove.off(TASK_EVENTS.TASK_END, this.handleTaskEnd);
taskToRemove.off(TASK_EVENTS.TASK_REJECT, (reason) => this.handleTaskReject(taskToRemove, reason));
taskToRemove.off(TASK_EVENTS.TASK_OUTDIAL_FAILED, (reason) => this.handleOutdialFailed(reason));
taskToRemove.off(TASK_EVENTS.AGENT_WRAPPEDUP, this.refreshTaskList);
taskToRemove.off(TASK_EVENTS.TASK_CONSULTING, this.handleConsulting);
taskToRemove.off(TASK_EVENTS.TASK_OFFER_CONSULT, this.handleConsultOffer);
Expand Down Expand Up @@ -527,6 +533,9 @@ class StoreWrapper implements IStoreWrapper {
// When we receive TASK_REJECT that means the task was not accepted by the agent and we wont need wrap up
task.on(TASK_EVENTS.TASK_REJECT, (reason) => this.handleTaskReject(task, reason));

// When we receive TASK_OUTDIAL_FAILED the outdial call failed
task.on(TASK_EVENTS.TASK_OUTDIAL_FAILED, (reason) => this.handleOutdialFailed(reason));

task.on(TASK_EVENTS.AGENT_WRAPPEDUP, this.refreshTaskList);

task.on(TASK_EVENTS.TASK_CONSULTING, this.handleConsulting);
Expand Down Expand Up @@ -646,6 +655,12 @@ class StoreWrapper implements IStoreWrapper {
this.refreshTaskList();
};

handleOutdialFailed = (reason: string) => {
if (this.onOutdialFailed) {
this.onOutdialFailed(reason || 'No reason provided');
}
};

getBuddyAgents = async (
mediaType: string = this.currentTask.data.interaction.mediaType
): Promise<Array<BuddyDetails>> => {
Expand Down
31 changes: 31 additions & 0 deletions packages/contact-center/store/tests/storeEventsWrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1176,6 +1176,7 @@ describe('storeEventsWrapper', () => {
expect(mockTask.on).toHaveBeenCalledWith(TASK_EVENTS.TASK_ASSIGNED, expect.any(Function));
expect(mockTask.on).toHaveBeenCalledWith(TASK_EVENTS.AGENT_WRAPPEDUP, expect.any(Function));
expect(mockTask.on).toHaveBeenCalledWith(TASK_EVENTS.TASK_REJECT, expect.any(Function));
expect(mockTask.on).toHaveBeenCalledWith(TASK_EVENTS.TASK_OUTDIAL_FAILED, expect.any(Function));
expect(mockTask.on).toHaveBeenCalledWith(TASK_EVENTS.TASK_HOLD, storeWrapper.refreshTaskList);
expect(mockTask.on).toHaveBeenCalledWith(TASK_EVENTS.TASK_RESUME, storeWrapper.refreshTaskList);

Expand Down Expand Up @@ -1227,6 +1228,7 @@ describe('storeEventsWrapper', () => {
expect(mockMergedTask.on).toHaveBeenCalledWith(TASK_EVENTS.TASK_ASSIGNED, expect.any(Function));
expect(mockMergedTask.on).toHaveBeenCalledWith(TASK_EVENTS.AGENT_WRAPPEDUP, expect.any(Function));
expect(mockMergedTask.on).toHaveBeenCalledWith(TASK_EVENTS.TASK_REJECT, expect.any(Function));
expect(mockMergedTask.on).toHaveBeenCalledWith(TASK_EVENTS.TASK_OUTDIAL_FAILED, expect.any(Function));
});

describe('customStates on hydration', () => {
Expand Down Expand Up @@ -1478,6 +1480,35 @@ describe('storeEventsWrapper', () => {
expect(removeSpy).toHaveBeenCalledWith(rejectTask);
});

it('should handle outdial failed event and call onOutdialFailed with the provided reason', () => {
const outdialTask: ITask = {
data: {interactionId: 'outdialTest', interaction: {state: 'connected'}},
on: jest.fn(),
off: jest.fn(),
} as unknown as ITask;

const outdialTaskOnSpy = jest.spyOn(outdialTask, 'on');
const onOutdialFailedMock = jest.fn();

storeWrapper.setOutdialFailed(onOutdialFailedMock);
storeWrapper['store'].cc.taskManager.getAllTasks = jest
.fn()
.mockReturnValue({[outdialTask.data.interactionId]: outdialTask});
storeWrapper.refreshTaskList();
storeWrapper.handleIncomingTask(outdialTask);

const outdialFailedCall = outdialTaskOnSpy.mock.calls.find((call) => call[0] === TASK_EVENTS.TASK_OUTDIAL_FAILED);

expect(outdialFailedCall).toBeDefined();

const outdialFailedCallback = outdialFailedCall[1];
const reason = 'Outdial Failed Reason';

outdialFailedCallback(reason);

expect(onOutdialFailedMock).toHaveBeenCalledWith(reason);
});

it('should handle consultEnd event and reset queue consult state', () => {
const isQueueConsultInProgressSpy = jest.spyOn(storeWrapper, 'setIsQueueConsultInProgress');
const currentConsultQueueIdSpy = jest.spyOn(storeWrapper, 'setCurrentConsultQueueId');
Expand Down
42 changes: 42 additions & 0 deletions widgets-samples/cc/samples-cc-react-app/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@
const [showRejectedPopup, setShowRejectedPopup] = useState(false);
const [rejectedReason, setRejectedReason] = useState('');
const [selectedState, setSelectedState] = useState('');
const [showOutdialFailedModal, setShowOutdialFailedModal] = useState(false);
const [outdialFailedReason, setOutdialFailedReason] = useState('');
const [isLoggedIn, setIsLoggedIn] = useState(false);
const [incomingTasks, setIncomingTasks] = useState([]);
const [loginType, setLoginType] = useState('token');
Expand Down Expand Up @@ -357,18 +359,42 @@
useEffect(() => {
store.setIncomingTaskCb(onIncomingTaskCB);
store.setOnError(onError);
store.setOutdialFailed(onOutdialFailed);

return () => {
store.setOnError(undefined);
store.setTaskRejected(undefined);
store.setOutdialFailed(undefined);
store.setIncomingTaskCb(undefined);
};
}, []);

useEffect(() => {
const handleEscape = (e: KeyboardEvent) => {
if (e.key === 'Escape' && showOutdialFailedModal) {
setShowOutdialFailedModal(false);
}
};

if (showOutdialFailedModal) {
window.addEventListener('keydown', handleEscape);
}

return () => {
window.removeEventListener('keydown', handleEscape);
};
}, [showOutdialFailedModal]);

const onError = (widgetName: string, error: Error) => {
console.log('Error in widgets:', widgetName, error);
};

const onOutdialFailed = (reason: string) => {
console.log('Outdial failed:', reason);
setOutdialFailedReason(reason);
setShowOutdialFailedModal(true);
};

const onStateChange = (status) => {
console.log('onStateChange invoked', status);
//adding a log to be used for automation
Expand Down Expand Up @@ -508,7 +534,7 @@
style={{color: 'var(--mds-color-theme-text-error-normal)', marginBottom: '10px'}}
>
<strong>Note:</strong> When a number is dialed, the agent gets an incoming task to
accept via an Extension, Dial Number, or Browser. It's recommended to have the

Check warning on line 537 in widgets-samples/cc/samples-cc-react-app/src/App.tsx

View workflow job for this annotation

GitHub Actions / linter

`'` can be escaped with `&apos;`, `&lsquo;`, `&#39;`, `&rsquo;`

Check warning on line 537 in widgets-samples/cc/samples-cc-react-app/src/App.tsx

View workflow job for this annotation

GitHub Actions / linter

`'` can be escaped with `&apos;`, `&lsquo;`, `&#39;`, `&rsquo;`
incoming task/task list widget and call controls widget according to your needs.
</div>
</Text>
Expand Down Expand Up @@ -599,9 +625,9 @@
className="warning-note"
style={{color: 'var(--mds-color-theme-text-error-normal)', marginBottom: '10px'}}
>
<strong>Note:</strong> The "Enable Multi Login" option must be set before initializing the

Check warning on line 628 in widgets-samples/cc/samples-cc-react-app/src/App.tsx

View workflow job for this annotation

GitHub Actions / linter

`"` can be escaped with `&quot;`, `&ldquo;`, `&#34;`, `&rdquo;`

Check warning on line 628 in widgets-samples/cc/samples-cc-react-app/src/App.tsx

View workflow job for this annotation

GitHub Actions / linter

`"` can be escaped with `&quot;`, `&ldquo;`, `&#34;`, `&rdquo;`

Check warning on line 628 in widgets-samples/cc/samples-cc-react-app/src/App.tsx

View workflow job for this annotation

GitHub Actions / linter

`"` can be escaped with `&quot;`, `&ldquo;`, `&#34;`, `&rdquo;`

Check warning on line 628 in widgets-samples/cc/samples-cc-react-app/src/App.tsx

View workflow job for this annotation

GitHub Actions / linter

`"` can be escaped with `&quot;`, `&ldquo;`, `&#34;`, `&rdquo;`
SDK. Changes to this setting after SDK initialization will not take effect. Please ensure
you configure this option before clicking the "Init Widgets" button.

Check warning on line 630 in widgets-samples/cc/samples-cc-react-app/src/App.tsx

View workflow job for this annotation

GitHub Actions / linter

`"` can be escaped with `&quot;`, `&ldquo;`, `&#34;`, `&rdquo;`

Check warning on line 630 in widgets-samples/cc/samples-cc-react-app/src/App.tsx

View workflow job for this annotation

GitHub Actions / linter

`"` can be escaped with `&quot;`, `&ldquo;`, `&#34;`, `&rdquo;`

Check warning on line 630 in widgets-samples/cc/samples-cc-react-app/src/App.tsx

View workflow job for this annotation

GitHub Actions / linter

`"` can be escaped with `&quot;`, `&ldquo;`, `&#34;`, `&rdquo;`

Check warning on line 630 in widgets-samples/cc/samples-cc-react-app/src/App.tsx

View workflow job for this annotation

GitHub Actions / linter

`"` can be escaped with `&quot;`, `&ldquo;`, `&#34;`, `&rdquo;`
</div>
</Text>
</PopoverNext>
Expand Down Expand Up @@ -941,6 +967,22 @@
</div>
)}

{showOutdialFailedModal && (
<div className="task-rejected-popup" data-testid="samples:outdial-failed-modal">
<button className="close-btn" onClick={() => setShowOutdialFailedModal(false)}>
×
</button>
<Text>
<div style={{textAlign: 'center', fontSize: '1.25rem', fontWeight: 600}}>Outdial Failed</div>
</Text>
<Text>
<div style={{fontSize: '0.875rem', textAlign: 'center', color: 'rgb(171, 10, 21)'}}>
Reason: {outdialFailedReason}
</div>
</Text>
</div>
)}

{isSdkReady && (store.isAgentLoggedIn || isLoggedIn) && (
<EngageWidget accessToken={accessToken} currentTheme={currentTheme} isSdkReady={isSdkReady} />
)}
Expand Down
Loading
Loading