Skip to content

Commit cf761a4

Browse files
authored
Merge pull request #3725 from techmatters/gian_CHI-3475
feat: bulk skills - assign/unassign flow [CHI-3475]
2 parents 002ffea + b27a68e commit cf761a4

File tree

6 files changed

+204
-67
lines changed

6 files changed

+204
-67
lines changed

lambdas/account-scoped/src/router.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import { AccountScopedRoute, FunctionRoute, HttpRequest } from './httpTypes';
1919
import { validateRequestMethod } from './validation/method';
2020
import { isAccountSID } from '@tech-matters/twilio-types';
2121
import { handleTaskRouterEvent } from './taskrouter';
22-
import { updateWorkersSkills } from './taskrouter/updateWorkersSkills';
22+
import { handleUpdateWorkersSkills } from './taskrouter/updateWorkersSkills';
2323
import { handleGetProfileFlagsForIdentifier } from './hrm/getProfileFlagsForIdentifier';
2424
import { handleToggleSwitchboardQueue } from './hrm/toggleSwitchboardQueue';
2525
import {
@@ -119,7 +119,7 @@ const ROUTES: Record<string, FunctionRoute> = {
119119
},
120120
updateWorkersSkills: {
121121
requestPipeline: [validateFlexTokenRequest({ tokenMode: 'supervisor' })],
122-
handler: updateWorkersSkills,
122+
handler: handleUpdateWorkersSkills,
123123
},
124124
};
125125

lambdas/account-scoped/src/taskrouter/updateWorkersSkills.ts

Lines changed: 171 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -22,74 +22,176 @@ import {
2222
getTwilioWorkspaceSid,
2323
} from '@tech-matters/twilio-configuration';
2424

25-
const validOperations = ['enable', 'disable'] as const;
25+
const validOperations = ['enable', 'disable', 'assign', 'unassign'] as const;
2626
type ValidOperations = (typeof validOperations)[number];
2727

2828
const moveElementsBetweenArrays = ({
2929
from,
3030
to,
3131
elements,
3232
}: {
33-
from: Array<string>;
34-
to: Array<string>;
35-
elements: Array<string>;
33+
from: string[];
34+
to: string[];
35+
elements: string[];
3636
}) => {
3737
const updatedFrom = from.filter(e => !elements.includes(e));
3838
const updatedTo = Array.from(new Set([...to, ...elements]));
3939

4040
return { updatedFrom, updatedTo };
4141
};
4242

