mirror of
https://github.com/apache/superset.git
synced 2026-05-06 16:34:32 +00:00
337 lines
12 KiB
JavaScript
337 lines
12 KiB
JavaScript
/**
|
|
* Licensed to the Apache Software Foundation (ASF) under one
|
|
* or more contributor license agreements. See the NOTICE file
|
|
* distributed with this work for additional information
|
|
* regarding copyright ownership. The ASF licenses this file
|
|
* to you under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance
|
|
* with the License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing,
|
|
* software distributed under the License is distributed on an
|
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
* KIND, either express or implied. See the License for the
|
|
* specific language governing permissions and limitations
|
|
* under the License.
|
|
*/
|
|
import { useEffect } from 'react';
|
|
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
|
|
|
// File extensions to track as downloads
|
|
const DOWNLOAD_EXTENSIONS = [
|
|
'pdf', 'zip', 'tar', 'gz', 'tgz', 'bz2',
|
|
'exe', 'dmg', 'pkg', 'deb', 'rpm',
|
|
'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx',
|
|
'csv', 'json', 'yaml', 'yml',
|
|
];
|
|
|
|
// Scroll depth milestones to track
|
|
const SCROLL_MILESTONES = [25, 50, 75, 100];
|
|
|
|
export default function Root({ children }) {
|
|
const { siteConfig } = useDocusaurusContext();
|
|
const { customFields } = siteConfig;
|
|
|
|
useEffect(() => {
|
|
const { matomoUrl, matomoSiteId } = customFields;
|
|
|
|
if (typeof window !== 'undefined') {
|
|
const devMode = ['localhost', '127.0.0.1', '::1', '0.0.0.0'].includes(window.location.hostname);
|
|
|
|
// Initialize the _paq array
|
|
window._paq = window._paq || [];
|
|
|
|
// Configure the tracker before loading matomo.js
|
|
window._paq.push(['enableHeartBeatTimer']);
|
|
window._paq.push(['enableLinkTracking']);
|
|
window._paq.push(['setTrackerUrl', `${matomoUrl}/matomo.php`]);
|
|
window._paq.push(['setSiteId', matomoSiteId]);
|
|
|
|
// Track downloads with custom extensions
|
|
window._paq.push(['setDownloadExtensions', DOWNLOAD_EXTENSIONS.join('|')]);
|
|
|
|
// Now load the matomo.js script
|
|
const script = document.createElement('script');
|
|
script.async = true;
|
|
script.src = `${matomoUrl}/matomo.js`;
|
|
document.head.appendChild(script);
|
|
|
|
// Helper to track events
|
|
const trackEvent = (category, action, name, value) => {
|
|
if (devMode) {
|
|
console.log('Matomo trackEvent:', { category, action, name, value });
|
|
}
|
|
window._paq.push(['trackEvent', category, action, name, value]);
|
|
};
|
|
|
|
// Helper to track site search
|
|
const trackSiteSearch = (keyword, category, resultsCount) => {
|
|
if (devMode) {
|
|
console.log('Matomo trackSiteSearch:', { keyword, category, resultsCount });
|
|
}
|
|
window._paq.push(['trackSiteSearch', keyword, category, resultsCount]);
|
|
};
|
|
|
|
// Helper to track page views
|
|
const trackPageView = (url, title) => {
|
|
if (devMode) {
|
|
console.log('Matomo trackPageView:', { url, title });
|
|
}
|
|
window._paq.push(['trackPageView']);
|
|
};
|
|
|
|
|
|
// Track external link clicks using domain as category (vendor-agnostic)
|
|
const handleLinkClick = (event) => {
|
|
const link = event.target.closest('a');
|
|
if (!link) return;
|
|
|
|
const href = link.getAttribute('href');
|
|
if (!href) return;
|
|
|
|
try {
|
|
const url = new URL(href, window.location.origin);
|
|
|
|
// Skip internal links
|
|
if (url.hostname === window.location.hostname) return;
|
|
|
|
// Use hostname as category for vendor-agnostic tracking
|
|
trackEvent('Outbound Link', url.hostname, href);
|
|
} catch {
|
|
// Invalid URL, skip tracking
|
|
}
|
|
};
|
|
|
|
// Track Algolia search queries
|
|
const setupAlgoliaTracking = () => {
|
|
const observer = new MutationObserver((mutations) => {
|
|
mutations.forEach((mutation) => {
|
|
mutation.addedNodes.forEach((node) => {
|
|
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
const searchInput = node.querySelector?.('.DocSearch-Input') ||
|
|
(node.classList?.contains('DocSearch-Input') ? node : null);
|
|
if (searchInput) {
|
|
let debounceTimer;
|
|
searchInput.addEventListener('input', (e) => {
|
|
clearTimeout(debounceTimer);
|
|
debounceTimer = setTimeout(() => {
|
|
const query = e.target.value.trim();
|
|
if (query.length >= 3) {
|
|
const results = document.querySelectorAll('.DocSearch-Hit');
|
|
trackSiteSearch(query, 'Documentation', results.length);
|
|
}
|
|
}, 1000);
|
|
});
|
|
}
|
|
}
|
|
});
|
|
});
|
|
});
|
|
|
|
observer.observe(document.body, { childList: true, subtree: true });
|
|
return observer;
|
|
};
|
|
|
|
// Track video plays
|
|
const handleVideoPlay = (event) => {
|
|
if (event.target.tagName === 'VIDEO') {
|
|
const videoSrc = event.target.currentSrc || event.target.src || 'unknown';
|
|
trackEvent('Video', 'Play', videoSrc);
|
|
}
|
|
};
|
|
|
|
// Track CTA button clicks
|
|
const handleCTAClick = (event) => {
|
|
const button = event.target.closest('.get-started-button, .default-button-theme');
|
|
if (button) {
|
|
const buttonText = button.textContent?.trim() || 'Unknown';
|
|
const clickedLink = event.target.closest?.('a');
|
|
const href =
|
|
clickedLink?.getAttribute('href') || button.getAttribute('href') || '';
|
|
trackEvent('CTA', 'Click', `${buttonText} - ${href}`);
|
|
}
|
|
};
|
|
|
|
// Track scroll depth
|
|
let scrollMilestonesReached = new Set();
|
|
const handleScroll = () => {
|
|
const scrollTop = window.scrollY;
|
|
const docHeight = document.documentElement.scrollHeight - window.innerHeight;
|
|
if (docHeight <= 0) return;
|
|
|
|
const scrollPercent = Math.round((scrollTop / docHeight) * 100);
|
|
|
|
SCROLL_MILESTONES.forEach(milestone => {
|
|
if (scrollPercent >= milestone && !scrollMilestonesReached.has(milestone)) {
|
|
scrollMilestonesReached.add(milestone);
|
|
trackEvent('Scroll Depth', `${milestone}%`, window.location.pathname);
|
|
}
|
|
});
|
|
};
|
|
|
|
// Reset scroll tracking on route change
|
|
const resetScrollTracking = () => {
|
|
scrollMilestonesReached = new Set();
|
|
};
|
|
|
|
// Track 404 pages
|
|
const track404 = () => {
|
|
const is404 = document.querySelector('.theme-doc-404') ||
|
|
document.title.toLowerCase().includes('not found') ||
|
|
document.querySelector('h1')?.textContent?.toLowerCase().includes('not found');
|
|
if (is404) {
|
|
trackEvent('Error', '404', window.location.pathname);
|
|
if (devMode) {
|
|
console.log('Matomo: 404 page detected', window.location.pathname);
|
|
}
|
|
}
|
|
};
|
|
|
|
// Track copy-to-clipboard events on code blocks
|
|
const handleCopy = (event) => {
|
|
const codeBlock = event.target.closest('pre, code, .prism-code');
|
|
if (codeBlock) {
|
|
const codeText = window.getSelection()?.toString() || '';
|
|
const codeSnippet = codeText.substring(0, 100) + (codeText.length > 100 ? '...' : '');
|
|
trackEvent('Code', 'Copy', `${window.location.pathname}: ${codeSnippet}`);
|
|
}
|
|
};
|
|
|
|
// Track color mode preference (as event, no admin config needed)
|
|
const trackColorMode = () => {
|
|
const colorMode = document.documentElement.getAttribute('data-theme') ||
|
|
(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
|
|
trackEvent('User Preference', 'Color Mode', colorMode);
|
|
};
|
|
|
|
// Track docs version from URL (as event, no admin config needed)
|
|
const trackDocsVersion = () => {
|
|
const pathMatch = window.location.pathname.match(/\/docs\/([\d.]+)\//);
|
|
const version = pathMatch ? pathMatch[1] : 'latest';
|
|
trackEvent('User Preference', 'Docs Version', version);
|
|
};
|
|
|
|
// Handle route changes for SPA
|
|
const handleRouteChange = () => {
|
|
if (devMode) {
|
|
console.log('Route changed to:', window.location.pathname);
|
|
}
|
|
|
|
// Reset scroll tracking for new page
|
|
resetScrollTracking();
|
|
|
|
setTimeout(() => {
|
|
const currentTitle = document.title;
|
|
const currentPath = window.location.pathname;
|
|
|
|
// Set custom dimensions before tracking page view
|
|
trackColorMode();
|
|
trackDocsVersion();
|
|
|
|
if (devMode) {
|
|
window._paq.push(['setDomains', ['superset.apache.org']]);
|
|
window._paq.push([
|
|
'setCustomUrl',
|
|
'https://superset.apache.org' + currentPath,
|
|
]);
|
|
} else {
|
|
window._paq.push(['setCustomUrl', currentPath]);
|
|
}
|
|
|
|
window._paq.push(['setReferrerUrl', window.location.href]);
|
|
window._paq.push(['setDocumentTitle', currentTitle]);
|
|
trackPageView(currentPath, currentTitle);
|
|
|
|
// Check for 404 after page renders
|
|
setTimeout(track404, 500);
|
|
}, 100);
|
|
};
|
|
|
|
// Set up Docusaurus route listeners
|
|
const possibleEvents = [
|
|
'docusaurus.routeDidUpdate',
|
|
'docusaurusRouteDidUpdate',
|
|
'routeDidUpdate',
|
|
];
|
|
|
|
if (devMode) {
|
|
console.log('Setting up Matomo tracking with enhanced features');
|
|
}
|
|
|
|
// Store handler references for proper cleanup
|
|
const routeHandlers = possibleEvents.map(eventName => {
|
|
const handler = () => {
|
|
if (devMode) {
|
|
console.log(`Docusaurus route update detected via ${eventName}`);
|
|
}
|
|
handleRouteChange();
|
|
};
|
|
document.addEventListener(eventName, handler);
|
|
return { eventName, handler };
|
|
});
|
|
|
|
// Manual history tracking as fallback
|
|
const originalPushState = window.history.pushState;
|
|
window.history.pushState = function () {
|
|
originalPushState.apply(this, arguments);
|
|
handleRouteChange();
|
|
};
|
|
|
|
window.addEventListener('popstate', handleRouteChange);
|
|
|
|
// Set up event listeners
|
|
document.addEventListener('click', handleLinkClick);
|
|
document.addEventListener('click', handleCTAClick);
|
|
document.addEventListener('play', handleVideoPlay, true);
|
|
document.addEventListener('copy', handleCopy);
|
|
window.addEventListener('scroll', handleScroll, { passive: true });
|
|
|
|
// Watch for color mode changes
|
|
const colorModeObserver = new MutationObserver((mutations) => {
|
|
mutations.forEach((mutation) => {
|
|
if (mutation.attributeName === 'data-theme') {
|
|
trackEvent('User Preference', 'Color Mode Change',
|
|
document.documentElement.getAttribute('data-theme'));
|
|
}
|
|
});
|
|
});
|
|
colorModeObserver.observe(document.documentElement, { attributes: true });
|
|
|
|
// Set up Algolia tracking
|
|
const algoliaObserver = setupAlgoliaTracking();
|
|
|
|
// Initial page tracking
|
|
handleRouteChange();
|
|
|
|
// Cleanup
|
|
return () => {
|
|
routeHandlers.forEach(({ eventName, handler }) => {
|
|
document.removeEventListener(eventName, handler);
|
|
});
|
|
|
|
if (originalPushState) {
|
|
window.history.pushState = originalPushState;
|
|
window.removeEventListener('popstate', handleRouteChange);
|
|
}
|
|
|
|
document.removeEventListener('click', handleLinkClick);
|
|
document.removeEventListener('click', handleCTAClick);
|
|
document.removeEventListener('play', handleVideoPlay, true);
|
|
document.removeEventListener('copy', handleCopy);
|
|
window.removeEventListener('scroll', handleScroll);
|
|
|
|
if (algoliaObserver) {
|
|
algoliaObserver.disconnect();
|
|
}
|
|
if (colorModeObserver) {
|
|
colorModeObserver.disconnect();
|
|
}
|
|
};
|
|
}
|
|
}, []);
|
|
|
|
return children;
|
|
}
|