add `search_client` to search events when the host is not `docs.githu… · github/docs@74a0ff1 · GitHub | Latest TMZ Celebrity News & Gossip | Watch TMZ Live
Skip to content

Commit 74a0ff1

Browse files
EbonsignoriCopilot
andauthored
add search_client to search events when the host is not docs.github.com (#56458)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 66c642b commit 74a0ff1

File tree

6 files changed

+128
-0
lines changed

6 files changed

+128
-0
lines changed

src/events/lib/schema.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,10 @@ const search = {
371371
type: 'string',
372372
description: 'Any additional search context, such as component searched.',
373373
},
374+
search_client: {
375+
type: 'string',
376+
description: 'The client name identifier when the request is not from docs.github.com.',
377+
},
374378
},
375379
}
376380

src/events/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ export type EventPropsByType = {
104104
[EventType.search]: {
105105
search_query: string
106106
search_context?: string
107+
search_client?: string
107108
}
108109
[EventType.searchResult]: {
109110
search_result_query: string

src/search/lib/ai-search-proxy.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import got from 'got'
44
import { getHmacWithEpoch } from '@/search/lib/helpers/get-cse-copilot-auth'
55
import { getCSECopilotSource } from '@/search/lib/helpers/cse-copilot-docs-versions'
66
import type { ExtendedRequest } from '@/types'
7+
import { handleExternalSearchAnalytics } from '@/search/lib/helpers/external-search-analytics'
78

89
export const aiSearchProxy = async (req: ExtendedRequest, res: Response) => {
910
const { query, version } = req.body
@@ -29,6 +30,15 @@ export const aiSearchProxy = async (req: ExtendedRequest, res: Response) => {
2930
return
3031
}
3132

33+
// Handle search analytics and client_name validation
34+
const analyticsError = await handleExternalSearchAnalytics(req, 'ai-search')
35+
if (analyticsError) {
36+
res.status(analyticsError.status).json({
37+
errors: [{ message: analyticsError.error }],
38+
})
39+
return
40+
}
41+
3242
const diagnosticTags = [
3343
`version:${version}`.slice(0, 200),
3444
`language:${req.language}`.slice(0, 200),
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { publish } from '@/events/lib/hydro'
2+
import { hydroNames } from '@/events/lib/schema'
3+
4+
/**
5+
* Handles search analytics and client_name validation for external requests
6+
* Returns null if the request should continue, or an error response object if validation failed
7+
*/
8+
export async function handleExternalSearchAnalytics(
9+
req: any,
10+
searchContext: string,
11+
): Promise<{ error: string; status: number } | null> {
12+
const host = req.headers['x-host'] || req.headers.host
13+
const normalizedHost = stripPort(host as string)
14+
15+
// Skip analytics entirely for production and internal staging environments
16+
if (
17+
normalizedHost === 'docs.github.com' ||
18+
normalizedHost.endsWith('.github.net') ||
19+
normalizedHost.endsWith('.githubapp.com')
20+
) {
21+
return null
22+
}
23+
24+
// For localhost, send analytics but auto-set client_name if not provided
25+
let client_name = req.query.client_name || req.body?.client_name
26+
if (normalizedHost === 'localhost' && !client_name) {
27+
client_name = 'localhost'
28+
}
29+
30+
// For all other external requests, require explicit client_name
31+
if (!client_name) {
32+
return {
33+
status: 400,
34+
error: "Missing required parameter 'client_name' for external requests",
35+
}
36+
}
37+
38+
// Send search event with client identifier
39+
try {
40+
await publish({
41+
schema: hydroNames.search,
42+
value: {
43+
type: 'search',
44+
version: '1.0.0',
45+
context: {
46+
event_id: crypto.randomUUID(),
47+
user: 'server-side',
48+
version: '1.0.0',
49+
created: new Date().toISOString(),
50+
hostname: normalizedHost,
51+
path: '',
52+
search: '',
53+
hash: '',
54+
path_language: 'en',
55+
path_version: '',
56+
path_product: '',
57+
path_article: '',
58+
},
59+
search_query: 'REDACTED',
60+
search_context: searchContext,
61+
search_client: client_name as string,
62+
},
63+
})
64+
} catch (error) {
65+
// Don't fail the request if analytics fails
66+
console.error('Failed to send search analytics:', error)
67+
}
68+
69+
return null
70+
}
71+
72+
/**
73+
* Determines if a host should bypass client_name requirement for analytics
74+
* Returns true if the host is docs.github.com or ends with github.net or githubapp.com
75+
* (for production and internal staging environments)
76+
* Note: localhost is NOT included here as it should send analytics with auto-set client_name
77+
*/
78+
export function shouldBypassClientNameRequirement(host: string | undefined): boolean {
79+
if (!host) return false
80+
81+
const normalizedHost = stripPort(host)
82+
return (
83+
normalizedHost === 'docs.github.com' ||
84+
normalizedHost.endsWith('.github.net') ||
85+
normalizedHost.endsWith('.githubapp.com')
86+
)
87+
}
88+
89+
/**
90+
* Strips port number from host string
91+
*/
92+
function stripPort(host: string): string {
93+
const [hostname] = host.split(':')
94+
return hostname
95+
}

src/search/lib/routes/combined-search-route.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { getAISearchAutocompleteResults } from '@/search/lib/get-elasticsearch-r
33
import { searchCacheControl } from '@/frame/middleware/cache-control'
44
import { SURROGATE_ENUMS, setFastlySurrogateKey } from '@/frame/middleware/set-fastly-surrogate-key'
55
import { handleGetSearchResultsError } from '@/search/middleware/search-routes'
6+
import { handleExternalSearchAnalytics } from '@/search/lib/helpers/external-search-analytics'
67

78
import type { Request, Response } from 'express'
89
import type { CombinedSearchResponse, GeneralSearchResponse } from '@/search/types'
@@ -35,6 +36,14 @@ export async function combinedSearchRoute(req: Request, res: Response) {
3536
return res.status(400).json(combinedValidationErrors[0])
3637
}
3738

39+
// Handle search analytics and client_name validation
40+
const analyticsError = await handleExternalSearchAnalytics(req, 'combined-search')
41+
if (analyticsError) {
42+
return res.status(analyticsError.status).json({
43+
error: analyticsError.error,
44+
})
45+
}
46+
3847
try {
3948
const autocompletePromise = getAISearchAutocompleteResults({
4049
indexName: aiIndexName,

src/search/middleware/search-routes.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { getAISearchAutocompleteResults } from '@/search/lib/get-elasticsearch-r
1717
import { getSearchFromRequestParams } from '@/search/lib/search-request-params/get-search-from-request-params'
1818
import { getGeneralSearchResults } from '@/search/lib/get-elasticsearch-results/general-search'
1919
import { combinedSearchRoute } from '@/search/lib/routes/combined-search-route'
20+
import { handleExternalSearchAnalytics } from '@/search/lib/helpers/external-search-analytics'
2021

2122
const router = express.Router()
2223

@@ -36,6 +37,14 @@ router.get(
3637
return res.status(400).json(validationErrors[0])
3738
}
3839

40+
// Handle search analytics and client_name validation
41+
const analyticsError = await handleExternalSearchAnalytics(req, 'general-search')
42+
if (analyticsError) {
43+
return res.status(analyticsError.status).json({
44+
error: analyticsError.error,
45+
})
46+
}
47+
3948
const getResultOptions = {
4049
indexName,
4150
searchParams,

0 commit comments

Comments
 (0)

TMZ Celebrity News – Breaking Stories, Videos & Gossip

Looking for the latest TMZ celebrity news? You've come to the right place. From shocking Hollywood scandals to exclusive videos, TMZ delivers it all in real time.

Whether it’s a red carpet slip-up, a viral paparazzi moment, or a legal drama involving your favorite stars, TMZ news is always first to break the story. Stay in the loop with daily updates, insider tips, and jaw-dropping photos.

🎥 Watch TMZ Live

TMZ Live brings you daily celebrity news and interviews straight from the TMZ newsroom. Don’t miss a beat—watch now and see what’s trending in Hollywood.