Skip to content

Commit d11dbe0

Browse files
committed
search implementation/fixes
1 parent 4baf710 commit d11dbe0

19 files changed

+171
-117
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,5 @@ graph.db
1818
*.txt
1919
*.swp
2020
.nextflow*
21+
work
2122

grebi_api/src/main/java/uk/ac/ebi/grebi/GrebiApi.java

+13-7
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public static void main(String[] args) throws ParseException, org.apache.commons
4747
ctx.result("{}");
4848

4949
var q = new GrebiSolrQuery();
50-
q.addFilter("grebi__nodeId", List.of(ctx.pathParam("nodeId")), SearchType.WHOLE_FIELD);
50+
q.addFilter("grebi:nodeId", List.of(ctx.pathParam("nodeId")), SearchType.WHOLE_FIELD, false);
5151

5252
var res = solr.getFirstNode(q);
5353

@@ -71,12 +71,15 @@ public static void main(String[] args) throws ParseException, org.apache.commons
7171
q.setSearchText(ctx.queryParam("q"));
7272
q.setExactMatch(false);
7373
q.addSearchField("id", 1000, SearchType.WHOLE_FIELD);
74-
q.addSearchField("grebi__synonym", 900, SearchType.WHOLE_FIELD);
74+
q.addSearchField("grebi:name", 900, SearchType.WHOLE_FIELD);
75+
q.addSearchField("grebi:synonym", 800, SearchType.WHOLE_FIELD);
7576
q.addSearchField("id", 500, SearchType.CASE_INSENSITIVE_TOKENS);
76-
q.addSearchField("grebi__synonym", 450, SearchType.CASE_INSENSITIVE_TOKENS);
77-
q.addSearchField("grebi__description", 400, SearchType.WHOLE_FIELD);
78-
q.addSearchField("grebi__description", 250, SearchType.CASE_INSENSITIVE_TOKENS);
77+
q.addSearchField("grebi:name", 450, SearchType.CASE_INSENSITIVE_TOKENS);
78+
q.addSearchField("grebi:synonym", 420, SearchType.CASE_INSENSITIVE_TOKENS);
79+
q.addSearchField("grebi:description", 400, SearchType.WHOLE_FIELD);
80+
q.addSearchField("grebi:description", 250, SearchType.CASE_INSENSITIVE_TOKENS);
7981
q.addSearchField("_text_", 1, SearchType.CASE_INSENSITIVE_TOKENS);
82+
q.addFilter("ols:isObsolete", Set.of("true"), SearchType.WHOLE_FIELD, true);
8083
for(var param : ctx.queryParamMap().entrySet()) {
8184
if(param.getKey().equals("q") ||
8285
param.getKey().equals("page") ||
@@ -88,15 +91,18 @@ public static void main(String[] args) throws ParseException, org.apache.commons
8891
) {
8992
continue;
9093
}
91-
q.addFilter(param.getKey(), param.getValue(), SearchType.WHOLE_FIELD);
94+
q.addFilter(param.getKey(), param.getValue(), SearchType.WHOLE_FIELD, false);
95+
}
96+
for(var facetField : ctx.queryParams("facet")) {
97+
q.addFacetField(facetField);
9298
}
9399
var page_num = ctx.queryParam("page");
94100
if(page_num == null) {
95101
page_num = "0";
96102
}
97103
var size = ctx.queryParam("size");
98104
if(size == null) {
99-
size = "100";
105+
size = "10";
100106
}
101107
var page = PageRequest.of(Integer.parseInt(page_num), Integer.parseInt(size));
102108
var res = solr.searchNodesPaginated(q, page);

grebi_api/src/main/java/uk/ac/ebi/grebi/db/GrebiSolrClient.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public GrebiFacetedResultsPage<SolrDocument> searchSolrPaginated(GrebiSolrQuery
5151
valueToCount.put(count.getName(), count.getCount());
5252
}
5353

54-
facetFieldToCounts.put(facetField.getName(), valueToCount);
54+
facetFieldToCounts.put(facetField.getName().replace("__", ":"), valueToCount);
5555
}
5656
}
5757

grebi_api/src/main/java/uk/ac/ebi/grebi/db/GrebiSolrQuery.java

+13-6
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ public void addFacetField(String propertyName) {
4545
this.facetFields.add(propertyName);
4646
}
4747

