Skip to content

Commit

Permalink
exposomekg ui: edges, graph view, prop view
Browse files Browse the repository at this point in the history
  • Loading branch information
jamesamcl committed Dec 8, 2024
1 parent 43edf21 commit 9ef3569
Show file tree
Hide file tree
Showing 12 changed files with 166 additions and 91 deletions.
11 changes: 11 additions & 0 deletions webapp/grebi_api/src/main/java/uk/ac/ebi/grebi/GrebiApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -19,7 +22,9 @@ public class GrebiSolrRepo {
GrebiSolrClient solrClient = new GrebiSolrClient();
ResolverClient resolver = new ResolverClient();

public GrebiSolrRepo() {}
public GrebiSolrRepo() {
}


public Set<String> getSubgraphs() {

Expand All @@ -29,7 +34,7 @@ public Set<String> 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));
}

Expand All @@ -40,45 +45,86 @@ public List<String> autocomplete(String subgraph, String q) {
return solrClient.autocomplete(subgraph, q);
}

public GrebiFacetedResultsPage<Map<String,Object>> searchNodesPaginated(String subgraph, GrebiSolrQuery query, Pageable pageable) {
return resolveNodeIds(subgraph, solrClient.searchSolrPaginated("grebi_nodes_"+subgraph, query, pageable));
public GrebiFacetedResultsPage<Map<String, Object>> searchNodesPaginated(String subgraph, GrebiSolrQuery query, Pageable pageable) {
return resolveNodeIds(subgraph, solrClient.searchSolrPaginated("grebi_nodes_" + subgraph, query, pageable));
}

public Map<String,Object> getFirstNode(String subgraph, GrebiSolrQuery query) {
return resolveNodeId(subgraph, solrClient.getFirst("grebi_nodes_"+subgraph, query));
public Map<String, Object> getFirstNode(String subgraph, GrebiSolrQuery query) {
return resolveNodeId(subgraph, solrClient.getFirst("grebi_nodes_" + subgraph, query));
}

private GrebiFacetedResultsPage<Map<String,Object>> resolveNodeIds(String subgraph, GrebiFacetedResultsPage<SolrDocument> solrDocs) {
private GrebiFacetedResultsPage<Map<String, Object>> resolveNodeIds(String subgraph, GrebiFacetedResultsPage<SolrDocument> solrDocs) {

List<String> ids = solrDocs.map(doc -> doc.getFieldValue("grebi__nodeId").toString()).toList();

List<Map<String,Object>> vals = resolver.resolveToList(subgraph, ids);
assert(vals.size() == solrDocs.getSize());
List<Map<String, Object>> vals = resolver.resolveToList(subgraph, ids);
assert (vals.size() == solrDocs.getSize());

return new GrebiFacetedResultsPage<>(vals, solrDocs.facetFieldToCounts, solrDocs.getPageable(), solrDocs.getTotalElements());
}

private Map<String,Object> resolveNodeId(String subgraph, SolrDocument solrDoc) {
private Map<String, Object> resolveNodeId(String subgraph, SolrDocument solrDoc) {
return resolver.resolveToList(subgraph, List.of(solrDoc.getFieldValue("grebi__nodeId").toString())).iterator().next();
}

private GrebiFacetedResultsPage<Map<String,Object>> resolveEdgeIds(String subgraph, GrebiFacetedResultsPage<SolrDocument> solrDocs) {
private GrebiFacetedResultsPage<Map<String, Object>> resolveEdgeIds(String subgraph, GrebiFacetedResultsPage<SolrDocument> solrDocs) {

List<String> ids = solrDocs.map(doc -> doc.getFieldValue("grebi__edgeId").toString()).toList();

List<Map<String,Object>> vals = resolver.resolveToList(subgraph, ids);
assert(vals.size() == solrDocs.getSize());
List<Map<String, Object>> vals = resolver.resolveToList(subgraph, ids);
assert (vals.size() == solrDocs.getSize());

return new GrebiFacetedResultsPage<>(vals, solrDocs.facetFieldToCounts, solrDocs.getPageable(), solrDocs.getTotalElements());
}

private Map<String,Object> resolveEdgeId(String subgraph, SolrDocument solrDoc) {
private Map<String, Object> resolveEdgeId(String subgraph, SolrDocument solrDoc) {

return resolver.resolveToList(subgraph, List.of(solrDoc.getFieldValue("grebi__edgeId").toString())).iterator().next();
}

public GrebiFacetedResultsPage<Map<String,Object>> searchEdgesPaginated(String subgraph, GrebiSolrQuery query, Pageable pageable) {
return resolveEdgeIds(subgraph, solrClient.searchSolrPaginated("grebi_edges_"+subgraph, query, pageable));
public GrebiFacetedResultsPage<Map<String, Object>> searchEdgesPaginated(String subgraph, GrebiSolrQuery query, Pageable pageable) {
return resolveEdgeIds(subgraph, solrClient.searchSolrPaginated("grebi_edges_" + subgraph, query, pageable));
}

public Map<String, Map<String, Integer>> 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<String, Map<String, Integer>> 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<String, Map<String, Integer>> pivotsToMaps(QueryResponse r) {
var pf = r.getFacetPivot().get("grebi__type,grebi__datasources");
Map<String, Map<String, Integer>> 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;
}



}
2 changes: 2 additions & 0 deletions webapp/grebi_ui/dev_server.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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)
}
Expand Down
16 changes: 12 additions & 4 deletions webapp/grebi_ui/src/components/SearchBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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: (
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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);
}
}}
>
Expand All @@ -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);
}
}}
>
Expand Down
46 changes: 22 additions & 24 deletions webapp/grebi_ui/src/components/exposomekg/ExposureLinks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,21 @@ export default function ExposureLinks({node}:{node:GraphNode}) {
<Grid item xs={2} className="py-0">
<Tabs orientation="horizontal" value={linksTab} className="bg-gray-100 border-black justify-center rounded-lg" sx={{ borderBottom: 1, borderColor: 'divider' }} onChange={(e, tab) => setSearchParams({linksTab:tab})}>
<Tab label={
<div><OpenInNew fontSize="small" style = { {verticalAlign : 'middle'} } /> Source IDs </div>
<div>
{/* <OpenInNew fontSize="small" style = { {verticalAlign : 'middle'} } /> */}
Source IDs </div>
} value={"sourceids"} className="grebi-subtab" />
{linksTabs.map(tab => <Tab label={
<div><OpenInNew fontSize="small" style = { {verticalAlign : 'middle'} } /> {tab.tabName} </div>
} value={tab.tabId} className="text-black" />)}
<div>
{/* <OpenInNew fontSize="small" style = { {verticalAlign : 'middle'} } /> */}
{tab.tabName} </div>
} value={tab.tabId} className="grebi-subtab" />)}
</Tabs>
</Grid>
<Grid item xs={10}>
<Grid item xs={10} >
<TabPanel value={linksTab} index={"sourceids"}>
<Grid container spacing={0.5} direction="row" alignItems={"left"} justifyContent={"left"} className="pb-5">
<Grid
container spacing={0.5} direction="row" alignItems={"left"} justifyContent={"left"} className="pb-5">
{node.getSourceIds().map(id => <Grid item>
<div className="bg-grey-default rounded-sm font-mono pl-1" style={{fontSize:'small'}}>
{id.value} <button onClick={() => { copyToClipboard(id.value); }} >
Expand All @@ -57,8 +62,14 @@ export default function ExposureLinks({node}:{node:GraphNode}) {
)}
</Grid>
</TabPanel>
</Grid>
</Grid>
{!linksTabs && <CircularProgress />}
{linksTabs && linksTabs.filter(tab => tab.tabId === 'chemical_gene_interactions').length > 0 &&
<TabPanel value={linksTab} index={"chemical_gene_interactions"}>
<GeneExposureLinks node={node} />
</TabPanel>
}
</Grid>
</Grid>
}


