1
0
mirror of synced 2025-12-19 09:57:42 -05:00

Merge pull request #41923 from github/repo-sync

Repo sync
This commit is contained in:
docs-bot
2025-12-17 16:45:08 -08:00
committed by GitHub
8 changed files with 124 additions and 29 deletions

View File

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

View File

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

View File

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

View File

@@ -34,3 +34,6 @@ export const minimumNotFoundHtml = `
&bull; <a href=https://docs.github.com/site-policy/privacy-policies/github-privacy-statement>Privacy</a>
</small>
`.replace(/\n/g, '')
export const ANALYTICS_ENABLED = true
export const HOVERCARDS_ENABLED = true

View File

@@ -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<string> | 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<string> {
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<JourneyContext | null> {
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 }) => {

View File

@@ -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,

View File

@@ -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)

View File

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