-
Notifications
You must be signed in to change notification settings - Fork 46
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
feat(widgets): added-blind-transfer #422
Changes from 5 commits
1ec0594
51bdf13
d52718f
3eb97f7
378dcc2
6951fdc
8139207
c9550c7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,4 @@ | ||
import {AgentLogin, IContactCenter, Profile, Team, LogContext} from '@webex/plugin-cc'; | ||
import {ITask} from '@webex/plugin-cc'; | ||
import {ITask, AgentLogin, IContactCenter, Profile, Team, LogContext, BuddyDetails} from '@webex/plugin-cc'; | ||
|
||
type ILogger = { | ||
log: (message: string, context?: LogContext) => void; | ||
|
@@ -42,6 +41,7 @@ interface IStore { | |
isAgentLoggedIn: boolean; | ||
deviceType: string; | ||
wrapupRequired: boolean; | ||
isAgentTransferred: boolean; | ||
currentState: string; | ||
lastStateChangeTimestamp?: number; | ||
lastIdleCodeChangeTimestamp?: number; | ||
|
@@ -67,6 +67,7 @@ interface IStoreWrapper extends IStore { | |
setIsAgentLoggedIn(value: boolean): void; | ||
setWrapupCodes(wrapupCodes: IWrapupCode[]): void; | ||
setState(state: IdleCode | ICustomState): void; | ||
setIsAgentTransferred(value: boolean): void; | ||
} | ||
|
||
interface IWrapupCode { | ||
|
@@ -93,6 +94,7 @@ enum TASK_EVENTS { | |
CONTACT_RECORDING_PAUSED = 'ContactRecordingPaused', | ||
CONTACT_RECORDING_RESUMED = 'ContactRecordingResumed', | ||
AGENT_WRAPPEDUP = 'AgentWrappedUp', | ||
AGENT_BLIND_TRANSFERRED = 'AgentBlindTransferred', | ||
} // TODO: remove this once cc sdk exports this enum | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As discussed, we will update above state on this event. |
||
|
||
// Events that are received on the contact center SDK | ||
|
@@ -130,6 +132,7 @@ export type { | |
IWrapupCode, | ||
IStoreWrapper, | ||
ICustomState, | ||
BuddyDetails, | ||
}; | ||
|
||
export {CC_EVENTS, TASK_EVENTS}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,6 +10,7 @@ import { | |
IdleCode, | ||
IContactCenter, | ||
ITask, | ||
BuddyDetails, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We need this for the budyd agents details. |
||
} from './store.types'; | ||
import Store from './store'; | ||
import {runInAction} from 'mobx'; | ||
|
@@ -84,6 +85,10 @@ class StoreWrapper implements IStoreWrapper { | |
return this.store.showMultipleLoginAlert; | ||
} | ||
|
||
get isAgentTransferred() { | ||
return this.store.isAgentTransferred; | ||
} | ||
|
||
get currentTheme() { | ||
return this.store.currentTheme; | ||
} | ||
|
@@ -131,6 +136,10 @@ class StoreWrapper implements IStoreWrapper { | |
this.store.wrapupRequired = value; | ||
}; | ||
|
||
setIsAgentTransferred = (value: boolean): void => { | ||
this.store.isAgentTransferred = value; | ||
}; | ||
|
||
setCurrentTask = (task: ITask): void => { | ||
runInAction(() => { | ||
this.store.currentTask = task; | ||
|
@@ -234,14 +243,17 @@ class StoreWrapper implements IStoreWrapper { | |
}); | ||
}; | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
handleAgentBlindTransferred = (event) => { | ||
this.setIsAgentTransferred(true); | ||
}; | ||
|
||
handleTaskEnd = (event) => { | ||
// If the call is ended by agent we get the task object in event.data | ||
// If the call is ended by customer we get the task object directly | ||
|
||
const task = event.data ? event.data : event; | ||
// TODO -- SDK needs to send only 1 event on end : https://jira-eng-gpk2.cisco.com/jira/browse/SPARK-615785 | ||
|
||
if (task.interaction.state === 'connected') { | ||
if (task.interaction.state === 'connected' || this.store.isAgentTransferred) { | ||
this.setWrapupRequired(true); | ||
return; | ||
} else if (task.interaction.state !== 'connected' && this.store.wrapupRequired !== true) { | ||
|
@@ -264,6 +276,7 @@ class StoreWrapper implements IStoreWrapper { | |
handleTaskWrapUp = (event) => { | ||
const task = event; | ||
this.setWrapupRequired(false); | ||
this.setIsAgentTransferred(false); | ||
this.handleTaskRemove(task.interactionId); | ||
}; | ||
|
||
|
@@ -286,6 +299,9 @@ class StoreWrapper implements IStoreWrapper { | |
|
||
task.on(TASK_EVENTS.AGENT_WRAPPEDUP, this.handleTaskWrapUp); | ||
|
||
// Listen for AGENT_BLIND_TRANSFERRED event | ||
task.on(TASK_EVENTS.AGENT_BLIND_TRANSFERRED, () => this.handleAgentBlindTransferred(task)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why are we listening for this event? I don't see SDK emitting any event for trasnfer. Complete dependency is there on promise resolve for transfer There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Edit: |
||
|
||
this.setIncomingTask(task); | ||
this.setTaskList([...this.store.taskList, task]); | ||
}; | ||
|
@@ -319,6 +335,9 @@ class StoreWrapper implements IStoreWrapper { | |
|
||
task.on(TASK_EVENTS.AGENT_WRAPPEDUP, this.handleTaskWrapUp); | ||
|
||
// Listen for AGENT_BLIND_TRANSFERRED event | ||
task.on(TASK_EVENTS.AGENT_BLIND_TRANSFERRED, () => this.handleAgentBlindTransferred(task)); | ||
|
||
if (!this.store.taskList.some((t) => t.data.interactionId === task.data.interactionId)) { | ||
this.setTaskList([...this.store.taskList, task]); | ||
} | ||
|
@@ -418,6 +437,22 @@ class StoreWrapper implements IStoreWrapper { | |
}); | ||
}); | ||
}; | ||
|
||
getBuddyAgents = async (): Promise<Array<BuddyDetails>> => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Method so we can extract buddy agents - queues will also be similar to this. |
||
try { | ||
const response = await this.store.cc.getBuddyAgents({ | ||
mediaType: 'telephony', | ||
state: 'Available', | ||
}); | ||
return response?.data?.agentList || []; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we have ? after response? In case of success, response will always be there and have data right. And even agentList is mandatory field but it can be empty array sometime. |
||
} catch (error) { | ||
this.store.logger.error(`Error fetching buddy agents: ${error}`, { | ||
module: 'cc-store#storeEventsWrapper.ts', | ||
method: 'getBuddyAgents', | ||
}); | ||
return []; | ||
} | ||
}; | ||
} | ||
|
||
// Create and export a single instance of the wrapper | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import React from 'react'; | ||
import {ListItemBase, ListItemBaseSection, AvatarNext, Text, ButtonCircle} from '@momentum-ui/react-collaboration'; | ||
import {Icon} from '@momentum-design/components/dist/react'; | ||
import classnames from 'classnames'; | ||
|
||
export interface CallControlListItemPresentationalProps { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This entire class is for the custom list item, right now all it consists of is an avatar, title, subtitle and a button which are all highly customisable. |
||
title: string; | ||
subtitle?: string; | ||
buttonIcon: string; | ||
onButtonPress: () => void; | ||
className?: string; | ||
} | ||
|
||
const CallControlListItemPresentational: React.FC<CallControlListItemPresentationalProps> = (props) => { | ||
const {title, subtitle, buttonIcon, onButtonPress, className} = props; | ||
const initials = title | ||
.split(' ') | ||
.map((word) => word[0]) | ||
.join('') | ||
.slice(0, 2) | ||
.toUpperCase(); | ||
|
||
return ( | ||
<ListItemBase className={classnames('call-control-list-item', className)} size={50} isPadded aria-label={title}> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Size is 50 by default. |
||
<ListItemBaseSection position="start"> | ||
<AvatarNext size={32} initials={initials} title={title} /> | ||
</ListItemBaseSection> | ||
<ListItemBaseSection | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Have used sections here as that is what the code on the web client was using. |
||
position="middle" | ||
style={{ | ||
flex: 1, | ||
display: 'flex', | ||
flexDirection: 'column', | ||
marginLeft: '8px', | ||
minWidth: 0, | ||
overflow: 'hidden', | ||
}} | ||
> | ||
<Text tagName="p" type="body-primary" style={{margin: 0, lineHeight: '1.2'}}> | ||
{title} | ||
</Text> | ||
{subtitle && ( | ||
<Text tagName="p" type="body-secondary" style={{margin: 0, lineHeight: '1.2'}}> | ||
{subtitle} | ||
</Text> | ||
)} | ||
</ListItemBaseSection> | ||
<ListItemBaseSection position="end"> | ||
<div className="hover-button"> | ||
<ButtonCircle onPress={onButtonPress} size={28} color="join"> | ||
<Icon name={buttonIcon} /> | ||
</ButtonCircle> | ||
</div> | ||
</ListItemBaseSection> | ||
</ListItemBase> | ||
); | ||
}; | ||
|
||
export default CallControlListItemPresentational; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import React, {useState} from 'react'; | ||
import {Text, TabListNext, TabNext, ListNext} from '@momentum-ui/react-collaboration'; | ||
import CallControlListItemPresentational from './call-control-list-item.presentational'; | ||
|
||
export interface CallControlPopoverPresentationalProps { | ||
heading: string; | ||
buttonIcon: string; | ||
buddyAgents: Array<{agentId: string; agentName: string; dn: string}>; | ||
onAgentSelect: (agentId: string) => void; | ||
} | ||
|
||
const CallControlPopoverPresentational: React.FC<CallControlPopoverPresentationalProps> = ({ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is for the internal list inside the popover (ie heading, tab list and the list of agents) |
||
heading, | ||
buttonIcon, | ||
buddyAgents, | ||
onAgentSelect, | ||
}) => { | ||
const [selectedTab, setSelectedTab] = useState('Agents'); | ||
const filteredAgents = buddyAgents; | ||
|
||
return ( | ||
<div className="agent-popover-content"> | ||
<Text tagName="h3" className="agent-popover-title" type="body-large-bold" style={{margin: '0 0 0 0'}}> | ||
{heading} | ||
</Text> | ||
<TabListNext | ||
aria-label="Tabs" | ||
className="agent-tablist" | ||
hasBackground={false} | ||
style={{marginTop: '0'}} | ||
onTabSelection={(key) => setSelectedTab(key as string)} | ||
> | ||
<TabNext key="Agents" className="agent-tab" active={selectedTab === 'Agents'}> | ||
Agents | ||
</TabNext> | ||
</TabListNext> | ||
<ListNext listSize={filteredAgents.length} className="agent-agent-list"> | ||
{filteredAgents.map((agent) => ( | ||
<div | ||
key={agent.agentId} | ||
onMouseDown={(e) => e.stopPropagation()} | ||
style={{cursor: 'pointer', pointerEvents: 'auto'}} | ||
> | ||
<CallControlListItemPresentational | ||
title={agent.agentName} | ||
buttonIcon={buttonIcon} | ||
onButtonPress={() => onAgentSelect(agent.agentId)} | ||
/> | ||
</div> | ||
))} | ||
</ListNext> | ||
{filteredAgents.length === 0 && ( | ||
<Text tagName="small" type="body-secondary"> | ||
No agents found | ||
</Text> | ||
)} | ||
</div> | ||
); | ||
}; | ||
|
||
export default CallControlPopoverPresentational; |
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.
State so we can check when the agent has been transferred.