mirror of
https://github.com/sourcegraph/sourcegraph.git
synced 2026-02-06 15:31:48 +00:00
Search: surface pattern type in query input (#63326)
We plan to remove the 'Keyword Search' toggle as part of bringing the feature to GA. Once the toggle is removed, the search UX will only represent `keyword` (the default pattern type) and `regexp` (through the regex toggle), with no visual indication for other pattern types. So if a user clicks on a link using `patterntype:standard`, the search will just behave differently, without any indication in the UX as to why. This PR surfaces the `patterntype` filter in the search bar whenever it's not `keyword` or `regexp`. That way, users can see an old pattern type is being used and understand why the search behavior may be different. Relates to SPLF-68
This commit is contained in:
parent
15ea951e0d
commit
5630eef9e9
@ -307,7 +307,7 @@ describe('Search aggregation', () => {
|
||||
const origQuery = 'context:global insights('
|
||||
|
||||
await driver.page.goto(
|
||||
`${driver.sourcegraphBaseUrl}/search?q=${encodeURIComponent(origQuery)}&patternType=literal`
|
||||
`${driver.sourcegraphBaseUrl}/search?q=${encodeURIComponent(origQuery)}&patternType=keyword`
|
||||
)
|
||||
|
||||
await driver.page.evaluate(() => {
|
||||
|
||||
@ -138,29 +138,6 @@ describe('search/index', () => {
|
||||
searchMode: SearchMode.Precise,
|
||||
})
|
||||
})
|
||||
|
||||
test('parseSearchURL preserves literal search compatibility', () => {
|
||||
expect(parseSearchURL('q=/a literal pattern/&patternType=literal')).toStrictEqual({
|
||||
query: 'content:"/a literal pattern/"',
|
||||
patternType: SearchPatternType.standard,
|
||||
caseSensitive: false,
|
||||
searchMode: SearchMode.Precise,
|
||||
})
|
||||
|
||||
expect(parseSearchURL('q=not /a literal pattern/&patternType=literal')).toStrictEqual({
|
||||
query: 'not content:"/a literal pattern/"',
|
||||
patternType: SearchPatternType.standard,
|
||||
caseSensitive: false,
|
||||
searchMode: SearchMode.Precise,
|
||||
})
|
||||
|
||||
expect(parseSearchURL('q=un.*touched&patternType=literal')).toStrictEqual({
|
||||
query: 'un.*touched',
|
||||
patternType: SearchPatternType.standard,
|
||||
caseSensitive: false,
|
||||
searchMode: SearchMode.Precise,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('repoFilterForRepoRevision escapes values with spaces', () => {
|
||||
|
||||
@ -7,10 +7,7 @@ import { memoizeObservable } from '@sourcegraph/common'
|
||||
import { SearchPatternType } from '@sourcegraph/shared/src/graphql-operations'
|
||||
import { SearchMode } from '@sourcegraph/shared/src/search'
|
||||
import { discreteValueAliases, escapeSpaces } from '@sourcegraph/shared/src/search/query/filters'
|
||||
import { stringHuman } from '@sourcegraph/shared/src/search/query/printer'
|
||||
import { findFilter, FilterKind, getGlobalSearchContextFilter } from '@sourcegraph/shared/src/search/query/query'
|
||||
import { scanSearchQuery } from '@sourcegraph/shared/src/search/query/scanner'
|
||||
import { createLiteral } from '@sourcegraph/shared/src/search/query/token'
|
||||
import { omitFilter } from '@sourcegraph/shared/src/search/query/transformer'
|
||||
import type { AggregateStreamingSearchResults, StreamSearchOptions } from '@sourcegraph/shared/src/search/stream'
|
||||
|
||||
@ -100,25 +97,18 @@ export function parseSearchURL(
|
||||
urlSearchQuery: string,
|
||||
{ appendCaseFilter = false }: { appendCaseFilter?: boolean } = {}
|
||||
): ParsedSearchURL {
|
||||
let queryInput = parseSearchURLQuery(urlSearchQuery) || ''
|
||||
let patternTypeInput = parseSearchURLPatternType(urlSearchQuery)
|
||||
let query = parseSearchURLQuery(urlSearchQuery) || ''
|
||||
let patternType = parseSearchURLPatternType(urlSearchQuery)
|
||||
let caseSensitive = searchURLIsCaseSensitive(urlSearchQuery)
|
||||
const searchMode = parseSearchURLSearchMode(urlSearchQuery)
|
||||
|
||||
const globalPatternType = findFilter(queryInput, 'patterntype', FilterKind.Global)
|
||||
const globalPatternType = findFilter(query, 'patterntype', FilterKind.Global)
|
||||
if (globalPatternType?.value && globalPatternType.value.type === 'literal') {
|
||||
// Any `patterntype:` filter in the query should override the patternType= URL query parameter if it exists.
|
||||
queryInput = omitFilter(queryInput, globalPatternType)
|
||||
patternTypeInput = globalPatternType.value.value as SearchPatternType
|
||||
query = omitFilter(query, globalPatternType)
|
||||
patternType = globalPatternType.value.value as SearchPatternType
|
||||
}
|
||||
|
||||
let query = queryInput
|
||||
const { queryInput: newQuery, patternTypeInput: patternType } = literalSearchCompatibility({
|
||||
queryInput,
|
||||
patternTypeInput,
|
||||
})
|
||||
query = newQuery
|
||||
|
||||
const globalCase = findFilter(query, 'case', FilterKind.Global)
|
||||
if (globalCase?.value && globalCase.value.type === 'literal') {
|
||||
// Any `case:` filter in the query should override the case= URL query parameter if it exists.
|
||||
@ -166,45 +156,6 @@ export function quoteIfNeeded(string: string): string {
|
||||
return string
|
||||
}
|
||||
|
||||
interface QueryCompatibility {
|
||||
queryInput: string
|
||||
patternTypeInput?: SearchPatternType
|
||||
}
|
||||
|
||||
export function literalSearchCompatibility({ queryInput, patternTypeInput }: QueryCompatibility): QueryCompatibility {
|
||||
if (patternTypeInput === undefined || patternTypeInput !== SearchPatternType.literal) {
|
||||
return { queryInput, patternTypeInput }
|
||||
}
|
||||
const tokens = scanSearchQuery(queryInput, false, SearchPatternType.standard)
|
||||
if (tokens.type === 'error') {
|
||||
return { queryInput, patternTypeInput }
|
||||
}
|
||||
|
||||
if (!tokens.term.find(token => token.type === 'pattern' && token.delimited)) {
|
||||
// If no /.../ pattern exists in this literal search, just return the query as-is.
|
||||
return { queryInput, patternTypeInput: SearchPatternType.standard }
|
||||
}
|
||||
|
||||
const newQueryInput = stringHuman(
|
||||
tokens.term.map(token =>
|
||||
token.type === 'pattern' && token.delimited
|
||||
? {
|
||||
type: 'filter',
|
||||
range: { start: 0, end: 0 },
|
||||
field: createLiteral('content', { start: 0, end: 0 }, false),
|
||||
value: createLiteral(`/${token.value}/`, { start: 0, end: 0 }, true),
|
||||
negated: false /** if `NOT` was used on this pattern, it's already preserved */,
|
||||
}
|
||||
: token
|
||||
)
|
||||
)
|
||||
|
||||
return {
|
||||
queryInput: newQueryInput,
|
||||
patternTypeInput: SearchPatternType.standard,
|
||||
}
|
||||
}
|
||||
|
||||
export interface SearchStreamingProps {
|
||||
streamSearch: (
|
||||
queryObservable: Observable<string>,
|
||||
|
||||
@ -95,6 +95,16 @@ describe('navbar query state', () => {
|
||||
|
||||
expect(useNavbarQueryState.getState().searchPatternType).toBe(SearchPatternType.keyword)
|
||||
})
|
||||
|
||||
it('should add patterntype to query if not keyword or regexp', () => {
|
||||
setQueryStateFromURL(parseSearchURL('q=hello!&patternType=keyword'))
|
||||
expect(useNavbarQueryState.getState().queryState.query).toBe('hello!')
|
||||
expect(useNavbarQueryState.getState().searchPatternType).toBe(SearchPatternType.keyword)
|
||||
|
||||
setQueryStateFromURL(parseSearchURL('q=hello!!&patternType=standard'))
|
||||
expect(useNavbarQueryState.getState().queryState.query).toBe('hello!! patterntype:standard')
|
||||
expect(useNavbarQueryState.getState().searchPatternType).toBe(SearchPatternType.standard)
|
||||
})
|
||||
})
|
||||
|
||||
describe('state initialization precedence', () => {
|
||||
|
||||
@ -8,11 +8,12 @@ import create from 'zustand'
|
||||
import {
|
||||
type BuildSearchQueryURLParameters,
|
||||
canSubmitSearch,
|
||||
type SearchQueryState,
|
||||
updateQuery,
|
||||
InitialParametersSource,
|
||||
SearchMode,
|
||||
type SearchQueryState,
|
||||
updateQuery,
|
||||
} from '@sourcegraph/shared/src/search'
|
||||
import { FilterType } from '@sourcegraph/shared/src/search/query/filters'
|
||||
import type { Settings, SettingsCascadeOrError } from '@sourcegraph/shared/src/settings/settings'
|
||||
import { buildSearchURLQuery } from '@sourcegraph/shared/src/util/url'
|
||||
|
||||
@ -27,6 +28,11 @@ import {
|
||||
|
||||
export interface NavbarQueryState extends SearchQueryState {}
|
||||
|
||||
const explicitPatternTypes = new Set([
|
||||
SearchPatternType.keyword,
|
||||
SearchPatternType.regexp,
|
||||
SearchPatternType.structural,
|
||||
])
|
||||
export const useNavbarQueryState = create<NavbarQueryState>((set, get) => ({
|
||||
parametersSource: InitialParametersSource.DEFAULT,
|
||||
queryState: { query: '' },
|
||||
@ -68,7 +74,10 @@ export const useNavbarQueryState = create<NavbarQueryState>((set, get) => ({
|
||||
}))
|
||||
|
||||
export function setSearchPatternType(searchPatternType: SearchPatternType): void {
|
||||
useNavbarQueryState.setState({ searchPatternType })
|
||||
// When changing the patterntype, we also need to reset the query to strip out any potential patterntype: filter
|
||||
const state = useNavbarQueryState.getState()
|
||||
const query = state.searchQueryFromURL ?? state.queryState.query
|
||||
useNavbarQueryState.setState({ searchPatternType, queryState: { query } })
|
||||
}
|
||||
|
||||
export function setSearchCaseSensitivity(searchCaseSensitivity: boolean): void {
|
||||
@ -108,8 +117,15 @@ export function setQueryStateFromURL(parsedSearchURL: ParsedSearchURL, query = p
|
||||
// Only update flags if the URL contains a search query.
|
||||
newState.parametersSource = InitialParametersSource.URL
|
||||
newState.searchCaseSensitivity = parsedSearchURL.caseSensitive
|
||||
if (parsedSearchURL.patternType !== undefined) {
|
||||
newState.searchPatternType = parsedSearchURL.patternType
|
||||
|
||||
const parsedPatternType = parsedSearchURL.patternType
|
||||
if (parsedPatternType !== undefined) {
|
||||
newState.searchPatternType = parsedPatternType
|
||||
// Only keyword, regexp, and structural are represented in the UI. For other pattern types, we make
|
||||
// sure to surface them in the query input itself.
|
||||
if (!explicitPatternTypes.has(parsedPatternType)) {
|
||||
query = `${query} ${FilterType.patterntype}:${parsedPatternType}`
|
||||
}
|
||||
}
|
||||
newState.queryState = { query }
|
||||
newState.searchQueryFromURL = parsedSearchURL.query
|
||||
|
||||
Loading…
Reference in New Issue
Block a user