Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions .github/workflows/generate-code-scanning-query-lists.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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.'
106 changes: 106 additions & 0 deletions src/frame/components/UtmPreserver.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLAnchorElement>(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
}
2 changes: 2 additions & 0 deletions src/frame/components/article/ArticlePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -101,6 +102,7 @@ export const ArticlePage = () => {
return (
<DefaultLayout>
<LinkPreviewPopover />
<UtmPreserver />
{isDev && <ClientSideRefresh />}
{router.pathname.includes('/rest/') && <RestRedirect />}
{currentLayout === 'inline' ? (
Expand Down
Loading