diff --git a/frontend/src/component/common/Search/Search.tsx b/frontend/src/component/common/Search/Search.tsx index 4be705d35c36..85b5a3b380db 100644 --- a/frontend/src/component/common/Search/Search.tsx +++ b/frontend/src/component/common/Search/Search.tsx @@ -79,7 +79,7 @@ export const Search = ({ debounceTime = 200, }: ISearchProps) => { const searchInputRef = useRef(null); - const suggestionsRef = useRef(null); + const searchContainerRef = useRef(null); const [showSuggestions, setShowSuggestions] = useState(false); const hideSuggestions = () => { setShowSuggestions(false); @@ -112,10 +112,11 @@ export const Search = ({ }); const placeholder = `${customPlaceholder ?? 'Search'} (${hotkey})`; - useOnClickOutside([searchInputRef, suggestionsRef], hideSuggestions); + useOnClickOutside([searchContainerRef], hideSuggestions); return ( @@ -148,7 +149,8 @@ export const Search = ({ { + onClick={e => { + e.stopPropagation(); // prevent outside click from the lazily added element onSearchChange(''); searchInputRef.current?.focus(); }} @@ -164,11 +166,13 @@ export const Search = ({ - - + { + onSearchChange(suggestion); + searchInputRef.current?.focus(); + }} + getSearchContext={getSearchContext!} + /> } /> diff --git a/frontend/src/component/common/Search/SearchSuggestions/SearchDescription/SearchDescription.tsx b/frontend/src/component/common/Search/SearchSuggestions/SearchDescription/SearchDescription.tsx index e2072b263b47..e25daf1d66aa 100644 --- a/frontend/src/component/common/Search/SearchSuggestions/SearchDescription/SearchDescription.tsx +++ b/frontend/src/component/common/Search/SearchSuggestions/SearchDescription/SearchDescription.tsx @@ -12,10 +12,7 @@ const StyledHeader = styled('span')(({ theme }) => ({ })); const StyledCode = styled('span')(({ theme }) => ({ - backgroundColor: theme.palette.background.elevation2, color: theme.palette.text.primary, - padding: theme.spacing(0, 0.5), - borderRadius: theme.spacing(0.5), })); interface ISearchDescriptionProps { diff --git a/frontend/src/component/common/Search/SearchSuggestions/SearchInstructions/SearchInstructions.tsx b/frontend/src/component/common/Search/SearchSuggestions/SearchInstructions/SearchInstructions.tsx index 0a977c3c3a30..651320843942 100644 --- a/frontend/src/component/common/Search/SearchSuggestions/SearchInstructions/SearchInstructions.tsx +++ b/frontend/src/component/common/Search/SearchSuggestions/SearchInstructions/SearchInstructions.tsx @@ -12,6 +12,7 @@ const StyledCode = styled('span')(({ theme }) => ({ color: theme.palette.text.primary, padding: theme.spacing(0.2, 1), borderRadius: theme.spacing(0.5), + cursor: 'pointer', })); const StyledFilterHint = styled('p')(({ theme }) => ({ @@ -21,11 +22,18 @@ const StyledFilterHint = styled('p')(({ theme }) => ({ interface ISearchInstructionsProps { filters: any[]; searchableColumnsString: string; + onClick: (instruction: string) => void; } +const firstFilterOption = (filter: { name: string; options: string[] }) => + `${filter.name}:${filter.options[0]}`; +const secondFilterOption = (filter: { name: string; options: string[] }) => + `${filter.name}:${filter.options.slice(0, 2).join(',')}`; + export const SearchInstructions: VFC = ({ filters, searchableColumnsString, + onClick, }) => { return ( <> @@ -41,17 +49,22 @@ export const SearchInstructions: VFC = ({ {filters.map(filter => ( {filter.header}:{' '} - - {filter.name}:{filter.options[0]} + onClick(firstFilterOption(filter))} + > + {firstFilterOption(filter)} 1} show={ <> {' or '} - - {filter.name}: - {filter.options.slice(0, 2).join(',')} + { + onClick(secondFilterOption(filter)); + }} + > + {secondFilterOption(filter)} } diff --git a/frontend/src/component/common/Search/SearchSuggestions/SearchSuggestions.test.tsx b/frontend/src/component/common/Search/SearchSuggestions/SearchSuggestions.test.tsx index 353176b09d10..ac10ca6b7653 100644 --- a/frontend/src/component/common/Search/SearchSuggestions/SearchSuggestions.test.tsx +++ b/frontend/src/component/common/Search/SearchSuggestions/SearchSuggestions.test.tsx @@ -34,7 +34,15 @@ const searchContext = { }; test('displays search and filter instructions when no search value is provided', () => { - render( searchContext} />); + let recordedSuggestion = ''; + render( + { + recordedSuggestion = suggestion; + }} + getSearchContext={() => searchContext} + /> + ); expect(screen.getByText(/Filter your results by:/i)).toBeInTheDocument(); @@ -47,11 +55,15 @@ test('displays search and filter instructions when no search value is provided', expect( screen.getByText(/Combine filters and search./i) ).toBeInTheDocument(); + + screen.getByText(/environment:"dev env",pre-prod/i).click(); + expect(recordedSuggestion).toBe('environment:"dev env",pre-prod'); }); test('displays search and filter instructions when search value is provided', () => { render( {}} getSearchContext={() => ({ ...searchContext, searchValue: 'Title', @@ -67,8 +79,12 @@ test('displays search and filter instructions when search value is provided', () }); test('displays search and filter instructions when filter value is provided', () => { + let recordedSuggestion = ''; render( { + recordedSuggestion = suggestion; + }} getSearchContext={() => ({ ...searchContext, searchValue: 'environment:prod', @@ -84,4 +100,9 @@ test('displays search and filter instructions when filter value is provided', () expect( screen.getByText(/Combine filters and search./i) ).toBeInTheDocument(); + expect(screen.getByText(/environment:"dev env"/i)).toBeInTheDocument(); + expect(screen.getByText(/Title A/i)).toBeInTheDocument(); + + screen.getByText(/Title A/i).click(); + expect(recordedSuggestion).toBe('environment:"dev env" Title A'); }); diff --git a/frontend/src/component/common/Search/SearchSuggestions/SearchSuggestions.tsx b/frontend/src/component/common/Search/SearchSuggestions/SearchSuggestions.tsx index ba249f066d11..653887c7846c 100644 --- a/frontend/src/component/common/Search/SearchSuggestions/SearchSuggestions.tsx +++ b/frontend/src/component/common/Search/SearchSuggestions/SearchSuggestions.tsx @@ -45,10 +45,12 @@ const StyledCode = styled('span')(({ theme }) => ({ color: theme.palette.text.primary, padding: theme.spacing(0.2, 0.5), borderRadius: theme.spacing(0.5), + cursor: 'pointer', })); interface SearchSuggestionsProps { getSearchContext: () => IGetSearchContextOutput; + onSuggestion: (suggestion: string) => void; } const quote = (item: string) => (item.includes(' ') ? `"${item}"` : item); @@ -57,14 +59,10 @@ const randomIndex = (arr: any[]) => Math.floor(Math.random() * arr.length); export const SearchSuggestions: VFC = ({ getSearchContext, + onSuggestion, }) => { const searchContext = getSearchContext(); - const randomRow = useMemo( - () => randomIndex(searchContext.data), - [searchContext.data] - ); - const filters = getFilterableColumns(searchContext.columns) .map(column => { const filterOptions = searchContext.data.map(row => @@ -82,8 +80,7 @@ export const SearchSuggestions: VFC = ({ name: column.filterName, header: column.Header ?? column.filterName, options, - suggestedOption: - options[randomRow] ?? `example-${column.filterName}`, + suggestedOption: options[0] ?? `example-${column.filterName}`, values: getFilterValues( column.filterName, searchContext.searchValue @@ -102,12 +99,13 @@ export const SearchSuggestions: VFC = ({ const suggestedTextSearch = searchContext.data.length && searchableColumns.length - ? getColumnValues( - searchableColumns[0], - searchContext.data[randomRow] - ) + ? getColumnValues(searchableColumns[0], searchContext.data[0]) : 'example-search-text'; + const selectedFilter = filters.map( + filter => `${filter.name}:${filter.suggestedOption}` + )[0]; + return ( @@ -130,6 +128,7 @@ export const SearchSuggestions: VFC = ({ searchableColumnsString={ searchableColumnsString } + onClick={onSuggestion} /> } /> @@ -141,12 +140,12 @@ export const SearchSuggestions: VFC = ({ condition={filters.length > 0} show="Combine filters and search: " /> - - {filters.map(filter => ( - - {filter.name}:{filter.suggestedOption}{' '} - - ))} + + onSuggestion(selectedFilter + ' ' + suggestedTextSearch) + } + > + {selectedFilter}{' '} {suggestedTextSearch}