Search filters: update empty state for dynamic filter searches (#60239)

This commit is contained in:
Camden Cheek 2024-02-08 09:23:18 -07:00 committed by GitHub
parent c470599dce
commit 3e9459c3de
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 92 additions and 11 deletions

View File

@ -118,6 +118,10 @@ export const NewSearchFilters: FC<NewSearchFiltersProps> = ({
telemetryService.log('SearchFiltersApplyFiltersClick')
}
const onAddFilterToQuery = (filter: string): void => {
onQueryChange(`${query} ${filter}`)
}
const handleKeyDown = useCallback(
(e: KeyboardEvent) => {
if (e.altKey && e.key === 'Backspace') {
@ -173,6 +177,7 @@ export const NewSearchFilters: FC<NewSearchFiltersProps> = ({
selectedFilters={selectedFilters}
renderItem={repoFilter}
onSelectedFilterChange={handleFilterChange}
onAddFilterToQuery={onAddFilterToQuery}
/>
<SearchDynamicFilter
@ -182,6 +187,7 @@ export const NewSearchFilters: FC<NewSearchFiltersProps> = ({
selectedFilters={selectedFilters}
renderItem={languageFilter}
onSelectedFilterChange={handleFilterChange}
onAddFilterToQuery={onAddFilterToQuery}
/>
<SearchDynamicFilter
@ -191,6 +197,7 @@ export const NewSearchFilters: FC<NewSearchFiltersProps> = ({
selectedFilters={selectedFilters}
renderItem={symbolFilter}
onSelectedFilterChange={handleFilterChange}
onAddFilterToQuery={onAddFilterToQuery}
/>
<SearchDynamicFilter
@ -200,6 +207,7 @@ export const NewSearchFilters: FC<NewSearchFiltersProps> = ({
selectedFilters={selectedFilters}
renderItem={authorFilter}
onSelectedFilterChange={handleFilterChange}
onAddFilterToQuery={onAddFilterToQuery}
/>
<SearchDynamicFilter
@ -209,6 +217,7 @@ export const NewSearchFilters: FC<NewSearchFiltersProps> = ({
selectedFilters={selectedFilters}
renderItem={commitDateFilter}
onSelectedFilterChange={handleFilterChange}
onAddFilterToQuery={onAddFilterToQuery}
/>
<SearchDynamicFilter
@ -217,6 +226,7 @@ export const NewSearchFilters: FC<NewSearchFiltersProps> = ({
filters={filters}
selectedFilters={selectedFilters}
onSelectedFilterChange={handleFilterChange}
onAddFilterToQuery={onAddFilterToQuery}
/>
<SearchDynamicFilter
@ -226,6 +236,7 @@ export const NewSearchFilters: FC<NewSearchFiltersProps> = ({
selectedFilters={selectedFilters}
renderItem={utilityFilter}
onSelectedFilterChange={handleFilterChange}
onAddFilterToQuery={onAddFilterToQuery}
/>
<SyntheticCountFilter
@ -339,6 +350,7 @@ const SyntheticCountFilter: FC<SyntheticCountFilterProps> = props => {
selectedFilters={selectedCountFilter}
renderItem={commitDateFilter}
onSelectedFilterChange={handleCountAllFilter}
onAddFilterToQuery={() => {}}
/>
)
}

View File

@ -67,18 +67,37 @@
}
.description {
&-header {
font-weight: 600;
margin-bottom: 0.5rem;
}
// margin: 0 0.65rem -0.25rem 0.65rem;
background-color: var(--secondary);
background-color: var(--secondary-2);
color: var(--text-muted);
padding: 0.75rem 1rem;
border-radius: 0.5rem;
}
.zero-state-search-button {
text-decoration: underline;
.zero-state-query-button {
display: inline;
padding: 0;
font-size: 0.75rem;
padding: 0 0.125rem;
border-radius: 3px;
background-color: var(--primary-4);
font-size: inherit;
text-align: unset;
color: var(--text-muted);
font-weight: normal;
margin: 0;
border: none;
vertical-align: unset;
}
.zero-state-search-button {
text-decoration: underline;
display: inline;
padding: 0;
font-size: inherit;
text-align: unset;
color: var(--text-muted);
font-weight: normal;

View File

@ -11,6 +11,7 @@ import { SymbolKind } from '@sourcegraph/shared/src/symbols/SymbolKind'
import { Button, Icon, H2, H4, Input, LanguageIcon, Code, Tooltip } from '@sourcegraph/wildcard'
import { codeHostIcon } from '../../../../components'
import { SyntaxHighlightedSearchQuery } from '../../../../components/SyntaxHighlightedSearchQuery'
import { URLQueryFilter } from '../../hooks'
import { DynamicFilterBadge } from '../DynamicFilterBadge'
@ -48,6 +49,8 @@ interface SearchDynamicFilterProps {
* @param nextQuery
*/
onSelectedFilterChange: (filterKind: Filter['kind'], filters: URLQueryFilter[]) => void
onAddFilterToQuery: (newFilter: string) => void
}
/**
@ -61,6 +64,7 @@ export const SearchDynamicFilter: FC<SearchDynamicFilterProps> = ({
selectedFilters,
renderItem,
onSelectedFilterChange,
onAddFilterToQuery,
}) => {
const inputRef = useRef<HTMLInputElement>(null)
@ -109,6 +113,12 @@ export const SearchDynamicFilter: FC<SearchDynamicFilterProps> = ({
? filteredFilters.slice(0, MAX_FILTERS_NUMBER)
: filteredFilters.slice(0, DEFAULT_FILTERS_NUMBER)
// HACK(camdencheek): we limit the number of filters of each type to 1000, so if we get
// exactly 1000 filters, assume that we hit that limit. Ideally, we wouldn't hard-code this
// and the backend would tell us whether we hit that limit.
const limitHit = filters?.some(filter => !filter.exhaustive) || filters?.length === 1000
const suggestedQueryFilter = filterForSearchTerm(searchTerm, filterKind)
return (
<div className={styles.root}>
{title && (
@ -139,12 +149,31 @@ export const SearchDynamicFilter: FC<SearchDynamicFilterProps> = ({
{filtersToShow.length === 0 && (
<small className={styles.description}>
<b>We couldnt return a {filterKind} that matched your filter input.</b> Try a more expansive
search{' '}
<Button onClick={handleZeroStateButtonClick} className={styles.zeroStateSearchButton}>
using the search bar
</Button>{' '}
above.
<div className={styles.descriptionHeader}>No matches in search results.</div>
{limitHit && suggestedQueryFilter ? (
<>
Try adding{' '}
<Button
onClick={() => onAddFilterToQuery(suggestedQueryFilter)}
className={styles.zeroStateQueryButton}
>
<SyntaxHighlightedSearchQuery query={suggestedQueryFilter} />
</Button>{' '}
to your original search query to narrow results to that repo.
</>
) : (
<>
Try expanding your search using the{' '}
<Button
variant="link"
onClick={handleZeroStateButtonClick}
className={styles.zeroStateSearchButton}
>
search bar
</Button>{' '}
above.
</>
)}
</small>
)}
</ul>
@ -191,6 +220,27 @@ const DynamicFilterItem: FC<DynamicFilterItemProps> = props => {
)
}
function filterForSearchTerm(input: string, filterKind: Filter['kind']): string | null {
switch (filterKind) {
case 'repo': {
return `repo:${maybeQuoteString(input)}`
}
case 'author': {
return `author:${maybeQuoteString(input)}`
}
default: {
return null
}
}
}
function maybeQuoteString(input: string): string {
if (input.match(/\s/)) {
return `"${input.replaceAll('"', '\\"')}"`
}
return input
}
function filtersEqual(a: URLQueryFilter, b: URLQueryFilter): boolean {
return a.kind === b.kind && a.label === b.label && a.value === b.value
}