diff --git a/.github/workflows/generate-code-scanning-query-lists.yml b/.github/workflows/generate-code-scanning-query-lists.yml index 200b5b3527db..a3e261803b87 100644 --- a/.github/workflows/generate-code-scanning-query-lists.yml +++ b/.github/workflows/generate-code-scanning-query-lists.yml @@ -153,8 +153,10 @@ jobs: --title "Update CodeQL query tables" \ --repo github/docs-internal \ --label "codeql-query-tables,skip FR board,ready-for-doc-review,workflow-generated" \ - --body '👋 humans. This PR updates the **CodeQL query table reusables** with the latest changes in preparation for the next **CodeQL CLI** release. + --body '👋 humans. This PR updates the **CodeQL query table reusables** with the latest changes in preparation for the next **CodeQL CLI** release. (Synced from codeql@${{ steps.codeql.outputs.OPENAPI_COMMIT_SHA }}) - No action is required from the first responder for the Docs content team. This PR will be reviewed and merged by the Code scanning and GHAS focus team as part of the next release of CodeQL CLI. (Synced from codeql@${{ steps.codeql.outputs.OPENAPI_COMMIT_SHA }}) - If CI does not pass or other problems arise, contact #docs-engineering on slack.' + No action is required from the first responder for the Docs content team. This PR is automatically added to the Docs content review board. Any writer can review this by checking that the PR looks sensible. If CI does not pass or other problems arise, contact #docs-engineering on slack. + + + When the DRI for the CodeQL CLI release is ready to publish, they will ask us to merge this PR in #docs-content.' diff --git a/src/frame/components/UtmPreserver.tsx b/src/frame/components/UtmPreserver.tsx new file mode 100644 index 000000000000..34e79445e7e1 --- /dev/null +++ b/src/frame/components/UtmPreserver.tsx @@ -0,0 +1,106 @@ +import { useEffect } from 'react' +import { useRouter } from 'next/router' + +type UtmPreserverProps = { + // CSS selector for links that should preserve UTM parameters + linkSelector?: string + // Specific page paths where this component should be active + activePaths?: string[] +} + +export const UtmPreserver = ({ + linkSelector = 'a[href*="github.com/copilot"], a[href*="github.com/github-copilot"]', + activePaths = ['/copilot/get-started/plans'], +}: UtmPreserverProps) => { + const router = useRouter() + + useEffect(() => { + // Check if current page should have UTM preservation + const shouldPreserveUtm = activePaths.some((path) => router.asPath.includes(path)) + if (!shouldPreserveUtm) return + + // Extract UTM parameters from current URL + const getUtmParams = (): URLSearchParams => { + const urlParams = new URLSearchParams(window.location.search) + const utmParams = new URLSearchParams() + + for (const [key, value] of urlParams) { + if (key.startsWith('utm_')) { + utmParams.set(key, value) + } + } + + return utmParams + } + + // Add UTM parameters to a URL + const addUtmParamsToUrl = (url: string, utmParams: URLSearchParams): string => { + try { + const urlObj = new URL(url) + + for (const [key, value] of utmParams) { + urlObj.searchParams.set(key, value) + } + + return urlObj.toString() + } catch { + // If URL parsing fails, return original URL + return url + } + } + + // Apply UTM parameters to relevant links + const applyUtmToLinks = (): void => { + const utmParams = getUtmParams() + + if (utmParams.toString() === '') return + + const links = document.querySelectorAll(linkSelector) + + links.forEach((link) => { + if (link.href && (link.href.startsWith('http://') || link.href.startsWith('https://'))) { + link.href = addUtmParamsToUrl(link.href, utmParams) + } + }) + } + + // Handle click events for dynamic link modification + const handleLinkClick = (event: Event): void => { + const link = (event.target as Element)?.closest('a') as HTMLAnchorElement + if (!link) return + + // Check if this link matches our selector + if (!link.matches(linkSelector)) return + + const utmParams = getUtmParams() + if (utmParams.toString() === '') return + + if (link.href && (link.href.startsWith('http://') || link.href.startsWith('https://'))) { + link.href = addUtmParamsToUrl(link.href, utmParams) + } + } + + // Apply UTM parameters immediately to existing links + applyUtmToLinks() + + // Also handle clicks for any dynamically added links + document.addEventListener('click', handleLinkClick, true) + + // Re-apply when the route changes (for single-page navigation) + const handleRouteChange = () => { + // Small delay to ensure DOM has updated + setTimeout(applyUtmToLinks, 100) + } + + router.events.on('routeChangeComplete', handleRouteChange) + + // Cleanup + return () => { + document.removeEventListener('click', handleLinkClick, true) + router.events.off('routeChangeComplete', handleRouteChange) + } + }, [router.asPath, router.events, linkSelector, activePaths]) + + // This component doesn't render anything + return null +} diff --git a/src/frame/components/article/ArticlePage.tsx b/src/frame/components/article/ArticlePage.tsx index 882edee0003d..89ef49ed8842 100644 --- a/src/frame/components/article/ArticlePage.tsx +++ b/src/frame/components/article/ArticlePage.tsx @@ -21,6 +21,7 @@ import { Breadcrumbs } from '@/frame/components/page-header/Breadcrumbs' import { Link } from '@/frame/components/Link' import { useTranslation } from '@/languages/components/useTranslation' import { LinkPreviewPopover } from '@/links/components/LinkPreviewPopover' +import { UtmPreserver } from '@/frame/components/UtmPreserver' const ClientSideRefresh = dynamic(() => import('@/frame/components/ClientSideRefresh'), { ssr: false, @@ -101,6 +102,7 @@ export const ArticlePage = () => { return ( + {isDev && } {router.pathname.includes('/rest/') && } {currentLayout === 'inline' ? (