Skip to content

Commit 92d65ae

Browse files
authored
Merge pull request #41923 from github/repo-sync
Repo sync
2 parents c2af03f + 738e2dd commit 92d65ae

File tree

8 files changed

+124
-29
lines changed

8 files changed

+124
-29
lines changed

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
# ---------------------------------------------------------------
99
# To update the sha:
1010
# https://github.com/github/gh-base-image/pkgs/container/gh-base-image%2Fgh-base-noble
11-
FROM ghcr.io/github/gh-base-image/gh-base-noble:20251119-090131-gb27dc275c AS base
11+
FROM ghcr.io/github/gh-base-image/gh-base-noble:20251217-105955-g05726ec4c AS base
1212

1313
# Install curl for Node install and determining the early access branch
1414
# Install git for cloning docs-early-access & translations repos

src/events/components/events.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import Cookies from '@/frame/components/lib/cookies'
2+
import { ANALYTICS_ENABLED } from '@/frame/lib/constants'
23
import { parseUserAgent } from './user-agent'
34
import { Router } from 'next/router'
45
import { isLoggedIn } from '@/frame/components/hooks/useHasAccount'
@@ -436,8 +437,7 @@ function initPrintEvent() {
436437
}
437438

438439
export function initializeEvents() {
439-
return
440-
// eslint-disable-next-line no-unreachable
440+
if (!ANALYTICS_ENABLED) return
441441
if (initialized) return
442442
initialized = true
443443
initPageAndExitEvent() // must come first

src/fixtures/tests/playwright-rendering.spec.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import dotenv from 'dotenv'
22
import { test, expect } from '@playwright/test'
33
import { turnOffExperimentsInPage, dismissCTAPopover } from '../helpers/turn-off-experiments'
4+
import { HOVERCARDS_ENABLED, ANALYTICS_ENABLED } from '../../frame/lib/constants'
45

56
// This exists for the benefit of local testing.
67
// In GitHub Actions, we rely on setting the environment variable directly
@@ -347,6 +348,8 @@ test('sidebar custom link functionality works', async ({ page }) => {
347348
})
348349

349350
test.describe('hover cards', () => {
351+
test.skip(!HOVERCARDS_ENABLED, 'Hovercards are disabled')
352+
350353
test('hover over link', async ({ page }) => {
351354
await page.goto('/pages/quickstart')
352355
await turnOffExperimentsInPage(page)
@@ -691,6 +694,8 @@ test.describe('test nav at different viewports', () => {
691694
})
692695

693696
test.describe('survey', () => {
697+
test.skip(!ANALYTICS_ENABLED, 'Analytics are disabled')
698+
694699
test('happy path, thumbs up and enter comment and email', async ({ page }) => {
695700
let fulfilled = 0
696701
let hasSurveyPressedEvent = false

src/frame/lib/constants.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,6 @@ export const minimumNotFoundHtml = `
3434
&bull; <a href=https://docs.github.com/site-policy/privacy-policies/github-privacy-statement>Privacy</a>
3535
</small>
3636
`.replace(/\n/g, '')
37+
38+
export const ANALYTICS_ENABLED = true
39+
export const HOVERCARDS_ENABLED = true

src/journeys/lib/journey-path-resolver.ts

Lines changed: 68 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,13 @@ type ContentContext = {
6868

6969
// Cache for journey pages so we only filter all pages once
7070
let cachedJourneyPages: JourneyPage[] | null = null
71+
// Cache for guide paths to quickly check if a page is part of any journey
72+
let cachedGuidePaths: Set<string> | null = null
73+
let hasDynamicGuides = false
74+
75+
function needsRendering(str: string): boolean {
76+
return str.includes('{{') || str.includes('{%') || str.includes('[') || str.includes('<')
77+
}
7178

7279
function getJourneyPages(pages: Pages): JourneyPage[] {
7380
if (!cachedJourneyPages) {
@@ -78,6 +85,27 @@ function getJourneyPages(pages: Pages): JourneyPage[] {
7885
return cachedJourneyPages
7986
}
8087

88+
function getGuidePaths(pages: Pages): Set<string> {
89+
if (!cachedGuidePaths) {
90+
cachedGuidePaths = new Set()
91+
const journeyPages = getJourneyPages(pages)
92+
for (const page of journeyPages) {
93+
if (!page.journeyTracks) continue
94+
for (const track of page.journeyTracks) {
95+
if (!track.guides) continue
96+
for (const guide of track.guides) {
97+
if (needsRendering(guide.href)) {
98+
hasDynamicGuides = true
99+
} else {
100+
cachedGuidePaths.add(normalizeGuidePath(guide.href))
101+
}
102+
}
103+
}
104+
}
105+
}
106+
return cachedGuidePaths
107+
}
108+
81109
function normalizeGuidePath(path: string): string {
82110
// First ensure we have a leading slash for consistent processing
83111
const pathWithSlash = path.startsWith('/') ? path : `/${path}`
@@ -133,6 +161,16 @@ export async function resolveJourneyContext(
133161
): Promise<JourneyContext | null> {
134162
const normalizedPath = normalizeGuidePath(articlePath)
135163

164+
// Optimization: Fast path check
165+
// If we are not forcing a specific journey page, check our global cache
166+
if (!currentJourneyPage) {
167+
const guidePaths = getGuidePaths(pages)
168+
// If we have no dynamic guides and this path isn't in our known guides, return null early.
169+
if (!hasDynamicGuides && !guidePaths.has(normalizedPath)) {
170+
return null
171+
}
172+
}
173+
136174
// Use the current journey page if provided, otherwise find all journey pages
137175
const journeyPages = currentJourneyPage ? [currentJourneyPage] : getJourneyPages(pages)
138176

@@ -165,15 +203,17 @@ export async function resolveJourneyContext(
165203
let renderedGuidePath = guidePath
166204

167205
// Handle Liquid conditionals in guide paths
168-
try {
169-
renderedGuidePath = await executeWithFallback(
170-
context,
171-
() => renderContent(guidePath, context, { textOnly: true }),
172-
() => guidePath,
173-
)
174-
} catch {
175-
// If rendering fails, use the original path rather than erroring
176-
renderedGuidePath = guidePath
206+
if (needsRendering(guidePath)) {
207+
try {
208+
renderedGuidePath = await executeWithFallback(
209+
context,
210+
() => renderContent(guidePath, context, { textOnly: true }),
211+
() => guidePath,
212+
)
213+
} catch {
214+
// If rendering fails, use the original path rather than erroring
215+
renderedGuidePath = guidePath
216+
}
177217
}
178218

179219
const normalizedGuidePath = normalizeGuidePath(renderedGuidePath)
@@ -189,15 +229,17 @@ export async function resolveJourneyContext(
189229
let renderedAlternativeNextStep = alternativeNextStep
190230

191231
// Handle Liquid conditionals in branching text which likely has links
192-
try {
193-
renderedAlternativeNextStep = await executeWithFallback(
194-
context,
195-
() => renderContent(alternativeNextStep, context),
196-
() => alternativeNextStep,
197-
)
198-
} catch {
199-
// If rendering fails, use the original branching text rather than erroring
200-
renderedAlternativeNextStep = alternativeNextStep
232+
if (needsRendering(alternativeNextStep)) {
233+
try {
234+
renderedAlternativeNextStep = await executeWithFallback(
235+
context,
236+
() => renderContent(alternativeNextStep, context),
237+
() => alternativeNextStep,
238+
)
239+
} catch {
240+
// If rendering fails, use the original branching text rather than erroring
241+
renderedAlternativeNextStep = alternativeNextStep
242+
}
201243
}
202244

203245
result = {
@@ -278,10 +320,14 @@ export async function resolveJourneyTracks(
278320
const result = await Promise.all(
279321
journeyTracks.map(async (track) => {
280322
// Render Liquid templates in title and description
281-
const renderedTitle = await renderContent(track.title, context, { textOnly: true })
282-
const renderedDescription = track.description
283-
? await renderContent(track.description, context, { textOnly: true })
284-
: undefined
323+
const renderedTitle = needsRendering(track.title)
324+
? await renderContent(track.title, context, { textOnly: true })
325+
: track.title
326+
327+
const renderedDescription =
328+
track.description && needsRendering(track.description)
329+
? await renderContent(track.description, context, { textOnly: true })
330+
: track.description
285331

286332
const guides = await Promise.all(
287333
track.guides.map(async (guide: { href: string; alternativeNextStep?: string }) => {

src/journeys/middleware/journey-track.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,22 @@
11
import type { Response, NextFunction } from 'express'
22
import type { ExtendedRequest, Context } from '@/types'
33

4+
import { resolveJourneyTracks, resolveJourneyContext } from '../lib/journey-path-resolver'
5+
46
export default async function journeyTrack(
57
req: ExtendedRequest & { context: Context },
68
res: Response,
79
next: NextFunction,
810
) {
11+
if (req.method !== 'GET' && req.method !== 'HEAD') return next()
12+
913
if (!req.context) throw new Error('request is not contextualized')
1014
if (!req.context.page) return next()
1115

1216
try {
13-
const journeyResolver = await import('../lib/journey-path-resolver')
14-
1517
// If this page has journey tracks defined, resolve them for the landing page
1618
if ((req.context.page as any).journeyTracks) {
17-
const resolvedTracks = await journeyResolver.resolveJourneyTracks(
19+
const resolvedTracks = await resolveJourneyTracks(
1820
(req.context.page as any).journeyTracks,
1921
req.context,
2022
)
@@ -24,7 +26,7 @@ export default async function journeyTrack(
2426
}
2527

2628
// Always try to resolve journey context (for navigation on guide articles)
27-
const journeyContext = await journeyResolver.resolveJourneyContext(
29+
const journeyContext = await resolveJourneyContext(
2830
req.pagePath || '',
2931
req.context.pages || {},
3032
req.context,

src/links/components/LinkPreviewPopover.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { useEffect } from 'react'
2+
import { HOVERCARDS_ENABLED } from '@/frame/lib/constants'
23

34
// We postpone the initial delay a bit in case the user didn't mean to
45
// hover over the link. Perhaps they just dragged the mouse over on their
@@ -450,6 +451,8 @@ export function LinkPreviewPopover() {
450451
// This is to track if the user entirely tabs out of the window.
451452
// For example if they go to the address bar.
452453
useEffect(() => {
454+
if (!HOVERCARDS_ENABLED) return
455+
453456
function windowBlur() {
454457
popoverHide()
455458
}
@@ -460,6 +463,8 @@ export function LinkPreviewPopover() {
460463
}, [])
461464

462465
useEffect(() => {
466+
if (!HOVERCARDS_ENABLED) return
467+
463468
function showPopover(event: MouseEvent) {
464469
const target = event.currentTarget as HTMLLinkElement
465470
popoverShow(target)

src/webhooks/README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,37 @@ Slack: `#docs-engineering`
5757
Repo: `github/docs-engineering`
5858

5959
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.
60+
61+
## Ownership & Escalation
62+
63+
### Ownership
64+
- **Team**: Docs Engineering
65+
- **Source data**: API Platform (github/rest-api-description)
66+
67+
### Escalation path
68+
1. **Pipeline failures** → #docs-engineering Slack
69+
2. **OpenAPI schema issues** → #api-platform Slack
70+
3. **Production incidents** → #docs-engineering
71+
72+
### On-call procedures
73+
If the webhooks pipeline fails:
74+
1. Check workflow logs in `.github/workflows/sync-openapi.yml`
75+
2. Verify access to `github/rest-api-description` repo
76+
3. Check for OpenAPI schema validation errors
77+
4. Review changes in generated data files
78+
5. Check `config.json` SHA tracking
79+
6. Escalate to API Platform team if schema issue
80+
81+
### Monitoring
82+
- Pipeline runs automatically on daily schedule (shared with REST/GitHub Apps)
83+
- PRs created with `github-openapi-bot` label
84+
- SHA tracking in `config.json` for version history
85+
- Failures visible in GitHub Actions
86+
87+
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.
88+
89+
### Known limitations
90+
- **Shared pipeline** - Cannot run webhooks independently of REST/GitHub Apps
91+
- **Single page** - All events on one page (may impact performance)
92+
- **Introduction placement** - Manual content must be at start of file
93+
- **Payload complexity** - Some payloads are very large and complex

0 commit comments

Comments
 (0)