diff --git a/src/views/clinicalGenomic/clinicalGenomicSearch.js b/src/views/clinicalGenomic/clinicalGenomicSearch.js
index b65955a..27df959 100644
--- a/src/views/clinicalGenomic/clinicalGenomicSearch.js
+++ b/src/views/clinicalGenomic/clinicalGenomicSearch.js
@@ -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';
@@ -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.
@@ -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}`]: {
@@ -66,6 +73,13 @@ const Root = styled('div')(({ _ }) => ({
[`& .${classes.mainContent}`]: {
padding: '16px !important'
+ },
+
+ [`& .${classes.toolbar}`]: {
+ padding: 5,
+ paddingLeft: 20,
+ paddingRight: 20,
+ minHeight: 58
}
}));
@@ -82,8 +96,8 @@ const StyledMainCard = styled(MainCard)((_) => ({
const sections = [
{
id: 'cohorts summary',
- header: 'Cohorts Summary',
- component:
+ header: undefined,
+ component:
},
{
id: 'counts',
@@ -97,7 +111,7 @@ const sections = [
},
{
id: 'authorized cohorts',
- header: 'Authorized Cohorts',
+ header: undefined,
component:
},
{
@@ -129,30 +143,34 @@ function ClinicalGenomicSearch() {
{/* Top bar */}
-
+
Federated Search
- {sections.map((section) => (
-
- ))}
+ {sections
+ .map((section) =>
+ section.header !== undefined ? (
+
+ ) : undefined
+ )
+ .filter((obj) => obj !== undefined)}
+
{/* Empty div to make sure the header takes up space */}
+
{sections.map((section) => (
diff --git a/src/views/clinicalGenomic/search/SearchHandler.js b/src/views/clinicalGenomic/search/SearchHandler.js
index 954cc16..bb640d8 100644
--- a/src/views/clinicalGenomic/search/SearchHandler.js
+++ b/src/views/clinicalGenomic/search/SearchHandler.js
@@ -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');
diff --git a/src/views/clinicalGenomic/widgets/searchExplainer.js b/src/views/clinicalGenomic/widgets/searchExplainer.js
new file mode 100644
index 0000000..d1821f7
--- /dev/null
+++ b/src/views/clinicalGenomic/widgets/searchExplainer.js
@@ -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) => [ OR , value]).slice(1);
+ const formattedKey = key.replaceAll('_', ' ');
+ newVal.splice(
+ 0,
+ 0,
+
+ {formattedKey}:{' '}
+
+ );
+ 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(
+ () => (
+
+
+ {queryChips
+ /* NB: FlatMap+slice(1) to insert ANDs between entries */
+ .flatMap((chip) => [
+
+ AND
+ ,
+
+ ])
+ .slice(1)}
+
+
+ ),
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ [reader.reqNum]
+ );
+}
+
+export default SearchExplainer;
diff --git a/src/views/clinicalGenomic/widgets/sidebar.js b/src/views/clinicalGenomic/widgets/sidebar.js
index 7b0b7a4..582af3d 100644
--- a/src/views/clinicalGenomic/widgets/sidebar.js
+++ b/src/views/clinicalGenomic/widgets/sidebar.js
@@ -1,6 +1,7 @@
import { useEffect, useState } from 'react';
import {
+ Chip,
Checkbox,
FormControl,
FormControlLabel,
@@ -193,7 +194,7 @@ function StyledCheckboxList(props) {
options={options}
disableCloseOnSelect
renderOption={(props, option, { selected }) => (
-
+
)}
renderInput={(params) => }
+ renderTags={(tagValue, getTagProps) =>
+ tagValue.map((option, index) => )
+ }
// set width to match parent
sx={{ width: '100%', paddingTop: '0.5em', paddingBottom: '0.5em' }}
onChange={(_, value, reason) => {
@@ -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 }));
};