Skip to content

Commit e703f02

Browse files
Merge pull request #2 from Shreyas281299/consult-agent-data-fix
fix(consult): fix CAD with correct consult agent details
2 parents e85e054 + 2792b0b commit e703f02

5 files changed

Lines changed: 116 additions & 53 deletions

File tree

packages/contact-center/cc-components/src/components/task/CallControl/CallControlCustom/call-control-custom.utils.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,16 @@ export const createConsultButtons = (
2727
try {
2828
const consultCtrl = controls?.consult;
2929
const mainCtrl = controls?.main;
30+
const isConsultLegActive = controls?.activeLeg === 'consult';
3031
return [
3132
{
3233
key: 'mute',
3334
icon: isMuted ? 'microphone-muted-bold' : 'microphone-bold',
3435
onClick: toggleConsultMute,
3536
tooltip: isMuted ? UNMUTE_CALL : MUTE_CALL,
3637
className: `${isMuted ? 'call-control-button-muted' : 'call-control-button'}`,
37-
disabled: !(consultCtrl?.mute?.isEnabled ?? false),
38+
// Consult mute should only be interactive while consult leg is active.
39+
disabled: !isConsultLegActive || !(consultCtrl?.mute?.isEnabled ?? false),
3840
isVisible: consultCtrl?.mute?.isVisible ?? false,
3941
},
4042
{

packages/contact-center/cc-components/src/components/task/CallControl/call-control.utils.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,8 @@ export const buildCallControlButtons = (
212212
onClick: handleMuteToggleFunc,
213213
tooltip: isMuted ? UNMUTE_CALL : MUTE_CALL,
214214
className: `${isMuted ? 'call-control-button-muted' : 'call-control-button'}`,
215-
disabled: isMuteButtonDisabled,
215+
// Respect SDK state and temporary click-guard state.
216+
disabled: isMuteButtonDisabled || !(mainCtrl?.mute?.isEnabled ?? false),
216217
isVisible: mainCtrl?.mute?.isVisible ?? false,
217218
dataTestId: 'call-control:mute-toggle',
218219
},

packages/contact-center/cc-components/tests/components/task/CallControl/CallControlCustom/call-control-custom.util.tsx

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,37 @@ describe('Call Control Custom Utils', () => {
188188
const muteButton = buttons.find((b) => b.key === 'mute');
189189
expect(muteButton?.isVisible).toBe(false);
190190
});
191+
192+
it('should disable consult mute when active leg is main', () => {
193+
const nestedControls = {
194+
activeLeg: 'main',
195+
main: {
196+
endConsult: {isVisible: true, isEnabled: true},
197+
},
198+
consult: {
199+
mute: {isVisible: true, isEnabled: true},
200+
switch: {isVisible: true, isEnabled: true},
201+
transfer: {isVisible: true, isEnabled: true},
202+
mergeToConference: {isVisible: true, isEnabled: true},
203+
endConsult: {isVisible: true, isEnabled: true},
204+
},
205+
};
206+
207+
const buttons = createConsultButtons(
208+
false,
209+
nestedControls as never,
210+
jest.fn(),
211+
jest.fn(),
212+
jest.fn(),
213+
jest.fn(),
214+
jest.fn(),
215+
loggerMock
216+
);
217+
218+
const muteButton = buttons.find((b) => b.key === 'mute');
219+
expect(muteButton?.isVisible).toBe(true);
220+
expect(muteButton?.disabled).toBe(true);
221+
});
191222
});
192223

193224
describe('getVisibleButtons', () => {

packages/contact-center/cc-components/tests/components/task/CallControl/call-control.utils.tsx

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -671,6 +671,46 @@ describe('CallControl Utils', () => {
671671
dataTestId: 'call-control:exit-conference',
672672
});
673673
});
674+
675+
it('should disable mute button when sdk marks main mute disabled', () => {
676+
const nestedControls = {
677+
main: {
678+
mute: {isVisible: true, isEnabled: false},
679+
hold: {isVisible: false, isEnabled: false},
680+
consult: {isVisible: false, isEnabled: false},
681+
transfer: {isVisible: false, isEnabled: false},
682+
recording: {isVisible: false, isEnabled: false},
683+
end: {isVisible: false, isEnabled: false},
684+
conference: {isVisible: false, isEnabled: false},
685+
switch: {isVisible: false, isEnabled: false},
686+
exitConference: {isVisible: false, isEnabled: false},
687+
},
688+
consult: {
689+
endConsult: {isVisible: false, isEnabled: false},
690+
},
691+
};
692+
693+
const buttons = buildCallControlButtons(
694+
false,
695+
false,
696+
false,
697+
mockMediaTypeInfo,
698+
nestedControls as never,
699+
false,
700+
mockFunctions.handleMuteToggleFunc,
701+
mockFunctions.handleToggleHoldFunc,
702+
mockFunctions.toggleRecording,
703+
mockFunctions.endCall,
704+
mockFunctions.exitConference,
705+
mockFunctions.switchToConsult,
706+
jest.fn(),
707+
jest.fn()
708+
);
709+
710+
const muteButton = buttons.find((b) => b.id === 'mute');
711+
expect(muteButton?.isVisible).toBe(true);
712+
expect(muteButton?.disabled).toBe(true);
713+
});
674714
});
675715

