Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DIG-1352 Add a search display bar to the top of the query page #188

Merged
merged 16 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 44 additions & 26 deletions src/views/clinicalGenomic/clinicalGenomicSearch.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import SearchHandler from './search/SearchHandler';
import GenomicData from './widgets/genomicData';
import { SearchIndicator } from 'ui-component/LoadingIndicator/SearchIndicator';
import AuthorizationSections from './widgets/authorizationSections';
import SearchExplainer from './widgets/searchExplainer';

const PREFIX = 'ClinicalGenomicSearch';

Expand All @@ -22,9 +23,11 @@ const classes = {
sidebarOffset: `${PREFIX}-sidebarOffset`,
noSidebarOffset: `${PREFIX}-noSidebarOffset`,
headerSpacing: `${PREFIX}-headerSpacing`,
headerSize: `${PREFIX}-headerSize`,
anchor: `${PREFIX}-anchor`,
navigationLink: `${PREFIX}-navigationLink`,
mainContent: `${PREFIX}-mainContent`
mainContent: `${PREFIX}-mainContent`,
toolbar: `${PREFIX}-toolbar`
};

// TODO jss-to-styled codemod: The Fragment root was replaced by div. Change the tag if needed.
Expand All @@ -39,24 +42,28 @@ const Root = styled('div')(({ _ }) => ({
},

[`& .${classes.sidebarOffset}`]: {
width: 'calc(100% - 320px)',
width: 'calc(100% - 300px)',
left: 280
},

[`& .${classes.noSidebarOffset}`]: {
width: 'calc(100% - 80px)',
left: 40
width: 'calc(100% - 35px)',
left: 18
},

[`& .${classes.headerSize}`]: {
height: 110
},

[`& .${classes.headerSpacing}`]: {
height: 50
height: 70
},

[`& .${classes.anchor}`]: {
display: 'block',
position: 'relative',
visibility: 'hidden',
top: -150
top: -250
},

[`& .${classes.navigationLink}`]: {
Expand All @@ -66,6 +73,13 @@ const Root = styled('div')(({ _ }) => ({

[`& .${classes.mainContent}`]: {
padding: '16px !important'
},

[`& .${classes.toolbar}`]: {
padding: 5,
paddingLeft: 20,
paddingRight: 20,
minHeight: 58
}
}));

Expand All @@ -82,8 +96,8 @@ const StyledMainCard = styled(MainCard)((_) => ({
const sections = [
{
id: 'cohorts summary',
header: 'Cohorts Summary',
component: <AuthorizationSections title="Cohorts Summary" />
header: undefined,
component: <AuthorizationSections title="All Cohorts" />
},
{
id: 'counts',
Expand All @@ -97,7 +111,7 @@ const sections = [
},
{
id: 'authorized cohorts',
header: 'Authorized Cohorts',
header: undefined,
component: <AuthorizationSections title="Authorized Cohorts" />
},
{
Expand Down Expand Up @@ -129,30 +143,34 @@ function ClinicalGenomicSearch() {
{/* Top bar */}
<AppBar
component="nav"
className={`${classes.stickytop} ${classes.headerSpacing} ${
sidebarOpened ? classes.sidebarOffset : classes.noSidebarOffset
}`}
className={`${classes.stickytop} ${classes.headerSize} ${sidebarOpened ? classes.sidebarOffset : classes.noSidebarOffset}`}
>
<Toolbar sx={{ padding: '5px' }}>
<Toolbar className={classes.toolbar}>
<Typography variant="h4" sx={{ flexGrow: 1 }}>
Federated Search
</Typography>
{sections.map((section) => (
<Button
onClick={() => {
window.location.href = `#${section.id}`;
}}
sx={{ my: 2, display: 'block' }}
key={section.id}
className={classes.navigationLink}
variant="text"
>
{section.header}
</Button>
))}
{sections
.map((section) =>
section.header !== undefined ? (
<Button
onClick={() => {
window.location.href = `#${section.id}`;
}}
sx={{ my: 2, display: 'block' }}
key={section.id}
className={classes.navigationLink}
variant="text"
>
{section.header}
</Button>
) : undefined
)
.filter((obj) => obj !== undefined)}
</Toolbar>
<SearchExplainer />
</AppBar>
{/* Empty div to make sure the header takes up space */}
<div className={classes.headerSpacing} />
<SearchHandler setLoading={setLoading} />
<MainCard sx={{ minHeight: 830, position: 'relative', borderRadius: customization.borderRadius * 0.25, marginTop: '2.5em' }}>
{sections.map((section) => (
Expand Down
2 changes: 1 addition & 1 deletion src/views/clinicalGenomic/search/SearchHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ function SearchHandler({ setLoading }) {
summaryFetchAbort.current = newAbort;
}, [reader.reqNum]);

// Query 2: when the search query changes, re-query the server
// Query 3: when the search query changes, re-query the server
useEffect(() => {
// First, we abort any currently-running search promises
clinicalFetchAbort.current.abort('New request started');
Expand Down
95 changes: 95 additions & 0 deletions src/views/clinicalGenomic/widgets/searchExplainer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { useEffect, useMemo } from 'react';
import { styled } from '@mui/material/styles';
import { Box, Chip } from '@mui/material';
import { useSearchQueryReaderContext, useSearchResultsWriterContext } from '../SearchResultsContext';

const PREFIX = 'SearchExplainer';

const Root = styled('div')(({ theme }) => ({
[`& .${PREFIX}-chiptext`]: {
textTransform: 'capitalize'
},
[`& .${PREFIX}-background`]: {
backgroundColor: theme.palette.primary.light,
color: 'black',
paddingLeft: 15,
paddingBottom: 20
},
[`& .${PREFIX}-chip`]: {
backgroundColor: 'white',
marginRight: 5,
marginLeft: 5,
marginTop: 20
},
[`& .${PREFIX}-bold`]: {
position: 'relative',
top: 10
}
}));

function SearchExplainer() {
const reader = useSearchQueryReaderContext();
const writer = useSearchResultsWriterContext();
const query = reader.query;
const queryChips = [];

// Decompose the query into its roots: what are we searching on?
if (query !== undefined) {
Object.keys(query).forEach((key) => {
if (key !== undefined && query[key] !== undefined) {
const onDelete = () => {
writer((old) => ({ ...old, clear: key }));
};
const splitQuery = query[key].split('|');
const newVal = splitQuery.flatMap((value) => [<b key={value}> OR </b>, value]).slice(1);
const formattedKey = key.replaceAll('_', ' ');
newVal.splice(
0,
0,
<span className={`${PREFIX}-chiptext`} key={`${key} span`}>
{formattedKey}:{' '}
</span>
);
queryChips.push([key, newVal, onDelete]);
}
});
}

useEffect(() => {
writer((old) => ({ ...old, clear: '' }));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [reader.reqNum]);

if (queryChips.length === 0) {
queryChips.push(['all', 'All results', undefined]);
}

return useMemo(
() => (
<Root>
<Box className={`${PREFIX}-background`}>
{queryChips
/* NB: FlatMap+slice(1) to insert ANDs between entries */
.flatMap((chip) => [
<span className={`${PREFIX}-bold`} key={`${chip[0]} and`}>
<b>&nbsp;AND&nbsp;</b>
</span>,
<Chip
key={`${chip[0]} chip`}
label={chip[1]}
onDelete={chip[2]}
variant="outlined"
color="primary"
className={`${PREFIX}-chip`}
/>
])
.slice(1)}
</Box>
</Root>
),
// eslint-disable-next-line react-hooks/exhaustive-deps
[reader.reqNum]
);
}

export default SearchExplainer;
67 changes: 66 additions & 1 deletion src/views/clinicalGenomic/widgets/sidebar.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useEffect, useState } from 'react';

import {
Chip,
Checkbox,
FormControl,
FormControlLabel,
Expand Down Expand Up @@ -193,7 +194,7 @@ function StyledCheckboxList(props) {
options={options}
disableCloseOnSelect
renderOption={(props, option, { selected }) => (
<li {...props} value={option}>
<li {...props} key={option}>
<Checkbox
icon={icon}
checkedIcon={checkedIcon}
Expand All @@ -205,6 +206,9 @@ function StyledCheckboxList(props) {
</li>
)}
renderInput={(params) => <TextField {...params} label={groupName} />}
renderTags={(tagValue, getTagProps) =>
tagValue.map((option, index) => <Chip {...getTagProps({ index })} key={option} label={option} />)
}
// set width to match parent
sx={{ width: '100%', paddingTop: '0.5em', paddingBottom: '0.5em' }}
onChange={(_, value, reason) => {
Expand Down Expand Up @@ -424,6 +428,67 @@ function Sidebar() {
writerContext(() => ({ reqNum: 0 }));
}, [writerContext]);

// Certain webpage components can cause the sidebar to clear a particular entry (e.g. the search explanation)
useEffect(() => {
if (readerContext.clear === 'nodes') {
setSelectedNodes({});
writerContext((old) => ({
...old,
filter: {
...old.filter,
node: [readerContext?.programs?.map((loc) => loc.location.name) || []]
},
reqNum: old.reqNum + 1
}));
} else if (readerContext.clear === 'cohorts') {
setSelectedCohorts({});
writerContext((old) => ({
...old,
filter: {
...old.filter,
exclude_cohorts: [
readerContext?.programs?.map((loc) => loc?.results?.items?.map((cohort) => cohort.program_id)).flat(1) || []
]
},
reqNum: old.reqNum + 1
}));
} else if (readerContext.clear === 'gene' || readerContext.clear === 'chrom' || readerContext.clear === 'assembly') {
setSelectedGenes('');
setSelectedChromosomes('');
setStartPos('0');
setEndPos('0');
writerContext((old) => {
const retVal = { ...old, reqNum: old.reqNum + 1 };
delete retVal.query.chrom;
delete retVal.query.gene;
delete retVal.query.assembly;
return retVal;
});
} else if (readerContext.clear === 'treatment') {
setSelectedTreatment({});
writerContext((old) => {
const retVal = { ...old, reqNum: old.reqNum + 1 };
delete retVal.query.treatment;
return retVal;
});
} else if (readerContext.clear === 'primary_site') {
setSelectedPrimarySite({});
writerContext((old) => {
const retVal = { ...old, reqNum: old.reqNum + 1 };
delete retVal.query.primary_site;
return retVal;
});
} else if (readerContext.clear === 'drug_name') {
setSelectedSystemicTherapy({});
writerContext((old) => {
const retVal = { ...old, reqNum: old.reqNum + 1 };
delete retVal.query.drug_name;
return retVal;
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [readerContext.clear]);

const triggerSearch = () => {
writerContext((old) => ({ ...old, reqNum: 'reqNum' in old ? old.reqNum + 1 : 0 }));
};
Expand Down
Loading