Skip to content

Commit be0208b

Browse files
authored
Merge pull request github#36500 from github/repo-sync
Repo sync
2 parents c0bb4c5 + 2e21b74 commit be0208b

27 files changed

+907
-468
lines changed

content/rest/orgs/rule-suites.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ intro: Use the REST API to manage rule suites for organizations.
55
versions: # DO NOT MANUALLY EDIT. CHANGES WILL BE OVERWRITTEN BY A 🤖
66
fpt: '*'
77
ghec: '*'
8-
ghes: '>=3.12'
8+
ghes: '*'
99
topics:
1010
- API
1111
autogenerated: rest

content/rest/repos/rule-suites.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ intro: Use the REST API to manage rule suites for repositories.
55
versions: # DO NOT MANUALLY EDIT. CHANGES WILL BE OVERWRITTEN BY A 🤖
66
fpt: '*'
77
ghec: '*'
8-
ghes: '>=3.12'
8+
ghes: '*'
99
topics:
1010
- API
1111
autogenerated: rest

data/ui.yml

+5-3
Original file line numberDiff line numberDiff line change
@@ -38,20 +38,22 @@ search:
3838
beta_tag: Beta
3939
return_to_search: Return to search
4040
clear_search_query: Clear
41+
view_all_search_results: View all {{length}} results
42+
no_results_found: No results found
4143
ai:
4244
disclaimer: Copilot uses AI. Check for mistakes by reviewing the links in the response.
4345
references: References from these articles
4446
loading_status_message: Loading Copilot response...
4547
done_loading_status_message: Done loading Copilot response
46-
unable_to_answer: Sorry, I'm unable to answer that question. Please try a different query.
48+
unable_to_answer: Sorry, I'm unable to answer that question. Please try a different query or search our docs.
4749
copy_answer: Copy answer
4850
copied_announcement: Copied!
4951
thumbs_up: This answer was helpful
5052
thumbs_down: This answer was not helpful
5153
thumbs_announcement: Thank you for your feedback!
5254
failure:
53-
autocomplete_title: There was an error loading autocomplete results.
54-
ai_title: There was an error loading the AI assistant.
55+
general_title: There was an error loading search results.
56+
ai_title: There was an error loading Copilot.
5557
description: You can still use this field to search our docs.
5658
old_search:
5759
description: Enter a search term to find it in the GitHub Docs.

src/events/lib/schema.ts

+23
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,22 @@ const exit = {
262262
},
263263
}
264264

265+
const keyboard = {
266+
type: 'object',
267+
additionalProperties: false,
268+
required: ['pressed_key', 'pressed_on'],
269+
properties: {
270+
pressed_key: {
271+
type: 'string',
272+
description: 'The key the user pressed.',
273+
},
274+
pressed_on: {
275+
type: 'string',
276+
description: 'The element/identifier the user pressed the key on.',
277+
},
278+
},
279+
}
280+
265281
const link = {
266282
type: 'object',
267283
additionalProperties: false,
@@ -400,6 +416,7 @@ const aiSearchResult = {
400416
'ai_search_result_query',
401417
'ai_search_result_response',
402418
'ai_search_result_links_json',
419+
'ai_search_result_provided_answer',
403420
],
404421
properties: {
405422
context,
@@ -420,6 +437,10 @@ const aiSearchResult = {
420437
description:
421438
'Dynamic JSON string of an array of "link" objects in the form: [{ "type": "reference" | "inline", "url": "https://..", "product": "issues" | "pages" | ... }, ...]',
422439
},
440+
ai_search_result_provided_answer: {
441+
type: 'boolean',
442+
description: 'Whether the GPT was able to answer the query.',
443+
},
423444
},
424445
}
425446