Expand Down Expand Up @@ -105,17 +116,11 @@ function GeneExposureLinks({node}:{node:GraphNode}) {

}, [node.getNodeId()])

if(!affectedBy) {
return <CircularProgress/>
}

return <div>
<ExpandableSection title={
affectedBy ?
`Chemical interactions (${affectedBy.totalElements})`
:
<Fragment>Chemical interactions <i color="gray">(Loading...)</i></Fragment>
} loading={!affectedBy}>

{affectedBy &&
<LocalDataTable
return <LocalDataTable
data={affectedBy?.elements}
addColumnsFromData={true}
columns={fixedCols}
Expand All @@ -133,13 +138,6 @@ function GeneExposureLinks({node}:{node:GraphNode}) {
"to"
]}
/>
}

</ExpandableSection>
</div>



}

function ChemicalExposureLinks({node}:{node:GraphNode}) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export default async function getExposureLinksTabs(node:GraphNode):Promise<Links
async function getGeneLinksTabs(node:GraphNode) {

let page = await (getPaginated<any>(`api/v1/subgraphs/${node.getSubgraph()}/nodes/${encodeNodeId(node.getNodeId())}/incoming_edges`, {
'size': "0",
'size': "1",
'grebi:type': 'biolink:chemical_gene_interaction_association'
}));

Expand Down
Loading

0 comments on commit 9ef3569

Please sign in to comment.