From 933c270361342cc4c0a8fa0870cfbe4e37db43c8 Mon Sep 17 00:00:00 2001 From: Kevin Heis Date: Wed, 17 Dec 2025 13:37:22 -0800 Subject: [PATCH 1/5] Disable analytics and hovercards (#58954) --- src/events/components/events.ts | 4 ++-- src/fixtures/tests/playwright-rendering.spec.ts | 5 +++++ src/frame/lib/constants.ts | 3 +++ src/links/components/LinkPreviewPopover.tsx | 5 +++++ 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/events/components/events.ts b/src/events/components/events.ts index 4cdf6a67dbb1..9c37bf255c72 100644 --- a/src/events/components/events.ts +++ b/src/events/components/events.ts @@ -1,4 +1,5 @@ import Cookies from '@/frame/components/lib/cookies' +import { ANALYTICS_ENABLED } from '@/frame/lib/constants' import { parseUserAgent } from './user-agent' import { Router } from 'next/router' import { isLoggedIn } from '@/frame/components/hooks/useHasAccount' @@ -436,8 +437,7 @@ function initPrintEvent() { } export function initializeEvents() { - return - // eslint-disable-next-line no-unreachable + if (!ANALYTICS_ENABLED) return if (initialized) return initialized = true initPageAndExitEvent() // must come first diff --git a/src/fixtures/tests/playwright-rendering.spec.ts b/src/fixtures/tests/playwright-rendering.spec.ts index 385439074e44..d1055d12a3f7 100644 --- a/src/fixtures/tests/playwright-rendering.spec.ts +++ b/src/fixtures/tests/playwright-rendering.spec.ts @@ -1,6 +1,7 @@ import dotenv from 'dotenv' import { test, expect } from '@playwright/test' import { turnOffExperimentsInPage, dismissCTAPopover } from '../helpers/turn-off-experiments' +import { HOVERCARDS_ENABLED, ANALYTICS_ENABLED } from '../../frame/lib/constants' // This exists for the benefit of local testing. // In GitHub Actions, we rely on setting the environment variable directly @@ -347,6 +348,8 @@ test('sidebar custom link functionality works', async ({ page }) => { }) test.describe('hover cards', () => { + test.skip(!HOVERCARDS_ENABLED, 'Hovercards are disabled') + test('hover over link', async ({ page }) => { await page.goto('/pages/quickstart') await turnOffExperimentsInPage(page) @@ -691,6 +694,8 @@ test.describe('test nav at different viewports', () => { }) test.describe('survey', () => { + test.skip(!ANALYTICS_ENABLED, 'Analytics are disabled') + test('happy path, thumbs up and enter comment and email', async ({ page }) => { let fulfilled = 0 let hasSurveyPressedEvent = false diff --git a/src/frame/lib/constants.ts b/src/frame/lib/constants.ts index 250450778ca6..2ae490485451 100644 --- a/src/frame/lib/constants.ts +++ b/src/frame/lib/constants.ts @@ -34,3 +34,6 @@ export const minimumNotFoundHtml = ` • Privacy `.replace(/\n/g, '') + +export const ANALYTICS_ENABLED = false +export const HOVERCARDS_ENABLED = false diff --git a/src/links/components/LinkPreviewPopover.tsx b/src/links/components/LinkPreviewPopover.tsx index 47b5e2d32bc0..c17deda00928 100644 --- a/src/links/components/LinkPreviewPopover.tsx +++ b/src/links/components/LinkPreviewPopover.tsx @@ -1,4 +1,5 @@ import { useEffect } from 'react' +import { HOVERCARDS_ENABLED } from '@/frame/lib/constants' // We postpone the initial delay a bit in case the user didn't mean to // hover over the link. Perhaps they just dragged the mouse over on their @@ -450,6 +451,8 @@ export function LinkPreviewPopover() { // This is to track if the user entirely tabs out of the window. // For example if they go to the address bar. useEffect(() => { + if (!HOVERCARDS_ENABLED) return + function windowBlur() { popoverHide() } @@ -460,6 +463,8 @@ export function LinkPreviewPopover() { }, []) useEffect(() => { + if (!HOVERCARDS_ENABLED) return + function showPopover(event: MouseEvent) { const target = event.currentTarget as HTMLLinkElement popoverShow(target) From e31ddba6e582935da465d3f0f7f4f077a32e0ec6 Mon Sep 17 00:00:00 2001 From: Kevin Heis Date: Wed, 17 Dec 2025 14:25:42 -0800 Subject: [PATCH 2/5] Expand README for src/webhooks (#58905) Co-authored-by: Robert Sese <734194+rsese@users.noreply.github.com> --- src/webhooks/README.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/webhooks/README.md b/src/webhooks/README.md index b1ea2db302e9..fc33b14a1c3c 100644 --- a/src/webhooks/README.md +++ b/src/webhooks/README.md @@ -57,3 +57,37 @@ Slack: `#docs-engineering` Repo: `github/docs-engineering` If you have a question about the webhooks pipeline, you can ask in the `#docs-engineering` Slack channel. If you notice a problem with the webhooks pipeline, you can open an issue in the `github/docs-engineering` repository. + +## Ownership & Escalation + +### Ownership +- **Team**: Docs Engineering +- **Source data**: API Platform (github/rest-api-description) + +### Escalation path +1. **Pipeline failures** → #docs-engineering Slack +2. **OpenAPI schema issues** → #api-platform Slack +3. **Production incidents** → #docs-engineering + +### On-call procedures +If the webhooks pipeline fails: +1. Check workflow logs in `.github/workflows/sync-openapi.yml` +2. Verify access to `github/rest-api-description` repo +3. Check for OpenAPI schema validation errors +4. Review changes in generated data files +5. Check `config.json` SHA tracking +6. Escalate to API Platform team if schema issue + +### Monitoring +- Pipeline runs automatically on daily schedule (shared with REST/GitHub Apps) +- PRs created with `github-openapi-bot` label +- SHA tracking in `config.json` for version history +- Failures visible in GitHub Actions + +This pipeline is in maintenance mode. We will continue to support ongoing improvements incoming from the platform but we are not expecting new functionality moving forward. + +### Known limitations +- **Shared pipeline** - Cannot run webhooks independently of REST/GitHub Apps +- **Single page** - All events on one page (may impact performance) +- **Introduction placement** - Manual content must be at start of file +- **Payload complexity** - Some payloads are very large and complex From f22fea3519c8b20cb559c394564878c4a5ab996a Mon Sep 17 00:00:00 2001 From: Kevin Heis Date: Wed, 17 Dec 2025 14:34:23 -0800 Subject: [PATCH 3/5] perf: optimize journey path resolver and middleware (#58955) Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/journeys/lib/journey-path-resolver.ts | 90 +++++++++++++++++------ src/journeys/middleware/journey-track.ts | 10 ++- 2 files changed, 74 insertions(+), 26 deletions(-) diff --git a/src/journeys/lib/journey-path-resolver.ts b/src/journeys/lib/journey-path-resolver.ts index b1136fbdece0..ec93200dfe8c 100644 --- a/src/journeys/lib/journey-path-resolver.ts +++ b/src/journeys/lib/journey-path-resolver.ts @@ -68,6 +68,13 @@ type ContentContext = { // Cache for journey pages so we only filter all pages once let cachedJourneyPages: JourneyPage[] | null = null +// Cache for guide paths to quickly check if a page is part of any journey +let cachedGuidePaths: Set | null = null +let hasDynamicGuides = false + +function needsRendering(str: string): boolean { + return str.includes('{{') || str.includes('{%') || str.includes('[') || str.includes('<') +} function getJourneyPages(pages: Pages): JourneyPage[] { if (!cachedJourneyPages) { @@ -78,6 +85,27 @@ function getJourneyPages(pages: Pages): JourneyPage[] { return cachedJourneyPages } +function getGuidePaths(pages: Pages): Set { + if (!cachedGuidePaths) { + cachedGuidePaths = new Set() + const journeyPages = getJourneyPages(pages) + for (const page of journeyPages) { + if (!page.journeyTracks) continue + for (const track of page.journeyTracks) { + if (!track.guides) continue + for (const guide of track.guides) { + if (needsRendering(guide.href)) { + hasDynamicGuides = true + } else { + cachedGuidePaths.add(normalizeGuidePath(guide.href)) + } + } + } + } + } + return cachedGuidePaths +} + function normalizeGuidePath(path: string): string { // First ensure we have a leading slash for consistent processing const pathWithSlash = path.startsWith('/') ? path : `/${path}` @@ -133,6 +161,16 @@ export async function resolveJourneyContext( ): Promise { const normalizedPath = normalizeGuidePath(articlePath) + // Optimization: Fast path check + // If we are not forcing a specific journey page, check our global cache + if (!currentJourneyPage) { + const guidePaths = getGuidePaths(pages) + // If we have no dynamic guides and this path isn't in our known guides, return null early. + if (!hasDynamicGuides && !guidePaths.has(normalizedPath)) { + return null + } + } + // Use the current journey page if provided, otherwise find all journey pages const journeyPages = currentJourneyPage ? [currentJourneyPage] : getJourneyPages(pages) @@ -165,15 +203,17 @@ export async function resolveJourneyContext( let renderedGuidePath = guidePath // Handle Liquid conditionals in guide paths - try { - renderedGuidePath = await executeWithFallback( - context, - () => renderContent(guidePath, context, { textOnly: true }), - () => guidePath, - ) - } catch { - // If rendering fails, use the original path rather than erroring - renderedGuidePath = guidePath + if (needsRendering(guidePath)) { + try { + renderedGuidePath = await executeWithFallback( + context, + () => renderContent(guidePath, context, { textOnly: true }), + () => guidePath, + ) + } catch { + // If rendering fails, use the original path rather than erroring + renderedGuidePath = guidePath + } } const normalizedGuidePath = normalizeGuidePath(renderedGuidePath) @@ -189,15 +229,17 @@ export async function resolveJourneyContext( let renderedAlternativeNextStep = alternativeNextStep // Handle Liquid conditionals in branching text which likely has links - try { - renderedAlternativeNextStep = await executeWithFallback( - context, - () => renderContent(alternativeNextStep, context), - () => alternativeNextStep, - ) - } catch { - // If rendering fails, use the original branching text rather than erroring - renderedAlternativeNextStep = alternativeNextStep + if (needsRendering(alternativeNextStep)) { + try { + renderedAlternativeNextStep = await executeWithFallback( + context, + () => renderContent(alternativeNextStep, context), + () => alternativeNextStep, + ) + } catch { + // If rendering fails, use the original branching text rather than erroring + renderedAlternativeNextStep = alternativeNextStep + } } result = { @@ -278,10 +320,14 @@ export async function resolveJourneyTracks( const result = await Promise.all( journeyTracks.map(async (track) => { // Render Liquid templates in title and description - const renderedTitle = await renderContent(track.title, context, { textOnly: true }) - const renderedDescription = track.description - ? await renderContent(track.description, context, { textOnly: true }) - : undefined + const renderedTitle = needsRendering(track.title) + ? await renderContent(track.title, context, { textOnly: true }) + : track.title + + const renderedDescription = + track.description && needsRendering(track.description) + ? await renderContent(track.description, context, { textOnly: true }) + : track.description const guides = await Promise.all( track.guides.map(async (guide: { href: string; alternativeNextStep?: string }) => { diff --git a/src/journeys/middleware/journey-track.ts b/src/journeys/middleware/journey-track.ts index 481ba58ca64b..38ddfe618aae 100644 --- a/src/journeys/middleware/journey-track.ts +++ b/src/journeys/middleware/journey-track.ts @@ -1,20 +1,22 @@ import type { Response, NextFunction } from 'express' import type { ExtendedRequest, Context } from '@/types' +import { resolveJourneyTracks, resolveJourneyContext } from '../lib/journey-path-resolver' + export default async function journeyTrack( req: ExtendedRequest & { context: Context }, res: Response, next: NextFunction, ) { + if (req.method !== 'GET' && req.method !== 'HEAD') return next() + if (!req.context) throw new Error('request is not contextualized') if (!req.context.page) return next() try { - const journeyResolver = await import('../lib/journey-path-resolver') - // If this page has journey tracks defined, resolve them for the landing page if ((req.context.page as any).journeyTracks) { - const resolvedTracks = await journeyResolver.resolveJourneyTracks( + const resolvedTracks = await resolveJourneyTracks( (req.context.page as any).journeyTracks, req.context, ) @@ -24,7 +26,7 @@ export default async function journeyTrack( } // Always try to resolve journey context (for navigation on guide articles) - const journeyContext = await journeyResolver.resolveJourneyContext( + const journeyContext = await resolveJourneyContext( req.pagePath || '', req.context.pages || {}, req.context, From e122627b86dab85fa43861294eab239561f18abb Mon Sep 17 00:00:00 2001 From: Kevin Heis Date: Wed, 17 Dec 2025 14:57:12 -0800 Subject: [PATCH 4/5] revert disable analytics (#58953) --- src/frame/lib/constants.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/frame/lib/constants.ts b/src/frame/lib/constants.ts index 2ae490485451..8dd839bd6f3c 100644 --- a/src/frame/lib/constants.ts +++ b/src/frame/lib/constants.ts @@ -35,5 +35,5 @@ export const minimumNotFoundHtml = ` `.replace(/\n/g, '') -export const ANALYTICS_ENABLED = false -export const HOVERCARDS_ENABLED = false +export const ANALYTICS_ENABLED = true +export const HOVERCARDS_ENABLED = true From 738e2dd9865e299c9af6dac49b4bc3c0c8749cd2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Dec 2025 23:11:31 +0000 Subject: [PATCH 5/5] Bump github/gh-base-image/gh-base-noble from 20251119-090131-gb27dc275c to 20251217-105955-g05726ec4c in the baseimages group (#58956) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 2573d0924ce9..9bee3357a106 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,7 @@ # --------------------------------------------------------------- # To update the sha: # https://github.com/github/gh-base-image/pkgs/container/gh-base-image%2Fgh-base-noble -FROM ghcr.io/github/gh-base-image/gh-base-noble:20251119-090131-gb27dc275c AS base +FROM ghcr.io/github/gh-base-image/gh-base-noble:20251217-105955-g05726ec4c AS base # Install curl for Node install and determining the early access branch # Install git for cloning docs-early-access & translations repos