Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion src/events/components/experiments/experiment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ export function shouldShowExperiment(
? routerQuery.feature.toLowerCase() === experiment.turnOnWithURLParam.toLowerCase()
: false
) {
controlGroupOverride[experimentKey] = TREATMENT_VARIATION
return true
}
}
Expand Down Expand Up @@ -115,6 +114,15 @@ export function getExperimentVariationForContext(locale: string, version: string
const experiments = getActiveExperiments(locale, version)
for (const experiment of experiments) {
if (experiment.includeVariationInContext) {
// If the user is using the URL param to view the experiment, include the variation in the context
if (
experiment.turnOnWithURLParam &&
window.location?.search
?.toLowerCase()
.includes(`feature=${experiment.turnOnWithURLParam.toLowerCase()}`)
) {
return TREATMENT_VARIATION
}
return getExperimentControlGroupFromSession(
experiment.key,
experiment.percentOfUsersToGetExperiment,
Expand Down
14 changes: 13 additions & 1 deletion src/frame/middleware/handle-next-data-path.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import type { Response, NextFunction } from 'express'
import statsd from '@/observability/lib/statsd.js'

import type { ExtendedRequest } from '@/types.js'

const STATSD_KEY = 'middleware.handle_next_data_path'

export default function handleNextDataPath(
req: ExtendedRequest,
res: Response,
Expand All @@ -12,7 +15,16 @@ export default function handleNextDataPath(
// this is triggered via client-side route transitions
// example path:
// /_next/data/development/en/free-pro-team%40latest/github/setting-up-and-managing-your-github-user-account.json
const decodedPath = decodeURIComponent(req.path)
let decodedPath = ''
try {
decodedPath = decodeURIComponent(req.path)
} catch {
res.status(400).send(`Bad request`)
const tags = ['response:400', `path:${req.path}`]
statsd.increment(STATSD_KEY, 1, tags)
return
}

const parts = decodedPath.split('/').slice(4)
// free-pro-team@latest should not be included in the page path
if (parts[1] === 'free-pro-team@latest') {
Expand Down
1 change: 1 addition & 0 deletions src/search/components/input/AskAIResults.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,7 @@ export function AskAIResults({
<ActionList.Item
sx={{
marginLeft: '0px',
paddingLeft: '0px',
}}
key={`reference-${index}`}
tabIndex={-1}
Expand Down
4 changes: 2 additions & 2 deletions src/search/components/input/SearchBarButton.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,8 @@
.searchIconContainer svg {
overflow: visible !important;

width: 16;
height: 16;
width: 16px;
height: 16px;

fill: currentColor;
color: var(--fgColor-muted, var(--color-fg-muted, #656d76));
Expand Down
10 changes: 8 additions & 2 deletions src/search/components/input/SearchBarButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,17 @@ export function SearchBarButton({ isSearchOpen, setIsSearchOpen }: Props) {
aria-hidden
tabIndex={-1}
>
<CopilotIcon aria-hidden className="mr-1" />
<span
className={cx(styles.queryText, !urlSearchInputQuery ? styles.placeholder : null)}
>
{urlSearchInputQuery ? urlSearchInputQuery : t('search.input.placeholder')}
{urlSearchInputQuery ? (
urlSearchInputQuery
) : (
<>
{t('search.input.placeholder')}
<CopilotIcon aria-hidden className="mr-1 ml-1" />
</>
)}
</span>
</div>
<span className={styles.searchIconContainer} aria-hidden tabIndex={-1}>
Expand Down
46 changes: 26 additions & 20 deletions src/search/components/input/SearchOverlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -240,13 +240,10 @@ export function SearchOverlay({
event.preventDefault()
const newQuery = event.target.value
setSelectedIndex(-1) // Reset selected index when query changes
// We don't need to fetch autocomplete results when asking the AI
if (!isAskAIState || aiSearchError || aiCouldNotAnswer) {
setSearchLoading(true)
updateAutocompleteResults(newQuery)
}
// If the query empties while we are in the AI state, we should switch back to the search state
if (isAskAIState && newQuery.trim() === '') {
// Whenever the query changes, we want to leave the Ask AI state
setSearchLoading(true)
updateAutocompleteResults(newQuery)
if (isAskAIState) {
updateParams({
'search-overlay-ask-ai': '',
'search-overlay-input': newQuery,
Expand Down Expand Up @@ -296,6 +293,7 @@ export function SearchOverlay({
'search-overlay-ask-ai': 'true',
'search-overlay-input': selectedOption.term,
})
setSearchLoading(true)
setAIQuery(selectedOption.term)
inputRef.current?.focus()
}
Expand Down Expand Up @@ -464,7 +462,6 @@ export function SearchOverlay({
<ActionList
aria-label={t('search.overlay.suggestions_list_aria_label')}
showDividers
selectionVariant="single"
className={styles.suggestionsList}
ref={suggestionsListHeightRef}
>
Expand Down Expand Up @@ -526,22 +523,12 @@ export function SearchOverlay({
selectedIndex,
listElementsRef,
askAIState,
searchLoading,
previousSuggestionsListHeight,
)}
</ActionList>
</>
)
} else if (searchLoading) {
OverlayContents = (
<Box
role="status"
className={styles.loadingContainer}
sx={{
height: `${previousSuggestionsListHeight}px`,
}}
>
<Spinner />
</Box>
)
} else {
OverlayContents = (
<ActionList
Expand All @@ -560,6 +547,8 @@ export function SearchOverlay({
selectedIndex,
listElementsRef,
askAIState,
searchLoading,
previousSuggestionsListHeight,
)}
</ActionList>
)
Expand Down Expand Up @@ -701,6 +690,8 @@ function renderSearchGroups(
aiCouldNotAnswer: boolean
setAICouldNotAnswer: (value: boolean) => void
},
searchLoading: boolean,
previousSuggestionsListHeight: number | string,
) {
const groups = []

Expand Down Expand Up @@ -744,6 +735,21 @@ function renderSearchGroups(
groups.push(<ActionList.Divider key="general-divider" />)
}

if (searchLoading) {
groups.push(
<Box
role="status"
className={styles.loadingContainer}
sx={{
height: `${previousSuggestionsListHeight}px`,
}}
>
<Spinner />
</Box>,
)
return groups
}

// We want to show general search suggestions underneath the AI Response section if the AI Could no answer
if ((generalSearchOptions.length && !isInAskAIState) || isInAskAIStateButNoAnswer) {
const items = []
Expand Down
2 changes: 1 addition & 1 deletion src/search/components/input/variables.scss
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Widths of the search bar button at different breakpoints
$smHeaderSearchInputWidth: 100%; // Technically we don't show the search bar at this breakpoint
$mdHeaderSearchInputWidth: 100%; // Technically we don't show the search bar at this breakpoint
$lgHeaderSearchInputWidth: 30rem;
$lgHeaderSearchInputWidth: 25rem;
$xlHeaderSearchInputWidth: 40rem;

// Widths of the search overlay popup at different breakpoints
Expand Down
4 changes: 2 additions & 2 deletions src/shielding/middleware/handle-invalid-nextjs-paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ export default function handleInvalidNextPaths(
// In local dev, we don't get these penetration-testing looking requests.
if (
process.env.NODE_ENV !== 'development' &&
req.path.startsWith('/_next/') &&
!req.path.startsWith('/_next/data')
((req.path.startsWith('/_next/') && !req.path.startsWith('/_next/data')) ||
req.query?.['__nextFallback'])
) {
defaultCacheControl(res)

Expand Down
1 change: 1 addition & 0 deletions src/shielding/middleware/handle-invalid-query-strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export const MAX_UNFAMILIAR_KEYS_REDIRECT = 3
const RECOGNIZED_KEYS_BY_PREFIX = {
'/_next/data/': ['versionId', 'productId', 'restPage', 'apiVersion', 'category', 'subcategory'],
'/api/search': ['query', 'language', 'version', 'page', 'product', 'autocomplete', 'limit'],
'/api/combined-search': ['query', 'version', 'size', 'debug'],
'/api/anchor-redirect': ['hash', 'path'],
'/api/webhooks': ['category', 'version'],
'/api/pageinfo': ['pathname'],
Expand Down