Skip to content

Commit 0022bd5

Browse files
akulakumrsarikaShreyas281299
authored
fix(user-state): fix-rona-state (#511)
Co-authored-by: rsarika <95286093+rsarika@users.noreply.github.com> Co-authored-by: Shreyas Sharma <72344404+Shreyas281299@users.noreply.github.com>
1 parent fdf8782 commit 0022bd5

7 files changed

Lines changed: 162 additions & 52 deletions

File tree

packages/contact-center/cc-components/src/components/UserState/user-state.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React, {useMemo} from 'react';
22

33
import {AgentUserState, UserStateComponentsProps} from './user-state.types';
44
import {formatTime} from '../../utils';
5-
5+
import {ERROR_TRIGGERING_IDLE_CODES} from '@webex/cc-store';
66
import './user-state.scss';
77
import {SelectNext, Text} from '@momentum-ui/react-collaboration';
88
import {Item} from '@react-stately/collections';
@@ -32,7 +32,7 @@ const UserStateComponent: React.FunctionComponent<UserStateComponentsProps> = (p
3232

3333
const previousSelectableState = useMemo(() => getPreviousSelectableState(idleCodes), [idleCodes]);
3434
const selectedKey = getSelectedKey(customState, currentState, idleCodes);
35-
const items = buildDropdownItems(customState, idleCodes);
35+
const items = buildDropdownItems(customState, idleCodes, currentState);
3636
const sortedItems = sortDropdownItems(items);
3737

3838
return (
@@ -50,8 +50,7 @@ const UserStateComponent: React.FunctionComponent<UserStateComponentsProps> = (p
5050
data-testid="state-select"
5151
>
5252
{(item) => {
53-
const isRonaOrEngaged = [AgentUserState.RONA, AgentUserState.Engaged].includes(
54-
//@ts-expect-error To be fixed in SDK - https://jira-eng-sjc12.cisco.com/jira/browse/CAI-6762
53+
const isRonaOrEngaged = [...Object.values(ERROR_TRIGGERING_IDLE_CODES), AgentUserState.Engaged].includes(
5554
idleCodes.find((code) => code.id === currentState)?.name || ''
5655
);
5756
const shouldHighlight = currentState === item.id || (isRonaOrEngaged && item.id === previousSelectableState);

packages/contact-center/cc-components/src/components/UserState/user-state.utils.ts

Lines changed: 81 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {ICustomState, IdleCode} from '@webex/cc-store';
1+
import {ICustomState, IdleCode, ERROR_TRIGGERING_IDLE_CODES} from '@webex/cc-store';
22
import {AgentUserState} from './user-state.types';
33
import {userStateLabels} from './constant';
44

@@ -13,7 +13,7 @@ export const getDropdownClass = (customState: ICustomState, currentState: string
1313
return '';
1414
}
1515
for (const item of idleCodes) {
16-
if (item.id === currentState && item.name === AgentUserState.RONA) {
16+
if (item.id === currentState && item.name === ERROR_TRIGGERING_IDLE_CODES.RONA) {
1717
return 'rona';
1818
}
1919
}
@@ -23,12 +23,26 @@ export const getDropdownClass = (customState: ICustomState, currentState: string
2323
/**
2424
* Gets the icon style configuration for a given item
2525
*/
26-
export const getIconStyle = (item: {id: string; name: string}): {class: string; iconName: string} => {
26+
export const getIconStyle = (item: {
27+
id: string;
28+
name: string;
29+
developerName?: string;
30+
}): {class: string; iconName: string} => {
31+
if (item.developerName) {
32+
return {class: 'custom', iconName: 'busy-presence-light'};
33+
}
34+
2735
switch (item.name) {
2836
case AgentUserState.Available:
29-
return {class: 'available', iconName: 'recents-presence-filled'};
30-
case AgentUserState.RONA:
31-
return {class: 'rona', iconName: 'warning-filled'};
37+
return {class: 'available', iconName: 'active-presence-small-filled'};
38+
case ERROR_TRIGGERING_IDLE_CODES.RONA:
39+
return {class: 'rona', iconName: 'dnd-presence-filled'};
40+
case ERROR_TRIGGERING_IDLE_CODES.INVALID_NUMBER:
41+
case ERROR_TRIGGERING_IDLE_CODES.UNAVAILABLE:
42+
case ERROR_TRIGGERING_IDLE_CODES.DECLINED:
43+
case ERROR_TRIGGERING_IDLE_CODES.BUSY:
44+
case ERROR_TRIGGERING_IDLE_CODES.CHANNEL_FAILURE:
45+
return {class: 'idle', iconName: 'dnd-presence-filled'};
3246
default:
3347
return {class: 'idle', iconName: 'recents-presence-filled'};
3448
}
@@ -57,32 +71,36 @@ export const handleSelectionChange = (
5771
setAgentStatus: (auxCodeId: string) => void,
5872
logger
5973
): void => {
60-
if (key !== currentState) {
61-
logger?.info(`CC-Widgets: UserState: state changed to: ${key}`, {
74+
// Remove 'hide-' prefix if present (for RONA states)
75+
const cleanKey = key.startsWith('hide-') ? key.substring(5) : key;
76+
77+
if (cleanKey !== currentState) {
78+
logger?.info(`CC-Widgets: UserState: state changed to: ${cleanKey}`, {
6279
module: 'cc-components#user-state.tsx',
6380
method: 'handleSelectionChange',
6481
});
65-
setAgentStatus(key);
82+
setAgentStatus(cleanKey);
6683
}
6784
};
6885

6986
/**
7087
* Sorts dropdown items with Available first, then others
7188
*/
7289
export const sortDropdownItems = (items: Array<{id: string; name: string}>): Array<{id: string; name: string}> => {
73-
return [...items].sort((a, b) => {
74-
if (a.name === AgentUserState.Available) return -1;
75-
if (b.name === AgentUserState.Available) return 1;
76-
return a.name.localeCompare(b.name);
77-
});
90+
// Keep Available at the top, sort the rest alphabetically
91+
return [
92+
...items.filter((item) => item.name === AgentUserState.Available),
93+
...items.filter((item) => item.name !== AgentUserState.Available).sort((a, b) => a.name.localeCompare(b.name)),
94+
];
7895
};
7996

8097
/**
8198
* Gets the previous selectable state (first non-RONA/Engaged state)
8299
*/
83100
export const getPreviousSelectableState = (idleCodes: IdleCode[]): string => {
84101
const selectableState = idleCodes.find(
85-
(code) => ![AgentUserState.RONA, AgentUserState.Engaged].includes(code.name as AgentUserState)
102+
(code) =>
103+
![...Object.values(ERROR_TRIGGERING_IDLE_CODES), AgentUserState.Engaged].includes(code.name as AgentUserState)
86104
);
87105
return selectableState?.id || '0';
88106
};
@@ -92,39 +110,72 @@ export const getPreviousSelectableState = (idleCodes: IdleCode[]): string => {
92110
*/
93111
export const getSelectedKey = (customState: ICustomState, currentState: string, idleCodes: IdleCode[]): string => {
94112
if (customState && 'developerName' in customState) {
95-
return `custom-${customState.developerName}`;
113+
return `hide-${customState.developerName}`;
96114
}
97115

116+
// Check if current state exists in idleCodes first
98117
const currentIdleCode = idleCodes.find((code) => code.id === currentState);
99-
const isRonaOrEngaged = [AgentUserState.RONA, AgentUserState.Engaged].includes(
100-
currentIdleCode?.name as AgentUserState
101-
);
102118

103-
if (isRonaOrEngaged) {
119+
// If currentIdleCode is not found, return currentState as-is
120+
if (!currentIdleCode) {
121+
return currentState;
122+
}
123+
124+
// Check if current state is an error-triggering idle code (like RONA)
125+
if (Object.values(ERROR_TRIGGERING_IDLE_CODES).includes(currentIdleCode.name)) {
126+
return `hide-${currentState}`;
127+
}
128+
129+
// Check if current state is Engaged
130+
const isEngaged = currentIdleCode.name === AgentUserState.Engaged;
131+
if (isEngaged) {
104132
return getPreviousSelectableState(idleCodes);
105133
}
106134

107135
return currentState;
108136
};
109137

110138
/**
111-
* Builds the dropdown items including custom state if present
139+
* Builds the dropdown items including custom state and conditional RONA
112140
*/
113141
export const buildDropdownItems = (
114142
customState: ICustomState,
115-
idleCodes: IdleCode[]
116-
): Array<{id: string; name: string}> => {
117-
const items = idleCodes
118-
.filter((code) => ![AgentUserState.RONA, AgentUserState.Engaged].includes(code.name as AgentUserState))
119-
.map((code) => ({
120-
id: code.id,
121-
name: code.name,
122-
}));
143+
idleCodes: IdleCode[],
144+
currentState: string
145+
): Array<{id: string; name: string; developerName?: string}> => {
146+
const items: Array<{id: string; name: string; developerName?: string}> = [];
123147

148+
// Add custom state if present
124149
if (customState && 'developerName' in customState) {
125150
items.push({
126-
id: `custom-${customState.developerName}`,
127151
name: customState.name,
152+
id: `hide-${customState.developerName}`,
153+
developerName: customState.developerName,
154+
});
155+
}
156+
157+
// Add regular idle codes, but handle RONA specially
158+
for (const code of idleCodes) {
159+
// Skip Engaged states entirely
160+
if (code.name === AgentUserState.Engaged) {
161+
continue;
162+
}
163+
164+
// For RONA: only include if it's the current state
165+
if (Object.values(ERROR_TRIGGERING_IDLE_CODES).includes(code.name)) {
166+
if (code.id === currentState) {
167+
items.push({
168+
...code,
169+
id: `hide-${code.id}`, // Use hide- prefix for RONA
170+
});
171+
}
172+
continue;
173+
}
174+
175+
// Add all other states normally
176+
items.push({
177+
id: code.id,
178+
name: code.name,
128179
});
129180
}
130181

packages/contact-center/cc-components/tests/components/UserState/user-state.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,11 @@ describe('UserStateComponent', () => {
172172
render(<UserStateComponent {...defaultProps} />);
173173
});
174174

175-
expect(buildDropdownItemsSpy).toHaveBeenCalledWith(defaultProps.customState, defaultProps.idleCodes);
175+
expect(buildDropdownItemsSpy).toHaveBeenCalledWith(
176+
defaultProps.customState,
177+
defaultProps.idleCodes,
178+
defaultProps.currentState
179+
);
176180
});
177181

178182
it('should call sortDropdownItems with items from buildDropdownItems', async () => {
@@ -242,7 +246,11 @@ describe('UserStateComponent', () => {
242246
});
243247

244248
expect(getSelectedKeySpy).toHaveBeenCalledWith(customState, defaultProps.currentState, defaultProps.idleCodes);
245-
expect(buildDropdownItemsSpy).toHaveBeenCalledWith(customState, defaultProps.idleCodes);
249+
expect(buildDropdownItemsSpy).toHaveBeenCalledWith(
250+
customState,
251+
defaultProps.idleCodes,
252+
defaultProps.currentState
253+
);
246254
});
247255
});
248256

packages/contact-center/cc-components/tests/components/UserState/user-state.utils.tsx

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ describe('UserState Utils', () => {
6565
const result = getIconStyle(item);
6666
expect(result).toEqual({
6767
class: 'available',
68-
iconName: 'recents-presence-filled',
68+
iconName: 'active-presence-small-filled',
6969
});
7070
});
7171

@@ -74,7 +74,7 @@ describe('UserState Utils', () => {
7474
const result = getIconStyle(item);
7575
expect(result).toEqual({
7676
class: 'rona',
77-
iconName: 'warning-filled',
77+
iconName: 'dnd-presence-filled',
7878
});
7979
});
8080

@@ -86,6 +86,15 @@ describe('UserState Utils', () => {
8686
iconName: 'recents-presence-filled',
8787
});
8888
});
89+
90+
it('should return custom style for items with developerName', () => {
91+
const item = {id: '1', name: 'Custom State', developerName: 'CUSTOM'};
92+
const result = getIconStyle(item);
93+
expect(result).toEqual({
94+
class: 'custom',
95+
iconName: 'busy-presence-light',
96+
});
97+
});
8998
});
9099

91100
describe('getTooltipText', () => {
@@ -224,22 +233,22 @@ describe('UserState Utils', () => {
224233
it('should return custom key when customState is present', () => {
225234
const customState = {name: 'Custom State', developerName: 'CUSTOM'};
226235
const result = getSelectedKey(customState, '1', mockIdleCodes);
227-
expect(result).toBe('custom-CUSTOM');
236+
expect(result).toBe('hide-CUSTOM');
228237
});
229238

230239
it('should return currentState for normal states', () => {
231240
const result = getSelectedKey(null, '1', mockIdleCodes);
232241
expect(result).toBe('1');
233242
});
234243

235-
it('should return previous selectable state for RONA', () => {
244+
it('should return hide-prefixed key for RONA when it is current state', () => {
236245
const result = getSelectedKey(null, '3', mockIdleCodes);
237-
expect(result).toBe('0'); // Available is the first selectable state
246+
expect(result).toBe('hide-3');
238247
});
239248

240249
it('should return previous selectable state for ENGAGED', () => {
241250
const result = getSelectedKey(null, '4', mockIdleCodes);
242-
expect(result).toBe('0'); // Available is the first selectable state
251+
expect(result).toBe('0');
243252
});
244253

245254
it('should handle missing currentState in idleCodes', () => {
@@ -250,7 +259,7 @@ describe('UserState Utils', () => {
250259

251260
describe('buildDropdownItems', () => {
252261
it('should filter out RONA and ENGAGED states', () => {
253-
const result = buildDropdownItems(null, mockIdleCodes);
262+
const result = buildDropdownItems(null, mockIdleCodes, '1');
254263

255264
expect(result).toEqual([
256265
{id: '0', name: 'Available'},
@@ -261,26 +270,26 @@ describe('UserState Utils', () => {
261270

262271
it('should include custom state when present', () => {
263272
const customState = {name: 'Custom State', developerName: 'CUSTOM'};
264-
const result = buildDropdownItems(customState, mockIdleCodes);
273+
const result = buildDropdownItems(customState, mockIdleCodes, '1');
265274

266275
expect(result).toEqual([
276+
{id: 'hide-CUSTOM', name: 'Custom State', developerName: 'CUSTOM'},
267277
{id: '0', name: 'Available'},
268278
{id: '1', name: 'Break'},
269279
{id: '2', name: 'Training'},
270-
{id: 'custom-CUSTOM', name: 'Custom State'},
271280
]);
272281
});
273282

274283
it('should handle empty idleCodes array', () => {
275-
const result = buildDropdownItems(null, []);
284+
const result = buildDropdownItems(null, [], '1');
276285
expect(result).toEqual([]);
277286
});
278287

279288
it('should handle empty idleCodes array with custom state', () => {
280289
const customState = {name: 'Custom State', developerName: 'CUSTOM'};
281-
const result = buildDropdownItems(customState, []);
290+
const result = buildDropdownItems(customState, [], '1');
282291

283-
expect(result).toEqual([{id: 'custom-CUSTOM', name: 'Custom State'}]);
292+
expect(result).toEqual([{id: 'hide-CUSTOM', name: 'Custom State', developerName: 'CUSTOM'}]);
284293
});
285294

286295
it('should handle idleCodes with only RONA and ENGAGED', () => {
@@ -289,8 +298,30 @@ describe('UserState Utils', () => {
289298
{id: '4', name: 'ENGAGED', isSystem: true, isDefault: false},
290299
];
291300

292-
const result = buildDropdownItems(null, ronaEngagedOnly);
301+
const result = buildDropdownItems(null, ronaEngagedOnly, '1');
293302
expect(result).toEqual([]);
294303
});
304+
305+
it('should include RONA when it is the current state', () => {
306+
const result = buildDropdownItems(null, mockIdleCodes, '3');
307+
308+
expect(result).toEqual([
309+
{id: '0', name: 'Available'},
310+
{id: '1', name: 'Break'},
311+
{id: '2', name: 'Training'},
312+
{id: 'hide-3', name: 'RONA', isSystem: true, isDefault: false},
313+
]);
314+
});
315+
316+
it('should use correct prefix for custom state', () => {
317+
const customState = {name: 'Custom State', developerName: 'CUSTOM'};
318+
const result = buildDropdownItems(customState, mockIdleCodes, '1');
319+
320+
expect(result[0]).toEqual({
321+
id: 'hide-CUSTOM',
322+
name: 'Custom State',
323+
developerName: 'CUSTOM',
324+
});
325+
});
295326
});
296327
});

packages/contact-center/store/src/store.types.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,15 @@ const LoginOptions: {[key: string]: string} = {
220220
[DESKTOP]: 'Desktop',
221221
};
222222

223+
const ERROR_TRIGGERING_IDLE_CODES = {
224+
INVALID_NUMBER: 'Invalid_Number',
225+
UNAVAILABLE: 'Agent_Unavailable',
226+
DECLINED: 'Agent_Declined',
227+
BUSY: 'Agent_Busy',
228+
CHANNEL_FAILURE: 'Channel_Failure',
229+
RONA: 'RONA',
230+
};
231+
223232
export type {
224233
IContactCenter,
225234
ITask,
@@ -241,4 +250,14 @@ export type {
241250
IWebex,
242251
};
243252

244-
export {CC_EVENTS, TASK_EVENTS, ENGAGED_LABEL, ENGAGED_USERNAME, DIALNUMBER, EXTENSION, DESKTOP, LoginOptions};
253+
export {
254+
CC_EVENTS,
255+
TASK_EVENTS,
256+
ENGAGED_LABEL,
257+
ENGAGED_USERNAME,
258+
DIALNUMBER,
259+
EXTENSION,
260+
DESKTOP,
261+
LoginOptions,
262+
ERROR_TRIGGERING_IDLE_CODES,
263+
};

0 commit comments

Comments
 (0)