Skip to content

Commit

Permalink
Staging (#488)
Browse files Browse the repository at this point in the history
* Dev (#483)

* 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

---------

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>

* Automated frontend build

* Automated frontend build

* Dev (#487)

* 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>

* Automated frontend build

* Automated frontend build

---------

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>
Co-authored-by: katiestahl <katiestahl@users.noreply.github.com>
  • Loading branch information
5 people authored Apr 4, 2024
1 parent 6715371 commit 4d9303b
Show file tree
Hide file tree
Showing 6 changed files with 175 additions and 34 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
6 changes: 3 additions & 3 deletions server/public/asset-manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"files": {
"main.css": "/static/css/main.090445a8.css",
"main.js": "/static/js/main.f12ce273.js",
"main.js": "/static/js/main.c80660af.js",
"static/js/461.86214142.chunk.js": "/static/js/461.86214142.chunk.js",
"static/js/80.a852d8b5.chunk.js": "/static/js/80.a852d8b5.chunk.js",
"static/js/990.d59bee53.chunk.js": "/static/js/990.d59bee53.chunk.js",
Expand All @@ -28,7 +28,7 @@
"static/js/925.a41f4405.chunk.js": "/static/js/925.a41f4405.chunk.js",
"index.html": "/index.html",
"main.090445a8.css.map": "/static/css/main.090445a8.css.map",
"main.f12ce273.js.map": "/static/js/main.f12ce273.js.map",
"main.c80660af.js.map": "/static/js/main.c80660af.js.map",
"461.86214142.chunk.js.map": "/static/js/461.86214142.chunk.js.map",
"80.a852d8b5.chunk.js.map": "/static/js/80.a852d8b5.chunk.js.map",
"990.d59bee53.chunk.js.map": "/static/js/990.d59bee53.chunk.js.map",
Expand Down Expand Up @@ -56,6 +56,6 @@
},
"entrypoints": [
"static/css/main.090445a8.css",
"static/js/main.f12ce273.js"
"static/js/main.c80660af.js"
]
}
2 changes: 1 addition & 1 deletion server/public/index.html
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.png"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="DGIdb, The Drug Gene Interaction Database, is a research resource that can be used to search candidate genes or drugs against the known and potentially druggable genome."/><link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=Fira+Sans:wght@300;350;400;500;700;800&display=swap" rel="stylesheet"><link href="https://fonts.googleapis.com/css2?family=Lato:wght@100;300;350;400;700&display=swap" rel="stylesheet"><link href="https://fonts.googleapis.com/css2?family=Work+Sans:wght@200;300;350;400;500;600&display=swap" rel="stylesheet"><link rel="apple-touch-icon" href="/dgidb-icon_48.png"/><link rel="manifest" href="/manifest.json"/><title>DGIdb</title><script defer="defer" src="/static/js/main.f12ce273.js"></script><link href="/static/css/main.090445a8.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.png"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="DGIdb, The Drug Gene Interaction Database, is a research resource that can be used to search candidate genes or drugs against the known and potentially druggable genome."/><link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=Fira+Sans:wght@300;350;400;500;700;800&display=swap" rel="stylesheet"><link href="https://fonts.googleapis.com/css2?family=Lato:wght@100;300;350;400;700&display=swap" rel="stylesheet"><link href="https://fonts.googleapis.com/css2?family=Work+Sans:wght@200;300;350;400;500;600&display=swap" rel="stylesheet"><link rel="apple-touch-icon" href="/dgidb-icon_48.png"/><link rel="manifest" href="/manifest.json"/><title>DGIdb</title><script defer="defer" src="/static/js/main.c80660af.js"></script><link href="/static/css/main.090445a8.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>

Large diffs are not rendered by default.

Large diffs are not rendered by default.

0 comments on commit 4d9303b

Please sign in to comment.