Skip to content

feat (contact-center): add accept campaign preview api#4786

Merged
rsarika merged 9 commits intowebex:nextfrom
cmullenx:next
Mar 19, 2026
Merged

feat (contact-center): add accept campaign preview api#4786
rsarika merged 9 commits intowebex:nextfrom
cmullenx:next

Conversation

@cmullenx
Copy link
Copy Markdown
Contributor

@cmullenx cmullenx commented Mar 16, 2026

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

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update
  • Tooling change
  • Internal code refactor

The following scenarios were tested

  • all existing unit tests pass
  • new unit tests added to test apis
  • manually tested ability to accept campaign preview

The GAI Coding Policy And Copyright Annotation Best Practices

  • GAI was not used (or, no additional notation is required)
  • Code was generated entirely by GAI
  • GAI was used to create a draft that was subsequently customized or modified
  • Coder created a draft manually that was non-substantively modified by GAI (e.g., refactoring was performed by GAI on manually written code)
  • Tool used for AI assistance (GitHub Copilot / Other - specify)
    • Github Copilot
    • Other - Please Specify
  • This PR is related to
    • Feature
    • Defect fix
    • Tech Debt
    • Automation

I certified that

  • I have read and followed contributing guidelines
  • I discussed changes with code owners prior to submitting this pull request
  • I have not skipped any automated checks
  • All existing and new tests passed
  • I have updated the documentation accordingly

Make sure to have followed the contributing guidelines before submitting.

@cmullenx cmullenx requested a review from a team as a code owner March 16, 2026 22:03
@cmullenx cmullenx changed the title feat feat (contact-center): add campaign preview apis Mar 16, 2026
@cmullenx cmullenx added the validated If the pull request is validated for automation. label Mar 16, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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}`,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Contributor

@akulakum akulakum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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}`,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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(),
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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`
  );
});

@cmullenx cmullenx changed the title feat (contact-center): add campaign preview apis feat (contact-center): add accept campaign preview api Mar 18, 2026

} catch (error) {
console.error('Failed to load outdial ANI entries:', error);
alert('Failed to load outdial ANI entries', error)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 = '';
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cleaning up campaign input fields when task ends

this.webSocketManager.on('message', this.onMessage.bind(this));
}

/**
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added docs to all the functions in here because they were harder to understand w/out context

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment on lines +82 to +89
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},
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Comment on lines +265 to +267
if (task) {
task = this.updateTaskData(task, payload.data);
task.emit(TASK_EVENTS.TASK_ASSIGNED, task);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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) */
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment on lines +535 to +537
task = new Task(
this.contact,
this.webCallingService,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Comment on lines +535 to +543
task = new Task(
this.contact,
this.webCallingService,
{
...payload.data,
wrapUpRequired: false,
isConferenceInProgress: false,
isAutoAnswering: false,
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown

@brain-frog brain-frog left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, one suggestion

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 });
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment on lines +98 to +103
notifFail: {
bind: {
type: TASK_MESSAGE_TYPE,
data: {type: CC_EVENTS.CAMPAIGN_PREVIEW_ACCEPT_FAILED, campaignId: p.data.campaignId},
},
errId: 'Service.aqm.dialer.acceptPreviewContact',
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Comment on lines +535 to +539
task = new Task(
this.contact,
this.webCallingService,
{
...payload.data,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here. please revert the code changes

@rsarika rsarika merged commit 84e9dea into webex:next Mar 19, 2026
11 checks passed
fnowakow pushed a commit to fnowakow/webex-js-sdk that referenced this pull request Mar 20, 2026
Co-authored-by: Christina Mullen <chrmulle@cisco.com>
Co-authored-by: Kesari3008 <65543166+Kesari3008@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

validated If the pull request is validated for automation.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants