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
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ const CallControlCADComponent: React.FC<CallControlComponentProps> = (props) =>
const isTelephony = mediaChannel === MediaChannelType.TELEPHONY;
const participantsCount = conferenceParticipants?.length || 1;
const participantsLabel = participantsCount === 1 ? 'Participant' : 'Participants';
const shouldShowParticipantsList = (conferenceParticipants?.length || 0) > 1;

const customerName = currentTask?.data?.interaction?.callAssociatedDetails?.customerName;

Expand Down Expand Up @@ -188,7 +189,7 @@ const CallControlCADComponent: React.FC<CallControlComponentProps> = (props) =>
</>
)}
</Text>
{controls?.main?.exitConference?.isVisible && !controls?.main?.wrapup?.isVisible && (
{shouldShowParticipantsList && !controls?.main?.wrapup?.isVisible && (
<>
<div className="vertical-divider"></div>
<div className="participants-section">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -379,4 +379,33 @@ describe('CallControlCADComponent', () => {
expect(screen.queryByText(/On hold/)).not.toBeInTheDocument();
});
});

describe('conference participants list visibility', () => {
it('shows participants list when there are more than two participants total', () => {
const screen = render(
<CallControlCADComponent
{...defaultProps}
controls={createEnabledMainTaskUIControls({exitConference: {isVisible: false, isEnabled: false}})}
conferenceParticipants={[
{id: 'agent-2', name: 'Agent Two', pType: 'Agent'},
{id: 'agent-3', name: 'Agent Three', pType: 'Agent'},
]}
/>
);

expect(screen.getByTestId('call-control:participants-trigger')).toBeInTheDocument();
});

it('hides participants list when two or fewer participants are present in total', () => {
const screen = render(
<CallControlCADComponent
{...defaultProps}
controls={createEnabledMainTaskUIControls({exitConference: {isVisible: true, isEnabled: true}})}
conferenceParticipants={[{id: 'agent-2', name: 'Agent Two', pType: 'Agent'}]}
/>
);

expect(screen.queryByTestId('call-control:participants-trigger')).not.toBeInTheDocument();
});
});
});
2 changes: 1 addition & 1 deletion packages/contact-center/store/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"deploy:npm": "yarn npm publish"
},
"dependencies": {
"@webex/contact-center": "3.12.0-task-refactor.6",
"@webex/contact-center": "3.12.0-task-refactor.7",
"mobx": "6.13.5",
"typescript": "5.6.3"
},
Expand Down
46 changes: 41 additions & 5 deletions packages/contact-center/store/src/task-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,13 +109,49 @@ export const setmTypeForEPDN = (task: ITask, mType: string) => {
return mType;
};
export const findMediaResourceId = (task: ITask, mType: string) => {
for (const key in task.data.interaction.media) {
if (task.data.interaction.media[key].mType === mType) {
return task.data.interaction.media[key].mediaResourceId;
}
if (!task?.data?.interaction?.media) {
return '';
}

const matchingMedia = Object.values(task.data.interaction.media).filter((media) => media.mType === mType);

if (matchingMedia.length === 0) {
return '';
}

if (matchingMedia.length === 1) {
return matchingMedia[0].mediaResourceId;
}

return '';
// In some consult flows, stale consult legs are retained in media. Prefer the
// latest snapshot to avoid resolving an older consulted agent.
const getMediaRecencyScore = (media: Record<string, unknown>, fallbackIndex: number): number => {
const candidateTimestamps = [
media.lastUpdated,
media.joinTimestamp,
media.consultTimestamp,
media.holdTimestamp,
media.eventTime,
media.createdAt,
];

for (const value of candidateTimestamps) {
if (typeof value === 'number' && Number.isFinite(value) && value > 0) {
return value;
}
}

// Fall back to the object traversal order if no timestamp exists.
return fallbackIndex;
};

const latestMedia = matchingMedia.reduce((latest, media, index) => {
const latestScore = getMediaRecencyScore(latest as Record<string, unknown>, index - 1);
const currentScore = getMediaRecencyScore(media as Record<string, unknown>, index);
return currentScore >= latestScore ? media : latest;
});

return latestMedia.mediaResourceId || '';
};

/**
Expand Down
29 changes: 27 additions & 2 deletions packages/contact-center/task/src/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,9 @@ export const useCallControl = (props: useCallControlProps) => {
const prevIsConsultingRef = useRef(
!!(initialControls?.consult?.endConsult?.isVisible || initialControls?.main?.endConsult?.isVisible)
);
const consultVisibilityRef = useRef(
!!(initialControls?.consult?.endConsult?.isVisible || initialControls?.main?.endConsult?.isVisible)
);
const [lastTargetType, setLastTargetType] = useState<TargetType>(TARGET_TYPE.AGENT);
const [conferenceParticipants, setConferenceParticipants] = useState<Participant[]>([]);
const lastWrapupAuxCodeIdRef = useRef<string | null>(null);
Expand Down Expand Up @@ -415,6 +418,24 @@ export const useCallControl = (props: useCallControlProps) => {
// Use custom hook for hold timer management
const holdTime = useHoldTimer(currentTask, controls);

useEffect(() => {
const isConsulting = !!(controls?.consult?.endConsult?.isVisible || controls?.main?.endConsult?.isVisible);
const wasConsulting = consultVisibilityRef.current;

if (wasConsulting && !isConsulting) {
setConsultAgentName('Consult Agent');
setConsultTimerLabel(TIMER_LABEL_CONSULTING);
setConsultTimerTimestamp(0);
setLastTargetType(TARGET_TYPE.AGENT);
store.setIsQueueConsultInProgress(false);
store.setCurrentConsultQueueId(null);
store.setLastConsultDestination(null);
store.setConsultStartTimeStamp(null);
}

consultVisibilityRef.current = isConsulting;
}, [controls?.consult?.endConsult?.isVisible, controls?.main?.endConsult?.isVisible]);

useEffect(() => {
if (currentTask && store?.cc?.agentConfig?.agentId) {
const participants = getConferenceParticipants(currentTask, store.cc.agentConfig.agentId);
Expand All @@ -424,9 +445,13 @@ export const useCallControl = (props: useCallControlProps) => {
// Function to extract consulting agent information
const extractConsultingAgent = useCallback(() => {
try {
if (!currentTask?.data?.interaction?.participants) return;
// currentTask.data can briefly lag behind the freshest state-machine snapshot.
// Prefer the latest taskData so consult UI always reflects the newest consult leg.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const latestTaskData = (currentTask as any)?.state?.context?.taskData;
const interaction = latestTaskData?.interaction ?? currentTask?.data?.interaction;
if (!interaction?.participants) return;

const {interaction} = currentTask.data;
const myAgentId = store.cc.agentConfig?.agentId;
const currentDestination = store.lastConsultDestination;
const destinationType = currentDestination?.destinationType;
Expand Down
10 changes: 5 additions & 5 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -9544,7 +9544,7 @@ __metadata:
"@testing-library/react": "npm:16.0.1"
"@types/jest": "npm:29.5.14"
"@types/react-test-renderer": "npm:18"
"@webex/contact-center": "npm:3.12.0-task-refactor.6"
"@webex/contact-center": "npm:3.12.0-task-refactor.7"
"@webex/test-fixtures": "workspace:*"
babel-jest: "npm:29.7.0"
babel-loader: "npm:9.2.1"
Expand Down Expand Up @@ -9937,9 +9937,9 @@ __metadata:
languageName: node
linkType: hard

"@webex/contact-center@npm:3.12.0-task-refactor.6":
version: 3.12.0-task-refactor.6
resolution: "@webex/contact-center@npm:3.12.0-task-refactor.6"
"@webex/contact-center@npm:3.12.0-task-refactor.7":
version: 3.12.0-task-refactor.7
resolution: "@webex/contact-center@npm:3.12.0-task-refactor.7"
dependencies:
"@types/platform": "npm:1.3.4"
"@webex/calling": "npm:3.12.0-task-refactor.1"
Expand All @@ -9953,7 +9953,7 @@ __metadata:
lodash: "npm:^4.17.21"
uuid: "npm:^3.3.2"
xstate: "npm:5.24.0"
checksum: 10c0/e529d6ca11cec201e116cb655407a26c88f5c4c08d8b5b2c0a603e6b9c029fc893945c8035ec8d6fdd5d5e87d600483246aa87d66999a5348064fabbe9110322
checksum: 10c0/663407a7eb7003f46c9b8efaa3c8a711a2e883a94b341810d8d7a5e2fc106e88f311824ed0dff6e5a03fea0da814298bc5ea184fee14470bdf09d42f810cdfef
languageName: node
linkType: hard

Expand Down