@@ -584,6 +605,7 @@ const validation = {
584605
export const schemas = {
585606
page,
586607
exit,
608+
keyboard,
587609
link,
588610
hover,
589611
search,
@@ -600,6 +622,7 @@ export const schemas = {
600622
export const hydroNames = {
601623
page: 'docs.v0.PageEvent',
602624
exit: 'docs.v0.ExitEvent',
625+
keyboard: 'docs.v0.KeyboardEvent',
603626
link: 'docs.v0.LinkEvent',
604627
hover: 'docs.v0.HoverEvent',
605628
search: 'docs.v0.SearchEvent',

src/events/types.ts

+6
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ export enum EventType {
22
aiSearchResult = 'aiSearchResult',
33
page = 'page',
44
exit = 'exit',
5+
keyboard = 'keyboard',
56
link = 'link',
67
hover = 'hover',
78
search = 'search',
@@ -59,6 +60,7 @@ export type EventPropsByType = {
5960
// Dynamic JSON string of an array of "link" objects in the form:
6061
// [{ "type": "reference" | "inline", "url": "https://..", "product": "issues" | "pages" | ... }, ...]
6162
ai_search_result_links_json: string
63+
ai_search_result_provided_answer: boolean
6264
}
6365
[EventType.clipboard]: {
6466
clipboard_operation: string
@@ -82,6 +84,10 @@ export type EventPropsByType = {
8284
hover_url: string
8385
hover_samesite?: boolean
8486
}
87+
[EventType.keyboard]: {
88+
pressed_key: string
89+
pressed_on: string
90+
}
8591
[EventType.link]: {
8692
link_url: string
8793
link_samesite?: boolean

src/fixtures/fixtures/data/ui.yml

+5-3
Original file line numberDiff line numberDiff line change
@@ -38,20 +38,22 @@ search:
3838
beta_tag: Beta
3939
return_to_search: Return to search
4040
clear_search_query: Clear
41+
view_all_search_results: View all {{length}} results
42+
no_results_found: No results found
4143
ai:
4244
disclaimer: Copilot uses AI. Check for mistakes by reviewing the links in the response.
4345
references: References from these articles
4446
loading_status_message: Loading Copilot response...
4547
done_loading_status_message: Done loading Copilot response
46-
unable_to_answer: Sorry, I'm unable to answer that question. Please try a different query.
48+
unable_to_answer: Sorry, I'm unable to answer that question. Please try a different query or search our docs.
4749
copy_answer: Copy answer
4850
copied_announcement: Copied!
4951
thumbs_up: This answer was helpful
5052
thumbs_down: This answer was not helpful
5153
thumbs_announcement: Thank you for your feedback!
5254
failure:
53-
autocomplete_title: There was an error loading autocomplete results.
54-
ai_title: There was an error loading the AI assistant.
55+
general_title: There was an error loading search results.
56+
ai_title: There was an error loading Copilot.
5557
description: You can still use this field to search our docs.
5658
old_search:
5759
description: Enter a search term to find it in the GitHub Docs.

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

+36-5
Original file line numberDiff line numberDiff line change
@@ -82,22 +82,53 @@ test('open new search, and perform a general search', async ({ page }) => {
8282
await page.getByTestId('search').click()
8383

8484
await page.getByTestId('overlay-search-input').fill('serve playwright')
85-
// Let new suggestions load
85+
// Wait for the results to load
86+
// NOTE: In the UI we wait for results to load before allowing "enter", because we don't want
87+
// to allow an unnecessary request when there are no search results. Easier to wait 1 second
8688
await page.waitForTimeout(1000)
87-
// Navigate to general search item, "serve playwright"
88-
await page.keyboard.press('ArrowDown')
89-
// Select the general search item, "serve playwright"
89+
// Press enter to perform general search
9090
await page.keyboard.press('Enter')
9191

92-
await expect(page).toHaveURL(/\/search\?query=serve\+playwright/)
92+
await expect(page).toHaveURL(
93+
/\/search\?search-overlay-input=serve\+playwright&query=serve\+playwright/,
94+
)
9395
await expect(page).toHaveTitle(/\d Search results for "serve playwright"/)
9496

97+
// The first result should be "For Playwright"
9598
await page.getByRole('link', { name: 'For Playwright' }).click()
9699

97100
await expect(page).toHaveURL(/\/get-started\/foo\/for-playwright$/)
98101
await expect(page).toHaveTitle(/For Playwright/)
99102
})
100103

104+
test('open new search, and select a general search article', async ({ page }) => {
105+
test.skip(!SEARCH_TESTS, 'No local Elasticsearch, no tests involving search')
106+
107+
await page.goto('/')
108+
109+
// Enable the AI search experiment by overriding the control group
110+
await page.evaluate(() => {
111+
// @ts-expect-error overrideControlGroup is a custom function added to the window object
112+
window.overrideControlGroup('ai_search_experiment', 'treatment')
113+
})
114+
115+
await page.getByTestId('search').click()
116+
117+
await page.getByTestId('overlay-search-input').fill('serve playwright')
118+
// Let new suggestions load
119+
await page.waitForTimeout(1000)
120+
// Navigate to general search item, "For Playwright"
121+
await page.keyboard.press('ArrowDown')
122+
// Select the general search item, "For Playwright"
123+
await page.keyboard.press('Enter')
124+
125+
// We should now be on the page for "For Playwright"
126+
await expect(page).toHaveURL(
127+
/\/get-started\/foo\/for-playwright\?search-overlay-input=serve\+playwright$/,
128+
)
129+
await expect(page).toHaveTitle(/For Playwright/)
130+
})
131+
101132
test('open new search, and get auto-complete results', async ({ page }) => {
102133
test.skip(!SEARCH_TESTS, 'No local Elasticsearch, no tests involving search')
103134

src/frame/components/hooks/useQueryParam.ts

+26-47
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// The `queryParam` variable returned from this method are stateful and will be set to the query param on page load
33

44
import { useRouter } from 'next/router'
5-
import { useEffect, useState } from 'react'
5+
import { useState, useEffect } from 'react'
66
import { parseDebug } from '@/search/components/hooks/useQuery'
77

88
type UseQueryParamReturn<T extends string | boolean> = {
@@ -23,69 +23,48 @@ export function useQueryParam(
2323
): UseQueryParamReturn<any> {
2424
const router = useRouter()
2525

26-
// Determine the initial value of the query param
27-
let initialQueryParam = ''
28-
const paramValue = router.query[queryParamKey]
29-
30-
if (paramValue) {
31-
if (Array.isArray(paramValue)) {
32-
initialQueryParam = paramValue[0]
33-
} else {
34-
initialQueryParam = paramValue
35-
}
36-
}
37-
38-
const debugValue = parseDebug(router.query.debug)
39-
40-
// Return type will be set based on overloads
41-
const [queryParamString, setQueryParamState] = useState<string>(initialQueryParam)
42-
const [debug] = useState<boolean>(debugValue)
26+
const [queryParamString, setQueryParamState] = useState<string>('')
27+
const [debug, setDebug] = useState<boolean>(false)
28+
const queryParam: string | boolean = isBoolean ? queryParamString === 'true' : queryParamString
4329

44-
// If the query param changes in the URL, update the state
30+
// Only set the initial query param values on page load, the rest of the time we use React state
4531
useEffect(() => {
32+
let initialQueryParam = ''
4633
const paramValue = router.query[queryParamKey]
47-
4834
if (paramValue) {
49-
if (Array.isArray(paramValue)) {
50-
setQueryParamState(paramValue[0])
51-
} else {
52-
setQueryParamState(paramValue)
53-
}
35+
initialQueryParam = Array.isArray(paramValue) ? paramValue[0] : paramValue
5436
}
55-
}, [router.query, queryParamKey])
56-
57-
// Determine the type of queryParam based on isBoolean
58-
const queryParam: string | boolean = isBoolean ? queryParamString === 'true' : queryParamString
37+
setQueryParamState(initialQueryParam)
38+
setDebug(parseDebug(router.query.debug || '') || false)
39+
}, [queryParamKey, router.pathname])
5940

6041
const setQueryParam = (value: string | boolean) => {
61-
const { pathname, hash, search } = window.location
62-
63-
let newValue: string = value as string
64-
65-
// If it's a false boolean or empty string, just remove the query param
66-
if (!value) {
67-
newValue = ''
68-
} else if (typeof value === 'boolean') {
69-
newValue = 'true'
70-
}
71-
72-
const params = new URLSearchParams(search)
42+
const newValue = typeof value === 'boolean' ? (value ? 'true' : '') : value
43+
const [asPathWithoutHash] = router.asPath.split('#')
44+
const [asPathRoot, asPathQuery = ''] = asPathWithoutHash.split('?')
45+
const currentParams = new URLSearchParams(asPathQuery)
7346
if (newValue) {
74-
params.set(queryParamKey, newValue)
47+
currentParams.set(queryParamKey, newValue)
7548
} else {
76-
params.delete(queryParamKey)
49+
currentParams.delete(queryParamKey)
50+
}
51+
const paramsString = currentParams.toString() ? `?${currentParams.toString()}` : ''
52+
let newUrl = `${asPathRoot}${paramsString}`
53+
if (asPathRoot !== '/' && router.locale) {
54+
newUrl = `${router.locale}${asPathRoot}${paramsString}`
55+
}
56+
if (!newUrl.startsWith('/')) {
57+
newUrl = `/${newUrl}`
7758
}
7859

79-
const newSearch = params.toString()
80-
const newUrl = newSearch ? `${pathname}?${newSearch}${hash}` : `${pathname}${hash}`
60+
router.replace(newUrl, undefined, { shallow: true, locale: router.locale, scroll: false })
8161

82-
window.history.replaceState({}, '', newUrl)
8362
setQueryParamState(newValue)
8463
}
8564

8665
return {
8766
debug,
8867
queryParam: queryParam as any, // Type will be set based on overloads
89-
setQueryParam: setQueryParam as any,
68+
setQueryParam,
9069
}
9170
}

src/frame/components/page-header/Header.tsx

+12
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,18 @@ export const Header = () => {
7373
return () => window.removeEventListener('keydown', close)
7474
}, [])
7575

76+
// Listen for '/' so we can open the search overlay when pressed. (only enabled for showNewSearch is true for new search experience)
77+
useEffect(() => {
78+
const open = (e: KeyboardEvent) => {
79+
if (e.key === '/' && showNewSearch && !isSearchOpen) {
80+
e.preventDefault()
81+
setIsSearchOpen(true)
82+
}
83+
}
84+
window.addEventListener('keydown', open)
85+
return () => window.removeEventListener('keydown', open)
86+
}, [isSearchOpen, showNewSearch])
87+
7688
// For the UI in smaller browser widths, and focus the picker menu button when the search
7789
// input is closed.
7890
useEffect(() => {

src/redirects/middleware/handle-redirects.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,12 @@ export default function handleRedirects(req: ExtendedRequest, res: Response, nex
2626
if (req.path === '/') {
2727
const language = getLanguage(req)
2828
languageCacheControl(res)
29-
return res.redirect(302, `/${language}`)
29+
// Forward query params to the new URL
30+
let queryParams = new URLSearchParams((req?.query as any) || '').toString()
31+
if (queryParams) {
32+
queryParams = `?${queryParams}`
33+
}
34+
return res.redirect(302, `/${language}${queryParams}`)
3035
}
3136

3237
// begin redirect handling

src/search/components/helpers/ai-search-links-json.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ type LinksJSON = Array<{
1010
//
1111
// We include the JSON string in our analytics events so we can see the
1212
// most popular sourced references, among other things.
13-
export function generateAiSearchLinksJson(
13+
export function generateAISearchLinksJson(
1414
sourcesBuffer: Array<{ url: string }>,
1515
aiResponse: string,
1616
): string {

0 commit comments

Comments
 (0)