Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
manulera authored Oct 29, 2024
1 parent f383ff4 commit 644f0b5
Show file tree
Hide file tree
Showing 6 changed files with 205 additions and 25 deletions.
41 changes: 41 additions & 0 deletions cypress/e2e/source_repository_id.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,47 @@ describe('RepositoryId Source', () => {
// links to the /edit
cy.get('li#source-1 a[href="https://benchling.com/siverson/f/lib_B94YxDHhQh-cidar-moclo-library/seq_dh1FrJTc-b0015_dh/edit"]').should('be.visible');
});
it.only('works with SnapGene', () => {
clickMultiSelectOption('Select repository', 'SnapGene', 'li#source-1');
// When clicking in the input, displays message
cy.get('li#source-1').contains('Plasmid name').siblings('div').first()
.click();
cy.get('div[role="presentation"]').contains('Type at least 3 characters to search').should('be.visible');

// Click outside
cy.get('body').click(0, 0);

// Cannot submit if nothing selected
cy.get('li#source-1 button').contains('Submit').should('not.exist');

setInputValue('Plasmid name', 'pfa', 'li#source-1');
clickMultiSelectOption('Plasmid name', 'pFastBac1', 'li#source-1');

// Can clear the input
cy.get('li#source-1').contains('Plasmid name').siblings('div').children('input')
.click();
cy.get('li#source-1 button.MuiAutocomplete-clearIndicator').click();
cy.get('body').click(0, 0);

// When clicking in the input, displays message
cy.get('li#source-1').contains('Plasmid name').siblings('div').first()
.click();
cy.get('div[role="presentation"]').contains('Type at least 3 characters to search').should('be.visible');
cy.get('body').click(0, 0);

// Can submit
setInputValue('Plasmid name', 'pfa', 'li#source-1');
clickMultiSelectOption('Plasmid name', 'pFastBac1', 'li#source-1');
cy.get('li#source-1 button').contains('Submit').click();

// Shows the plasmid name
cy.get('li#sequence-2').contains('pFastBac1');
cy.get('li#sequence-2').contains('4776 bps');

// Links to https://www.snapgene.com/plasmids/insect_cell_vectors/pFastBac1
cy.get('li#source-1 a[href="https://www.snapgene.com/plasmids/insect_cell_vectors/pFastBac1"]').should('be.visible');
cy.get('li#source-1').contains('Plasmid pFastBac1 from SnapGene').should('be.visible');
});
it('handles empty value and wrong IDs', () => {
// AddGene =================================
clickMultiSelectOption('Select repository', 'AddGene', 'li#source-1');
Expand Down
20 changes: 20 additions & 0 deletions src/components/sources/FinishedSource.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,24 @@ function BenchlingMessage({ source }) {
);
}

function SnapGenePlasmidMessage({ source }) {
const { repository_id: repositoryId } = source;
const [plasmidSet, plasmidName] = repositoryId.split('/');
return (
<>
Plasmid
{' '}
<strong>
<a href={`https://www.snapgene.com/plasmids/${plasmidSet}/${plasmidName}`} target="_blank" rel="noopener noreferrer">
{plasmidName}
</a>
</strong>
{' '}
from SnapGene
</>
);
}

function RepositoryIdMessage({ source }) {
const { repository_name: repositoryName } = source;
let url = '';
Expand Down Expand Up @@ -84,6 +102,8 @@ function FinishedSource({ sourceId }) {
break;
case 'BenchlingUrlSource': message = <BenchlingMessage source={source} />;
break;
case 'SnapGenePlasmidSource': message = <SnapGenePlasmidMessage source={source} />;
break;
case 'GenomeCoordinatesSource':
message = (
<>
Expand Down
2 changes: 2 additions & 0 deletions src/components/sources/Source.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ function Source({ sourceId }) {
specificSource = <SourceRepositoryId {...{ source, requestStatus, sendPostRequest }} />; break;
case 'AddGeneIdSource':
specificSource = <SourceRepositoryId {...{ source, requestStatus, sendPostRequest, initialSelectedRepository: 'addgene' }} />; break;
case 'SnapGenePlasmidSource':
specificSource = <SourceRepositoryId {...{ source, requestStatus, sendPostRequest, initialSelectedRepository: 'snapgene' }} />; break;
case 'LigationSource':
specificSource = <SourceAssembly {...{ source, requestStatus, sendPostRequest }} />; break;
case 'GibsonAssemblySource':
Expand Down
164 changes: 140 additions & 24 deletions src/components/sources/SourceRepositoryId.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import MenuItem from '@mui/material/MenuItem';
import TextField from '@mui/material/TextField';
import FormControl from '@mui/material/FormControl';
import Select from '@mui/material/Select';
import { Alert } from '@mui/material';
import { Alert, Autocomplete } from '@mui/material';
import axios from 'axios';
import SubmitButtonBackendAPI from '../form/SubmitButtonBackendAPI';

function validateRepositoryId(repositoryId, repository) {
Expand Down Expand Up @@ -37,12 +38,119 @@ const inputLabels = {
benchling: 'Benchling URL',
};

const checkOption = (option, inputValue) => option.name.toLowerCase().includes(inputValue.toLowerCase());
const formatOption = (option, plasmidSet, plasmidSetName) => ({ name: option.name, path: `${plasmidSet}/${option.subpath}`, plasmidSetName, plasmidSet });

function SnapGenePlasmidSelector({ setInputValue }) {
const url = 'https://raw.githubusercontent.com/manulera/SnapGene_crawler/master/index.json';
const [userInput, setUserInput] = React.useState('');
const [data, setData] = React.useState(null);
const [options, setOptions] = React.useState([]);
// const [filter, setFilter] = React.useState('');

React.useEffect(() => {
const fetchOptions = async () => {
const resp = await axios.get(url);
setData(resp.data);
};
fetchOptions();
}, []);

const onInputChange = (newInputValue) => {
if (newInputValue === undefined) {
// When clearing the input via x button
setUserInput('');
setOptions([]);
return;
}
setUserInput(newInputValue);
if (newInputValue.length < 3) {
setOptions([]);
return;
}
// if (filter !== '') {
// setOptions(data[filter].plasmids
// .filter((option) => checkOption(option, newInputValue))
// .map((option) => formatOption(option, filter, data[filter].name)));
// } else {
setOptions(Object.entries(data)
.flatMap(([plasmidSet, category]) => category.plasmids
.filter((option) => checkOption(option, newInputValue))
.map((option) => formatOption(option, plasmidSet, data[plasmidSet].name))));
// }
};

if (data === null) {
return <div>Loading...</div>;
}

const selectedOption = options.find((option) => option.name === userInput);

return (
<>
{/* <FormControl fullWidth>
<InputLabel id="plasmid-set-label">Filter by set</InputLabel>
<Select
value={filter}
onChange={(event) => setFilter(event.target.value)}
labelId="plasmid-set-label"
label="Filter by set"
>
<MenuItem key="all" value="">All</MenuItem>
{Object.keys(data).map((plasmidSet) => (
<MenuItem key={plasmidSet} value={plasmidSet}>{data[plasmidSet].name}</MenuItem>
))}
</Select>
</FormControl> */}

<FormControl fullWidth>
<Autocomplete
onChange={(event, value) => { onInputChange(value?.name); value && setInputValue(value.path); }}
// Change options only when input changes (not when an option is picked)
onInputChange={(event, newInputValue, reason) => (reason === 'input') && onInputChange(newInputValue)}
id="tags-standard"
options={options}
noOptionsText={(
<div>
Type at least 3 characters to search, see
{' '}
<a href="https://www.snapgene.com/plasmids" target="_blank" rel="noopener noreferrer">SnapGene plasmids</a>
{' '}
for options
</div>
)}
getOptionLabel={(o) => o.name}
isOptionEqualToValue={(o1, o2) => o1.subpath === o2.subpath}
inputValue={userInput}
renderInput={(params) => (
<TextField
{...params}
label="Plasmid name"
/>
)}
/>
</FormControl>
{selectedOption && (
<Alert severity="info" sx={{ mb: 1 }}>
Plasmid
{' '}
<a href={`https://www.snapgene.com/plasmids/${selectedOption.path}`}>{selectedOption.name}</a>
{' '}
from set
{' '}
<a href={`https://www.snapgene.com/plasmids/${selectedOption.plasmidSet}`}>{selectedOption.plasmidSetName}</a>
</Alert>
)}
</>
);
}

// A component providing an interface for the user to type a repository ID
// and get a sequence
function SourceRepositoryId({ source, requestStatus, sendPostRequest }) {
const { id: sourceId } = source;
const [inputValue, setInputValue] = React.useState('');
const [selectedRepository, setSelectedRepository] = React.useState(source.repository_name || '');
const [selectedRepository, setSelectedRepository] = React.useState(source.repository_name || 'snapgene');
const [error, setError] = React.useState('');

React.useEffect(() => {
Expand All @@ -61,6 +169,7 @@ function SourceRepositoryId({ source, requestStatus, sendPostRequest }) {
setError('');
}
}, [inputValue]);

const onSubmit = (event) => {
event.preventDefault();
let repositoryId = inputValue;
Expand All @@ -85,31 +194,38 @@ function SourceRepositoryId({ source, requestStatus, sendPostRequest }) {
<MenuItem value="addgene">AddGene</MenuItem>
<MenuItem value="genbank">GenBank</MenuItem>
<MenuItem value="benchling">Benchling</MenuItem>
<MenuItem value="snapgene">SnapGene</MenuItem>
</Select>
</FormControl>
{selectedRepository !== '' && (
<form onSubmit={onSubmit}>
<FormControl fullWidth>
<TextField
label={inputLabels[selectedRepository]}
id={`repository-id-${sourceId}`}
value={inputValue}
onChange={(event) => setInputValue(event.target.value)}
helperText={helperText}
error={error}
/>
</FormControl>
{/* Extra info for benchling case */}
{selectedRepository === 'benchling' && (
<Alert severity="info" sx={{ mb: 1 }}>
The sequence must be publicly accessible. Use the URL from a sequence editor page (ending in &quot;/edit&quot;), like
{' '}
<a target="_blank" rel="noopener noreferrer" href="https://benchling.com/siverson/f/lib_B94YxDHhQh-cidar-moclo-library/seq_dh1FrJTc-b0015_dh/edit">this example</a>
.
</Alert>
)}
{inputValue && !error && (<SubmitButtonBackendAPI requestStatus={requestStatus}>Submit</SubmitButtonBackendAPI>)}
</form>
<form onSubmit={onSubmit}>
{selectedRepository !== 'snapgene' && (
<>
<FormControl fullWidth>
<TextField
label={inputLabels[selectedRepository]}
id={`repository-id-${sourceId}`}
value={inputValue}
onChange={(event) => setInputValue(event.target.value)}
helperText={helperText}
error={error}
/>
</FormControl>
{/* Extra info for benchling case */}
{selectedRepository === 'benchling' && (
<Alert severity="info" sx={{ mb: 1 }}>
The sequence must be publicly accessible. Use the URL from a sequence editor page (ending in &quot;/edit&quot;), like
{' '}
<a target="_blank" rel="noopener noreferrer" href="https://benchling.com/siverson/f/lib_B94YxDHhQh-cidar-moclo-library/seq_dh1FrJTc-b0015_dh/edit">this example</a>
.
</Alert>
)}
</>
)}
{selectedRepository === 'snapgene' && <SnapGenePlasmidSelector setInputValue={setInputValue} />}
{inputValue && !error && (<SubmitButtonBackendAPI requestStatus={requestStatus}>Submit</SubmitButtonBackendAPI>)}

</form>
)}
</>
);
Expand Down
1 change: 1 addition & 0 deletions src/utils/sourceFunctions.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const classNameToEndPointMap = {
RepositoryIdSource: 'repository_id',
AddGeneIdSource: 'repository_id',
BenchlingUrlSource: 'repository_id',
SnapGenePlasmidSource: 'repository_id',
GenomeCoordinatesSource: 'genome_coordinates',
ManuallyTypedSource: 'manually_typed',
OligoHybridizationSource: 'oligonucleotide_hybridization',
Expand Down

0 comments on commit 644f0b5

Please sign in to comment.