From ae36b5ba7f1021dec46a6ee249b181678b57cd07 Mon Sep 17 00:00:00 2001 From: disha-akarte <25985874+disha-akarte@users.noreply.github.com> Date: Mon, 24 Aug 2026 14:52:09 -0700 Subject: [PATCH 01/12] initial changes --- libs/blocks/brand-concierge/bc-analytics.js | 9 +++++++ libs/blocks/brand-concierge/bc-bootstrap.js | 2 ++ .../blocks/brand-concierge/brand-concierge.js | 25 +++++++++++++++++++ 3 files changed, 36 insertions(+) diff --git a/libs/blocks/brand-concierge/bc-analytics.js b/libs/blocks/brand-concierge/bc-analytics.js index d79d3108a20..a8af0ff5ab1 100644 --- a/libs/blocks/brand-concierge/bc-analytics.js +++ b/libs/blocks/brand-concierge/bc-analytics.js @@ -112,6 +112,15 @@ export const bcAnalytics = (event) => { }, }); break; + case 'navigation:historyChange': + _satellite.track('event', { + data: { + web: { webInteraction: { name: `BC-navigation_history_change|loginStatus:${event.data?.loginStatus}|precedingClick:${event.data?.precedingClickType}` } }, + // eslint-disable-next-line max-len + _adobe_corpnew: { digitalData: { primaryEvent: { eventInfo: { interaction: { click: `BC-navigation_history_change|loginStatus:${event.data?.loginStatus}|precedingClick:${event.data?.precedingClickType}` } } } } }, + }, + }); + break; default: break; } diff --git a/libs/blocks/brand-concierge/bc-bootstrap.js b/libs/blocks/brand-concierge/bc-bootstrap.js index 26454684ca6..5f4e1c50581 100644 --- a/libs/blocks/brand-concierge/bc-bootstrap.js +++ b/libs/blocks/brand-concierge/bc-bootstrap.js @@ -281,6 +281,8 @@ export async function bcBootstrap(initialMessage, mountIdentifier) { selector: `#${mountId}`, onBeforeEventSend, onEvent: (event) => { + if (event.eventType === 'card:clicked') window.history.replaceState({ ...window.history.state, bcClickType: 'card' }, ''); + else if (event.eventType === 'cta:clicked') window.history.replaceState({ ...window.history.state, bcClickType: 'cta' }, ''); bcAnalytics(event); }, }); diff --git a/libs/blocks/brand-concierge/brand-concierge.js b/libs/blocks/brand-concierge/brand-concierge.js index 7566e569485..6b1d00e2a00 100644 --- a/libs/blocks/brand-concierge/brand-concierge.js +++ b/libs/blocks/brand-concierge/brand-concierge.js @@ -1,4 +1,5 @@ import { createTag } from '../../utils/utils.js'; +import { bcAnalytics } from './bc-analytics.js'; import { decorateBackground, decorateMarqueeBackground, @@ -88,6 +89,30 @@ export default async function init(el) { setCssGnavHeight(); + // bfcache restore — page is brought back from memory, init does not re-run. + window.addEventListener('pageshow', (e) => { + if (!e.persisted) return; + const clickType = window.history.state?.bcClickType; + if (!clickType) return; + bcAnalytics({ + eventType: 'navigation:historyChange', + data: { + loginStatus: window.adobeIMS?.isSignedInUser() ? 'logged-in' : 'logged-out', + precedingClickType: clickType, + }, + }); + }); + // Full-page back/forward load — init re-runs, navigation type is back_forward. + if (window.performance.getEntriesByType('navigation')[0]?.type === 'back_forward') { + bcAnalytics({ + eventType: 'navigation:historyChange', + data: { + loginStatus: window.adobeIMS?.isSignedInUser() ? 'logged-in' : 'logged-out', + precedingClickType: window.history.state?.bcClickType ?? 'unknown', + }, + }); + } + const rows = el.querySelectorAll(':scope > div'); const [background, header, cards, input, legal] = rows; From b42c6a5ff992822f1bdcf2794f163ca2088aaa4b Mon Sep 17 00:00:00 2001 From: Logan Zartman Date: Wed, 26 Aug 2026 15:55:45 -0700 Subject: [PATCH 02/12] flesh out analytics payload; cleanup --- libs/blocks/brand-concierge/bc-analytics.js | 27 +++++++++++-- libs/blocks/brand-concierge/bc-bootstrap.js | 22 ++++++++++- libs/blocks/brand-concierge/bc-utils.js | 14 ++++++- .../blocks/brand-concierge/brand-concierge.js | 38 +++++++++++-------- 4 files changed, 78 insertions(+), 23 deletions(-) diff --git a/libs/blocks/brand-concierge/bc-analytics.js b/libs/blocks/brand-concierge/bc-analytics.js index a8af0ff5ab1..de6c74a2c37 100644 --- a/libs/blocks/brand-concierge/bc-analytics.js +++ b/libs/blocks/brand-concierge/bc-analytics.js @@ -112,15 +112,34 @@ export const bcAnalytics = (event) => { }, }); break; - case 'navigation:historyChange': + case 'navigation:backNavigation': { + const { + clickType = 'unknown', + sessionId = '', + sourcePage = '', + destinationPage = '', + loginStatus = '', + navigatedBack = true, + } = event.data ?? {}; + const eventName = `BC-chat_${clickType}_back_navigation`; _satellite.track('event', { data: { - web: { webInteraction: { name: `BC-navigation_history_change|loginStatus:${event.data?.loginStatus}|precedingClick:${event.data?.precedingClickType}` } }, - // eslint-disable-next-line max-len - _adobe_corpnew: { digitalData: { primaryEvent: { eventInfo: { interaction: { click: `BC-navigation_history_change|loginStatus:${event.data?.loginStatus}|precedingClick:${event.data?.precedingClickType}` } } } } }, + web: { webInteraction: { name: `${eventName}|loginStatus:${loginStatus}` } }, + bc: { + eventName, + timestamp: new Date().toISOString(), + sessionId, + clickType, + sourcePage, + destinationPage, + navigatedBack, + loginStatus, + }, + _adobe_corpnew: { digitalData: { primaryEvent: { eventInfo: { interaction: { click: `${eventName}|session:${sessionId}|from:${sourcePage}|to:${destinationPage}|nav:${navigatedBack}|loginStatus:${loginStatus}` } } } } }, }, }); break; + } default: break; } diff --git a/libs/blocks/brand-concierge/bc-bootstrap.js b/libs/blocks/brand-concierge/bc-bootstrap.js index 5f4e1c50581..9ff7d6416cf 100644 --- a/libs/blocks/brand-concierge/bc-bootstrap.js +++ b/libs/blocks/brand-concierge/bc-bootstrap.js @@ -210,6 +210,23 @@ export function loadWebclient() { loadScript(src); } +/** + * Record BC click in the current history entry. + * + * When the user navigates back to this page, we can attribute the original link. + */ +function recordBcNavClick(clickType, destinationPage) { + window.history.replaceState( + { + ...window.history.state, + bcClickType: clickType, + bcSourcePage: window.location.href, + bcDestinationPage: destinationPage ?? '', + }, + '', + ); +} + export async function bcBootstrap(initialMessage, mountIdentifier) { const mountEl = document.querySelector(`#${mountIdentifier}`); const { locale } = getConfig(); @@ -281,8 +298,9 @@ export async function bcBootstrap(initialMessage, mountIdentifier) { selector: `#${mountId}`, onBeforeEventSend, onEvent: (event) => { - if (event.eventType === 'card:clicked') window.history.replaceState({ ...window.history.state, bcClickType: 'card' }, ''); - else if (event.eventType === 'cta:clicked') window.history.replaceState({ ...window.history.state, bcClickType: 'cta' }, ''); + if (event.eventType === 'card:clicked') recordBcNavClick('product_card_cta', event.data?.element?.productPageURL); + else if (event.eventType === 'cta:clicked') recordBcNavClick('cta', event.data?.element?.productPageURL); + else if (event.eventType === 'link:clicked') recordBcNavClick(event.data?.element?.linkType ?? 'inline_hyperlink', event.data?.element?.href); bcAnalytics(event); }, }); diff --git a/libs/blocks/brand-concierge/bc-utils.js b/libs/blocks/brand-concierge/bc-utils.js index b6b712dc572..a3b19215478 100644 --- a/libs/blocks/brand-concierge/bc-utils.js +++ b/libs/blocks/brand-concierge/bc-utils.js @@ -27,18 +27,28 @@ const getTargetHeight = (target) => { return target.scrollHeight + (parseFloat(marginBottom) * 2); }; +const BC_SESSION_COOKIE = 'kndctr_9E1005A551ED61CA0A490D45_AdobeOrg_bc_session_id'; + export function hasChatCookie() { const cookies = document.cookie.split(';'); - const cookieName = 'kndctr_9E1005A551ED61CA0A490D45_AdobeOrg_bc_session_id'; for (let i = 0; i < cookies.length; i += 1) { const cookie = cookies[i].trim(); - if (cookie.includes(cookieName)) { + if (cookie.includes(BC_SESSION_COOKIE)) { return true; } } return false; } +/** Get session ID used to correlate analytics events */ +export function getChatSessionId() { + const match = document.cookie + .split(';') + .map((cookie) => cookie.trim()) + .find((cookie) => cookie.startsWith(`${BC_SESSION_COOKIE}=`)); + return match ? decodeURIComponent(match.slice(BC_SESSION_COOKIE.length + 1)) : ''; +} + export function setCssGnavHeight() { const gnav = document.querySelector('header.global-navigation'); if (!gnav) return; diff --git a/libs/blocks/brand-concierge/brand-concierge.js b/libs/blocks/brand-concierge/brand-concierge.js index 6b1d00e2a00..83141e883c8 100644 --- a/libs/blocks/brand-concierge/brand-concierge.js +++ b/libs/blocks/brand-concierge/brand-concierge.js @@ -13,6 +13,7 @@ import { handleConsent, setCssGnavHeight, hasChatCookie, + getChatSessionId, } from './bc-utils.js'; import { loadWebclient, @@ -89,28 +90,35 @@ export default async function init(el) { setCssGnavHeight(); - // bfcache restore — page is brought back from memory, init does not re-run. - window.addEventListener('pageshow', (e) => { - if (!e.persisted) return; - const clickType = window.history.state?.bcClickType; - if (!clickType) return; + const emitAnalyticsIfBackNav = () => { + // BC records "click type" when navigating to outgoing links. + // This indicates the user is returning via back button, etc. + const state = window.history.state ?? {}; + if (!state.bcClickType) { + return; + } + bcAnalytics({ - eventType: 'navigation:historyChange', + eventType: 'navigation:backNavigation', data: { + clickType: state.bcClickType, + sessionId: getChatSessionId(), + sourcePage: state.bcSourcePage ?? window.location.href, + destinationPage: state.bcDestinationPage ?? document.referrer ?? '', loginStatus: window.adobeIMS?.isSignedInUser() ? 'logged-in' : 'logged-out', - precedingClickType: clickType, + navigatedBack: true, }, }); + }; + + // bfcache restore: page is brought back from memory, init does not re-run. + window.addEventListener('pageshow', (e) => { + if (e.persisted) emitAnalyticsIfBackNav(); }); - // Full-page back/forward load — init re-runs, navigation type is back_forward. + + // Full-page back/forward load: init re-runs, navigation type is back_forward. if (window.performance.getEntriesByType('navigation')[0]?.type === 'back_forward') { - bcAnalytics({ - eventType: 'navigation:historyChange', - data: { - loginStatus: window.adobeIMS?.isSignedInUser() ? 'logged-in' : 'logged-out', - precedingClickType: window.history.state?.bcClickType ?? 'unknown', - }, - }); + emitAnalyticsIfBackNav(); } const rows = el.querySelectorAll(':scope > div'); From 06b5be0b767c628d96b9f40c6d58ab180cb31f31 Mon Sep 17 00:00:00 2001 From: Logan Zartman Date: Wed, 26 Aug 2026 16:41:00 -0700 Subject: [PATCH 03/12] BRANDCON-8316: add tests for BC back-navigation analytics Covers getChatSessionId cookie parsing and the bcAnalytics navigation:backNavigation payload (event name + required fields). Co-Authored-By: Claude Opus 4.8 (1M context) --- .../brand-concierge/brand-concierge.test.js | 69 ++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/test/blocks/brand-concierge/brand-concierge.test.js b/test/blocks/brand-concierge/brand-concierge.test.js index 577ad71ce77..5026839009f 100644 --- a/test/blocks/brand-concierge/brand-concierge.test.js +++ b/test/blocks/brand-concierge/brand-concierge.test.js @@ -7,8 +7,9 @@ import { setConfig } from '../../../libs/utils/utils.js'; setConfig({ codeRoot: '/libs', brandConciergeAA: 'testAA' }); const { default: init } = await import('../../../libs/blocks/brand-concierge/brand-concierge.js'); -const { updateReplicatedValue } = await import('../../../libs/blocks/brand-concierge/bc-utils.js'); +const { updateReplicatedValue, getChatSessionId } = await import('../../../libs/blocks/brand-concierge/bc-utils.js'); const { getUpdatedChatUIConfig, createSusiComponentForModal } = await import('../../../libs/blocks/brand-concierge/bc-bootstrap.js'); +const { bcAnalytics } = await import('../../../libs/blocks/brand-concierge/bc-analytics.js'); describe('Brand Concierge', () => { afterEach(() => { @@ -467,3 +468,69 @@ describe('Brand Concierge', () => { // Should not throw even without onSuccessfulToken handler }); }); + +/* eslint-disable no-underscore-dangle */ +describe('Brand Concierge back-navigation analytics', () => { + const SESSION_COOKIE = 'kndctr_9E1005A551ED61CA0A490D45_AdobeOrg_bc_session_id'; + + afterEach(() => { + document.cookie = `${SESSION_COOKIE}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`; + delete window._satellite; + sinon.restore(); + }); + + describe('getChatSessionId', () => { + it('returns the decoded session id from the cookie', () => { + document.cookie = `${SESSION_COOKIE}=${encodeURIComponent('sess 123')}; path=/`; + expect(getChatSessionId()).to.equal('sess 123'); + }); + + it('returns an empty string when the cookie is absent', () => { + expect(getChatSessionId()).to.equal(''); + }); + }); + + describe('bcAnalytics navigation:backNavigation', () => { + it('tracks a back-navigation event with all required fields', () => { + const track = sinon.spy(); + window._satellite = { track }; + + bcAnalytics({ + eventType: 'navigation:backNavigation', + data: { + clickType: 'inline_hyperlink', + sessionId: 'sess-1', + sourcePage: 'https://www.adobe.com/source', + destinationPage: 'https://www.adobe.com/dest', + loginStatus: 'logged-in', + navigatedBack: true, + }, + }); + + expect(track.calledOnce).to.be.true; + const [type, payload] = track.firstCall.args; + expect(type).to.equal('event'); + const eventName = 'BC-chat_inline_hyperlink_back_navigation'; + expect(payload.data.web.webInteraction.name).to.equal(`${eventName}|loginStatus:logged-in`); + expect(payload.data.bc).to.include({ + eventName, + sessionId: 'sess-1', + clickType: 'inline_hyperlink', + sourcePage: 'https://www.adobe.com/source', + destinationPage: 'https://www.adobe.com/dest', + navigatedBack: true, + loginStatus: 'logged-in', + }); + expect(payload.data.bc.timestamp).to.be.a('string'); + }); + + it('does not track when _satellite is unavailable', () => { + delete window._satellite; + expect(() => bcAnalytics({ + eventType: 'navigation:backNavigation', + data: { clickType: 'cta' }, + })).to.not.throw(); + }); + }); +}); +/* eslint-enable no-underscore-dangle */ From 1644a165230509950097e7ab4a09323f07dd64ff Mon Sep 17 00:00:00 2001 From: Logan Zartman Date: Mon, 31 Aug 2026 11:08:32 -0700 Subject: [PATCH 04/12] PR feedback; restructure --- libs/blocks/brand-concierge/bc-analytics.js | 66 +++++++++++++++++++ libs/blocks/brand-concierge/bc-bootstrap.js | 20 ------ .../blocks/brand-concierge/brand-concierge.js | 36 +--------- 3 files changed, 69 insertions(+), 53 deletions(-) diff --git a/libs/blocks/brand-concierge/bc-analytics.js b/libs/blocks/brand-concierge/bc-analytics.js index de6c74a2c37..0c361208a70 100644 --- a/libs/blocks/brand-concierge/bc-analytics.js +++ b/libs/blocks/brand-concierge/bc-analytics.js @@ -1,11 +1,42 @@ import { getConfig } from '../../utils/utils.js'; +import { getChatSessionId } from './bc-utils.js'; export function getAnalyticsLabel(step) { return `Filters|${getConfig()?.brandConciergeAA ? getConfig()?.brandConciergeAA : 'app-reco'}|bc#${step}`; } +const recordNavClick = (clickType, destinationPage) => { + window.history.replaceState( + { + ...window.history.state, + bcClickType: clickType, + bcSourcePage: window.location.href, + bcDestinationPage: destinationPage ?? '', + }, + '', + ); +}; + +const handleNav = (event) => { + switch (event.eventType) { + case 'card:clicked': + recordNavClick('product_card_cta', event.data?.element?.productPageURL); + break; + case 'cta:clicked': + recordNavClick('cta', event.data?.element?.productPageURL); + break; + case 'link:clicked': + recordNavClick(event.data?.element?.linkType ?? 'inline_hyperlink', event.data?.element?.href); + break; + default: + break; + } +}; + /* eslint-disable no-undef, no-underscore-dangle */ export const bcAnalytics = (event) => { + handleNav(event); + if (window?._satellite?.track) { switch (event.eventType) { case 'query:submitted': @@ -145,3 +176,38 @@ export const bcAnalytics = (event) => { } } }; + +const initBackNavAnalytics = () => { + const emitAnalyticsIfBackNav = () => { + const state = window.history.state ?? {}; + if (!state.bcClickType) { + return; + } + + bcAnalytics({ + eventType: 'navigation:backNavigation', + data: { + clickType: state.bcClickType, + sessionId: getChatSessionId(), + sourcePage: state.bcSourcePage ?? window.location.href, + destinationPage: state.bcDestinationPage ?? document.referrer ?? '', + loginStatus: window.adobeIMS?.isSignedInUser() ? 'logged-in' : 'logged-out', + navigatedBack: true, + }, + }); + }; + + // bfcache restore: page is brought back from memory, init does not re-run. + window.addEventListener('pageshow', (e) => { + if (e.persisted) emitAnalyticsIfBackNav(); + }); + + // full-page back/forward load: init re-runs, navigation type is back_forward. + if (window.performance.getEntriesByType('navigation')[0]?.type === 'back_forward') { + emitAnalyticsIfBackNav(); + } +}; + +export const initAnalytics = () => { + initBackNavAnalytics(); +}; diff --git a/libs/blocks/brand-concierge/bc-bootstrap.js b/libs/blocks/brand-concierge/bc-bootstrap.js index 9ff7d6416cf..26454684ca6 100644 --- a/libs/blocks/brand-concierge/bc-bootstrap.js +++ b/libs/blocks/brand-concierge/bc-bootstrap.js @@ -210,23 +210,6 @@ export function loadWebclient() { loadScript(src); } -/** - * Record BC click in the current history entry. - * - * When the user navigates back to this page, we can attribute the original link. - */ -function recordBcNavClick(clickType, destinationPage) { - window.history.replaceState( - { - ...window.history.state, - bcClickType: clickType, - bcSourcePage: window.location.href, - bcDestinationPage: destinationPage ?? '', - }, - '', - ); -} - export async function bcBootstrap(initialMessage, mountIdentifier) { const mountEl = document.querySelector(`#${mountIdentifier}`); const { locale } = getConfig(); @@ -298,9 +281,6 @@ export async function bcBootstrap(initialMessage, mountIdentifier) { selector: `#${mountId}`, onBeforeEventSend, onEvent: (event) => { - if (event.eventType === 'card:clicked') recordBcNavClick('product_card_cta', event.data?.element?.productPageURL); - else if (event.eventType === 'cta:clicked') recordBcNavClick('cta', event.data?.element?.productPageURL); - else if (event.eventType === 'link:clicked') recordBcNavClick(event.data?.element?.linkType ?? 'inline_hyperlink', event.data?.element?.href); bcAnalytics(event); }, }); diff --git a/libs/blocks/brand-concierge/brand-concierge.js b/libs/blocks/brand-concierge/brand-concierge.js index 83141e883c8..5952304399a 100644 --- a/libs/blocks/brand-concierge/brand-concierge.js +++ b/libs/blocks/brand-concierge/brand-concierge.js @@ -1,5 +1,5 @@ import { createTag } from '../../utils/utils.js'; -import { bcAnalytics } from './bc-analytics.js'; +import { initAnalytics } from './bc-analytics.js'; import { decorateBackground, decorateMarqueeBackground, @@ -13,7 +13,6 @@ import { handleConsent, setCssGnavHeight, hasChatCookie, - getChatSessionId, } from './bc-utils.js'; import { loadWebclient, @@ -88,38 +87,9 @@ export default async function init(el) { } }); - setCssGnavHeight(); - - const emitAnalyticsIfBackNav = () => { - // BC records "click type" when navigating to outgoing links. - // This indicates the user is returning via back button, etc. - const state = window.history.state ?? {}; - if (!state.bcClickType) { - return; - } + initAnalytics(); - bcAnalytics({ - eventType: 'navigation:backNavigation', - data: { - clickType: state.bcClickType, - sessionId: getChatSessionId(), - sourcePage: state.bcSourcePage ?? window.location.href, - destinationPage: state.bcDestinationPage ?? document.referrer ?? '', - loginStatus: window.adobeIMS?.isSignedInUser() ? 'logged-in' : 'logged-out', - navigatedBack: true, - }, - }); - }; - - // bfcache restore: page is brought back from memory, init does not re-run. - window.addEventListener('pageshow', (e) => { - if (e.persisted) emitAnalyticsIfBackNav(); - }); - - // Full-page back/forward load: init re-runs, navigation type is back_forward. - if (window.performance.getEntriesByType('navigation')[0]?.type === 'back_forward') { - emitAnalyticsIfBackNav(); - } + setCssGnavHeight(); const rows = el.querySelectorAll(':scope > div'); const [background, header, cards, input, legal] = rows; From a02211da0f515dd9e7b5ec7b1f0c8e2a0b5b2bbd Mon Sep 17 00:00:00 2001 From: Logan Zartman Date: Mon, 31 Aug 2026 11:15:44 -0700 Subject: [PATCH 05/12] fix payload --- libs/blocks/brand-concierge/bc-analytics.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libs/blocks/brand-concierge/bc-analytics.js b/libs/blocks/brand-concierge/bc-analytics.js index 0c361208a70..8b4c72e77dd 100644 --- a/libs/blocks/brand-concierge/bc-analytics.js +++ b/libs/blocks/brand-concierge/bc-analytics.js @@ -20,10 +20,11 @@ const recordNavClick = (clickType, destinationPage) => { const handleNav = (event) => { switch (event.eventType) { case 'card:clicked': - recordNavClick('product_card_cta', event.data?.element?.productPageURL); + recordNavClick('product_card_cta', event.data?.element?.entity_info?.productPageURL); break; case 'cta:clicked': - recordNavClick('cta', event.data?.element?.productPageURL); + // cta:clicked payload carries no destination URL (only source/actionType/ctaLabel). + recordNavClick('cta'); break; case 'link:clicked': recordNavClick(event.data?.element?.linkType ?? 'inline_hyperlink', event.data?.element?.href); From 264aed8a4562090066eb2cd78c3b170326b6d295 Mon Sep 17 00:00:00 2001 From: Logan Zartman Date: Mon, 31 Aug 2026 11:46:18 -0700 Subject: [PATCH 06/12] BRANDCON-8316: add clickSource to back-navigation analytics Thread the widget's cta:clicked `source` enum through history state into the navigation:backNavigation payload, so back-nav events are attributable to the CTA surface. Use an options object for recordNavClick. Co-Authored-By: Claude Opus 4.8 (1M context) --- libs/blocks/brand-concierge/bc-analytics.js | 22 +++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/libs/blocks/brand-concierge/bc-analytics.js b/libs/blocks/brand-concierge/bc-analytics.js index 8b4c72e77dd..1cdb75cc8c5 100644 --- a/libs/blocks/brand-concierge/bc-analytics.js +++ b/libs/blocks/brand-concierge/bc-analytics.js @@ -5,13 +5,14 @@ export function getAnalyticsLabel(step) { return `Filters|${getConfig()?.brandConciergeAA ? getConfig()?.brandConciergeAA : 'app-reco'}|bc#${step}`; } -const recordNavClick = (clickType, destinationPage) => { +const recordNavClick = ({ clickType, destinationPage, clickSource } = {}) => { window.history.replaceState( { ...window.history.state, bcClickType: clickType, bcSourcePage: window.location.href, bcDestinationPage: destinationPage ?? '', + bcClickSource: clickSource ?? '', }, '', ); @@ -20,14 +21,20 @@ const recordNavClick = (clickType, destinationPage) => { const handleNav = (event) => { switch (event.eventType) { case 'card:clicked': - recordNavClick('product_card_cta', event.data?.element?.entity_info?.productPageURL); + recordNavClick({ + clickType: 'product_card_cta', + destinationPage: event.data?.element?.entity_info?.productPageURL, + }); break; case 'cta:clicked': - // cta:clicked payload carries no destination URL (only source/actionType/ctaLabel). - recordNavClick('cta'); + // cta:clicked carries no destination URL; `source` (a stable enum) identifies the surface. + recordNavClick({ clickType: 'cta', clickSource: event.data?.source }); break; case 'link:clicked': - recordNavClick(event.data?.element?.linkType ?? 'inline_hyperlink', event.data?.element?.href); + recordNavClick({ + clickType: event.data?.element?.linkType ?? 'inline_hyperlink', + destinationPage: event.data?.element?.href, + }); break; default: break; @@ -150,6 +157,7 @@ export const bcAnalytics = (event) => { sessionId = '', sourcePage = '', destinationPage = '', + clickSource = '', loginStatus = '', navigatedBack = true, } = event.data ?? {}; @@ -164,10 +172,11 @@ export const bcAnalytics = (event) => { clickType, sourcePage, destinationPage, + clickSource, navigatedBack, loginStatus, }, - _adobe_corpnew: { digitalData: { primaryEvent: { eventInfo: { interaction: { click: `${eventName}|session:${sessionId}|from:${sourcePage}|to:${destinationPage}|nav:${navigatedBack}|loginStatus:${loginStatus}` } } } } }, + _adobe_corpnew: { digitalData: { primaryEvent: { eventInfo: { interaction: { click: `${eventName}|session:${sessionId}|from:${sourcePage}|to:${destinationPage}|source:${clickSource}|nav:${navigatedBack}|loginStatus:${loginStatus}` } } } } }, }, }); break; @@ -192,6 +201,7 @@ const initBackNavAnalytics = () => { sessionId: getChatSessionId(), sourcePage: state.bcSourcePage ?? window.location.href, destinationPage: state.bcDestinationPage ?? document.referrer ?? '', + clickSource: state.bcClickSource ?? '', loginStatus: window.adobeIMS?.isSignedInUser() ? 'logged-in' : 'logged-out', navigatedBack: true, }, From 54bfdbb27fb9665eed474a478c47515749573d90 Mon Sep 17 00:00:00 2001 From: Logan Date: Mon, 31 Aug 2026 12:06:16 -0700 Subject: [PATCH 07/12] Apply suggestion from @loganzartman --- libs/blocks/brand-concierge/bc-analytics.js | 1 - 1 file changed, 1 deletion(-) diff --git a/libs/blocks/brand-concierge/bc-analytics.js b/libs/blocks/brand-concierge/bc-analytics.js index 1cdb75cc8c5..ce9829c54d8 100644 --- a/libs/blocks/brand-concierge/bc-analytics.js +++ b/libs/blocks/brand-concierge/bc-analytics.js @@ -27,7 +27,6 @@ const handleNav = (event) => { }); break; case 'cta:clicked': - // cta:clicked carries no destination URL; `source` (a stable enum) identifies the surface. recordNavClick({ clickType: 'cta', clickSource: event.data?.source }); break; case 'link:clicked': From 82a88e762dedb1d83c503bef60c384007624d51e Mon Sep 17 00:00:00 2001 From: Logan Zartman Date: Mon, 31 Aug 2026 12:31:47 -0700 Subject: [PATCH 08/12] init backnav analytics in bc-global --- libs/blocks/brand-concierge-global/brand-concierge-global.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libs/blocks/brand-concierge-global/brand-concierge-global.js b/libs/blocks/brand-concierge-global/brand-concierge-global.js index a4879bbe6c9..42ed6b679b8 100644 --- a/libs/blocks/brand-concierge-global/brand-concierge-global.js +++ b/libs/blocks/brand-concierge-global/brand-concierge-global.js @@ -14,6 +14,7 @@ import { openSideModal, setAuthoredContent, } from '../brand-concierge/bc-bootstrap.js'; +import { initAnalytics } from '../brand-concierge/bc-analytics.js'; let stayActive = false; @@ -133,6 +134,8 @@ export default function init(el) { } }); + initAnalytics(); + setCssGnavHeight(); const rows = el.querySelectorAll(':scope > div'); From bf1d349b6b5c181b4c280e5c0919d1b2688ae5b9 Mon Sep 17 00:00:00 2001 From: Logan Zartman Date: Tue, 1 Sep 2026 16:18:06 -0700 Subject: [PATCH 09/12] c2 impl --- .../c2/blocks/brand-concierge/bc-analytics.js | 104 ++++++++++++++++++ libs/c2/blocks/brand-concierge/bc-utils.js | 11 ++ .../blocks/brand-concierge/brand-concierge.js | 3 + 3 files changed, 118 insertions(+) diff --git a/libs/c2/blocks/brand-concierge/bc-analytics.js b/libs/c2/blocks/brand-concierge/bc-analytics.js index 21485196638..f9ce054e2fe 100644 --- a/libs/c2/blocks/brand-concierge/bc-analytics.js +++ b/libs/c2/blocks/brand-concierge/bc-analytics.js @@ -1,11 +1,49 @@ import { getConfig } from '../../../utils/utils.js'; +import { getChatSessionId } from './bc-utils.js'; export function getAnalyticsLabel(step) { return `Filters|${getConfig()?.brandConciergeAA ? getConfig()?.brandConciergeAA : 'app-reco'}|bc#${step}`; } +const recordNavClick = ({ clickType, destinationPage, clickSource } = {}) => { + window.history.replaceState( + { + ...window.history.state, + bcClickType: clickType, + bcSourcePage: window.location.href, + bcDestinationPage: destinationPage ?? '', + bcClickSource: clickSource ?? '', + }, + '', + ); +}; + +const handleNav = (event) => { + switch (event.eventType) { + case 'card:clicked': + recordNavClick({ + clickType: 'product_card_cta', + destinationPage: event.data?.element?.entity_info?.productPageURL, + }); + break; + case 'cta:clicked': + recordNavClick({ clickType: 'cta', clickSource: event.data?.source }); + break; + case 'link:clicked': + recordNavClick({ + clickType: event.data?.element?.linkType ?? 'inline_hyperlink', + destinationPage: event.data?.element?.href, + }); + break; + default: + break; + } +}; + /* eslint-disable no-undef, no-underscore-dangle */ export const bcAnalytics = (event) => { + handleNav(event); + if (window?._satellite?.track) { switch (event.eventType) { case 'query:submitted': @@ -112,8 +150,74 @@ export const bcAnalytics = (event) => { }, }); break; + case 'navigation:backNavigation': { + const { + clickType = 'unknown', + sessionId = '', + sourcePage = '', + destinationPage = '', + clickSource = '', + loginStatus = '', + navigatedBack = true, + } = event.data ?? {}; + const eventName = `BC-chat_${clickType}_back_navigation`; + _satellite.track('event', { + data: { + web: { webInteraction: { name: `${eventName}|loginStatus:${loginStatus}` } }, + bc: { + eventName, + timestamp: new Date().toISOString(), + sessionId, + clickType, + sourcePage, + destinationPage, + clickSource, + navigatedBack, + loginStatus, + }, + _adobe_corpnew: { digitalData: { primaryEvent: { eventInfo: { interaction: { click: `${eventName}|session:${sessionId}|from:${sourcePage}|to:${destinationPage}|source:${clickSource}|nav:${navigatedBack}|loginStatus:${loginStatus}` } } } } }, + }, + }); + break; + } default: break; } } }; + +const initBackNavAnalytics = () => { + const emitAnalyticsIfBackNav = () => { + const state = window.history.state ?? {}; + if (!state.bcClickType) { + return; + } + + bcAnalytics({ + eventType: 'navigation:backNavigation', + data: { + clickType: state.bcClickType, + sessionId: getChatSessionId(), + sourcePage: state.bcSourcePage ?? window.location.href, + destinationPage: state.bcDestinationPage ?? document.referrer ?? '', + clickSource: state.bcClickSource ?? '', + loginStatus: window.adobeIMS?.isSignedInUser() ? 'logged-in' : 'logged-out', + navigatedBack: true, + }, + }); + }; + + // bfcache restore: page is brought back from memory, init does not re-run. + window.addEventListener('pageshow', (e) => { + if (e.persisted) emitAnalyticsIfBackNav(); + }); + + // full-page back/forward load: init re-runs, navigation type is back_forward. + if (window.performance.getEntriesByType('navigation')[0]?.type === 'back_forward') { + emitAnalyticsIfBackNav(); + } +}; + +export const initAnalytics = () => { + initBackNavAnalytics(); +}; diff --git a/libs/c2/blocks/brand-concierge/bc-utils.js b/libs/c2/blocks/brand-concierge/bc-utils.js index 1120121ee7a..4628fbb30a8 100644 --- a/libs/c2/blocks/brand-concierge/bc-utils.js +++ b/libs/c2/blocks/brand-concierge/bc-utils.js @@ -27,6 +27,17 @@ const getTargetHeight = (target) => { return target.scrollHeight + (parseFloat(marginBottom) * 2); }; +const BC_SESSION_COOKIE = 'kndctr_9E1005A551ED61CA0A490D45_AdobeOrg_bc_session_id'; + +/** Get session ID used to correlate analytics events */ +export function getChatSessionId() { + const match = document.cookie + .split(';') + .map((cookie) => cookie.trim()) + .find((cookie) => cookie.startsWith(`${BC_SESSION_COOKIE}=`)); + return match ? decodeURIComponent(match.slice(BC_SESSION_COOKIE.length + 1)) : ''; +} + export function handleConsent(el) { if (!window.adobePrivacy) return; const cookieGrp = window.adobePrivacy.activeCookieGroups(); diff --git a/libs/c2/blocks/brand-concierge/brand-concierge.js b/libs/c2/blocks/brand-concierge/brand-concierge.js index ffdca8612b7..0653248a893 100644 --- a/libs/c2/blocks/brand-concierge/brand-concierge.js +++ b/libs/c2/blocks/brand-concierge/brand-concierge.js @@ -1,4 +1,5 @@ import { createTag } from '../../../utils/utils.js'; +import { initAnalytics } from './bc-analytics.js'; import { decorateBackground, decorateMarqueeBackground, @@ -81,6 +82,8 @@ export default async function init(el) { } }); + initAnalytics(); + const rows = el.querySelectorAll(':scope > div'); const [background, header, cards, input, legal] = rows; From 09d6bb45ebc1fc5bf7329c922d7204dfb2991042 Mon Sep 17 00:00:00 2001 From: Logan Zartman Date: Tue, 1 Sep 2026 16:18:23 -0700 Subject: [PATCH 10/12] c2 basic tests --- .../brand-concierge/brand-concierge.test.js | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 test/c2/blocks/brand-concierge/brand-concierge.test.js diff --git a/test/c2/blocks/brand-concierge/brand-concierge.test.js b/test/c2/blocks/brand-concierge/brand-concierge.test.js new file mode 100644 index 00000000000..c1c4461094e --- /dev/null +++ b/test/c2/blocks/brand-concierge/brand-concierge.test.js @@ -0,0 +1,74 @@ +import { expect } from '@esm-bundle/chai'; +import sinon from 'sinon'; +import { setConfig } from '../../../../libs/utils/utils.js'; + +setConfig({ codeRoot: '/libs', brandConciergeAA: 'testAA' }); + +const { getChatSessionId } = await import('../../../../libs/c2/blocks/brand-concierge/bc-utils.js'); +const { bcAnalytics } = await import('../../../../libs/c2/blocks/brand-concierge/bc-analytics.js'); + +/* eslint-disable no-underscore-dangle */ +describe('Brand Concierge back-navigation analytics', () => { + const SESSION_COOKIE = 'kndctr_9E1005A551ED61CA0A490D45_AdobeOrg_bc_session_id'; + + afterEach(() => { + document.cookie = `${SESSION_COOKIE}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`; + delete window._satellite; + sinon.restore(); + }); + + describe('getChatSessionId', () => { + it('returns the decoded session id from the cookie', () => { + document.cookie = `${SESSION_COOKIE}=${encodeURIComponent('sess 123')}; path=/`; + expect(getChatSessionId()).to.equal('sess 123'); + }); + + it('returns an empty string when the cookie is absent', () => { + expect(getChatSessionId()).to.equal(''); + }); + }); + + describe('bcAnalytics navigation:backNavigation', () => { + it('tracks a back-navigation event with all required fields', () => { + const track = sinon.spy(); + window._satellite = { track }; + + bcAnalytics({ + eventType: 'navigation:backNavigation', + data: { + clickType: 'inline_hyperlink', + sessionId: 'sess-1', + sourcePage: 'https://www.adobe.com/source', + destinationPage: 'https://www.adobe.com/dest', + loginStatus: 'logged-in', + navigatedBack: true, + }, + }); + + expect(track.calledOnce).to.be.true; + const [type, payload] = track.firstCall.args; + expect(type).to.equal('event'); + const eventName = 'BC-chat_inline_hyperlink_back_navigation'; + expect(payload.data.web.webInteraction.name).to.equal(`${eventName}|loginStatus:logged-in`); + expect(payload.data.bc).to.include({ + eventName, + sessionId: 'sess-1', + clickType: 'inline_hyperlink', + sourcePage: 'https://www.adobe.com/source', + destinationPage: 'https://www.adobe.com/dest', + navigatedBack: true, + loginStatus: 'logged-in', + }); + expect(payload.data.bc.timestamp).to.be.a('string'); + }); + + it('does not track when _satellite is unavailable', () => { + delete window._satellite; + expect(() => bcAnalytics({ + eventType: 'navigation:backNavigation', + data: { clickType: 'cta' }, + })).to.not.throw(); + }); + }); +}); +/* eslint-enable no-underscore-dangle */ From 7f51c1799b084ab1fdcb3816bec904ac593df80a Mon Sep 17 00:00:00 2001 From: Logan Zartman Date: Thu, 3 Sep 2026 16:00:18 -0700 Subject: [PATCH 11/12] use new destination value --- libs/blocks/brand-concierge/bc-analytics.js | 4 +-- .../brand-concierge/brand-concierge.test.js | 25 +++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/libs/blocks/brand-concierge/bc-analytics.js b/libs/blocks/brand-concierge/bc-analytics.js index ce9829c54d8..7fc61cad5d1 100644 --- a/libs/blocks/brand-concierge/bc-analytics.js +++ b/libs/blocks/brand-concierge/bc-analytics.js @@ -23,7 +23,7 @@ const handleNav = (event) => { case 'card:clicked': recordNavClick({ clickType: 'product_card_cta', - destinationPage: event.data?.element?.entity_info?.productPageURL, + destinationPage: event.data?.destinationUrl, }); break; case 'cta:clicked': @@ -199,7 +199,7 @@ const initBackNavAnalytics = () => { clickType: state.bcClickType, sessionId: getChatSessionId(), sourcePage: state.bcSourcePage ?? window.location.href, - destinationPage: state.bcDestinationPage ?? document.referrer ?? '', + destinationPage: state.bcDestinationPage ?? '', clickSource: state.bcClickSource ?? '', loginStatus: window.adobeIMS?.isSignedInUser() ? 'logged-in' : 'logged-out', navigatedBack: true, diff --git a/test/blocks/brand-concierge/brand-concierge.test.js b/test/blocks/brand-concierge/brand-concierge.test.js index 4b472186b2d..bb9df477e9a 100644 --- a/test/blocks/brand-concierge/brand-concierge.test.js +++ b/test/blocks/brand-concierge/brand-concierge.test.js @@ -532,5 +532,30 @@ describe('Brand Concierge back-navigation analytics', () => { })).to.not.throw(); }); }); + + describe('card:clicked navigation recording', () => { + it('records the resolved destinationUrl from the event payload', () => { + const replaceState = sinon.spy(window.history, 'replaceState'); + + bcAnalytics({ + eventType: 'card:clicked', + data: { destinationUrl: 'https://acrobat.adobe.com/pdf-editor?adobe_brand_concierge_source=bc-adobe-product-card' }, + }); + + expect(replaceState.calledOnce).to.be.true; + const [state] = replaceState.firstCall.args; + expect(state.bcClickType).to.equal('product_card_cta'); + expect(state.bcDestinationPage).to.equal('https://acrobat.adobe.com/pdf-editor?adobe_brand_concierge_source=bc-adobe-product-card'); + }); + + it('stores an empty destination without falling back to the referrer', () => { + const replaceState = sinon.spy(window.history, 'replaceState'); + + bcAnalytics({ eventType: 'card:clicked', data: {} }); + + const [state] = replaceState.firstCall.args; + expect(state.bcDestinationPage).to.equal(''); + }); + }); }); /* eslint-enable no-underscore-dangle */ From fa49671815d77723e3f5dfb80b74d8d64bf3a6cc Mon Sep 17 00:00:00 2001 From: Logan Zartman Date: Thu, 3 Sep 2026 16:14:36 -0700 Subject: [PATCH 12/12] c2 destination --- .../c2/blocks/brand-concierge/bc-analytics.js | 4 +-- .../brand-concierge/brand-concierge.test.js | 25 +++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/libs/c2/blocks/brand-concierge/bc-analytics.js b/libs/c2/blocks/brand-concierge/bc-analytics.js index f9ce054e2fe..edec7c818c1 100644 --- a/libs/c2/blocks/brand-concierge/bc-analytics.js +++ b/libs/c2/blocks/brand-concierge/bc-analytics.js @@ -23,7 +23,7 @@ const handleNav = (event) => { case 'card:clicked': recordNavClick({ clickType: 'product_card_cta', - destinationPage: event.data?.element?.entity_info?.productPageURL, + destinationPage: event.data?.destinationUrl, }); break; case 'cta:clicked': @@ -199,7 +199,7 @@ const initBackNavAnalytics = () => { clickType: state.bcClickType, sessionId: getChatSessionId(), sourcePage: state.bcSourcePage ?? window.location.href, - destinationPage: state.bcDestinationPage ?? document.referrer ?? '', + destinationPage: state.bcDestinationPage ?? '', clickSource: state.bcClickSource ?? '', loginStatus: window.adobeIMS?.isSignedInUser() ? 'logged-in' : 'logged-out', navigatedBack: true, diff --git a/test/c2/blocks/brand-concierge/brand-concierge.test.js b/test/c2/blocks/brand-concierge/brand-concierge.test.js index c1c4461094e..cabfb80b057 100644 --- a/test/c2/blocks/brand-concierge/brand-concierge.test.js +++ b/test/c2/blocks/brand-concierge/brand-concierge.test.js @@ -70,5 +70,30 @@ describe('Brand Concierge back-navigation analytics', () => { })).to.not.throw(); }); }); + + describe('card:clicked navigation recording', () => { + it('records the resolved destinationUrl from the event payload', () => { + const replaceState = sinon.spy(window.history, 'replaceState'); + + bcAnalytics({ + eventType: 'card:clicked', + data: { destinationUrl: 'https://acrobat.adobe.com/pdf-editor?adobe_brand_concierge_source=bc-adobe-product-card' }, + }); + + expect(replaceState.calledOnce).to.be.true; + const [state] = replaceState.firstCall.args; + expect(state.bcClickType).to.equal('product_card_cta'); + expect(state.bcDestinationPage).to.equal('https://acrobat.adobe.com/pdf-editor?adobe_brand_concierge_source=bc-adobe-product-card'); + }); + + it('stores an empty destination without falling back to the referrer', () => { + const replaceState = sinon.spy(window.history, 'replaceState'); + + bcAnalytics({ eventType: 'card:clicked', data: {} }); + + const [state] = replaceState.firstCall.args; + expect(state.bcDestinationPage).to.equal(''); + }); + }); }); /* eslint-enable no-underscore-dangle */