From 0a2825b0bf7c273a99e0c65c1141d8eadd0a2d0f Mon Sep 17 00:00:00 2001 From: Etienne Boileau Date: Fri, 12 Apr 2024 17:06:54 +0200 Subject: [PATCH 01/20] WIP #78 prj request form components --- client/src/components/layout/HeaderLayout.vue | 4 +- client/src/components/project/ProjectForm.vue | 134 +++++++++++++++++ client/src/views/ProjectView.vue | 139 +----------------- 3 files changed, 140 insertions(+), 137 deletions(-) create mode 100644 client/src/components/project/ProjectForm.vue diff --git a/client/src/components/layout/HeaderLayout.vue b/client/src/components/layout/HeaderLayout.vue index 9b85f4a9..4ea38fa5 100644 --- a/client/src/components/layout/HeaderLayout.vue +++ b/client/src/components/layout/HeaderLayout.vue @@ -137,7 +137,7 @@ function getUserName() {

- Submit +
+ + +
diff --git a/client/src/components/project/ProjectMetaData.vue b/client/src/components/project/ProjectMetaData.vue new file mode 100644 index 00000000..74d77059 --- /dev/null +++ b/client/src/components/project/ProjectMetaData.vue @@ -0,0 +1,204 @@ + + + diff --git a/client/src/components/ui/FormDropdown.vue b/client/src/components/ui/FormDropdown.vue new file mode 100644 index 00000000..d60ef451 --- /dev/null +++ b/client/src/components/ui/FormDropdown.vue @@ -0,0 +1,72 @@ + + + diff --git a/client/src/main.js b/client/src/main.js index e5a3dfac..75a91a7a 100644 --- a/client/src/main.js +++ b/client/src/main.js @@ -28,6 +28,8 @@ import Panel from 'primevue/panel' import ProgressSpinner from 'primevue/progressspinner' import RadioButton from 'primevue/radiobutton' import Row from 'primevue/row' +import Stepper from 'primevue/stepper' +import StepperPanel from 'primevue/stepperpanel' import TabPanel from 'primevue/tabpanel' import TabView from 'primevue/tabview' import Textarea from 'primevue/textarea' @@ -67,6 +69,8 @@ app.component('Panel', Panel) app.component('ProgressSpinner', ProgressSpinner) app.component('RadioButton', RadioButton) app.component('Row', Row) +app.component('Stepper', Stepper) +app.component('StepperPanel', StepperPanel) app.component('TabPanel', TabPanel) app.component('TabView', TabView) app.component('Textarea', Textarea) diff --git a/client/src/views/ProjectView.vue b/client/src/views/ProjectView.vue index 8914ca6f..f3e68edc 100644 --- a/client/src/views/ProjectView.vue +++ b/client/src/views/ProjectView.vue @@ -2,6 +2,27 @@ import { ref } from 'vue' import ProjectForm from '@/components/project/ProjectForm.vue' +import ProjectMetaData from '@/components/project/ProjectMetaData.vue' + +const projectInfo = ref() +const projectData = ref() + +const active = ref(0) +const completed = ref(false) +const products = ref() +const name = ref() +const email = ref() +const password = ref() +const option1 = ref(false) +const option2 = ref(false) +const option3 = ref(false) +const option4 = ref(false) +const option5 = ref(false) +const option6 = ref(false) +const option7 = ref(false) +const option8 = ref(false) +const option9 = ref(false) +const option10 = ref(false) diff --git a/server/src/scimodom/api/public.py b/server/src/scimodom/api/public.py index fc4ad078..79d65a12 100644 --- a/server/src/scimodom/api/public.py +++ b/server/src/scimodom/api/public.py @@ -9,6 +9,20 @@ api = Blueprint("api", __name__) +@api.route("/modification", methods=["GET"]) +@cross_origin(supports_credentials=True) +def get_modification(): + public_service = get_public_service() + return public_service.get_modomics() + + +@api.route("/method", methods=["GET"]) +@cross_origin(supports_credentials=True) +def get_method(): + public_service = get_public_service() + return public_service.get_detection_method() + + @api.route("/selection", methods=["GET"]) @cross_origin(supports_credentials=True) def get_selection(): diff --git a/server/src/scimodom/services/public.py b/server/src/scimodom/services/public.py index 3e12b3a5..f4c059a2 100644 --- a/server/src/scimodom/services/public.py +++ b/server/src/scimodom/services/public.py @@ -63,6 +63,26 @@ class PublicService: def __init__(self, session: Session): self._session = session + def get_modomics(self): + """Get all modifications. + + :returns: Query result + :rtype: list of dict + """ + + query = select(Modomics.id, Modomics.short_name.label("modomics_sname")) + return self._dump(query) + + def get_detection_method(self): + """Get all standard methods. + + :returns: Query result + :rtype: list of dict + """ + + query = select(DetectionMethod.id, DetectionMethod.cls, DetectionMethod.meth) + return self._dump(query) + def get_selection(self): """Get available selections. From 987455ea7ca140b6d33b88022a829e0f34550e83 Mon Sep 17 00:00:00 2001 From: Etienne Boileau Date: Tue, 16 Apr 2024 17:11:03 +0200 Subject: [PATCH 03/20] WIP #78 --- .../components/project/ProjectMetaData.vue | 271 +++++++++++++----- server/src/scimodom/api/public.py | 14 + server/src/scimodom/services/public.py | 29 ++ 3 files changed, 239 insertions(+), 75 deletions(-) diff --git a/client/src/components/project/ProjectMetaData.vue b/client/src/components/project/ProjectMetaData.vue index 74d77059..577bf1ed 100644 --- a/client/src/components/project/ProjectMetaData.vue +++ b/client/src/components/project/ProjectMetaData.vue @@ -13,14 +13,18 @@ import { updSelectionFromAll } from '@/utils/selection.js' -const modification = ref() -const method = ref() +const modification = ref([]) +const method = ref([]) +const taxid = ref([]) +const assembly = ref([]) const selectedModification = ref() const selectedMethod = ref() const selectedType = ref() +const selectedTaxid = ref() +const selectedAssembly = ref() // TODO define in BE -const rna = ref([{ key: 'mRNA' }, { key: 'rRNA' }]) +const rna = ref(['mRNA', 'rRNA']) import FormDropdown from '@/components/ui/FormDropdown.vue' @@ -34,47 +38,70 @@ const model = defineModel() const validationSchema = object({ metadata: array().of( object().shape({ - rna: string().required('RNA type is required@'), - modification: string().required('Modification is required!'), - method: string().required('Method is required!'), + rna: string().required('RNA type is required!'), //.nullable().transform((value) => !!value ? value : null), + modification: string().required('Modification is required!'), //.nullable().transform((value) => !!value ? value : null), + method: string().required('Method is required!'), //.nullable().transform((value) => !!value ? value : null), technology: string().required('Technology is required!'), - taxid: number().integer(), - organism: string(), - assembly: string(), //? + taxid: number().integer().required('Organism is a required field!'), + organism: string().required('Cell, tissue, or organ is required!'), + assembly: number() + .integer() + .typeError('Assembly ID must be a number!') + .transform((_, val) => (val !== '' ? Number(val) : null)), + freeAssembly: string(), note: string() }) ) }) -// const getInitialValues = () => { -// if (model.value === undefined) { -// return { doi: '', pmid: null } -// } else { -// return { ...model.value} -// } -// } - -const { defineField, handleSubmit, errors } = useForm({ - validationSchema: validationSchema - // initialValues: getInitialValues() +const initialValues = { + rna: '', + modification: '', + method: '', + technology: '', + taxid: null, + organism: '', + assembly: null, + freeAssembly: '', + note: '' +} + +const getInitialValues = () => { + if (model.value === undefined) { + return initialValues + } else { + return { ...model.value } + } +} +console.log('INITIAL', getInitialValues()) + +const { handleSubmit, errors } = useForm({ + validationSchema: validationSchema, + initialValues: getInitialValues() }) -// const [forename, forenameProps] = defineField('forename') -// const [surname, surnameProps] = defineField('surname') -// const [institution, institutionProps] = defineField('institution') -// const [email, emailProps] = defineField('email') -// const [title, titleProps] = defineField('title') -// const [summary, summaryProps] = defineField('summary') -// const [published, publishedProps] = defineField('published') const { remove, push, fields } = useFieldArray('metadata') +console.log('FIELDS', fields) + const onSubmit = handleSubmit((values) => { // Submit to API + console.log('ON SUBMIT') console.log(values) // model.value = values // props.nextCallback() }) +const getAssemblies = () => { + HTTP.get(`/assembly/${selectedTaxid.value.key}`) + .then(function (response) { + assembly.value = response.data + }) + .catch((error) => { + console.log(error) + }) +} + onMounted(() => { HTTP.get('/modification') .then(function (response) { @@ -93,6 +120,19 @@ onMounted(() => { .catch((error) => { console.log(error) }) + HTTP.get('/taxid') + .then(function (response) { + let opts = response.data + opts = opts.map((item) => { + const kingdom = Object.is(item.kingdom, null) ? item.domain : item.kingdom + return { ...item, kingdom } + }) + taxid.value = toCascade(toTree(opts, ['kingdom', 'taxa_sname'], 'id')) + nestedSort(taxid.value, ['child1']) + }) + .catch((error) => { + console.log(error) + }) }) @@ -103,43 +143,59 @@ onMounted(() => {

Project metadata...

- - diff --git a/client/src/components/project/ProjectMetaData.vue b/client/src/components/project/ProjectMetaData.vue index 90ca7c9f..357d5ded 100644 --- a/client/src/components/project/ProjectMetaData.vue +++ b/client/src/components/project/ProjectMetaData.vue @@ -19,10 +19,16 @@ const taxid = ref([]) const assembly = ref([]) const selectedModification = ref() const selectedMethod = ref() -const selectedType = ref() const selectedTaxid = ref() const selectedAssembly = ref() +import { useRouter } from 'vue-router' +const router = useRouter() +const routeData = router.resolve({ name: 'documentation' }) +const tata = () => { + window.open(routeData.href, '_blank') +} + // TODO define in BE const rna = ref(['mRNA', 'rRNA']) @@ -32,23 +38,33 @@ import FormTextInput from '@/components/ui/FormTextInput.vue' import FormTextArea from '@/components/ui/FormTextArea.vue' import FormButton from '@/components/ui/FormButton.vue' -const props = defineProps(['nextCallback']) +const props = defineProps(['nextCallback', 'prevCallback']) const model = defineModel() const validationSchema = object({ metadata: array().of( object().shape({ - rna: string().required('RNA type is required!'), //.nullable().transform((value) => !!value ? value : null), - modification: string().required('Modification is required!'), //.nullable().transform((value) => !!value ? value : null), - method: string().required('Method is required!'), //.nullable().transform((value) => !!value ? value : null), - technology: string().required('Technology is required!'), - taxid: number().integer().required('Organism is a required field!'), - organism: string().required('Cell, tissue, or organ is required!'), + rna: string().max(32, 'At most 32 characters allowed!').required('RNA type is required!'), + modification: string() + .max(128, 'At most 128 characters allowed!') + .required('Modification is required!'), + method: string().max(8, 'At most 8 characters allowed').required('Method is required!'), + technology: string() + .max(255, 'At most 255 characters allowed!') + .required('Technology is required!'), + taxid: number().integer().required('Organism is required!'), + organism: string() + .max(255, 'At most 255 characters allowed!') + .required('Cell, tissue, or organ is required!'), assembly: number() .integer() .typeError('Assembly ID must be a number!') .transform((_, val) => (val !== '' ? Number(val) : null)), - freeAssembly: string(), + freeAssembly: string() + .max(128, 'At most 128 characters allowed!') + .required( + 'Assembly is required! If selecting from existing (left), copy your selection above.' + ), note: string() }) ) @@ -73,7 +89,6 @@ const getInitialValues = () => { return { ...model.value } } } -console.log('INITIAL', getInitialValues()) const { handleSubmit, errors } = useForm({ validationSchema: validationSchema, @@ -82,14 +97,19 @@ const { handleSubmit, errors } = useForm({ const { remove, push, fields } = useFieldArray('metadata') -console.log('FIELDS', fields) +const addMetadata = () => { + push(initialValues) + selectedModification.value = undefined + selectedMethod.value = undefined + selectedTaxid.value = undefined + selectedAssembly.value = undefined +} const onSubmit = handleSubmit((values) => { // Submit to API - console.log('ON SUBMIT') console.log(values) - // model.value = values - // props.nextCallback() + model.value = values + props.nextCallback() }) const getAssemblies = () => { @@ -140,19 +160,38 @@ onMounted(() => {
- - -

Project metadata...

-

-
- +
+
diff --git a/client/src/views/DocumentationView.vue b/client/src/views/DocumentationView.vue index f59ab771..585aebb6 100644 --- a/client/src/views/DocumentationView.vue +++ b/client/src/views/DocumentationView.vue @@ -155,6 +155,44 @@ selected in A and B. Records can be exported to CSV.

+ +
+

+ Data management (for logged in users) +

+

+ Project template +

+

+ To create a new project, fill the template request form, accessible for logged-in users + through User menu > Data > Project template. Add a new metadata sheet for each dataset + that belongs to this project. +

+

For example:

+
+
    +
  • + A study generates human and mouse data, for two conditions, each in three replicates, + using a detection technology for m6A. For this project, create two metadata sheets, + one for human and one for mouse. Add notes if necessary. Once the project has been + created, upload data in separate files for human and mouse, and ideally with as much + granularity as possible, e.g. one file per + condition and/or replicate, with appropriate information. +
  • +
  • + A study generates human data using a technology to detect two different modifications. + The resulting bedRMod file explicitely contains the modification in the 4th column. + For this project, create two identical metadata sheets, except for the modification. +
  • +
+
+

+ In general, add a metadata sheet (i) for each dataset + associated with a different modification, detection technology, and/or organism (incl. + cell type, tissue, or organ), or (ii) for each modification + associated with a single dataset. +

+
diff --git a/client/src/views/ProjectView.vue b/client/src/views/ProjectView.vue index f3e68edc..7d59bbd6 100644 --- a/client/src/views/ProjectView.vue +++ b/client/src/views/ProjectView.vue @@ -91,7 +91,11 @@ const option10 = ref(false)