Skip to content

CHI-2916 switchboarding #3040

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 54 commits into from
Jun 12, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
8ec84ab
import switchboarding tile and ux functionality
mythilytm Jun 3, 2025
2544b36
implement switchboarding backend logic
mythilytm Jun 4, 2025
428be9b
move switchboarding logic to taskrouter
mythilytm Jun 4, 2025
b46f0f0
move switchboarding logic to taskrouter
mythilytm Jun 4, 2025
788f08c
fix accountscoped path
mythilytm Jun 4, 2025
42f4669
fix endpoint with hrm api
mythilytm Jun 4, 2025
c271763
fix endpoint with hrm api
mythilytm Jun 4, 2025
b8fc83c
fix endpoint with hrm base api and token
mythilytm Jun 4, 2025
81e7ad1
add sync data to redux state
mythilytm Jun 4, 2025
8ef6191
add more validation
mythilytm Jun 4, 2025
899ef3c
add more validation
mythilytm Jun 4, 2025
d568b55
add sync data to redux state - refactor
mythilytm Jun 4, 2025
8659fe9
Merge branch 'master' into CHI-2916-3228-switchboarding
mythilytm Jun 4, 2025
feae6aa
use the new protectedpi for usign lambda url
mythilytm Jun 4, 2025
1d2d2be
use the new protectedpi for usign lambda url
mythilytm Jun 4, 2025
12794c6
resolve conflicts, linters and nits
mythilytm Jun 4, 2025
976c565
resolve conflicts, linters and nits
mythilytm Jun 4, 2025
fefbf35
use validateFlexTokenRequest for toggler
mythilytm Jun 5, 2025
45a2981
remove custom validation in switchboard function
mythilytm Jun 5, 2025
d72d743
sync client document debug
mythilytm Jun 5, 2025
4eab903
sync client document debug
mythilytm Jun 5, 2025
88648f2
sync client document debug
mythilytm Jun 5, 2025
197945d
more debugging of sync servce id
mythilytm Jun 5, 2025
1f5c6f8
more debugging of sync servce id
mythilytm Jun 5, 2025
0651cda
move supervisor worker sid to be sent from frontend
mythilytm Jun 5, 2025
dc2169b
move sync calls to redux layer
mythilytm Jun 6, 2025
c69b3b4
move sync calls to redux layer
mythilytm Jun 6, 2025
f3d274b
move sync calls to redux layer
mythilytm Jun 6, 2025
7706009
add matches the original queue to get its expression
mythilytm Jun 6, 2025
abb9a44
remove fallback, add both expressions
mythilytm Jun 6, 2025
0dae7d8
pr review comment
mythilytm Jun 9, 2025
4ba31f0
chore: handle multiple filters targeting original queue scenario
gpaoloni Jun 9, 2025
a54a897
added master workflow sid param from ssm getter
mythilytm Jun 9, 2025
7d0d15b
refactor to remove race condition - use create & remove
mythilytm Jun 9, 2025
080d749
move sync state to services/
mythilytm Jun 9, 2025
8063ec2
chore: changes to sync doc subscription
gpaoloni Jun 9, 2025
e13df25
comment
gpaoloni Jun 9, 2025
9416c3f
fix use of workflow sid
mythilytm Jun 9, 2025
ed16fd7
fix use of workflow sid
mythilytm Jun 10, 2025
0590e64
fix empty state doc
mythilytm Jun 10, 2025
dc9584e
fix empty state doc
mythilytm Jun 10, 2025
59c0182
fix errors related to sync doc creation
mythilytm Jun 10, 2025
43e9e15
localization of switchboard related strings
mythilytm Jun 10, 2025
8c009fd
chore: use sb notify document to sync clients
gpaoloni Jun 10, 2025
06d45bf
chore: implement supervisor auth for account scoped lambdas
gpaoloni Jun 10, 2025
25e85b9
fix: missing sb-state doc signals sb is off
gpaoloni Jun 10, 2025
1d83a16
fix: fix modal reading the wrong supervisor
mythilytm Jun 10, 2025
019edf5
chore: cleanup types and use result in toggleSwitchboardQueue
gpaoloni Jun 10, 2025
2c843cb
fix: type errors
gpaoloni Jun 10, 2025
9d1ec6b
fix: lint
gpaoloni Jun 10, 2025
d5b4f66
fix: lint
gpaoloni Jun 10, 2025
61de98b
fix: correct SSM param for sb queue
gpaoloni Jun 10, 2025
f51e773
fix: correct params to remove sb filters
gpaoloni Jun 10, 2025
a720422
Merge branch 'v2.33-rc' into CHI-2916-3228-switchboarding
mythilytm Jun 12, 2025
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
Prev Previous commit
Next Next commit
move sync state to services/
  • Loading branch information
