@webex/contact-center 3.12.0-next.2 → 3.12.0-next.21
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/dist/cc.js +132 -0
- package/dist/cc.js.map +1 -1
- package/dist/constants.js +2 -0
- package/dist/constants.js.map +1 -1
- package/dist/metrics/behavioral-events.js +26 -0
- package/dist/metrics/behavioral-events.js.map +1 -1
- package/dist/metrics/constants.js +4 -0
- package/dist/metrics/constants.js.map +1 -1
- package/dist/services/config/constants.js +1 -1
- package/dist/services/config/constants.js.map +1 -1
- package/dist/services/config/types.js +4 -0
- package/dist/services/config/types.js.map +1 -1
- package/dist/services/core/Err.js.map +1 -1
- package/dist/services/task/TaskManager.js +90 -8
- package/dist/services/task/TaskManager.js.map +1 -1
- package/dist/services/task/constants.js +3 -1
- package/dist/services/task/constants.js.map +1 -1
- package/dist/services/task/dialer.js +78 -0
- package/dist/services/task/dialer.js.map +1 -1
- package/dist/services/task/index.js +7 -2
- package/dist/services/task/index.js.map +1 -1
- package/dist/services/task/types.js +44 -0
- package/dist/services/task/types.js.map +1 -1
- package/dist/types/cc.d.ts +44 -0
- package/dist/types/constants.d.ts +2 -0
- package/dist/types/metrics/constants.d.ts +4 -0
- package/dist/types/services/config/types.d.ts +8 -0
- package/dist/types/services/core/Err.d.ts +4 -0
- package/dist/types/services/task/constants.d.ts +2 -0
- package/dist/types/services/task/dialer.d.ts +30 -0
- package/dist/types/services/task/types.d.ts +53 -1
- package/dist/webex.js +1 -1
- package/package.json +8 -8
- package/src/cc.ts +160 -0
- package/src/constants.ts +2 -0
- package/src/metrics/behavioral-events.ts +28 -0
- package/src/metrics/constants.ts +4 -0
- package/src/services/config/constants.ts +1 -1
- package/src/services/config/types.ts +4 -0
- package/src/services/core/Err.ts +2 -0
- package/src/services/task/TaskManager.ts +102 -22
- package/src/services/task/constants.ts +2 -0
- package/src/services/task/dialer.ts +80 -0
- package/src/services/task/index.ts +7 -2
- package/src/services/task/types.ts +56 -0
- package/test/unit/spec/cc.ts +130 -0
- package/test/unit/spec/services/config/index.ts +1 -1
- package/test/unit/spec/services/task/TaskManager.ts +238 -7
- package/test/unit/spec/services/task/dialer.ts +190 -0
- package/test/unit/spec/services/task/index.ts +21 -0
- package/umd/contact-center.min.js +2 -2
- package/umd/contact-center.min.js.map +1 -1
package/src/cc.ts
CHANGED
|
@@ -1656,6 +1656,166 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
|
|
|
1656
1656
|
}
|
|
1657
1657
|
}
|
|
1658
1658
|
|
|
1659
|
+
/**
|
|
1660
|
+
* Skips a campaign preview contact, requesting the next contact from the campaign.
|
|
1661
|
+
*
|
|
1662
|
+
* When a campaign manager reserves a contact for an agent, the agent receives an
|
|
1663
|
+
* `AgentOfferCampaignReservation` event. Instead of accepting, the agent can skip the
|
|
1664
|
+
* preview contact to move to the next contact in the campaign.
|
|
1665
|
+
*
|
|
1666
|
+
* @param {PreviewContactPayload} payload - The preview contact payload containing interactionId and campaignId (campaign name, not UUID).
|
|
1667
|
+
* @returns {Promise<TaskResponse>} Promise resolving with agent contact on success.
|
|
1668
|
+
* @throws {Error} If the operation fails (network error, etc.)
|
|
1669
|
+
*
|
|
1670
|
+
* @example
|
|
1671
|
+
* ```typescript
|
|
1672
|
+
* webex.cc.on('task:campaignPreviewReservation', async (task) => {
|
|
1673
|
+
* const { interactionId } = task.data;
|
|
1674
|
+
* const campaignId = task.data.interaction.callProcessingDetails.campaignId;
|
|
1675
|
+
*
|
|
1676
|
+
* const result = await webex.cc.skipPreviewContact({ interactionId, campaignId });
|
|
1677
|
+
* });
|
|
1678
|
+
* ```
|
|
1679
|
+
*/
|
|
1680
|
+
public async skipPreviewContact(payload: PreviewContactPayload): Promise<TaskResponse> {
|
|
1681
|
+
const task = this.taskManager.getTask(payload.interactionId);
|
|
1682
|
+
if (task?.data?.interaction?.callProcessingDetails?.campaignPreviewSkipDisabled === 'true') {
|
|
1683
|
+
LoggerProxy.warn('Skip action is disabled for this campaign preview contact', {
|
|
1684
|
+
module: CC_FILE,
|
|
1685
|
+
method: METHODS.SKIP_PREVIEW_CONTACT,
|
|
1686
|
+
interactionId: payload.interactionId,
|
|
1687
|
+
});
|
|
1688
|
+
throw new Error('Skip action is disabled for this campaign preview contact');
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
LoggerProxy.info('Skipping campaign preview contact', {
|
|
1692
|
+
module: CC_FILE,
|
|
1693
|
+
method: METHODS.SKIP_PREVIEW_CONTACT,
|
|
1694
|
+
});
|
|
1695
|
+
try {
|
|
1696
|
+
this.metricsManager.timeEvent([
|
|
1697
|
+
METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_SKIP_SUCCESS,
|
|
1698
|
+
METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_SKIP_FAILED,
|
|
1699
|
+
]);
|
|
1700
|
+
|
|
1701
|
+
const result = await this.services.dialer.skipPreviewContact({data: payload});
|
|
1702
|
+
|
|
1703
|
+
this.metricsManager.trackEvent(
|
|
1704
|
+
METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_SKIP_SUCCESS,
|
|
1705
|
+
{
|
|
1706
|
+
...MetricsManager.getCommonTrackingFieldForAQMResponse(result),
|
|
1707
|
+
interactionId: payload.interactionId,
|
|
1708
|
+
campaignId: payload.campaignId,
|
|
1709
|
+
},
|
|
1710
|
+
['behavioral', 'business', 'operational']
|
|
1711
|
+
);
|
|
1712
|
+
|
|
1713
|
+
LoggerProxy.log('Campaign preview contact skipped successfully', {
|
|
1714
|
+
module: CC_FILE,
|
|
1715
|
+
method: METHODS.SKIP_PREVIEW_CONTACT,
|
|
1716
|
+
trackingId: result.trackingId,
|
|
1717
|
+
interactionId: payload.interactionId,
|
|
1718
|
+
});
|
|
1719
|
+
|
|
1720
|
+
return result;
|
|
1721
|
+
} catch (error) {
|
|
1722
|
+
const failure = error.details as Failure;
|
|
1723
|
+
this.metricsManager.trackEvent(
|
|
1724
|
+
METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_SKIP_FAILED,
|
|
1725
|
+
{
|
|
1726
|
+
...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(failure),
|
|
1727
|
+
interactionId: payload.interactionId,
|
|
1728
|
+
campaignId: payload.campaignId,
|
|
1729
|
+
},
|
|
1730
|
+
['behavioral', 'business', 'operational']
|
|
1731
|
+
);
|
|
1732
|
+
const {error: detailedError} = getErrorDetails(error, METHODS.SKIP_PREVIEW_CONTACT, CC_FILE);
|
|
1733
|
+
throw detailedError;
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
/**
|
|
1738
|
+
* Removes a campaign preview contact from the campaign list entirely.
|
|
1739
|
+
*
|
|
1740
|
+
* When a campaign manager reserves a contact for an agent, the agent receives an
|
|
1741
|
+
* `AgentOfferCampaignReservation` event. Instead of accepting or skipping, the agent can
|
|
1742
|
+
* remove the preview contact to permanently take it out of the campaign contact list.
|
|
1743
|
+
*
|
|
1744
|
+
* @param {PreviewContactPayload} payload - The preview contact payload containing interactionId and campaignId (campaign name, not UUID).
|
|
1745
|
+
* @returns {Promise<TaskResponse>} Promise resolving with agent contact on success.
|
|
1746
|
+
* @throws {Error} If the operation fails (network error, etc.)
|
|
1747
|
+
*
|
|
1748
|
+
* @example
|
|
1749
|
+
* ```typescript
|
|
1750
|
+
* webex.cc.on('task:campaignPreviewReservation', async (task) => {
|
|
1751
|
+
* const { interactionId } = task.data;
|
|
1752
|
+
* const campaignId = task.data.interaction.callProcessingDetails.campaignId;
|
|
1753
|
+
*
|
|
1754
|
+
* const result = await webex.cc.removePreviewContact({ interactionId, campaignId });
|
|
1755
|
+
* });
|
|
1756
|
+
* ```
|
|
1757
|
+
*/
|
|
1758
|
+
public async removePreviewContact(payload: PreviewContactPayload): Promise<TaskResponse> {
|
|
1759
|
+
const task = this.taskManager.getTask(payload.interactionId);
|
|
1760
|
+
if (task?.data?.interaction?.callProcessingDetails?.campaignPreviewRemoveDisabled === 'true') {
|
|
1761
|
+
LoggerProxy.warn('Remove action is disabled for this campaign preview contact', {
|
|
1762
|
+
module: CC_FILE,
|
|
1763
|
+
method: METHODS.REMOVE_PREVIEW_CONTACT,
|
|
1764
|
+
interactionId: payload.interactionId,
|
|
1765
|
+
});
|
|
1766
|
+
throw new Error('Remove action is disabled for this campaign preview contact');
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
LoggerProxy.info('Removing campaign preview contact', {
|
|
1770
|
+
module: CC_FILE,
|
|
1771
|
+
method: METHODS.REMOVE_PREVIEW_CONTACT,
|
|
1772
|
+
});
|
|
1773
|
+
try {
|
|
1774
|
+
this.metricsManager.timeEvent([
|
|
1775
|
+
METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_REMOVE_SUCCESS,
|
|
1776
|
+
METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_REMOVE_FAILED,
|
|
1777
|
+
]);
|
|
1778
|
+
|
|
1779
|
+
const result = await this.services.dialer.removePreviewContact({data: payload});
|
|
1780
|
+
|
|
1781
|
+
this.metricsManager.trackEvent(
|
|
1782
|
+
METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_REMOVE_SUCCESS,
|
|
1783
|
+
{
|
|
1784
|
+
...MetricsManager.getCommonTrackingFieldForAQMResponse(result),
|
|
1785
|
+
interactionId: payload.interactionId,
|
|
1786
|
+
campaignId: payload.campaignId,
|
|
1787
|
+
},
|
|
1788
|
+
['behavioral', 'business', 'operational']
|
|
1789
|
+
);
|
|
1790
|
+
|
|
1791
|
+
LoggerProxy.log('Campaign preview contact removed successfully', {
|
|
1792
|
+
module: CC_FILE,
|
|
1793
|
+
method: METHODS.REMOVE_PREVIEW_CONTACT,
|
|
1794
|
+
trackingId: result.trackingId,
|
|
1795
|
+
interactionId: payload.interactionId,
|
|
1796
|
+
});
|
|
1797
|
+
|
|
1798
|
+
return result;
|
|
1799
|
+
} catch (error) {
|
|
1800
|
+
const failure = error.details as Failure;
|
|
1801
|
+
this.metricsManager.trackEvent(
|
|
1802
|
+
METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_REMOVE_FAILED,
|
|
1803
|
+
{
|
|
1804
|
+
...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(failure),
|
|
1805
|
+
interactionId: payload.interactionId,
|
|
1806
|
+
campaignId: payload.campaignId,
|
|
1807
|
+
},
|
|
1808
|
+
['behavioral', 'business', 'operational']
|
|
1809
|
+
);
|
|
1810
|
+
const {error: detailedError} = getErrorDetails(
|
|
1811
|
+
error,
|
|
1812
|
+
METHODS.REMOVE_PREVIEW_CONTACT,
|
|
1813
|
+
CC_FILE
|
|
1814
|
+
);
|
|
1815
|
+
throw detailedError;
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1659
1819
|
/**
|
|
1660
1820
|
* Fetches outdial ANI (Automatic Number Identification) entries for an outdial ANI ID.
|
|
1661
1821
|
*
|
package/src/constants.ts
CHANGED
|
@@ -50,6 +50,8 @@ export const METHODS = {
|
|
|
50
50
|
HANDLE_TASK_HYDRATE: 'handleTaskHydrate',
|
|
51
51
|
INCOMING_TASK_LISTENER: 'incomingTaskListener',
|
|
52
52
|
ACCEPT_PREVIEW_CONTACT: 'acceptPreviewContact',
|
|
53
|
+
SKIP_PREVIEW_CONTACT: 'skipPreviewContact',
|
|
54
|
+
REMOVE_PREVIEW_CONTACT: 'removePreviewContact',
|
|
53
55
|
GET_BASE_URL: 'getBaseUrl',
|
|
54
56
|
SEND_EVENT: 'sendEvent',
|
|
55
57
|
FETCH_HISTORIC_TRANSCRIPTS: 'fetchHistoricTranscripts',
|
|
@@ -449,6 +449,34 @@ const eventTaxonomyMap: Record<string, BehavioralEventTaxonomy> = {
|
|
|
449
449
|
target: 'campaign_preview_accept',
|
|
450
450
|
verb: 'fail',
|
|
451
451
|
},
|
|
452
|
+
|
|
453
|
+
// Campaign Preview Skip API Events
|
|
454
|
+
[METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_SKIP_SUCCESS]: {
|
|
455
|
+
product,
|
|
456
|
+
agent: 'user',
|
|
457
|
+
target: 'campaign_preview_skip',
|
|
458
|
+
verb: 'complete',
|
|
459
|
+
},
|
|
460
|
+
[METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_SKIP_FAILED]: {
|
|
461
|
+
product,
|
|
462
|
+
agent: 'user',
|
|
463
|
+
target: 'campaign_preview_skip',
|
|
464
|
+
verb: 'fail',
|
|
465
|
+
},
|
|
466
|
+
|
|
467
|
+
// Campaign Preview Remove API Events
|
|
468
|
+
[METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_REMOVE_SUCCESS]: {
|
|
469
|
+
product,
|
|
470
|
+
agent: 'user',
|
|
471
|
+
target: 'campaign_preview_remove',
|
|
472
|
+
verb: 'complete',
|
|
473
|
+
},
|
|
474
|
+
[METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_REMOVE_FAILED]: {
|
|
475
|
+
product,
|
|
476
|
+
agent: 'user',
|
|
477
|
+
target: 'campaign_preview_remove',
|
|
478
|
+
verb: 'fail',
|
|
479
|
+
},
|
|
452
480
|
};
|
|
453
481
|
|
|
454
482
|
/**
|
package/src/metrics/constants.ts
CHANGED
|
@@ -163,6 +163,10 @@ export const METRIC_EVENT_NAMES = {
|
|
|
163
163
|
// Campaign Preview API Events
|
|
164
164
|
CAMPAIGN_PREVIEW_ACCEPT_SUCCESS: 'Campaign Preview Accept Success',
|
|
165
165
|
CAMPAIGN_PREVIEW_ACCEPT_FAILED: 'Campaign Preview Accept Failed',
|
|
166
|
+
CAMPAIGN_PREVIEW_SKIP_SUCCESS: 'Campaign Preview Skip Success',
|
|
167
|
+
CAMPAIGN_PREVIEW_SKIP_FAILED: 'Campaign Preview Skip Failed',
|
|
168
|
+
CAMPAIGN_PREVIEW_REMOVE_SUCCESS: 'Campaign Preview Remove Success',
|
|
169
|
+
CAMPAIGN_PREVIEW_REMOVE_FAILED: 'Campaign Preview Remove Failed',
|
|
166
170
|
|
|
167
171
|
// AI Assistant transcript events
|
|
168
172
|
AI_ASSISTANT_SEND_EVENT_SUCCESS: 'AI Assistant Send Event Success',
|
|
@@ -166,7 +166,7 @@ export const endPointMap = {
|
|
|
166
166
|
) =>
|
|
167
167
|
`organization/${orgId}/v2/auxiliary-code?page=${page}&pageSize=${pageSize}${
|
|
168
168
|
filter && filter.length > 0 ? `&filter=id=in=(${filter})` : ''
|
|
169
|
-
}&attributes=${attributes}`,
|
|
169
|
+
}&attributes=${attributes}&desktopProfileFilter=true`,
|
|
170
170
|
|
|
171
171
|
/**
|
|
172
172
|
* Gets the endpoint for organization info.
|
|
@@ -117,6 +117,10 @@ export const CC_TASK_EVENTS = {
|
|
|
117
117
|
CAMPAIGN_CONTACT_UPDATED: 'CampaignContactUpdated',
|
|
118
118
|
/** Event emitted when accepting a campaign preview contact fails */
|
|
119
119
|
CAMPAIGN_PREVIEW_ACCEPT_FAILED: 'CampaignPreviewAcceptFailed',
|
|
120
|
+
/** Event emitted when skipping a campaign preview contact fails */
|
|
121
|
+
CAMPAIGN_PREVIEW_SKIP_FAILED: 'CampaignPreviewSkipFailed',
|
|
122
|
+
/** Event emitted when removing a campaign preview contact fails */
|
|
123
|
+
CAMPAIGN_PREVIEW_REMOVE_FAILED: 'CampaignPreviewRemoveFailed',
|
|
120
124
|
/** Event emitted when a real-time transcript chunk is received */
|
|
121
125
|
REAL_TIME_TRANSCRIPTION: 'REAL_TIME_TRANSCRIPTION',
|
|
122
126
|
} as const;
|
package/src/services/core/Err.ts
CHANGED
|
@@ -39,6 +39,8 @@ export type TaskErrorIds =
|
|
|
39
39
|
| {'Service.aqm.task.resumeRecording': Failure}
|
|
40
40
|
| {'Service.aqm.dialer.startOutdial': Failure}
|
|
41
41
|
| {'Service.aqm.dialer.acceptPreviewContact': Failure}
|
|
42
|
+
| {'Service.aqm.dialer.skipPreviewContact': Failure}
|
|
43
|
+
| {'Service.aqm.dialer.removePreviewContact': Failure}
|
|
42
44
|
| {'Service.reqs.generic.failure': {trackingId: string}};
|
|
43
45
|
|
|
44
46
|
export type ReqError =
|
|
@@ -312,14 +312,11 @@ export default class TaskManager extends EventEmitter {
|
|
|
312
312
|
case CC_EVENTS.AGENT_CONTACT_OFFER_RONA:
|
|
313
313
|
case CC_EVENTS.AGENT_CONTACT_ASSIGN_FAILED:
|
|
314
314
|
case CC_EVENTS.AGENT_INVITE_FAILED: {
|
|
315
|
-
LoggerProxy.
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
interactionId: payload.data.interactionId,
|
|
321
|
-
}
|
|
322
|
-
);
|
|
315
|
+
LoggerProxy.info(`Task removal triggered by ${payload.data.type}`, {
|
|
316
|
+
module: TASK_MANAGER_FILE,
|
|
317
|
+
method: METHODS.REGISTER_TASK_LISTENERS,
|
|
318
|
+
interactionId: payload.data.interactionId,
|
|
319
|
+
});
|
|
323
320
|
task = this.updateTaskData(task, payload.data);
|
|
324
321
|
|
|
325
322
|
const eventTypeToMetricMap: Record<string, keyof typeof METRIC_EVENT_NAMES> = {
|
|
@@ -343,19 +340,31 @@ export default class TaskManager extends EventEmitter {
|
|
|
343
340
|
break;
|
|
344
341
|
}
|
|
345
342
|
case CC_EVENTS.CONTACT_ENDED:
|
|
346
|
-
// Update task data
|
|
343
|
+
// Update task data.
|
|
347
344
|
if (task) {
|
|
348
|
-
LoggerProxy.
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
345
|
+
LoggerProxy.info(`Contact ended for interaction`, {
|
|
346
|
+
module: TASK_MANAGER_FILE,
|
|
347
|
+
method: METHODS.REGISTER_TASK_LISTENERS,
|
|
348
|
+
interactionId: payload.data.interactionId,
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
// Campaign preview tasks should never trigger wrapup on ContactEnded —
|
|
352
|
+
// they are terminal cleanup events. For all other tasks, derive
|
|
353
|
+
// wrapUpRequired from agentsPendingWrapUp as before.
|
|
354
|
+
const CAMPAIGN_OUTBOUND_TYPES = [
|
|
355
|
+
'STANDARD_PREVIEW_CAMPAIGN',
|
|
356
|
+
'DIRECT_PREVIEW_CAMPAIGN',
|
|
357
|
+
];
|
|
358
|
+
const isCampaignPreview = CAMPAIGN_OUTBOUND_TYPES.includes(
|
|
359
|
+
task.data?.interaction?.outboundType ?? ''
|
|
355
360
|
);
|
|
361
|
+
const wrapUpRequired = isCampaignPreview
|
|
362
|
+
? false
|
|
363
|
+
: payload.data.agentsPendingWrapUp?.includes(this.agentId) || false;
|
|
364
|
+
|
|
356
365
|
task = this.updateTaskData(task, {
|
|
357
366
|
...payload.data,
|
|
358
|
-
wrapUpRequired
|
|
367
|
+
wrapUpRequired,
|
|
359
368
|
});
|
|
360
369
|
|
|
361
370
|
// Handle cleanup based on whether task should be deleted
|
|
@@ -364,12 +373,83 @@ export default class TaskManager extends EventEmitter {
|
|
|
364
373
|
task?.emit(TASK_EVENTS.TASK_END, task);
|
|
365
374
|
}
|
|
366
375
|
break;
|
|
367
|
-
case CC_EVENTS.CAMPAIGN_CONTACT_UPDATED:
|
|
368
|
-
// CampaignContactUpdated is a non-terminal event (
|
|
369
|
-
//
|
|
370
|
-
//
|
|
376
|
+
case CC_EVENTS.CAMPAIGN_CONTACT_UPDATED: {
|
|
377
|
+
// CampaignContactUpdated is a non-terminal event (e.g., next contact after skip/remove).
|
|
378
|
+
// Update the task data and emit an event so consumers can react to the updated contact.
|
|
379
|
+
// Do NOT remove the task or emit TASK_END — cleanup is handled by CONTACT_ENDED.
|
|
371
380
|
if (task) {
|
|
372
|
-
|
|
381
|
+
// Carry forward campaign preview fields from existing task data since the updated
|
|
382
|
+
// contact payload may not include them, and reconcileData would delete them.
|
|
383
|
+
const existingCpd = task.data?.interaction?.callProcessingDetails;
|
|
384
|
+
const updatedData = {...payload.data};
|
|
385
|
+
|
|
386
|
+
if (existingCpd) {
|
|
387
|
+
const campaignFields = {
|
|
388
|
+
...(existingCpd.campaignPreviewAutoAction && {
|
|
389
|
+
campaignPreviewAutoAction: existingCpd.campaignPreviewAutoAction,
|
|
390
|
+
}),
|
|
391
|
+
...(existingCpd.campaignPreviewOfferTimeout && {
|
|
392
|
+
campaignPreviewOfferTimeout: existingCpd.campaignPreviewOfferTimeout,
|
|
393
|
+
}),
|
|
394
|
+
...(existingCpd.campaignPreviewSkipDisabled && {
|
|
395
|
+
campaignPreviewSkipDisabled: existingCpd.campaignPreviewSkipDisabled,
|
|
396
|
+
}),
|
|
397
|
+
...(existingCpd.campaignPreviewRemoveDisabled && {
|
|
398
|
+
campaignPreviewRemoveDisabled: existingCpd.campaignPreviewRemoveDisabled,
|
|
399
|
+
}),
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
if (!updatedData.interaction) {
|
|
403
|
+
updatedData.interaction = {} as typeof updatedData.interaction;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
updatedData.interaction = {
|
|
407
|
+
...updatedData.interaction,
|
|
408
|
+
callProcessingDetails: {
|
|
409
|
+
...campaignFields,
|
|
410
|
+
...(updatedData.interaction.callProcessingDetails || {}),
|
|
411
|
+
} as typeof existingCpd,
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
LoggerProxy.log('Campaign contact updated - carrying forward preview fields', {
|
|
416
|
+
module: TASK_MANAGER_FILE,
|
|
417
|
+
method: METHODS.REGISTER_TASK_LISTENERS,
|
|
418
|
+
interactionId: payload.data.interactionId,
|
|
419
|
+
data: {
|
|
420
|
+
hasCpd: !!updatedData.interaction?.callProcessingDetails,
|
|
421
|
+
autoAction:
|
|
422
|
+
updatedData.interaction?.callProcessingDetails?.campaignPreviewAutoAction,
|
|
423
|
+
skipDisabled:
|
|
424
|
+
updatedData.interaction?.callProcessingDetails?.campaignPreviewSkipDisabled,
|
|
425
|
+
removeDisabled:
|
|
426
|
+
updatedData.interaction?.callProcessingDetails?.campaignPreviewRemoveDisabled,
|
|
427
|
+
},
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
task = this.updateTaskData(task, updatedData);
|
|
431
|
+
task.emit(TASK_EVENTS.TASK_CAMPAIGN_CONTACT_UPDATED, task);
|
|
432
|
+
}
|
|
433
|
+
break;
|
|
434
|
+
}
|
|
435
|
+
case CC_EVENTS.CAMPAIGN_PREVIEW_ACCEPT_FAILED:
|
|
436
|
+
if (task) {
|
|
437
|
+
// Failure payloads are sparse (no interaction field). Spread existing
|
|
438
|
+
// task data first so reconcileData doesn't delete interaction/cpd.
|
|
439
|
+
task = this.updateTaskData(task, {...task.data, ...payload.data});
|
|
440
|
+
task.emit(TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_ACCEPT_FAILED, task);
|
|
441
|
+
}
|
|
442
|
+
break;
|
|
443
|
+
case CC_EVENTS.CAMPAIGN_PREVIEW_SKIP_FAILED:
|
|
444
|
+
if (task) {
|
|
445
|
+
task = this.updateTaskData(task, {...task.data, ...payload.data});
|
|
446
|
+
task.emit(TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_SKIP_FAILED, task);
|
|
447
|
+
}
|
|
448
|
+
break;
|
|
449
|
+
case CC_EVENTS.CAMPAIGN_PREVIEW_REMOVE_FAILED:
|
|
450
|
+
if (task) {
|
|
451
|
+
task = this.updateTaskData(task, {...task.data, ...payload.data});
|
|
452
|
+
task.emit(TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_REMOVE_FAILED, task);
|
|
373
453
|
}
|
|
374
454
|
break;
|
|
375
455
|
case CC_EVENTS.CONTACT_MERGED:
|
|
@@ -24,6 +24,8 @@ export const CONFERENCE_EXIT = '/conference/exit';
|
|
|
24
24
|
export const CONFERENCE_TRANSFER = '/conference/transfer';
|
|
25
25
|
export const DIALER_API = '/v1/dialer';
|
|
26
26
|
export const CAMPAIGN_PREVIEW_ACCEPT = '/accept';
|
|
27
|
+
export const CAMPAIGN_PREVIEW_SKIP = '/skip';
|
|
28
|
+
export const CAMPAIGN_PREVIEW_REMOVE = '/remove';
|
|
27
29
|
/** 80-second timeout for accepting preview contact (outbound call setup takes longer than default 20s) */
|
|
28
30
|
export const TIMEOUT_PREVIEW_ACCEPT = 80000;
|
|
29
31
|
export const TASK_MANAGER_FILE = 'taskManager';
|
|
@@ -7,6 +7,8 @@ import {
|
|
|
7
7
|
TASK_API,
|
|
8
8
|
DIALER_API,
|
|
9
9
|
CAMPAIGN_PREVIEW_ACCEPT,
|
|
10
|
+
CAMPAIGN_PREVIEW_SKIP,
|
|
11
|
+
CAMPAIGN_PREVIEW_REMOVE,
|
|
10
12
|
TIMEOUT_PREVIEW_ACCEPT,
|
|
11
13
|
} from './constants';
|
|
12
14
|
import * as Contact from './types';
|
|
@@ -103,5 +105,83 @@ export default function aqmDialer(aqm: AqmReqs) {
|
|
|
103
105
|
errId: 'Service.aqm.dialer.acceptPreviewContact',
|
|
104
106
|
},
|
|
105
107
|
})),
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Skips a campaign preview contact, requesting the next contact from the campaign.
|
|
111
|
+
*
|
|
112
|
+
* @param {Object} p - Parameters object.
|
|
113
|
+
* @param {Contact.PreviewContactPayload} p.data - Payload containing interactionId and campaignId.
|
|
114
|
+
* @returns {Promise<Contact.AgentContact>} A promise that resolves with agent contact on success.
|
|
115
|
+
*
|
|
116
|
+
* Emits:
|
|
117
|
+
* - `CC_EVENTS.CAMPAIGN_CONTACT_UPDATED` or `CC_EVENTS.CONTACT_ENDED` on success
|
|
118
|
+
* - `CC_EVENTS.CAMPAIGN_PREVIEW_SKIP_FAILED` on failure
|
|
119
|
+
* @ignore
|
|
120
|
+
*/
|
|
121
|
+
skipPreviewContact: aqm.req((p: {data: Contact.PreviewContactPayload}) => ({
|
|
122
|
+
url: `${DIALER_API}/campaign/${encodeURIComponent(p.data.campaignId)}/preview-task/${
|
|
123
|
+
p.data.interactionId
|
|
124
|
+
}${CAMPAIGN_PREVIEW_SKIP}`,
|
|
125
|
+
host: WCC_API_GATEWAY,
|
|
126
|
+
data: {},
|
|
127
|
+
method: HTTP_METHODS.POST,
|
|
128
|
+
err,
|
|
129
|
+
notifSuccess: {
|
|
130
|
+
bind: {
|
|
131
|
+
type: TASK_MESSAGE_TYPE,
|
|
132
|
+
data: {
|
|
133
|
+
type: [CC_EVENTS.CAMPAIGN_CONTACT_UPDATED, CC_EVENTS.CONTACT_ENDED],
|
|
134
|
+
interactionId: p.data.interactionId,
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
msg: {} as Contact.AgentContact,
|
|
138
|
+
},
|
|
139
|
+
notifFail: {
|
|
140
|
+
bind: {
|
|
141
|
+
type: TASK_MESSAGE_TYPE,
|
|
142
|
+
data: {type: CC_EVENTS.CAMPAIGN_PREVIEW_SKIP_FAILED, campaignId: p.data.campaignId},
|
|
143
|
+
},
|
|
144
|
+
errId: 'Service.aqm.dialer.skipPreviewContact',
|
|
145
|
+
},
|
|
146
|
+
})),
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Removes a campaign preview contact from the campaign list entirely.
|
|
150
|
+
*
|
|
151
|
+
* @param {Object} p - Parameters object.
|
|
152
|
+
* @param {Contact.PreviewContactPayload} p.data - Payload containing interactionId and campaignId.
|
|
153
|
+
* @returns {Promise<Contact.AgentContact>} A promise that resolves with agent contact on success.
|
|
154
|
+
*
|
|
155
|
+
* Emits:
|
|
156
|
+
* - `CC_EVENTS.CAMPAIGN_CONTACT_UPDATED` or `CC_EVENTS.CONTACT_ENDED` on success
|
|
157
|
+
* - `CC_EVENTS.CAMPAIGN_PREVIEW_REMOVE_FAILED` on failure
|
|
158
|
+
* @ignore
|
|
159
|
+
*/
|
|
160
|
+
removePreviewContact: aqm.req((p: {data: Contact.PreviewContactPayload}) => ({
|
|
161
|
+
url: `${DIALER_API}/campaign/${encodeURIComponent(p.data.campaignId)}/preview-task/${
|
|
162
|
+
p.data.interactionId
|
|
163
|
+
}${CAMPAIGN_PREVIEW_REMOVE}`,
|
|
164
|
+
host: WCC_API_GATEWAY,
|
|
165
|
+
data: {},
|
|
166
|
+
method: HTTP_METHODS.POST,
|
|
167
|
+
err,
|
|
168
|
+
notifSuccess: {
|
|
169
|
+
bind: {
|
|
170
|
+
type: TASK_MESSAGE_TYPE,
|
|
171
|
+
data: {
|
|
172
|
+
type: [CC_EVENTS.CAMPAIGN_CONTACT_UPDATED, CC_EVENTS.CONTACT_ENDED],
|
|
173
|
+
interactionId: p.data.interactionId,
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
msg: {} as Contact.AgentContact,
|
|
177
|
+
},
|
|
178
|
+
notifFail: {
|
|
179
|
+
bind: {
|
|
180
|
+
type: TASK_MESSAGE_TYPE,
|
|
181
|
+
data: {type: CC_EVENTS.CAMPAIGN_PREVIEW_REMOVE_FAILED, campaignId: p.data.campaignId},
|
|
182
|
+
},
|
|
183
|
+
errId: 'Service.aqm.dialer.removePreviewContact',
|
|
184
|
+
},
|
|
185
|
+
})),
|
|
106
186
|
};
|
|
107
187
|
}
|
|
@@ -565,7 +565,10 @@ export default class Task extends EventEmitter implements ITask {
|
|
|
565
565
|
METRIC_EVENT_NAMES.TASK_HOLD_FAILED,
|
|
566
566
|
]);
|
|
567
567
|
|
|
568
|
-
const
|
|
568
|
+
const {mainInteractionId} = this.data.interaction;
|
|
569
|
+
const defaultMediaResourceId =
|
|
570
|
+
this.data.interaction.media[mainInteractionId]?.mediaResourceId;
|
|
571
|
+
const effectiveMediaResourceId = mediaResourceId ?? defaultMediaResourceId;
|
|
569
572
|
|
|
570
573
|
const response = await this.contact.hold({
|
|
571
574
|
interactionId: this.data.interactionId,
|
|
@@ -599,7 +602,9 @@ export default class Task extends EventEmitter implements ITask {
|
|
|
599
602
|
errorData: err.data?.errorData,
|
|
600
603
|
reasonCode: err.data?.reasonCode,
|
|
601
604
|
};
|
|
602
|
-
const
|
|
605
|
+
const defaultMediaResourceId =
|
|
606
|
+
this.data.interaction.media[this.data.interaction.mainInteractionId]?.mediaResourceId;
|
|
607
|
+
const effectiveMediaResourceId = mediaResourceId ?? defaultMediaResourceId;
|
|
603
608
|
|
|
604
609
|
this.metricsManager.trackEvent(
|
|
605
610
|
METRIC_EVENT_NAMES.TASK_HOLD_FAILED,
|
|
@@ -554,6 +554,54 @@ export enum TASK_EVENTS {
|
|
|
554
554
|
* ```
|
|
555
555
|
*/
|
|
556
556
|
TASK_CAMPAIGN_PREVIEW_RESERVATION = 'task:campaignPreviewReservation',
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* Triggered when accepting a campaign preview contact fails
|
|
560
|
+
* @example
|
|
561
|
+
* ```typescript
|
|
562
|
+
* task.on(TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_ACCEPT_FAILED, (task: ITask) => {
|
|
563
|
+
* console.log('Campaign preview accept failed:', task.data.interactionId);
|
|
564
|
+
* // Handle accept failure
|
|
565
|
+
* });
|
|
566
|
+
* ```
|
|
567
|
+
*/
|
|
568
|
+
TASK_CAMPAIGN_PREVIEW_ACCEPT_FAILED = 'task:campaignPreviewAcceptFailed',
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* Triggered when skipping a campaign preview contact fails
|
|
572
|
+
* @example
|
|
573
|
+
* ```typescript
|
|
574
|
+
* task.on(TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_SKIP_FAILED, (task: ITask) => {
|
|
575
|
+
* console.log('Campaign preview skip failed:', task.data.interactionId);
|
|
576
|
+
* // Handle skip failure
|
|
577
|
+
* });
|
|
578
|
+
* ```
|
|
579
|
+
*/
|
|
580
|
+
TASK_CAMPAIGN_PREVIEW_SKIP_FAILED = 'task:campaignPreviewSkipFailed',
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* Triggered when removing a campaign preview contact fails
|
|
584
|
+
* @example
|
|
585
|
+
* ```typescript
|
|
586
|
+
* task.on(TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_REMOVE_FAILED, (task: ITask) => {
|
|
587
|
+
* console.log('Campaign preview remove failed:', task.data.interactionId);
|
|
588
|
+
* // Handle remove failure
|
|
589
|
+
* });
|
|
590
|
+
* ```
|
|
591
|
+
*/
|
|
592
|
+
TASK_CAMPAIGN_PREVIEW_REMOVE_FAILED = 'task:campaignPreviewRemoveFailed',
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* Triggered when a campaign contact is updated (e.g., after skip or remove, when the next contact is offered)
|
|
596
|
+
* @example
|
|
597
|
+
* ```typescript
|
|
598
|
+
* task.on(TASK_EVENTS.TASK_CAMPAIGN_CONTACT_UPDATED, (task: ITask) => {
|
|
599
|
+
* console.log('Campaign contact updated:', task.data.interactionId);
|
|
600
|
+
* // Handle updated campaign contact (e.g., display next contact)
|
|
601
|
+
* });
|
|
602
|
+
* ```
|
|
603
|
+
*/
|
|
604
|
+
TASK_CAMPAIGN_CONTACT_UPDATED = 'task:campaignContactUpdated',
|
|
557
605
|
}
|
|
558
606
|
|
|
559
607
|
/**
|
|
@@ -702,6 +750,14 @@ export type Interaction = {
|
|
|
702
750
|
fcDesktopView?: string;
|
|
703
751
|
/** Agent ID who initiated the outdial call */
|
|
704
752
|
outdialAgentId?: string;
|
|
753
|
+
/** Indicates if the skip action is disabled for campaign preview contacts */
|
|
754
|
+
campaignPreviewSkipDisabled?: string;
|
|
755
|
+
/** Indicates if the remove action is disabled for campaign preview contacts */
|
|
756
|
+
campaignPreviewRemoveDisabled?: string;
|
|
757
|
+
/** Auto-action to perform when campaign preview offer times out (ACCEPT, SKIP, REMOVE) */
|
|
758
|
+
campaignPreviewAutoAction?: string;
|
|
759
|
+
/** Timestamp (ms) when the campaign preview offer expires */
|
|
760
|
+
campaignPreviewOfferTimeout?: string;
|
|
705
761
|
};
|
|
706
762
|
/** Main interaction identifier for related interactions */
|
|
707
763
|
mainInteractionId?: string;
|