From 9ef356921c40379bc647343d94c176b7acc5a990 Mon Sep 17 00:00:00 2001 From: James McLaughlin Date: Sun, 8 Dec 2024 00:37:54 +0000 Subject: [PATCH] exposomekg ui: edges, graph view, prop view --- .../main/java/uk/ac/ebi/grebi/GrebiApi.java | 11 +++ .../uk/ac/ebi/grebi/repo/GrebiSolrRepo.java | 78 +++++++++++++---- webapp/grebi_ui/dev_server.mjs | 2 + webapp/grebi_ui/src/components/SearchBox.tsx | 16 +++- .../components/exposomekg/ExposureLinks.tsx | 46 +++++----- .../exposomekg/getExposureLinksTabs.tsx | 2 +- .../node_graph_view/GraphViewCtx.tsx | 86 ++++++++++--------- .../PropRowManyDatasourceSets.tsx | 4 +- .../PropRowOneDatasourceSet.tsx | 2 +- .../src/frontends/exposomekg/EkgHeader.tsx | 2 +- .../exposomekg/pages/EkgNodePage.tsx | 2 +- webapp/grebi_ui/src/model/GraphNodeRef.ts | 6 ++ 12 files changed, 166 insertions(+), 91 deletions(-) diff --git a/webapp/grebi_api/src/main/java/uk/ac/ebi/grebi/GrebiApi.java b/webapp/grebi_api/src/main/java/uk/ac/ebi/grebi/GrebiApi.java index 51efffc..45a9f42 100644 --- a/webapp/grebi_api/src/main/java/uk/ac/ebi/grebi/GrebiApi.java +++ b/webapp/grebi_api/src/main/java/uk/ac/ebi/grebi/GrebiApi.java @@ -13,6 +13,7 @@ import java.util.stream.Collectors; import io.javalin.plugin.bundled.CorsPluginConfig; +import org.apache.solr.client.solrj.SolrQuery; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; import uk.ac.ebi.grebi.repo.GrebiNeoRepo; @@ -145,6 +146,16 @@ static void run( ctx.contentType("application/json"); ctx.result(gson.toJson(res)); }) + .get("/api/v1/subgraphs/{subgraph}/nodes/{nodeId}/incoming_edge_counts", ctx -> { + var nodeId = new String(Base64.getUrlDecoder().decode(ctx.pathParam("nodeId"))); + ctx.contentType("application/json"); + ctx.result(gson.toJson(solr.getIncomingEdgeCounts(ctx.pathParam("subgraph"), nodeId))); + }) + .get("/api/v1/subgraphs/{subgraph}/nodes/{nodeId}/outgoing_edge_counts", ctx -> { + var nodeId = new String(Base64.getUrlDecoder().decode(ctx.pathParam("nodeId"))); + ctx.contentType("application/json"); + ctx.result(gson.toJson(solr.getIncomingEdgeCounts(ctx.pathParam("subgraph"), nodeId))); + }) .get("/api/v1/subgraphs/{subgraph}/nodes/{nodeId}/incoming_edges", ctx -> { var nodeId = new String(Base64.getUrlDecoder().decode(ctx.pathParam("nodeId"))); var page_num = Objects.requireNonNullElse(ctx.queryParam("page"), "0"); diff --git a/webapp/grebi_api/src/main/java/uk/ac/ebi/grebi/repo/GrebiSolrRepo.java b/webapp/grebi_api/src/main/java/uk/ac/ebi/grebi/repo/GrebiSolrRepo.java index d34405b..e95d398 100644 --- a/webapp/grebi_api/src/main/java/uk/ac/ebi/grebi/repo/GrebiSolrRepo.java +++ b/webapp/grebi_api/src/main/java/uk/ac/ebi/grebi/repo/GrebiSolrRepo.java @@ -4,6 +4,9 @@ import java.util.stream.Collector; import java.util.stream.Collectors; +import com.google.gson.internal.LinkedTreeMap; +import org.apache.solr.client.solrj.SolrQuery; +import org.apache.solr.client.solrj.response.QueryResponse; import org.apache.solr.common.SolrDocument; import org.springframework.data.domain.Pageable; @@ -19,7 +22,9 @@ public class GrebiSolrRepo { GrebiSolrClient solrClient = new GrebiSolrClient(); ResolverClient resolver = new ResolverClient(); - public GrebiSolrRepo() {} + public GrebiSolrRepo() { + } + public Set getSubgraphs() { @@ -29,7 +34,7 @@ public Set getSubgraphs() { var nodesCores = cores.stream().filter(core -> core.startsWith("grebi_nodes_")).map(core -> core.replace("grebi_nodes_", "")).collect(Collectors.toSet()); var edgesCores = cores.stream().filter(core -> core.startsWith("grebi_edges_")).map(core -> core.replace("grebi_edges_", "")).collect(Collectors.toSet()); - if(new HashSet<>(List.of(autocompleteCores, nodesCores, edgesCores)).size() != 1) { + if (new HashSet<>(List.of(autocompleteCores, nodesCores, edgesCores)).size() != 1) { throw new RuntimeException("autocomplete, nodes, and edges cores must be present for all subgraphs. Found cores: " + String.join(",", cores)); } @@ -40,45 +45,86 @@ public List autocomplete(String subgraph, String q) { return solrClient.autocomplete(subgraph, q); } - public GrebiFacetedResultsPage> searchNodesPaginated(String subgraph, GrebiSolrQuery query, Pageable pageable) { - return resolveNodeIds(subgraph, solrClient.searchSolrPaginated("grebi_nodes_"+subgraph, query, pageable)); + public GrebiFacetedResultsPage> searchNodesPaginated(String subgraph, GrebiSolrQuery query, Pageable pageable) { + return resolveNodeIds(subgraph, solrClient.searchSolrPaginated("grebi_nodes_" + subgraph, query, pageable)); } - public Map getFirstNode(String subgraph, GrebiSolrQuery query) { - return resolveNodeId(subgraph, solrClient.getFirst("grebi_nodes_"+subgraph, query)); + public Map getFirstNode(String subgraph, GrebiSolrQuery query) { + return resolveNodeId(subgraph, solrClient.getFirst("grebi_nodes_" + subgraph, query)); } - private GrebiFacetedResultsPage> resolveNodeIds(String subgraph, GrebiFacetedResultsPage solrDocs) { + private GrebiFacetedResultsPage> resolveNodeIds(String subgraph, GrebiFacetedResultsPage solrDocs) { List ids = solrDocs.map(doc -> doc.getFieldValue("grebi__nodeId").toString()).toList(); - List> vals = resolver.resolveToList(subgraph, ids); - assert(vals.size() == solrDocs.getSize()); + List> vals = resolver.resolveToList(subgraph, ids); + assert (vals.size() == solrDocs.getSize()); return new GrebiFacetedResultsPage<>(vals, solrDocs.facetFieldToCounts, solrDocs.getPageable(), solrDocs.getTotalElements()); } - private Map resolveNodeId(String subgraph, SolrDocument solrDoc) { + private Map resolveNodeId(String subgraph, SolrDocument solrDoc) { return resolver.resolveToList(subgraph, List.of(solrDoc.getFieldValue("grebi__nodeId").toString())).iterator().next(); } - private GrebiFacetedResultsPage> resolveEdgeIds(String subgraph, GrebiFacetedResultsPage solrDocs) { + private GrebiFacetedResultsPage> resolveEdgeIds(String subgraph, GrebiFacetedResultsPage solrDocs) { List ids = solrDocs.map(doc -> doc.getFieldValue("grebi__edgeId").toString()).toList(); - List> vals = resolver.resolveToList(subgraph, ids); - assert(vals.size() == solrDocs.getSize()); + List> vals = resolver.resolveToList(subgraph, ids); + assert (vals.size() == solrDocs.getSize()); return new GrebiFacetedResultsPage<>(vals, solrDocs.facetFieldToCounts, solrDocs.getPageable(), solrDocs.getTotalElements()); } - private Map resolveEdgeId(String subgraph, SolrDocument solrDoc) { + private Map resolveEdgeId(String subgraph, SolrDocument solrDoc) { return resolver.resolveToList(subgraph, List.of(solrDoc.getFieldValue("grebi__edgeId").toString())).iterator().next(); } - public GrebiFacetedResultsPage> searchEdgesPaginated(String subgraph, GrebiSolrQuery query, Pageable pageable) { - return resolveEdgeIds(subgraph, solrClient.searchSolrPaginated("grebi_edges_"+subgraph, query, pageable)); + public GrebiFacetedResultsPage> searchEdgesPaginated(String subgraph, GrebiSolrQuery query, Pageable pageable) { + return resolveEdgeIds(subgraph, solrClient.searchSolrPaginated("grebi_edges_" + subgraph, query, pageable)); + } + + public Map> getIncomingEdgeCounts(String subgraph, String nodeId) { + SolrQuery q = new SolrQuery(); + q.set("defType", "edismax"); + q.set("qf", "grebi__toNodeId"); + q.setQuery(nodeId); + q.addFacetPivotField("grebi__type,grebi__datasources"); + QueryResponse r = solrClient.runSolrQuery("grebi_edges_" + subgraph, q, Pageable.ofSize(1)); + return pivotsToMaps(r); + } + + public Map> getOutgoingEdgeCounts(String subgraph, String nodeId) { + SolrQuery q = new SolrQuery(); + q.set("defType", "edismax"); + q.set("qf", "grebi__fromNodeId"); + q.setQuery(nodeId); + q.addFacetPivotField("grebi__type,grebi__datasources"); + QueryResponse r = solrClient.runSolrQuery("grebi_edges_" + subgraph, q, Pageable.ofSize(1)); + return pivotsToMaps(r); } + private Map> pivotsToMaps(QueryResponse r) { + var pf = r.getFacetPivot().get("grebi__type,grebi__datasources"); + Map> res = new LinkedTreeMap<>(); + for (var f : pf) { + String type = (String) f.getValue(); + for (var pivot : f.getPivot()) { + String datasource = (String) pivot.getValue(); + int count = pivot.getCount(); + var dsToCount = res.get(type); + if (dsToCount == null) { + dsToCount = new LinkedTreeMap<>(); + res.put(type, dsToCount); + } + dsToCount.put(datasource, count); + } + } + return res; + } + + + } diff --git a/webapp/grebi_ui/dev_server.mjs b/webapp/grebi_ui/dev_server.mjs index a0c09e0..eff4329 100644 --- a/webapp/grebi_ui/dev_server.mjs +++ b/webapp/grebi_ui/dev_server.mjs @@ -16,6 +16,7 @@ if(process.env.GREBI_DEV_BACKEND_PROXY_URL === undefined) { server.use(/^\/api.*/, async (req, res) => { let backendUrl = urlJoin(process.env.GREBI_DEV_BACKEND_PROXY_URL, req.originalUrl) console.log('forwarding api request to: ' + backendUrl) + console.time('forwarding api request to: ' + backendUrl) try { let apiResponse = await fetch(backendUrl, { redirect: 'follow', @@ -25,6 +26,7 @@ server.use(/^\/api.*/, async (req, res) => { res.header('content-type', apiResponse.headers.get('content-type')) res.status(apiResponse.status) apiResponse.body.pipe(res) + console.timeEnd('forwarding api request to: ' + backendUrl) } catch(e) { console.log(e) } diff --git a/webapp/grebi_ui/src/components/SearchBox.tsx b/webapp/grebi_ui/src/components/SearchBox.tsx index 14c1c44..33717e0 100644 --- a/webapp/grebi_ui/src/components/SearchBox.tsx +++ b/webapp/grebi_ui/src/components/SearchBox.tsx @@ -127,7 +127,9 @@ export default function SearchBox({ (text, i): SearchBoxEntry => { searchParams.set("q", text); if (collectionId) searchParams.set("collection", collectionId); - const linkUrl = `/subgraphs/${subgraph}/search?${new URLSearchParams(searchParams)}`; + + var linkUrl = process.env.GREBI_FRONTEND === 'exposomekg' ? `/search?${new URLSearchParams(searchParams)}` : `/subgraphs/${subgraph}/search?${new URLSearchParams(searchParams)}`; + return { linkUrl, li: ( @@ -238,7 +240,9 @@ export default function SearchBox({ } else if (query) { searchParams.set("q", query); if (collectionId) searchParams.set("collection", collectionId); - navigate(`/subgraphs/${subgraph}/search?${new URLSearchParams(searchParams)}`); + + var linkUrl = process.env.GREBI_FRONTEND === 'exposomekg' ? `/search?${new URLSearchParams(searchParams)}` : `/subgraphs/${subgraph}/search?${new URLSearchParams(searchParams)}`; + navigate(linkUrl); } } else if (ev.key === "ArrowDown") { setArrowKeySelectedN( @@ -292,7 +296,9 @@ export default function SearchBox({ searchParams.set("q", query); if (collectionId) searchParams.set("collection", collectionId); - navigate(`/subgraphs/${subgraph}/search?${new URLSearchParams(searchParams)}`); + + var linkUrl = process.env.GREBI_FRONTEND === 'exposomekg' ? `/search?${new URLSearchParams(searchParams)}` : `/subgraphs/${subgraph}/search?${new URLSearchParams(searchParams)}`; + navigate(linkUrl); } }} > @@ -309,7 +315,9 @@ export default function SearchBox({ if (query) { searchParams.set("q", query); if (collectionId) searchParams.set("collection", collectionId); - navigate(`/subgraphs/${subgraph}/search?${new URLSearchParams(searchParams)}`); + + var linkUrl = process.env.GREBI_FRONTEND === 'exposomekg' ? `/search?${new URLSearchParams(searchParams)}` : `/subgraphs/${subgraph}/search?${new URLSearchParams(searchParams)}`; + navigate(linkUrl); } }} > diff --git a/webapp/grebi_ui/src/components/exposomekg/ExposureLinks.tsx b/webapp/grebi_ui/src/components/exposomekg/ExposureLinks.tsx index e9db24e..4ee8ec5 100644 --- a/webapp/grebi_ui/src/components/exposomekg/ExposureLinks.tsx +++ b/webapp/grebi_ui/src/components/exposomekg/ExposureLinks.tsx @@ -37,16 +37,21 @@ export default function ExposureLinks({node}:{node:GraphNode}) { setSearchParams({linksTab:tab})}> Source IDs +
+ {/* */} + Source IDs
} value={"sourceids"} className="grebi-subtab" /> {linksTabs.map(tab => {tab.tabName} - } value={tab.tabId} className="text-black" />)} +
+ {/* */} + {tab.tabName}
+ } value={tab.tabId} className="grebi-subtab" />)}
- + - + {node.getSourceIds().map(id =>
{id.value}