From 7d874c2ed654319f2327903e5f8de7939e662314 Mon Sep 17 00:00:00 2001 From: Ronnie Miller Date: Fri, 24 Oct 2025 13:21:33 -0700 Subject: [PATCH] Replace Ketch with IBM TrustArc consent management --- src/js/04-segment-analytics.js | 142 +++++++++++++++++++++++++++++++-- src/js/05-feedback-dialog.js | 30 ++++++- src/js/06-copy-to-clipboard.js | 20 +++-- src/js/09-ketch-consent.js | 86 -------------------- src/partials/footer.hbs | 10 ++- src/partials/head-scripts.hbs | 70 ++++++++++++++-- 6 files changed, 244 insertions(+), 114 deletions(-) delete mode 100644 src/js/09-ketch-consent.js diff --git a/src/js/04-segment-analytics.js b/src/js/04-segment-analytics.js index fffa0e30..7cb28e38 100644 --- a/src/js/04-segment-analytics.js +++ b/src/js/04-segment-analytics.js @@ -1,15 +1,96 @@ ;(function () { 'use strict' - const trackEvent = (name, payload) => { - if (window.analytics) { - window.analytics.track(name, payload || {}) + /** + * IBM Segment Event Types: + * - 'CTA Clicked': Call-to-action clicks (links, buttons) + * - 'UI Interaction': General UI interactions (excludes userId) + * - 'User Form': Form interactions (handled separately in form-specific files) + */ + + // Determine if a data-track event should be CTA Clicked or UI Interaction + const getEventType = (eventName) => { + // Links are typically CTAs + if (eventName.includes('Link Clicked') || eventName.includes('Clicked')) { + return 'CTA Clicked' + } + // Buttons and other interactions + if (eventName.includes('Button') || eventName.includes('Form')) { + return 'UI Interaction' + } + // Default to UI Interaction + return 'UI Interaction' + } + + // Extract CTA text from event name or element + const extractCTA = (eventName, element) => { + if (element) { + return element.textContent?.trim() || element.getAttribute('title') || eventName + } + return eventName + } + + // Extract namespace from event name + const extractNamespace = (eventName) => { + if (eventName.includes('Footer')) return 'footer' + if (eventName.includes('Tutorial')) return 'tutorial' + if (eventName.includes('Feedback')) return 'feedback' + if (eventName.includes('Edit')) return 'content' + if (eventName.includes('Colab')) return 'tutorial' + return 'docs' + } + + // Extract action from event name + const extractAction = (eventName) => { + if (eventName.includes('Clicked')) return 'clicked' + if (eventName.includes('Submitted')) return 'submitted' + if (eventName.includes('Copied')) return 'copied' + return 'interacted' + } + + const trackEvent = (name, payload = {}, element = null) => { + if (window.analytics && window.getSegmentCommonProperties) { + const eventType = getEventType(name) + const commonProps = window.getSegmentCommonProperties(eventType) + + let eventPayload = { ...commonProps, ...payload } + + if (eventType === 'CTA Clicked') { + eventPayload = { + ...eventPayload, + CTA: extractCTA(name, element), + location: extractNamespace(name), + } + } else if (eventType === 'UI Interaction') { + eventPayload = { + ...eventPayload, + action: extractAction(name), + CTA: extractCTA(name, element), + namespace: extractNamespace(name), + elementId: element?.id || '', + payload: payload, + } + } + + window.analytics.track(eventType, eventPayload) } } - const trackLinkEvent = (element, name, payload) => { - if (window.analytics) { - window.analytics.trackLink(element, name, payload || {}) + const trackLinkEvent = (element, name, payload = {}) => { + if (window.analytics && window.getSegmentCommonProperties) { + const eventType = 'CTA Clicked' // Links are always CTAs + const commonProps = window.getSegmentCommonProperties(eventType) + + const eventPayload = { + ...commonProps, + ...payload, + CTA: extractCTA(name, element), + location: extractNamespace(name), + type: 'Link', + text: element.textContent?.trim() || element.getAttribute('title') || '', + } + + window.analytics.trackLink(element, eventType, eventPayload) } } @@ -24,12 +105,57 @@ trackedElements.forEach((element) => { element.addEventListener('click', (e) => { - trackEvent(element.dataset.track) + trackEvent(element.dataset.track, {}, element) }) }) } - // Expose trackEvent and trackLinkEvent to the global scope. + /** + * Track page view with friendly name + * IBM requires page events to have a friendly "page" property + */ + const trackPage = (pageName) => { + if (window.analytics && window.SEGMENT_COMMON_PROPERTIES) { + // Get friendly page name from title or use provided name + const friendlyName = pageName || document.title.split('|')[0].trim() + + // Get common properties for page event (excludes userId per IBM requirements) + const pageProperties = window.getSegmentCommonProperties('page') + + window.analytics.page(friendlyName, { + ...pageProperties, + path: window.location.pathname, + url: window.location.href, + title: document.title, + }) + } + } + + // Wait for analytics to load, then track page view on initial load + const waitForAnalytics = (callback, maxAttempts = 50, interval = 100) => { + let attempts = 0 + const checkAnalytics = () => { + attempts++ + if (window.analytics && window.analytics.initialized) { + callback() + } else if (attempts < maxAttempts) { + setTimeout(checkAnalytics, interval) + } + } + checkAnalytics() + } + + // Track page view on initial load once analytics is ready + waitForAnalytics(() => { + // Identify user with anonymous ID before tracking page + if (window.SEGMENT_COMMON_PROPERTIES && window.SEGMENT_COMMON_PROPERTIES.userId) { + window.analytics.identify(window.SEGMENT_COMMON_PROPERTIES.userId) + } + trackPage() + }) + + // Expose trackEvent, trackLinkEvent, and trackPage to the global scope. window.trackEvent = trackEvent window.trackLinkEvent = trackLinkEvent + window.trackPage = trackPage })() diff --git a/src/js/05-feedback-dialog.js b/src/js/05-feedback-dialog.js index a10d6fcc..836753e9 100644 --- a/src/js/05-feedback-dialog.js +++ b/src/js/05-feedback-dialog.js @@ -40,9 +40,19 @@ form.onsubmit = (e) => { e.preventDefault() const message = form.elements.message.value - if (message && window.trackEvent) { - window.trackEvent('Feedback Form', { - message, + if (message && window.analytics && window.getSegmentCommonProperties) { + const commonProps = window.getSegmentCommonProperties('User Form') + + // Map to IBM User Form schema + window.analytics.track('User Form', { + ...commonProps, + action: 'submitted', + formId: 'feedback_form', + formType: 'feedback', + field: 'feedback_message', + fieldType: 'text_field', + title: 'Give Feedback', + data: message, }) } form.elements.message.value = '' @@ -52,6 +62,20 @@ const feedbackButtonYes = document.getElementById('feedback_button_yes') feedbackButtonYes.addEventListener('click', (e) => { + if (window.analytics && window.getSegmentCommonProperties) { + const commonProps = window.getSegmentCommonProperties('User Form') + + // Track positive feedback button click + window.analytics.track('User Form', { + ...commonProps, + action: 'clicked', + formId: 'feedback_buttons', + formType: 'feedback', + field: 'helpful_yes', + fieldType: 'button', + title: 'Was This Helpful?', + }) + } showThankYou() }) diff --git a/src/js/06-copy-to-clipboard.js b/src/js/06-copy-to-clipboard.js index 9b7ada5c..8c1c54f7 100644 --- a/src/js/06-copy-to-clipboard.js +++ b/src/js/06-copy-to-clipboard.js @@ -92,12 +92,22 @@ } function trackCopy (language, title, text) { - if (window.trackEvent) { + if (window.analytics && window.getSegmentCommonProperties) { var sample = text.slice(0, 50).replace(/\s+/g, ' ').trim() - window.trackEvent('Code Snippet Copied', { - snippetLanguage: language, - snippetTitle: title, - snippetSample: sample, + var commonProps = window.getSegmentCommonProperties('UI Interaction') + + // Map to IBM UI Interaction schema + window.analytics.track('UI Interaction', { + ...commonProps, + action: 'copied', + CTA: 'Copy Code', + namespace: 'code-snippet', + elementId: 'copy-button', + payload: { + language: language, + title: title, + sample: sample, + }, }) } } diff --git a/src/js/09-ketch-consent.js b/src/js/09-ketch-consent.js deleted file mode 100644 index 6293ef55..00000000 --- a/src/js/09-ketch-consent.js +++ /dev/null @@ -1,86 +0,0 @@ -;(function () { - 'use strict' - - window.analytics.ready(() => { - window.ketch('once', 'consent', onKetchConsent) - window.ketch('on', 'consent', onKetchConsentGtagTrack) - window.ketch('on', 'userConsentUpdated', onKetchConsentUpdated) - }) - - // If the user is in the US-CA region, change the text of the preference center link - // 3/19/25: Disabled this feature due to shifting CA legal requirements. Will re-enable with new textContent if needed - // window.ketch('on', 'regionInfo', (regionInfo) => { - // const customTextRegions = ['US-CA'] - // if (customTextRegions.includes(regionInfo)) { - // const preferenceCenterLinkElement = document.getElementById('preferenceCenterLink') - // if (preferenceCenterLinkElement) { - // preferenceCenterLinkElement.textContent = 'Do Not Sell My Personal Information' - // } - // } - // }) - - // If the user is in the default jurisdiction, remove the preference center link - window.ketch('on', 'jurisdiction', (jurisdiction) => { - if (jurisdiction.includes('default')) { - const preferenceCenterContainerElement = document.getElementById('preferenceCenterContainer') - if (preferenceCenterContainerElement) { - preferenceCenterContainerElement.remove() - } - } - }) - - // Once - This will be fired only one time, will initialize all the main features. - const onKetchConsent = (consent) => { - window.ketchConsent = consent - addKetchConsentToContextMiddleware() - window.analytics.page() - // loadScripts(); // Load any script if we have scripts to fire after ketch consent is fired. - } - - // on - Each time the user changes the preferences, save them to the global variable - const onKetchConsentUpdated = (consent) => { - window.ketchConsent = consent - } - - // On - each time the consent is loaded, track it to the gtag event - const onKetchConsentGtagTrack = (consent) => { - if (window.gtag && - consent.purposes && - 'analytics' in consent.purposes && - 'targeted_advertising' in consent.purposes - ) { - const analyticsString = consent.purposes.analytics === true ? 'granted' : 'denied' - const targetedAdsString = consent.purposes.targeted_advertising === true ? 'granted' : 'denied' - - const gtagObject = { - analytics_storage: analyticsString, - ad_personalization: targetedAdsString, - ad_storage: targetedAdsString, - ad_user_data: targetedAdsString, - } - window.gtag('consent', 'update', gtagObject) - } - } - - // Use the analytics.addSourceMiddleware function to include the consent on all the events - const addKetchConsentToContextMiddleware = () => { - window.analytics.addSourceMiddleware(({ payload, next }) => { - if (window.ketchConsent) { - const analyticsString = window.ketchConsent.purposes.analytics === true ? 'granted' : 'denied' - const targetedAdsString = window.ketchConsent.purposes.targeted_advertising === true ? 'granted' : 'denied' - - payload.obj.properties = { - ...(payload.obj.properties || {}), - analyticsStorageConsentState: analyticsString, - adsStorageConsentState: targetedAdsString, - adUserDataConsentState: targetedAdsString, - adPersonalizationConsentState: targetedAdsString, - } - payload.obj.context.consent = { - categoryPreferences: window.ketchConsent?.purposes, - } - } - next(payload) - }) - } -})() diff --git a/src/partials/footer.hbs b/src/partials/footer.hbs index c2e0a2d7..ccd65814 100644 --- a/src/partials/footer.hbs +++ b/src/partials/footer.hbs @@ -24,13 +24,15 @@ target="_blank" data-track="Footer Terms of Use Link Clicked" >Terms of use - {{#with site.keys.ketchSmartTagUrl}} + {{#with site.keys.ibmSegment}} - | + ยท  Manage Privacy Choices {{/with}} @@ -93,4 +95,4 @@ >info@datastax.com

- \ No newline at end of file + diff --git a/src/partials/head-scripts.hbs b/src/partials/head-scripts.hbs index 9e543ddd..fb8a4077 100644 --- a/src/partials/head-scripts.hbs +++ b/src/partials/head-scripts.hbs @@ -1,13 +1,67 @@ -{{#with site.keys.segment}} +{{!-- IBM Analytics Configuration with TrustArc Consent Management --}} +{{!-- Only loads when site.keys.ibmSegment is set --}} +{{#with site.keys.ibmSegment}} -{{/with}} + window._ibmAnalytics = { + "settings": { + "name": "DataStax", + "tealiumProfileName": "ibm-subsidiary", + }, + "trustarc": { + "privacyPolicyLink": "https://ibm.com/privacy" + }, + "digitalData.page.services.google.enabled": true + }; -{{#with site.keys.ketchSmartTagUrl}} - + /** + * Shared Segment Common Properties + * Single source of truth for all common properties included in Segment events + * Conforms to IBM Cloud Platform Analytics schema + */ + window.SEGMENT_COMMON_PROPERTIES = { + productTitle: "watsonx.data", + productCode: "WW1544", + productCodeType: "WWPC", + UT30: "30AW0", + instanceId: "docs-site", + subscriptionId: "public-access", + userId: "IBMid-ANONYMOUS", + }; + + window.digitalData = { + "page": { + "pageInfo": { + "ibm": { + "siteId": "IBM_" + _ibmAnalytics.settings.name, + }, + "segment": { + "enabled": true, + "env": "prod", + "key": "{{this}}", + "coremetrics": false, + "carbonComponentEvents": false, + "commonProperties": window.SEGMENT_COMMON_PROPERTIES + } + }, + "category": { + "primaryCategory": "PC230" + } + } + }; + + /** + * Get common properties for Segment events + * @param {string} eventType - The type of event ('CTA Clicked', 'UI Interaction', 'User Form', 'page', etc.) + * @returns {object} Common properties including userId for all events + */ + window.getSegmentCommonProperties = function(eventType) { + return { + ...window.SEGMENT_COMMON_PROPERTIES + }; + }; + + + {{/with}}