Skip to content

Commit

Permalink
Dev (#487)
Browse files Browse the repository at this point in the history
* switch multiple drug matching to use left anchored, case insensitive search

* Update citations with V5 citation (#477)

* update citations for v5

* fixed link

* prettier

* rudimentary support for querying current data_version

* implement ga4gh service service info spec

* update griffith lab affiliation

* run prettier

* fix: categories.tsv was downloading interactions (#475)

* fix: categories.tsv was downloading interactions

* feat: change top row of data downloads to latest

* feat: change top row of data downloads to latest

* feat: add support for pasting terms into the search bar (#486)

* feat: add support for pasting terms into the search bar

* feat: allow user to paste searches

* refactor: moving demo lists to use function for prettier code

* feat: add option for users to paste searches

* feat: add option for users to paste searches

* feat: persist previous search terms when a user pastes + remove any duplicate terms

* feat: update naming for delimiter options

* feat: let user paste search terms without selecting bulk search (default to csv), update warning message

* feat: add tooltip for bulk search option

* update styling

* feat: remove paste alert if user clears search terms

* feat: make tooltip arrow for better accessibility

* feat: justify bulk search option to right (below search buttons)

---------

Co-authored-by: Adam Coffman <acc@fastmail.com>
Co-authored-by: Matthew Cannon <87494086+mcannon068nw@users.noreply.github.com>
Co-authored-by: Adam Coffman <acoffman@wustl.edu>
  • Loading branch information
4 people authored Apr 4, 2024
1 parent 1f96796 commit 172701a
Showing 1 changed file with 167 additions and 26 deletions.
193 changes: 167 additions & 26 deletions client/src/components/Shared/SearchBar/SearchBar.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
import './SearchBar.scss';
import Autocomplete from '@mui/material/Autocomplete';
import {
Alert,
AlertTitle,
Box,
Button,
Checkbox,
FormControl,
FormControlLabel,
InputLabel,
MenuItem,
Select,
SelectChangeEvent,
TextField,
Tooltip,
} from '@mui/material';
import { useContext, useEffect } from 'react';
import React from 'react';
Expand All @@ -15,6 +22,13 @@ import { ActionTypes } from 'stores/Global/reducers';
import { useGetNameSuggestions } from 'hooks/queries/useGetNameSuggestions';
import { SearchTypes } from 'types/types';
import { useGetIsMobile } from 'hooks/shared/useGetIsMobile';
import HelpIcon from '@mui/icons-material/Help';

enum DelimiterTypes {
Comma = 'Comma',
CommaSpace = 'Comma With Space',
TabNewline = 'Tab or Newline',
}

type SearchBarProps = {
handleSubmit: () => void;
Expand All @@ -27,6 +41,10 @@ const SearchBar: React.FC<SearchBarProps> = ({ handleSubmit }) => {
state.interactionMode
);
const [typedSearchTerm, setTypedSearchTerm] = React.useState('');
const [pastingFromDocument, setPastingFromDocument] = React.useState(false);
const [pastedSearchDelimiter, setPastedSearchDelimiter] = React.useState('');
const [searchWasPasted, setSearchWasPasted] = React.useState(false);

const typeAheadQuery = useGetNameSuggestions(typedSearchTerm, searchType);
let autocompleteOptions = typeAheadQuery?.data?.geneSuggestions || [];
const drugAutocompleteOptions = typeAheadQuery?.data?.drugSuggestions || [];
Expand All @@ -37,9 +55,11 @@ const SearchBar: React.FC<SearchBarProps> = ({ handleSubmit }) => {

// support searching for terms that the API may not return (add user's typed term to options if it's not already there)
if (
typedSearchTerm &&
autocompleteOptions.filter(
(option: { suggestion: string }) => option.suggestion === typedSearchTerm
).length === 0
).length === 0 &&
typedSearchTerm.trim() !== ''
) {
autocompleteOptions = [
{ suggestion: typedSearchTerm },
Expand All @@ -52,6 +72,8 @@ const SearchBar: React.FC<SearchBarProps> = ({ handleSubmit }) => {
const handleAutocompleteChange = (event: any, value: any) => {
if (value.length === 0) {
setSelectedOptions([]);
// for clearing the paste warning, if applicable
setSearchWasPasted(false);
dispatch({ type: ActionTypes.DeleteAllTerms });
} else {
setSelectedOptions(value);
Expand All @@ -76,33 +98,36 @@ const SearchBar: React.FC<SearchBarProps> = ({ handleSubmit }) => {

const handleDemoClick = () => {
if (searchType === SearchTypes.Gene) {
setSelectedOptions([
{ suggestion: 'FLT1' },
{ suggestion: 'FLT2' },
{ suggestion: 'FLT3' },
{ suggestion: 'STK1' },
{ suggestion: 'MM1' },
{ suggestion: 'AQP1' },
{ suggestion: 'LOC100508755' },
{ suggestion: 'FAKE1' },
]);
const geneDemoList = [
'FLT1',
'FLT2',
'FLT3',
'STK1',
'MM1',
'AQP1',
'LOC100508755',
'FAKE1',
];
setSelectedOptions(convertToDropdownOptions(geneDemoList));
} else if (searchType === SearchTypes.Drug) {
setSelectedOptions([
{ suggestion: 'SUNITINIB' },
{ suggestion: 'ZALCITABINE' },
{ suggestion: 'TRASTUZUMAB' },
{ suggestion: 'NOTREAL' },
]);
const drugDemoList = [
'SUNITINIB',
'ZALCITABINE',
'TRASTUZUMAB',
'NOTREAL',
];
setSelectedOptions(convertToDropdownOptions(drugDemoList));
} else if (searchType === SearchTypes.Categories) {
setSelectedOptions([
{ suggestion: 'HER2' },
{ suggestion: 'ERBB2' },
{ suggestion: 'PTGDR' },
{ suggestion: 'EGFR' },
{ suggestion: 'RECK' },
{ suggestion: 'KCNMA1' },
{ suggestion: 'MM1' },
]);
const categoriesDemoList = [
'HER2',
'ERBB2',
'PTGDR',
'EGFR',
'RECK',
'KCNMA1',
'MM1',
];
setSelectedOptions(convertToDropdownOptions(categoriesDemoList));
}
};
const handleSearchClick = () => {
Expand All @@ -125,9 +150,77 @@ const SearchBar: React.FC<SearchBarProps> = ({ handleSubmit }) => {
}
}, [selectedOptions]);

const convertToDropdownOptions = (options: string[]) => {
return options.map((item: string) => {
return { suggestion: item.trim() };
});
};

const handlePaste = (event: any) => {
let pastedText = event.clipboardData.getData('text');
let pastedOptions: any[] = convertToDropdownOptions([pastedText]);

const commaSepOptions = pastedText.split(',');

if (pastedSearchDelimiter === DelimiterTypes.Comma) {
pastedOptions = convertToDropdownOptions(commaSepOptions);
} else if (pastedSearchDelimiter === DelimiterTypes.CommaSpace) {
const commaSpaceSepOptions = pastedText.split(', ');
pastedOptions = convertToDropdownOptions(commaSpaceSepOptions);
} else if (pastedSearchDelimiter === DelimiterTypes.TabNewline) {
const whitespaceRegex = /[\t\n\r\f\v]/;
const whitespaceSepOptions = pastedText.split(whitespaceRegex);
pastedOptions = convertToDropdownOptions(whitespaceSepOptions);
} else {
pastedOptions = convertToDropdownOptions(commaSepOptions);
}
setSearchWasPasted(true);
// make sure we persist the search terms already entered, combine any pre-existing search terms with the new pasted options
const newSearchOptions = selectedOptions.concat(pastedOptions);
// remove any duplicated terms (need to iterate through only the terms since objects are never equivalent in js, even if the contents are the same)
const uniqueSearchTerms = [
...new Set(newSearchOptions.map((option) => option.suggestion)),
];
setSelectedOptions(convertToDropdownOptions(uniqueSearchTerms));
// we don't want the code to also run what's in onInputChange for the Autocomplete since everything is handled here
event.preventDefault();
};

const handleCheckboxSelect = (event: any) => {
setPastingFromDocument(event.target.checked);
// reset the selected delimiter and searchWasPasted, to avoid potential weird behaviors if a user deselects the checkbox
setPastedSearchDelimiter('');
setSearchWasPasted(false);
};

const handleDelimiterChange = (event: any) => {
setPastedSearchDelimiter(event.target.value as string);
};

const pasteAlert =
searchWasPasted && pastedSearchDelimiter === '' ? (
<Box width="100%" pb={2}>
<Alert severity="info">
<AlertTitle>Verify your search terms</AlertTitle>
<p>
It looks like you pasted search terms. We have defaulted the
delimiter to comma-separated terms.
</p>
<p style={{ marginTop: '10px' }}>
If this is incorrect or you would like to use a different delimiter,
make sure to check the “Bulk search” option below and select a
delimiter from the drop down.
</p>
</Alert>
</Box>
) : (
<></>
);

return (
<>
<Box id="search-bar-container" width={isMobile ? '95%' : '75%'}>
{pasteAlert}
<Box display="flex" flexWrap={isMobile ? 'wrap' : 'nowrap'}>
<Box width={isMobile ? '100%' : 'fit-content'}>
<Select
Expand Down Expand Up @@ -155,6 +248,7 @@ const SearchBar: React.FC<SearchBarProps> = ({ handleSubmit }) => {
<Box>
<TextField
{...params}
onPaste={handlePaste}
variant="standard"
label="Search Terms"
/>
Expand Down Expand Up @@ -190,6 +284,53 @@ const SearchBar: React.FC<SearchBarProps> = ({ handleSubmit }) => {
</Button>
</Box>
</Box>
<Box
display="flex"
pt={5}
flexWrap="wrap"
height="100px"
alignContent="center"
justifyContent="end"
>
<Tooltip
title="Select this option if you are pasting terms from an external document"
arrow
>
<FormControlLabel
checked={pastingFromDocument}
onChange={handleCheckboxSelect}
control={<Checkbox />}
label="Bulk search"
/>
</Tooltip>
<Box
width={isMobile ? '100%' : '35%'}
display={pastingFromDocument ? '' : 'none'}
>
<FormControl fullWidth>
<InputLabel id="delimiter-select-label">
Select delimiter
</InputLabel>
<Select
labelId="delimiter-select-label"
id="delimiter-select"
value={pastedSearchDelimiter}
label="Select delimiter"
onChange={handleDelimiterChange}
>
<MenuItem value={DelimiterTypes.Comma}>
{DelimiterTypes.Comma}
</MenuItem>
<MenuItem value={DelimiterTypes.CommaSpace}>
{DelimiterTypes.CommaSpace}
</MenuItem>
<MenuItem value={DelimiterTypes.TabNewline}>
{DelimiterTypes.TabNewline}
</MenuItem>
</Select>
</FormControl>
</Box>
</Box>
</Box>
</>
);
Expand Down

0 comments on commit 172701a

Please sign in to comment.