diff --git a/docs/samples/contact-center/app.js b/docs/samples/contact-center/app.js index 8a86c820c1d..60af9e14047 100644 --- a/docs/samples/contact-center/app.js +++ b/docs/samples/contact-center/app.js @@ -79,17 +79,31 @@ const taskEvents = new CustomEvent('task:incoming', { }, }); -// TODO: Activate the call control buttons once the call is accepted and refctor this +function updateButtonsPostEndCall() { + holdResumeElm.disabled = true; + endElm.disabled = true; + pauseResumeRecordingElm.disabled = true; + wrapupElm.disabled = false; + wrapupCodesDropdownElm.disabled = false; +} + function registerTaskListeners(task) { task.on('task:assigned', (task) => { console.info('Call has been accepted for task: ', task.data.interactionId); + holdResumeElm.disabled = false; + holdResumeElm.innerText = 'Hold'; + pauseResumeRecordingElm.disabled = false; + pauseResumeRecordingElm.innerText = 'Pause Recording'; + endElm.disabled = false; }); task.on('task:media', (track) => { document.getElementById('remote-audio').srcObject = new MediaStream([track]); }); task.on('task:end', (task) => { - // Need to add check here to enable wrapup if the end button is not pressed - console.info('Call ended'); + if (!endElm.disabled) { + console.info('Call ended successfully by the external user'); + updateButtonsPostEndCall(); + } }); } @@ -309,11 +323,6 @@ incomingCallListener.addEventListener('task:incoming', (event) => { function answer() { answerElm.disabled = true; declineElm.disabled = true; - holdResumeElm.disabled = false; - holdResumeElm.innerText = 'Hold'; - pauseResumeRecordingElm.disabled = false; - pauseResumeRecordingElm.innerText = 'Pause Recording'; - endElm.disabled = false; task.accept(taskId); incomingDetailsElm.innerText = 'Call Accepted'; } @@ -436,11 +445,8 @@ function togglePauseResumeRecording() { function endCall() { endElm.disabled = true; task.end().then(() => { - console.log('Call ended successfully'); - holdResumeElm.disabled = true; - pauseResumeRecordingElm.disabled = true; - wrapupElm.disabled = false; - wrapupCodesDropdownElm.disabled = false; + console.log('Call ended successfully by agent'); + updateButtonsPostEndCall(); }).catch((error) => { console.error('Failed to end the call', error); endElm.disabled = false; diff --git a/packages/@webex/plugin-cc/src/services/task/TaskManager.ts b/packages/@webex/plugin-cc/src/services/task/TaskManager.ts index 44d66a50aee..143fc50cf8d 100644 --- a/packages/@webex/plugin-cc/src/services/task/TaskManager.ts +++ b/packages/@webex/plugin-cc/src/services/task/TaskManager.ts @@ -70,7 +70,7 @@ export default class TaskManager extends EventEmitter { break; case CC_EVENTS.AGENT_CONTACT_ASSIGNED: this.currentTask = this.currentTask.updateTaskData(payload.data); - this.emit(TASK_EVENTS.TASK_ASSIGNED, this.currentTask); + this.currentTask.emit(TASK_EVENTS.TASK_ASSIGNED, this.currentTask); break; case CC_EVENTS.CONTACT_ENDED: this.emit(TASK_EVENTS.TASK_UNASSIGNED, this.currentTask); @@ -81,7 +81,7 @@ export default class TaskManager extends EventEmitter { break; case CC_EVENTS.AGENT_WRAPUP: this.currentTask = this.currentTask.updateTaskData(payload.data); - this.emit(TASK_EVENTS.TASK_END, this.currentTask); + this.currentTask.emit(TASK_EVENTS.TASK_END, this.currentTask); break; default: break; diff --git a/packages/@webex/plugin-cc/src/services/task/index.ts b/packages/@webex/plugin-cc/src/services/task/index.ts index c6a44d07036..3f57dcb4ef9 100644 --- a/packages/@webex/plugin-cc/src/services/task/index.ts +++ b/packages/@webex/plugin-cc/src/services/task/index.ts @@ -120,6 +120,10 @@ export default class Task extends EventEmitter implements ITask { * */ public async hold(): Promise { try { + if (!this.data.mediaResourceId || this.data.mediaResourceId.length === 0) { + throw new Error('MediaResourceId is required'); + } + return this.contact.hold({ interactionId: this.data.interactionId, data: {mediaResourceId: this.data.mediaResourceId}, @@ -141,6 +145,10 @@ export default class Task extends EventEmitter implements ITask { */ public async resume(): Promise { try { + if (!this.data.mediaResourceId || this.data.mediaResourceId.length === 0) { + throw new Error('MediaResourceId is required'); + } + return this.contact.unHold({ interactionId: this.data.interactionId, data: {mediaResourceId: this.data.mediaResourceId}, @@ -184,6 +192,12 @@ export default class Task extends EventEmitter implements ITask { if (!this.data) { throw new Error('No task data available'); } + if (!wrapupPayload.auxCodeId || wrapupPayload.auxCodeId.length === 0) { + throw new Error('AuxCodeId is required'); + } + if (!wrapupPayload.wrapUpReason || wrapupPayload.wrapUpReason.length === 0) { + throw new Error('WrapUpReason is required'); + } return this.contact.wrapup({interactionId: this.data.interactionId, data: wrapupPayload}); } catch (error) { diff --git a/packages/@webex/plugin-cc/src/services/task/types.ts b/packages/@webex/plugin-cc/src/services/task/types.ts index c49f3698879..1f924b60d40 100644 --- a/packages/@webex/plugin-cc/src/services/task/types.ts +++ b/packages/@webex/plugin-cc/src/services/task/types.ts @@ -1,4 +1,5 @@ import {CallId} from '@webex/calling/dist/types/common/types'; +import EventEmitter from 'events'; import {Msg} from '../core/GlobalTypes'; export type TaskId = string; @@ -327,7 +328,7 @@ export type TaskResponse = AgentContact | Error | void; /** * Represents an interface for managing task related operations. */ -export interface ITask { +export interface ITask extends EventEmitter { /** * Event data received in the CC events */ diff --git a/packages/@webex/plugin-cc/test/unit/spec/services/task/TaskManager.ts b/packages/@webex/plugin-cc/test/unit/spec/services/task/TaskManager.ts index 375f0d51343..239709d19b6 100644 --- a/packages/@webex/plugin-cc/test/unit/spec/services/task/TaskManager.ts +++ b/packages/@webex/plugin-cc/test/unit/spec/services/task/TaskManager.ts @@ -13,7 +13,8 @@ import config from '../../../../../src/config'; jest.mock('./../../../../../src/services/task', () => { return jest.fn().mockImplementation(() => { return { - updateTaskData: jest.fn(), + updateTaskData: jest.fn().mockReturnThis(), + emit: jest.fn() }; }); }); @@ -147,11 +148,11 @@ describe('TaskManager', () => { }, }; - const taskAssignedSpy = jest.spyOn(taskManager, 'emit'); + const currentTaskAssignedSpy = jest.spyOn(taskManager.currentTask, 'emit'); webSocketManagerMock.emit('message', JSON.stringify(assignedPayload)); - expect(taskAssignedSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_ASSIGNED, taskManager.currentTask); + expect(currentTaskAssignedSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_ASSIGNED, taskManager.currentTask); }); it('should handle WebSocket message for AGENT_CONTACT_RESERVED and emit task:incoming for extension case', () => { diff --git a/packages/@webex/plugin-cc/test/unit/spec/services/task/index.ts b/packages/@webex/plugin-cc/test/unit/spec/services/task/index.ts index 6ef4db3c77c..33bbd3cd615 100644 --- a/packages/@webex/plugin-cc/test/unit/spec/services/task/index.ts +++ b/packages/@webex/plugin-cc/test/unit/spec/services/task/index.ts @@ -214,6 +214,11 @@ describe('Task', () => { expect(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'hold', CC_FILE); }); + it('should throw an error if mediaResourceId is missing in hold method', async () => { + task.data.mediaResourceId = ''; + await expect(task.hold()).rejects.toThrow(); + }); + it('should resume the task and return the expected response', async () => { const expectedResponse: TaskResponse = { data: { interactionId: taskId } } as AgentContact; contactMock.unHold.mockResolvedValue(expectedResponse); @@ -239,6 +244,11 @@ describe('Task', () => { expect(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'resume', CC_FILE); }); + it('should throw an error if mediaResourceId is missing in hold method', async () => { + task.data.mediaResourceId = ''; + await expect(task.resume()).rejects.toThrow(); + }); + it('should end the task and return the expected response', async () => { const expectedResponse: TaskResponse = { data: { interactionId: taskId } } as AgentContact; contactMock.end.mockResolvedValue(expectedResponse); @@ -298,6 +308,22 @@ describe('Task', () => { expect(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'wrapup', CC_FILE); }); + it('should throw an error if auxCodeId is missing in wrapup method', async () => { + const wrapupPayload = { + wrapUpReason: 'Customer request', + auxCodeId: '' + }; + await expect(task.wrapup(wrapupPayload)).rejects.toThrow(); + }); + + it('should throw an error if wrapUpReason is missing in wrapup method', async () => { + const wrapupPayload = { + wrapUpReason: '', + auxCodeId: 'auxCodeId123' + }; + await expect(task.wrapup(wrapupPayload)).rejects.toThrow(); + }); + it('should pause the recording of the task', async () => { await task.pauseRecording();