@opencloning/ui 1.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 +18 -0
- package/package.json +37 -0
- package/src/components/AppAlerts.jsx +19 -0
- package/src/components/CloningHistory.jsx +40 -0
- package/src/components/DataModelDisplayer.jsx +30 -0
- package/src/components/DescriptionEditor.jsx +68 -0
- package/src/components/DownloadCloningStrategyDialog.jsx +84 -0
- package/src/components/DownloadSequenceFileDialog.jsx +90 -0
- package/src/components/DragAndDropCloningHistoryWrapper.jsx +39 -0
- package/src/components/DraggableDialogPaper.jsx +16 -0
- package/src/components/EditSequenceNameDialog.jsx +90 -0
- package/src/components/ExternalServicesStatusCheck.jsx +92 -0
- package/src/components/HistoryLoadedDialog.jsx +49 -0
- package/src/components/LoadCloningHistoryWrapper.jsx +166 -0
- package/src/components/MainSequenceCheckBox.jsx +83 -0
- package/src/components/MainSequenceEditor.jsx +165 -0
- package/src/components/NetworkNode.jsx +159 -0
- package/src/components/NetworkTree.css +127 -0
- package/src/components/ObjectTable.jsx +24 -0
- package/src/components/OpenCloning.jsx +102 -0
- package/src/components/OverhangsDisplay.jsx +25 -0
- package/src/components/SequenceEditor.jsx +120 -0
- package/src/components/SequenceTab.jsx +14 -0
- package/src/components/TemplateSequence.jsx +38 -0
- package/src/components/annotation/PlannotateAnnotationReport.jsx +33 -0
- package/src/components/annotation/useUpdateAnnotationInMainSequence.js +39 -0
- package/src/components/assembler/AssemblePartWidget.jsx +252 -0
- package/src/components/assembler/Assembler.jsx +273 -0
- package/src/components/assembler/AssemblerPart.jsx +99 -0
- package/src/components/assembler/StopIcon.jsx +34 -0
- package/src/components/assembler/assembler_data2.json +50 -0
- package/src/components/assembler/assembly_component.module.css +81 -0
- package/src/components/assembler/moclo.json +110 -0
- package/src/components/assembler/sbol_visual_glyphs/LICENSE.html +21 -0
- package/src/components/assembler/sbol_visual_glyphs/assembly-scar.svg +63 -0
- package/src/components/assembler/sbol_visual_glyphs/cds-stop.svg +85 -0
- package/src/components/assembler/sbol_visual_glyphs/cds.svg +60 -0
- package/src/components/assembler/sbol_visual_glyphs/chromosomal-locus.svg +78 -0
- package/src/components/assembler/sbol_visual_glyphs/engineered-region.svg +56 -0
- package/src/components/assembler/sbol_visual_glyphs/five-prime-sticky-restriction-site.svg +56 -0
- package/src/components/assembler/sbol_visual_glyphs/origin-of-replication.svg +57 -0
- package/src/components/assembler/sbol_visual_glyphs/primer-binding-site.svg +59 -0
- package/src/components/assembler/sbol_visual_glyphs/promoter.svg +60 -0
- package/src/components/assembler/sbol_visual_glyphs/ribosome-entry-site.svg +56 -0
- package/src/components/assembler/sbol_visual_glyphs/specific-recombination-site.svg +59 -0
- package/src/components/assembler/sbol_visual_glyphs/terminator.svg +60 -0
- package/src/components/assembler/sbol_visual_glyphs/three-prime-sticky-restriction-site.svg +56 -0
- package/src/components/assembler/sbol_visual_glyphs.js +36 -0
- package/src/components/assembler/useAssembler.js +71 -0
- package/src/components/dummy/DummyInterface.js +41 -0
- package/src/components/dummy/GetSequenceFileAndDatabaseIdComponent.jsx +59 -0
- package/src/components/eLabFTW/ELabFTWCategorySelect.cy.jsx +86 -0
- package/src/components/eLabFTW/ELabFTWCategorySelect.jsx +43 -0
- package/src/components/eLabFTW/ELabFTWFileSelect.cy.jsx +43 -0
- package/src/components/eLabFTW/ELabFTWFileSelect.jsx +29 -0
- package/src/components/eLabFTW/ELabFTWResourceSelect.cy.jsx +107 -0
- package/src/components/eLabFTW/ELabFTWResourceSelect.jsx +23 -0
- package/src/components/eLabFTW/GetPrimerComponent.cy.jsx +261 -0
- package/src/components/eLabFTW/GetPrimerComponent.jsx +55 -0
- package/src/components/eLabFTW/GetSequenceFileAndDatabaseIdComponent.cy.jsx +184 -0
- package/src/components/eLabFTW/GetSequenceFileAndDatabaseIdComponent.jsx +62 -0
- package/src/components/eLabFTW/LoadHistoryComponent.cy.jsx +235 -0
- package/src/components/eLabFTW/LoadHistoryComponent.jsx +51 -0
- package/src/components/eLabFTW/PrimersNotInDatabaseComponent.cy.jsx +159 -0
- package/src/components/eLabFTW/PrimersNotInDatabaseComponent.jsx +54 -0
- package/src/components/eLabFTW/SubmitToDatabaseComponent.cy.jsx +185 -0
- package/src/components/eLabFTW/SubmitToDatabaseComponent.jsx +51 -0
- package/src/components/eLabFTW/common.js +26 -0
- package/src/components/eLabFTW/eLabFTWInterface.js +294 -0
- package/src/components/eLabFTW/eLabFTWInterface.test.js +839 -0
- package/src/components/eLabFTW/envValues.js +7 -0
- package/src/components/eLabFTW/utils.js +39 -0
- package/src/components/form/CustomFormHelperText.jsx +10 -0
- package/src/components/form/EnzymeMultiSelect.cy.jsx +61 -0
- package/src/components/form/EnzymeMultiSelect.jsx +34 -0
- package/src/components/form/GetRequestMultiSelect.jsx +107 -0
- package/src/components/form/LabelWithTooltip.jsx +16 -0
- package/src/components/form/PostRequestSelect.cy.jsx +70 -0
- package/src/components/form/PostRequestSelect.jsx +86 -0
- package/src/components/form/RequestStatusWrapper.jsx +17 -0
- package/src/components/form/RetryAlert.jsx +20 -0
- package/src/components/form/ServerErrorMessage.jsx +10 -0
- package/src/components/form/SubmitButtonBackendAPI.jsx +15 -0
- package/src/components/form/SubmitToDatabaseDialog.jsx +133 -0
- package/src/components/form/TextFieldValidate.jsx +67 -0
- package/src/components/form/ValidatedTextField.jsx +33 -0
- package/src/components/form/intermediates_disclaimer.svg +181 -0
- package/src/components/navigation/ButtonWithMenu.jsx +43 -0
- package/src/components/navigation/CustomTab.jsx +14 -0
- package/src/components/navigation/FeedbackDialog.jsx +34 -0
- package/src/components/navigation/GithubCornerRight.jsx +29 -0
- package/src/components/navigation/MainAppBar.css +26 -0
- package/src/components/navigation/MainAppBar.jsx +205 -0
- package/src/components/navigation/SelectExampleDialog.jsx +69 -0
- package/src/components/navigation/SelectTemplateDialog.jsx +107 -0
- package/src/components/navigation/TabPanel.jsx +28 -0
- package/src/components/navigation/VersionDialog.jsx +33 -0
- package/src/components/primers/CreatePrimerFromSequenceForm.jsx +42 -0
- package/src/components/primers/DownloadPrimersButton.jsx +104 -0
- package/src/components/primers/PrimerForm.css +14 -0
- package/src/components/primers/PrimerForm.jsx +107 -0
- package/src/components/primers/PrimerList.css +46 -0
- package/src/components/primers/PrimerList.cy.jsx +95 -0
- package/src/components/primers/PrimerList.jsx +126 -0
- package/src/components/primers/PrimerTableRow.cy.jsx +57 -0
- package/src/components/primers/PrimerTableRow.jsx +84 -0
- package/src/components/primers/SelectPrimerForm.jsx +66 -0
- package/src/components/primers/import_primers/ImportPrimersButton.jsx +101 -0
- package/src/components/primers/import_primers/ImportPrimersTable.jsx +51 -0
- package/src/components/primers/import_primers/PrimerDatabaseImportForm.jsx +107 -0
- package/src/components/primers/import_primers/styles.css +60 -0
- package/src/components/primers/primer_design/SequenceTabComponents/CollapsableLabel.jsx +31 -0
- package/src/components/primers/primer_design/SequenceTabComponents/GatewayRoiSelect.jsx +164 -0
- package/src/components/primers/primer_design/SequenceTabComponents/OrientationPicker.jsx +37 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignContext.jsx +369 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignEBIC.jsx +24 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignForm.jsx +29 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignGatewayBP.jsx +36 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignGibsonAssembly.jsx +26 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignHomologousRecombination.jsx +32 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignRestriction.jsx +25 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignSimplePair.jsx +25 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignStepper.jsx +53 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesigner.jsx +80 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerResultForm.jsx +49 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerSettingsForm.jsx +68 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerSpacerForm.jsx +84 -0
- package/src/components/primers/primer_design/SequenceTabComponents/RestrictionSpacerForm.jsx +85 -0
- package/src/components/primers/primer_design/SequenceTabComponents/StepNavigation.jsx +48 -0
- package/src/components/primers/primer_design/SequenceTabComponents/TabPanelEBICSettings.jsx +216 -0
- package/src/components/primers/primer_design/SequenceTabComponents/TabPanelResults.jsx +42 -0
- package/src/components/primers/primer_design/SequenceTabComponents/TabPanelSelectRoi.jsx +61 -0
- package/src/components/primers/primer_design/SequenceTabComponents/TabPannelSettings.jsx +59 -0
- package/src/components/primers/primer_design/SequenceTabComponents/primerDesignMinimalValues.json +5 -0
- package/src/components/primers/primer_design/SequenceTabComponents/useEBICPrimerDesignSettings.js +31 -0
- package/src/components/primers/primer_design/SequenceTabComponents/useEnzymePrimerDesignSettings.js +57 -0
- package/src/components/primers/primer_design/SequenceTabComponents/useGatewayPrimerDesignSettings.js +18 -0
- package/src/components/primers/primer_design/SequenceTabComponents/usePrimerDesignSettings.js +31 -0
- package/src/components/primers/primer_design/SourceComponents/PrimerDesignGatewayBP.jsx +88 -0
- package/src/components/primers/primer_design/SourceComponents/PrimerDesignGibsonAssembly.jsx +65 -0
- package/src/components/primers/primer_design/SourceComponents/PrimerDesignHomologousRecombination.jsx +84 -0
- package/src/components/primers/primer_design/SourceComponents/PrimerDesignSourceForm.jsx +74 -0
- package/src/components/primers/primer_design/common/NoAttPSitesError.jsx +31 -0
- package/src/components/primers/primer_details/PCRTable.cy.jsx +51 -0
- package/src/components/primers/primer_details/PCRTable.jsx +35 -0
- package/src/components/primers/primer_details/Primer3Figure.jsx +25 -0
- package/src/components/primers/primer_details/PrimerDetailsTds.jsx +39 -0
- package/src/components/primers/primer_details/PrimerInfoIcon.cy.jsx +137 -0
- package/src/components/primers/primer_details/PrimerInfoIcon.jsx +132 -0
- package/src/components/primers/primer_details/TableSection.jsx +17 -0
- package/src/components/primers/primer_details/primerDetailsFormatting.js +3 -0
- package/src/components/primers/primer_details/useMultiplePrimerDetails.js +29 -0
- package/src/components/primers/primer_details/usePCRDetails.js +47 -0
- package/src/components/primers/primer_details/usePrimerDetailsEndpoints.js +49 -0
- package/src/components/primers/primer_details/useSinglePrimerSequenceDetails.js +25 -0
- package/src/components/primers/primersToTabularFile.js +49 -0
- package/src/components/primers/primersToTabularFile.test.js +108 -0
- package/src/components/settings/SettingsTab.cy.jsx +267 -0
- package/src/components/settings/SettingsTab.jsx +170 -0
- package/src/components/sources/AssemblyPlanDisplayer.cy.jsx +22 -0
- package/src/components/sources/AssemblyPlanDisplayer.jsx +27 -0
- package/src/components/sources/CollectionSource.jsx +97 -0
- package/src/components/sources/FinishedSource.jsx +397 -0
- package/src/components/sources/KnownSourceErrors.jsx +50 -0
- package/src/components/sources/MultipleInputsSelector.jsx +63 -0
- package/src/components/sources/MultipleOutputsSelector.jsx +63 -0
- package/src/components/sources/NewSourceBox.jsx +37 -0
- package/src/components/sources/PCRUnitForm.jsx +102 -0
- package/src/components/sources/SingleInputSelector.jsx +36 -0
- package/src/components/sources/Source.jsx +125 -0
- package/src/components/sources/SourceAnnotation.jsx +44 -0
- package/src/components/sources/SourceAssembly.jsx +201 -0
- package/src/components/sources/SourceBox.css +18 -0
- package/src/components/sources/SourceBox.jsx +60 -0
- package/src/components/sources/SourceCopySequence.jsx +38 -0
- package/src/components/sources/SourceDatabase.jsx +28 -0
- package/src/components/sources/SourceFile.jsx +188 -0
- package/src/components/sources/SourceGenomeRegion.cy.jsx +131 -0
- package/src/components/sources/SourceGenomeRegion.jsx +486 -0
- package/src/components/sources/SourceHomologousRecombination.jsx +125 -0
- package/src/components/sources/SourceKnownGenomeRegion.jsx +60 -0
- package/src/components/sources/SourceManuallyTyped.jsx +116 -0
- package/src/components/sources/SourcePCRorHybridization.jsx +165 -0
- package/src/components/sources/SourcePolymeraseExtension.jsx +44 -0
- package/src/components/sources/SourceRepositoryId.jsx +409 -0
- package/src/components/sources/SourceRestriction.jsx +41 -0
- package/src/components/sources/SourceReverseComplement.jsx +33 -0
- package/src/components/sources/SourceTypeSelector.jsx +94 -0
- package/src/components/sources/SubSequenceDisplayer.jsx +70 -0
- package/src/components/sources/VerifyDeleteDialog.jsx +23 -0
- package/src/components/sources/repositoryMetadata.js +14 -0
- package/src/components/verification/LoadFromDatabaseButton.jsx +90 -0
- package/src/components/verification/SequencingFileRow.jsx +34 -0
- package/src/components/verification/VerificationFileDialog.cy.jsx +176 -0
- package/src/components/verification/VerificationFileDialog.jsx +248 -0
- package/src/config/defaultMainEditorProps.js +44 -0
- package/src/hooks/useAlerts.js +16 -0
- package/src/hooks/useBackendAPI.js +51 -0
- package/src/hooks/useBackendRoute.js +22 -0
- package/src/hooks/useDatabase.js +18 -0
- package/src/hooks/useDragAndDropFile.js +31 -0
- package/src/hooks/useGatewaySites.js +40 -0
- package/src/hooks/useHttpClient.js +12 -0
- package/src/hooks/useLoadDatabaseFile.js +108 -0
- package/src/hooks/useStoreEditor.js +101 -0
- package/src/hooks/useValidateState.js +43 -0
- package/vitest.config.js +18 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useSelector } from 'react-redux';
|
|
3
|
+
import { Alert } from '@mui/material';
|
|
4
|
+
import { getSubState } from '@opencloning/utils/network';
|
|
5
|
+
import ELabFTWCategorySelect from './ELabFTWCategorySelect';
|
|
6
|
+
|
|
7
|
+
function PrimersNotInDatabaseComponent({ id, submissionData, setSubmissionData }) {
|
|
8
|
+
const primerCategoryId = submissionData?.primerCategoryId;
|
|
9
|
+
const primers = useSelector((state) => {
|
|
10
|
+
const subState = getSubState(state, id, true);
|
|
11
|
+
return subState.primers.filter((p) => !p.database_id);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
if (primers.length === 0) return null;
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<Alert
|
|
18
|
+
severity={primerCategoryId ? 'success' : 'info'}
|
|
19
|
+
sx={{
|
|
20
|
+
marginTop: 2,
|
|
21
|
+
paddingY: 1,
|
|
22
|
+
width: '100%',
|
|
23
|
+
'& .MuiAlert-message': {
|
|
24
|
+
width: '100%',
|
|
25
|
+
},
|
|
26
|
+
}}
|
|
27
|
+
icon={false}
|
|
28
|
+
>
|
|
29
|
+
{!primerCategoryId && (
|
|
30
|
+
<>
|
|
31
|
+
<div>Do you want used primers to be saved to the database?</div>
|
|
32
|
+
<ul>
|
|
33
|
+
{primers.map((primer) => (
|
|
34
|
+
<li key={primer.id}>
|
|
35
|
+
{primer.name}
|
|
36
|
+
</li>
|
|
37
|
+
))}
|
|
38
|
+
</ul>
|
|
39
|
+
</>
|
|
40
|
+
)}
|
|
41
|
+
|
|
42
|
+
<ELabFTWCategorySelect
|
|
43
|
+
setCategory={(c) => {
|
|
44
|
+
setSubmissionData((prev) => ({ ...prev, primerCategoryId: c ? c.id : null }));
|
|
45
|
+
}}
|
|
46
|
+
label="Save primers as"
|
|
47
|
+
fullWidth
|
|
48
|
+
/>
|
|
49
|
+
|
|
50
|
+
</Alert>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export default PrimersNotInDatabaseComponent;
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Provider } from 'react-redux';
|
|
3
|
+
import { configureStore } from '@reduxjs/toolkit';
|
|
4
|
+
import SubmitToDatabaseComponent from './SubmitToDatabaseComponent';
|
|
5
|
+
import { eLabFTWHttpClient } from './common';
|
|
6
|
+
import { mockSequences, mockPrimers, mockSources, mockTeselaJsonCache } from '../../../../../tests/mockNetworkData';
|
|
7
|
+
import { clearAutocompleteValue } from '../../../../../cypress/e2e/common_functions';
|
|
8
|
+
|
|
9
|
+
const PRIMER_CATEGORY_ID = 3;
|
|
10
|
+
|
|
11
|
+
// Mock initial state with both primers and sequences
|
|
12
|
+
const defaultState = {
|
|
13
|
+
sequences: mockSequences,
|
|
14
|
+
sources: mockSources,
|
|
15
|
+
primers: mockPrimers,
|
|
16
|
+
teselaJsonCache: mockTeselaJsonCache,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const createTestStore = (cloningState) => configureStore({
|
|
20
|
+
reducer: {
|
|
21
|
+
cloning: (state = cloningState) => state,
|
|
22
|
+
},
|
|
23
|
+
preloadedState: { cloning: cloningState },
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe('<SubmitToDatabaseComponent />', () => {
|
|
27
|
+
it('Primers: initializes with primer name and handles updates', () => {
|
|
28
|
+
cy.stub(eLabFTWHttpClient, 'get')
|
|
29
|
+
.withArgs('/api/v2/items_types', { headers: { Authorization: 'test-read-key' }, params: { limit: 9999 } })
|
|
30
|
+
.resolves({
|
|
31
|
+
data: [
|
|
32
|
+
{ id: PRIMER_CATEGORY_ID, title: 'Primers' },
|
|
33
|
+
{ id: 2, title: 'Sequences' },
|
|
34
|
+
],
|
|
35
|
+
}).withArgs('/api/v2/info').resolves({
|
|
36
|
+
data: {
|
|
37
|
+
elabftw_version_int: 50200,
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
const store = createTestStore(defaultState);
|
|
41
|
+
const setSubmissionDataSpy = cy.spy().as('setSubmissionDataSpy');
|
|
42
|
+
|
|
43
|
+
cy.mount(
|
|
44
|
+
<Provider store={store}>
|
|
45
|
+
<SubmitToDatabaseComponent
|
|
46
|
+
id={mockPrimers[0].id}
|
|
47
|
+
resourceType="primer"
|
|
48
|
+
setSubmissionData={setSubmissionDataSpy}
|
|
49
|
+
/>
|
|
50
|
+
</Provider>,
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
// Should initialize with primer name
|
|
54
|
+
cy.get('input#resource_title').should('have.value', 'Primer1');
|
|
55
|
+
|
|
56
|
+
// Change title
|
|
57
|
+
cy.get('input#resource_title').clear();
|
|
58
|
+
cy.get('input#resource_title').type('Modified Primer');
|
|
59
|
+
|
|
60
|
+
// Select category
|
|
61
|
+
cy.get('input').last().click();
|
|
62
|
+
cy.get('li').contains('Primers').click();
|
|
63
|
+
|
|
64
|
+
// Should update submission data with both title and category
|
|
65
|
+
cy.get('@setSubmissionDataSpy').should((spy) => {
|
|
66
|
+
const updateFn = spy.lastCall.args[0];
|
|
67
|
+
const result = updateFn({ existingKey: 'value' });
|
|
68
|
+
expect(result).to.deep.equal({
|
|
69
|
+
existingKey: 'value',
|
|
70
|
+
categoryId: PRIMER_CATEGORY_ID,
|
|
71
|
+
title: 'Modified Primer',
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
// Clearing the field should set submission data to null
|
|
75
|
+
clearAutocompleteValue('Save primer as', 'div');
|
|
76
|
+
cy.get('@setSubmissionDataSpy').should((spy) => {
|
|
77
|
+
const result = spy.lastCall.args[0];
|
|
78
|
+
expect(result).equal(null);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('Sequences: initializes with sequence name and handles updates', () => {
|
|
83
|
+
cy.stub(eLabFTWHttpClient, 'get')
|
|
84
|
+
.withArgs('/api/v2/items_types', { headers: { Authorization: 'test-read-key' }, params: { limit: 9999 } })
|
|
85
|
+
.resolves({
|
|
86
|
+
data: [
|
|
87
|
+
{ id: PRIMER_CATEGORY_ID, title: 'Primers' },
|
|
88
|
+
{ id: 2, title: 'Sequences' },
|
|
89
|
+
],
|
|
90
|
+
}).withArgs('/api/v2/info').resolves({
|
|
91
|
+
data: {
|
|
92
|
+
elabftw_version_int: 50200,
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
const store = createTestStore(defaultState);
|
|
96
|
+
const setSubmissionDataSpy = cy.spy().as('setSubmissionDataSpy');
|
|
97
|
+
|
|
98
|
+
cy.mount(
|
|
99
|
+
<Provider store={store}>
|
|
100
|
+
<SubmitToDatabaseComponent
|
|
101
|
+
id={mockSequences[0].id}
|
|
102
|
+
resourceType="sequence"
|
|
103
|
+
setSubmissionData={setSubmissionDataSpy}
|
|
104
|
+
/>
|
|
105
|
+
</Provider>,
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
// Should initialize with primer name
|
|
109
|
+
cy.get('input#resource_title').should('have.value', 'Seq1');
|
|
110
|
+
|
|
111
|
+
// Change title
|
|
112
|
+
cy.get('input#resource_title').clear();
|
|
113
|
+
cy.get('input#resource_title').type('Modified Sequence');
|
|
114
|
+
|
|
115
|
+
// Select category
|
|
116
|
+
cy.get('input').last().click();
|
|
117
|
+
cy.get('li').contains('Sequences').click();
|
|
118
|
+
|
|
119
|
+
// Should update submission data with both title and category
|
|
120
|
+
cy.get('@setSubmissionDataSpy').should((spy) => {
|
|
121
|
+
const updateFn = spy.lastCall.args[0];
|
|
122
|
+
const result = updateFn({ existingKey: 'value' });
|
|
123
|
+
expect(result).to.deep.equal({
|
|
124
|
+
existingKey: 'value',
|
|
125
|
+
categoryId: 2,
|
|
126
|
+
title: 'Modified Sequence',
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
// Clearing the field should set submission data to null
|
|
130
|
+
clearAutocompleteValue('Save sequence as', 'div');
|
|
131
|
+
cy.get('@setSubmissionDataSpy').should((spy) => {
|
|
132
|
+
const result = spy.lastCall.args[0];
|
|
133
|
+
expect(result).equal(null);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('handles API errors in category loading', () => {
|
|
138
|
+
const store = createTestStore(defaultState);
|
|
139
|
+
let firstCall = true;
|
|
140
|
+
|
|
141
|
+
// Stub API to fail first time
|
|
142
|
+
cy.stub(eLabFTWHttpClient, 'get')
|
|
143
|
+
.withArgs('/api/v2/items_types', { headers: { Authorization: 'test-read-key' }, params: { limit: 9999 } })
|
|
144
|
+
.callsFake(() => {
|
|
145
|
+
if (firstCall) {
|
|
146
|
+
firstCall = false;
|
|
147
|
+
const error = new Error('Failed to fetch');
|
|
148
|
+
error.response = { status: 500 };
|
|
149
|
+
return Promise.reject(error);
|
|
150
|
+
}
|
|
151
|
+
return Promise.resolve({
|
|
152
|
+
data: [
|
|
153
|
+
{ id: PRIMER_CATEGORY_ID, title: 'Primers' },
|
|
154
|
+
{ id: 2, title: 'Sequences' },
|
|
155
|
+
],
|
|
156
|
+
})
|
|
157
|
+
}).withArgs('/api/v2/info', { headers: { Authorization: 'test-read-key' }}).resolves({
|
|
158
|
+
data: {
|
|
159
|
+
elabftw_version_int: 50200,
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
cy.mount(
|
|
164
|
+
<Provider store={store}>
|
|
165
|
+
<SubmitToDatabaseComponent
|
|
166
|
+
id={mockPrimers[0].id}
|
|
167
|
+
resourceType="primer"
|
|
168
|
+
setSubmissionData={cy.spy()}
|
|
169
|
+
/>
|
|
170
|
+
</Provider>,
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
// Should show error state
|
|
174
|
+
cy.get('.MuiAlert-colorError').should('exist');
|
|
175
|
+
cy.contains('Could not retrieve categories').should('exist');
|
|
176
|
+
|
|
177
|
+
// Click retry
|
|
178
|
+
cy.contains('Retry').click();
|
|
179
|
+
|
|
180
|
+
// Should show categories after retry
|
|
181
|
+
cy.get('input').last().click();
|
|
182
|
+
cy.get('li').contains('Primers').should('exist');
|
|
183
|
+
cy.get('li').contains('Sequences').should('exist');
|
|
184
|
+
});
|
|
185
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { FormControl, TextField } from '@mui/material';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { useSelector } from 'react-redux';
|
|
4
|
+
import ELabFTWCategorySelect from './ELabFTWCategorySelect';
|
|
5
|
+
|
|
6
|
+
function SubmitToDatabaseComponent({ id, setSubmissionData, resourceType }) {
|
|
7
|
+
const name = useSelector((state) => {
|
|
8
|
+
if (resourceType === 'primer') {
|
|
9
|
+
return state.cloning.primers.find((p) => p.id === id).name;
|
|
10
|
+
}
|
|
11
|
+
return state.cloning.teselaJsonCache[id].name;
|
|
12
|
+
});
|
|
13
|
+
const [title, setTitle] = React.useState(name);
|
|
14
|
+
const [category, setCategory] = React.useState(null);
|
|
15
|
+
|
|
16
|
+
React.useEffect(() => {
|
|
17
|
+
setTitle(name);
|
|
18
|
+
}, [name]);
|
|
19
|
+
|
|
20
|
+
React.useEffect(() => {
|
|
21
|
+
if (category && title) {
|
|
22
|
+
// We do this not overwrite primerCategoryId, set from a different component
|
|
23
|
+
setSubmissionData((prev) => ({ ...prev, categoryId: category.id, title }));
|
|
24
|
+
} else {
|
|
25
|
+
setSubmissionData(null);
|
|
26
|
+
}
|
|
27
|
+
}, [category, title]);
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<>
|
|
31
|
+
<FormControl fullWidth sx={{ mb: 2 }}>
|
|
32
|
+
<TextField
|
|
33
|
+
autoFocus
|
|
34
|
+
required
|
|
35
|
+
id="resource_title"
|
|
36
|
+
label="Resource title"
|
|
37
|
+
variant="standard"
|
|
38
|
+
value={title}
|
|
39
|
+
onChange={(e) => setTitle(e.target.value)}
|
|
40
|
+
/>
|
|
41
|
+
</FormControl>
|
|
42
|
+
<ELabFTWCategorySelect
|
|
43
|
+
fullWidth
|
|
44
|
+
label={`Save ${resourceType} as`}
|
|
45
|
+
setCategory={setCategory}
|
|
46
|
+
/>
|
|
47
|
+
</>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export default SubmitToDatabaseComponent;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import { readApiKey, writeApiKey, baseUrl as envBaseUrl } from './envValues';
|
|
3
|
+
|
|
4
|
+
export const baseUrl = envBaseUrl;
|
|
5
|
+
export const readHeaders = readApiKey ? { Authorization: readApiKey } : {};
|
|
6
|
+
export const writeHeaders = writeApiKey ? { Authorization: writeApiKey } : {};
|
|
7
|
+
|
|
8
|
+
export const eLabFTWHttpClient = axios.create({
|
|
9
|
+
baseURL: baseUrl,
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export const makeSequenceMetadata = (sequence) => JSON.stringify({
|
|
13
|
+
extra_fields: {
|
|
14
|
+
sequence: {
|
|
15
|
+
type: 'text',
|
|
16
|
+
value: sequence,
|
|
17
|
+
group_id: null,
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
export const getELabFTWVersion = async () => {
|
|
23
|
+
const url = `/api/v2/info`;
|
|
24
|
+
const resp = await eLabFTWHttpClient.get(url, { headers: readHeaders });
|
|
25
|
+
return resp.data.elabftw_version_int;
|
|
26
|
+
};
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import SaveIcon from '@mui/icons-material/Save';
|
|
2
|
+
import LinkIcon from '@mui/icons-material/Link';
|
|
3
|
+
import GetSequenceFileAndDatabaseIdComponent from './GetSequenceFileAndDatabaseIdComponent';
|
|
4
|
+
import SubmitToDatabaseComponent from './SubmitToDatabaseComponent';
|
|
5
|
+
import PrimersNotInDatabaseComponent from './PrimersNotInDatabaseComponent';
|
|
6
|
+
import GetPrimerComponent from './GetPrimerComponent';
|
|
7
|
+
import { eLabFTWHttpClient, writeHeaders, readHeaders, baseUrl, getELabFTWVersion } from './common';
|
|
8
|
+
import { getFileFromELabFTW, error2String } from './utils';
|
|
9
|
+
import LoadHistoryComponent from './LoadHistoryComponent';
|
|
10
|
+
|
|
11
|
+
async function deleteResource(resourceId) {
|
|
12
|
+
const url = `/api/v2/items/${resourceId}`;
|
|
13
|
+
// eLabFTW requires application/json for delete requests, axios seems to require a data field for it to work
|
|
14
|
+
const resp = await eLabFTWHttpClient.delete(url, { data: {}, headers: { ...writeHeaders, 'Content-Type': 'application/json' } });
|
|
15
|
+
return resp.data;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const linkToParent = async (childId, parentId) => {
|
|
19
|
+
await eLabFTWHttpClient.post(
|
|
20
|
+
`/api/v2/items/${childId}/items_links/${parentId}`,
|
|
21
|
+
{},
|
|
22
|
+
{ headers: writeHeaders },
|
|
23
|
+
);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const createResource = async (categoryId) => {
|
|
27
|
+
const eLabFTWVersion = await getELabFTWVersion();
|
|
28
|
+
const categoryKey = eLabFTWVersion && eLabFTWVersion >= 50300 ? 'category' : 'category_id';
|
|
29
|
+
const createdItemResponse = await eLabFTWHttpClient.post(
|
|
30
|
+
'/api/v2/items',
|
|
31
|
+
{
|
|
32
|
+
[categoryKey]: categoryId,
|
|
33
|
+
},
|
|
34
|
+
{ headers: writeHeaders },
|
|
35
|
+
);
|
|
36
|
+
return Number(createdItemResponse.headers.location.split('/').pop());
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const patchResource = async (resourceId, title, metadata = undefined) => eLabFTWHttpClient.patch(
|
|
40
|
+
`/api/v2/items/${resourceId}`,
|
|
41
|
+
{ title, ...(metadata !== undefined && { metadata }) },
|
|
42
|
+
{ headers: writeHeaders },
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
async function submitPrimerToDatabase({ submissionData: { title, categoryId }, primer, linkedSequenceId = null }) {
|
|
46
|
+
let resourceId;
|
|
47
|
+
try {
|
|
48
|
+
resourceId = await createResource(categoryId);
|
|
49
|
+
} catch (e) {
|
|
50
|
+
console.error(e);
|
|
51
|
+
throw new Error(`Error creating primer: ${error2String(e)}`);
|
|
52
|
+
}
|
|
53
|
+
const metadata = JSON.stringify({ extra_fields: { sequence: { type: 'text', value: primer.sequence, group_id: null } } });
|
|
54
|
+
let stage;
|
|
55
|
+
try {
|
|
56
|
+
stage = 'naming primer';
|
|
57
|
+
await patchResource(resourceId, title, metadata);
|
|
58
|
+
if (linkedSequenceId) {
|
|
59
|
+
stage = 'linking to sequence';
|
|
60
|
+
await linkToParent(linkedSequenceId, resourceId);
|
|
61
|
+
}
|
|
62
|
+
} catch (e) {
|
|
63
|
+
console.error(e);
|
|
64
|
+
try {
|
|
65
|
+
await deleteResource(resourceId);
|
|
66
|
+
} catch (e2) {
|
|
67
|
+
console.error(e2);
|
|
68
|
+
throw new Error(`There was an error (${error2String(e2)}) while trying to delete primer with id ${resourceId} after an error ${stage}.`);
|
|
69
|
+
}
|
|
70
|
+
throw new Error(`Error ${stage}: ${error2String(e)}`);
|
|
71
|
+
}
|
|
72
|
+
return resourceId;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function uploadTextFileToResource(resourceId, fileName, textContent, comment) {
|
|
76
|
+
const blob = new Blob([textContent], { type: 'text/plain' });
|
|
77
|
+
const formData = new FormData();
|
|
78
|
+
formData.append('file', blob, fileName);
|
|
79
|
+
formData.append('comment', comment);
|
|
80
|
+
const response = await eLabFTWHttpClient.post(`/api/v2/items/${resourceId}/uploads`, formData, { headers: writeHeaders });
|
|
81
|
+
return Number(response.headers.location.split('/').pop());
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function submitSequenceToDatabase({ submissionData: { title, categoryId, primerCategoryId }, substate, id }) {
|
|
85
|
+
/**
|
|
86
|
+
* Submit a sequence to eLabFTW database
|
|
87
|
+
* @param {Object} params - The parameters object
|
|
88
|
+
* @param {Object} params.submissionData - Data needed for submission
|
|
89
|
+
* @param {string} params.submissionData.title - Title of the sequence
|
|
90
|
+
* @param {number} params.submissionData.categoryId - Category ID in eLabFTW
|
|
91
|
+
* @param {Object} params.substate - The substate containing sequence data
|
|
92
|
+
* @param {Array} params.substate.sources - Array of source objects
|
|
93
|
+
* @param {Array} params.substate.primers - Array of primer objects
|
|
94
|
+
* @param {Array} params.substate.sequences - Array of sequence objects
|
|
95
|
+
* @param {string} params.id - ID of the sequence to submit
|
|
96
|
+
* @returns {Promise<Object>}
|
|
97
|
+
*/
|
|
98
|
+
|
|
99
|
+
const { sources, primers, sequences, appInfo } = substate;
|
|
100
|
+
const { backendVersion, schemaVersion, frontendVersion } = appInfo;
|
|
101
|
+
|
|
102
|
+
const sequence2export = sequences.find((e) => e.id === id);
|
|
103
|
+
const parentSource = sources.find((s) => s.id === id);
|
|
104
|
+
if (parentSource.database_id) {
|
|
105
|
+
throw new Error('Sequence already has a database_id');
|
|
106
|
+
}
|
|
107
|
+
// Get ancestor sources that are database sources to link to the sequence
|
|
108
|
+
const parentDatabaseSources = sources.filter((source) => source.database_id);
|
|
109
|
+
const parentResourceIds = parentDatabaseSources.map((source) => source.database_id);
|
|
110
|
+
const primerIds = primers.map((p) => p.id);
|
|
111
|
+
|
|
112
|
+
// Link and/or add used primers
|
|
113
|
+
const newPrimersToSave = [];
|
|
114
|
+
const existingPrimersToLink = primers.filter((p) => primerIds.includes(p.id) && p.database_id).map((p) => p.database_id);
|
|
115
|
+
if (primerCategoryId) {
|
|
116
|
+
newPrimersToSave.push(...primers.filter((p) => primerIds.includes(p.id) && !p.database_id));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Create and name the resource
|
|
120
|
+
let resourceId;
|
|
121
|
+
try {
|
|
122
|
+
resourceId = await createResource(categoryId);
|
|
123
|
+
} catch (e) {
|
|
124
|
+
console.error(e);
|
|
125
|
+
throw new Error(`Error creating resource: ${error2String(e)}`);
|
|
126
|
+
}
|
|
127
|
+
let stage;
|
|
128
|
+
let newPrimerDatabaseIds = [];
|
|
129
|
+
try {
|
|
130
|
+
// Patch the resource with the title
|
|
131
|
+
stage = 'setting resource title';
|
|
132
|
+
await patchResource(resourceId, title);
|
|
133
|
+
|
|
134
|
+
// Add the links to parent Resources
|
|
135
|
+
stage = 'linking to parent resources';
|
|
136
|
+
await Promise.all(parentResourceIds.map((parentId) => linkToParent(resourceId, parentId)));
|
|
137
|
+
|
|
138
|
+
// Add the links to the existing primers
|
|
139
|
+
stage = 'linking to existing primers';
|
|
140
|
+
await Promise.all(existingPrimersToLink.map((primerId) => linkToParent(resourceId, primerId)));
|
|
141
|
+
|
|
142
|
+
// Add the new primers to the database and link them to the resource
|
|
143
|
+
stage = 'submitting new primers';
|
|
144
|
+
newPrimerDatabaseIds = await Promise.all(newPrimersToSave.map((primer) => submitPrimerToDatabase({ submissionData: { title: primer.name, categoryId: primerCategoryId }, primer, linkedSequenceId: resourceId })));
|
|
145
|
+
const primerMappings = newPrimerDatabaseIds.map((databaseId, index) => ({ databaseId, localId: newPrimersToSave[index].id }));
|
|
146
|
+
|
|
147
|
+
// Deep-copy primers and update the primers with the database IDs before storing the history
|
|
148
|
+
const primersCopy = primers.map((p) => ({ ...p }));
|
|
149
|
+
primerMappings.forEach(({ databaseId: dbId, localId }) => {
|
|
150
|
+
primersCopy.find((p) => p.id === localId).database_id = dbId;
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const cloningStrategy = {
|
|
154
|
+
sources,
|
|
155
|
+
primers: primersCopy,
|
|
156
|
+
sequences,
|
|
157
|
+
backend_version: backendVersion,
|
|
158
|
+
schema_version: schemaVersion,
|
|
159
|
+
frontend_version: frontendVersion,
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
// Add the sequence and history files to the resource
|
|
163
|
+
stage = 'uploading sequence file';
|
|
164
|
+
await uploadTextFileToResource(resourceId, `${title}.gb`, sequence2export.file_content, 'resource sequence - generated by OpenCloning');
|
|
165
|
+
stage = 'uploading history file';
|
|
166
|
+
await uploadTextFileToResource(resourceId, `${title}_history.json`, JSON.stringify(cloningStrategy), 'history file - generated by OpenCloning');
|
|
167
|
+
// Format output values
|
|
168
|
+
return { primerMappings, databaseId: resourceId };
|
|
169
|
+
} catch (e) {
|
|
170
|
+
console.error(e);
|
|
171
|
+
let primersDeleted = false;
|
|
172
|
+
try {
|
|
173
|
+
await Promise.all(newPrimerDatabaseIds.map(deleteResource));
|
|
174
|
+
primersDeleted = true;
|
|
175
|
+
await deleteResource(resourceId);
|
|
176
|
+
} catch (e2) {
|
|
177
|
+
console.error(e2);
|
|
178
|
+
if (primersDeleted) {
|
|
179
|
+
throw new Error(`There was an error (${error2String(e2)}) while trying to delete the sequence with id ${resourceId} after an error ${stage}.`);
|
|
180
|
+
}
|
|
181
|
+
throw new Error(`There was an error (${error2String(e2)}) while trying to delete newly created primers linked to sequence with id ${resourceId} after an error ${stage}.`);
|
|
182
|
+
}
|
|
183
|
+
throw new Error(`Error ${stage}: ${error2String(e)}`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function isSubmissionDataValid(submissionData) {
|
|
188
|
+
// This function is necessary because you might be setting submissionData from multiple components
|
|
189
|
+
return Boolean(submissionData.title && submissionData.categoryId);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async function loadSequenceFromUrlParams(urlParams) {
|
|
193
|
+
const { item_id: itemId, file_id: fileId } = urlParams;
|
|
194
|
+
|
|
195
|
+
if (itemId && fileId) {
|
|
196
|
+
const url = `/api/v2/items/${itemId}/uploads/${fileId}`;
|
|
197
|
+
let fileInfo;
|
|
198
|
+
try {
|
|
199
|
+
const resp = await eLabFTWHttpClient.get(url, { headers: readHeaders });
|
|
200
|
+
fileInfo = resp.data;
|
|
201
|
+
} catch (e) {
|
|
202
|
+
throw new Error(`${error2String(e)}`);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// getFileFromELabFTW already handles errors
|
|
206
|
+
const file = await getFileFromELabFTW(itemId, fileInfo);
|
|
207
|
+
return { file, databaseId: itemId };
|
|
208
|
+
}
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async function getPrimer(databaseId) {
|
|
213
|
+
const url = `/api/v2/items/${databaseId}`;
|
|
214
|
+
try {
|
|
215
|
+
const resp = await eLabFTWHttpClient.get(url, { headers: readHeaders });
|
|
216
|
+
resp.data.metadata = JSON.parse(resp.data.metadata);
|
|
217
|
+
return { name: resp.data.title, database_id: databaseId, sequence: resp.data.metadata.extra_fields.sequence?.value };
|
|
218
|
+
} catch (e) {
|
|
219
|
+
console.error(e);
|
|
220
|
+
if (e.code === 'ERR_NETWORK') {
|
|
221
|
+
throw new Error(`Error getting primer: ${error2String(e)}`);
|
|
222
|
+
}
|
|
223
|
+
throw new Error(`Error getting primer with id ${databaseId}, it might have been deleted or you can no longer access it`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async function getSequenceName(databaseId) {
|
|
228
|
+
const url = `/api/v2/items/${databaseId}`;
|
|
229
|
+
try {
|
|
230
|
+
const resp = await eLabFTWHttpClient.get(url, { headers: readHeaders });
|
|
231
|
+
return resp.data.title;
|
|
232
|
+
} catch (e) {
|
|
233
|
+
console.error(e);
|
|
234
|
+
if (e.code === 'ERR_NETWORK') {
|
|
235
|
+
throw new Error(`Error getting sequence name: ${error2String(e)}`);
|
|
236
|
+
}
|
|
237
|
+
throw new Error(`Error getting name of sequence with id ${databaseId}, it might have been deleted or you can no longer access it`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async function getSequencingFiles(databaseId) {
|
|
242
|
+
// This function should return an array of objects:
|
|
243
|
+
// name: the name of the file
|
|
244
|
+
// getFile: an async function that returns the file content
|
|
245
|
+
const url = `/api/v2/items/${databaseId}/uploads`;
|
|
246
|
+
try {
|
|
247
|
+
const resp = await eLabFTWHttpClient.get(url, { headers: readHeaders });
|
|
248
|
+
return resp.data.map((fileInfo) => ({
|
|
249
|
+
name: fileInfo.real_name,
|
|
250
|
+
getFile: async () => getFileFromELabFTW(databaseId, fileInfo),
|
|
251
|
+
}));
|
|
252
|
+
} catch (e) {
|
|
253
|
+
console.error(e);
|
|
254
|
+
throw new Error(`${error2String(e)}`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export default {
|
|
259
|
+
// Name of the database interface
|
|
260
|
+
name: 'eLabFTW',
|
|
261
|
+
// Returns a link to the sequence in the database
|
|
262
|
+
getSequenceLink: (databaseId) => `${baseUrl}/database.php?mode=view&id=${databaseId}`,
|
|
263
|
+
// Returns a link to the primer in the database
|
|
264
|
+
getPrimerLink: (databaseId) => `${baseUrl}/database.php?mode=view&id=${databaseId}`,
|
|
265
|
+
// Component for selecting and loading sequence files from the database
|
|
266
|
+
GetSequenceFileAndDatabaseIdComponent,
|
|
267
|
+
// Component for selecting and loading primers from the database
|
|
268
|
+
GetPrimerComponent,
|
|
269
|
+
// Component for submitting resources to the database
|
|
270
|
+
SubmitToDatabaseComponent,
|
|
271
|
+
// Component for handling primers not yet in database
|
|
272
|
+
PrimersNotInDatabaseComponent,
|
|
273
|
+
// Function to submit a primer to the database
|
|
274
|
+
submitPrimerToDatabase,
|
|
275
|
+
// Function to submit a sequence and its history to the database
|
|
276
|
+
submitSequenceToDatabase,
|
|
277
|
+
// Function to validate submission data
|
|
278
|
+
isSubmissionDataValid,
|
|
279
|
+
// Icon displayed on the node corner to submit
|
|
280
|
+
SubmitIcon: SaveIcon,
|
|
281
|
+
// Icon displayed on the node corner for sequences in the database
|
|
282
|
+
DatabaseIcon: LinkIcon,
|
|
283
|
+
// OPTIONAL =======================================================================
|
|
284
|
+
// Component for loading history from the database (can be hook-like does not have to render anything)
|
|
285
|
+
LoadHistoryComponent,
|
|
286
|
+
// Function to load sequences from url parameters
|
|
287
|
+
loadSequenceFromUrlParams,
|
|
288
|
+
// Function to get the primer ({name, database_id, sequence}) from the database
|
|
289
|
+
getPrimer,
|
|
290
|
+
// Function to get the name of a sequence from the database
|
|
291
|
+
getSequenceName,
|
|
292
|
+
// Function to get the sequencing files from the database, see docs for what the return value should be
|
|
293
|
+
getSequencingFiles,
|
|
294
|
+
};
|