Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,24 @@
}
}

.global-variables {
display: flex;
flex-flow: row wrap;
justify-content: space-between;
width: 100%;
max-height: 11.25rem;
overflow-y: auto;
margin-top: 0.75rem;

.global-variable-item {
flex-basis: 50%;
min-width: 0;
box-sizing: border-box;
padding: 0.125rem 0;
line-height: 1.75rem;
}
}

/* On-hold chip styling */
.on-hold {
display: flex;
Expand All @@ -44,7 +62,6 @@
}
}


.recording-indicator {
position: absolute;
top: 0.5rem;
Expand Down Expand Up @@ -143,8 +160,6 @@
transform: rotate(180deg);
}



.call-status {
display: flex;
align-items: center;
Expand Down Expand Up @@ -200,11 +215,11 @@
}

.digital-customer-name {
width: auto;
width: auto;
}

.digital-phone-number {
width: auto;
width: auto;
}

@media (min-width: 64.0625rem) and (max-width: 75rem) {
Expand All @@ -230,8 +245,8 @@
max-width: 15rem;
}
.call-control-task-tooltip {
width: 50%;
text-align: center;
width: 50%;
text-align: center;
}
}

Expand All @@ -246,8 +261,8 @@
max-width: 13rem;
}
.call-control-task-tooltip {
width: 50%;
text-align: center;
width: 50%;
text-align: center;
}
}

Expand All @@ -262,27 +277,27 @@
max-width: 12rem;
}
.call-control-task-tooltip {
width: 50%;
text-align: center;
width: 50%;
text-align: center;
}
}

