diff --git a/dataload/configs/subgraph_configs/src/shared.py b/dataload/configs/subgraph_configs/src/shared.py index 4d3ce2d..0a0324c 100644 --- a/dataload/configs/subgraph_configs/src/shared.py +++ b/dataload/configs/subgraph_configs/src/shared.py @@ -30,7 +30,8 @@ "mondo:0000001", "efo:0000408", "chebi:36080", - "chebi:24431" + "chebi:24431", + "biolink:ChemicalEntity" ], "additional_equivalence_groups": [ ["grebi:name", "ols:label", "rdfs:label", "monarch:name", "impc:name", "reactome:displayName", "dcterms:title", "ncit:Preferred_Name", "robokop:name"], diff --git a/webapp/grebi_ui/dev_server.mjs b/webapp/grebi_ui/dev_server.mjs index 6da7410..a0c09e0 100644 --- a/webapp/grebi_ui/dev_server.mjs +++ b/webapp/grebi_ui/dev_server.mjs @@ -8,6 +8,7 @@ import nocache from 'nocache' let server = express() server.use(nocache()) +server.set('etag', false) if(process.env.GREBI_DEV_BACKEND_PROXY_URL === undefined) { throw new Error('please set GREBI_DEV_BACKEND_PROXY_URL before running dev server') @@ -30,7 +31,7 @@ server.use(/^\/api.*/, async (req, res) => { }) -server.use(express.static('dist')) +server.use(express.static('dist', { etag: false })) server.get(/^(?!\/api).*$/, (req, res) => { res.sendFile(process.cwd() + '/dist/index.html') diff --git a/webapp/grebi_ui/src/app/api.ts b/webapp/grebi_ui/src/app/api.ts index 516fcf1..f3f0e17 100644 --- a/webapp/grebi_ui/src/app/api.ts +++ b/webapp/grebi_ui/src/app/api.ts @@ -69,7 +69,7 @@ export async function getPaginated( res.page || 0, res.numElements || 0, res.totalPages || 0, - res.totalElements || 0, + res.total || 0, res.content || [], res.facetFieldToCounts || new Map() ); diff --git a/webapp/grebi_ui/src/components/SearchInterface.tsx b/webapp/grebi_ui/src/components/SearchInterface.tsx new file mode 100644 index 0000000..7d5c6ed --- /dev/null +++ b/webapp/grebi_ui/src/components/SearchInterface.tsx @@ -0,0 +1,368 @@ + +import { Close, KeyboardArrowDown } from "@mui/icons-material"; +import { Pagination } from "@mui/material"; +import { Fragment, useCallback, useEffect, useState } from "react"; +import { Link, useParams, useSearchParams } from "react-router-dom"; +import { getPaginated } from "../app/api"; +import { usePrevious, copyToClipboard } from "../app/util"; +import GraphNode from "../model/GraphNode"; +import CollapsingIdList from "./CollapsingIdList"; +import { DatasourceTags } from "./DatasourceTag"; +import LoadingOverlay from "./LoadingOverlay"; +import SearchBox from "./SearchBox"; + +export default function SeachInterface(opts:{ subgraph:string } +) { + let { subgraph } = opts + + const [searchParams] = useSearchParams(); + const search = searchParams.get("q") || ""; + + const params = useParams(); + + let [loadingResults, setLoadingResults] = useState(true); + let [results, setResults] = useState([]); + let [totalResults, setTotalResults] = useState(0); + + let [facets, setFacets] = useState({}); + + const prevSearch = usePrevious(search); + + const [page, setPage] = useState(0); + const [rowsPerPage, setRowsPerPage] = useState(10); + const [ontologyFacetQuery, setOntologyFacetQuery] = useState(""); + const [hideFilters, setHideFilters] = useState(true); + + const datasourceFacets = + facets && Object.keys(facets).length > 0 ? facets["grebi:datasources"] : {}; + const [datasourceFacetselected, setDatasourceFacetselected] = useState( + [] + ); + const handleOntologyFacet = useCallback( + (checked, key) => { + let selected: string[] = datasourceFacetselected; + if (checked) { + selected = [...selected, key]; + } else { + selected = selected.filter((facet) => facet !== key); + } + setDatasourceFacetselected((prev) => { + if (selected !== prev) setPage(0); + return selected; + }); + }, + [datasourceFacetselected, setDatasourceFacetselected] + ); + const typeFacets = + facets && Object.keys(facets).length > 0 ? facets["type"] : {}; + const [typeFacetSelected, setTypeFacetSelected] = useState([]); + const handleTypeFacet = useCallback( + (checked, key) => { + let selected: string[] = typeFacetSelected; + if (checked) { + selected = [...selected, key]; + } else { + selected = selected.filter((facet) => facet !== key); + } + setTypeFacetSelected((prev) => { + if (selected !== prev) setPage(0); + return selected; + }); + }, + [typeFacetSelected, setTypeFacetSelected] + ); + + const [ontologyFacetFiltered, setOntologyFacetFiltered] = useState( + {} + ); + useEffect(() => { + setOntologyFacetFiltered(datasourceFacets); + }, [JSON.stringify(datasourceFacets)]); + + const [isShortFormCopied, setIsShortFormCopied] = useState(false); + const copyShortForm = (text: string) => { + copyToClipboard(text) + .then(() => { + setIsShortFormCopied(true); + // revert after a few seconds + setTimeout(() => { + setIsShortFormCopied(false); + }, 500); + }) + .catch((err) => { + console.log(err); + }); + }; + + useEffect(() => { + + async function doSearch() { + setLoadingResults(true) + + let res = (await getPaginated(`api/v1/subgraphs/${subgraph}/search`, { + page: page.toString(), size: rowsPerPage.toString(), q: search, + facet: ['grebi:datasources','grebi:type'] + /*grebi__datasource: datasourceFacetselected, + type: typeFacetSelected, + searchParams,*/ + })).map(r => new GraphNode(r)) + + setResults(res.elements) + setFacets(res.facetFieldsToCounts) + + setLoadingResults(false) + } + + doSearch() + }, [ + search, + page, + rowsPerPage, + datasourceFacetselected, + typeFacetSelected, + searchParams, + ]); + useEffect(() => { + if (prevSearch !== search) setPage(0); + }, [search, prevSearch]); + + return +
+ +
+
+
+
+
+ {`Showing ${ + totalResults > rowsPerPage ? rowsPerPage : totalResults + } from a total of ${totalResults}`} +
+ +
+ {totalResults > 0 ? ( +
+
Type
+
+ {typeFacets && Object.keys(typeFacets).length > 0 + ? Object.keys(typeFacets) + .sort((a, b) => { + const ac = a ? a.toString() : ""; + const bc = b ? b.toString() : ""; + return ac.localeCompare(bc); + }) + .map((key) => { + if (key !== "entity" && typeFacets[key] > 0) { + return ( + + ); + } else return null; + }) + : null} +
+
Ontology
+
+ { + if (event.target.value) { + setOntologyFacetFiltered( + Object.fromEntries( + Object.entries(datasourceFacets).filter((key) => + key + .toString() + .toLowerCase() + .includes(event.target.value.toLowerCase()) + ) + ) + ); + setOntologyFacetQuery(event.target.value); + } else { + setOntologyFacetFiltered(datasourceFacets); + setOntologyFacetQuery(""); + } + }} + /> + {ontologyFacetQuery ? ( +
+ +
+ ) : null} +
+
+ {ontologyFacetFiltered && + Object.keys(ontologyFacetFiltered).length > 0 + ? Object.keys(ontologyFacetFiltered) + .sort((a, b) => { + const ac = a ? a.toString() : ""; + const bc = b ? b.toString() : ""; + return ac.localeCompare(bc); + }) + .map((key) => { + if (ontologyFacetFiltered[key] > 0) { + return ( + + ); + } else return null; + }) + : null} +
+
+ ) : null} +
+
+
+
+ Search results for: {search} +
+
+ +
+ + +
+ +
+
+
+
+ {results.length > 0 ? ( +
+ + {results.map((graphNode: GraphNode) => { + let nodeType = graphNode.extractType() + return ( +
+
+ + {graphNode.getName()} + + { nodeType && + {nodeType.long} + } + +
+ + {graphNode.getDescription() && ( +
+ {graphNode.getDescription()} +
)} +
+ ); + })} + +
+ ) : ( +
+
+ )} +
+
+
setHideFilters(true)} + /> + {loadingResults ? ( + + ) : null} + +} diff --git a/webapp/grebi_ui/src/components/exposomekg/ExposureLinks.tsx b/webapp/grebi_ui/src/components/exposomekg/ExposureLinks.tsx new file mode 100644 index 0000000..9f4f525 --- /dev/null +++ b/webapp/grebi_ui/src/components/exposomekg/ExposureLinks.tsx @@ -0,0 +1,90 @@ +import { useEffect, useState } from "react"; +import GraphNode from "../../model/GraphNode"; +import { getPaginated, Page } from "../../app/api"; +import encodeNodeId from "../../encodeNodeId"; +import { Grid, Typography } from "@mui/material"; +import { copyToClipboard } from "../../app/util"; + + +export default function ExposureLinks({node}:{node:GraphNode}) { + + let type = node.extractType() + var links:any = null + if(type) { + if(type.short === 'Gene') { + links = + } + if(type.short === 'Chemical') { + links = + } + } + + + return
+ + + {node.getSourceIds().map(id => +
+ {id.value} +
+
+)} +
+
+ {links && links} +
+} + +function GeneExposureLinks({node}:{node:GraphNode}) { + + let [affectedBy, setAffectedBy] = useState|null>(null) + + useEffect(() => { + + async function getAffectedBy() { + let res = await getPaginated(`api/v1/subgraphs/${node.getSubgraph()}/nodes/${encodeNodeId(node.getNodeId())}/incoming_edges`, { + 'grebi:type': 'biolink:chemical_gene_interaction_association' + }) + setAffectedBy(res) + } + + getAffectedBy() + + }, [node.getNodeId()]) + + return
+ + { affectedBy && + +
+ + } + +
+ + + +} + +function ChemicalExposureLinks({node}:{node:GraphNode}) { + + return
+} + + + +function ExpandableSection({title, children}) { + + let [expanded, setExpanded] = useState(false); + + return
+ setExpanded(!expanded)} style={{cursor:'pointer'}}> + {expanded ? '-\t' : '+\t'} + {title} + + { expanded && children } +
+ +} \ No newline at end of file diff --git a/webapp/grebi_ui/src/frontends/ebi/pages/EbiSearchPage.tsx b/webapp/grebi_ui/src/frontends/ebi/pages/EbiSearchPage.tsx index 07d8a2e..8d08fe9 100644 --- a/webapp/grebi_ui/src/frontends/ebi/pages/EbiSearchPage.tsx +++ b/webapp/grebi_ui/src/frontends/ebi/pages/EbiSearchPage.tsx @@ -1,370 +1,17 @@ -import { Close, KeyboardArrowDown } from "@mui/icons-material"; -import { Pagination } from "@mui/material"; -import { useCallback, useEffect, useState } from "react"; -import { Link, useParams, useSearchParams } from "react-router-dom"; -import { getPaginated } from "../../../app/api"; -import { usePrevious, copyToClipboard } from "../../../app/util"; -import { DatasourceTags } from "../../../components/DatasourceTag"; -import LoadingOverlay from "../../../components/LoadingOverlay"; -import SearchBox from "../../../components/SearchBox"; -import GraphNode from "../../../model/GraphNode"; import EbiHeader from "../EbiHeader"; -import CollapsingIdList from "../../../components/CollapsingIdList"; +import SearchInterface from "../../../components/SearchInterface"; +import { useParams } from "react-router-dom"; export default function EbiSearchPage() { - const [searchParams] = useSearchParams(); - const search = searchParams.get("q") || ""; - const params = useParams(); + let params = useParams() const subgraph: string = params.subgraph as string; - let [loadingResults, setLoadingResults] = useState(true); - let [results, setResults] = useState([]); - let [totalResults, setTotalResults] = useState(0); - - let [facets, setFacets] = useState({}); - - const prevSearch = usePrevious(search); - - const [page, setPage] = useState(0); - const [rowsPerPage, setRowsPerPage] = useState(10); - const [ontologyFacetQuery, setOntologyFacetQuery] = useState(""); - const [hideFilters, setHideFilters] = useState(true); - - const datasourceFacets = - facets && Object.keys(facets).length > 0 ? facets["grebi:datasources"] : {}; - const [datasourceFacetselected, setDatasourceFacetselected] = useState( - [] - ); - const handleOntologyFacet = useCallback( - (checked, key) => { - let selected: string[] = datasourceFacetselected; - if (checked) { - selected = [...selected, key]; - } else { - selected = selected.filter((facet) => facet !== key); - } - setDatasourceFacetselected((prev) => { - if (selected !== prev) setPage(0); - return selected; - }); - }, - [datasourceFacetselected, setDatasourceFacetselected] - ); - const typeFacets = - facets && Object.keys(facets).length > 0 ? facets["type"] : {}; - const [typeFacetSelected, setTypeFacetSelected] = useState([]); - const handleTypeFacet = useCallback( - (checked, key) => { - let selected: string[] = typeFacetSelected; - if (checked) { - selected = [...selected, key]; - } else { - selected = selected.filter((facet) => facet !== key); - } - setTypeFacetSelected((prev) => { - if (selected !== prev) setPage(0); - return selected; - }); - }, - [typeFacetSelected, setTypeFacetSelected] - ); - - const [ontologyFacetFiltered, setOntologyFacetFiltered] = useState( - {} - ); - useEffect(() => { - setOntologyFacetFiltered(datasourceFacets); - }, [JSON.stringify(datasourceFacets)]); - - const [isShortFormCopied, setIsShortFormCopied] = useState(false); - const copyShortForm = (text: string) => { - copyToClipboard(text) - .then(() => { - setIsShortFormCopied(true); - // revert after a few seconds - setTimeout(() => { - setIsShortFormCopied(false); - }, 500); - }) - .catch((err) => { - console.log(err); - }); - }; - - useEffect(() => { - - async function doSearch() { - setLoadingResults(true) - - let res = (await getPaginated(`api/v1/subgraphs/${subgraph}/search`, { - page: page.toString(), size: rowsPerPage.toString(), q: search, - facet: ['grebi:datasources','grebi:type'] - /*grebi__datasource: datasourceFacetselected, - type: typeFacetSelected, - searchParams,*/ - })).map(r => new GraphNode(r)) - - setResults(res.elements) - setFacets(res.facetFieldsToCounts) - - setLoadingResults(false) - } - - doSearch() - }, [ - search, - page, - rowsPerPage, - datasourceFacetselected, - typeFacetSelected, - searchParams, - ]); - useEffect(() => { - if (prevSearch !== search) setPage(0); - }, [search, prevSearch]); - return (
-
- -
-
-
-
-
- {`Showing ${ - totalResults > rowsPerPage ? rowsPerPage : totalResults - } from a total of ${totalResults}`} -
- -
- {totalResults > 0 ? ( -
-
Type
-
- {typeFacets && Object.keys(typeFacets).length > 0 - ? Object.keys(typeFacets) - .sort((a, b) => { - const ac = a ? a.toString() : ""; - const bc = b ? b.toString() : ""; - return ac.localeCompare(bc); - }) - .map((key) => { - if (key !== "entity" && typeFacets[key] > 0) { - return ( - - ); - } else return null; - }) - : null} -
-
Ontology
-
- { - if (event.target.value) { - setOntologyFacetFiltered( - Object.fromEntries( - Object.entries(datasourceFacets).filter((key) => - key - .toString() - .toLowerCase() - .includes(event.target.value.toLowerCase()) - ) - ) - ); - setOntologyFacetQuery(event.target.value); - } else { - setOntologyFacetFiltered(datasourceFacets); - setOntologyFacetQuery(""); - } - }} - /> - {ontologyFacetQuery ? ( -
- -
- ) : null} -
-
- {ontologyFacetFiltered && - Object.keys(ontologyFacetFiltered).length > 0 - ? Object.keys(ontologyFacetFiltered) - .sort((a, b) => { - const ac = a ? a.toString() : ""; - const bc = b ? b.toString() : ""; - return ac.localeCompare(bc); - }) - .map((key) => { - if (ontologyFacetFiltered[key] > 0) { - return ( - - ); - } else return null; - }) - : null} -
-
- ) : null} -
-
-
-
- Search results for: {search} -
-
- -
- - -
- -
-
-
-
- {results.length > 0 ? ( -
- - {results.map((graphNode: GraphNode) => { - let nodeType = graphNode.extractType() - return ( -
-
- - {graphNode.getName()} - - { nodeType && - {nodeType.long} - } - -
- - {graphNode.getDescription() && ( -
- {graphNode.getDescription()} -
)} -
- ); - })} - -
- ) : ( -
-
- )} -
-
-
setHideFilters(true)} - /> - {loadingResults ? ( - - ) : null} +
); diff --git a/webapp/grebi_ui/src/frontends/exposomekg/EkgHeader.tsx b/webapp/grebi_ui/src/frontends/exposomekg/EkgHeader.tsx index 0e30518..e368698 100644 --- a/webapp/grebi_ui/src/frontends/exposomekg/EkgHeader.tsx +++ b/webapp/grebi_ui/src/frontends/exposomekg/EkgHeader.tsx @@ -27,19 +27,9 @@ export default function EkgHeader({ section }: { section?: string }) { > - {caps(section)} - GrEBI + {caps(section)} - ExposomeKG
-
- - GrEBI logo - -