feat (contact-center): add accept campaign preview api#4786
feat (contact-center): add accept campaign preview api#4786rsarika merged 9 commits intowebex:nextfrom
Conversation
- adds outdial aini events metrics to behavioral events - adds helper function to create event objects to reduce code duplication
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 810f455764
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: bae59a17c6
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
packages/@webex/contact-center/src/services/task/TaskManager.ts
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e6f80b4ff1
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| * @returns true if the task is a pending campaign preview reservation, false otherwise | ||
| */ | ||
| export const isCampaignPreviewReservation = (task: ITask): boolean => { | ||
| return task?.data?.type === CC_EVENTS.AGENT_OFFER_CAMPAIGN_RESERVATION; |
There was a problem hiding this comment.
Treat CampaignContactUpdated tasks as preview reservations
isCampaignPreviewReservation() only checks for AgentOfferCampaignReservation, but the CAMPAIGN_CONTACT_UPDATED branch in TaskManager keeps the same preview task alive and re-emits it as TASK_CAMPAIGN_PREVIEW_RESERVATION. After a skip/remove update, that task is no longer filtered in handleIncomingWebCall, so a subsequent WebRTC incoming call can be mapped/emitted as TASK_INCOMING for a still-unaccepted preview task, causing incorrect call-task association and premature ringing.
Useful? React with 👍 / 👎.
| * @ignore | ||
| */ | ||
| acceptPreviewContact: aqm.req((p: {data: Contact.PreviewContactPayload}) => ({ | ||
| url: `${DIALER_API}/campaign/${p.data.campaignId}/preview-task/${p.data.interactionId}${CAMPAIGN_PREVIEW_ACCEPT}`, |
There was a problem hiding this comment.
Encode campaign names before building dialer preview URLs
The preview dialer endpoints interpolate campaignId directly into the URL path, but this field is documented as a campaign name (not a UUID). If the name contains reserved characters (for example spaces, /, #, or ?), accept/skip/remove requests are sent to a malformed path or wrong route, resulting in avoidable API failures for valid campaign names.
Useful? React with 👍 / 👎.
akulakum
left a comment
There was a problem hiding this comment.
Thanks for the PR! The campaign preview feature looks well structured and follows existing patterns nicely. I have a few comments below.
| * @ignore | ||
| */ | ||
| acceptPreviewContact: aqm.req((p: {data: Contact.PreviewContactPayload}) => ({ | ||
| url: `${DIALER_API}/campaign/${p.data.campaignId}/preview-task/${p.data.interactionId}${CAMPAIGN_PREVIEW_ACCEPT}`, |
There was a problem hiding this comment.
URL-encode campaignId (and interactionId) before interpolating into the path.
campaignId is documented as a campaign name, not a UUID. If the name contains reserved URL characters (spaces, /, #, ?, etc.), this produces a malformed path or routes to the wrong endpoint.
Suggested fix — apply to all three preview URLs (accept, skip, remove):
url: `${DIALER_API}/campaign/${encodeURIComponent(p.data.campaignId)}/preview-task/${encodeURIComponent(p.data.interactionId)}${CAMPAIGN_PREVIEW_ACCEPT}`,| }), | ||
| evt: jest.fn() | ||
| evt: jest.fn(), | ||
| }; |
There was a problem hiding this comment.
Missing test for URL encoding of campaignId.
The URL-construction tests only use a clean campaignId (TestCampaignPreview). Once encodeURIComponent is added to the URL building (see comment on dialer.ts), please add at least one test with a campaignId that contains reserved characters (e.g. spaces or /) and assert the path segments are properly encoded.
Example:
it('should encode campaignId with special characters in the URL', () => {
const specialPayload = {
interactionId: 'interaction-123',
campaignId: 'My Campaign/Test',
};
const dialer = aqmDialer(fakeAqm as any);
const config = dialer.acceptPreviewContact({ data: specialPayload }) as any;
expect(config.url).toBe(
`/v1/dialer/campaign/${encodeURIComponent('My Campaign/Test')}/preview-task/interaction-123/accept`
);
});|
|
||
| } catch (error) { | ||
| console.error('Failed to load outdial ANI entries:', error); | ||
| alert('Failed to load outdial ANI entries', error) |
There was a problem hiding this comment.
removed this alert because it was annoying and the log is enough to show that outdial ani doesnt work.
| task.on('task:end', updateTaskList); // Update the task list UI to have latest tasks | ||
| task.on('task:end', () => { | ||
| updateTaskList(); | ||
| document.getElementById('campaign-interaction-id').value = ''; |
There was a problem hiding this comment.
cleaning up campaign input fields when task ends
| this.webSocketManager.on('message', this.onMessage.bind(this)); | ||
| } | ||
|
|
||
| /** |
There was a problem hiding this comment.
added docs to all the functions in here because they were harder to understand w/out context
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e777b042a0
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| type: [CC_EVENTS.AGENT_CONTACT_ASSIGNED, CC_EVENTS.CONTACT_ENDED], | ||
| __typeMap: { | ||
| typeField: 'type', | ||
| conditions: { | ||
| [CC_EVENTS.AGENT_CONTACT_ASSIGNED]: { | ||
| reservationInteractionId: p.data.interactionId, | ||
| }, | ||
| [CC_EVENTS.CONTACT_ENDED]: {interactionId: p.data.interactionId}, |
There was a problem hiding this comment.
Do not resolve preview accepts when the reservation just ends
If the backend closes the preview reservation before it ever emits AgentContactAssigned—for example because the customer is unavailable—this bind still resolves the request as a success because ContactEnded is included in notifSuccess. cc.acceptPreviewContact() then records a success metric and returns to the caller even though no outbound conversation was established, so clients cannot distinguish an immediate failure from a real accept.
Useful? React with 👍 / 👎.
| if (task) { | ||
| task = this.updateTaskData(task, payload.data); | ||
| task.emit(TASK_EVENTS.TASK_ASSIGNED, task); |
There was a problem hiding this comment.
Auto-answer accepted preview calls for browser agents
In Browser/WebRTC mode, this path only rekeys the preview task and emits task:assigned; unlike the AGENT_OFFER_CONTACT/AGENT_OFFER_CONSULT handlers it never calls handleAutoAnswer(task), and the reservation task was created with isAutoAnswering: false. Because Task.accept() is the only code path that invokes answerCall(), accepting a preview contact still requires a second manual task.accept() after the incoming call event, so the new one-click preview flow will not actually connect audio for browser agents.
Useful? React with 👍 / 👎.
| export const CONFERENCE_TRANSFER = '/conference/transfer'; | ||
| export const DIALER_API = '/v1/dialer'; | ||
| export const CAMPAIGN_PREVIEW_ACCEPT = '/accept'; | ||
| /** 80-second timeout for accepting preview contact (outbound call setup takes longer than default 20s) */ |
There was a problem hiding this comment.
this timeout value is consistent with agent desktop: https://sqbu-github.cisco.com/CBABU/wxcc-desktop/blob/main/packages/agentx-services/src/services/aqm/aqm-service-dialer.ts#L82
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d43a9146e4
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| task = new Task( | ||
| this.contact, | ||
| this.webCallingService, |
There was a problem hiding this comment.
Prevent preview reservation tasks from exposing a broken
accept() path
This emits an ordinary Task instance for AgentOfferCampaignReservation, but Task.accept() never calls dialer.acceptPreviewContact(): for browser telephony it only answers an already-existing WebRTC call, and for non-browser telephony it resolves without sending any AQM request (packages/@webex/contact-center/src/services/task/index.ts:336-375). Any client that reuses its existing task.accept() handler for offered tasks will therefore silently fail to accept preview contacts even though the SDK handed it an ITask.
Useful? React with 👍 / 👎.
| task = new Task( | ||
| this.contact, | ||
| this.webCallingService, | ||
| { | ||
| ...payload.data, | ||
| wrapUpRequired: false, | ||
| isConferenceInProgress: false, | ||
| isAutoAnswering: false, | ||
| }, |
There was a problem hiding this comment.
Avoid tearing down the active browser call for preview cleanup
Creating preview reservations as normal telephony Tasks means their terminal events will later flow through handleTaskCleanup(), which unconditionally calls webCallingService.cleanUpCall() for any browser telephony task (packages/@webex/contact-center/src/services/task/TaskManager.ts:723-730). In browser/WebRTC mode, an unaccepted preview reservation never owns a call, so if it expires or is rejected while the agent is already on another call this cleanup will drop the unrelated active call.
Useful? React with 👍 / 👎.
| function getCampaignPreviewPayload() { | ||
| const interactionId = document.getElementById('campaign-interaction-id').value.trim(); | ||
| const campaignId = document.getElementById('campaign-id').value.trim(); | ||
| console.log('[CampaignPreview] getCampaignPreviewPayload:', { interactionId, campaignId }); |
There was a problem hiding this comment.
I think it would make more sense to put this after the following if statement.
Currently if either value is missing, the payload will be logged here and in the warning a moment later which feels unnecessary
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ad5101641a
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| notifFail: { | ||
| bind: { | ||
| type: TASK_MESSAGE_TYPE, | ||
| data: {type: CC_EVENTS.CAMPAIGN_PREVIEW_ACCEPT_FAILED, campaignId: p.data.campaignId}, | ||
| }, | ||
| errId: 'Service.aqm.dialer.acceptPreviewContact', |
There was a problem hiding this comment.
Clean up preview tasks on accept-failure events
acceptPreviewContact() now rejects on the dedicated CampaignPreviewAcceptFailed websocket event, but TaskManager.registerTaskListeners() never handles that new event (unlike AGENT_CONTACT_ASSIGN_FAILED for normal accepts). If the backend reports a preview-accept failure without a follow-up ContactEnded, the reservation task stays in taskCollection and never emits a task-level failure/end signal, so agents can be left with a stale preview reservation after an accept error.
Useful? React with 👍 / 👎.
| task = new Task( | ||
| this.contact, | ||
| this.webCallingService, | ||
| { | ||
| ...payload.data, |
There was a problem hiding this comment.
Avoid exposing
decline() on preview reservations
This creates a normal Task instance for AgentOfferCampaignReservation, which means callers also get the standard task.decline() API. Task.decline() only hangs up the current WebRTC call in services/task/index.ts and never sends any preview skip/remove request, so for a preview reservation (where no call exists yet) clients that reuse their usual “decline offered task” handler cannot dismiss the reservation and will leave it active server-side.
Useful? React with 👍 / 👎.
| * @param bind - The bind object to convert | ||
| * @returns A string representation of the bind object | ||
| */ | ||
| private bindPrint(bind: any): string { |
There was a problem hiding this comment.
can we only have the docs changes, not code changes. this file exact replica of agentdesktop code base. to have parity lets keep it as it is
| * @param msg - The message to check | ||
| * @returns True if the message matches the bind object, false otherwise | ||
| */ | ||
| private bindCheck(bind: any, msg: any): boolean { |
There was a problem hiding this comment.
same here. please revert the code changes
Co-authored-by: Christina Mullen <chrmulle@cisco.com> Co-authored-by: Kesari3008 <65543166+Kesari3008@users.noreply.github.com>
COMPLETES # https://jira-eng-sjc12.cisco.com/jira/browse/CAI-7657
This pull request addresses
the inability to handle campaign previews
by making the following changes
adding the API to accept a campaign
Here is a vidcast: https://app.vidcast.io/share/75e66ce8-c0d9-4175-9282-aef5b9062393
Change Type
The following scenarios were tested
The GAI Coding Policy And Copyright Annotation Best Practices
I certified that
Make sure to have followed the contributing guidelines before submitting.