Skip to content

Commit

Permalink
Merge pull request #110 from MyersResearchGroup/sbh-part-download
Browse files Browse the repository at this point in the history
User annotation libraries
  • Loading branch information
cl117 authored Oct 23, 2024
2 parents da8e6a1 + 99015de commit 7a9f7a8
Show file tree
Hide file tree
Showing 5 changed files with 336 additions and 30 deletions.
40 changes: 39 additions & 1 deletion apps/server/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ def create_app():
# only retrieves feature library that already exists after setup
# might create a libarary in the future
def create_feature_library(part_library_file_name):
if part_library_file_name.startswith('https://synbiohub.org'): #if url, return the obj if indexed with url(sbh downloads only)
if ('synbiohub.org' in part_library_file_name): #if url, return the obj if indexed with url(sbh downloads only)
if part_library_file_name in FEATURE_LIBRARIES:
return FEATURE_LIBRARIES[part_library_file_name]
else: #for locally stored sbh collections(indexed with file name for annotation)
Expand Down Expand Up @@ -201,6 +201,7 @@ def run_synbict(sbol_content: str, part_library_file_names: list[str]) -> tuple[
target_library = FeatureLibrary([target_doc])
# feature_library = FEATURE_LIBRARIES[0]
feature_library = create_feature_library(part_lib_f_name)
print(f"feature library for {part_lib_f_name}: {feature_library}")
min_feature_length = 10
annotater = FeatureAnnotater(feature_library, min_feature_length)
min_target_length = 10
Expand Down Expand Up @@ -424,6 +425,43 @@ def annotate_text():
biobert_result = run_biobert(free_text)
return {"text": free_text, "annotations": biobert_result}

@app.post("/api/importUserLibrary")
def import_library():
request_data = request.get_json()
SBHSessionToken = request_data['sessionToken']
collectionURL = request_data['url']

headers = {
"Accept": "text/plain",
"X-authorization": SBHSessionToken
}

response = requests.get(collectionURL, headers=headers)

# Check if the request was successful
if response.status_code == 200:
feature_doc = sbol2.Document()
feature_doc.readString(response.text)
FEATURE_LIBRARIES[collectionURL] = FeatureLibrary([feature_doc])
print(f"all libraries: {FEATURE_LIBRARIES.keys()}")

return {"response": response.text}
else:
print(f"Request failed with status code: {response.status_code}")
return {"response": response.text}

@app.post("/api/deleteUserLibrary")
def remove_library():
request_data = request.get_json()
collectionURL = request_data['url']

if FEATURE_LIBRARIES[collectionURL]: del FEATURE_LIBRARIES[collectionURL]
else: return {"response": "Library does not exist"}

print(f"\nupdated libraries: {FEATURE_LIBRARIES.keys()}")

return {"response": "Library successfully deleted"}

# if __name__ == '__main__':
# app.run(debug=True,host='0.0.0.0',port=5000)
if __name__ == "__main__":
Expand Down
19 changes: 14 additions & 5 deletions apps/web/src/components/CurationForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ function SynBioHubClient({opened, onClose, setIsInteractingWithSynBioHub, synBio
);
}

function SynBioHubClientLogin({ synBioHubs }) {
export function SynBioHubClientLogin({ synBioHubs }) {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [inputError, setInputError] = useState(false);
Expand All @@ -62,7 +62,15 @@ function SynBioHubClientLogin({ synBioHubs }) {
data={synBioHubs}
onChange={(v) => {
setWorkingSynBioHubUrlPrefix(v);
}}
}}
searchable
creatable
getCreateLabel={(query) => `Custom SBH: ${query}`}
onCreate={(query) => {
const item = { value: query, label: query };
synBioHubs.push(query)
return item;
}}
/>
<TextInput
label="Email"
Expand All @@ -87,7 +95,7 @@ function SynBioHubClientLogin({ synBioHubs }) {
const params = new URLSearchParams();
params.append('email', email);
params.append('password', password);
const synbiohub_url_login = "https:/" + "/synbiohub.org/login";
const synbiohub_url_login = workingSynBioHubUrlPrefix + "/login";
setIsLoading(true);
const response = await fetch(synbiohub_url_login, {
method: "POST",
Expand All @@ -99,7 +107,7 @@ function SynBioHubClientLogin({ synBioHubs }) {
if (response.ok) {
setInputError(false);
const session_token = await response.text();
// setSynBioHubSessionToken(session_token);
localStorage.setItem("synBioHubs", JSON.stringify(synBioHubs))
loginToSynBioHubFn(session_token, workingSynBioHubUrlPrefix);

} else if (response.status == 401) {
Expand Down Expand Up @@ -440,7 +448,8 @@ export default function CurationForm({ }) {
const loadSynBioHubs = async () => {
const response = await fetch("https://wor.synbiohub.org/instances");
const registries = await response.json();
setSynBioHubs(registries.map(r => r.uriPrefix));
if (localStorage.getItem("synBioHubs")) setSynBioHubs(JSON.parse(localStorage.getItem("synBioHubs")))
else setSynBioHubs(registries.map(r => r.uriPrefix));
};

function isValid(sequence) {
Expand Down
207 changes: 188 additions & 19 deletions apps/web/src/components/SequenceSection.jsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
import { useState, useEffect, forwardRef, createElement } from "react"
import { useForceUpdate } from "@mantine/hooks"
import { Checkbox } from '@mantine/core';
import { Button, Center, Group, Loader, NavLink, Space, CopyButton, ActionIcon, Tooltip, Textarea, MultiSelect, Text, Highlight} from "@mantine/core"
import { Checkbox, CloseButton, Flex, Grid, SegmentedControl, Select, Title } from '@mantine/core';
import { Button, Center, Group, Stack, Loader, Modal, NavLink, Space, CopyButton, ActionIcon, Tooltip, Textarea, MultiSelect, Text, Highlight} from "@mantine/core"
import { FiDownloadCloud } from "react-icons/fi"
import { FaCheck, FaPencilAlt, FaPlus, FaTimes, FaArrowRight } from "react-icons/fa"
import { mutateDocument, mutateSequencePartLibrariesSelected, useAsyncLoader, useStore } from "../modules/store"
import AnnotationCheckbox from "./AnnotationCheckbox"
import FormSection from "./FormSection"
import SequenceHighlighter from "./SequenceHighlighter"
import { Copy, Check } from "tabler-icons-react"
import { showErrorNotification } from '../modules/util'
import { showErrorNotification, showNotificationSuccess } from "../modules/util"
import "../../src/sequence-edit.css"
import { HighlightWithinTextarea } from 'react-highlight-within-textarea'
import { openConfirmModal, openContextModal } from "@mantine/modals"
import { SynBioHubClientLogin } from "./CurationForm";
import { importLibrary } from "../modules/api";

const WORDSIZE = 8;

Expand Down Expand Up @@ -143,21 +146,7 @@ function Sequence({ colors }) {
}
style={{ maxWidth: "800px" }}
>
{workingSequence !== false ? //TextArea is the editor // TODO: add highlight in TextArea
// <Textarea
// size="md"
// minRows={20}
// value={workingSequence}
// onChange={event => {
// const textArea = event.currentTarget;
// const start = textArea.selectionStart;
// const end = textArea.selectionEnd;
// // isValid?nothing : update the state
// console.log("isInValid: ", start);
// setWorkingSequence(textArea.value); //value is the value of ACTG, reformat is adding spaces, call validate function
// }}
// styles={{ input: { font: "14px monospace", lineHeight: "1.5em", error: true } }}
// />
{workingSequence !== false ?
<HighlightWithinTextarea
value={workingSequence}
highlight={{
Expand Down Expand Up @@ -235,6 +224,21 @@ function Annotations({ colors }) {

const { isActive, setActive } = useStore(s => s.sequenceAnnotationActions)
const sequence = useStore(s => s.document?.root.sequence)?.toLowerCase()

const libraryImported = useStore(s => s.libraryImported)
const isLoggedInToSynBioHub = useStore(s => s.isLoggedInToSomeSynBioHub);
const [ isInteractingWithSynBioHub, setIsInteractingWithSynBioHub ] = useState(false);
const [ isImportingLibrary, setIsImportingLibrary ] = useState(false);
const [ synBioHubs, setSynBioHubs ] = useState([]);

useStore(s => s.libraryImported);

const loadSynBioHubs = async () => {
const response = await fetch("https://wor.synbiohub.org/instances");
const registries = await response.json();
if (localStorage.getItem("synBioHubs")) setSynBioHubs(JSON.parse(localStorage.getItem("synBioHubs")))
else setSynBioHubs(registries.map(r => r.uriPrefix));
};

const sequencePartLibraries = [
{ value: 'local_libraries', label: 'SeqImprove Local Libraries'},
Expand Down Expand Up @@ -263,6 +267,9 @@ function Annotations({ colors }) {
];

const [sequencePartLibrariesSelected, setSequencePartLibrariesSelected] = useState([]);
const importedLibraries = useStore(s => s.importedLibraries)
const toggleLibrary = useStore(s => s.toggleImportedLibraries)
const removeLibrary = useStore(s => s.removeImportedLibrary)


const AnnotationCheckboxContainer = forwardRef((props, ref) => (
Expand All @@ -272,10 +279,16 @@ function Annotations({ colors }) {
));

const handleAnalyzeSequenceClick = () => {
if (sequencePartLibrariesSelected.length > 0) loadSequenceAnnotations()
const libs = importedLibraries.filter((lib) =>
lib.enabled == true)
if (sequencePartLibrariesSelected.length > 0 || libs.length > 0) {
loadSequenceAnnotations(libs)
}
else showErrorNotification('No libraries selected ', 'Select one or more libraries to continue')
}

const handleClose = (library) => {removeLibrary(library)};

return (
<FormSection title="Sequence Annotations" key="Sequence Annotations">
{annotations.map((anno, i) =>
Expand Down Expand Up @@ -324,6 +337,52 @@ function Annotations({ colors }) {
})}
/>


{libraryImported && <Stack mt="sm" gap="xs">
{importedLibraries.map((library, index) => (
<Grid key={index}>
<Grid.Col span={10}>
<Checkbox
label={library.label}
checked={library.enabled}
onChange={() => toggleLibrary(index)}
key={index}
/>
</Grid.Col>
<Grid.Col span={2}>
<Tooltip label="Delete from server memory">
<CloseButton
onClick={() => handleClose(library)}
/>
</Tooltip>
</Grid.Col>
</Grid>
))}
</Stack>
}

<NavLink
label="Import Library"
icon={<FaPlus />}
variant="subtle"
active={true}
color="blue"
onClick={() => {
loadSynBioHubs();
setIsInteractingWithSynBioHub(true);
}}
sx={{ borderRadius: 6 }}
/>

<SynBioHubClient
opened={isInteractingWithSynBioHub}
setIsInteractingWithSynBioHub={setIsInteractingWithSynBioHub}
onClose={() => setIsInteractingWithSynBioHub(false)}
setOpened={setIsInteractingWithSynBioHub}
synBioHubs={synBioHubs}
setIsImportingLibrary={setIsImportingLibrary}
/>

{loading ?
<Center>
<Loader my={30} size="sm" variant="dots" /> :
Expand All @@ -334,6 +393,7 @@ function Annotations({ colors }) {
variant="subtle"
active={true}
color="blue"
disabled={isImportingLibrary}
onClick={handleAnalyzeSequenceClick}
sx={{ borderRadius: 6 }}
/>
Expand All @@ -342,6 +402,115 @@ function Annotations({ colors }) {
)
}

function SynBioHubClient({opened, onClose, setIsInteractingWithSynBioHub, synBioHubs, setIsImportingLibrary}) {
const isLoggedInToSynBioHub = useStore(s => s.isLoggedInToSomeSynBioHub);

return (
<Modal
title="SynBioHub"
opened={opened}
onClose={onClose}
size={"auto"}
>
{isLoggedInToSynBioHub ?
<SynBioHubClientSelect setIsInteractingWithSynBioHub={setIsInteractingWithSynBioHub} setIsImportingLibrary={setIsImportingLibrary}/> :
<SynBioHubClientLogin synBioHubs={synBioHubs} />
}
</Modal>
);
}

function SynBioHubClientSelect({ setIsInteractingWithSynBioHub, setIsImportingLibrary }) {
const synBioHubUrlPrefix = useStore(s => s.synBioHubUrlPrefix);
const [ synBioHubSessionToken, _ ] = useState(sessionStorage.getItem('SynBioHubSessionToken'));
const [inputError, setInputError] = useState(false);
const [ isLoading, setIsLoading ] = useState(false);
const libraryImported = useStore(s => s.libraryImported);
const [ id, setID ] = useState("collection_id");
const [ rootCollectionsLoaded, setRootCollectionsLoaded ] = useState(false);
const [ rootCollectionsIDs, setRootCollectionsIDs ] = useState([]);
const [ rootCollections, setRootCollections ] = useState([]);
const [ rootCollectionURI, setRootCollectionURI ] = useState('');
const [ selectedCollectionID, selectCollectionID ] = useState('');

const xml = useStore(s => s.serializeXML());

(async () => {
if (!rootCollectionsLoaded) { // curl -X GET -H "Accept: text/plain" -H "X-authorization: 5ab3af6e-2ddd-4ac2-af76-d4285d2ffe03" https://synbiohub.org/rootCollections
console.log(synBioHubSessionToken);
const response2 = await fetch(synBioHubUrlPrefix + "/rootCollections", {
method: "GET",
headers: {
"Accept": "text/plain",
"X-authorization": synBioHubSessionToken,
},
});

const _rootCollections = await response2.json();

let regex = RegExp(synBioHubUrlPrefix.replace(/^https?/, 'https?') + "/user/.*");
const userRootCollections = _rootCollections.filter(collection => collection.uri.match(regex));
setRootCollections(userRootCollections);
setRootCollectionsIDs(userRootCollections.map(collection => collection.displayId));
setRootCollectionsLoaded(true);
}
})();

const [ inputErrorID, setInputErrorID ] = useState(false);
const importedLibraries = useStore(s => s.importedLibraries)
const addLibrary = useStore(s => s.addImportedLibrary)

return (
<Group>
<Title order={3}>Download from SynBioHub</Title>
<Group>
<Group>
{!rootCollectionsLoaded ?
<Center>
<Loader my={30} size="sm" variant="dots" />
</Center> :
<Select
label="Root Collection"
placeholder="Pick one"
data={rootCollectionsIDs}
onChange={(v) => {
setRootCollectionURI(rootCollections.find(collection => collection.displayId == v).uri)
selectCollectionID(v)
}}
searchable
/>
}

{isLoading ?
<Center>
<Loader my={30} size="sm" variant="dots" />
</Center> :
<Button onClick={async () => {
const params = new FormData();
//send to backend
params.append('rootCollections', rootCollectionURI);
// Create a Blob from the text
setIsLoading(true);
const response = importLibrary(synBioHubSessionToken, rootCollectionURI, setIsImportingLibrary)

if (response) {
setInputError(false);
setIsInteractingWithSynBioHub(false);
showNotificationSuccess("Success!", "Imported Library: " + selectedCollectionID + ".");
mutateDocument(useStore.setState, state => {state.libraryImported = true});
addLibrary({ value: rootCollectionURI, label: selectedCollectionID, enabled: false})
} else {
showErrorNotification("Failure.", "SynBioHub did not accept the request");
}
}}>
Submit
</Button>
}
</Group>
</Group>
</Group>
)
}

export default {
Sequence, Annotations
Expand Down
Loading

0 comments on commit 7a9f7a8

Please sign in to comment.