-
Notifications
You must be signed in to change notification settings - Fork 71
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
Added Chat Barge to Supervisor Barge Coach Feature #521
base: main
Are you sure you want to change the base?
Changes from all commits
781af06
593eaec
a626790
ee8f679
169b565
62c917f
ea059f7
a47ede0
08260e1
2ebbe39
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
import * as React from 'react'; | ||
import * as Flex from '@twilio/flex-ui'; | ||
import { useFlexSelector, ITask, templates, StateHelper } from '@twilio/flex-ui'; | ||
import { useDispatch, useSelector } from 'react-redux'; | ||
import { Box } from '@twilio-paste/core/box'; | ||
import { Button } from '@twilio-paste/core/button'; | ||
import { Tooltip } from '@twilio-paste/core/tooltip'; | ||
import { Flex as FlexBox } from '@twilio-paste/core/flex'; | ||
|
||
import { AppState } from '../../../../types/manager'; | ||
import { reduxNamespace } from '../../../../utils/state'; | ||
import { Actions, SupervisorBargeCoachState } from '../../flex-hooks/states/SupervisorBargeCoach'; | ||
import BargeCoachService from '../../utils/serverless/BargeCoachService'; | ||
import { StringTemplates } from '../../flex-hooks/strings/BargeCoachAssist'; | ||
|
||
type SupervisorChatBargeProps = { | ||
task: ITask; | ||
}; | ||
|
||
export const SupervisorChatBargeButton = ({ task }: SupervisorChatBargeProps) => { | ||
const dispatch = useDispatch(); | ||
const [processing, setProcessing] = React.useState(false); | ||
const [isChecking, setIsChecking] = React.useState(false); | ||
const { chatBarge } = useSelector( | ||
(state: AppState) => state[reduxNamespace].supervisorBargeCoach as SupervisorBargeCoachState, | ||
); | ||
const teamViewTaskSID = useFlexSelector((state) => state?.flex?.view?.selectedTaskInSupervisorSid) || ''; | ||
const agentWorkerSID = useFlexSelector((state) => state?.flex?.supervisor?.stickyWorker?.worker?.sid) || ''; | ||
const myWorkerName = useFlexSelector((state) => state?.flex?.session?.identity) || ''; | ||
const myWorkerSid = useFlexSelector((state) => state?.flex?.worker?.worker?.sid) || ''; | ||
const conversationState = StateHelper.getConversationStateForTask(task) || null; | ||
const conversationSid = task?.attributes?.conversationSid || ''; | ||
const [chatBargeStatus, setChatBargeStatus] = React.useState(false); | ||
// Storing teamViewPath to browser cache to help if a refresh happens | ||
// will use this in the browserRefreshHelper | ||
if (teamViewTaskSID && agentWorkerSID) { | ||
localStorage.setItem('teamViewTaskSID', teamViewTaskSID); | ||
localStorage.setItem('agentWorkerSID', agentWorkerSID); | ||
} | ||
const bargeHandleClick = async () => { | ||
if (!teamViewTaskSID || processing) { | ||
return; | ||
} | ||
setProcessing(true); | ||
if (chatBarge[teamViewTaskSID]) { | ||
await BargeCoachService.removeWorkerParticipant(conversationSid, myWorkerName); | ||
const { [teamViewTaskSID]: value, ...newChatBargeState } = chatBarge; | ||
Check warning on line 47 in plugin-flex-ts-template-v2/src/feature-library/supervisor-barge-coach/custom-components/ChatBargeButton/SupervisorChatBargeButton.tsx GitHub Actions / ESLint Report Analysisplugin-flex-ts-template-v2/src/feature-library/supervisor-barge-coach/custom-components/ChatBargeButton/SupervisorChatBargeButton.tsx#L47
|
||
dispatch(Actions.setBargeCoachStatus({ chatBarge: newChatBargeState })); | ||
localStorage.setItem('chatBarge', JSON.stringify(newChatBargeState)); | ||
} else { | ||
await BargeCoachService.inviteWorkerParticipant(conversationSid, myWorkerName); | ||
const newChatBargeState = { ...chatBarge, [teamViewTaskSID]: conversationSid }; | ||
dispatch(Actions.setBargeCoachStatus({ chatBarge: newChatBargeState })); | ||
localStorage.setItem('chatBarge', JSON.stringify(newChatBargeState)); | ||
} | ||
// Because of how the monitor panel renders, we need to close it and re-open it to show | ||
// we are part of the conversation | ||
console.warn('teamViewTaskSID', teamViewTaskSID); | ||
await Flex.Actions.invokeAction('SelectTaskInSupervisor', { sid: null }); | ||
Flex.Actions.invokeAction('SelectTaskInSupervisor', { sid: teamViewTaskSID }); | ||
setProcessing(false); | ||
}; | ||
React.useEffect(() => { | ||
// If the supervisor closes or refreshes their browser, we need to check if they are still in the chat | ||
// First will check if we have it from the redux value, otherwise let's check the conversation state itself | ||
const checkSupervisorInChat = async () => { | ||
if (teamViewTaskSID in chatBarge) { | ||
setIsChecking(true); | ||
let supervisorInChat = false; | ||
if (conversationState && !conversationState.isLoadingParticipants) { | ||
supervisorInChat = conversationState.participants.has(myWorkerName); | ||
if (supervisorInChat) { | ||
setChatBargeStatus(true); | ||
} else { | ||
const { [teamViewTaskSID]: value, ...newChatBargeState } = chatBarge; | ||
Check warning on line 75 in plugin-flex-ts-template-v2/src/feature-library/supervisor-barge-coach/custom-components/ChatBargeButton/SupervisorChatBargeButton.tsx GitHub Actions / ESLint Report Analysisplugin-flex-ts-template-v2/src/feature-library/supervisor-barge-coach/custom-components/ChatBargeButton/SupervisorChatBargeButton.tsx#L75
|
||
dispatch(Actions.setBargeCoachStatus({ chatBarge: newChatBargeState })); | ||
localStorage.setItem('chatBarge', JSON.stringify(newChatBargeState)); | ||
setChatBargeStatus(false); | ||
} | ||
} | ||
setIsChecking(false); | ||
} else { | ||
setChatBargeStatus(false); | ||
} | ||
}; | ||
checkSupervisorInChat(); | ||
}, [teamViewTaskSID, chatBarge, conversationState]); | ||
|
||
React.useEffect(() => { | ||
if (chatBargeStatus && (task.status === 'wrapping' || task.status === 'completed')) { | ||
BargeCoachService.removeWorkerParticipant(conversationSid, myWorkerName); | ||
setChatBargeStatus(false); | ||
} | ||
}, [task]); | ||
|
||
const isLiveConversation = (task: ITask): boolean => | ||
task !== null && task.status !== 'completed' && task.status !== 'wrapping' && myWorkerSid !== agentWorkerSID; | ||
|
||
// Returning two options, if it's disabled due to the supervisor being assigned the task | ||
// we want a hover text explaing why, otherwise don't do any Tooltip | ||
return ( | ||
<FlexBox hAlignContent="left" vertical> | ||
{myWorkerSid === agentWorkerSID ? ( | ||
<Tooltip placement="right" text={templates[StringTemplates.TaskAssignedToYou]()}> | ||
<Box padding="space10" element="BARGE_COACH_BUTTON_BOX"> | ||
<Button | ||
variant="primary" | ||
size="small" | ||
onClick={bargeHandleClick} | ||
disabled={true} // always disabled when condition is met | ||
> | ||
{templates[StringTemplates.Join]()} | ||
</Button> | ||
</Box> | ||
</Tooltip> | ||
) : ( | ||
<Box padding="space10" element="BARGE_COACH_BUTTON_BOX"> | ||
<Button | ||
variant="primary" | ||
size="small" | ||
onClick={bargeHandleClick} | ||
disabled={processing || isChecking || !isLiveConversation(task)} | ||
> | ||
{processing | ||
? templates[StringTemplates.Joining]() | ||
: chatBarge[teamViewTaskSID] | ||
? templates[StringTemplates.Leave]() | ||
: templates[StringTemplates.Join]()} | ||
</Button> | ||
</Box> | ||
)} | ||
</FlexBox> | ||
); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would break functionality if the supervisor-barge-coach feature is removed, but not the conversation-transfer feature. This will need to be implemented a different way to not introduce a co-dependency of features.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could maybe approach it with the following
Add a config value in conversation-transfer that checks whether supervisor barge coach is enabled or not (similar example is in custom-transfer-directory with its dependency on conversation-transfer)
Use that config value here to determine whether to extract the participants count from redux or use the old method
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like this piece of state is only set from the conversation-transfer feature, and only read by the conversation-transfer feature, so we shouldn't need multiple implementations. This piece of state can probably just live within the conversation-transfer feature, but after I've looked into things some more, I don't think we need to keep this state at all!
I think we should remove
useParticipantCountEffect
and instead fetch the participant list using existing data in Redux, which seems to be up-to-date and includes supervisor participants. Its location in Redux isflex.chat.conversations[conversationSid].participants
. Using this should be a lot more efficient, as the current code makes 8 or so network requests when selecting the task.