@opencloning/ui 1.9.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +73 -0
- package/package.json +5 -4
- package/src/components/CloningSpeedDial.jsx +3 -5
- package/src/components/EditSequenceNameDialog.jsx +3 -5
- package/src/components/ExternalServicesStatusCheck.jsx +3 -5
- package/src/components/LoadCloningHistoryWrapper.jsx +21 -46
- package/src/components/SequenceViewer.jsx +12 -0
- package/src/components/assembler/Assembler.jsx +4 -6
- package/src/components/assembler/ExistingSyntaxDialog.jsx +2 -2
- package/src/components/assembler/useAssembler.js +6 -8
- package/src/components/domesticate/DomesticateDialog.jsx +3 -5
- package/src/components/form/EnzymeMultiSelect.jsx +3 -5
- package/src/components/form/TextFieldQueryValidated.jsx +22 -4
- package/src/components/navigation/MainAppBar.jsx +26 -20
- package/src/components/navigation/SelectTemplateDialog.jsx +4 -2
- package/src/components/primers/primer_design/SequenceTabComponents/hooks/useDesignPrimers.js +4 -6
- package/src/components/primers/primer_details/usePrimerDetailsEndpoints.js +4 -6
- package/src/components/sources/CollectionSource.jsx +2 -2
- package/src/components/sources/Source.jsx +32 -32
- package/src/components/sources/SourceAnnotation.jsx +2 -2
- package/src/components/sources/SourceAssembly.jsx +7 -7
- package/src/components/sources/SourceDatabase.jsx +2 -2
- package/src/components/sources/SourceFile.jsx +21 -51
- package/src/components/sources/SourceGenomeRegion.cy.jsx +46 -0
- package/src/components/sources/SourceGenomeRegion.jsx +29 -15
- package/src/components/sources/SourceHomologousRecombination.jsx +3 -3
- package/src/components/sources/SourceKnownGenomeRegion.jsx +3 -3
- package/src/components/sources/SourceManuallyTyped.jsx +2 -2
- package/src/components/sources/SourcePCRorHybridization.jsx +3 -3
- package/src/components/sources/SourcePolymeraseExtension.jsx +2 -2
- package/src/components/sources/SourceRepositoryId.jsx +4 -4
- package/src/components/sources/SourceRestriction.jsx +2 -2
- package/src/components/sources/SourceReverseComplement.jsx +2 -2
- package/src/components/sources/SourceServerStaticFile.jsx +3 -3
- package/src/components/verification/VerificationFileDialog.cy.jsx +17 -21
- package/src/components/verification/VerificationFileDialog.jsx +12 -11
- package/src/hooks/index.js +1 -0
- package/src/hooks/useBackendHttpClient.js +17 -0
- package/src/hooks/useCloningHistoryLoader.js +161 -0
- package/src/hooks/useGatewaySites.js +3 -5
- package/src/hooks/useInitializeApp.js +3 -4
- package/src/hooks/useLoadDatabaseFile.js +17 -34
- package/src/hooks/useSequencingAlignment.js +4 -6
- package/src/hooks/useServerStaticFiles.js +2 -2
- package/src/hooks/{useBackendAPI.js → useSourceRequest.js} +6 -8
- package/src/hooks/useStoreEditor.js +2 -1
- package/src/hooks/useUrlParamsLoader.js +29 -23
- package/src/hooks/useValidateState.js +4 -6
- package/src/version.js +1 -1
- package/src/hooks/useBackendRoute.js +0 -23
- package/src/hooks/useHttpClient.js +0 -20
- package/src/hooks/useSnapgeneHistoryEndpoint.js +0 -72
|
@@ -2,14 +2,16 @@ import { Accordion, AccordionDetails, AccordionSummary, Alert, Button, CircularP
|
|
|
2
2
|
|
|
3
3
|
import React from 'react';
|
|
4
4
|
import { ExpandMore as ExpandMoreIcon } from '@mui/icons-material';
|
|
5
|
-
import
|
|
5
|
+
import { getExternalHttpClient } from '@opencloning/utils/httpClients';
|
|
6
|
+
|
|
7
|
+
// External urls (not the backend): no auth token, only whitelisted urls
|
|
8
|
+
const httpClient = getExternalHttpClient();
|
|
6
9
|
|
|
7
10
|
function SelectTemplateDialog({ onClose, open }) {
|
|
8
11
|
const [templates, setTemplates] = React.useState(null);
|
|
9
12
|
const [connectAttempt, setConnectAttemp] = React.useState(0);
|
|
10
13
|
const [error, setError] = React.useState(null);
|
|
11
14
|
const baseUrl = 'https://assets.opencloning.org/OpenCloning-submission';
|
|
12
|
-
const httpClient = useHttpClient();
|
|
13
15
|
|
|
14
16
|
React.useEffect(() => {
|
|
15
17
|
const fetchData = async () => {
|
package/src/components/primers/primer_design/SequenceTabComponents/hooks/useDesignPrimers.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { useState, useCallback } from 'react';
|
|
2
2
|
import { batch, useDispatch, useStore } from 'react-redux';
|
|
3
|
-
import
|
|
4
|
-
import useHttpClient from '../../../../../hooks/useHttpClient';
|
|
3
|
+
import useBackendHttpClient from '../../../../../hooks/useBackendHttpClient';
|
|
5
4
|
import useStoreEditor from '../../../../../hooks/useStoreEditor';
|
|
6
5
|
import error2String from '@opencloning/utils/error2String';
|
|
7
6
|
import { cloningActions } from '@opencloning/store/cloning';
|
|
@@ -19,8 +18,7 @@ export default function useDesignPrimers({
|
|
|
19
18
|
|
|
20
19
|
const store = useStore();
|
|
21
20
|
const dispatch = useDispatch();
|
|
22
|
-
const
|
|
23
|
-
const httpClient = useHttpClient();
|
|
21
|
+
const httpClient = useBackendHttpClient();
|
|
24
22
|
const { updateStoreEditor } = useStoreEditor();
|
|
25
23
|
|
|
26
24
|
const designPrimers = useCallback(async () => {
|
|
@@ -43,7 +41,7 @@ export default function useDesignPrimers({
|
|
|
43
41
|
});
|
|
44
42
|
|
|
45
43
|
requestData.settings = globalPrimerSettings;
|
|
46
|
-
const url =
|
|
44
|
+
const url = `primer_design/${endpoint}`;
|
|
47
45
|
|
|
48
46
|
try {
|
|
49
47
|
const resp = await httpClient.post(url, requestData, { params });
|
|
@@ -55,7 +53,7 @@ export default function useDesignPrimers({
|
|
|
55
53
|
setError(error2String(thrownError));
|
|
56
54
|
return true;
|
|
57
55
|
}
|
|
58
|
-
}, [fragmentOrientations, rois, sequenceIds, templateSequenceIds, designType, circularAssembly, primerDesignSettings, spacers, store, httpClient,
|
|
56
|
+
}, [fragmentOrientations, rois, sequenceIds, templateSequenceIds, designType, circularAssembly, primerDesignSettings, spacers, store, httpClient, handleNext, isAmplified]);
|
|
59
57
|
|
|
60
58
|
const addPrimers = useCallback(() => {
|
|
61
59
|
const pcrSources = store.getState().cloning.sources.filter((source) => source.type === 'PCRSource');
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import React, { useCallback } from 'react';
|
|
2
|
-
import
|
|
3
|
-
import useHttpClient from '../../../hooks/useHttpClient';
|
|
2
|
+
import useBackendHttpClient from '../../../hooks/useBackendHttpClient';
|
|
4
3
|
import { isEqual } from 'lodash-es';
|
|
5
4
|
import { useSelector } from 'react-redux';
|
|
6
5
|
|
|
@@ -16,15 +15,14 @@ export function makeHeterodimerCacheKey(sequence1, sequence2, globalPrimerSettin
|
|
|
16
15
|
return `${pairKey}::${settingsKey}`;
|
|
17
16
|
}
|
|
18
17
|
export function usePrimerDetailsEndpoints() {
|
|
19
|
-
const
|
|
20
|
-
const httpClient = useHttpClient();
|
|
18
|
+
const httpClient = useBackendHttpClient();
|
|
21
19
|
const globalPrimerSettings = useSelector((state) => state.cloning.globalPrimerSettings, isEqual);
|
|
22
20
|
|
|
23
21
|
// Caches are keyed by both the input sequence(s) and the current globalPrimerSettings,
|
|
24
22
|
// so we don't need to clear them on settings change.
|
|
25
23
|
|
|
26
|
-
const url =
|
|
27
|
-
const heterodimerUrl =
|
|
24
|
+
const url = 'primer_details';
|
|
25
|
+
const heterodimerUrl = 'primer_heterodimer';
|
|
28
26
|
|
|
29
27
|
const getPrimerDetails = useCallback(async (sequence) => {
|
|
30
28
|
const cacheKey = makePrimerDetailsCacheKey(sequence, globalPrimerSettings);
|
|
@@ -7,7 +7,7 @@ import { classNameToEndPointMap } from '@opencloning/utils/sourceFunctions';
|
|
|
7
7
|
import ObjectTable from '../ObjectTable';
|
|
8
8
|
import { cloningActions } from '@opencloning/store/cloning';
|
|
9
9
|
|
|
10
|
-
function CollectionSource({ source, requestStatus,
|
|
10
|
+
function CollectionSource({ source, requestStatus, sendSourceRequest }) {
|
|
11
11
|
const { id: sourceId, options, image: imageInfo, title, description } = source;
|
|
12
12
|
const [image, imageWidth] = imageInfo || [null, null];
|
|
13
13
|
const [selectedOption, setSelectedOption] = React.useState(null);
|
|
@@ -34,7 +34,7 @@ function CollectionSource({ source, requestStatus, sendPostRequest }) {
|
|
|
34
34
|
const reverseOligo = primers.find((primer) => primer.id === selectedSource.input[1].sequence);
|
|
35
35
|
requestData = { source: { id: sourceId, ...selectedSource }, primers: [forwardOligo, reverseOligo] };
|
|
36
36
|
}
|
|
37
|
-
|
|
37
|
+
sendSourceRequest({ endpoint, requestData, source, modifySource });
|
|
38
38
|
};
|
|
39
39
|
|
|
40
40
|
const onChange = (event) => {
|
|
@@ -14,7 +14,7 @@ import SourceAnnotation from './SourceAnnotation';
|
|
|
14
14
|
import SourceDatabase from './SourceDatabase';
|
|
15
15
|
import SourcePolymeraseExtension from './SourcePolymeraseExtension';
|
|
16
16
|
import CollectionSource from './CollectionSource';
|
|
17
|
-
import
|
|
17
|
+
import useSourceRequest from '../../hooks/useSourceRequest';
|
|
18
18
|
import MultipleOutputsSelector from './MultipleOutputsSelector';
|
|
19
19
|
import { cloningActions } from '@opencloning/store/cloning';
|
|
20
20
|
import SourceCopySequence from './SourceCopySequence';
|
|
@@ -31,7 +31,7 @@ function Source({ sourceId }) {
|
|
|
31
31
|
const { type: sourceType } = source;
|
|
32
32
|
let specificSource = null;
|
|
33
33
|
const templateOnlySources = ['CollectionSource', 'KnownGenomeCoordinatesSource'];
|
|
34
|
-
const { requestStatus,
|
|
34
|
+
const { requestStatus, sendSourceRequest, sources, sequences } = useSourceRequest();
|
|
35
35
|
const { addSequenceAndUpdateItsSource, updateSequenceAndItsSource } = cloningActions;
|
|
36
36
|
const [chosenFragment, setChosenFragment] = React.useState(null);
|
|
37
37
|
const dispatch = useDispatch();
|
|
@@ -49,67 +49,67 @@ function Source({ sourceId }) {
|
|
|
49
49
|
switch (sourceType) {
|
|
50
50
|
/* eslint-disable */
|
|
51
51
|
case 'UploadedFileSource':
|
|
52
|
-
specificSource = <SourceFile {...{ source, requestStatus,
|
|
52
|
+
specificSource = <SourceFile {...{ source, requestStatus, sendSourceRequest }} />; break;
|
|
53
53
|
case 'LocalFileSource':
|
|
54
|
-
specificSource = <SourceServerStaticFile {...{ source, requestStatus,
|
|
54
|
+
specificSource = <SourceServerStaticFile {...{ source, requestStatus, sendSourceRequest }} />; break;
|
|
55
55
|
case 'RestrictionEnzymeDigestionSource':
|
|
56
|
-
specificSource = <SourceRestriction {...{ source, requestStatus,
|
|
56
|
+
specificSource = <SourceRestriction {...{ source, requestStatus, sendSourceRequest }} />; break;
|
|
57
57
|
case 'RepositoryIdSource':
|
|
58
|
-
specificSource = <SourceRepositoryId {...{ source, requestStatus,
|
|
58
|
+
specificSource = <SourceRepositoryId {...{ source, requestStatus, sendSourceRequest }} />; break;
|
|
59
59
|
case 'AddgeneIdSource':
|
|
60
|
-
specificSource = <SourceRepositoryId {...{ source, requestStatus,
|
|
60
|
+
specificSource = <SourceRepositoryId {...{ source, requestStatus, sendSourceRequest }} />; break;
|
|
61
61
|
case 'SnapGenePlasmidSource':
|
|
62
|
-
specificSource = <SourceRepositoryId {...{ source, requestStatus,
|
|
62
|
+
specificSource = <SourceRepositoryId {...{ source, requestStatus, sendSourceRequest }} />; break;
|
|
63
63
|
case 'EuroscarfSource':
|
|
64
|
-
specificSource = <SourceRepositoryId {...{ source, requestStatus,
|
|
64
|
+
specificSource = <SourceRepositoryId {...{ source, requestStatus, sendSourceRequest }} />; break;
|
|
65
65
|
case 'WekWikGeneIdSource':
|
|
66
|
-
specificSource = <SourceRepositoryId {...{ source, requestStatus,
|
|
66
|
+
specificSource = <SourceRepositoryId {...{ source, requestStatus, sendSourceRequest }} />; break;
|
|
67
67
|
case 'OpenDNACollectionsSource':
|
|
68
|
-
specificSource = <SourceRepositoryId {...{ source, requestStatus,
|
|
68
|
+
specificSource = <SourceRepositoryId {...{ source, requestStatus, sendSourceRequest }} />; break;
|
|
69
69
|
case 'LigationSource':
|
|
70
|
-
specificSource = <SourceAssembly {...{ source, requestStatus,
|
|
70
|
+
specificSource = <SourceAssembly {...{ source, requestStatus, sendSourceRequest }} />; break;
|
|
71
71
|
case 'GibsonAssemblySource':
|
|
72
|
-
specificSource = <SourceAssembly {...{ source, requestStatus,
|
|
72
|
+
specificSource = <SourceAssembly {...{ source, requestStatus, sendSourceRequest }} />; break;
|
|
73
73
|
case 'OverlapExtensionPCRLigationSource':
|
|
74
|
-
specificSource = <SourceAssembly {...{ source, requestStatus,
|
|
74
|
+
specificSource = <SourceAssembly {...{ source, requestStatus, sendSourceRequest }} />; break;
|
|
75
75
|
case 'InFusionSource':
|
|
76
|
-
specificSource = <SourceAssembly {...{ source, requestStatus,
|
|
76
|
+
specificSource = <SourceAssembly {...{ source, requestStatus, sendSourceRequest }} />; break;
|
|
77
77
|
case 'InVivoAssemblySource':
|
|
78
|
-
specificSource = <SourceAssembly {...{ source, requestStatus,
|
|
78
|
+
specificSource = <SourceAssembly {...{ source, requestStatus, sendSourceRequest }} />; break;
|
|
79
79
|
case 'GatewaySource':
|
|
80
|
-
specificSource = <SourceAssembly {...{ source, requestStatus,
|
|
80
|
+
specificSource = <SourceAssembly {...{ source, requestStatus, sendSourceRequest }} />; break;
|
|
81
81
|
case 'CreLoxRecombinationSource':
|
|
82
|
-
specificSource = <SourceAssembly {...{ source, requestStatus,
|
|
82
|
+
specificSource = <SourceAssembly {...{ source, requestStatus, sendSourceRequest }} />; break;
|
|
83
83
|
case 'RecombinaseSource':
|
|
84
|
-
specificSource = <SourceAssembly {...{ source, requestStatus,
|
|
84
|
+
specificSource = <SourceAssembly {...{ source, requestStatus, sendSourceRequest }} />; break;
|
|
85
85
|
case 'HomologousRecombinationSource':
|
|
86
|
-
specificSource = <SourceHomologousRecombination {...{ source, requestStatus,
|
|
86
|
+
specificSource = <SourceHomologousRecombination {...{ source, requestStatus, sendSourceRequest }} />; break;
|
|
87
87
|
case 'PCRSource':
|
|
88
|
-
specificSource = <SourcePCRorHybridization {...{ source, requestStatus,
|
|
88
|
+
specificSource = <SourcePCRorHybridization {...{ source, requestStatus, sendSourceRequest }} />; break;
|
|
89
89
|
case 'RestrictionAndLigationSource':
|
|
90
|
-
specificSource = <SourceAssembly {...{ source, requestStatus,
|
|
90
|
+
specificSource = <SourceAssembly {...{ source, requestStatus, sendSourceRequest }} />; break;
|
|
91
91
|
case 'GenomeCoordinatesSource':
|
|
92
|
-
specificSource = <SourceGenomeRegion {...{ source, requestStatus,
|
|
92
|
+
specificSource = <SourceGenomeRegion {...{ source, requestStatus, sendSourceRequest }} />; break;
|
|
93
93
|
case 'KnownGenomeCoordinatesSource':
|
|
94
|
-
specificSource = <SourceKnownGenomeRegion {...{ source, requestStatus,
|
|
94
|
+
specificSource = <SourceKnownGenomeRegion {...{ source, requestStatus, sendSourceRequest }} />; break;
|
|
95
95
|
case 'ManuallyTypedSource':
|
|
96
|
-
specificSource = <SourceManuallyTyped {...{ source, requestStatus,
|
|
96
|
+
specificSource = <SourceManuallyTyped {...{ source, requestStatus, sendSourceRequest }} />; break;
|
|
97
97
|
case 'CRISPRSource':
|
|
98
|
-
specificSource = <SourceHomologousRecombination {...{ source, requestStatus,
|
|
98
|
+
specificSource = <SourceHomologousRecombination {...{ source, requestStatus, sendSourceRequest }} />; break;
|
|
99
99
|
case 'OligoHybridizationSource':
|
|
100
|
-
specificSource = <SourcePCRorHybridization {...{ source, requestStatus,
|
|
100
|
+
specificSource = <SourcePCRorHybridization {...{ source, requestStatus, sendSourceRequest }} />; break;
|
|
101
101
|
case 'PolymeraseExtensionSource':
|
|
102
|
-
specificSource = <SourcePolymeraseExtension {...{ source, requestStatus,
|
|
102
|
+
specificSource = <SourcePolymeraseExtension {...{ source, requestStatus, sendSourceRequest }} />; break;
|
|
103
103
|
case 'DatabaseSource':
|
|
104
|
-
specificSource = <SourceDatabase {...{ source, requestStatus,
|
|
104
|
+
specificSource = <SourceDatabase {...{ source, requestStatus, sendSourceRequest }} />; break;
|
|
105
105
|
case 'CollectionSource':
|
|
106
|
-
specificSource = <CollectionSource {...{ source, requestStatus,
|
|
106
|
+
specificSource = <CollectionSource {...{ source, requestStatus, sendSourceRequest }} />; break;
|
|
107
107
|
case 'CopySequence':
|
|
108
108
|
specificSource = <SourceCopySequence {...{ source }} />; break;
|
|
109
109
|
case 'AnnotationSource':
|
|
110
|
-
specificSource = <SourceAnnotation {...{ source, requestStatus,
|
|
110
|
+
specificSource = <SourceAnnotation {...{ source, requestStatus, sendSourceRequest }} />; break;
|
|
111
111
|
case 'ReverseComplementSource':
|
|
112
|
-
specificSource = <SourceReverseComplement {...{ source, requestStatus,
|
|
112
|
+
specificSource = <SourceReverseComplement {...{ source, requestStatus, sendSourceRequest }} />; break;
|
|
113
113
|
default:
|
|
114
114
|
break;
|
|
115
115
|
/* eslint-enable */
|
|
@@ -5,7 +5,7 @@ import { isEqual } from 'lodash-es';
|
|
|
5
5
|
import { getInputSequencesFromSourceId } from '@opencloning/store/cloning_utils';
|
|
6
6
|
import SubmitButtonBackendAPI from '../form/SubmitButtonBackendAPI';
|
|
7
7
|
|
|
8
|
-
function SourceAnnotation({ source, requestStatus,
|
|
8
|
+
function SourceAnnotation({ source, requestStatus, sendSourceRequest }) {
|
|
9
9
|
const [annotationTool, setAnnotationTool] = React.useState('plannotate');
|
|
10
10
|
|
|
11
11
|
const inputSequences = useSelector((state) => getInputSequencesFromSourceId(state, source.id), isEqual);
|
|
@@ -16,7 +16,7 @@ function SourceAnnotation({ source, requestStatus, sendPostRequest }) {
|
|
|
16
16
|
sequence: inputSequences[0],
|
|
17
17
|
source: { id: source.id, input: inputSequences.map((e) => ({ sequence: e.id })), annotation_tool: annotationTool },
|
|
18
18
|
};
|
|
19
|
-
|
|
19
|
+
sendSourceRequest({ endpoint: 'annotate/plannotate', requestData, source });
|
|
20
20
|
};
|
|
21
21
|
|
|
22
22
|
return (
|
|
@@ -15,7 +15,7 @@ const helpSingleSite = 'Even if input sequences contain multiple att sites '
|
|
|
15
15
|
+ 'Select this option to get those products.';
|
|
16
16
|
|
|
17
17
|
// A component representing the ligation or gibson assembly of several fragments
|
|
18
|
-
function SourceAssembly({ source, requestStatus,
|
|
18
|
+
function SourceAssembly({ source, requestStatus, sendSourceRequest }) {
|
|
19
19
|
const assemblyType = source.type;
|
|
20
20
|
const { id: sourceId, input: sourceInput } = source;
|
|
21
21
|
const inputSequences = useSelector((state) => getInputSequencesFromSourceId(state, sourceId), shallowEqual);
|
|
@@ -91,21 +91,21 @@ function SourceAssembly({ source, requestStatus, sendPostRequest }) {
|
|
|
91
91
|
} };
|
|
92
92
|
// To instantiate the correct class on the backend
|
|
93
93
|
requestData.source.type = assemblyType;
|
|
94
|
-
|
|
94
|
+
sendSourceRequest({ endpoint: 'gibson_assembly', requestData, config, source });
|
|
95
95
|
} else if (assemblyType === 'RestrictionAndLigationSource') {
|
|
96
96
|
if (enzymes.length === 0) { return; }
|
|
97
97
|
requestData.source.restriction_enzymes = enzymes;
|
|
98
98
|
const config = { params: {
|
|
99
99
|
circular_only: circularOnly,
|
|
100
100
|
} };
|
|
101
|
-
|
|
101
|
+
sendSourceRequest({ endpoint: 'restriction_and_ligation', requestData, config, source });
|
|
102
102
|
} else if (assemblyType === 'GatewaySource') {
|
|
103
103
|
requestData.source.greedy = gatewaySettings.greedy;
|
|
104
104
|
requestData.source.reaction_type = gatewaySettings.reactionType;
|
|
105
105
|
const config = { params: { circular_only: circularOnly, only_multi_site: gatewaySettings.onlyMultiSite } };
|
|
106
|
-
|
|
106
|
+
sendSourceRequest({ endpoint: 'gateway', requestData, config, source });
|
|
107
107
|
} else if (assemblyType === 'CreLoxRecombinationSource') {
|
|
108
|
-
|
|
108
|
+
sendSourceRequest({ endpoint: 'cre_lox_recombination', requestData, source });
|
|
109
109
|
} else if (assemblyType === 'RecombinaseSource') {
|
|
110
110
|
if (recombinases.length === 0) { return; }
|
|
111
111
|
requestData.source.recombinases = recombinases;
|
|
@@ -113,14 +113,14 @@ function SourceAssembly({ source, requestStatus, sendPostRequest }) {
|
|
|
113
113
|
reverse_recombinase: reverseRecombinase,
|
|
114
114
|
input_contains_genome: inputContainsGenome,
|
|
115
115
|
} };
|
|
116
|
-
|
|
116
|
+
sendSourceRequest({ endpoint: 'recombinase', requestData, config, source });
|
|
117
117
|
} else {
|
|
118
118
|
const config = { params: {
|
|
119
119
|
allow_partial_overlap: allowPartialOverlap,
|
|
120
120
|
circular_only: circularOnly,
|
|
121
121
|
blunt: bluntLigation,
|
|
122
122
|
} };
|
|
123
|
-
|
|
123
|
+
sendSourceRequest({ endpoint: classNameToEndPointMap[assemblyType], requestData, config, source });
|
|
124
124
|
}
|
|
125
125
|
};
|
|
126
126
|
|
|
@@ -4,12 +4,12 @@ import SubmitButtonBackendAPI from '../form/SubmitButtonBackendAPI';
|
|
|
4
4
|
import useDatabase from '../../hooks/useDatabase';
|
|
5
5
|
import useLoadDatabaseFile from '../../hooks/useLoadDatabaseFile';
|
|
6
6
|
|
|
7
|
-
function SourceDatabase({ source, requestStatus,
|
|
7
|
+
function SourceDatabase({ source, requestStatus, sendSourceRequest }) {
|
|
8
8
|
const [file, setFile] = React.useState(null);
|
|
9
9
|
const [databaseId, setDatabaseId] = React.useState(null);
|
|
10
10
|
const database = useDatabase();
|
|
11
11
|
const [historyFileError, setHistoryFileError] = React.useState(null);
|
|
12
|
-
const { loadDatabaseFile } = useLoadDatabaseFile({ source,
|
|
12
|
+
const { loadDatabaseFile } = useLoadDatabaseFile({ source, sendSourceRequest, setHistoryFileError });
|
|
13
13
|
|
|
14
14
|
const onSubmit = async (e) => {
|
|
15
15
|
e.preventDefault();
|
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { FormHelperText, Alert, Checkbox, FormControl, FormControlLabel, InputLabel, MenuItem, Select, TextField } from '@mui/material';
|
|
3
|
-
import {
|
|
3
|
+
import { useSelector } from 'react-redux';
|
|
4
4
|
import SubmitButtonBackendAPI from '../form/SubmitButtonBackendAPI';
|
|
5
5
|
import LabelWithTooltip from '../form/LabelWithTooltip';
|
|
6
|
-
import
|
|
7
|
-
import { loadFilesToSessionStorage, loadHistoryFile, updateVerificationFileNames } from '@opencloning/utils/readNwrite';
|
|
8
|
-
import useValidateState from '../../hooks/useValidateState';
|
|
9
|
-
import { mergeStates, getGraftSequenceId, graftState } from '@opencloning/utils/network';
|
|
10
|
-
import useSnapgeneHistoryEndpoint from '../../hooks/useSnapgeneHistoryEndpoint';
|
|
11
|
-
|
|
12
|
-
const { deleteSourceAndItsChildren, restoreSource, setState: setCloningState } = cloningActions;
|
|
6
|
+
import useCloningHistoryLoader from '../../hooks/useCloningHistoryLoader';
|
|
13
7
|
|
|
14
8
|
const fileFormatToExtension = {
|
|
15
9
|
genbank: 'gb',
|
|
@@ -20,7 +14,7 @@ const fileFormatToExtension = {
|
|
|
20
14
|
};
|
|
21
15
|
|
|
22
16
|
// A component providing an interface to import a file
|
|
23
|
-
function SourceFile({ source, requestStatus,
|
|
17
|
+
function SourceFile({ source, requestStatus, sendSourceRequest }) {
|
|
24
18
|
const [circularize, setCircularize] = React.useState(false);
|
|
25
19
|
const [showCoordinates, setShowCoordinates] = React.useState(false);
|
|
26
20
|
const [coordinates, setCoordinates] = React.useState({ start: null, end: null });
|
|
@@ -28,10 +22,7 @@ function SourceFile({ source, requestStatus, sendPostRequest }) {
|
|
|
28
22
|
const hasOutput = useSelector((state) => state.cloning.sequences.some((s) => s.id === source.id));
|
|
29
23
|
// Error message for json only
|
|
30
24
|
const [alert, setAlert] = React.useState(null);
|
|
31
|
-
const
|
|
32
|
-
const validateState = useValidateState();
|
|
33
|
-
const store = useStore();
|
|
34
|
-
const { loadSnapgeneHistory } = useSnapgeneHistoryEndpoint();
|
|
25
|
+
const { loadHistoryFromFile, loadSnapgeneHistory } = useCloningHistoryLoader();
|
|
35
26
|
const onChange = async (event) => {
|
|
36
27
|
setAlert(null);
|
|
37
28
|
const files = Array.from(event.target.files);
|
|
@@ -39,6 +30,7 @@ function SourceFile({ source, requestStatus, sendPostRequest }) {
|
|
|
39
30
|
return;
|
|
40
31
|
}
|
|
41
32
|
event.target.value = null;
|
|
33
|
+
const reportError = (message) => setAlert({ message, severity: 'error' });
|
|
42
34
|
// If the file is a history file, we load it
|
|
43
35
|
if (
|
|
44
36
|
fileFormat === 'json' || fileFormat === 'zip'
|
|
@@ -50,54 +42,32 @@ function SourceFile({ source, requestStatus, sendPostRequest }) {
|
|
|
50
42
|
type: fileFormat === 'json' ? 'application/json' : files[0].type,
|
|
51
43
|
});
|
|
52
44
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
({ cloningStrategy, verificationFiles } = await loadHistoryFile(files[0]));
|
|
57
|
-
} catch (e) {
|
|
58
|
-
console.error(e);
|
|
59
|
-
setAlert({ message: e.message, severity: 'error' });
|
|
45
|
+
|
|
46
|
+
const prepared = await loadHistoryFromFile(files[0], { onError: reportError });
|
|
47
|
+
if (!prepared) {
|
|
60
48
|
return;
|
|
61
49
|
}
|
|
62
50
|
|
|
63
|
-
|
|
64
|
-
// Update the verificationFiles names if needed
|
|
65
|
-
const updatedVerificationFiles = updateVerificationFileNames(verificationFiles, cloningStrategy.files, validatedCloningStrategy.files);
|
|
66
|
-
|
|
67
|
-
const canGraft = getGraftSequenceId(validatedCloningStrategy) !== null;
|
|
68
|
-
const graft = hasOutput && canGraft;
|
|
69
|
-
|
|
70
|
-
if (hasOutput && !canGraft) {
|
|
51
|
+
if (hasOutput && !prepared.canGraft) {
|
|
71
52
|
setAlert({ message: 'Cannot graft cloning strategy as it does not converge on a single sequence, you can load it on a source without outputs', severity: 'error' });
|
|
72
53
|
return;
|
|
73
54
|
}
|
|
74
55
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
let mergedState;
|
|
83
|
-
let idShift;
|
|
84
|
-
if (graft) {
|
|
85
|
-
({ mergedState, idShift } = graftState(validatedCloningStrategy, cloningState, source.id));
|
|
86
|
-
} else {
|
|
87
|
-
({ mergedState, idShift } = mergeStates(validatedCloningStrategy, cloningState));
|
|
88
|
-
}
|
|
89
|
-
dispatch(setCloningState(mergedState));
|
|
90
|
-
await loadFilesToSessionStorage(updatedVerificationFiles, idShift);
|
|
91
|
-
} catch (e) {
|
|
92
|
-
setAlert({ message: e.message, severity: 'error' });
|
|
93
|
-
dispatch(restoreSource({ ...source, type: 'UploadedFileSource' }));
|
|
56
|
+
const graft = hasOutput && prepared.canGraft;
|
|
57
|
+
try {
|
|
58
|
+
const applyOptions = { source, onError: reportError };
|
|
59
|
+
if (graft) {
|
|
60
|
+
await prepared.graft(applyOptions);
|
|
61
|
+
} else {
|
|
62
|
+
await prepared.merge(applyOptions);
|
|
94
63
|
}
|
|
95
|
-
}
|
|
64
|
+
} catch {
|
|
65
|
+
// Error already reported via onError; state restored in applyCloningStrategy
|
|
66
|
+
}
|
|
96
67
|
return;
|
|
97
68
|
}
|
|
98
69
|
if (fileFormat === 'snapgene' || (fileFormat === '' && files[0].name.endsWith('.dna'))) {
|
|
99
|
-
|
|
100
|
-
const success = await loadSnapgeneHistory(files[0], source.id);
|
|
70
|
+
const success = await loadSnapgeneHistory(files[0], { source });
|
|
101
71
|
if (success) {
|
|
102
72
|
return;
|
|
103
73
|
}
|
|
@@ -110,7 +80,7 @@ function SourceFile({ source, requestStatus, sendPostRequest }) {
|
|
|
110
80
|
},
|
|
111
81
|
params: { circularize, sequence_file_format: fileFormat || null, ...coordinates },
|
|
112
82
|
};
|
|
113
|
-
|
|
83
|
+
sendSourceRequest({ endpoint: 'read_from_file', requestData, config, source });
|
|
114
84
|
};
|
|
115
85
|
|
|
116
86
|
const updateCoordinates = (value, isStart) => {
|
|
@@ -62,6 +62,52 @@ describe('<AssemblyIdSelector />', () => {
|
|
|
62
62
|
cy.wait('@getPairedAssemblyInfo');
|
|
63
63
|
cy.contains('Equivalent assembly GCF_000002945.3 has annotation').should('exist');
|
|
64
64
|
});
|
|
65
|
+
it('shows a warning when the paired accession lookup fails', () => {
|
|
66
|
+
const setAssemblyId = cy.spy().as('setAssemblyId');
|
|
67
|
+
const setHasAnnotation = cy.spy().as('setHasAnnotation');
|
|
68
|
+
const onAssemblyIdChange = cy.spy().as('onAssemblyIdChange');
|
|
69
|
+
|
|
70
|
+
cy.intercept('GET', 'https://api.ncbi.nlm.nih.gov/datasets/v2alpha/genome/accession/GCA_000002945.3/dataset_report*', {
|
|
71
|
+
statusCode: 200,
|
|
72
|
+
body: {
|
|
73
|
+
reports: [{
|
|
74
|
+
accession: 'GCA_000002945.3',
|
|
75
|
+
organism: {
|
|
76
|
+
tax_id: 559292,
|
|
77
|
+
organism_name: 'Saccharomyces cerevisiae'
|
|
78
|
+
},
|
|
79
|
+
annotation_info: undefined,
|
|
80
|
+
paired_accession: 'GCF_000002945.3',
|
|
81
|
+
assembly_info: {
|
|
82
|
+
assembly_status: 'current'
|
|
83
|
+
}
|
|
84
|
+
}]
|
|
85
|
+
}
|
|
86
|
+
}).as('getAssemblyInfo');
|
|
87
|
+
|
|
88
|
+
cy.intercept('GET', 'https://api.ncbi.nlm.nih.gov/datasets/v2alpha/genome/accession/GCF_000002945.3/dataset_report*', {
|
|
89
|
+
statusCode: 500,
|
|
90
|
+
body: {}
|
|
91
|
+
}).as('getPairedAssemblyInfo');
|
|
92
|
+
|
|
93
|
+
cy.mount(
|
|
94
|
+
<ConfigProvider config={config}>
|
|
95
|
+
<AssemblyIdSelector
|
|
96
|
+
setAssemblyId={setAssemblyId}
|
|
97
|
+
setHasAnnotation={setHasAnnotation}
|
|
98
|
+
onAssemblyIdChange={onAssemblyIdChange}
|
|
99
|
+
/>
|
|
100
|
+
</ConfigProvider>
|
|
101
|
+
);
|
|
102
|
+
cy.get('input').type('GCA_000002945.3');
|
|
103
|
+
cy.wait('@getAssemblyInfo');
|
|
104
|
+
cy.wait('@getPairedAssemblyInfo');
|
|
105
|
+
|
|
106
|
+
cy.contains('Could not check whether an equivalent assembly has annotation:').should('exist');
|
|
107
|
+
cy.contains('Equivalent assembly GCF_000002945.3 has annotation').should('not.exist');
|
|
108
|
+
cy.get('label').contains('Species').siblings('div').children('input')
|
|
109
|
+
.should('have.value', 'Saccharomyces cerevisiae - 559292');
|
|
110
|
+
});
|
|
65
111
|
it('handles NCBI being down displaying the right error', () => {
|
|
66
112
|
cy.intercept('GET', 'https://api.ncbi.nlm.nih.gov/datasets/v2alpha/genome/accession/GCA_000002945.3/dataset_report*', {
|
|
67
113
|
statusCode: 500,
|
|
@@ -5,8 +5,11 @@ import { getReferenceAssemblyId, taxonSuggest, geneSuggest, getInfoFromAssemblyI
|
|
|
5
5
|
import TextFieldValidate from '../form/TextFieldValidate';
|
|
6
6
|
import SubmitButtonBackendAPI from '../form/SubmitButtonBackendAPI';
|
|
7
7
|
import GetRequestMultiSelect from '../form/GetRequestMultiSelect';
|
|
8
|
-
import useHttpClient from '../../hooks/useHttpClient';
|
|
9
8
|
import { formatSequenceLocationString } from '@opencloning/utils/other';
|
|
9
|
+
import { getExternalHttpClient } from '@opencloning/utils/httpClients';
|
|
10
|
+
|
|
11
|
+
// External urls (not the backend): no auth token, only whitelisted urls
|
|
12
|
+
const httpClient = getExternalHttpClient();
|
|
10
13
|
|
|
11
14
|
function getGeneCoordsInfo(gene) {
|
|
12
15
|
const { range: geneRange, accession_version: accessionVersion } = gene.annotation.genomic_regions[0].gene_range;
|
|
@@ -59,7 +62,6 @@ function SpeciesPicker({ setSpecies, setAssemblyId }) {
|
|
|
59
62
|
}
|
|
60
63
|
|
|
61
64
|
function SequenceAccessionPicker({ assemblyAccession, setSequenceAccession }) {
|
|
62
|
-
const httpClient = useHttpClient();
|
|
63
65
|
|
|
64
66
|
const url = `https://api.ncbi.nlm.nih.gov/datasets/v2alpha/genome/accession/${assemblyAccession}/sequence_reports`
|
|
65
67
|
return (
|
|
@@ -80,7 +82,7 @@ function SequenceAccessionPicker({ assemblyAccession, setSequenceAccession }) {
|
|
|
80
82
|
}
|
|
81
83
|
|
|
82
84
|
// Extra component to be used in SourceGenomeRegion
|
|
83
|
-
function SourceGenomeRegionLocusOnReference({ source, requestStatus,
|
|
85
|
+
function SourceGenomeRegionLocusOnReference({ source, requestStatus, sendSourceRequest }) {
|
|
84
86
|
const { id: sourceId } = source;
|
|
85
87
|
const [gene, setGene] = React.useState(null);
|
|
86
88
|
const [species, setSpecies] = React.useState(null);
|
|
@@ -92,7 +94,7 @@ function SourceGenomeRegionLocusOnReference({ source, requestStatus, sendPostReq
|
|
|
92
94
|
event.preventDefault();
|
|
93
95
|
const requestData = formatBackendPayloadWithGene(assemblyId, gene, Number(upstreamBasesRef.current.value), Number(downstreamBasesRef.current.value));
|
|
94
96
|
requestData.id = sourceId;
|
|
95
|
-
|
|
97
|
+
sendSourceRequest({ endpoint: 'genome_coordinates', requestData, source });
|
|
96
98
|
};
|
|
97
99
|
|
|
98
100
|
// Reset gene when species changes
|
|
@@ -153,9 +155,11 @@ function AssemblyIdSelector({ setAssemblyId, setHasAnnotation = () => {}, onAsse
|
|
|
153
155
|
const [newerAssembly, setNewerAssembly] = React.useState(false);
|
|
154
156
|
const [species, setSpecies] = React.useState(null);
|
|
155
157
|
const [pairedAccessionWithAnnotation, setPairedAccessionWithAnnotation] = React.useState('');
|
|
158
|
+
const [pairedAccessionLookupFailed, setPairedAccessionLookupFailed] = React.useState(false);
|
|
156
159
|
|
|
157
160
|
const onChange = async (userInput, resp) => {
|
|
158
161
|
setPairedAccessionWithAnnotation('');
|
|
162
|
+
setPairedAccessionLookupFailed(false);
|
|
159
163
|
setSpecies(resp === null ? null : resp.species);
|
|
160
164
|
if (resp === null) {
|
|
161
165
|
setAssemblyId('');
|
|
@@ -172,9 +176,14 @@ function AssemblyIdSelector({ setAssemblyId, setHasAnnotation = () => {}, onAsse
|
|
|
172
176
|
setNewerAssembly(resp !== null && resp.newerAssembly);
|
|
173
177
|
onAssemblyIdChange();
|
|
174
178
|
if (resp !== null && !resp.hasAnnotation && resp.pairedAccession) {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
179
|
+
try {
|
|
180
|
+
const pairedAccessionInfo = await getInfoFromAssemblyId(resp.pairedAccession);
|
|
181
|
+
if (pairedAccessionInfo !== null && pairedAccessionInfo.hasAnnotation) {
|
|
182
|
+
setPairedAccessionWithAnnotation(resp.pairedAccession);
|
|
183
|
+
}
|
|
184
|
+
} catch (e) {
|
|
185
|
+
console.error(e);
|
|
186
|
+
setPairedAccessionLookupFailed(true);
|
|
178
187
|
}
|
|
179
188
|
}
|
|
180
189
|
};
|
|
@@ -194,13 +203,18 @@ function AssemblyIdSelector({ setAssemblyId, setHasAnnotation = () => {}, onAsse
|
|
|
194
203
|
Equivalent assembly <a href={`https://www.ncbi.nlm.nih.gov/datasets/genome/${pairedAccessionWithAnnotation}`} target="_blank" rel="noopener noreferrer">{pairedAccessionWithAnnotation}</a> has annotation.
|
|
195
204
|
</Alert>
|
|
196
205
|
)}
|
|
206
|
+
{pairedAccessionLookupFailed && (
|
|
207
|
+
<Alert severity="warning">
|
|
208
|
+
Could not check whether an equivalent assembly has annotation: Try deleting and retyping the assembly ID.
|
|
209
|
+
</Alert>
|
|
210
|
+
)}
|
|
197
211
|
{species && <KnownSpeciesField species={species} />}
|
|
198
212
|
</>
|
|
199
213
|
);
|
|
200
214
|
}
|
|
201
215
|
|
|
202
216
|
// Extra component to be used in SourceGenomeRegion
|
|
203
|
-
function SourceGenomeRegionLocusOnOther({ source, requestStatus,
|
|
217
|
+
function SourceGenomeRegionLocusOnOther({ source, requestStatus, sendSourceRequest }) {
|
|
204
218
|
const { id: sourceId } = source;
|
|
205
219
|
const [gene, setGene] = React.useState(null);
|
|
206
220
|
const [assemblyId, setAssemblyId] = React.useState('');
|
|
@@ -212,7 +226,7 @@ function SourceGenomeRegionLocusOnOther({ source, requestStatus, sendPostRequest
|
|
|
212
226
|
event.preventDefault();
|
|
213
227
|
const requestData = formatBackendPayloadWithGene(assemblyId, gene, Number(upstreamBasesRef.current.value), Number(downstreamBasesRef.current.value));
|
|
214
228
|
requestData.id = sourceId;
|
|
215
|
-
|
|
229
|
+
sendSourceRequest({ endpoint: 'genome_coordinates', requestData, source });
|
|
216
230
|
};
|
|
217
231
|
|
|
218
232
|
return (
|
|
@@ -235,7 +249,7 @@ function SourceGenomeRegionLocusOnOther({ source, requestStatus, sendPostRequest
|
|
|
235
249
|
}
|
|
236
250
|
|
|
237
251
|
// Extra component to be used in SourceGenomeRegion
|
|
238
|
-
function SourceGenomeRegionCustomCoordinates({ source, requestStatus,
|
|
252
|
+
function SourceGenomeRegionCustomCoordinates({ source, requestStatus, sendSourceRequest, selectionMode }) {
|
|
239
253
|
// https://eutils.ncbi.nlm.nih.gov/entrez/eutils/elink.fcgi?dbfrom=nuccore&db=assembly&id=CM041205.1&idtype=acc
|
|
240
254
|
const { id: sourceId } = source;
|
|
241
255
|
const [species, setSpecies] = React.useState(null);
|
|
@@ -285,7 +299,7 @@ function SourceGenomeRegionCustomCoordinates({ source, requestStatus, sendPostRe
|
|
|
285
299
|
assembly_accession: assemblyId || null,
|
|
286
300
|
coordinates: formatSequenceLocationString(coords.start, coords.end, coords.strand === 'plus' ? 1 : -1),
|
|
287
301
|
};
|
|
288
|
-
|
|
302
|
+
sendSourceRequest({ endpoint: 'genome_coordinates', requestData, source });
|
|
289
303
|
};
|
|
290
304
|
|
|
291
305
|
const onSequenceAccessionChange = async (userInput, resp) => {
|
|
@@ -446,7 +460,7 @@ function SourceGenomeRegionSelectGene({ gene, upstreamBasesRef, downstreamBasesR
|
|
|
446
460
|
);
|
|
447
461
|
}
|
|
448
462
|
|
|
449
|
-
function SourceGenomeRegion({ source, requestStatus,
|
|
463
|
+
function SourceGenomeRegion({ source, requestStatus, sendSourceRequest }) {
|
|
450
464
|
const { id: sourceId } = source;
|
|
451
465
|
const [selectionMode, setSelectionMode] = React.useState('');
|
|
452
466
|
const changeSelectionMode = (event) => { setSelectionMode(event.target.value); };
|
|
@@ -470,9 +484,9 @@ function SourceGenomeRegion({ source, requestStatus, sendPostRequest }) {
|
|
|
470
484
|
</Select>
|
|
471
485
|
</FormControl>
|
|
472
486
|
</form>
|
|
473
|
-
{selectionMode === 'reference_genome' && (<SourceGenomeRegionLocusOnReference {...{ source, requestStatus,
|
|
474
|
-
{selectionMode === 'other_assembly' && (<SourceGenomeRegionLocusOnOther {...{ source, requestStatus,
|
|
475
|
-
{selectionMode.startsWith('custom') && (<SourceGenomeRegionCustomCoordinates {...{ source, requestStatus,
|
|
487
|
+
{selectionMode === 'reference_genome' && (<SourceGenomeRegionLocusOnReference {...{ source, requestStatus, sendSourceRequest }} />)}
|
|
488
|
+
{selectionMode === 'other_assembly' && (<SourceGenomeRegionLocusOnOther {...{ source, requestStatus, sendSourceRequest }} />)}
|
|
489
|
+
{selectionMode.startsWith('custom') && (<SourceGenomeRegionCustomCoordinates {...{ source, requestStatus, sendSourceRequest, selectionMode }} />)}
|
|
476
490
|
|
|
477
491
|
</>
|
|
478
492
|
);
|