Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
26 changes: 18 additions & 8 deletions packages/contact-center/store/src/storeEventsWrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -414,10 +414,15 @@ class StoreWrapper implements IStoreWrapper {
this.store.cc.on(event, callback);
};

setTaskCallback = (event: TASK_EVENTS, callback, taskId: string) => {
if (!callback) return;
const task = this.store.taskList[taskId];
if (!task) return;
setTaskCallback = (event: TASK_EVENTS, callback, task: ITask) => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve task-id callback callers

Existing @webex/cc-store consumers can still call setTaskCallback(event, cb, interactionId) because this singleton is a published/default package surface, and this change now treats that string as an ITask: the guard passes, the log prints an undefined id, and the call then fails at task.on(...) instead of registering the listener. Since this PR is described as non-breaking and only updates the in-repo callers, keep accepting the old string-id form (or add a separate new API) so already-published widgets/hosts do not crash after upgrading; the same compatibility issue applies to removeTaskCallback.

Useful? React with 👍 / 👎.

if (!callback || !task) return;
this.store.logger?.info(
`CC-Widgets: setTaskCallback(): registering task event '${event}' for ${task.data?.interactionId}`,
{
module: 'storeEventsWrapper.ts',
method: 'setTaskCallback',
}
);
task.on(event, callback);
};

Expand Down Expand Up @@ -445,10 +450,15 @@ class StoreWrapper implements IStoreWrapper {
this.store.cc.off(event);
};