@media (min-width: 15.625rem) and (max-width: 20rem){
@media (min-width: 15.625rem) and (max-width: 20rem) {
.call-control-task-tooltip {
width: 50%;
text-align: center;
}
}

.participants-popover {
background: var(--mds-color-theme-background-solid-primary-normal, #FFFFFF) !important;
background: var(--mds-color-theme-background-solid-primary-normal, #ffffff) !important;
align-items: normal;
width: 100%;
padding: 0 !important;
border: var(--mds-color-theme-outline-secondary-normal);

&::part(popover-content) {
background: var(--mds-color-theme-background-solid-primary-normal, #FFFFFF);
background: var(--mds-color-theme-background-solid-primary-normal, #ffffff);
border-radius: 0.75rem;
border: 1px solid var(--mds-color-theme-outline-secondary-normal);
box-shadow: 0 0.25rem 0.75rem rgba(0, 0, 0, 0.08);
Expand All @@ -307,7 +322,7 @@
padding: 0.4rem 0.6rem;
font-size: 0.875rem;
cursor: pointer;
color: var(--mds-color-theme-text-primary-normal, #000000F2);
color: var(--mds-color-theme-text-primary-normal, #000000f2);
border-radius: 0.5rem;
min-width: 0;
max-width: 100%;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@ import {Brandvisual, Icon, Tooltip, Button} from '@momentum-design/components/di
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, getCallerIdentifier} from '../task.types';
import {
MEDIA_CHANNEL as MediaChannelType,
CallControlComponentProps,
getCallerIdentifier,
CallAssociatedDataMap,
} from '../task.types';
import {getAgentViewableGlobalVariables} from '../Task/task.utils';

import {getMediaTypeInfo} from '../../../utils';
import {
Expand Down Expand Up @@ -69,6 +75,10 @@ const CallControlCADComponent: React.FC<CallControlComponentProps> = (props) =>
//@ts-expect-error To be fixed in SDK - https://jira-eng-sjc12.cisco.com/jira/browse/CAI-6762
const dn = currentTask?.data?.interaction?.callAssociatedDetails?.dn;

//@ts-expect-error To be fixed in SDK - https://jira-eng-sjc12.cisco.com/jira/browse/CAI-6762
const callAssociatedData = currentTask?.data?.interaction?.callAssociatedData as CallAssociatedDataMap | undefined;
const globalVariables = getAgentViewableGlobalVariables(callAssociatedData);

// Create unique IDs for tooltips
const customerNameTriggerId = `customer-name-trigger-${currentTask.data.interaction.interactionId}`;
const customerNameTooltipId = `customer-name-tooltip-${currentTask.data.interaction.interactionId}`;
Expand Down Expand Up @@ -275,6 +285,24 @@ const CallControlCADComponent: React.FC<CallControlComponentProps> = (props) =>
</Text>
{renderPhoneNumber()}
</div>
{globalVariables.length > 0 && (
<div className="global-variables" data-testid="cc-cad:global-variables">
{globalVariables.map((variable) => (
<div
key={variable.name}
className="global-variable-item"
data-testid={`cc-cad:global-var-${variable.name}`}
>
<Text type="body-secondary" tagName={'small'}>
{variable.displayName || variable.name}
</Text>
<Text type="body-secondary" tagName={'small'}>
{variable.value || ''}
</Text>
</div>
))}
</div>
)}
</div>
{controlVisibility.isConsultInitiatedOrAccepted && !controlVisibility.wrapup.isVisible && (
<div className={`call-control-consult-container ${callControlConsultClassName || ''}`}>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,43 @@
import type {MEDIA_CHANNEL as MediaChannelType, TaskComponentData} from '../task.types';
import type {
MEDIA_CHANNEL as MediaChannelType,
TaskComponentData,
CADVariable,
CallAssociatedDataMap,
} from '../task.types';
import {getMediaTypeInfo} from '../../../utils';

/** System CAD variable keys that are already displayed elsewhere in the UI. */
export const SYSTEM_CAD_KEYS = new Set([
'ani',
'dn',
'customerName',
'virtualTeamName',
'ronaTimeout',
'FC-DESKTOP-VIEW',
]);

/**
* Returns agent-viewable global variables from a callAssociatedData map,
* excluding system variables that are already rendered elsewhere.
*/
export const getAgentViewableGlobalVariables = (
callAssociatedData: CallAssociatedDataMap | undefined
): CADVariable[] => {
if (!callAssociatedData || typeof callAssociatedData !== 'object') {
return [];
}

return Object.entries(callAssociatedData)
.filter(([key, cadVar]) => {
if (!cadVar || !cadVar.name) return false;
if (cadVar.agentViewable === false) return false;
if (!cadVar.global) return false;
if (SYSTEM_CAD_KEYS.has(key)) return false;
return true;
})
.map(([, cadVar]) => cadVar);
};

/**
* Capitalizes the first word of a string
* @param str - The string to capitalize
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,29 @@ import {

type Enum<T extends Record<string, unknown>> = T[keyof T];

/**
* Represents a single Call Associated Data (CAD) variable on an interaction.
* Global variables have `global: true` and are set by flow control.
*/
export interface CADVariable {
name: string;
displayName: string;
value: string;
type: string;
agentEditable: boolean;
agentViewable: boolean;
global: boolean;
isSecure: boolean;
secureKeyId: string;
secureKeyVersion: number;
}

/**
* Record of CAD variables keyed by variable name.
* This is the shape of `callAssociatedData` on the interaction at runtime.
*/
export type CallAssociatedDataMap = Record<string, CADVariable>;

/**
* Target types for consult/transfer operations
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
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 {mockTask} from '@webex/test-fixtures';
import {
CallControlComponentProps,
TARGET_TYPE,
OUTBOUND_TYPE,
CallAssociatedDataMap,
} from '../../../../src/components/task/task.types';
import {mockTask, mockCallAssociatedData} from '@webex/test-fixtures';
import {BuddyDetails} from '@webex/cc-store';
import '@testing-library/jest-dom';

Expand Down Expand Up @@ -370,4 +375,92 @@ describe('CallControlCADComponent', () => {
const customConsultContainer = customScreen.container.querySelector('.call-control-consult-container');
expect(customConsultContainer).toHaveClass('custom-consult-control');
});

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('cc-cad:global-variables');
expect(globalVarsContainer).toBeInTheDocument();

expect(screen.getByTestId('cc-cad:global-var-Global_Language')).toBeInTheDocument();
expect(screen.getByText('Customer Language')).toBeInTheDocument();
expect(screen.getByText('English')).toBeInTheDocument();

expect(screen.getByTestId('cc-cad:global-var-Global_FeedbackSurveyOptIn')).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)} />);

expect(screen.queryByTestId('cc-cad:global-var-ani')).not.toBeInTheDocument();
});

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

expect(screen.queryByTestId('cc-cad:global-var-Global_Hidden')).not.toBeInTheDocument();
});

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

expect(screen.queryByTestId('cc-cad:global-variables')).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('cc-cad:global-variables')).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();
});
});
});
Loading
Loading