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 30 commits
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
530 changes: 530 additions & 0 deletions lambdas/account-scoped/src/hrm/toggleSwitchboardQueue.ts

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions lambdas/account-scoped/src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { validateRequestMethod } from './validation/method';
import { isAccountSID } from './twilioTypes';
import { handleTaskRouterEvent } from './taskrouter';
import { handleGetProfileFlagsForIdentifier } from './hrm/getProfileFlagsForIdentifier';
import { handleToggleSwitchboardQueue } from './hrm/toggleSwitchboardQueue';
import {
handleCaptureChannelWithBot,
handleChatbotCallback,
Expand Down Expand Up @@ -85,6 +86,10 @@ const ROUTES: Record<string, FunctionRoute> = {
requestPipeline: [validateWebhookRequest],
handler: statusCallbackHandler,
},
toggleSwitchboardQueue: {
requestPipeline: [validateFlexTokenRequest],
handler: handleToggleSwitchboardQueue,
},
};

export const lookupRoute = (event: HttpRequest): AccountScopedRoute | undefined => {
Expand Down
8 changes: 6 additions & 2 deletions plugin-hrm-form/src/HrmFormPlugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,10 @@ 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 { setUpSharedStateClient } from './utils/sharedState';
import { setUpSyncClient } from './utils/sharedState';
import { FeatureFlags } from './types/types';
import { setUpReferrableResources } from './components/resources/setUpReferrableResources';
import QueuesView from './components/queuesView';
import TeamsView from './components/teamsView';
import { setUpCounselorToolkits } from './components/toolkits/setUpCounselorToolkits';
import { setUpTransferComponents } from './components/transfer/setUpTransferComponents';
Expand Down Expand Up @@ -123,6 +124,9 @@ const setUpComponents = (featureFlags: FeatureFlags, setupObject: ReturnType<typ
TeamsView.setUpTeamsViewFilters();
TeamsView.setUpWorkerDirectoryFilters();

// if (featureFlags.enable_switchboarding)
QueuesView.setUpSwitchboard();

if (featureFlags.enable_conferencing) setupConferenceComponents();

if (featureFlags.enable_language_selector) Components.setupWorkerLanguageSelect();
Expand Down Expand Up @@ -188,7 +192,7 @@ export default class HrmFormPlugin extends FlexPlugin {
await validateAndSetPermissionRules();
await ActionFunctions.loadCurrentDefinitionVersion();

setUpSharedStateClient();
setUpSyncClient();

/*
* localization setup (translates the UI if necessary)
Expand Down
38 changes: 38 additions & 0 deletions plugin-hrm-form/src/components/common/icons/SwitchboardIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* Copyright (C) 2021-2023 Technology Matters
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/

/* eslint-disable react/prop-types */
import React from 'react';

type Props = {
width: string;
height: string;
};

const SwitchboardIcon: React.FC<Props> = ({ width, height }) => {
return (
<svg width="35" height="35" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M8.74967 10.7186H6.66634V8.24493H8.74967V6.18353H11.2497V8.24493H13.333V10.7186H11.2497V12.78H8.74967V10.7186ZM9.99967 1.64844L3.33301 4.12212V9.1437C3.33301 13.3077 6.17467 17.1914 9.99967 18.1397C13.8247 17.1914 16.6663 13.3077 16.6663 9.1437V4.12212L9.99967 1.64844ZM14.9997 9.1437C14.9997 12.4419 12.8747 15.4928 9.99967 16.4246C7.12467 15.4928 4.99967 12.4502 4.99967 9.1437V5.26826L9.99967 3.413L14.9997 5.26826V9.1437Z"
fill="black"
/>
</svg>
);
};

SwitchboardIcon.displayName = 'SwitchboardIcon';

export default SwitchboardIcon;
258 changes: 258 additions & 0 deletions plugin-hrm-form/src/components/queuesView/QueueSelectionModals.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
/**
* Copyright (C) 2021-2023 Technology Matters
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/

import React from 'react';
import { Modal, Paper, FormControl, FormControlLabel } from '@material-ui/core';

import { Box, SaveAndEndButton, StyledNextStepButton, TertiaryButton, FormLabel } from '../../styles';
import { CloseButton, NonDataCallTypeDialogContainer, CloseTaskDialog } from '../callTypeButtons/styles';
import TabPressWrapper from '../TabPressWrapper';

type Queue = {
key: string;
// eslint-disable-next-line camelcase
friendly_name: string;
};

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,
}) => {
// Define custom radio button handler
const handleRadioChange = (value: string) => {
setSelectedQueue(value);
queueRef.current = value;
};

return (
<Modal open={isOpen} onClose={onClose} aria-labelledby="queue-selection-modal-title">
<Paper
style={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
maxWidth: '700px',
width: '90%',
backgroundColor: 'white',
padding: '20px',
outline: 'none',
borderRadius: '4px',
}}
>
<Box style={{ position: 'absolute', top: '10px', right: '10px' }}>
<CloseButton tabIndex={3} aria-label="CloseButton" onClick={onClose} />
</Box>

<Box style={{ marginBottom: '20px' }}>
<h2 id="queue-selection-modal-title" style={{ fontSize: '18px', fontWeight: 'bold', margin: '0' }}>
Select queue to switchboard
</h2>
</Box>

<FormControl component="fieldset" style={{ width: '100%' }}>
<Box
style={{
display: 'grid',
gridTemplateColumns: '1fr 1fr',
gap: '2px 30px',
margin: '0 10px',
padding: '10px 0',
width: '100%',
}}
>
{queues &&
[...queues]
.sort((a, b) => a.friendly_name.localeCompare(b.friendly_name))
.map(queue => (
<div
key={queue.key}
onClick={() => handleRadioChange(queue.key)}
onKeyDown={e => {
if (e.key === 'Enter' || e.key === ' ') {
handleRadioChange(queue.key);
}
}}
role="radio"
aria-checked={selectedQueue === queue.key}
tabIndex={0}
style={{
marginBottom: '10px',
display: 'flex',
alignItems: 'center',
cursor: 'pointer',
}}
>
<div
style={{
width: '16px',
height: '16px',
borderRadius: '50%',
border: '2px solid #000',
marginRight: '8px',
position: 'relative',
backgroundColor: 'white',
}}
>
{selectedQueue === queue.key && (
<div
style={{
width: '8px',
height: '8px',
borderRadius: '50%',
backgroundColor: '#000',
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
}}
/>
)}
</div>
<label
id={`queue-label-${queue.key}`}
htmlFor={`queue-radio-${queue.key}`}
style={{ cursor: 'pointer' }}
>
{queue.friendly_name}
</label>
<input
type="radio"
id={`queue-radio-${queue.key}`}
name="queue-selection"
value={queue.key}
checked={selectedQueue === queue.key}
onChange={() => handleRadioChange(queue.key)}
style={{ position: 'absolute', opacity: 0 }}
aria-labelledby={`queue-label-${queue.key}`}
/>
</div>
))}
</Box>
</FormControl>

<Box
style={{
display: 'flex',
justifyContent: 'flex-end',
marginTop: '20px',
gap: '10px',
}}
>
<TertiaryButton
type="button"
onClick={onClose}
style={{
background: '#EEEEEE',
border: 'none',
borderRadius: '3px',
}}
>
Cancel
</TertiaryButton>
<StyledNextStepButton
onClick={() => {
const currentQueue = selectedQueue || queueRef.current;
if (currentQueue) {
onSelect(currentQueue);
} else {
alert('Please select a queue first');
}
}}
>
Activate Switchboarding
</StyledNextStepButton>
</Box>
</Paper>
</Modal>
);
};

type TurnOffSwitchboardDialogProps = {
isOpen: boolean;
onClose: () => void;
onConfirm: () => void;
selectedQueue: string | null;
renderStatusText: (queueKey: string, startTime: string | null) => React.ReactNode;
switchboardingStartTime: string | null;
};

export const TurnOffSwitchboardDialog: React.FC<TurnOffSwitchboardDialogProps> = ({
isOpen,
onClose,
onConfirm,
selectedQueue,
renderStatusText,
switchboardingStartTime,
}) => {
return (
<CloseTaskDialog open={isOpen} onClose={onClose} width={500}>
<TabPressWrapper>
<NonDataCallTypeDialogContainer style={{ position: 'relative', maxWidth: '500px', padding: '20px' }}>
<Box style={{ position: 'absolute', top: '10px', right: '10px' }}>
<CloseButton tabIndex={3} aria-label="CloseButton" onClick={onClose} />
</Box>
<Box style={{ marginBottom: '20px' }}>
<h2
id="turn-off-switchboard-title"
style={{ fontSize: '18px', fontWeight: 'bold', margin: '0', maxWidth: '80%', textAlign: 'center' }}
>
Are you sure you want to turn off switchboarding?
</h2>
</Box>
<Box style={{ margin: '20px' }}>{renderStatusText(selectedQueue || '', switchboardingStartTime)}</Box>
<Box
style={{
display: 'flex',
justifyContent: 'flex-end',
marginTop: '20px',
gap: '10px',
alignSelf: 'flex-end',
}}
>
<TertiaryButton
type="button"
onClick={onClose}
style={{
background: '#EEEEEE',
border: 'none',
borderRadius: '3px',
}}
>
Cancel
</TertiaryButton>
<SaveAndEndButton onClick={onConfirm}>Turn Off Switchboarding</SaveAndEndButton>
</Box>
</NonDataCallTypeDialogContainer>
</TabPressWrapper>
</CloseTaskDialog>
);
};
Loading
Loading