mythilytm committed Jun 9, 2025
commit 080d7495146311cc33a2ee9ae9f460ec868d9ee9
2 changes: 1 addition & 1 deletion plugin-hrm-form/src/HrmFormPlugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import * as Channels from './channels/setUpChannels';
import setUpMonitoring from './utils/setUpMonitoring';
import { changeLanguage } from './states/configuration/actions';
import { getAseloFeatureFlags, getHrmConfig, initializeConfig, subscribeToConfigUpdates } from './hrmConfig';
import { setUpSyncClient } from './utils/sharedState';
import { setUpSyncClient } from './services/SyncService';
import { FeatureFlags } from './types/types';
import { setUpReferrableResources } from './components/resources/setUpReferrableResources';
import QueuesView from './components/queuesView';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { Manager, Notifications, TaskContextProps, TaskHelper, Template, withTas
import AddIcCallRounded from '@material-ui/icons/AddIcCallRounded';
import { useDispatch, useSelector } from 'react-redux';

import { createCallStatusSyncDocument } from '../../../utils/sharedState';
import { createCallStatusSyncDocument } from '../../../services/SyncService';
import { ConferenceNotifications } from '../../../conference/setUpConferenceActions';
import PhoneInputDialog from './PhoneInputDialog';
import { ConferenceButtonWrapper, ConferenceButton } from './styles';
Expand Down
18 changes: 4 additions & 14 deletions plugin-hrm-form/src/components/queuesView/QueueSelectionModals.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,25 +31,15 @@ type SelectQueueModalProps = {
isOpen: boolean;
onClose: () => void;
onSelect: (queue: string) => void;
selectedQueue: string | null;
setSelectedQueue: (queue: string) => void;
queueRef: React.MutableRefObject<string | null>;
queues: Queue[];
};

export const SelectQueueModal: React.FC<SelectQueueModalProps> = ({
isOpen,
onClose,
onSelect,
selectedQueue,
setSelectedQueue,
queueRef,
queues,
}) => {
export const SelectQueueModal: React.FC<SelectQueueModalProps> = ({ isOpen, onClose, onSelect, queues }) => {
// Define custom radio button handler
const [selectedQueue, setSelectedQueue] = React.useState<string | null>(null);

const handleRadioChange = (value: string) => {
setSelectedQueue(value);
queueRef.current = value;
};

return (
Expand Down Expand Up @@ -180,7 +170,7 @@ export const SelectQueueModal: React.FC<SelectQueueModalProps> = ({
</TertiaryButton>
<StyledNextStepButton
onClick={() => {
const currentQueue = selectedQueue || queueRef.current;
const currentQueue = selectedQueue;
if (currentQueue) {
onSelect(currentQueue);
} else {
Expand Down
27 changes: 11 additions & 16 deletions plugin-hrm-form/src/components/queuesView/SwitchboardTile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export const setUpSwitchboard = () => {
};

const SwitchboardTile = () => {
const [selectedQueue, setSelectedQueue] = useState<string | null>(null);
// const [selectedQueue, setSelectedQueue] = useState<string | null>(null);
const [isModalOpen, setIsModalOpen] = useState(false);
const [isConfirmationDialogOpen, setIsConfirmationDialogOpen] = useState(false);

Expand All @@ -55,12 +55,11 @@ const SwitchboardTile = () => {
useSubscribeToSwitchboardState();

useEffect(() => {
selectedQueueRef.current = selectedQueue;
}, [selectedQueue]);
selectedQueueRef.current = switchboardState.queueName;
}, [switchboardState]);

const handleOpenModal = () => {
setIsModalOpen(true);
setSelectedQueue(null);
selectedQueueRef.current = null;
};

Expand All @@ -78,15 +77,15 @@ const SwitchboardTile = () => {

const toggleSwitchboardingForQueue = useToggleSwitchboardingForQueue();

const handleSwitchboarding = async (queue: string) => {
if (!queue) {
const handleSwitchboarding = async (queueSid: string) => {
if (!queueSid) {
return;
}

try {
await toggleSwitchboardingForQueue(queue, workerSid);
await toggleSwitchboardingForQueue({ queueSid, supervisorWorkerSid: workerSid });
setIsModalOpen(false);
setSelectedQueue(queue);
selectedQueueRef.current = queueSid;
} catch (error) {
console.error('Error in switchboarding:', error);
}
Expand All @@ -96,7 +95,7 @@ const SwitchboardTile = () => {
if (switchboardState?.isSwitchboardingActive) {
// If already switchboarding, open confirmation modal to turn it off
if (switchboardState.queueSid) {
setSelectedQueue(switchboardState.queueSid);
selectedQueueRef.current = switchboardState.queueSid;
handleOpenConfirmationDialog();
}
} else {
Expand Down Expand Up @@ -142,10 +141,9 @@ const SwitchboardTile = () => {
);

const handleConfirmTurnOff = () => {
if (selectedQueue) {
handleSwitchboarding(selectedQueue);
if (!switchboardState.isSwitchboardingActive) {
handleSwitchboarding(switchboardState.queueName);
handleCloseConfirmationDialog();
} else {
}
};
const borderColor = switchboardState?.isSwitchboardingActive ? '#f8c000' : '#e1e3ea';
Expand Down Expand Up @@ -230,17 +228,14 @@ const SwitchboardTile = () => {
isOpen={isModalOpen}
onClose={handleCloseModal}
onSelect={handleSwitchboarding}
selectedQueue={selectedQueue}
setSelectedQueue={setSelectedQueue}
queueRef={selectedQueueRef}
queues={filteredQueues}
/>

<TurnOffSwitchboardDialog
isOpen={isConfirmationDialogOpen}
onClose={handleCloseConfirmationDialog}
onConfirm={handleConfirmTurnOff}
selectedQueue={selectedQueue}
selectedQueue={selectedQueueRef.current}
renderStatusText={(queueKey, startTime) =>
renderSwitchboardStatusText(
getQueueName(queueKey),
Expand Down
2 changes: 1 addition & 1 deletion plugin-hrm-form/src/dualWrite/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

import { ITask } from '@twilio/flex-ui';

import { savePendingContactToSharedState } from '../utils/sharedState';
import { savePendingContactToSharedState } from '../services/SyncService';
import saveContactToSaferNet from './br';
import { getAseloFeatureFlags, getHrmConfig } from '../hrmConfig';

Expand Down
11 changes: 2 additions & 9 deletions plugin-hrm-form/src/services/SwitchboardService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@
*/

import { Manager } from '@twilio/flex-ui';
import { SwitchboardSyncState } from 'hrm-types';

import { getSwitchboardState, SwitchboardSyncState } from '../states/switchboard/useSwitchboard';
import { getSwitchboardState } from '../states/switchboard/useSwitchboard';

Check failure on line 20 in plugin-hrm-form/src/services/SwitchboardService.ts

View workflow job for this annotation

GitHub Actions / flex-ci / lint

getSwitchboardState not found in '../states/switchboard/useSwitchboard'
import fetchProtectedApi from './fetchProtectedApi';

/**
Expand All @@ -36,21 +37,13 @@
throw new Error('Invalid queue SID or supervisor worker SID provided');
}

const manager = Manager.getInstance();
const flexToken = manager.user.token;

if (!flexToken) {
throw new Error('Authentication token not available. Please refresh the page and try again.');
}

const currentState = await getSwitchboardState();
const isDisabling = currentState.isSwitchboardingActive && currentState.queueSid === queueSid;
const operation = isDisabling ? 'disable' : 'enable';

const body = {
originalQueueSid: queueSid,
operation,
Token: flexToken,
supervisorWorkerSid,
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,15 @@
*/

import SyncClient from 'twilio-sync';
import { DEFAULT_SWITCHBOARD_STATE, SWITCHBOARD_DOCUMENT_NAME, SwitchboardSyncState } from 'hrm-types';

import { issueSyncToken } from '../services/ServerlessService';
import { issueSyncToken } from './ServerlessService';
import { getAseloFeatureFlags, getTemplateStrings } from '../hrmConfig';

// eslint-disable-next-line import/no-mutable-exports
let sharedSyncClient: SyncClient;

export { sharedSyncClient };

Check failure on line 26 in plugin-hrm-form/src/services/SyncService.ts

View workflow job for this annotation

GitHub Actions / flex-ci / lint

exported declaration 'sharedSyncClient' not used within other modules

export const setUpSyncClient = async () => {
const updateSharedStateToken = async () => {
Expand Down Expand Up @@ -139,3 +140,44 @@

return { status: 'success', callStatusSyncDocument } as const;
};

/**
* Get the current switchboard state
* @returns Current switchboarding state
*/
export const getSwitchboardState = async (): Promise<SwitchboardSyncState> => {

Check failure on line 148 in plugin-hrm-form/src/services/SyncService.ts

View workflow job for this annotation

GitHub Actions / flex-ci / lint

exported declaration 'getSwitchboardState' not used within other modules
try {
const doc = await sharedSyncClient.document(SWITCHBOARD_DOCUMENT_NAME);
return doc.data as SwitchboardSyncState;
} catch (error) {
console.error('Error getting switchboard state:', error);
throw error;
}
};

/**
* Subscribe to switchboarding state changes
* @param callback Function to call when switchboarding state changes: (state: SwitchboardSyncState) => void
* @returns Function to unsubscribe from updates: () => void
*/
export const subscribeSwitchboardState = async (
callback: (state: SwitchboardSyncState) => void,
): Promise<() => void> => {
try {
const doc = await getSwitchboardState();

const handler = (event: { data: unknown }) => {
callback(event.data as SwitchboardSyncState);
};

doc.on('updated', handler);
callback(doc.data as SwitchboardSyncState);

return () => {
doc.off('updated', handler);
};
} catch (error) {
console.error('Error subscribing to switchboard state:', error);
throw error;
}
};
2 changes: 1 addition & 1 deletion plugin-hrm-form/src/states/contacts/llmAssistant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export const llmAssistantReducer = (initialState: ContactsState) =>
(state, { payload: { contactId, summaryText, form, item } }): ContactsState => {
const { draftContact, savedContact } = state.existingContacts[contactId];
const llmSupportedEntries =
draftContact?.rawJson?.llmSupportedEntries ?? (savedContact.rawJson as any).llmSupportedEntries ?? {};
draftContact?.rawJson?.llmSupportedEntries ?? savedContact.rawJson.llmSupportedEntries ?? {};
llmSupportedEntries[form] = Array.from(new Set([...(llmSupportedEntries[form] ?? []), item]));
const existingText = draftContact?.rawJson[form]?.[item] ?? savedContact.rawJson[form]?.[item] ?? '';
const updatedText = `${existingText}${existingText ? '\n\n' : ''}${summaryText}`;
Expand Down
76 changes: 10 additions & 66 deletions plugin-hrm-form/src/states/switchboard/useSwitchboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,73 +16,14 @@

import { useDispatch, useSelector } from 'react-redux';
import { useEffect, useCallback } from 'react';
import { SwitchboardSyncState, SWITCHBOARD_DOCUMENT_NAME, DEFAULT_SWITCHBOARD_STATE } from 'hrm-types';

import { updateSwitchboardState, setSwitchboardLoading, setSwitchboardError } from './actions';
import { toggleSwitchboardingForQueue } from '../../services/SwitchboardService';
import { RootState } from '..';
import { SwitchboardState } from './types';
import { namespace, switchboardBase } from '../storeNamespaces';
import { sharedSyncClient } from '../../utils/sharedState';
import {
SwitchboardSyncState,
SWITCHBOARD_DOCUMENT_NAME,
DEFAULT_SWITCHBOARD_STATE
} from '@tech-matters/hrm-types';

/**
* Initialize or get the switchboard document from Twilio Sync
* @returns Twilio Sync document
*/
const initSwitchboardSyncDocument = () => {
try {
return sharedSyncClient.document(SWITCHBOARD_DOCUMENT_NAME);
} catch (error) {
return sharedSyncClient.document({
id: SWITCHBOARD_DOCUMENT_NAME,
data: DEFAULT_SWITCHBOARD_STATE,
ttl: 48 * 60 * 60, // 48 hours
});
}
};

/**
* Get the current switchboard state
* @returns Current switchboarding state
*/
export const getSwitchboardState = async (): Promise<SwitchboardSyncState> => {
try {
const doc = await initSwitchboardSyncDocument();
return doc.data as SwitchboardSyncState;
} catch (error) {
console.error('Error getting switchboard state:', error);
throw error;
}
};

/**
* Subscribe to switchboarding state changes
* @param callback Function to call when switchboarding state changes: (state: SwitchboardSyncState) => void
* @returns Function to unsubscribe from updates: () => void
*/
const subscribeSwitchboardState = async (callback: (state: SwitchboardSyncState) => void): Promise<() => void> => {
try {
const doc = await initSwitchboardSyncDocument();

const handler = (event: { data: unknown }) => {
callback(event.data as SwitchboardSyncState);
};

doc.on('updated', handler);
callback(doc.data as SwitchboardSyncState);

return () => {
doc.off('updated', handler);
};
} catch (error) {
console.error('Error subscribing to switchboard state:', error);
throw error;
}
};
import { subscribeSwitchboardState } from '../../services/SyncService';

export const useSwitchboardState = (): SwitchboardState => {
return useSelector((state: RootState) => state[namespace][switchboardBase]);
Expand Down Expand Up @@ -137,14 +78,17 @@ export const useSubscribeToSwitchboardState = (): void => {
* Hook that provides a function to toggle switchboarding for a queue
* @returns Function to toggle switchboarding for a queue
*/
export const useToggleSwitchboardingForQueue = (): ((
queueSid: string,
supervisorWorkerSid?: string,
) => Promise<void>) => {
export const useToggleSwitchboardingForQueue = (): (({
queueSid,
supervisorWorkerSid,
}: {
queueSid: string;
supervisorWorkerSid?: string;
}) => Promise<void>) => {
const dispatch = useDispatch();

return useCallback(
async (queueSid: string, supervisorWorkerSid?: string): Promise<void> => {
async ({ queueSid, supervisorWorkerSid }: { queueSid: string; supervisorWorkerSid?: string }): Promise<void> => {
try {
dispatch(setSwitchboardLoading(true));
dispatch(setSwitchboardError(null));
Expand Down
Loading