From 258269f467ce2ca26e3d0a26919fa9a22d08cb19 Mon Sep 17 00:00:00 2001 From: Zach Raymer Date: Tue, 9 Jun 2026 13:52:26 -0500 Subject: [PATCH 1/3] chore: add timeput inactivity properties to cc store --- packages/contact-center/store/src/store.ts | 2 ++ packages/contact-center/store/src/store.types.ts | 2 ++ packages/contact-center/store/src/storeEventsWrapper.ts | 2 ++ packages/contact-center/store/tests/store.ts | 8 +++++++- packages/contact-center/store/tests/storeEventsWrapper.ts | 2 ++ 5 files changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/contact-center/store/src/store.ts b/packages/contact-center/store/src/store.ts index 38de25e04..bb61ed9d2 100644 --- a/packages/contact-center/store/src/store.ts +++ b/packages/contact-center/store/src/store.ts @@ -116,6 +116,8 @@ class Store implements IStore { this.isAddressBookEnabled = Boolean(response.addressBookId); this.allowConsultToQueue = response.allowConsultToQueue; this.agentProfile.agentName = response.agentName; + this.agentProfile.isTimeoutDesktopInactivityEnabled = response.isTimeoutDesktopInactivityEnabled; + this.agentProfile.timeoutDesktopInactivityMins = response.timeoutDesktopInactivityMins; this.dataCenter = (response as {environment?: string}).environment || ''; }) .catch((error) => { diff --git a/packages/contact-center/store/src/store.types.ts b/packages/contact-center/store/src/store.types.ts index 9cea68f2a..f163cc9c4 100644 --- a/packages/contact-center/store/src/store.types.ts +++ b/packages/contact-center/store/src/store.types.ts @@ -286,6 +286,8 @@ type AgentLoginProfile = { social: number; telephony: number; }; + isTimeoutDesktopInactivityEnabled?: boolean; + timeoutDesktopInactivityMins?: number; }; // Generic pagination params for list-fetching APIs diff --git a/packages/contact-center/store/src/storeEventsWrapper.ts b/packages/contact-center/store/src/storeEventsWrapper.ts index 8e14f5a2f..dcb8d2de2 100644 --- a/packages/contact-center/store/src/storeEventsWrapper.ts +++ b/packages/contact-center/store/src/storeEventsWrapper.ts @@ -418,6 +418,8 @@ class StoreWrapper implements IStoreWrapper { orgId: profile.orgId || undefined, roles: profile.roles || undefined, deviceType: profile.deviceType || undefined, + isTimeoutDesktopInactivityEnabled: profile.isTimeoutDesktopInactivityEnabled, + timeoutDesktopInactivityMins: profile.timeoutDesktopInactivityMins, }; }); }; diff --git a/packages/contact-center/store/tests/store.ts b/packages/contact-center/store/tests/store.ts index afcdba5bc..b57924e0c 100644 --- a/packages/contact-center/store/tests/store.ts +++ b/packages/contact-center/store/tests/store.ts @@ -91,6 +91,8 @@ describe('Store', () => { lastStateChangeTimestamp: date, agentName: mockAgentName, environment: 'produs1', + isTimeoutDesktopInactivityEnabled: true, + timeoutDesktopInactivityMins: 15, }; mockWebex.cc.register.mockResolvedValue(mockResponse); @@ -104,7 +106,11 @@ describe('Store', () => { expect(storeInstance.deviceType).toEqual(mockResponse.deviceType); expect(storeInstance.currentState).toEqual(mockResponse.lastStateAuxCodeId); expect(storeInstance.lastStateChangeTimestamp).toEqual(date); - expect(storeInstance.agentProfile).toEqual({agentName: mockAgentName}); + expect(storeInstance.agentProfile).toEqual({ + agentName: mockAgentName, + isTimeoutDesktopInactivityEnabled: true, + timeoutDesktopInactivityMins: 15, + }); expect(storeInstance.dataCenter).toEqual(mockResponse.environment); }); diff --git a/packages/contact-center/store/tests/storeEventsWrapper.ts b/packages/contact-center/store/tests/storeEventsWrapper.ts index dabf58e3e..1048168dc 100644 --- a/packages/contact-center/store/tests/storeEventsWrapper.ts +++ b/packages/contact-center/store/tests/storeEventsWrapper.ts @@ -130,6 +130,8 @@ const mockAgentProfile = { roles: ['agent'], orgId: 'mockOrgId', profileType: 'BLENDED', + isTimeoutDesktopInactivityEnabled: true, + timeoutDesktopInactivityMins: 30, }; const mockAgentProfilePayload = { From 54954e9ec5d15fa0c8a01a25884b69f9134f678a Mon Sep 17 00:00:00 2001 From: Zach Raymer Date: Mon, 15 Jun 2026 15:54:04 -0500 Subject: [PATCH 2/3] chore: set default values for inactivity properties --- packages/contact-center/store/src/storeEventsWrapper.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/contact-center/store/src/storeEventsWrapper.ts b/packages/contact-center/store/src/storeEventsWrapper.ts index dcb8d2de2..48dc54a63 100644 --- a/packages/contact-center/store/src/storeEventsWrapper.ts +++ b/packages/contact-center/store/src/storeEventsWrapper.ts @@ -418,8 +418,8 @@ class StoreWrapper implements IStoreWrapper { orgId: profile.orgId || undefined, roles: profile.roles || undefined, deviceType: profile.deviceType || undefined, - isTimeoutDesktopInactivityEnabled: profile.isTimeoutDesktopInactivityEnabled, - timeoutDesktopInactivityMins: profile.timeoutDesktopInactivityMins, + isTimeoutDesktopInactivityEnabled: profile.isTimeoutDesktopInactivityEnabled || undefined, + timeoutDesktopInactivityMins: profile.timeoutDesktopInactivityMins || undefined, }; }); }; From 8dd0586cea5ec7fb40696d240e87fa90684ae480 Mon Sep 17 00:00:00 2001 From: Zach Raymer Date: Wed, 1 Jul 2026 10:39:04 -0500 Subject: [PATCH 3/3] fix: adds missing campaign global variables --- .../call-control-cad.styles.scss | 1 + .../task/CallControlCAD/call-control-cad.tsx | 22 +-- .../campaign-task-popover.tsx | 11 +- .../task/CampaignTask/campaign-task.tsx | 18 ++- .../task/CallControlCAD/call-control-cad.tsx | 125 ++++++++++++++++++ 5 files changed, 159 insertions(+), 18 deletions(-) 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(); + }); }); });