removeTaskCallback = (event: TASK_EVENTS, callback, taskId: string) => {
if (!callback) return;
const task = this.store.taskList[taskId];
if (!task) return;
removeTaskCallback = (event: TASK_EVENTS, callback, task: ITask) => {
if (!callback || !task) return;
this.store.logger?.info(
`CC-Widgets: removeTaskCallback(): removing task event '${event}' for ${task.data?.interactionId}`,
{
module: 'storeEventsWrapper.ts',
method: 'removeTaskCallback',
}
);
task.off(event, callback);
};

Expand Down
31 changes: 17 additions & 14 deletions packages/contact-center/store/tests/storeEventsWrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -474,46 +474,49 @@ describe('storeEventsWrapper', () => {
it('should set task callback', () => {
const mockCb = jest.fn();
expect(storeWrapper.setTaskCallback).toBeInstanceOf(Function);
storeWrapper['store'].taskList = {
mockTaskId: mockTask,
};

storeWrapper.setTaskCallback(TASK_EVENTS.TASK_ASSIGNED, mockCb, 'mockTaskId');
storeWrapper.setTaskCallback(TASK_EVENTS.TASK_ASSIGNED, mockCb, mockTask);
expect(mockTask.on).toHaveBeenCalledWith(TASK_EVENTS.TASK_ASSIGNED, mockCb);
});

it('should return if callback is not present or task is not found', () => {
it('should return if callback is not present or task is not provided', () => {
const mockCb = jest.fn();
expect(storeWrapper.setTaskCallback).toBeInstanceOf(Function);

storeWrapper.setTaskCallback(TASK_EVENTS.TASK_ASSIGNED, undefined, 'mockTaskId');
storeWrapper.setTaskCallback(TASK_EVENTS.TASK_ASSIGNED, undefined, mockTask);
expect(mockTask.on).not.toHaveBeenCalledWith(TASK_EVENTS.TASK_ASSIGNED, mockCb);

storeWrapper.setTaskCallback(TASK_EVENTS.TASK_ASSIGNED, mockCb, 'mockTaskI2');
storeWrapper.setTaskCallback(TASK_EVENTS.TASK_ASSIGNED, mockCb, null);
expect(mockTask.on).not.toHaveBeenCalledWith(TASK_EVENTS.TASK_ASSIGNED, mockCb);
});

it('should remove task callback', () => {
const mockCb = jest.fn();
storeWrapper['store'].taskList = {
mockTaskId: mockTask,
};
expect(storeWrapper.removeTaskCallback).toBeInstanceOf(Function);

storeWrapper.removeTaskCallback(TASK_EVENTS.TASK_WRAPPEDUP, mockCb, 'mockTaskId');
storeWrapper.removeTaskCallback(TASK_EVENTS.TASK_WRAPPEDUP, mockCb, mockTask);
expect(mockTask.off).toHaveBeenCalledWith(TASK_EVENTS.TASK_WRAPPEDUP, mockCb);
});

it('should return and not remove callback if callback is not present or task is not found', () => {
it('should return and not remove callback if callback is not present or task is not provided', () => {
const mockCb = jest.fn();
expect(storeWrapper.removeTaskCallback).toBeInstanceOf(Function);

storeWrapper.removeTaskCallback(TASK_EVENTS.TASK_ASSIGNED, undefined, 'mockTaskId');
storeWrapper.removeTaskCallback(TASK_EVENTS.TASK_ASSIGNED, undefined, mockTask);
expect(mockTask.on).not.toHaveBeenCalledWith(TASK_EVENTS.TASK_ASSIGNED, mockCb);

storeWrapper.removeTaskCallback(TASK_EVENTS.TASK_ASSIGNED, mockCb, 'mockTaskI2');
storeWrapper.removeTaskCallback(TASK_EVENTS.TASK_ASSIGNED, mockCb, null);
expect(mockTask.on).not.toHaveBeenCalledWith(TASK_EVENTS.TASK_ASSIGNED, mockCb);
});

it('should remove task callback even when task is absent from store.taskList', () => {
const mockCb = jest.fn();
// Clear taskList so the task is not found by ID lookup
storeWrapper['store'].taskList = {};

storeWrapper.removeTaskCallback(TASK_EVENTS.TASK_ASSIGNED, mockCb, mockTask);
expect(mockTask.off).toHaveBeenCalledWith(TASK_EVENTS.TASK_ASSIGNED, mockCb);
});
});
});

Expand Down
67 changes: 26 additions & 41 deletions packages/contact-center/task/src/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,29 +241,21 @@ export const useIncomingTask = (props: UseTaskProps) => {
useEffect(() => {
try {
if (!incomingTask) return;
store.setTaskCallback(TASK_EVENTS.TASK_ASSIGNED, taskAssignCallback, incomingTask.data.interactionId);
store.setTaskCallback(TASK_EVENTS.TASK_CONSULT_ACCEPTED, taskAssignCallback, incomingTask?.data.interactionId);
store.setTaskCallback(TASK_EVENTS.TASK_END, taskRejectCallback, incomingTask?.data.interactionId);
store.setTaskCallback(TASK_EVENTS.TASK_REJECT, taskRejectCallback, incomingTask?.data.interactionId);
store.setTaskCallback(TASK_EVENTS.TASK_CONSULT_END, taskRejectCallback, incomingTask?.data.interactionId);
store.setTaskCallback(TASK_EVENTS.TASK_OUTDIAL_FAILED, taskRejectCallback, incomingTask?.data.interactionId);
store.setTaskCallback(TASK_EVENTS.TASK_ASSIGNED, taskAssignCallback, incomingTask);
store.setTaskCallback(TASK_EVENTS.TASK_CONSULT_ACCEPTED, taskAssignCallback, incomingTask);
store.setTaskCallback(TASK_EVENTS.TASK_END, taskRejectCallback, incomingTask);
store.setTaskCallback(TASK_EVENTS.TASK_REJECT, taskRejectCallback, incomingTask);
store.setTaskCallback(TASK_EVENTS.TASK_CONSULT_END, taskRejectCallback, incomingTask);
store.setTaskCallback(TASK_EVENTS.TASK_OUTDIAL_FAILED, taskRejectCallback, incomingTask);

return () => {
try {
store.removeTaskCallback(TASK_EVENTS.TASK_ASSIGNED, taskAssignCallback, incomingTask?.data.interactionId);
store.removeTaskCallback(
TASK_EVENTS.TASK_CONSULT_ACCEPTED,
taskAssignCallback,
incomingTask?.data.interactionId
);
store.removeTaskCallback(TASK_EVENTS.TASK_END, taskRejectCallback, incomingTask?.data.interactionId);
store.removeTaskCallback(TASK_EVENTS.TASK_REJECT, taskRejectCallback, incomingTask?.data.interactionId);
store.removeTaskCallback(TASK_EVENTS.TASK_CONSULT_END, taskRejectCallback, incomingTask?.data.interactionId);
store.removeTaskCallback(
TASK_EVENTS.TASK_OUTDIAL_FAILED,
taskRejectCallback,
incomingTask?.data.interactionId
);
store.removeTaskCallback(TASK_EVENTS.TASK_ASSIGNED, taskAssignCallback, incomingTask);
store.removeTaskCallback(TASK_EVENTS.TASK_CONSULT_ACCEPTED, taskAssignCallback, incomingTask);
store.removeTaskCallback(TASK_EVENTS.TASK_END, taskRejectCallback, incomingTask);
store.removeTaskCallback(TASK_EVENTS.TASK_REJECT, taskRejectCallback, incomingTask);
store.removeTaskCallback(TASK_EVENTS.TASK_CONSULT_END, taskRejectCallback, incomingTask);
store.removeTaskCallback(TASK_EVENTS.TASK_OUTDIAL_FAILED, taskRejectCallback, incomingTask);
} catch (error) {
logger?.error(`CC-Widgets: Task: Error in useIncomingTask cleanup - ${error.message}`, {
module: 'useIncomingTask',
Expand Down Expand Up @@ -741,29 +733,22 @@ export const useCallControl = (props: useCallControlProps) => {
method: 'useEffect-init',
});

const interactionId = currentTask.data.interactionId;

store.setTaskCallback(
// Should use holdCallback
TASK_EVENTS.TASK_HOLD,
holdCallback,
interactionId
);
store.setTaskCallback(TASK_EVENTS.TASK_RESUME, resumeCallback, interactionId);
store.setTaskCallback(TASK_EVENTS.TASK_END, endCallCallback, interactionId);
store.setTaskCallback(TASK_EVENTS.TASK_WRAPUP, endCallCallback, interactionId); // Also call onEnd when entering wrapup
store.setTaskCallback(TASK_EVENTS.TASK_WRAPPEDUP, wrapupCallCallback, interactionId);
store.setTaskCallback(TASK_EVENTS.TASK_RECORDING_PAUSED, pauseRecordingCallback, interactionId);
store.setTaskCallback(TASK_EVENTS.TASK_RECORDING_RESUMED, resumeRecordingCallback, interactionId);
store.setTaskCallback(TASK_EVENTS.TASK_HOLD, holdCallback, currentTask);
store.setTaskCallback(TASK_EVENTS.TASK_RESUME, resumeCallback, currentTask);
store.setTaskCallback(TASK_EVENTS.TASK_END, endCallCallback, currentTask);
store.setTaskCallback(TASK_EVENTS.TASK_WRAPUP, endCallCallback, currentTask); // Also call onEnd when entering wrapup
store.setTaskCallback(TASK_EVENTS.TASK_WRAPPEDUP, wrapupCallCallback, currentTask);
store.setTaskCallback(TASK_EVENTS.TASK_RECORDING_PAUSED, pauseRecordingCallback, currentTask);
store.setTaskCallback(TASK_EVENTS.TASK_RECORDING_RESUMED, resumeRecordingCallback, currentTask);

return () => {
store.removeTaskCallback(TASK_EVENTS.TASK_HOLD, holdCallback, interactionId);
store.removeTaskCallback(TASK_EVENTS.TASK_RESUME, resumeCallback, interactionId);
store.removeTaskCallback(TASK_EVENTS.TASK_END, endCallCallback, interactionId);
store.removeTaskCallback(TASK_EVENTS.TASK_WRAPUP, endCallCallback, interactionId);
store.removeTaskCallback(TASK_EVENTS.TASK_WRAPPEDUP, wrapupCallCallback, interactionId);
store.removeTaskCallback(TASK_EVENTS.TASK_RECORDING_PAUSED, pauseRecordingCallback, interactionId);
store.removeTaskCallback(TASK_EVENTS.TASK_RECORDING_RESUMED, resumeRecordingCallback, interactionId);
store.removeTaskCallback(TASK_EVENTS.TASK_HOLD, holdCallback, currentTask);
store.removeTaskCallback(TASK_EVENTS.TASK_RESUME, resumeCallback, currentTask);
store.removeTaskCallback(TASK_EVENTS.TASK_END, endCallCallback, currentTask);
store.removeTaskCallback(TASK_EVENTS.TASK_WRAPUP, endCallCallback, currentTask);
store.removeTaskCallback(TASK_EVENTS.TASK_WRAPPEDUP, wrapupCallCallback, currentTask);
store.removeTaskCallback(TASK_EVENTS.TASK_RECORDING_PAUSED, pauseRecordingCallback, currentTask);
store.removeTaskCallback(TASK_EVENTS.TASK_RECORDING_RESUMED, resumeRecordingCallback, currentTask);
};
}, [currentTask]);

Expand Down
45 changes: 16 additions & 29 deletions packages/contact-center/task/tests/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,45 +107,29 @@ describe('useIncomingTask Hook', () => {
})
);

expect(setTaskCallbackSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_ASSIGNED, expect.any(Function), 'interaction1');
expect(setTaskCallbackSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_REJECT, expect.any(Function), 'interaction1');
expect(setTaskCallbackSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_END, expect.any(Function), 'interaction1');
expect(setTaskCallbackSpy).toHaveBeenCalledWith(
TASK_EVENTS.TASK_CONSULT_ACCEPTED,
expect.any(Function),
'interaction1'
);
expect(setTaskCallbackSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_CONSULT_END, expect.any(Function), 'interaction1');
expect(setTaskCallbackSpy).toHaveBeenCalledWith(
TASK_EVENTS.TASK_OUTDIAL_FAILED,
expect.any(Function),
'interaction1'
);
expect(setTaskCallbackSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_ASSIGNED, expect.any(Function), taskMock);
expect(setTaskCallbackSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_REJECT, expect.any(Function), taskMock);
expect(setTaskCallbackSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_END, expect.any(Function), taskMock);
expect(setTaskCallbackSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_CONSULT_ACCEPTED, expect.any(Function), taskMock);
expect(setTaskCallbackSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_CONSULT_END, expect.any(Function), taskMock);
expect(setTaskCallbackSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_OUTDIAL_FAILED, expect.any(Function), taskMock);
expect(setTaskCallbackSpy).toHaveBeenCalledTimes(6);

// Clean up
act(() => {
unmount();
});

expect(removeTaskCallbackSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_ASSIGNED, expect.any(Function), 'interaction1');
expect(removeTaskCallbackSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_REJECT, expect.any(Function), 'interaction1');
expect(removeTaskCallbackSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_END, expect.any(Function), 'interaction1');
expect(removeTaskCallbackSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_ASSIGNED, expect.any(Function), taskMock);
expect(removeTaskCallbackSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_REJECT, expect.any(Function), taskMock);
expect(removeTaskCallbackSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_END, expect.any(Function), taskMock);
expect(removeTaskCallbackSpy).toHaveBeenCalledWith(
TASK_EVENTS.TASK_CONSULT_ACCEPTED,
expect.any(Function),
'interaction1'
);
expect(removeTaskCallbackSpy).toHaveBeenCalledWith(
TASK_EVENTS.TASK_CONSULT_END,
expect.any(Function),
'interaction1'
);
expect(removeTaskCallbackSpy).toHaveBeenCalledWith(
TASK_EVENTS.TASK_OUTDIAL_FAILED,
expect.any(Function),
'interaction1'
taskMock
);
expect(removeTaskCallbackSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_CONSULT_END, expect.any(Function), taskMock);
expect(removeTaskCallbackSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_OUTDIAL_FAILED, expect.any(Function), taskMock);
expect(removeTaskCallbackSpy).toHaveBeenCalledTimes(6);

setTaskCallbackSpy.mockRestore();
Expand All @@ -155,7 +139,7 @@ describe('useIncomingTask Hook', () => {
it('should call onAccepted if it is provided', async () => {
// Mock store.setTaskCallback to capture the callback
let assignedCallback;
jest.spyOn(store, 'setTaskCallback').mockImplementation((event, callback) => {
const setTaskCallbackSpy = jest.spyOn(store, 'setTaskCallback').mockImplementation((event, callback) => {
if (event === TASK_EVENTS.TASK_ASSIGNED) {
assignedCallback = callback;
}
Expand All @@ -182,6 +166,7 @@ describe('useIncomingTask Hook', () => {

// Ensure no errors are logged
expect(logger.error).not.toHaveBeenCalled();
setTaskCallbackSpy.mockRestore();
});

it('should call onRejected if it is provided', async () => {
Expand Down Expand Up @@ -755,6 +740,8 @@ describe('useCallControl', () => {
const mockOnWrapUp = jest.fn();

beforeEach(() => {
// Restore any spied implementations leaked from prior describe blocks
jest.restoreAllMocks();
store.refreshTaskList();
// Mock the MediaStreamTrack and MediaStream classes for the test environment
global.MediaStreamTrack = jest.fn().mockImplementation(() => ({
Expand Down
Loading