676716
describe('filterButtonsForConsultation', () => {

packages/contact-center/task/src/helper.ts

Lines changed: 40 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,12 @@ import store, {
2525
isInteractionOnHold,
2626
MEDIA_TYPE_TELEPHONY_LOWER,
2727
} from '@webex/cc-store';
28-
import {TIMER_LABEL_CONSULTING, TIMER_LABEL_CONSULT_REQUESTED, TIMER_LABEL_CONSULT_ON_HOLD, TIMER_LABEL_WRAP_UP} from './Utils/constants';
28+
import {
29+
TIMER_LABEL_CONSULTING,
30+
TIMER_LABEL_CONSULT_REQUESTED,
31+
TIMER_LABEL_CONSULT_ON_HOLD,
32+
TIMER_LABEL_WRAP_UP,
33+
} from './Utils/constants';
2934
import {calculateStateTimerData, calculateConsultTimerData, findLatestConsultMedia} from './Utils/timer-utils';
3035
import {useHoldTimer} from './Utils/useHoldTimer';
3136
import {OutdialAniEntriesResponse} from '@webex/contact-center/dist/types/services/config/types';
@@ -334,8 +339,7 @@ export const useCallControl = (props: useCallControlProps) => {
334339
useEffect(() => {
335340
// During conference, the call is never on hold
336341
const isInConference =
337-
controls?.main?.exitConference?.isVisible ||
338-
currentTask?.data?.interaction?.state === 'conference';
342+
controls?.main?.exitConference?.isVisible || currentTask?.data?.interaction?.state === 'conference';
339343
if (isInConference) {
340344
setIsHeld(false);
341345
return;
@@ -368,6 +372,9 @@ export const useCallControl = (props: useCallControlProps) => {
368372

369373
const {interaction} = currentTask.data;
370374
const myAgentId = store.cc.agentConfig?.agentId;
375+
const currentDestination = store.lastConsultDestination;
376+
const destinationType = currentDestination?.destinationType;
377+
const destinationId = currentDestination?.to;
371378

372379
// For Entry Point or Dial Number consults, check if destination agent has joined
373380
if (lastTargetType === TARGET_TYPE.ENTRY_POINT || lastTargetType === TARGET_TYPE.DIAL_NUMBER) {
@@ -396,8 +403,10 @@ export const useCallControl = (props: useCallControlProps) => {
396403
// eslint-disable-next-line @typescript-eslint/no-explicit-any
397404
const participant = interaction.participants[consultParticipantId] as any;
398405
const phoneNumber = participant.dn || participant.id;
406+
const matchesCurrentDestination =
407+
!destinationId || participant.epId === destinationId || participant.id === destinationId;
399408

400-
if (phoneNumber && phoneNumber !== consultAgentName) {
409+
if (phoneNumber && matchesCurrentDestination) {
401410
setConsultAgentName(phoneNumber);
402411
logger.info(`${lastTargetType} consult ringing - showing phone number: ${phoneNumber}`, {
403412
module: 'widget-cc-task#helper.ts',
@@ -418,7 +427,12 @@ export const useCallControl = (props: useCallControlProps) => {
418427
// Find the agent participant in consult media who is not the current agent
419428
const consultParticipantId = consultMedia.participants?.find((participantId: string) => {
420429
const participant = interaction.participants[participantId];
421-
return participant && participant.id !== myAgentId && participant.pType === 'Agent';
430+
const matchesDestination =
431+
destinationType !== 'agent' ||
432+
!destinationId ||
433+
participantId === destinationId ||
434+
participant?.id === destinationId;
435+
return participant && participant.id !== myAgentId && participant.pType === 'Agent' && matchesDestination;
422436
});
423437

424438
if (consultParticipantId && interaction.participants[consultParticipantId]) {
@@ -430,45 +444,12 @@ export const useCallControl = (props: useCallControlProps) => {
430444
});
431445
}
432446
} else {
433-
// Fallback: Use old logic if consult media not found
434-
const otherAgents = Object.values(interaction.participants || {}).filter(
435-
(participant) => participant.pType === 'Agent' && participant.id !== myAgentId
436-
);
437-
438-
// In a conference with multiple agents, find the agent currently being consulted
439-
// Priority: 1) consultState="consulting" 2) most recent consultTimestamp
440-
let foundAgent: {id: string; name: string} | null = null;
441-
442-
if (otherAgents.length > 0) {
443-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
444-
const consultingAgent = otherAgents.find((agent: any) => agent.consultState === 'consulting');
445-
446-
if (consultingAgent) {
447-
foundAgent = {
448-
id: consultingAgent.id,
449-
name: consultingAgent.name,
450-
};
451-
} else {
452-
// Fallback: Find agent with most recent consultTimestamp
453-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
454-
const agentWithMostRecentTimestamp = otherAgents.reduce((latest: any, current: any) => {
455-
const currentTimestamp = current.consultTimestamp || current.joinTimestamp || 0;
456-
const latestTimestamp = latest ? latest.consultTimestamp || latest.joinTimestamp || 0 : 0;
457-
return currentTimestamp >= latestTimestamp ? current : latest;
458-
}, null);
459-
460-
if (agentWithMostRecentTimestamp) {
461-
foundAgent = {
462-
id: agentWithMostRecentTimestamp.id,
463-
name: agentWithMostRecentTimestamp.name,
464-
};
465-
}
466-
}
467-
}
468-
469-
if (foundAgent) {
470-
setConsultAgentName(foundAgent.name);
471-
logger.info(`Consulting agent detected (fallback): ${foundAgent.name} ${foundAgent.id}`, {
447+
// When consult media is temporarily missing, trust the current consult
448+
// destination instead of broad participant fallbacks that can be stale.
449+
if (destinationType === 'agent' && destinationId && interaction.participants?.[destinationId]) {
450+
const targetedAgent = interaction.participants[destinationId];
451+
setConsultAgentName(targetedAgent.name || targetedAgent.id);
452+
logger.info(`Consulting agent detected (destination): ${targetedAgent.name} ${targetedAgent.id}`, {
472453
module: 'widget-cc-task#helper.ts',
473454
method: 'useCallControl#extractConsultingAgent',
474455
});
@@ -481,7 +462,7 @@ export const useCallControl = (props: useCallControlProps) => {
481462
method: 'extractConsultingAgent',
482463
});
483464
}
484-
}, [currentTask, logger, lastTargetType, consultAgentName, setConsultAgentName]);
465+
}, [currentTask, logger, lastTargetType]);
485466

486467
// Extract main call timestamp whenever currentTask changes
487468
useEffect(() => {
@@ -893,6 +874,10 @@ export const useCallControl = (props: useCallControlProps) => {
893874
holdParticipants: !allowParticipantsToInteract,
894875
};
895876

877+
// Update target type at source before consult starts so extraction logic
878+
// does not use a stale previous consult target type.
879+
setLastTargetType(destinationType as TargetType);
880+
896881
store.setLastConsultDestination({to: consultDestination, destinationType});
897882

898883
if (destinationType === 'queue') {
@@ -969,7 +954,7 @@ export const useCallControl = (props: useCallControlProps) => {
969954
let recoveredDestinationType: DestinationType = 'agent' as DestinationType;
970955
if (consultMedia?.participants) {
971956
for (const pid of consultMedia.participants) {
972-
const p = interaction?.participants?.[pid] as any;
957+
const p = interaction?.participants?.[pid] as {id?: string; pType?: string; epId?: string} | undefined;
973958
if (!p || p.id === myAgentId) continue;
974959
if (p.pType === 'Agent') {
975960
recoveredTo = pid;
@@ -1100,9 +1085,14 @@ export const useCallControl = (props: useCallControlProps) => {
11001085
setStateTimerTimestamp(stateTimerData.timestamp);
11011086
}
11021087
}, [
1103-
currentTask, controls, agentId,
1104-
participantIsWrapUp, participantWrapUpTimestamp, participantLastUpdated,
1105-
participantCurrentState, interactionState,
1088+
currentTask,
1089+
controls,
1090+
agentId,
1091+
participantIsWrapUp,
1092+
participantWrapUpTimestamp,
1093+
participantLastUpdated,
1094+
participantCurrentState,
1095+
interactionState,
11061096
]);
11071097

11081098
// Calculate consult timer label and timestamp.
@@ -1132,8 +1122,7 @@ export const useCallControl = (props: useCallControlProps) => {
11321122
}, [currentTask, controls, agentId, consultMediaIsHold, consultMediaId, participantConsultState]);
11331123

11341124
const isInConferenceState =
1135-
controls?.main?.exitConference?.isVisible ||
1136-
currentTask?.data?.interaction?.state === 'conference';
1125+
controls?.main?.exitConference?.isVisible || currentTask?.data?.interaction?.state === 'conference';
11371126
const effectiveIsHeld = isInConferenceState ? false : isHeld;
11381127

11391128
return {

0 commit comments

Comments
 (0)