48-
public void addFilter(String propertyName, Collection<String> propertyValues, SearchType searchType) {
49-
this.filters.add(new Filter(propertyName, propertyValues, searchType));
48+
public void addFilter(String propertyName, Collection<String> propertyValues, SearchType searchType, boolean negate) {
49+
this.filters.add(new Filter(propertyName, propertyValues, searchType, negate));
5050
}
5151

5252
public SolrQuery constructQuery() {
@@ -107,6 +107,10 @@ public SolrQuery constructQuery() {
107107
fq.append("{!tag=grebifacet}");
108108
}
109109

110+
if(f.negate) {
111+
fq.append("-");
112+
}
113+
110114
fq.append( ClientUtils.escapeQueryChars(getSolrPropertyName(f.propertyName, f.searchType)) );
111115
fq.append(":(");
112116

@@ -127,8 +131,9 @@ public SolrQuery constructQuery() {
127131

128132
if(facetFields.size() > 0) {
129133
for(String facetField : facetFields) {
130-
query.addFacetField("{!ex=grebifacet}" + facetField);
134+
query.addFacetField("{!ex=grebifacet}" + facetField.replace(":", "__"));
131135
}
136+
query.setFacetMinCount(1);
132137
}
133138

134139
return query;
@@ -139,11 +144,13 @@ private class Filter {
139144
String propertyName;
140145
Collection<String> propertyValues; // all values to search for ("OR")
141146
SearchType searchType;
147+
boolean negate;
142148

143-
public Filter(String propertyName, Collection<String> propertyValues, SearchType searchType) {
149+
public Filter(String propertyName, Collection<String> propertyValues, SearchType searchType, boolean negate) {
144150
this.propertyName = propertyName;
145151
this.propertyValues = propertyValues;
146152
this.searchType = searchType;
153+
this.negate = negate;
147154
}
148155
}
149156

@@ -180,9 +187,9 @@ public BoostField(String propertyName, String propertyValue, int weight, SearchT
180187
private String getSolrPropertyName(String propertyName, SearchType searchType) {
181188
switch(searchType) {
182189
case CASE_INSENSITIVE_TOKENS:
183-
return propertyName;
190+
return propertyName.replace(":", "__");
184191
case WHOLE_FIELD:
185-
return "str_" + propertyName;
192+
return "str_" + propertyName.replace(":", "__");
186193
default:
187194
throw new RuntimeException("unknown filter accuracy");
188195
}

grebi_api/src/main/java/uk/ac/ebi/grebi/db/ResolverClient.java

+11-1
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,11 @@
88
import com.google.gson.JsonElement;
99
import java.util.List;
1010
import java.util.Map;
11+
import java.util.stream.Collectors;
12+
1113
import com.google.gson.JsonElement;
1214
import com.google.gson.Gson;
15+
import com.google.gson.internal.LinkedTreeMap;
1316
import org.apache.http.HttpEntity;
1417
import org.apache.http.HttpResponse;
1518
import org.apache.http.client.HttpClient;
@@ -31,7 +34,7 @@ public static String getResolverHost() {
3134
return "http://localhost:8080/";
3235
}
3336

34-
public Map<String, Map<String,JsonElement>> resolve(Collection<String> ids) {
37+
public Map<String, Map<String,JsonElement>> resolveToMap(Collection<String> ids) {
3538

3639
Stopwatch timer = Stopwatch.createStarted();
3740

@@ -65,4 +68,11 @@ public Map<String, Map<String,JsonElement>> resolve(Collection<String> ids) {
6568

6669
return null;
6770
}
71+
72+
public List<Map<String, JsonElement>> resolveToList(Collection<String> ids) {
73+
74+
var resolved = resolveToMap(ids);
75+
76+
return ids.stream().map(id -> resolved.get(id)).collect(Collectors.toList());
77+
}
6878
}

grebi_api/src/main/java/uk/ac/ebi/grebi/repo/GrebiNeoRepo.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ public List<EdgeAndNode> getIncomingEdges(String nodeId) {
6565
.withParameters(Map.of("nodeId", nodeId))
6666
.withConfig(QueryConfig.builder().withDatabase("neo4j").build()).execute();
6767

68-
var resolved = resolver.resolve(
68+
var resolved = resolver.resolveToMap(
6969
res.records().stream().flatMap(record -> {
7070
var props = record.asMap();
7171
return List.of((String) props.get("otherId"), (String) props.get("edgeId")).stream();

grebi_api/src/main/java/uk/ac/ebi/grebi/repo/GrebiSolrRepo.java

+4-13
Original file line numberDiff line numberDiff line change
@@ -38,38 +38,29 @@ private GrebiFacetedResultsPage<Map<String,JsonElement>> resolveNodeIds(GrebiFac
3838

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

41-
Map<String,Map<String,JsonElement>> idToEntity = resolver.resolve(ids);
42-
43-
List<Map<String,JsonElement>> vals = idToEntity.values().stream().collect(Collectors.toList());
41+
List<Map<String,JsonElement>> vals = resolver.resolveToList(ids);
4442
assert(vals.size() == solrDocs.getSize());
4543

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

4947
private Map<String,JsonElement> resolveNodeId(SolrDocument solrDoc) {
50-
51-
Map<String,Map<String,JsonElement>> idToEntity = resolver.resolve(List.of(solrDoc.getFieldValue("grebi__nodeId").toString()));
52-
53-
return idToEntity.values().iterator().next();
48+
return resolver.resolveToList(List.of(solrDoc.getFieldValue("grebi__nodeId").toString())).iterator().next();
5449
}
5550

5651
private GrebiFacetedResultsPage<Map<String,JsonElement>> resolveEdgeIds(GrebiFacetedResultsPage<SolrDocument> solrDocs) {
5752

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

60-
Map<String, Map<String,JsonElement>> idToEntity = resolver.resolve(ids);
61-
62-
List<Map<String,JsonElement>> vals = idToEntity.values().stream().collect(Collectors.toList());
55+
List<Map<String,JsonElement>> vals = resolver.resolveToList(ids);
6356
assert(vals.size() == solrDocs.getSize());
6457

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

6861
private Map<String,JsonElement> resolveEdgeId(SolrDocument solrDoc) {
6962

70-
Map<String,Map<String,JsonElement>> idToEntity = resolver.resolve(List.of(solrDoc.getFieldValue("grebi__edgeId").toString()));
71-
72-
return idToEntity.values().iterator().next();
63+
return resolver.resolveToList(List.of(solrDoc.getFieldValue("grebi__edgeId").toString())).iterator().next();
7364
}
7465

7566
}

grebi_ui/src/App.tsx

+18
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,26 @@ import Search from "./pages/search/Search";
1313
import NodePage from "./pages/node/NodePage";
1414
import DownloadsPage from "./pages/downloads/Downloads";
1515

16+
17+
import MuiThemeProvider from '@mui/styles/ThemeProvider'
18+
import createTheme from '@mui/material/styles/createTheme'
19+
20+
const theme = createTheme({
21+
palette: {
22+
primary: {
23+
main: '#ff0000',
24+
},
25+
secondary: {
26+
main: '#ff0000',
27+
}
28+
}
29+
});
30+
31+
1632
class App extends React.Component {
1733
render() {
1834
return (
35+
<MuiThemeProvider theme={theme}>
1936
<Fragment>
2037
<Helmet>
2138
<meta charSet="utf-8" />
@@ -36,6 +53,7 @@ class App extends React.Component {
3653
{/* <Footer /> */}
3754
</BrowserRouter>
3855
</Fragment>
56+
</MuiThemeProvider>
3957
);
4058
}
4159
}

grebi_ui/src/app/api.ts

+17-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,20 @@
11

2-
type ReqParams = {[k:string]:string}|undefined
2+
type ReqParams = {[k:string]:(string|string[])}|undefined
3+
4+
function buildSearchParams(reqParams:ReqParams):string {
5+
let params = new URLSearchParams()
6+
for(let key in reqParams) {
7+
let val = reqParams[key]
8+
if(Array.isArray(val)) {
9+
for(let v of val) {
10+
params.append(key, v)
11+
}
12+
} else {
13+
params.append(key, val)
14+
}
15+
}
16+
return params.toString()
17+
}
318

419
export async function request(
520
path: string,
@@ -9,7 +24,7 @@ export async function request(
924
): Promise<any> {
1025
const url = (apiUrl || process.env.REACT_APP_APIURL) + path;
1126
//const res = await fetch(url.replace(/([^:]\/)\/+/g, "$1"), {
12-
const res = await fetch(url + (reqParams ? ('?' + new URLSearchParams(Object.entries(reqParams)).toString()) : ''), {
27+
const res = await fetch(url + (reqParams ? ('?' + buildSearchParams(reqParams)) : ''), {
1328
...(init ? init : {}),
1429
//headers: { ...(init?.headers || {}), ...getAuthHeaders() }
1530
});

grebi_ui/src/components/SearchBox.tsx

+2-3
Original file line numberDiff line numberDiff line change
@@ -147,9 +147,8 @@ export default function SearchBox({
147147
.map((entry: GraphNode, i: number): SearchBoxEntry => {
148148
let name = entry.getName();
149149
let type = entry.extractType()?.short
150-
const linkUrl = `/nodes/${encodeNodeId(entry.getNodeId())}`;
151150
return {
152-
linkUrl,
151+
linkUrl: entry.getLinkUrl(),
153152
li: (
154153
<li
155154
key={randomString()}
@@ -164,7 +163,7 @@ export default function SearchBox({
164163
onClick={() => {
165164
setQuery("");
166165
}}
167-
to={linkUrl}
166+
to={entry.getLinkUrl()}
168167
>
169168
<div className="flex justify-between">
170169

grebi_ui/src/model/GraphNode.ts

+41-8
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { pickBestDisplayName } from "../app/util";
2+
import encodeNodeId from "../encodeNodeId";
23
import PropVal from "./PropVal";
34
import Refs from "./Refs";
45

@@ -14,22 +15,30 @@ export default class GraphNode {
1415
return PropVal.arrFrom(this.props['grebi:name'] || []);
1516
}
1617

18+
getSynonyms():PropVal[] {
19+
return PropVal.arrFrom(this.props['grebi:synonym'] || []);
20+
}
21+
1722
getName():string {
1823
return pickBestDisplayName(this.getNames().map(n => n.value)) || this.getId().value
1924
}
2025

2126
getDescriptions():PropVal[] {
22-
return PropVal.arrFrom(this.props['rdfs:comment'])
27+
return PropVal.arrFrom(this.props['grebi:description'])
2328
}
2429

25-
getDescription():PropVal|undefined {
26-
return PropVal.arrFrom(this.getDescriptions())[0]
30+
getDescription():string|undefined {
31+
return PropVal.arrFrom(this.getDescriptions())[0]?.value
2732
}
2833

2934
getNodeId():string {
3035
return this.props['grebi:nodeId']
3136
}
3237

38+
getLinkUrl():string {
39+
return `/nodes/${encodeNodeId(this.getNodeId())}`;
40+
}
41+
3342
getId():PropVal {
3443
if(this.props['ols:curie']){
3544
return PropVal.arrFrom(this.props['ols:curie'])[0]
@@ -62,6 +71,9 @@ export default class GraphNode {
6271
}
6372
if(types.indexOf('ols:Class') !== -1) {
6473
let ancestors:any[] = PropVal.arrFrom(this.props['ols:directAncestor']).map(a => a.value)
74+
if(ancestors.indexOf("chebi:36080") !== -1) {
75+
return {long:'Protein',short:'Protein'}
76+
}
6577
if(ancestors.indexOf("chebi:24431") !== -1) {
6678
return {long:'Chemical',short:'Chemical'}
6779
}
@@ -83,8 +95,13 @@ export default class GraphNode {
8395
return this.props['grebi:datasources'] || []
8496
}
8597

86-
isBold() { // idk yet
87-
return false
98+
isBoldForQuery(q:string) {
99+
let allIdentifiers = [
100+
...this.getNames().map(p => p.value),
101+
...this.getSynonyms().map(p => p.value),
102+
...this.getIds().map(p => p.value),
103+
];
104+
return allIdentifiers.indexOf(q) !== -1
88105
}
89106

90107
isDeprecated() {
@@ -97,12 +114,28 @@ export default class GraphNode {
97114

98115
getProps():{[key:string]:PropVal[]} {
99116
let res_props = {}
100-
for(let k of Object.keys(this.props)) {
101-
if( (k.startsWith('grebi:') || k === '_refs') && k !== 'grebi:type') {
117+
let keys = Object.keys(this.props)
118+
119+
let sortOrder = [
120+
'grebi:name',
121+
'grebi:synonym',
122+
'grebi:description',
123+
'grebi:type'
124+
].filter(k => {
125+
if(keys.indexOf(k) !== -1) {
126+
keys.splice(keys.indexOf(k), 1)
127+
return true
128+
}
129+
})
130+
keys = sortOrder.concat(keys)
131+
for(let k of keys) {
132+
//if( (k.startsWith('grebi:') || k === '_refs') && k !== 'grebi:type') {
133+
if(k === '_refs') {
102134
continue;
103135
}
104136
res_props[k] = PropVal.arrFrom(this.props[k])
105137
}
106138
return res_props
107139
}
108-
}
140+
}
141+

0 commit comments

Comments
 (0)