Skip to content

Commit 6a3751f

Browse files
authored
Handle ncbi errors better (#554)
* update cypress * better handle when NCBI is down * update axios and zip.js, closes #553, closes #552 * update backend * fix tests
1 parent 2a0225e commit 6a3751f

File tree

7 files changed

+176
-164
lines changed

7 files changed

+176
-164
lines changed

cypress/e2e/group-1/source_genome_region.cy.js

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -204,10 +204,10 @@ describe('GenomeRegion Source', () => {
204204
cy.get('label').contains('Chromosome').should('exist');
205205
cy.get('label').contains('Chromosome').siblings('div.MuiInputBase-root ').click();
206206
// Should contain the 4 pombe chromosomes
207-
cy.get('li[data-value="NC_003424.3"]').should('exist');
208-
cy.get('li[data-value="NC_003423.3"]').should('exist');
209-
cy.get('li[data-value="NC_003421.2"]').should('exist');
210-
cy.get('li[data-value="NC_088682.1"]').should('exist');
207+
cy.get('li[data-value="I - NC_003424.3"]').should('exist');
208+
cy.get('li[data-value="II - NC_003423.3"]').should('exist');
209+
cy.get('li[data-value="III - NC_003421.2"]').should('exist');
210+
cy.get('li[data-value="MT - NC_088682.1"]').should('exist');
211211
// Click outside
212212
cy.get('body').click(0, 0);
213213
// Select chromosome 1
@@ -228,10 +228,10 @@ describe('GenomeRegion Source', () => {
228228
cy.get('label').contains('Chromosome').should('exist');
229229
cy.get('label').contains('Chromosome').siblings('div.MuiInputBase-root ').click();
230230
// Should contain the 4 pombe chromosomes
231-
cy.get('li[data-value="NC_003424.3"]').should('exist');
232-
cy.get('li[data-value="NC_003423.3"]').should('exist');
233-
cy.get('li[data-value="NC_003421.2"]').should('exist');
234-
cy.get('li[data-value="NC_088682.1"]').should('exist');
231+
cy.get('li[data-value="I - NC_003424.3"]').should('exist');
232+
cy.get('li[data-value="II - NC_003423.3"]').should('exist');
233+
cy.get('li[data-value="III - NC_003421.2"]').should('exist');
234+
cy.get('li[data-value="MT - NC_088682.1"]').should('exist');
235235
// Click outside
236236
cy.get('body').click(0, 0);
237237
// Select chromosome 1
@@ -248,7 +248,6 @@ describe('GenomeRegion Source', () => {
248248
clickMultiSelectOption('Type of region', 'coordinates in other assembly', 'li#source-1');
249249
setInputValue('Assembly ID', 'Hello', 'li#source-1');
250250
cy.intercept('GET', 'https://api.ncbi.nlm.nih.gov/datasets/v2alpha/genome/accession/Hello/dataset_report?filters.assembly_version=all_assemblies', {
251-
statusCode: 404,
252251
body: {}
253252
}).as('getAssemblyInfo');
254253
cy.get('div').contains('Assembly ID does not exist').should('exist');

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@
6161
"@testing-library/user-event": "^14.6.1",
6262
"@vitejs/plugin-react": "^4.6.0",
6363
"@vitest/coverage-v8": "^3.2.4",
64-
"cypress": "^14.5.2",
64+
"cypress": "^15.7.0",
6565
"eslint": "^9.28.0",
6666
"eslint-config-airbnb": "^19.0.4",
6767
"eslint-plugin-cypress": "^5.1.0",

src/components/sources/SourceGenomeRegion.cy.jsx

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from 'react';
2-
import { AssemblyIdSelector } from './SourceGenomeRegion';
2+
import { AssemblyIdSelector, SpeciesPicker, SequenceAccessionPicker, } from './SourceGenomeRegion';
33

44
describe('<AssemblyIdSelector />', () => {
55
it('can propose a paired accession if the assembly has no annotation', () => {
@@ -55,4 +55,77 @@ describe('<AssemblyIdSelector />', () => {
5555
cy.wait('@getPairedAssemblyInfo');
5656
cy.contains('Equivalent assembly GCF_000002945.3 has annotation').should('exist');
5757
});
58+
it('handles NCBI being down displaying the right error', () => {
59+
cy.intercept('GET', 'https://api.ncbi.nlm.nih.gov/datasets/v2alpha/genome/accession/GCA_000002945.3/dataset_report*', {
60+
statusCode: 500,
61+
body: {}
62+
}).as('getAssemblyInfo');
63+
64+
cy.mount(
65+
<AssemblyIdSelector setAssemblyId={cy.spy()} setHasAnnotation={cy.spy()} onAssemblyIdChange={cy.spy()} />
66+
);
67+
cy.get('input').type('GCA_000002945.3', { delay: 0});
68+
cy.wait('@getAssemblyInfo');
69+
cy.contains('Could not connect to server for validation.').should('exist');
70+
});
71+
});
72+
73+
describe('<SpeciesPicker />', () => {
74+
it('handles NCBI being down displaying the right error', () => {
75+
const setSpecies = cy.spy().as('setSpecies');
76+
const setAssemblyId = cy.spy().as('setAssemblyId');
77+
78+
cy.mount(
79+
<SpeciesPicker setSpecies={setSpecies} setAssemblyId={setAssemblyId} />
80+
);
81+
cy.intercept('GET', 'https://api.ncbi.nlm.nih.gov/datasets/v2alpha/taxonomy/taxon_suggest/**', {
82+
statusCode: 500,
83+
body: {}
84+
}).as('getTaxonSuggest');
85+
86+
cy.get('input').type('Saccharomyces cerevisiae', { delay: 0});
87+
cy.wait('@getTaxonSuggest');
88+
cy.contains('Could not retrieve data').should('exist');
89+
// cy.get('li').contains('Saccharomyces cerevisiae - 559292').click();
90+
});
5891
});
92+
93+
describe('<SequenceAccessionPicker />', () => {
94+
it('handles NCBI being down displaying the right error', () => {
95+
const setSequenceAccession = cy.spy().as('setSequenceAccession');
96+
const assemblyAccession = 'GCA_000002945.3';
97+
98+
cy.intercept('GET', 'https://api.ncbi.nlm.nih.gov/datasets/v2alpha/genome/accession/GCA_000002945.3/sequence_reports*', {
99+
statusCode: 500,
100+
body: {}
101+
}).as('getSequenceReports');
102+
103+
cy.mount(
104+
<SequenceAccessionPicker assemblyAccession={assemblyAccession} sequenceAccession={''} setSequenceAccession={setSequenceAccession} />
105+
);
106+
cy.contains('Could not load chromosomes').should('exist');
107+
});
108+
it('displays the chromosomes', () => {
109+
const setSequenceAccession = cy.spy().as('setSequenceAccession');
110+
const assemblyAccession = 'GCA_000002945.3';
111+
112+
cy.intercept('GET', 'https://api.ncbi.nlm.nih.gov/datasets/v2alpha/genome/accession/GCA_000002945.3/sequence_reports*', {
113+
statusCode: 200,
114+
body: { reports: [{ chr_name: 'chr1', refseq_accession: 'NC_000001.10' }, { chr_name: 'chr2', refseq_accession: 'NC_000002.11' }] }
115+
}).as('getSequenceReports');
116+
117+
cy.mount(
118+
<SequenceAccessionPicker assemblyAccession={assemblyAccession} setSequenceAccession={setSequenceAccession} />
119+
);
120+
cy.wait('@getSequenceReports');
121+
cy.get('label').siblings('div').first().click();
122+
cy.contains('chr1 - NC_000001.10').should('exist');
123+
cy.contains('chr2 - NC_000002.11').should('exist');
124+
cy.get('div[role="presentation"]').contains('chr1 - NC_000001.10').click();
125+
// Check that the spy was called with the expected value
126+
cy.get('@setSequenceAccession').should('have.been.calledWith', 'NC_000001.10');
127+
128+
});
129+
});
130+
131+

src/components/sources/SourceGenomeRegion.jsx

Lines changed: 50 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ import FormControl from '@mui/material/FormControl';
66
import Select from '@mui/material/Select';
77
import { Alert, Box, FormHelperText, FormLabel } from '@mui/material';
88
import PostRequestSelect from '../form/PostRequestSelect';
9-
import { getReferenceAssemblyId, taxonSuggest, geneSuggest, getInfoFromAssemblyId, getInfoFromSequenceAccession, getSequenceAccessionsFromAssemblyAccession } from '../../utils/ncbiRequests';
9+
import { getReferenceAssemblyId, taxonSuggest, geneSuggest, getInfoFromAssemblyId, getInfoFromSequenceAccession } from '../../utils/ncbiRequests';
1010
import TextFieldValidate from '../form/TextFieldValidate';
1111
import SubmitButtonBackendAPI from '../form/SubmitButtonBackendAPI';
12+
import GetRequestMultiSelect from '../form/GetRequestMultiSelect';
13+
import useHttpClient from '../../hooks/useHttpClient';
1214

1315
function getGeneCoordsInfo(gene) {
1416
const { range: geneRange, accession_version: accessionVersion } = gene.annotation.genomic_regions[0].gene_range;
@@ -61,29 +63,23 @@ function SpeciesPicker({ setSpecies, setAssemblyId }) {
6163
return (<PostRequestSelect {...speciesPostRequestSettings} fullWidth />);
6264
}
6365

64-
function SequenceAccessionPicker({ assemblyAccesion, sequenceAccession, setSequenceAccession }) {
65-
const [options, setOptions] = React.useState([]);
66-
React.useEffect(() => {
67-
getSequenceAccessionsFromAssemblyAccession(assemblyAccesion).then((opts) => {
68-
setOptions(opts);
69-
}).catch((e) => { setOptions([]); console.error(e); });
70-
}, [assemblyAccesion]);
66+
function SequenceAccessionPicker({ assemblyAccession, setSequenceAccession }) {
67+
const httpClient = useHttpClient();
7168

69+
const url = `https://api.ncbi.nlm.nih.gov/datasets/v2alpha/genome/accession/${assemblyAccession}/sequence_reports`
7270
return (
7371
<FormControl fullWidth>
74-
<InputLabel id="select-sequence-accession">Chromosome</InputLabel>
75-
<Select
76-
labelId="select-sequence-accession"
72+
<GetRequestMultiSelect
73+
getOptionsFromResponse={(data) => data.reports}
74+
httpClient={httpClient}
75+
url={url}
7776
label="Chromosome"
78-
value={sequenceAccession}
79-
onChange={(event) => setSequenceAccession(event.target.value)}
80-
>
81-
{options.map(({ chr_name, refseq_accession }) => (
82-
<MenuItem key={refseq_accession} value={refseq_accession}>
83-
{`${chr_name} - ${refseq_accession}`}
84-
</MenuItem>
85-
))}
86-
</Select>
77+
messages={{ loadingMessage: 'Loading chromosomes...', errorMessage: 'Could not load chromosomes' }}
78+
onChange={(value, options) => { setSequenceAccession(options.find((o) => `${o.chr_name} - ${o.refseq_accession}` === value).refseq_accession)}}
79+
getOptionLabel={({ chr_name, refseq_accession }) => `${chr_name} - ${refseq_accession}`}
80+
multiple={false}
81+
autoComplete={false}
82+
/>
8783
</FormControl>
8884
);
8985
}
@@ -187,11 +183,11 @@ function AssemblyIdSelector({ setAssemblyId, setHasAnnotation = () => {}, onAsse
187183
<>
188184
<TextFieldValidate onChange={onChange} getterFunction={getInfoFromAssemblyId} label="Assembly ID" defaultHelperText="Example ID: GCA_000002945.3" />
189185
{newerAssembly && (
190-
<Alert severity="warning">
191-
{!exactMatch ? 'Using assembly ID' : 'Newer assembly exists:'}
192-
{' '}
193-
<a href={`https://www.ncbi.nlm.nih.gov/datasets/genome/${newerAssembly}`} target="_blank" rel="noopener noreferrer">{newerAssembly}</a>
194-
</Alert>
186+
<Alert severity="warning">
187+
{!exactMatch ? 'Using assembly ID' : 'Newer assembly exists:'}
188+
{' '}
189+
<a href={`https://www.ncbi.nlm.nih.gov/datasets/genome/${newerAssembly}`} target="_blank" rel="noopener noreferrer">{newerAssembly}</a>
190+
</Alert>
195191
)}
196192
{pairedAccessionWithAnnotation && (
197193
<Alert severity="warning">
@@ -321,7 +317,7 @@ function SourceGenomeRegionCustomCoordinates({ source, requestStatus, sendPostRe
321317
<AssemblyIdSelector {...{ setAssemblyId, onAssemblyIdChange: () => { setFormError({ ...noError }); setSequenceAccession(''); } }} />
322318
)}
323319
{assemblyId && ['custom_reference', 'custom_other'].includes(selectionMode) && (
324-
<SequenceAccessionPicker {...{ assemblyAccesion: assemblyId, sequenceAccession, setSequenceAccession }} />
320+
<SequenceAccessionPicker {...{ assemblyAccession: assemblyId, setSequenceAccession }} />
325321
)}
326322
{sequenceAccession && (
327323
<>
@@ -412,33 +408,33 @@ function SourceGenomeRegionSelectGene({ gene, upstreamBasesRef, downstreamBasesR
412408
<PostRequestSelect {...genePostRequestSettings} fullWidth />
413409
{error && (<Alert severity="error">{error}</Alert>)}
414410
{gene && (
415-
<>
416-
<FormControl fullWidth>
417-
<TextField
418-
label="Gene coordinates"
419-
value={formatGeneCoords(gene)}
420-
disabled
421-
/>
422-
</FormControl>
423-
<FormControl fullWidth>
424-
<TextField
425-
fullWidth
426-
label="Upstream bases"
427-
inputRef={upstreamBasesRef}
428-
type="number"
429-
defaultValue={1000}
430-
/>
431-
</FormControl>
432-
<FormControl fullWidth>
433-
<TextField
434-
fullWidth
435-
label="Downstream bases"
436-
inputRef={downstreamBasesRef}
437-
type="number"
438-
defaultValue={1000}
439-
/>
440-
</FormControl>
441-
</>
411+
<>
412+
<FormControl fullWidth>
413+
<TextField
414+
label="Gene coordinates"
415+
value={formatGeneCoords(gene)}
416+
disabled
417+
/>
418+
</FormControl>
419+
<FormControl fullWidth>
420+
<TextField
421+
fullWidth
422+
label="Upstream bases"
423+
inputRef={upstreamBasesRef}
424+
type="number"
425+
defaultValue={1000}
426+
/>
427+
</FormControl>
428+
<FormControl fullWidth>
429+
<TextField
430+
fullWidth
431+
label="Downstream bases"
432+
inputRef={downstreamBasesRef}
433+
type="number"
434+
defaultValue={1000}
435+
/>
436+
</FormControl>
437+
</>
442438
)}
443439
</>
444440
);
@@ -476,4 +472,4 @@ function SourceGenomeRegion({ source, requestStatus, sendPostRequest }) {
476472
);
477473
}
478474

479-
export { SourceGenomeRegion, AssemblyIdSelector };
475+
export { SourceGenomeRegion, AssemblyIdSelector, SpeciesPicker, SequenceAccessionPicker };

src/utils/ncbiRequests.js

Lines changed: 1 addition & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -2,43 +2,6 @@ import getHttpClient from './getHttpClient';
22

33
const httpClient = getHttpClient();
44

5-
export async function querySpecies(userInput) {
6-
// Get ids from search
7-
const query = userInput.toLowerCase();
8-
// Max number of ids that will be received
9-
const retMax = 10000;
10-
let retStart = 0;
11-
const ids = [];
12-
while (retStart >= 0) {
13-
const url1 = `https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?db=taxonomy&term=*${query}*[WORD]+species[rank]&retmax=${retMax}&retmode=json&retstart=${retStart}`;
14-
const resp1 = await httpClient.get(url1);
15-
resp1.data.esearchresult.idlist.forEach((e) => ids.push(e));
16-
if (Number(resp1.data.esearchresult.count) > ids.length) {
17-
retStart += retMax;
18-
} else {
19-
retStart = -1;
20-
}
21-
if (retStart > 50000) {
22-
throw Error('Too many results, narrow your search');
23-
}
24-
}
25-
26-
const maxRequestIds = 400;
27-
// Make requests with maxRequestIds ids
28-
const taxons = [];
29-
for (let i = 0; i < ids.length; i += maxRequestIds) {
30-
const requestedIds = ids.slice(i, i + maxRequestIds).join(',');
31-
const url2 = `https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esummary.fcgi?db=taxonomy&id=${requestedIds}&retmode=json`;
32-
const resp2Data = await httpClient.get(url2);
33-
resp2Data.data.result.uids.forEach((e) => taxons.push(resp2Data.data.result[e]));
34-
// NCBI is very sensitive to the number of requests, so we need to wait a bit
35-
if (i + maxRequestIds < ids.length) { await setTimeout(() => {}, 2000); }
36-
}
37-
38-
return taxons.filter((taxon) => taxon.species !== ''
39-
&& !taxon.species.includes(' ')
40-
&& taxon.rank === 'species' && taxon.scientificname.includes(query));
41-
}
425

436
export async function taxonSuggest(userInput) {
447
const url = `https://api.ncbi.nlm.nih.gov/datasets/v2alpha/taxonomy/taxon_suggest/${userInput}`;
@@ -73,7 +36,7 @@ export async function geneSuggest(assemblyId, userInput) {
7336

7437
export async function getInfoFromAssemblyId(assemblyId) {
7538
const url = `https://api.ncbi.nlm.nih.gov/datasets/v2alpha/genome/accession/${assemblyId}/dataset_report?filters.assembly_version=all_assemblies`;
76-
const resp = await httpClient.get(url, { validateStatus: false });
39+
const resp = await httpClient.get(url);
7740

7841
if (resp.status === 404 || resp.data.reports === undefined) {
7942
return null;
@@ -137,14 +100,3 @@ export async function getInfoFromSequenceAccession(sequenceAccession) {
137100
const { scientificname: organismName } = resp2.data.result[resp2.data.result.uids[0]];
138101
return { species: { tax_id: taxId, organism_name: organismName }, sequenceAccessionStandard };
139102
}
140-
141-
export async function getSequenceAccessionsFromAssemblyAccession(assemblyAccession) {
142-
const url = `https://api.ncbi.nlm.nih.gov/datasets/v2alpha/genome/accession/${assemblyAccession}/sequence_reports`;
143-
// For example: https://api.ncbi.nlm.nih.gov/datasets/v2alpha/genome/accession/GCF_000005845.2/sequence_reports
144-
try {
145-
const resp = await httpClient.get(url);
146-
return resp.data.reports;
147-
} catch (error) {
148-
return [];
149-
}
150-
}

0 commit comments

Comments
 (0)