Skip to content

Commit

Permalink
Merge branch 'main' into custom-directory-performance
Browse files Browse the repository at this point in the history
  • Loading branch information
dremin committed Jan 17, 2025
2 parents 936bb7f + ca40b9d commit 9460dc8
Show file tree
Hide file tree
Showing 15 changed files with 93 additions and 19 deletions.
4 changes: 4 additions & 0 deletions docs/docs/feature-library/conversation-transfer.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ title: conversation-transfer
---
import PluginLibraryFeature from "./_plugin-library-feature.md";

:::caution Native feature now available
A new [native messaging transfers feature](https://www.twilio.com/docs/flex/admin-guide/setup/conversations/messaging-transfers) is available in Flex UI 2.8 and later. The native feature does not support warm transfers, invite tracking, or email, so the template feature remains available with support for these items, however both features cannot be enabled simultaneously.
:::

<PluginLibraryFeature />

This feature implements transferring of chats between agents and multiple agents in the same chat. It supports webchat, SMS and whatsapp that use [Flex Conversations](https://www.twilio.com/docs/flex/conversations).
Expand Down
1 change: 1 addition & 0 deletions plugin-flex-ts-template-v2/__mocks__/@twilio/flex-ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ module.exports = {
},
},
),
useFlexSelector: (callback) => callback(getMockedReduxState()),
// Component wrappers
withTaskContext: (WrappedComponent) => {
return () => ({
Expand Down
1 change: 1 addition & 0 deletions plugin-flex-ts-template-v2/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ module.exports = {
coverageReporters: ['json', 'lcov', 'text', 'clover', 'cobertura'],
globalSetup: '<rootDir>/globalSetupTests.js',
setupFilesAfterEnv: ['<rootDir>/setupTests.js'],
transformIgnorePatterns: ['/node_modules/(?!@twilio-paste/icons/.*)'],
};
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Manager } from '@twilio/flex-ui';

import { getFeatureFlags } from '../../utils/configuration';
import ConversationTransferConfiguration from './types/ServiceConfiguration';

Expand All @@ -7,8 +9,12 @@ const {
multi_participant = false,
} = (getFeatureFlags()?.features?.conversation_transfer as ConversationTransferConfiguration) || {};

const isNativeDigitalXferEnabled = (): boolean => {
return Manager.getInstance().store.getState().flex.featureFlags?.transfersConfig?.enabled === true;
};

export const isFeatureEnabled = () => {
return enabled;
return enabled && !isNativeDigitalXferEnabled();
};

export const isColdTransferEnabled = () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Manager } from '@twilio/flex-ui';

import { getFeatureFlags, getFlexFeatureFlag, getLoadedFeatures } from '../../utils/configuration';
import { ExternalDirectoryEntry } from './types/DirectoryEntry';
import CustomTransferDirectoryConfig from './types/ServiceConfiguration';
Expand Down Expand Up @@ -75,7 +77,10 @@ export const shouldFetchInsightsData = (): boolean => {
};

export const isCbmColdTransferEnabled = (): boolean => {
return getLoadedFeatures().includes('conversation-transfer') && conversation_transfer_cold_transfer;
return (
isNativeDigitalXferEnabled() ||
(getLoadedFeatures().includes('conversation-transfer') && conversation_transfer_cold_transfer)
);
};

export const isCbmWarmTransferEnabled = (): boolean => {
Expand Down Expand Up @@ -109,3 +114,7 @@ export const showOnlyAvailableWorkers = (): boolean => {
export const getMaxTaskRouterWorkers = (): number => {
return max_taskrouter_workers;
};

export const isNativeDigitalXferEnabled = (): boolean => {
return Manager.getInstance().store.getState().flex.featureFlags?.transfersConfig?.enabled === true;
};
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
isCbmColdTransferEnabled,
isCbmWarmTransferEnabled,
showRealTimeQueueData,
isNativeDigitalXferEnabled,
} from '../config';
import { CustomTransferDirectoryNotification } from '../flex-hooks/notifications/CustomTransferDirectory';
import { CustomWorkerAttributes } from '../../../types/task-router/Worker';
Expand All @@ -32,6 +33,7 @@ import { DirectoryEntry } from '../types/DirectoryEntry';
import DirectoryTab, { TransferClickPayload } from './DirectoryTab';
import logger from '../../../utils/logger';
import { getFlexFeatureFlag } from '../../../utils/configuration';
import ConversationsHelper from '../../../utils/helpers/ConversationsHelper';

export interface IRealTimeQueueData {
total_tasks: number | null;
Expand Down Expand Up @@ -212,6 +214,25 @@ const QueueDirectoryTab = (props: OwnProps) => {
};

const onTransferQueueClick = (entry: DirectoryEntry, transferOptions: TransferClickPayload) => {
if (isNativeDigitalXferEnabled() && TaskHelper.isCBMTask(props.task) && transferOptions.mode !== 'WARM') {
const {
flexInteractionSid: interactionSid,
flexInteractionChannelSid: channelSid,
conversationSid,
} = props.task.attributes;
(async () => {
const agent = await ConversationsHelper.getMyParticipant(props.task);
Actions.invokeAction('StartChannelTransfer', {
instanceSid: Manager.getInstance().serviceConfiguration.flex_instance_sid,
interactionSid,
channelSid,
fromSid: agent?.participantSid,
toSid: entry.address,
conversationSid,
});
})();
return;
}
Actions.invokeAction('TransferTask', {
task: props.task,
targetSid: entry.address,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ import {
isCbmColdTransferEnabled,
isCbmWarmTransferEnabled,
getMaxTaskRouterWorkers,
isNativeDigitalXferEnabled,
} from '../config';
import { DirectoryEntry } from '../types/DirectoryEntry';
import DirectoryTab from './DirectoryTab';
import { StringTemplates } from '../flex-hooks/strings/CustomTransferDirectory';
import logger from '../../../utils/logger';
import { getFlexFeatureFlag } from '../../../utils/configuration';
import ConversationsHelper from '../../../utils/helpers/ConversationsHelper';

export interface TransferClickPayload {
mode: 'WARM' | 'COLD';
Expand Down Expand Up @@ -107,6 +109,25 @@ const QueueDirectoryTab = (props: OwnProps) => {
};

const onTransferClick = (entry: DirectoryEntry, transferOptions: TransferClickPayload) => {
if (isNativeDigitalXferEnabled() && TaskHelper.isCBMTask(props.task) && transferOptions.mode !== 'WARM') {
const {
flexInteractionSid: interactionSid,
flexInteractionChannelSid: channelSid,
conversationSid,
} = props.task.attributes;
(async () => {
const agent = await ConversationsHelper.getMyParticipant(props.task);
Actions.invokeAction('StartChannelTransfer', {
instanceSid: Manager.getInstance().serviceConfiguration.flex_instance_sid,
interactionSid,
channelSid,
fromSid: agent?.participantSid,
toSid: entry.address,
conversationSid,
});
})();
return;
}
Actions.invokeAction('TransferTask', {
task: props.task,
targetSid: entry.address,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import zhHans from './zh-hans.json';

// Export the template names as an enum for better maintainability when accessing them elsewhere
export enum StringTemplates {
InternalDial = 'InternalDial',
CallAgent = 'PSInternalCallCallAgent',
SelectAgent = 'PSInternalCallSelectAgent',
CallQueue = 'PSInternalCallCallQueue',
Expand All @@ -15,7 +14,6 @@ export enum StringTemplates {

export const stringHook = () => ({
'en-US': {
[StringTemplates.InternalDial]: 'Internal Dial',
[StringTemplates.CallAgent]: 'Call Agent',
[StringTemplates.SelectAgent]: 'Select an agent',
[StringTemplates.CallQueue]: 'Call Queue',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ class AudioPlayerManagerHelper {
const custom_data = getFeatureFlags() || {};
let domain = `${custom_data.serverless_functions_protocol ?? 'https'}://${custom_data.serverless_functions_domain}`;
if (custom_data.serverless_functions_port) domain += `:${custom_data.serverless_functions_port}`;
if (this._mediaId) {
// AudioPlayerManager supports playing one media at a time, and we already are playing something. If we try to play more, it will get queued and potentially play indefinitely.
return;
}
this._mediaId = Flex.AudioPlayerManager.play({
url: `${domain}/features/ring-notification/phone_ringing.mp3`,
repeatable: true,
Expand All @@ -18,6 +22,7 @@ class AudioPlayerManagerHelper {
stop = (): void => {
if (this._mediaId) {
Flex.AudioPlayerManager.stop(this._mediaId);
this._mediaId = undefined;
}
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,20 @@ export const SupervisorBargeCoachButtons = ({ task }: SupervisorBargeCoachProps)
setIsLoading(true);

switch (mode) {
case 'barge':
case 'barge': {
// Barge-in will "unmute" their line if the are muted and disable coaching if enabled
await enterBargeMode(conferenceSid, participantSid);
const agentParticipant = conference?.participants.find(
(p) => p.participantType === 'worker' && monitoringTask?.workerSid === p.workerSid,
);
const agentSid = agentParticipant?.callSid;
if (!agentSid) {
setIsLoading(false);
return;
}
await enterBargeMode(conferenceSid, participantSid, agentSid);
break;
case 'coaching':
}
case 'coaching': {
// Coaching will "unmute" their line if the are muted and coach the specific agent using their call SID
const agentParticipant = conference?.participants.find(
(p) => p.participantType === 'worker' && monitoringTask?.workerSid === p.workerSid,
Expand All @@ -96,6 +105,7 @@ export const SupervisorBargeCoachButtons = ({ task }: SupervisorBargeCoachProps)
}
await enterCoachMode(conferenceSid, participantSid, agentSid);
break;
}
case 'monitoring':
// Mute their line and disable coaching
await enterListenMode(conferenceSid, participantSid);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"PSBargeCoachAgentSeekingAssistance": "Agente buscando asistencia",
"PSBargeCoachBarge": "Intervenir",
"PSBargeCoachCoach": "Entrenador",
"PSBargeCoachListen": "Escuchar",
"PSBargeCoachAgentCoachedBy": "Estás siendo entrenado por:",
"PSBargeCoachActiveSupervisors": "Supervisores activos:",
"PSBargeCoachNone": "Ninguno",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"PSBargeCoachBarge": "Chamada",
"PSBargeCoachAgentSeekingAssistance": "Agente buscando assistência",
"PSBargeCoachCoach": "Treinar",
"PSBargeCoachListen": "Ouvir",
"PSBargeCoachAgentCoachedBy": "Você está sendo assistido por:",
"PSBargeCoachAssistanceAlertsEnabled": "Alertas de Assistência do Agente Habilitados",
"PSBargeCoachAssistanceAlertsDisabled": "Alertas de Assistência do Agente Desativados",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ export const enterListenMode = async (conferenceSid: string, participantSid?: st
Manager.getInstance().store.dispatch(listen());
};

export const enterBargeMode = async (conferenceSid: string, participantSid: string) => {
await BargeCoachService.updateParticipantBargeCoach(conferenceSid, participantSid, '', false, false);
export const enterBargeMode = async (conferenceSid: string, participantSid: string, agentCallSid: string) => {
await BargeCoachService.updateParticipantBargeCoach(conferenceSid, participantSid, agentCallSid, false, false);
updateSyncDoc(conferenceSid, 'barge');
Manager.getInstance().store.dispatch(barge());
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,4 @@ export const supervisorBrowserRefresh = async () => {
SyncDoc.initSyncDocSupervisors(agentWorkerSID, '', myWorkerSID, '', '', 'remove');
localStorage.removeItem('agentWorkerSID');
}
// This is here if the Supervisor refreshes and has toggled alerts to false
// By default alerts set to true
const privateToggle = localStorage.getItem('privateToggle');
if (privateToggle === 'false') {
Flex.Manager.getInstance().store.dispatch(
setBargeCoachStatus({
coachingStatusPanel: false,
}),
);
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ class ConversationsHelper {
}
return false;
};

getMyParticipant = async (task: Flex.ITask): Promise<any> => {
if (!task || !task.attributes?.flexInteractionChannelSid || !task?.workerSid) return null;
const participants = await task.getParticipants(task.attributes.flexInteractionChannelSid);
return participants.find((p: any) => p.type === 'agent' && task.workerSid === p.routingProperties?.workerSid);
};
}
const ConversationsHelperSingleton = new ConversationsHelper();
export default ConversationsHelperSingleton;

0 comments on commit 9460dc8

Please sign in to comment.