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}}