@webitel/api-services 26.8.59 → 26.8.61
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/api/clients/queues/__tests__/queues.spec.ts +128 -1
- package/src/api/clients/queues/queues.ts +88 -8
- package/src/enums/QueuePeriod/QueuePeriod.ts +10 -0
- package/src/enums/index.ts +2 -0
- package/types/.tsbuildinfo +1 -1
- package/types/enums/QueuePeriod/QueuePeriod.d.ts +9 -0
- package/types/enums/index.d.ts +2 -1
package/package.json
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
2
|
|
|
3
|
-
import { QueueType } from '../../../../enums';
|
|
3
|
+
import { QueuePeriod, QueueType } from '../../../../enums';
|
|
4
4
|
|
|
5
5
|
const readQueue = vi.fn();
|
|
6
6
|
const searchQueue = vi.fn();
|
|
7
|
+
const searchQueueReportGeneral = vi.fn();
|
|
7
8
|
|
|
8
9
|
vi.mock('../../../../gen-wire', () => ({
|
|
9
10
|
getQueueService: () => ({
|
|
10
11
|
readQueue,
|
|
11
12
|
searchQueue,
|
|
13
|
+
searchQueueReportGeneral,
|
|
12
14
|
}),
|
|
13
15
|
}));
|
|
14
16
|
|
|
@@ -128,3 +130,128 @@ describe('QueuesAPI.getList, on type-less rows', () => {
|
|
|
128
130
|
});
|
|
129
131
|
});
|
|
130
132
|
});
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* `queuePeriod` is a relative-window preset the supervisor queues table sends
|
|
136
|
+
* ('today', '3hour', …); the service only understands an absolute
|
|
137
|
+
* `joined_at.from`/`joined_at.to` range, so resolving the preset to
|
|
138
|
+
* timestamps happens inside `getReportGeneral` itself.
|
|
139
|
+
*/
|
|
140
|
+
describe('QueuesAPI.getReportGeneral, queuePeriod window resolution', () => {
|
|
141
|
+
beforeEach(() => {
|
|
142
|
+
searchQueueReportGeneral.mockReset();
|
|
143
|
+
searchQueueReportGeneral.mockResolvedValue({
|
|
144
|
+
data: {
|
|
145
|
+
items: [],
|
|
146
|
+
next: false,
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('defaults to today (start of day through now)', async () => {
|
|
152
|
+
const end = Date.now();
|
|
153
|
+
|
|
154
|
+
await QueuesAPI.getReportGeneral({});
|
|
155
|
+
|
|
156
|
+
const sentParams = searchQueueReportGeneral.mock.calls[0][0];
|
|
157
|
+
const expectedFrom = new Date(end).setHours(0, 0, 0, 0);
|
|
158
|
+
|
|
159
|
+
// checking approx equality: `Date.now()` here and inside the call may
|
|
160
|
+
// not be perfectly in sync
|
|
161
|
+
expect(sentParams['joined_at.from'].slice(0, -4)).toEqual(
|
|
162
|
+
`${expectedFrom}`.slice(0, -4),
|
|
163
|
+
);
|
|
164
|
+
expect(sentParams['joined_at.to'].slice(0, -4)).toEqual(
|
|
165
|
+
`${end}`.slice(0, -4),
|
|
166
|
+
);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('resolves "3hour" to now minus 3 hours', async () => {
|
|
170
|
+
const end = Date.now();
|
|
171
|
+
|
|
172
|
+
await QueuesAPI.getReportGeneral({
|
|
173
|
+
queuePeriod: QueuePeriod.ThreeHours,
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
const sentParams = searchQueueReportGeneral.mock.calls[0][0];
|
|
177
|
+
const expectedFrom = end - 3 * 60 * 60 * 1000;
|
|
178
|
+
|
|
179
|
+
expect(sentParams['joined_at.from'].slice(0, -3)).toEqual(
|
|
180
|
+
`${expectedFrom}`.slice(0, -3),
|
|
181
|
+
);
|
|
182
|
+
expect(sentParams['joined_at.to'].slice(0, -3)).toEqual(
|
|
183
|
+
`${end}`.slice(0, -3),
|
|
184
|
+
);
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* The table renders bridged/abandoned/sl20/sl30 as percent strings and
|
|
190
|
+
* duration fields rounded to 2 decimals directly, with no caller-side
|
|
191
|
+
* formatting step; a queue with no matching agents omits `agent_status`
|
|
192
|
+
* entirely, so it has to be defaulted here too.
|
|
193
|
+
*/
|
|
194
|
+
describe('QueuesAPI.getReportGeneral, response shaping', () => {
|
|
195
|
+
beforeEach(() => {
|
|
196
|
+
searchQueueReportGeneral.mockReset();
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('formats percentages, rounds durations, and defaults agentStatus/aggs', async () => {
|
|
200
|
+
searchQueueReportGeneral.mockResolvedValue({
|
|
201
|
+
data: {
|
|
202
|
+
items: [
|
|
203
|
+
{
|
|
204
|
+
processed: 1,
|
|
205
|
+
count: 1,
|
|
206
|
+
sum_bill_sec: 60,
|
|
207
|
+
avg_wrap_sec: 60,
|
|
208
|
+
avg_asa_sec: 60,
|
|
209
|
+
avg_awt_sec: 60,
|
|
210
|
+
},
|
|
211
|
+
],
|
|
212
|
+
next: false,
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
const response = await QueuesAPI.getReportGeneral({});
|
|
217
|
+
|
|
218
|
+
expect(response).toEqual({
|
|
219
|
+
aggs: {
|
|
220
|
+
online: 0,
|
|
221
|
+
offline: 0,
|
|
222
|
+
free: 0,
|
|
223
|
+
pause: 0,
|
|
224
|
+
total: 0,
|
|
225
|
+
},
|
|
226
|
+
items: [
|
|
227
|
+
{
|
|
228
|
+
_isSelected: false,
|
|
229
|
+
abandoned: 0,
|
|
230
|
+
agentStatus: {
|
|
231
|
+
free: 0,
|
|
232
|
+
offline: 0,
|
|
233
|
+
online: 0,
|
|
234
|
+
pause: 0,
|
|
235
|
+
total: 0,
|
|
236
|
+
},
|
|
237
|
+
avgAhtSec: 0,
|
|
238
|
+
avgAsaSec: 60,
|
|
239
|
+
avgAwtSec: 60,
|
|
240
|
+
avgWrapSec: 60,
|
|
241
|
+
bridged: 0,
|
|
242
|
+
count: 1,
|
|
243
|
+
members: {
|
|
244
|
+
processing: 1,
|
|
245
|
+
waiting: 0,
|
|
246
|
+
},
|
|
247
|
+
processed: 1,
|
|
248
|
+
sumBillSec: 60,
|
|
249
|
+
transferred: 0,
|
|
250
|
+
sl20: 0,
|
|
251
|
+
sl30: 0,
|
|
252
|
+
},
|
|
253
|
+
],
|
|
254
|
+
next: false,
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
});
|
|
@@ -3,7 +3,7 @@ import { queueSchema } from '@webitel/api-services/validations';
|
|
|
3
3
|
import deepCopy from 'deep-copy';
|
|
4
4
|
import deepmerge from 'deepmerge';
|
|
5
5
|
import { isEmpty } from 'lodash-es';
|
|
6
|
-
import { QueueType } from '../../../enums';
|
|
6
|
+
import { QueuePeriod, QueueType } from '../../../enums';
|
|
7
7
|
import { getQueueService } from '../../../gen-wire';
|
|
8
8
|
import { getDefaultGetListResponse, getDefaultGetParams } from '../../defaults';
|
|
9
9
|
import {
|
|
@@ -287,16 +287,66 @@ export {
|
|
|
287
287
|
QueueTypeDefaults,
|
|
288
288
|
} from './defaults/queueTypeDefaults';
|
|
289
289
|
|
|
290
|
+
const resolveJoinedAtWindow = (
|
|
291
|
+
queuePeriod: QueuePeriod = QueuePeriod.Today,
|
|
292
|
+
) => {
|
|
293
|
+
const end = new Date();
|
|
294
|
+
let start: Date;
|
|
295
|
+
|
|
296
|
+
const hour = 60 * 60 * 1000;
|
|
297
|
+
const min = 60 * 1000;
|
|
298
|
+
|
|
299
|
+
switch (queuePeriod) {
|
|
300
|
+
case QueuePeriod.SixHours:
|
|
301
|
+
start = new Date(end.getTime() - 6 * hour);
|
|
302
|
+
break;
|
|
303
|
+
case QueuePeriod.ThreeHours:
|
|
304
|
+
start = new Date(end.getTime() - 3 * hour);
|
|
305
|
+
break;
|
|
306
|
+
case QueuePeriod.OneHour:
|
|
307
|
+
start = new Date(end.getTime() - hour);
|
|
308
|
+
break;
|
|
309
|
+
case QueuePeriod.ThirtyMinutes:
|
|
310
|
+
start = new Date(end.getTime() - 30 * min);
|
|
311
|
+
break;
|
|
312
|
+
case QueuePeriod.FifteenMinutes:
|
|
313
|
+
start = new Date(end.getTime() - 15 * min);
|
|
314
|
+
break;
|
|
315
|
+
default:
|
|
316
|
+
start = new Date(end);
|
|
317
|
+
start.setHours(0, 0, 0, 0);
|
|
318
|
+
break;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return {
|
|
322
|
+
joinedAtFrom: start.getTime(),
|
|
323
|
+
joinedAtTo: end.getTime(),
|
|
324
|
+
};
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
const defaultAgentStatusObject = {
|
|
328
|
+
total: 0,
|
|
329
|
+
online: 0,
|
|
330
|
+
pause: 0,
|
|
331
|
+
offline: 0,
|
|
332
|
+
free: 0,
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
const asPercent = (value: number) => (value ? `${+value.toFixed(2)}%` : 0);
|
|
336
|
+
const rounded = (value: number) => (value ? +value.toFixed(2) : 0);
|
|
337
|
+
|
|
290
338
|
/**
|
|
291
339
|
* Aggregated queue performance over a joined-at window — the supervisor queues
|
|
292
|
-
* table.
|
|
340
|
+
* table. Bridged/abandoned/sl20/sl30 arrive as percent strings and durations
|
|
341
|
+
* are rounded to 2 decimals so the table renders them without a caller-side
|
|
342
|
+
* formatting step; `agentStatus` and `aggs` are defaulted to zero values since
|
|
343
|
+
* a queue with no matching agents omits the field entirely.
|
|
293
344
|
*/
|
|
294
345
|
const getQueuesReportGeneral = async (params: ApiParams) => {
|
|
295
346
|
const {
|
|
296
347
|
page,
|
|
297
348
|
size,
|
|
298
|
-
|
|
299
|
-
joinedAtTo,
|
|
349
|
+
queuePeriod,
|
|
300
350
|
fields,
|
|
301
351
|
sort,
|
|
302
352
|
search,
|
|
@@ -305,15 +355,20 @@ const getQueuesReportGeneral = async (params: ApiParams) => {
|
|
|
305
355
|
queueType,
|
|
306
356
|
} = applyTransform(params, [
|
|
307
357
|
merge(getDefaultGetParams()),
|
|
358
|
+
merge({
|
|
359
|
+
search: '',
|
|
360
|
+
sort: '+priority',
|
|
361
|
+
}),
|
|
308
362
|
starToSearch('search'),
|
|
309
363
|
]);
|
|
364
|
+
const { joinedAtFrom, joinedAtTo } = resolveJoinedAtWindow(queuePeriod);
|
|
310
365
|
|
|
311
366
|
try {
|
|
312
367
|
const response = await getQueueService().searchQueueReportGeneral({
|
|
313
368
|
page,
|
|
314
369
|
size,
|
|
315
|
-
'joined_at.from': joinedAtFrom,
|
|
316
|
-
'joined_at.to': joinedAtTo,
|
|
370
|
+
'joined_at.from': String(joinedAtFrom),
|
|
371
|
+
'joined_at.to': String(joinedAtTo),
|
|
317
372
|
fields,
|
|
318
373
|
sort,
|
|
319
374
|
// the generated param is `q`; `search` is what the datalist store sends
|
|
@@ -327,8 +382,33 @@ const getQueuesReportGeneral = async (params: ApiParams) => {
|
|
|
327
382
|
merge(getDefaultGetListResponse()),
|
|
328
383
|
]);
|
|
329
384
|
return {
|
|
330
|
-
items
|
|
331
|
-
|
|
385
|
+
items: items.map((item: ApiParams) => ({
|
|
386
|
+
...item,
|
|
387
|
+
_isSelected: false,
|
|
388
|
+
count: item.count || 0,
|
|
389
|
+
transferred: item.transferred || 0,
|
|
390
|
+
bridged: asPercent(item.bridged),
|
|
391
|
+
abandoned: asPercent(item.abandoned),
|
|
392
|
+
sumBillSec: rounded(item.sumBillSec),
|
|
393
|
+
avgWrapSec: rounded(item.avgWrapSec),
|
|
394
|
+
avgAsaSec: rounded(item.avgAsaSec),
|
|
395
|
+
avgAwtSec: rounded(item.avgAwtSec),
|
|
396
|
+
avgAhtSec: rounded(item.avgAhtSec),
|
|
397
|
+
sl20: asPercent(item.sl20),
|
|
398
|
+
sl30: asPercent(item.sl30),
|
|
399
|
+
agentStatus: {
|
|
400
|
+
...defaultAgentStatusObject,
|
|
401
|
+
...item.agentStatus,
|
|
402
|
+
},
|
|
403
|
+
members: {
|
|
404
|
+
processing: item.processed || 0,
|
|
405
|
+
waiting: item.waiting || 0,
|
|
406
|
+
},
|
|
407
|
+
})),
|
|
408
|
+
aggs: {
|
|
409
|
+
...defaultAgentStatusObject,
|
|
410
|
+
...aggs,
|
|
411
|
+
},
|
|
332
412
|
next,
|
|
333
413
|
};
|
|
334
414
|
} catch (err) {
|
package/src/enums/index.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { CallReportingStatus } from './CallReportingStatus/CallReportingStatus';
|
|
|
2
2
|
import { ChatGatewayProvider } from './ChatGatewayProvider/ChatGatewayPrivider';
|
|
3
3
|
import { FormatDateMode } from './FormatDateMode/FormatDateMode';
|
|
4
4
|
import { MemberStopCause } from './MemberStopCause/MemberStopCause';
|
|
5
|
+
import { QueuePeriod } from './QueuePeriod/QueuePeriod';
|
|
5
6
|
import {
|
|
6
7
|
QueueStrategy,
|
|
7
8
|
QueueStrategyList,
|
|
@@ -18,6 +19,7 @@ export {
|
|
|
18
19
|
ChatGatewayProvider,
|
|
19
20
|
FormatDateMode,
|
|
20
21
|
MemberStopCause,
|
|
22
|
+
QueuePeriod,
|
|
21
23
|
QueueStrategy,
|
|
22
24
|
QueueStrategyList,
|
|
23
25
|
QueueType,
|