diff --git a/webapp/grebi_ui/src/app/api.ts b/webapp/grebi_ui/src/app/api.ts index f3f0e17..4dfbb7d 100644 --- a/webapp/grebi_ui/src/app/api.ts +++ b/webapp/grebi_ui/src/app/api.ts @@ -23,11 +23,15 @@ export async function request( apiUrl?: string ): Promise { const url = (apiUrl || process.env.REACT_APP_APIURL) + path; + const message = `Loading ${url}` + console.log(message) + console.time(message) //const res = await fetch(url.replace(/([^:]\/)\/+/g, "$1"), { const res = await fetch(url + (reqParams ? ('?' + buildSearchParams(reqParams)) : ''), { ...(init ? init : {}), //headers: { ...(init?.headers || {}), ...getAuthHeaders() } }); + console.timeEnd(message) if (!res.ok) { const message = `Failure loading ${res.url} with status ${res.status} (${res.statusText})`; console.dir(message); diff --git a/webapp/grebi_ui/src/components/TabPanel.tsx b/webapp/grebi_ui/src/components/TabPanel.tsx new file mode 100644 index 0000000..88f893c --- /dev/null +++ b/webapp/grebi_ui/src/components/TabPanel.tsx @@ -0,0 +1,28 @@ +import { Box, Typography } from "@mui/material"; + +export interface TabPanelProps { + children?: React.ReactNode; + index: string; + value: string; +} + +export default function TabPanel(props: TabPanelProps) { + const { children, value, index, ...other } = props; + + return ( + + ); +} + diff --git a/webapp/grebi_ui/src/components/datatable/DataTable.tsx b/webapp/grebi_ui/src/components/datatable/DataTable.tsx index 7cd55ca..cdb08d3 100644 --- a/webapp/grebi_ui/src/components/datatable/DataTable.tsx +++ b/webapp/grebi_ui/src/components/datatable/DataTable.tsx @@ -30,7 +30,8 @@ export default function DataTable({ sortColumn, setSortColumn, sortDir, - setSortDir + setSortDir, + maxRowHeight }: { columns?: readonly Column[]|undefined; defaultSelector:undefined|((row:any, key:string)=>any); @@ -49,6 +50,7 @@ export default function DataTable({ setSortColumn?: (sortColumn: string) => void, sortDir?: 'asc'|'desc', setSortDir?: (sortDir: 'asc'|'desc') => void, + maxRowHeight?:string|undefined }) { let [autoAddedColumns, setAutoAddedColumns] = useState([]) @@ -186,9 +188,11 @@ export default function DataTable({ className="text-md align-top py-2 px-4" key={randomString()} > +
{column.selector(row, column.id) ? column.selector(row, column.id) : "(no data)"} +
); })} diff --git a/webapp/grebi_ui/src/components/datatable/LocalDataTable.tsx b/webapp/grebi_ui/src/components/datatable/LocalDataTable.tsx index f0fbbaa..04a14c7 100644 --- a/webapp/grebi_ui/src/components/datatable/LocalDataTable.tsx +++ b/webapp/grebi_ui/src/components/datatable/LocalDataTable.tsx @@ -7,35 +7,49 @@ export default function LocalDataTable({ hideColumns, addColumnsFromData, defaultSelector, - onSelectRow + onSelectRow, + maxRowHeight }:{ data: any[], columns?: readonly Column[]|undefined, hideColumns?: string[]|undefined, addColumnsFromData?:boolean, defaultSelector:undefined|((row:any, key:string)=>any); - onSelectRow?: (row: any) => void + onSelectRow?: (row: any) => void, + maxRowHeight?:string|undefined }) { - let [page, setPage] = useState(0) + let [page, setPage] = useState(1) let [rowsPerPage, setRowsPerPage] = useState(10) let [sortColumn, setSortColumn] = useState("") let [sortDir, setSortDir] = useState<'asc'|'desc'>("asc") let [filter, setFilter] = useState("") - let filteredData = data.filter(row => { + data = data.filter(row => { return Object.values(row).some(v => { return (v+'').toLowerCase().includes(filter.toLowerCase()) }) }); + data = data.slice((page-1)*rowsPerPage, page*rowsPerPage) + return - } diff --git a/webapp/grebi_ui/src/components/datatable/renderValueBestEffort.tsx b/webapp/grebi_ui/src/components/datatable/renderValueBestEffort.tsx deleted file mode 100644 index 53fbd6e..0000000 --- a/webapp/grebi_ui/src/components/datatable/renderValueBestEffort.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { Fragment } from "react/jsx-runtime" -import Refs from "../../model/Refs" - -function renderValueBestEffort(value:any, refs?:Refs):JSX.Element { - if(Array.isArray(value)) { - let elems:JSX.Element[] = [] - let isFirst = true - for(let elem of value) { - if(isFirst) { - isFirst = false - } else { - elems.push(; ) - } - elems.push(renderValueBestEffort(elem)) - } - return {elems} - } - if(typeof value === 'string') { - return {value} - } - return {JSON.stringify(value)} -} - - diff --git a/webapp/grebi_ui/src/components/exposomekg/ExposureLinks.tsx b/webapp/grebi_ui/src/components/exposomekg/ExposureLinks.tsx index 51a52b8..e9db24e 100644 --- a/webapp/grebi_ui/src/components/exposomekg/ExposureLinks.tsx +++ b/webapp/grebi_ui/src/components/exposomekg/ExposureLinks.tsx @@ -2,7 +2,7 @@ import { Fragment, useEffect, useState } from "react"; import GraphNode from "../../model/GraphNode"; import { getPaginated, Page } from "../../app/api"; import encodeNodeId from "../../encodeNodeId"; -import { CircularProgress, Grid, Typography } from "@mui/material"; +import { CircularProgress, Grid, Tab, Tabs, Typography } from "@mui/material"; import { asArray, copyToClipboard } from "../../app/util"; import LocalDataTable from "../datatable/LocalDataTable"; import NodeRefLink from "../node_edge_list/NodeRefLink"; @@ -12,37 +12,53 @@ import { DatasourceTags } from "../DatasourceTag"; import Refs from "../../model/Refs"; import PropVals from "../node_prop_table/PropVals"; import PropVal from "../../model/PropVal"; +import { useSearchParams } from "react-router-dom"; +import getExposureLinksTabs, { LinksTab } from "./getExposureLinksTabs"; +import { OpenInNew, Share } from "@mui/icons-material"; +import TabPanel from "../TabPanel"; 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 = - } - } + let [searchParams, setSearchParams] = useSearchParams(); + let linksTab = searchParams.get("linksTab") || "sourceids"; + let [linksTabs, setLinksTabs] = useState([]) - return
- + useEffect(() => { + async function getLinksTabs() { + let tabs = await getExposureLinksTabs(node) + setLinksTabs(tabs) + } + getLinksTabs() + }, [node]) + + return + + setSearchParams({linksTab:tab})}> + Source IDs
+ } value={"sourceids"} className="grebi-subtab" /> + {linksTabs.map(tab => {tab.tabName} + } value={tab.tabId} className="text-black" />)} + + + + - {node.getSourceIds().map(id => -
- {id.value} -
-
-)} -
- - {links && links} - + {node.getSourceIds().map(id => +
+ {id.value} +
+
+ )} +
+ + + } @@ -51,11 +67,13 @@ let fixedCols = [ id: "grebi:datasources", name: "Datasources", selector: (edge:GraphEdge, key:string) => , + sortable:true }, { id: "from", name: "Chemical", selector: (edge:GraphEdge, key:string) => , + sortable:true } ]; @@ -91,9 +109,9 @@ function GeneExposureLinks({node}:{node:GraphNode}) { return
Chemical interactions (Loading...) } loading={!affectedBy}> {affectedBy && @@ -101,6 +119,7 @@ function GeneExposureLinks({node}:{node:GraphNode}) { data={affectedBy?.elements} addColumnsFromData={true} columns={fixedCols} + maxRowHeight={"1.5em"} defaultSelector={DefaultSelector} hideColumns={[ "_refs", @@ -130,7 +149,7 @@ function ChemicalExposureLinks({node}:{node:GraphNode}) { -function ExpandableSection({title, loading, children}) { +function ExpandableSection({title, loading, children}:{title:string, loading?:boolean|undefined, children:any}) { let [expanded, setExpanded] = useState(false); diff --git a/webapp/grebi_ui/src/components/exposomekg/getExposureLinksTabs.tsx b/webapp/grebi_ui/src/components/exposomekg/getExposureLinksTabs.tsx new file mode 100644 index 0000000..e4d25d9 --- /dev/null +++ b/webapp/grebi_ui/src/components/exposomekg/getExposureLinksTabs.tsx @@ -0,0 +1,35 @@ +import { getPaginated } from "../../app/api"; +import encodeNodeId from "../../encodeNodeId"; +import GraphNode from "../../model/GraphNode"; + +export interface LinksTab { + tabId:string, + tabName:string, + count:number +} + +export default async function getExposureLinksTabs(node:GraphNode):Promise { + + let type = node.extractType() + let metadata_promises:any = [] + + if(type?.short === 'Gene') { + metadata_promises.push(getGeneLinksTabs(node)) + } + + return await Promise.all(metadata_promises) +} + +async function getGeneLinksTabs(node:GraphNode) { + + let page = await (getPaginated(`api/v1/subgraphs/${node.getSubgraph()}/nodes/${encodeNodeId(node.getNodeId())}/incoming_edges`, { + 'size': "0", + 'grebi:type': 'biolink:chemical_gene_interaction_association' + })); + + return { + tabId: "chemical_gene_interactions", + tabName: "Chemical Interactions", + count: page.totalElements + } +} diff --git a/webapp/grebi_ui/src/css/common.css b/webapp/grebi_ui/src/css/common.css index 83f7650..3d43262 100644 --- a/webapp/grebi_ui/src/css/common.css +++ b/webapp/grebi_ui/src/css/common.css @@ -1403,4 +1403,8 @@ filter: gray; .button-primary { background-color: var(--grebi-base-color); border-color: var(--grebi-base-color); -} \ No newline at end of file +} +.grebi-subtab { + color: black !important; + text-transform: none !important; +} diff --git a/webapp/grebi_ui/src/frontends/exposomekg/pages/EkgNodePage.tsx b/webapp/grebi_ui/src/frontends/exposomekg/pages/EkgNodePage.tsx index 861b5dc..0673c5c 100644 --- a/webapp/grebi_ui/src/frontends/exposomekg/pages/EkgNodePage.tsx +++ b/webapp/grebi_ui/src/frontends/exposomekg/pages/EkgNodePage.tsx @@ -21,6 +21,7 @@ import GraphNode from "../../../model/GraphNode"; import { get, getPaginated } from "../../../app/api"; import encodeNodeId from "../../../encodeNodeId"; import ExposureLinks from "../../../components/exposomekg/ExposureLinks"; +import TabPanel from "../../../components/TabPanel"; export default function EkgNodePage() { @@ -79,15 +80,15 @@ export default function EkgNodePage() { - + - + - + @@ -96,31 +97,3 @@ export default function EkgNodePage() {
); } - -interface TabPanelProps { - children?: React.ReactNode; - index: string; - value: string; -} - -function TabPanel(props: TabPanelProps) { - const { children, value, index, ...other } = props; - - return ( - - ); -} - -