diff --git a/packages/contact-center/cc-components/src/components/task/CallControlCAD/call-control-cad.styles.scss b/packages/contact-center/cc-components/src/components/task/CallControlCAD/call-control-cad.styles.scss index abfe8e114..a1604fc82 100644 --- a/packages/contact-center/cc-components/src/components/task/CallControlCAD/call-control-cad.styles.scss +++ b/packages/contact-center/cc-components/src/components/task/CallControlCAD/call-control-cad.styles.scss @@ -24,6 +24,7 @@ .cad-global-variables { margin-top: 0.5rem; + max-height: none; } /* On-hold chip styling */ diff --git a/packages/contact-center/cc-components/src/components/task/CallControlCAD/call-control-cad.tsx b/packages/contact-center/cc-components/src/components/task/CallControlCAD/call-control-cad.tsx index 47d4c2249..da3481c5a 100644 --- a/packages/contact-center/cc-components/src/components/task/CallControlCAD/call-control-cad.tsx +++ b/packages/contact-center/cc-components/src/components/task/CallControlCAD/call-control-cad.tsx @@ -82,14 +82,16 @@ const CallControlCADComponent: React.FC = (props) => const callAssociatedData = currentTask?.data?.interaction?.callAssociatedData as CallAssociatedDataMap | undefined; const latestGlobalVariables = getAgentViewableGlobalVariables(callAssociatedData); - // Persist global variables across task updates — some store refreshes - // replace currentTask with a snapshot that omits callAssociatedData, - // which causes getAgentViewableGlobalVariables to return []. - // We intentionally keep the previous values when length === 0 because - // an empty array indicates missing data, not a legitimate clearing of - // variables. Variables are never cleared mid-call by the backend. - // Reset when the interaction changes so stale CAD from a previous task - // is never shown on a new call. + // Persist and accumulate global variables across task updates. + // Each websocket event may only carry a subset of the full + // callAssociatedData, so we merge new variables into the ref by name + // instead of replacing the whole array. This ensures all global + // variables seen during the interaction are displayed — matching the + // regular desktop behaviour. + // When length === 0 we keep previous values (missing data, not a + // legitimate clearing — variables are never cleared mid-call). + // Reset when the interaction changes so stale CAD from a previous + // task is never shown on a new call. const interactionId = currentTask.data.interaction.interactionId; const globalVariablesRef = useRef(latestGlobalVariables); const prevInteractionIdRef = useRef(interactionId); @@ -97,7 +99,9 @@ const CallControlCADComponent: React.FC = (props) => prevInteractionIdRef.current = interactionId; globalVariablesRef.current = latestGlobalVariables; } else if (latestGlobalVariables.length > 0) { - globalVariablesRef.current = latestGlobalVariables; + const existingMap = new Map(globalVariablesRef.current.map((v) => [v.name, v])); + latestGlobalVariables.forEach((v) => existingMap.set(v.name, v)); + globalVariablesRef.current = Array.from(existingMap.values()); } const globalVariables = globalVariablesRef.current; diff --git a/packages/contact-center/cc-components/src/components/task/CampaignTask/CampaignTaskPopover/campaign-task-popover.tsx b/packages/contact-center/cc-components/src/components/task/CampaignTask/CampaignTaskPopover/campaign-task-popover.tsx index 597e8a87c..e4676e50c 100644 --- a/packages/contact-center/cc-components/src/components/task/CampaignTask/CampaignTaskPopover/campaign-task-popover.tsx +++ b/packages/contact-center/cc-components/src/components/task/CampaignTask/CampaignTaskPopover/campaign-task-popover.tsx @@ -42,9 +42,10 @@ const CampaignTaskPopover: React.FC = ({ .callAssociatedData; const latestGlobalVariables = getAgentViewableGlobalVariables(callAssociatedData); - // Persist global variables across task updates — some store refreshes - // replace the task with a snapshot that omits callAssociatedData, - // which causes getAgentViewableGlobalVariables to return []. + // Persist and accumulate global variables across task updates. + // Each websocket event may only carry a subset of the full + // callAssociatedData, so we merge new variables into the ref by name + // instead of replacing the whole array. // Reset when the interaction changes so stale CAD from a previous // contact is never shown on the next preview. const interactionId = task.data.interactionId; @@ -54,7 +55,9 @@ const CampaignTaskPopover: React.FC = ({ prevInteractionIdRef.current = interactionId; globalVariablesRef.current = latestGlobalVariables; } else if (latestGlobalVariables.length > 0) { - globalVariablesRef.current = latestGlobalVariables; + const existingMap = new Map(globalVariablesRef.current.map((v) => [v.name, v])); + latestGlobalVariables.forEach((v) => existingMap.set(v.name, v)); + globalVariablesRef.current = Array.from(existingMap.values()); } const globalVariables = globalVariablesRef.current; diff --git a/packages/contact-center/cc-components/src/components/task/CampaignTask/campaign-task.tsx b/packages/contact-center/cc-components/src/components/task/CampaignTask/campaign-task.tsx index c13d48dd8..711264857 100644 --- a/packages/contact-center/cc-components/src/components/task/CampaignTask/campaign-task.tsx +++ b/packages/contact-center/cc-components/src/components/task/CampaignTask/campaign-task.tsx @@ -51,17 +51,25 @@ const CampaignTask: React.FC = ({ .callAssociatedData; const latestGlobalVariables = getAgentViewableGlobalVariables(callAssociatedData); - // Persist global variables across task updates — some store refreshes - // replace the task with a snapshot that omits callAssociatedData. - // Reset when the interaction changes so stale CAD from a previous task - // is never shown on a new call. + // Persist and accumulate global variables across task updates. + // Each websocket event may only carry a subset of the full + // callAssociatedData, so we merge new variables into the ref by name + // instead of replacing the whole array. This ensures all global + // variables seen during the interaction are displayed — matching the + // regular desktop behaviour. + // When length === 0 we keep previous values (missing data, not a + // legitimate clearing — variables are never cleared mid-call). + // Reset when the interaction changes so stale CAD from a previous + // task is never shown on a new call. const globalVariablesRef = useRef(latestGlobalVariables); const prevInteractionIdRef = useRef(interactionId); if (prevInteractionIdRef.current !== interactionId) { prevInteractionIdRef.current = interactionId; globalVariablesRef.current = latestGlobalVariables; } else if (latestGlobalVariables.length > 0) { - globalVariablesRef.current = latestGlobalVariables; + const existingMap = new Map(globalVariablesRef.current.map((v) => [v.name, v])); + latestGlobalVariables.forEach((v) => existingMap.set(v.name, v)); + globalVariablesRef.current = Array.from(existingMap.values()); } const globalVariables = globalVariablesRef.current; diff --git a/packages/contact-center/cc-components/tests/components/task/CallControlCAD/call-control-cad.tsx b/packages/contact-center/cc-components/tests/components/task/CallControlCAD/call-control-cad.tsx index a3c6a7d30..a8e302b4c 100644 --- a/packages/contact-center/cc-components/tests/components/task/CallControlCAD/call-control-cad.tsx +++ b/packages/contact-center/cc-components/tests/components/task/CallControlCAD/call-control-cad.tsx @@ -462,5 +462,130 @@ describe('CallControlCADComponent', () => { expect(screen.getByText('Global_NoDisplay:')).toBeInTheDocument(); expect(screen.getByText('some value')).toBeInTheDocument(); }); + + it('should accumulate global variables across re-renders (CAI-8143)', () => { + const firstPayload: CallAssociatedDataMap = { + Global_Language: { + name: 'Global_Language', + displayName: 'Customer Language', + value: 'English', + type: 'STRING', + agentEditable: false, + agentViewable: true, + global: true, + isSecure: false, + secureKeyId: '', + secureKeyVersion: 0, + }, + }; + + const secondPayload: CallAssociatedDataMap = { + Global_FeedbackSurveyOptIn: { + name: 'Global_FeedbackSurveyOptIn', + displayName: 'Post Call Survey Opt-in', + value: 'true', + type: 'STRING', + agentEditable: false, + agentViewable: true, + global: true, + isSecure: false, + secureKeyId: '', + secureKeyVersion: 0, + }, + }; + + // First render with only Global_Language + const screen = render(); + expect(screen.getByText('Customer Language:')).toBeInTheDocument(); + expect(screen.getByText('English')).toBeInTheDocument(); + + // Re-render with only Global_FeedbackSurveyOptIn (simulating a new websocket payload) + screen.rerender(); + + // Both variables should now be visible (merged, not replaced) + 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 update existing variable values when re-rendered with new data (CAI-8143)', () => { + const initialPayload: CallAssociatedDataMap = { + Global_Language: { + name: 'Global_Language', + displayName: 'Customer Language', + value: 'English', + type: 'STRING', + agentEditable: false, + agentViewable: true, + global: true, + isSecure: false, + secureKeyId: '', + secureKeyVersion: 0, + }, + }; + + const updatedPayload: CallAssociatedDataMap = { + Global_Language: { + name: 'Global_Language', + displayName: 'Customer Language', + value: 'Spanish', + type: 'STRING', + agentEditable: false, + agentViewable: true, + global: true, + isSecure: false, + secureKeyId: '', + secureKeyVersion: 0, + }, + }; + + const screen = render(); + expect(screen.getByText('English')).toBeInTheDocument(); + + // Re-render with updated value for the same variable + screen.rerender(); + expect(screen.getByText('Spanish')).toBeInTheDocument(); + expect(screen.queryByText('English')).not.toBeInTheDocument(); + }); + + it('should reset global variables when interaction changes (CAI-8143)', () => { + const payload: CallAssociatedDataMap = { + Global_Language: { + name: 'Global_Language', + displayName: 'Customer Language', + value: 'English', + type: 'STRING', + agentEditable: false, + agentViewable: true, + global: true, + isSecure: false, + secureKeyId: '', + secureKeyVersion: 0, + }, + }; + + const screen = render(); + expect(screen.getByText('Customer Language:')).toBeInTheDocument(); + + // Re-render with a different interaction ID and no global variables + const newInteractionProps = { + ...defaultProps, + currentTask: { + ...defaultProps.currentTask, + data: { + ...defaultProps.currentTask.data, + interaction: { + ...defaultProps.currentTask.data.interaction, + interactionId: 'new-interaction-456', + }, + }, + }, + }; + screen.rerender(); + + // Old variables should be cleared for the new interaction + expect(screen.queryByText('Customer Language:')).not.toBeInTheDocument(); + }); }); });