Skip to content

Commit e0c2733

Browse files
committed
Merge branch 'staging' into feat-chatgptep
2 parents f0603aa + 1416c7d commit e0c2733

File tree

5 files changed

+105
-53
lines changed

5 files changed

+105
-53
lines changed

src/pages/Tutorial.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export default function Tutorial() {
3636
<em>2 3 7 8-tetrachlorodibenzo-p-dioxin -&gt; associated with -&gt; disease</em>
3737
</p>
3838
<p>
39-
In the ROBOKOP user interface (UI), a user either enters <code>2,3,7,8-tetrachlorodibenzo-P-dioxin</code> or the equivalent CURIE (UMLS:C003965) in the first node (n0). If the autocomplete dropdown menu cannot identify an exact text match, then a user can try simplifying the entry by, for example, not specifying a specific isomer and entering <code>tetrachlorodibenzo-p-dioxin</code>.
39+
In the ROBOKOP user interface (UI), a user either enters <code>2,3,7,8-tetrachlorodibenzo-P-dioxin</code> or the equivalent CURIE (PUBCHEM.COMPOUND:15625) in the first node (n0). If the autocomplete dropdown menu cannot identify an exact text match, then a user can try simplifying the entry by, for example, not specifying a specific isomer and entering <code>tetrachlorodibenzo-p-dioxin</code>.
4040
</p>
4141
<p>
4242
Note that users can lookup CURIEs by name using the <a href="https://name-resolution-sri.renci.org/docs#/" target="_blank" rel="noreferrer">Translator Name Resolver service</a>, which is a service that was created by the <a href="https://ncats.nih.gov/translator/about" target="_blank" rel="noopener noreferrer">Biomedical Data Translator Consortium</a>, funded by the National Center for Advancing Translational Sciences.

src/pages/answer/useAnswerStore.js

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -120,30 +120,33 @@ export default function useAnswerStore() {
120120
const edges = {};
121121
const edgesJSON = {};
122122
row.analyses.forEach((analysis) => {
123-
Object.values(analysis.edge_bindings).forEach((edgeBindings) => {
124-
edgeBindings.forEach((binding) => {
125-
const kgEdge = message.knowledge_graph.edges[binding.id];
126-
edgesJSON[binding.id] = kgEdge || 'Unknown';
127-
if (kgEdge) {
128-
const graphEdge = {
129-
id: binding.id,
130-
source: kgEdge.subject,
131-
target: kgEdge.object,
132-
predicate: kgEdge.predicate,
133-
};
134-
edges[binding.id] = graphEdge;
135-
if (kgEdge.subject in nodes) {
136-
nodes[kgEdge.subject].score += 1;
137-
}
138-
if (kgEdge.object in nodes) {
139-
nodes[kgEdge.object].score += 1;
140-
}
141-
const subjectNode = message.knowledge_graph.nodes[kgEdge.subject];
142-
const objectNode = message.knowledge_graph.nodes[kgEdge.object];
143-
const edgeKey = `${subjectNode.name || kgEdge.subject} ${stringUtils.displayPredicate(kgEdge.predicate)} ${objectNode.name || kgEdge.object}`;
144-
publications[edgeKey] = resultsUtils.getPublications(kgEdge);
123+
const edge_bindings = Object.values(analysis.edge_bindings).flat();
124+
const support_graph_edge_bindings = analysis.support_graphs.reduce((acc, support_graph_id) => (
125+
[...acc, ...message.auxiliary_graphs[support_graph_id].edges.map((e) => ({ id: e }))]
126+
), []);
127+
128+
[...edge_bindings, ...support_graph_edge_bindings].forEach((binding) => {
129+
const kgEdge = message.knowledge_graph.edges[binding.id];
130+
edgesJSON[binding.id] = kgEdge || 'Unknown';
131+
if (kgEdge) {
132+
const graphEdge = {
133+
id: binding.id,
134+
source: kgEdge.subject,
135+
target: kgEdge.object,
136+
predicate: kgEdge.predicate,
137+
};
138+
edges[binding.id] = graphEdge;
139+
if (kgEdge.subject in nodes) {
140+
nodes[kgEdge.subject].score += 1;
141+
}
142+
if (kgEdge.object in nodes) {
143+
nodes[kgEdge.object].score += 1;
145144
}
146-
});
145+
const subjectNode = message.knowledge_graph.nodes[kgEdge.subject];
146+
const objectNode = message.knowledge_graph.nodes[kgEdge.object];
147+
const edgeKey = `${subjectNode.name || kgEdge.subject} ${stringUtils.displayPredicate(kgEdge.predicate)} ${objectNode.name || kgEdge.object}`;
148+
publications[edgeKey] = resultsUtils.getPublications(kgEdge);
149+
}
147150
});
148151
});
149152
setSelectedResult({ nodes, edges });

src/pages/queryBuilder/textEditor/textEditorRow/NodeSelector.jsx

Lines changed: 69 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ import axios from 'axios';
55
import Autocomplete from '@material-ui/lab/Autocomplete';
66
import TextField from '@material-ui/core/TextField';
77
import CircularProgress from '@material-ui/core/CircularProgress';
8+
import IconButton from '@material-ui/core/IconButton';
9+
import FileCopy from '@material-ui/icons/FileCopy';
10+
import Check from '@material-ui/icons/Check';
11+
import Tooltip from '@material-ui/core/Tooltip';
12+
import { withStyles } from '@material-ui/core';
813

914
import AlertContext from '~/context/alert';
1015
import BiolinkContext from '~/context/biolink';
@@ -98,14 +103,13 @@ export default function NodeSelector({
98103
if (includeCuries) {
99104
if (searchTerm.includes(':')) { // user is typing a specific curie
100105
newOptions.push({ name: searchTerm, ids: [searchTerm] });
101-
} else {
102-
if (cancel) {
103-
cancel.cancel();
104-
}
105-
cancel = CancelToken.source();
106-
const curies = await fetchCuries(searchTerm, displayAlert, cancel.token);
107-
newOptions.push(...curies);
108106
}
107+
if (cancel) {
108+
cancel.cancel();
109+
}
110+
cancel = CancelToken.source();
111+
const curies = await fetchCuries(searchTerm, displayAlert, cancel.token);
112+
newOptions.push(...curies);
109113
}
110114
toggleLoading(false);
111115
setOptions(newOptions);
@@ -204,6 +208,7 @@ export default function NodeSelector({
204208
onOpen={() => toggleOpen(true)}
205209
onClose={() => toggleOpen(false)}
206210
onInputChange={(e, v) => updateInputText(v)}
211+
renderOption={(props) => <Option {...props} />}
207212
renderInput={(params) => (
208213
<TextField
209214
{...params}
@@ -237,3 +242,60 @@ export default function NodeSelector({
237242
/>
238243
);
239244
}
245+
246+
const CustomTooltip = withStyles((theme) => ({
247+
tooltip: {
248+
fontSize: theme.typography.pxToRem(14),
249+
},
250+
}))(Tooltip);
251+
252+
function Option({ name, ids, categories }) {
253+
return (
254+
<CustomTooltip
255+
interactive
256+
arrow
257+
title={(
258+
<div className="node-option-tooltip-wrapper">
259+
{Array.isArray(ids) && ids.length > 0 && (
260+
<div>
261+
<span>{ids[0]}</span>
262+
<CopyButton textToCopy={ids[0]} />
263+
</div>
264+
)}
265+
{Array.isArray(categories) && categories.length > 0 && (
266+
<span>{categories[0]}</span>
267+
)}
268+
</div>
269+
)}
270+
placement="left"
271+
>
272+
<div>
273+
{ name }
274+
</div>
275+
</CustomTooltip>
276+
);
277+
}
278+
279+
function CopyButton({ textToCopy }) {
280+
const [hasCopied, setHasCopied] = useState(false);
281+
282+
const handleCopy = (e) => {
283+
e.stopPropagation();
284+
navigator.clipboard.writeText(textToCopy);
285+
setHasCopied(true);
286+
};
287+
288+
if (
289+
navigator.clipboard === 'undefined' ||
290+
typeof navigator.clipboard.writeText !== 'function' ||
291+
typeof textToCopy !== 'string'
292+
) {
293+
return null;
294+
}
295+
296+
return (
297+
<IconButton color="inherit" size="small" onClick={handleCopy}>
298+
{ hasCopied ? <Check /> : <FileCopy /> }
299+
</IconButton>
300+
);
301+
}

src/pages/queryBuilder/textEditor/textEditorRow/textEditorRow.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,9 @@
44
}
55
.referenceNode > .nodeDropdown {
66
background-color: #f2f2f2;
7+
}
8+
.node-option-tooltip-wrapper {
9+
display: flex;
10+
flex-direction: column;
11+
align-items: flex-start;
712
}

src/utils/fetchCuries.js

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,28 +12,10 @@ export default async function fetchCuries(entity, displayAlert, cancel) {
1212
if (!Array.isArray(response)) {
1313
return [];
1414
}
15-
const curieResponse = response.map((node) => node.curie);
16-
if (!curieResponse.length) {
17-
return [];
18-
}
19-
20-
// Pass curies to nodeNormalizer to get category information and
21-
// a better curie identifier
22-
const normalizationResponse = await API.nodeNormalization.getNormalizedNodes({ curies: curieResponse }, cancel);
23-
24-
if (normalizationResponse.status === 'error') {
25-
displayAlert('error',
26-
'Failed to contact node normalizer to search curies. Please try again later.');
27-
return [];
28-
}
2915

30-
// Sometimes the nodeNormalizer returns null responses
31-
// so we use a filter to remove those
32-
const newOptions = Object.values(normalizationResponse).filter((c) => c).map((c) => ({
33-
name: c.id.label || c.id.identifier,
34-
categories: c.type,
35-
ids: [c.id.identifier],
16+
return response.map(({ curie, label, types }) => ({
17+
name: label,
18+
categories: types,
19+
ids: [curie],
3620
}));
37-
38-
return newOptions;
3921
}

0 commit comments

Comments
 (0)