43+
const mergeAttributes = ({
44+
attributes,
45+
enabledSkills,
46+
disabledSkills,
47+
}: {
48+
attributes: { [k: string]: any };
49+
enabledSkills: string[];
50+
disabledSkills: string[];
51+
}) => {
52+
return {
53+
...attributes,
54+
routing: {
55+
...attributes?.routing,
56+
skills: enabledSkills,
57+
},
58+
disabled_skills: {
59+
...attributes.disabled_skills,
60+
skills: disabledSkills,
61+
},
62+
};
63+
};
64+
65+
const setSkillsEnable = ({
66+
attributes,
67+
enabledSkills,
68+
disabledSkills,
69+
skills,
70+
}: {
71+
attributes: { [k: string]: any };
72+
enabledSkills: string[];
73+
disabledSkills: string[];
74+
skills: string[];
75+
}) => {
76+
const { updatedFrom, updatedTo } = moveElementsBetweenArrays({
77+
from: disabledSkills,
78+
to: enabledSkills,
79+
elements: skills,
80+
});
81+
82+
return mergeAttributes({
83+
attributes,
84+
enabledSkills: updatedTo,
85+
disabledSkills: updatedFrom,
86+
});
87+
};
88+
89+
const setSkillsDisable = ({
90+
attributes,
91+
enabledSkills,
92+
disabledSkills,
93+
skills,
94+
}: {
95+
attributes: { [k: string]: any };
96+
enabledSkills: string[];
97+
disabledSkills: string[];
98+
skills: string[];
99+
}) => {
100+
const { updatedFrom, updatedTo } = moveElementsBetweenArrays({
101+
from: enabledSkills,
102+
to: disabledSkills,
103+
elements: skills,
104+
});
105+
106+
return mergeAttributes({
107+
attributes,
108+
enabledSkills: updatedFrom,
109+
disabledSkills: updatedTo,
110+
});
111+
};
112+
113+
const setSkillsAssign = ({
114+
attributes,
115+
enabledSkills,
116+
disabledSkills,
117+
skills,
118+
}: {
119+
attributes: { [k: string]: any };
120+
enabledSkills: string[];
121+
disabledSkills: string[];
122+
skills: string[];
123+
}) => {
124+
const assignedSkills = new Set([...enabledSkills, ...disabledSkills]);
125+
126+
const updatedEnabledSkills = skills.reduce((accum, skill) => {
127+
if (assignedSkills.has(skill)) {
128+
return accum;
129+
}
130+
131+
return [...accum, skill];
132+
}, enabledSkills);
133+
134+
return mergeAttributes({
135+
attributes,
136+
enabledSkills: updatedEnabledSkills,
137+
disabledSkills,
138+
});
139+
};
140+
141+
const setSkillsUnassign = ({
142+
attributes,
143+
enabledSkills,
144+
disabledSkills,
145+
skills,
146+
}: {
147+
attributes: { [k: string]: any };
148+
enabledSkills: string[];
149+
disabledSkills: string[];
150+
skills: string[];
151+
}) => {
152+
const updatedEnabledSkills = enabledSkills.filter(s => !skills.includes(s));
153+
const updatedDisabledSkills = disabledSkills.filter(s => !skills.includes(s));
154+
155+
return mergeAttributes({
156+
attributes,
157+
enabledSkills: updatedEnabledSkills,
158+
disabledSkills: updatedDisabledSkills,
159+
});
160+
};
161+
43162
const updateSkillsOperation = ({
44163
attributes,
45164
operation,
46165
skills,
47166
}: {
48-
attributes: any;
49-
skills: Array<string>;
167+
attributes: { [k: string]: any };
168+
skills: string[];
50169
operation: ValidOperations;
51170
}) => {
52171
const enabledSkills = attributes?.routing?.skills || [];
53172
const disabledSkills = attributes?.disabled_skills?.skills || [];
173+
const params = {
174+
attributes,
175+
enabledSkills,
176+
disabledSkills,
177+
skills,
178+
};
54179

55-
if (operation === 'enable') {
56-
const { updatedFrom, updatedTo } = moveElementsBetweenArrays({
57-
from: disabledSkills,
58-
to: enabledSkills,
59-
elements: skills,
60-
});
61-
62-
return {
63-
...attributes,
64-
routing: {
65-
...attributes?.routing,
66-
skills: updatedTo,
67-
},
68-
disabled_skills: {
69-
...attributes.disabled_skills,
70-
skills: updatedFrom,
71-
},
72-
};
73-
}
74-
75-
if (operation === 'disable') {
76-
const { updatedFrom, updatedTo } = moveElementsBetweenArrays({
77-
from: enabledSkills,
78-
to: disabledSkills,
79-
elements: skills,
80-
});
81-
82-
return {
83-
...attributes,
84-
routing: {
85-
...attributes?.routing,
86-
skills: updatedFrom,
87-
},
88-
disabled_skills: {
89-
...attributes.disabled_skills,
90-
skills: updatedTo,
91-
},
92-
};
180+
switch (operation) {
181+
case 'enable': {
182+
return setSkillsEnable(params);
183+
}
184+
case 'disable': {
185+
return setSkillsDisable(params);
186+
}
187+
case 'assign': {
188+
return setSkillsAssign(params);
189+
}
190+
case 'unassign': {
191+
return setSkillsUnassign(params);
192+
}
193+
default:
194+
return attributes;
93195
}
94196
};
95197

@@ -131,28 +233,35 @@ const updateWorkerSkills = async ({
131233
}
132234
};
133235

134-
export const updateWorkersSkills: AccountScopedHandler = async ({ body }, accountSid) => {
135-
const { workers, skills, operation } = body;
236+
export const handleUpdateWorkersSkills: AccountScopedHandler = async (
237+
{ body },
238+
accountSid,
239+
) => {
240+
try {
241+
const { workers, skills, operation } = body;
136242

137-
if (!workers || !Array.isArray(workers)) return newMissingParameterResult('workers');
138-
if (!skills || !Array.isArray(skills)) return newMissingParameterResult('skills');
139-
if (!operation || !validOperations.includes(operation))
140-
return newMissingParameterResult('operation');
243+
if (!workers || !Array.isArray(workers)) return newMissingParameterResult('workers');
244+
if (!skills || !Array.isArray(skills)) return newMissingParameterResult('skills');
245+
if (!operation || !validOperations.includes(operation))
246+
return newMissingParameterResult('operation');
141247

142-
const workspaceSid = await getTwilioWorkspaceSid(accountSid);
248+
const workspaceSid = await getTwilioWorkspaceSid(accountSid);
143249

144-
const client = await getTwilioClient(accountSid);
250+
const client = await getTwilioClient(accountSid);
145251

146-
const result = await Promise.all(
147-
workers.map(workerSid =>
148-
updateWorkerSkills({ operation, skills, workerSid, workspaceSid, client }),
149-
),
150-
);
252+
const result = await Promise.all(
253+
workers.map(workerSid =>
254+
updateWorkerSkills({ operation, skills, workerSid, workspaceSid, client }),
255+
),
256+
);
151257

152-
console.debug(`Skills ${skills} ${operation} for workers ${workers}`);
258+
console.debug(`Skills ${skills} ${operation} for workers ${workers}`);
153259

154-
return newOk({
155-
message: `Skills ${skills} ${operation} for workers ${workers}`,
156-
result,
157-
});
260+
return newOk({
261+
message: `Skills ${skills} ${operation} for workers ${workers}`,
262+
result,
263+
});
264+
} catch (error: any) {
265+
return newErr({ message: error.message, error: { statusCode: 500, cause: error } });
266+
}
158267
};

plugin-hrm-form/src/components/teamsView/SkillsColumn.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import React from 'react';
1818
import { createPortal } from 'react-dom';
1919
import { useDispatch, useSelector } from 'react-redux';
2020
import { WorkersDataTable, ColumnDefinition, Template, styled } from '@twilio/flex-ui';
21-
import { Tooltip } from '@material-ui/core';
21+
import { Divider, Tooltip } from '@material-ui/core';
2222
import { ArrowDropDown, ArrowDropUp, KeyboardArrowRight } from '@material-ui/icons';
2323

2424
import { Flex, Column, OpaqueText, Row, Box } from '../../styles';
@@ -197,6 +197,15 @@ const DropdownPortal: React.FC<{
197197
<Template code="Disable Skills" />
198198
<KeyboardArrowRight style={{ marginLeft: '20px' }} />
199199
</DropDownButton>
200+
<Divider variant="middle" />
201+
<DropDownButton onClick={onClickAction('assign')}>
202+
<Template code="Assign Skills" />
203+
<KeyboardArrowRight style={{ marginLeft: '20px' }} />
204+
</DropDownButton>
205+
<DropDownButton onClick={onClickAction('unassign')}>
206+
<Template code="Unassign Skills" />
207+
<KeyboardArrowRight style={{ marginLeft: '20px' }} />
208+
</DropDownButton>
200209
</Column>
201210
</div>,
202211
document.body,

plugin-hrm-form/src/components/teamsView/UpdateWorkersSkillsModal.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,24 @@ const ConfirmUpdatesModal: React.FC<Props> = () => {
192192
.length;
193193
}
194194

195+
if (operation === 'assign') {
196+
return selectedWorkersFlexState.filter(
197+
w =>
198+
!(
199+
w.worker.attributes.routing?.skills?.includes(skill) ||
200+
w.worker.attributes.disabled_skills?.skills?.includes(skill)
201+
),
202+
).length;
203+
}
204+
205+
if (operation === 'unassign') {
206+
return selectedWorkersFlexState.filter(
207+
w =>
208+
w.worker.attributes.routing?.skills?.includes(skill) ||
209+
w.worker.attributes.disabled_skills?.skills?.includes(skill),
210+
).length;
211+
}
212+
195213
return 0;
196214
};
197215

plugin-hrm-form/src/services/twilioWorkerService.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { getHrmConfig } from '../hrmConfig';
1818
import fetchProtectedApi from './fetchProtectedApi';
1919
import { TaskSID } from '../types/twilio';
2020
import { ApiError } from './fetchApi';
21+
import { TeamsViewState } from '../states/teamsView';
2122

2223
type PopulateCounselorsReturn = { sid: string; fullName: string }[];
2324

@@ -74,5 +75,5 @@ export const getWorkerAttributes = async (workerSid: string) => {
7475
export const updateWorkersSkills = async (payload: {
7576
workers: Array<string>;
7677
skills: Array<string>;
77-
operation: 'enable' | 'disable';
78+
operation: Required<TeamsViewState['operation']>;
7879
}) => fetchProtectedApi('/updateWorkersSkills', payload, { useTwilioLambda: true, useJsonEncode: true });

plugin-hrm-form/src/states/teamsView/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import { updateWorkersSkills } from '../../services/twilioWorkerService';
2121
export type TeamsViewState = {
2222
selectedWorkers: Set<string>;
2323
selectedSkills: Set<string>;
24-
operation?: 'enable' | 'disable';
24+
operation?: 'enable' | 'disable' | 'assign' | 'unassign';
2525
status: {
2626
loading: boolean;
2727
error: any;

0 commit comments

Comments
 (0)