Skip to content

Commit

Permalink
feat: Clickable search filter options (#4618)
Browse files Browse the repository at this point in the history
  • Loading branch information
kwasniew authored Sep 6, 2023
1 parent f55c67f commit caff040
Show file tree
Hide file tree
Showing 5 changed files with 68 additions and 34 deletions.
20 changes: 12 additions & 8 deletions frontend/src/component/common/Search/Search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export const Search = ({
debounceTime = 200,
}: ISearchProps) => {
const searchInputRef = useRef<HTMLInputElement>(null);
const suggestionsRef = useRef<HTMLInputElement>(null);
const searchContainerRef = useRef<HTMLInputElement>(null);
const [showSuggestions, setShowSuggestions] = useState(false);
const hideSuggestions = () => {
setShowSuggestions(false);
Expand Down Expand Up @@ -112,10 +112,11 @@ export const Search = ({
});
const placeholder = `${customPlaceholder ?? 'Search'} (${hotkey})`;

useOnClickOutside([searchInputRef, suggestionsRef], hideSuggestions);
useOnClickOutside([searchContainerRef], hideSuggestions);

return (
<StyledContainer
ref={searchContainerRef}
style={containerStyles}
active={expandable && showSuggestions}
>
Expand Down Expand Up @@ -148,7 +149,8 @@ export const Search = ({
<Tooltip title="Clear search query" arrow>
<IconButton
size="small"
onClick={() => {
onClick={e => {
e.stopPropagation(); // prevent outside click from the lazily added element
onSearchChange('');
searchInputRef.current?.focus();
}}
Expand All @@ -164,11 +166,13 @@ export const Search = ({
<ConditionallyRender
condition={Boolean(hasFilters) && showSuggestions}
show={
<div ref={suggestionsRef}>
<SearchSuggestions
getSearchContext={getSearchContext!}
/>
</div>
<SearchSuggestions
onSuggestion={suggestion => {
onSearchChange(suggestion);
searchInputRef.current?.focus();
}}
getSearchContext={getSearchContext!}
/>
}
/>
</StyledContainer>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 }) => ({
Expand All @@ -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<ISearchInstructionsProps> = ({
filters,
searchableColumnsString,
onClick,
}) => {
return (
<>
Expand All @@ -41,17 +49,22 @@ export const SearchInstructions: VFC<ISearchInstructionsProps> = ({
{filters.map(filter => (
<StyledFilterHint key={filter.name}>
{filter.header}:{' '}
<StyledCode>
{filter.name}:{filter.options[0]}
<StyledCode
onClick={() => onClick(firstFilterOption(filter))}
>
{firstFilterOption(filter)}
</StyledCode>
<ConditionallyRender
condition={filter.options.length > 1}
show={
<>
{' or '}
<StyledCode>
{filter.name}:
{filter.options.slice(0, 2).join(',')}
<StyledCode
onClick={() => {
onClick(secondFilterOption(filter));
}}
>
{secondFilterOption(filter)}
</StyledCode>
</>
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,15 @@ const searchContext = {
};

test('displays search and filter instructions when no search value is provided', () => {
render(<SearchSuggestions getSearchContext={() => searchContext} />);
let recordedSuggestion = '';
render(
<SearchSuggestions
onSuggestion={suggestion => {
recordedSuggestion = suggestion;
}}
getSearchContext={() => searchContext}
/>
);

expect(screen.getByText(/Filter your results by:/i)).toBeInTheDocument();

Expand All @@ -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(
<SearchSuggestions
onSuggestion={() => {}}
getSearchContext={() => ({
...searchContext,
searchValue: 'Title',
Expand All @@ -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(
<SearchSuggestions
onSuggestion={suggestion => {
recordedSuggestion = suggestion;
}}
getSearchContext={() => ({
...searchContext,
searchValue: 'environment:prod',
Expand All @@ -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');
});
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -57,14 +59,10 @@ const randomIndex = (arr: any[]) => Math.floor(Math.random() * arr.length);

export const SearchSuggestions: VFC<SearchSuggestionsProps> = ({
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 =>
Expand All @@ -82,8 +80,7 @@ export const SearchSuggestions: VFC<SearchSuggestionsProps> = ({
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
Expand All @@ -102,12 +99,13 @@ export const SearchSuggestions: VFC<SearchSuggestionsProps> = ({

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 (
<StyledPaper className="dropdown-outline">
<StyledBox>
Expand All @@ -130,6 +128,7 @@ export const SearchSuggestions: VFC<SearchSuggestionsProps> = ({
searchableColumnsString={
searchableColumnsString
}
onClick={onSuggestion}
/>
}
/>
Expand All @@ -141,12 +140,12 @@ export const SearchSuggestions: VFC<SearchSuggestionsProps> = ({
condition={filters.length > 0}
show="Combine filters and search: "
/>
<StyledCode>
{filters.map(filter => (
<span key={filter.name}>
{filter.name}:{filter.suggestedOption}{' '}
</span>
))}
<StyledCode
onClick={() =>
onSuggestion(selectedFilter + ' ' + suggestedTextSearch)
}
>
<span key={selectedFilter}>{selectedFilter}</span>{' '}
<span>{suggestedTextSearch}</span>
</StyledCode>
</Box>
Expand Down

0 comments on commit caff040

Please sign in to comment.