Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
354e58e
docs(ai-docs): add docs for meetings widget (#642)
riteshfyi Mar 31, 2026
bb3bea7
fix(meetings): address version resolution (#664)
riteshfyi Apr 8, 2026
571199d
feat(changelog): Enable viewing commit history between two widgets st…
vivekv1504 Apr 8, 2026
a477740
fix(cc-components): show dialed number instead of entrypoint for outd…
bhabalan Apr 8, 2026
37259ea
feat(ci): add comment on pr workflow (#663)
riteshfyi Apr 15, 2026
f566438
feat(ccwidgets): merge rtt with next (#666)
Kesari3008 Apr 15, 2026
f4e696c
feat(task): Create CampaginErrorDialog Component (#670)
brain-frog Apr 15, 2026
158a20d
fix(changelog): correct variable declarations in populatePackageNames…
vivekv1504 Apr 16, 2026
8642d32
chore(cc): bump @webex/contact-center to 3.12.0-next.14 (#672)
bhabalan Apr 16, 2026
64a3f77
chore: add spec-drift detection commands and pre-commit hook (#649)
ciscoRankush Apr 17, 2026
0543142
feat(agents): claude agents and commands for git operations, QA test …
bhabalan Apr 24, 2026
42ee76e
fix(claude): correct hook schema in settings.json (#677)
bhabalan Apr 27, 2026
273a2bf
fix(ci): prevent command injection vulnerability in PR title validati…
vamshigovardhana Apr 28, 2026
ebb6ca2
feat(ci): add Webex space notification for widgets (#675)
vamshigovardhana Apr 29, 2026
0566ab1
feat(callcontrolcad): update to support global variables (#679)
cmullenx Apr 29, 2026
d027c4c
chore(cc-components): migrate OutdialCall SelectNext to momentum-desi…
bhabalan Apr 30, 2026
881fef4
feat(task): CAI-7811 Campaign Countdown (#682)
brain-frog Apr 30, 2026
b1189c7
chore(ci): serialize E2E test runs across PRs (#678)
bhabalan May 6, 2026
26a6dbe
chore(cc): bump @webex/contact-center to 3.12.0-next.42 (#686)
bhabalan May 19, 2026
6be4607
fix(cc-components): correct recording state during hold/unhold operat…
bhabalan May 19, 2026
bc41a9f
feat(cc): add campaign preview (#684)
cmullenx May 27, 2026
e320745
chore(ci): add no-op next deploy comment (#699)
rsarika Jun 12, 2026
ed77827
chore(tooling): remove validated label on each push (#701)
mkesavan13 Jun 15, 2026
663db9c
fix(cc-widgets): Add timeout inactivity properties to cc store (#695)
zachraymer Jun 15, 2026
5da6077
chore(meetings): update meetings e2e test workflow (#705)
riteshfyi Jul 1, 2026
06040d9
fix(tooling): remove caching from github pipeline (#715)
Shreyas281299 Jul 3, 2026
c7ca9f6
fix(store): merge origin/next into task-refactor
akulakum Jul 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,8 @@ run-name: ${{ github.actor }} is running Deploy CD
on:
push:
branches: # Allow list of deployable tags and branches. Note that all allow-listed branches cannot include any `/` characters
<<<<<<< next-in-task-refactor
- next # primary deploy branch
=======
- next
- task-refactor
>>>>>>> task-refactor

env:
rid: ${{ github.run_id }}-${{ github.run_number }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@
margin-top: 0.5rem;
}

.cad-global-variables {
margin-top: 0.5rem;
}

/* On-hold chip styling */
.on-hold {
display: flex;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ import {Avatar, Brandvisual, Icon, Tooltip, Button} from '@momentum-design/compo
import './call-control-cad.styles.scss';
import TaskTimer from '../TaskTimer/index';
import CallControlConsultComponent from '../CallControl/CallControlCustom/call-control-consult';
import {MEDIA_CHANNEL as MediaChannelType, CallControlComponentProps, CallAssociatedDataMap} from '../task.types';
import {
MEDIA_CHANNEL as MediaChannelType,
CallControlComponentProps,
getCallerIdentifier,
CallAssociatedDataMap,
} from '../task.types';
import {getAgentViewableGlobalVariables} from '../Task/task.utils';
import GlobalVariablesPanel from '../GlobalVariablesPanel/global-variables-panel';

Expand Down Expand Up @@ -79,11 +84,7 @@ const CallControlCADComponent: React.FC<CallControlComponentProps> = (props) =>
const customerName = currentTask?.data?.interaction?.callAssociatedDetails?.customerName;

const ani = currentTask?.data?.interaction?.callAssociatedDetails?.ani;
const isOutdial = currentTask?.data?.interaction?.outboundType === 'OUTDIAL';
const dnis =
currentTask?.data?.interaction?.callAssociatedDetails?.dnis ||
currentTask?.data?.interaction?.callProcessingDetails?.dnis;
const displayNumber = isOutdial ? dnis || ani : ani;
const dn = currentTask?.data?.interaction?.callAssociatedDetails?.dn;

const callAssociatedData = currentTask?.data?.interaction?.callAssociatedData as CallAssociatedDataMap | undefined;
const latestGlobalVariables = getAgentViewableGlobalVariables(callAssociatedData);
Expand Down Expand Up @@ -116,8 +117,11 @@ const CallControlCADComponent: React.FC<CallControlComponentProps> = (props) =>
// For telephony calls, ani is the originating number and dn is the destination.
// Inbound: ani = caller's number, dn = entry point dialed by caller
// Outdial: ani = agent's originating number (entry point), dn = customer's dialed number
const outboundType = currentTask?.data?.interaction?.outboundType;
const callerNumber = getCallerIdentifier(ani, dn, outboundType);

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 Restore the dnis fallback for outdial caller IDs

When an outdial task has the customer's dialed number in callAssociatedDetails.dnis or callProcessingDetails.dnis but does not populate callAssociatedDetails.dn, this now passes undefined as dn and getCallerIdentifier falls back to ani, which is the agent/entry-point number for outdial calls. The previous code, and the TaskList/IncomingTask utilities, preserve the dnis fallback, so those outdial calls will show the wrong caller identifier in CallControlCAD.

Useful? React with 👍 / 👎.


const renderCustomerName = () => {
const customerText = isSocial ? customerName || NO_CUSTOMER_NAME : displayNumber || NO_CALLER_ID;
const customerText = isSocial ? customerName || NO_CUSTOMER_NAME : callerNumber || NO_CALLER_ID;

const textComponent = (
<Text
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const TaskListComponent: React.FunctionComponent<TaskListComponentProps> = (prop
const tasks = getTasksArray(taskList!);

// Only one campaign preview should appear — pick the most recent active one
const activeCampaignId = hasCampaignPreviewEnabled ? getActiveCampaignPreviewId(tasks) : null;
const activeCampaignId = hasCampaignPreviewEnabled ? getActiveCampaignPreviewId(tasks, agentId) : null;

return (
<ul className="task-list" data-testid="task-list">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,11 @@ export const hasAgentJoinedTask = (task: ITask, agentId: string | undefined): bo
return participants?.[agentId]?.hasJoined === true;
};
/**
* Returns the interactionId of the most recent campaign preview task.
* Only one campaign preview should be visible at a time; a newer incoming
* offer supersedes an older hydrated preview.
* Returns the interactionId of the most recent active campaign preview task
* that the agent has joined. Only one campaign preview should be visible at a time.
*/
export const getActiveCampaignPreviewId = (tasks: ITask[]): string | null => {
const activePreviews = tasks.filter((t) => isCampaignPreviewTask(t));
export const getActiveCampaignPreviewId = (tasks: ITask[], agentId: string | undefined): string | null => {
const activePreviews = tasks.filter((t) => isCampaignPreviewTask(t) && hasAgentJoinedTask(t, agentId));
if (activePreviews.length === 0) return null;
// Pick the most recent by createdTimestamp
activePreviews.sort(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import React from 'react';
import {render} from '@testing-library/react';
import CallControlCADComponent from '../../../../src/components/task/CallControlCAD/call-control-cad';
import {CallControlComponentProps, TARGET_TYPE, OUTBOUND_TYPE} from '../../../../src/components/task/task.types';
import {
CallControlComponentProps,
TARGET_TYPE,
OUTBOUND_TYPE,
CallAssociatedDataMap,
} from '../../../../src/components/task/task.types';
import {
mockTask,
mockCallAssociatedData,
createEnabledMainTaskUIControls,
createMockTaskUIControls,
enabledControl,
Expand Down Expand Up @@ -249,7 +255,7 @@ describe('CallControlCADComponent', () => {
chatConsultScreen.unmount();
});

it.skip('should display correct phone number for inbound vs outdial calls', () => {
it('should display correct phone number for inbound vs outdial calls', () => {
// Inbound call: caller ID = ani, phone number = ani
const inboundScreen = render(<CallControlCADComponent {...defaultProps} />);
// ani (555-123-4567) should appear as both caller ID and phone number
Expand Down Expand Up @@ -513,4 +519,92 @@ describe('CallControlCADComponent', () => {
expect(screen.queryByTestId('call-control:participants-trigger')).not.toBeInTheDocument();
});
});

describe('Global Variables', () => {
const makePropsWithCallAssociatedData = (callAssociatedData: CallAssociatedDataMap) => ({
...defaultProps,
currentTask: {
...defaultProps.currentTask,
data: {
...defaultProps.currentTask.data,
interaction: {
...defaultProps.currentTask.data.interaction,
callAssociatedData,
},
},
},
});

it('should render agent-viewable global variables', () => {
const screen = render(<CallControlCADComponent {...makePropsWithCallAssociatedData(mockCallAssociatedData)} />);

const globalVarsContainer = screen.getByTestId('global-variables-panel');
expect(globalVarsContainer).toBeInTheDocument();

expect(screen.getByText('Customer Language:')).toBeInTheDocument();
expect(screen.getByText('English')).toBeInTheDocument();

expect(screen.getByText('Post Call Survey Opt-in:')).toBeInTheDocument();
expect(screen.getByText('true')).toBeInTheDocument();
});

it('should not render non-global variables (e.g. system CAD like ani)', () => {
const screen = render(<CallControlCADComponent {...makePropsWithCallAssociatedData(mockCallAssociatedData)} />);

// ani is a system CAD key, filtered out by getAgentViewableGlobalVariables
expect(screen.queryByText('ani:')).not.toBeInTheDocument();
});

it('should not render global variables where agentViewable is false', () => {
const screen = render(<CallControlCADComponent {...makePropsWithCallAssociatedData(mockCallAssociatedData)} />);

// Global_Hidden has agentViewable: false
expect(screen.queryByText('Hidden Variable:')).not.toBeInTheDocument();
});

it('should not render global variables section when no global variables exist', () => {
const screen = render(<CallControlCADComponent {...defaultProps} />);

expect(screen.queryByTestId('global-variables-panel')).not.toBeInTheDocument();
});

it('should not render global variables section when callAssociatedData is undefined', () => {
const propsWithNoData = {
...defaultProps,
currentTask: {
...defaultProps.currentTask,
data: {
...defaultProps.currentTask.data,
interaction: {
...defaultProps.currentTask.data.interaction,
},
},
},
};
const screen = render(<CallControlCADComponent {...propsWithNoData} />);

expect(screen.queryByTestId('global-variables-panel')).not.toBeInTheDocument();
});

it('should use variable name as label when displayName is empty', () => {
const dataWithEmptyDisplayName: CallAssociatedDataMap = {
Global_NoDisplay: {
name: 'Global_NoDisplay',
displayName: '',
value: 'some value',
type: 'STRING',
agentEditable: false,
agentViewable: true,
global: true,
isSecure: false,
secureKeyId: '',
secureKeyVersion: 0,
},
};
const screen = render(<CallControlCADComponent {...makePropsWithCallAssociatedData(dataWithEmptyDisplayName)} />);

expect(screen.getByText('Global_NoDisplay:')).toBeInTheDocument();
expect(screen.getByText('some value')).toBeInTheDocument();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ describe('incoming-task.utils', () => {
mockTask.data.interaction.outboundType = originalOutboundType;
});

it.skip('should extract correct button states for outdial telephony on non-browser', () => {
it('should extract correct button states for outdial telephony on non-browser', () => {
const originalMediaType = mockTask.data.interaction.mediaType;
const originalCallAssociatedDetails = mockTask.data.interaction.callAssociatedDetails;
const originalOutboundType = mockTask.data.interaction.outboundType;
Expand All @@ -266,7 +266,7 @@ describe('incoming-task.utils', () => {
ronaTimeout: '30',
};

const result = extractIncomingTaskData(mockTask, false);
const result = extractIncomingTaskData(mockTask, logger, visibleDisabledAccept, disabledControl, false, false);

expect(result.title).toBe('+14155559876');
expect(result.acceptText).toBe('Ringing...');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,7 @@ describe('task-list.utils', () => {
mockTask.data.interaction.outboundType = originalOutboundType;
});

it.skip('should extract correct button states for incoming outdial telephony on non-browser', () => {
it('should extract correct button states for incoming outdial telephony on non-browser', () => {
// Mock isIncomingTask to return true for this test
(isIncomingTask as jest.Mock).mockReturnValueOnce(true);

Expand All @@ -442,6 +442,10 @@ describe('task-list.utils', () => {
virtualTeamName: 'Outbound Team',
ronaTimeout: '30',
};
mockTask.uiControls = createEnabledMainTaskUIControls({
accept: {isVisible: true, isEnabled: false},
decline: disabledControl,
});

const result = extractTaskListItemData(mockTask, mockTask.data.agentId, undefined, undefined, false);

Expand Down
10 changes: 10 additions & 0 deletions packages/contact-center/store/src/storeEventsWrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -585,6 +585,15 @@ class StoreWrapper implements IStoreWrapper {
};

handleCampaignPreviewReservation = (event: ITask) => {
// Only track accepted campaign IDs for actual preview campaigns.
// Predictive/progressive campaigns don't need preview-accept tracking.
if (this.isCampaignPreview(event)) {
const interactionId = event?.data?.interactionId;
if (interactionId) {
this.addAcceptedCampaign(interactionId);
}
}

const isCampaignPreview = this.isCampaignPreview(event);

runInAction(() => {
Expand Down Expand Up @@ -775,6 +784,7 @@ class StoreWrapper implements IStoreWrapper {
task.on(CC_EVENTS.REAL_TIME_TRANSCRIPTION, this.realtimeTranscriptionListeners[taskId]);
}

// Register media event listener for browser devices
if (this.deviceType === DEVICE_TYPE_BROWSER) {
task.on(TASK_EVENTS.TASK_MEDIA, this.handleTaskMedia);
}
Expand Down
4 changes: 2 additions & 2 deletions packages/contact-center/store/tests/storeEventsWrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2476,15 +2476,15 @@ describe('storeEventsWrapper', () => {
});

describe('handleCampaignPreviewReservation — campaign type branching', () => {
it.skip('should add to acceptedCampaignIds for standard preview campaign', () => {
it('should add to acceptedCampaignIds for standard preview campaign', () => {
const task = createCampaignPreviewTask('preview-accept-1');

storeWrapper.handleCampaignPreviewReservation(task);

expect(storeWrapper['store'].acceptedCampaignIds.has('preview-accept-1')).toBe(true);
});

it.skip('should NOT add to acceptedCampaignIds for predictive campaign', () => {
it('should NOT add to acceptedCampaignIds for predictive campaign', () => {
const task = makeMockTask({
data: {
interactionId: 'predictive-accept-1',
Expand Down
Loading