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() {
+import { ref, onMounted } from 'vue'
+import { useForm, useFieldArray } from 'vee-validate'
+import { object, array, string, number, date } from 'yup'
+import { HTTPSecure } from '@/services/API'
+
+import FormTextInput from '@/components/ui/FormTextInput.vue'
+import FormTextArea from '@/components/ui/FormTextArea.vue'
+import FormButton from '@/components/ui/FormButton.vue'
+
+const validationSchema = object({
+ forename: string().required('Forename is required!'),
+ surname: string().required('Surname is required!'),
+ institution: string()
+ .required('Institution is required!')
+ .max(255, 'At most 255 characters allowed!'),
+ email: string()
+ .required('Email is required!')
+ .email('Invalid email!')
+ .max(320, 'At most 320 characters allowed!'),
+ title: string().required('Title is required!').max(255, 'At most 255 characters allowed!'),
+ summary: string().required('Summary is required!'),
+ published: date(),
+ sources: array().of(
+ object().shape({
+ doi: string().max(255, 'At most 255 characters allowed!'),
+ pmid: number()
+ .integer()
+ .typeError('PMID must be a number!')
+ .nullable()
+ .transform((_, val) => (val !== '' ? Number(val) : null))
+ })
+ )
+})
+
+const { defineField, handleSubmit, errors } = useForm({
+ validationSchema: validationSchema,
+ initialValues: {
+ sources: [{ doi: '', pmid: null }]
+ }
+})
+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('sources')
+
+const onSubmit = handleSubmit((values) => {
+ // Submit to API
+ console.log(values)
+})
+
+onMounted(() => {
+ HTTPSecure.get('/access/username')
+ .then((response) => {
+ email.value = response.data.username
+ })
+ .catch((err) => {
+ // console.log(err.response.status)
+ console.log(err)
+ // on error what to do
+ })
+})
+
+
+
+
+
+
+
diff --git a/client/src/views/ProjectView.vue b/client/src/views/ProjectView.vue
index 8d7a14fc..8914ca6f 100644
--- a/client/src/views/ProjectView.vue
+++ b/client/src/views/ProjectView.vue
@@ -1,74 +1,7 @@
@@ -97,72 +30,8 @@ onMounted(() => {
To create a new project, complete the form below.
-
-
+
+
From 2c2b8378899f9dace686a4c923e4a9c4ccd72685 Mon Sep 17 00:00:00 2001
From: Etienne Boileau
Date: Mon, 15 Apr 2024 17:26:22 +0200
Subject: [PATCH 02/20] WIP #78 prj request metadata
---
client/package-lock.json | 243 +++++++++---------
client/package.json | 24 +-
client/src/components/project/ProjectForm.vue | 30 ++-
.../components/project/ProjectMetaData.vue | 204 +++++++++++++++
client/src/components/ui/FormDropdown.vue | 72 ++++++
client/src/main.js | 4 +
client/src/views/ProjectView.vue | 106 +++++++-
server/src/scimodom/api/public.py | 14 +
server/src/scimodom/services/public.py | 20 ++
9 files changed, 576 insertions(+), 141 deletions(-)
create mode 100644 client/src/components/project/ProjectMetaData.vue
create mode 100644 client/src/components/ui/FormDropdown.vue
diff --git a/client/package-lock.json b/client/package-lock.json
index 3cb0f09c..9ad7c22c 100644
--- a/client/package-lock.json
+++ b/client/package-lock.json
@@ -8,31 +8,31 @@
"name": "client",
"version": "0.0.0",
"dependencies": {
- "axios": "^1.6.7",
+ "axios": "^1.6.8",
"jwt-decode": "^4.0.0",
"pinia": "^2.1.7",
- "primeicons": "^6.0.1",
- "primevue": "^3.49.1",
- "vee-validate": "^4.12.5",
+ "primeicons": "^7.0.0",
+ "primevue": "^3.51.0",
+ "vee-validate": "^4.12.6",
"vite-svg-loader": "^4.0.0",
"vue": "^3.2.47",
- "vue-cookies": "^1.8.3",
+ "vue-cookies": "^1.8.4",
"vue-router": "^4.3.0",
"yup": "^1.4.0"
},
"devDependencies": {
- "@rushstack/eslint-patch": "^1.7.2",
+ "@rushstack/eslint-patch": "^1.10.2",
"@vitejs/plugin-vue": "^4.6.2",
"@vue/eslint-config-prettier": "^7.1.0",
- "@vue/test-utils": "^2.4.4",
- "autoprefixer": "^10.4.18",
+ "@vue/test-utils": "^2.4.5",
+ "autoprefixer": "^10.4.19",
"eslint": "^8.57.0",
- "eslint-plugin-vue": "^9.22.0",
+ "eslint-plugin-vue": "^9.25.0",
"jsdom": "^21.1.2",
- "postcss": "^8.4.35",
+ "postcss": "^8.4.38",
"prettier": "^2.8.8",
- "tailwindcss": "^3.4.1",
- "vite": "^4.5.2",
+ "tailwindcss": "^3.4.3",
+ "vite": "^4.5.3",
"vitest": "^0.29.8"
}
},
@@ -58,9 +58,9 @@
}
},
"node_modules/@babel/parser": {
- "version": "7.24.0",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.0.tgz",
- "integrity": "sha512-QuP/FxEAzMSjXygs8v4N9dvdXzEHN4W1oF3PxuWAtPo08UdM17u89RDMgjLn/mlc56iM0HlLmVkO/wgR+rDgHg==",
+ "version": "7.24.4",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.4.tgz",
+ "integrity": "sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg==",
"bin": {
"parser": "bin/babel-parser.js"
},
@@ -504,9 +504,9 @@
}
},
"node_modules/@humanwhocodes/object-schema": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz",
- "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==",
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz",
+ "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==",
"dev": true
},
"node_modules/@isaacs/cliui": {
@@ -652,9 +652,9 @@
}
},
"node_modules/@rushstack/eslint-patch": {
- "version": "1.7.2",
- "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.7.2.tgz",
- "integrity": "sha512-RbhOOTCNoCrbfkRyoXODZp75MlpiHMgbE5MEBZAnnnLyQNgrigEj4p0lzsMDyc1zVsJDLrivB58tgg3emX0eEA==",
+ "version": "1.10.2",
+ "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.10.2.tgz",
+ "integrity": "sha512-hw437iINopmQuxWPSUEvqE56NCPsiU8N4AYtfHmJFckclktzK9YQJieD3XkDCDH4OjL+C7zgPUh73R/nrcHrqw==",
"dev": true
},
"node_modules/@tootallnate/once": {
@@ -675,9 +675,9 @@
}
},
"node_modules/@types/chai": {
- "version": "4.3.12",
- "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.12.tgz",
- "integrity": "sha512-zNKDHG/1yxm8Il6uCCVsm+dRdEsJlFoDu73X17y09bId6UwoYww+vFBsAcRzl8knM1sab3Dp1VRikFQwDOtDDw==",
+ "version": "4.3.14",
+ "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.14.tgz",
+ "integrity": "sha512-Wj71sXE4Q4AkGdG9Tvq1u/fquNz9EdG4LIJMwVVII7ashjD/8cf8fyIfJAjRr6YcsXnSE8cOGQPq1gqeR8z+3w==",
"dev": true
},
"node_modules/@types/chai-subset": {
@@ -690,9 +690,9 @@
}
},
"node_modules/@types/node": {
- "version": "20.11.25",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.25.tgz",
- "integrity": "sha512-TBHyJxk2b7HceLVGFcpAUjsa5zIdsPWlR6XHfyGzd0SFu+/NFgQgMAl96MSDZgQDvJAvV6BKsFOrt6zIL09JDw==",
+ "version": "20.12.7",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz",
+ "integrity": "sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==",
"dev": true,
"dependencies": {
"undici-types": "~5.26.4"
@@ -897,22 +897,13 @@
"integrity": "sha512-PuJe7vDIi6VYSinuEbUIQgMIRZGgM8e4R+G+/dQTk0X1NEdvgvvgv7m+rfmDH1gZzyA1OjjoWskvHlfRNfQf3g=="
},
"node_modules/@vue/test-utils": {
- "version": "2.4.4",
- "resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-2.4.4.tgz",
- "integrity": "sha512-8jkRxz8pNhClAf4Co4ZrpAoFISdvT3nuSkUlY6Ys6rmTpw3DMWG/X3mw3gQ7QJzgCZO9f+zuE2kW57fi09MW7Q==",
+ "version": "2.4.5",
+ "resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-2.4.5.tgz",
+ "integrity": "sha512-oo2u7vktOyKUked36R93NB7mg2B+N7Plr8lxp2JBGwr18ch6EggFjixSCdIVVLkT6Qr0z359Xvnafc9dcKyDUg==",
"dev": true,
"dependencies": {
"js-beautify": "^1.14.9",
- "vue-component-type-helpers": "^1.8.21"
- },
- "peerDependencies": {
- "@vue/server-renderer": "^3.0.1",
- "vue": "^3.0.1"
- },
- "peerDependenciesMeta": {
- "@vue/server-renderer": {
- "optional": true
- }
+ "vue-component-type-helpers": "^2.0.0"
}
},
"node_modules/abab": {
@@ -1069,9 +1060,9 @@
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"node_modules/autoprefixer": {
- "version": "10.4.18",
- "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.18.tgz",
- "integrity": "sha512-1DKbDfsr6KUElM6wg+0zRNkB/Q7WcKYAaK+pzXn+Xqmszm/5Xa9coeNdtP88Vi+dPzZnMjhge8GIV49ZQkDa+g==",
+ "version": "10.4.19",
+ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz",
+ "integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==",
"dev": true,
"funding": [
{
@@ -1089,7 +1080,7 @@
],
"dependencies": {
"browserslist": "^4.23.0",
- "caniuse-lite": "^1.0.30001591",
+ "caniuse-lite": "^1.0.30001599",
"fraction.js": "^4.3.7",
"normalize-range": "^0.1.2",
"picocolors": "^1.0.0",
@@ -1106,11 +1097,11 @@
}
},
"node_modules/axios": {
- "version": "1.6.7",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz",
- "integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==",
+ "version": "1.6.8",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz",
+ "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==",
"dependencies": {
- "follow-redirects": "^1.15.4",
+ "follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
@@ -1122,12 +1113,15 @@
"dev": true
},
"node_modules/binary-extensions": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
- "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
+ "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
"dev": true,
"engines": {
"node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/boolbase": {
@@ -1217,9 +1211,9 @@
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001596",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001596.tgz",
- "integrity": "sha512-zpkZ+kEr6We7w63ORkoJ2pOfBwBkY/bJrG/UZ90qNb45Isblu8wzDgevEOrRL1r9dWayHjYiiyCMEXPn4DweGQ==",
+ "version": "1.0.30001610",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001610.tgz",
+ "integrity": "sha512-QFutAY4NgaelojVMjY63o6XlZyORPaLfyMnsl3HgnWdJUcX6K0oaJymHjH8PT5Gk7sTm8rvC/c5COUQKXqmOMA==",
"dev": true,
"funding": [
{
@@ -1708,9 +1702,9 @@
}
},
"node_modules/electron-to-chromium": {
- "version": "1.4.698",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.698.tgz",
- "integrity": "sha512-f9iZD1t3CLy1AS6vzM5EKGa6p9pRcOeEFXRFbaG2Ta+Oe7MkfRQ3fsvPYidzHe1h4i0JvIvpcY55C+B6BZNGtQ==",
+ "version": "1.4.736",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.736.tgz",
+ "integrity": "sha512-Rer6wc3ynLelKNM4lOCg7/zPQj8tPOCB2hzD32PX9wd3hgRRi9MxEbmkFCokzcEhRVMiOVLjnL9ig9cefJ+6+Q==",
"dev": true
},
"node_modules/emoji-regex": {
@@ -1898,12 +1892,13 @@
}
},
"node_modules/eslint-plugin-vue": {
- "version": "9.22.0",
- "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.22.0.tgz",
- "integrity": "sha512-7wCXv5zuVnBtZE/74z4yZ0CM8AjH6bk4MQGm7hZjUC2DBppKU5ioeOk5LGSg/s9a1ZJnIsdPLJpXnu1Rc+cVHg==",
+ "version": "9.25.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.25.0.tgz",
+ "integrity": "sha512-tDWlx14bVe6Bs+Nnh3IGrD+hb11kf2nukfm6jLsmJIhmiRQ1SUaksvwY9U5MvPB0pcrg0QK0xapQkfITs3RKOA==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
+ "globals": "^13.24.0",
"natural-compare": "^1.4.0",
"nth-check": "^2.1.1",
"postcss-selector-parser": "^6.0.15",
@@ -1915,7 +1910,7 @@
"node": "^14.17.0 || >=16.0.0"
},
"peerDependencies": {
- "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0"
+ "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0"
}
},
"node_modules/eslint-scope": {
@@ -2145,9 +2140,9 @@
"dev": true
},
"node_modules/follow-redirects": {
- "version": "1.15.5",
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz",
- "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==",
+ "version": "1.15.6",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
+ "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
"funding": [
{
"type": "individual",
@@ -2244,16 +2239,16 @@
}
},
"node_modules/glob": {
- "version": "10.3.10",
- "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz",
- "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==",
+ "version": "10.3.12",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz",
+ "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==",
"dev": true,
"dependencies": {
"foreground-child": "^3.1.0",
- "jackspeak": "^2.3.5",
+ "jackspeak": "^2.3.6",
"minimatch": "^9.0.1",
- "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0",
- "path-scurry": "^1.10.1"
+ "minipass": "^7.0.4",
+ "path-scurry": "^1.10.2"
},
"bin": {
"glob": "dist/esm/bin.mjs"
@@ -2287,9 +2282,9 @@
}
},
"node_modules/glob/node_modules/minimatch": {
- "version": "9.0.3",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
- "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
+ "version": "9.0.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz",
+ "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==",
"dev": true,
"dependencies": {
"brace-expansion": "^2.0.1"
@@ -2332,9 +2327,9 @@
}
},
"node_modules/hasown": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz",
- "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==",
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"dev": true,
"dependencies": {
"function-bind": "^1.1.2"
@@ -2775,9 +2770,9 @@
}
},
"node_modules/magic-string": {
- "version": "0.30.8",
- "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.8.tgz",
- "integrity": "sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==",
+ "version": "0.30.9",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.9.tgz",
+ "integrity": "sha512-S1+hd+dIrC8EZqKyT9DstTH/0Z+f76kmmvZnkfQVmOpDEF9iVgdYif3Q/pIWHmCoo59bQVGW0kVL3e2nl+9+Sw==",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.4.15"
},
@@ -3092,12 +3087,12 @@
"dev": true
},
"node_modules/path-scurry": {
- "version": "1.10.1",
- "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz",
- "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==",
+ "version": "1.10.2",
+ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz",
+ "integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==",
"dev": true,
"dependencies": {
- "lru-cache": "^9.1.1 || ^10.0.0",
+ "lru-cache": "^10.2.0",
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
},
"engines": {
@@ -3219,9 +3214,9 @@
}
},
"node_modules/postcss": {
- "version": "8.4.35",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz",
- "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==",
+ "version": "8.4.38",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
+ "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==",
"funding": [
{
"type": "opencollective",
@@ -3239,7 +3234,7 @@
"dependencies": {
"nanoid": "^3.3.7",
"picocolors": "^1.0.0",
- "source-map-js": "^1.0.2"
+ "source-map-js": "^1.2.0"
},
"engines": {
"node": "^10 || ^12 || >=14"
@@ -3348,9 +3343,9 @@
}
},
"node_modules/postcss-selector-parser": {
- "version": "6.0.15",
- "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz",
- "integrity": "sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw==",
+ "version": "6.0.16",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz",
+ "integrity": "sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==",
"dev": true,
"dependencies": {
"cssesc": "^3.0.0",
@@ -3429,14 +3424,14 @@
}
},
"node_modules/primeicons": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/primeicons/-/primeicons-6.0.1.tgz",
- "integrity": "sha512-KDeO94CbWI4pKsPnYpA1FPjo79EsY9I+M8ywoPBSf9XMXoe/0crjbUK7jcQEDHuc0ZMRIZsxH3TYLv4TUtHmAA=="
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/primeicons/-/primeicons-7.0.0.tgz",
+ "integrity": "sha512-jK3Et9UzwzTsd6tzl2RmwrVY/b8raJ3QZLzoDACj+oTJ0oX7L9Hy+XnVwgo4QVKlKpnP/Ur13SXV/pVh4LzaDw=="
},
"node_modules/primevue": {
- "version": "3.49.1",
- "resolved": "https://registry.npmjs.org/primevue/-/primevue-3.49.1.tgz",
- "integrity": "sha512-OmUTqbKbPB63Zqf7uA49cipDi+Qh+/13AYJPwgvsVsI4QmAKIkeibBwkOgj1CNIFlopfF79YmyBshFUAPqlw9A==",
+ "version": "3.51.0",
+ "resolved": "https://registry.npmjs.org/primevue/-/primevue-3.51.0.tgz",
+ "integrity": "sha512-BdMveidLSr0fNJ5+mxuke8mMCHyiXwvfDP4iwPr6R98rl3E0Wcm1u4/RKVrL7o0Iq606SXyhPQL3LGyAfXngcA==",
"peerDependencies": {
"vue": "^3.0.0"
}
@@ -3781,9 +3776,9 @@
}
},
"node_modules/source-map-js": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
- "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
+ "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
"engines": {
"node": ">=0.10.0"
}
@@ -4008,9 +4003,9 @@
"dev": true
},
"node_modules/tailwindcss": {
- "version": "3.4.1",
- "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz",
- "integrity": "sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA==",
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.3.tgz",
+ "integrity": "sha512-U7sxQk/n397Bmx4JHbJx/iSOOv5G+II3f1kpLpY2QeUv5DcPdcTsYLlusZfq1NthHS1c1cZoyFmmkex1rzke0A==",
"dev": true,
"dependencies": {
"@alloc/quick-lru": "^5.2.0",
@@ -4021,7 +4016,7 @@
"fast-glob": "^3.3.0",
"glob-parent": "^6.0.2",
"is-glob": "^4.0.3",
- "jiti": "^1.19.1",
+ "jiti": "^1.21.0",
"lilconfig": "^2.1.0",
"micromatch": "^4.0.5",
"normalize-path": "^3.0.0",
@@ -4077,9 +4072,9 @@
"integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q=="
},
"node_modules/tinybench": {
- "version": "2.6.0",
- "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.6.0.tgz",
- "integrity": "sha512-N8hW3PG/3aOoZAN5V/NSAEDz0ZixDSSt5b/a05iqtpgfLWMSVuCo7w0k2vVvEjdrIoeGqZzweX2WlyioNIHchA==",
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.7.0.tgz",
+ "integrity": "sha512-Qgayeb106x2o4hNzNjsZEfFziw8IbKqtbXBjVh7VIZfBxfD5M4gWtpyx5+YTae2gJ6Y6Dz/KLepiv16RFeQWNA==",
"dev": true
},
"node_modules/tinypool": {
@@ -4184,9 +4179,9 @@
}
},
"node_modules/ufo": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.4.0.tgz",
- "integrity": "sha512-Hhy+BhRBleFjpJ2vchUNN40qgkh0366FWJGqVLYBHev0vpHTrXSA0ryT+74UiW6KWsldNurQMKGqCm1M2zBciQ==",
+ "version": "1.5.3",
+ "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.3.tgz",
+ "integrity": "sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==",
"dev": true
},
"node_modules/undici-types": {
@@ -4260,9 +4255,9 @@
"dev": true
},
"node_modules/vee-validate": {
- "version": "4.12.5",
- "resolved": "https://registry.npmjs.org/vee-validate/-/vee-validate-4.12.5.tgz",
- "integrity": "sha512-rvaDfLPSLwTk+mf016XWE4drB8yXzOsKXiKHTb9gNXNLTtQSZ0Ww26O0/xbIFQe+n3+u8Wv1Y8uO/aLDX4fxOg==",
+ "version": "4.12.6",
+ "resolved": "https://registry.npmjs.org/vee-validate/-/vee-validate-4.12.6.tgz",
+ "integrity": "sha512-EKM3YHy8t1miPh30d5X6xOrfG/Ctq0nbN4eMpCK7ezvI6T98/S66vswP+ihL4QqAK/k5KqreWOxof09+JG7N/A==",
"dependencies": {
"@vue/devtools-api": "^6.5.1",
"type-fest": "^4.8.3"
@@ -4272,9 +4267,9 @@
}
},
"node_modules/vee-validate/node_modules/type-fest": {
- "version": "4.11.1",
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.11.1.tgz",
- "integrity": "sha512-MFMf6VkEVZAETidGGSYW2B1MjXbGX+sWIywn2QPEaJ3j08V+MwVRHMXtf2noB8ENJaD0LIun9wh5Z6OPNf1QzQ==",
+ "version": "4.15.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.15.0.tgz",
+ "integrity": "sha512-tB9lu0pQpX5KJq54g+oHOLumOx+pMep4RaM6liXh2PKmVRFF+/vAtUP0ZaJ0kOySfVNjF6doBWPHhBhISKdlIA==",
"engines": {
"node": ">=16"
},
@@ -4283,9 +4278,9 @@
}
},
"node_modules/vite": {
- "version": "4.5.2",
- "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.2.tgz",
- "integrity": "sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==",
+ "version": "4.5.3",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz",
+ "integrity": "sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==",
"dev": true,
"dependencies": {
"esbuild": "^0.18.10",
@@ -4467,15 +4462,15 @@
}
},
"node_modules/vue-component-type-helpers": {
- "version": "1.8.27",
- "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-1.8.27.tgz",
- "integrity": "sha512-0vOfAtI67UjeO1G6UiX5Kd76CqaQ67wrRZiOe7UAb9Jm6GzlUr/fC7CV90XfwapJRjpCMaZFhv1V0ajWRmE9Dg==",
+ "version": "2.0.13",
+ "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-2.0.13.tgz",
+ "integrity": "sha512-xNO5B7DstNWETnoYflLkVgh8dK8h2ZDgxY1M2O0zrqGeBNq5yAZ8a10yCS9+HnixouNGYNX+ggU9MQQq86HTpg==",
"dev": true
},
"node_modules/vue-cookies": {
- "version": "1.8.3",
- "resolved": "https://registry.npmjs.org/vue-cookies/-/vue-cookies-1.8.3.tgz",
- "integrity": "sha512-VBRsyRMVdahBgFfh389TMHPmDdr4URDJNMk4FKSCfuNITs7+jitBDhwyL4RJd3WUsfOYNNjPAkfbehyH9AFuoA=="
+ "version": "1.8.4",
+ "resolved": "https://registry.npmjs.org/vue-cookies/-/vue-cookies-1.8.4.tgz",
+ "integrity": "sha512-9zjvACKE4W0kEb8OQtXzpizKhf6zfFOG/Z1TEUjSJn4Z4rintuAHo8y/FpCUhTWHMmPe8E+Fko+/tiXVM+5jOw=="
},
"node_modules/vue-eslint-parser": {
"version": "9.4.2",
diff --git a/client/package.json b/client/package.json
index d38d12b6..d55b6c77 100644
--- a/client/package.json
+++ b/client/package.json
@@ -11,31 +11,31 @@
"format": "prettier --write src/"
},
"dependencies": {
- "axios": "^1.6.7",
+ "axios": "^1.6.8",
"jwt-decode": "^4.0.0",
"pinia": "^2.1.7",
- "primeicons": "^6.0.1",
- "primevue": "^3.49.1",
- "vee-validate": "^4.12.5",
+ "primeicons": "^7.0.0",
+ "primevue": "^3.51.0",
+ "vee-validate": "^4.12.6",
"vite-svg-loader": "^4.0.0",
"vue": "^3.2.47",
- "vue-cookies": "^1.8.3",
+ "vue-cookies": "^1.8.4",
"vue-router": "^4.3.0",
"yup": "^1.4.0"
},
"devDependencies": {
- "@rushstack/eslint-patch": "^1.7.2",
+ "@rushstack/eslint-patch": "^1.10.2",
"@vitejs/plugin-vue": "^4.6.2",
"@vue/eslint-config-prettier": "^7.1.0",
- "@vue/test-utils": "^2.4.4",
- "autoprefixer": "^10.4.18",
+ "@vue/test-utils": "^2.4.5",
+ "autoprefixer": "^10.4.19",
"eslint": "^8.57.0",
- "eslint-plugin-vue": "^9.22.0",
+ "eslint-plugin-vue": "^9.25.0",
"jsdom": "^21.1.2",
- "postcss": "^8.4.35",
+ "postcss": "^8.4.38",
"prettier": "^2.8.8",
- "tailwindcss": "^3.4.1",
- "vite": "^4.5.2",
+ "tailwindcss": "^3.4.3",
+ "vite": "^4.5.3",
"vitest": "^0.29.8"
}
}
diff --git a/client/src/components/project/ProjectForm.vue b/client/src/components/project/ProjectForm.vue
index 5e0500df..eefe4081 100644
--- a/client/src/components/project/ProjectForm.vue
+++ b/client/src/components/project/ProjectForm.vue
@@ -8,6 +8,9 @@ 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 model = defineModel()
+
const validationSchema = object({
forename: string().required('Forename is required!'),
surname: string().required('Surname is required!'),
@@ -33,11 +36,17 @@ const validationSchema = object({
)
})
+const getInitialValues = () => {
+ if (model.value === undefined) {
+ return { doi: '', pmid: null }
+ } else {
+ return { ...model.value }
+ }
+}
+
const { defineField, handleSubmit, errors } = useForm({
validationSchema: validationSchema,
- initialValues: {
- sources: [{ doi: '', pmid: null }]
- }
+ initialValues: getInitialValues()
})
const [forename, forenameProps] = defineField('forename')
const [surname, surnameProps] = defineField('surname')
@@ -52,6 +61,8 @@ const { remove, push, fields } = useFieldArray('sources')
const onSubmit = handleSubmit((values) => {
// Submit to API
console.log(values)
+ model.value = values
+ props.nextCallback()
})
onMounted(() => {
@@ -127,7 +138,18 @@ onMounted(() => {
- 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 @@
+
+
+
+
+
+
+
+
+
+
+ {{ error }}
+
+
+
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)
@@ -32,6 +53,89 @@ import ProjectForm from '@/components/project/ProjectForm.vue'
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Account created successfully
+
+
+
+
+
+
+
+
+
+
+
+
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...
-
+
-
RNA type
-
-
- Method
+ RNA type
+
+
+
+ {{ errors[`metadata[${idx}].rna`] }}
+
+
+
+ Modification
+
+
+
+ {{ errors[`metadata[${idx}].modification`] }}
+
+
+
+ Method
{
: ''
"
/>
- {{ errors[`metadata[${idx}].method`] }}
-
Technology
+
+ Organism
+
+
+
+ {{ errors[`metadata[${idx}].taxid`] }}
+
+
+
Cell, tissue, organ
+
+
+ Assembly (select from existing assemblies)
+
+
+
+
+ {{ errors[`metadata[${idx}].assembly`] }}
+
+
+
Assembly (if not available)
+
+ Additional notes for this metadata template.
+
+ FIELDS:
{{ fields }}
-
+
+ ERRORS:
+ {{ errors }}
diff --git a/server/src/scimodom/api/public.py b/server/src/scimodom/api/public.py
index 79d65a12..398c17d2 100644
--- a/server/src/scimodom/api/public.py
+++ b/server/src/scimodom/api/public.py
@@ -23,6 +23,20 @@ def get_method():
return public_service.get_detection_method()
+@api.route("/taxid", methods=["GET"])
+@cross_origin(supports_credentials=True)
+def get_taxid():
+ public_service = get_public_service()
+ return public_service.get_taxa()
+
+
+@api.route("/assembly/
", methods=["GET"])
+@cross_origin(supports_credentials=True)
+def get_assembly(taxid):
+ public_service = get_public_service()
+ return public_service.get_assembly_for_taxid(taxid)
+
+
@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 f4c059a2..dbbd1de9 100644
--- a/server/src/scimodom/services/public.py
+++ b/server/src/scimodom/services/public.py
@@ -83,6 +83,35 @@ def get_detection_method(self):
query = select(DetectionMethod.id, DetectionMethod.cls, DetectionMethod.meth)
return self._dump(query)
+ def get_taxa(self):
+ """Get all organisms with their taxonomy.
+
+ :returns: Query result
+ :rtype: list of dict
+ """
+
+ query = select(
+ Taxa.id,
+ Taxa.short_name.label("taxa_sname"),
+ Taxonomy.domain,
+ Taxonomy.kingdom,
+ Taxonomy.phylum,
+ ).join_from(Taxa, Taxonomy, Taxa.inst_taxonomy)
+
+ return self._dump(query)
+
+ def get_assembly_for_taxid(self, taxid: int):
+ """Get available assemblies for given Taxa ID.
+
+ :param taxid: Taxa ID
+ :type taxid: int
+ :returns: Query result
+ :rtype: list of dict
+ """
+
+ query = select(Assembly.id, Assembly.name).where(Assembly.taxa_id == taxid)
+ return self._dump(query)
+
def get_selection(self):
"""Get available selections.
From b87ca574c67aa7c4b57be88569156d67f27c177c Mon Sep 17 00:00:00 2001
From: Etienne Boileau
Date: Tue, 16 Apr 2024 17:13:47 +0200
Subject: [PATCH 04/20] FIX fmt
---
client/src/components/project/ProjectMetaData.vue | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/client/src/components/project/ProjectMetaData.vue b/client/src/components/project/ProjectMetaData.vue
index 577bf1ed..90ca7c9f 100644
--- a/client/src/components/project/ProjectMetaData.vue
+++ b/client/src/components/project/ProjectMetaData.vue
@@ -229,10 +229,7 @@ onMounted(() => {
Date: Wed, 17 Apr 2024 14:08:25 +0200
Subject: [PATCH 05/20] WIP #78 TODO: validation, model
---
client/src/components/project/ProjectForm.vue | 16 ++-
.../components/project/ProjectMetaData.vue | 98 ++++++++++++-------
client/src/views/DocumentationView.vue | 38 +++++++
client/src/views/ProjectView.vue | 6 +-
4 files changed, 117 insertions(+), 41 deletions(-)
diff --git a/client/src/components/project/ProjectForm.vue b/client/src/components/project/ProjectForm.vue
index eefe4081..eef5add2 100644
--- a/client/src/components/project/ProjectForm.vue
+++ b/client/src/components/project/ProjectForm.vue
@@ -82,6 +82,11 @@ onMounted(() => {
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(() => {
-
diff --git a/client/src/components/ui/FormCascade.vue b/client/src/components/ui/FormCascade.vue
new file mode 100644
index 00000000..dabcd5a8
--- /dev/null
+++ b/client/src/components/ui/FormCascade.vue
@@ -0,0 +1,96 @@
+
+
+
+
+
+
+
+
+
+
+ {{ error }}
+
+
+
diff --git a/client/src/components/ui/FormDropdown.vue b/client/src/components/ui/FormDropdown.vue
index d60ef451..2ef1ac7a 100644
--- a/client/src/components/ui/FormDropdown.vue
+++ b/client/src/components/ui/FormDropdown.vue
@@ -1,15 +1,12 @@
@@ -58,9 +65,9 @@ const props = defineProps({
diff --git a/client/src/views/ProjectView.vue b/client/src/views/ProjectView.vue
index 7d59bbd6..7c45e2c0 100644
--- a/client/src/views/ProjectView.vue
+++ b/client/src/views/ProjectView.vue
@@ -51,10 +51,7 @@ const option10 = ref(false)
To create a new project, complete the form below.
-
-
-
-
+
From 74d3483eae2a38146dbf276f955a2e4e0ce605fb Mon Sep 17 00:00:00 2001
From: Etienne Boileau
Date: Thu, 18 Apr 2024 15:36:46 +0200
Subject: [PATCH 07/20] WIP #78 Fix assembly
---
.../components/project/ProjectMetaData.vue | 32 +++----------------
client/src/components/ui/FormCascade.vue | 11 +++++--
client/src/components/ui/FormDropdown.vue | 2 +-
3 files changed, 13 insertions(+), 32 deletions(-)
diff --git a/client/src/components/project/ProjectMetaData.vue b/client/src/components/project/ProjectMetaData.vue
index 00d9744c..6c9eca2b 100644
--- a/client/src/components/project/ProjectMetaData.vue
+++ b/client/src/components/project/ProjectMetaData.vue
@@ -17,17 +17,6 @@ const modification = ref([])
const method = ref([])
const taxid = ref([])
const assembly = ref([])
-const selectedModification = ref()
-const selectedMethod = 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([
@@ -86,16 +75,7 @@ const initialValues = {
note: ''
}
-const resetRefs = () => {
- selectedModification.value = undefined
- selectedMethod.value = undefined
- selectedTaxid.value = undefined
- selectedAssembly.value = undefined
-}
-
const getInitialValues = () => {
- // is this used at all? i.e. it's not called on previous/next Stepper navigation
- // resetRefs()
if (model.value === undefined) {
return null
} else {
@@ -110,11 +90,6 @@ const { handleSubmit, errors } = useForm({
const { remove, push, fields } = useFieldArray('metadata')
-const addMetadata = () => {
- resetRefs()
- push(initialValues)
-}
-
const onSubmit = handleSubmit((values) => {
// Submit to API
console.log(values)
@@ -122,8 +97,8 @@ const onSubmit = handleSubmit((values) => {
props.nextCallback()
})
-const getAssemblies = () => {
- HTTP.get(`/assembly/${selectedTaxid.value.key}`)
+const getAssemblies = (taxid) => {
+ HTTP.get(`/assembly/${taxid}`)
.then(function (response) {
assembly.value = response.data
})
@@ -187,7 +162,7 @@ onMounted(() => {
for more information and examples.
-
+
{
Organism
diff --git a/client/src/components/ui/FormCascade.vue b/client/src/components/ui/FormCascade.vue
index dabcd5a8..ce9aa172 100644
--- a/client/src/components/ui/FormCascade.vue
+++ b/client/src/components/ui/FormCascade.vue
@@ -56,11 +56,15 @@ const props = defineProps({
default: '!ring-red-700'
}
})
-const emit = defineEmits(['update:modelValue'])
-// boiler plate... there is surely a nicer solution?!
+// onChange event also send modelValue...
+const emit = defineEmits(['update:modelValue', 'onChange'])
+// ugly boiler plate... there is surely a nicer solution?!
const computedModel = computed({
get() {
- if (!(props.modelValue == '' || props.modelValue == undefined)) {
+ if (
+ !(props.modelValue == '' || props.modelValue == undefined) &&
+ !(props.options.length === 0)
+ ) {
return props.options
.filter((a) => a[props.optionGroupChildren].some((b) => b.key == props.modelValue))
.map((c) => c[props.optionGroupChildren])[0]
@@ -81,6 +85,7 @@ const computedModel = computed({
// provides a custom wrapper for the PrimeVue Dropdown component
-// to be used in a form
+// to be used in a form - hard coded "id"
import { ref, computed } from 'vue'
const props = defineProps({
From 1af243bab6a84e0b857d15b4b2241084b2b5d688 Mon Sep 17 00:00:00 2001
From: Etienne Boileau
Date: Thu, 18 Apr 2024 18:02:18 +0200
Subject: [PATCH 08/20] WIP #78 add submission
---
client/src/components/project/ProjectForm.vue | 36 +++---
.../components/project/ProjectMetaData.vue | 116 +++++++++---------
.../components/project/ProjectSubmission.vue | 32 +++++
client/src/views/ProjectView.vue | 30 +----
4 files changed, 110 insertions(+), 104 deletions(-)
create mode 100644 client/src/components/project/ProjectSubmission.vue
diff --git a/client/src/components/project/ProjectForm.vue b/client/src/components/project/ProjectForm.vue
index c387fe11..c83cb0d9 100644
--- a/client/src/components/project/ProjectForm.vue
+++ b/client/src/components/project/ProjectForm.vue
@@ -13,17 +13,17 @@ const model = defineModel()
const validationSchema = object({
forename: string().required('Forename is required!'),
surname: string().required('Surname is required!'),
- institution: string()
+ contact_institution: string()
.required('Institution is required!')
.max(255, 'At most 255 characters allowed!'),
- email: string()
+ contact_email: string()
.required('Email is required!')
.email('Invalid email!')
.max(320, 'At most 320 characters allowed!'),
title: string().required('Title is required!').max(255, 'At most 255 characters allowed!'),
summary: string().required('Summary is required!'),
- published: date(),
- sources: array().of(
+ date_published: date(),
+ external_sources: array().of(
object().shape({
doi: string().max(255, 'At most 255 characters allowed!'),
pmid: number()
@@ -49,16 +49,14 @@ const { defineField, handleSubmit, errors } = useForm({
})
const [forename, forenameProps] = defineField('forename')
const [surname, surnameProps] = defineField('surname')
-const [institution, institutionProps] = defineField('institution')
-const [email, emailProps] = defineField('email')
+const [contact_institution, institutionProps] = defineField('contact_institution')
+const [contact_email, emailProps] = defineField('contact_email')
const [title, titleProps] = defineField('title')
const [summary, summaryProps] = defineField('summary')
-const [published, publishedProps] = defineField('published')
-const { remove, push, fields } = useFieldArray('sources')
+const [date_published, publishedProps] = defineField('date_published')
+const { remove, push, fields } = useFieldArray('external_sources')
const onSubmit = handleSubmit((values) => {
- // Submit to API
- console.log(values)
model.value = values
props.nextCallback()
})
@@ -66,7 +64,7 @@ const onSubmit = handleSubmit((values) => {
onMounted(() => {
HTTPSecure.get('/access/username')
.then((response) => {
- email.value = response.data.username
+ contact_email.value = response.data.username
})
.catch((err) => {
// console.log(err.response.status)
@@ -86,7 +84,7 @@ onMounted(() => {
Your contact details
-
+
Forename
@@ -94,12 +92,14 @@ onMounted(() => {
>Surname (family name)
Institution
- Email address
+ Email address
Project details
@@ -116,7 +116,7 @@ onMounted(() => {
>
Summary (project description)
- Date published (add if published)
@@ -129,13 +129,13 @@ onMounted(() => {
DOI
PMID
diff --git a/client/src/components/project/ProjectMetaData.vue b/client/src/components/project/ProjectMetaData.vue
index 6c9eca2b..9e376ffc 100644
--- a/client/src/components/project/ProjectMetaData.vue
+++ b/client/src/components/project/ProjectMetaData.vue
@@ -1,59 +1,61 @@
+
+
+
+
+
+
+ Project submission
+
+
+
+ The project form has been successfully validated. Click
+ "Submit" to finalise the submission. A
+ notification will be sent to your email address as soon as the project is created.
+
+
+ {{ JSON.stringify(projectForm, undefined, 2) }}
+
+
+
+
+
+
+
diff --git a/client/src/views/ProjectView.vue b/client/src/views/ProjectView.vue
index 7c45e2c0..9609cfe5 100644
--- a/client/src/views/ProjectView.vue
+++ b/client/src/views/ProjectView.vue
@@ -1,8 +1,8 @@
@@ -20,12 +37,8 @@ const props = defineProps(['projectForm'])
"Submit" to finalise the submission. A
notification will be sent to your email address as soon as the project is created.
-
- {{ JSON.stringify(projectForm, undefined, 2) }}
-
-
+
diff --git a/client/src/views/ProjectView.vue b/client/src/views/ProjectView.vue
index 9609cfe5..7cbc741f 100644
--- a/client/src/views/ProjectView.vue
+++ b/client/src/views/ProjectView.vue
@@ -6,23 +6,8 @@ import ProjectSubmission from '@/components/project/ProjectSubmission.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/management.py b/server/src/scimodom/api/management.py
new file mode 100644
index 00000000..3eb3419f
--- /dev/null
+++ b/server/src/scimodom/api/management.py
@@ -0,0 +1,54 @@
+import logging
+from smtplib import SMTPException
+
+from flask import Blueprint, request, jsonify
+from flask_cors import cross_origin
+from flask_jwt_extended import jwt_required
+
+from scimodom.services.project import ProjectService
+from scimodom.services.mail import get_mail_service
+import scimodom.utils.utils as utils
+
+logger = logging.getLogger(__name__)
+
+management_api = Blueprint("management_api", __name__)
+
+
+@management_api.route("/project", methods=["POST"])
+@cross_origin(supports_credentials=True)
+@jwt_required()
+def get_project():
+ """Create a project request.
+
+ NOTE: Users are not curently allowed to create
+ projects, though this could be the case in the future,
+ and only small changes would be required.
+ """
+ project_form = request.json
+ try:
+ uuid = ProjectService.create_project_request(project_form)
+ except FileNotFoundError as exc:
+ logger.error(f"Failed to save the project submission form: {exc}")
+ return (
+ jsonify(
+ {
+ "result": "Failed to save the project submission form. Contact the administrator."
+ }
+ ),
+ 500,
+ )
+
+ mail_service = get_mail_service()
+ try:
+ mail_service.send_project_request_notification(uuid)
+ except SMTPException as exc:
+ logger.error(f"Failed to sent out notification email: {exc}")
+ return (
+ jsonify(
+ {
+ "result": "Failed to sent out notification email. Contact the administrator."
+ }
+ ),
+ 500,
+ )
+ return jsonify({"result": "Ok"}), 200
diff --git a/server/src/scimodom/app.py b/server/src/scimodom/app.py
index fc4a2b8c..66743f17 100644
--- a/server/src/scimodom/app.py
+++ b/server/src/scimodom/app.py
@@ -8,6 +8,7 @@
from scimodom.api.public import api
from scimodom.api.user import user_api
from scimodom.api.access import access_api
+from scimodom.api.management import management_api
from scimodom.api.upload import upload_api
from scimodom.app_singleton import create_app_singleton
from scimodom.database.database import make_session, init
@@ -25,6 +26,7 @@
USER_API_ROUTE,
UPLOAD_API_ROUTE,
ACCESS_API_ROUTE,
+ DATA_MANAGEMENT_API_ROUTE,
)
@@ -46,6 +48,7 @@ def create_app():
app.register_blueprint(user_api, url_prefix=USER_API_ROUTE)
app.register_blueprint(access_api, url_prefix=ACCESS_API_ROUTE)
app.register_blueprint(upload_api, url_prefix=UPLOAD_API_ROUTE)
+ app.register_blueprint(management_api, url_prefix=DATA_MANAGEMENT_API_ROUTE)
app.register_blueprint(frontend, url_prefix="/")
jwt = JWTManager(app)
diff --git a/server/src/scimodom/config.py b/server/src/scimodom/config.py
index 96ff9e1d..e5d8e622 100644
--- a/server/src/scimodom/config.py
+++ b/server/src/scimodom/config.py
@@ -40,6 +40,7 @@ class Config:
try:
SMTP_SERVER: ClassVar[str] = os.environ["SMTP_SERVER"]
SMTP_FROM_ADDRESS: ClassVar[str] = os.environ["SMTP_FROM_ADDRESS"]
+ SMTP_TO_ADDRESS: ClassVar[str] = os.environ["SMTP_TO_ADDRESS"]
HTTP_PUBLIC_URL: ClassVar[str] = os.environ["HTTP_PUBLIC_URL"]
except KeyError:
msg = (
diff --git a/server/src/scimodom/services/mail.py b/server/src/scimodom/services/mail.py
index 7e6250e6..e9499162 100644
--- a/server/src/scimodom/services/mail.py
+++ b/server/src/scimodom/services/mail.py
@@ -109,6 +109,18 @@ def send_password_reset_token(self, email: str, token: str):
""",
)
+ def send_project_request_notification(self, uuid: str):
+ """Notify aministrator of a new project submission.
+
+ :param uuid: Project temporary identifier
+ :type uuid: str
+ """
+ self._send(
+ to_address=Config.SMTP_TO_ADDRESS,
+ subject="Sci-ModoM - NEW PROJECT REQUEST RECEIVED",
+ text=f"""Project template: {uuid}.json""",
+ )
+
_cached_mail_service: Optional[MailService] = None
diff --git a/server/src/scimodom/services/project.py b/server/src/scimodom/services/project.py
index e238985e..7973c1df 100644
--- a/server/src/scimodom/services/project.py
+++ b/server/src/scimodom/services/project.py
@@ -52,6 +52,7 @@ class ProjectService:
SMID_LENGTH: ClassVar[int] = specs.SMID_LENGTH
DATA_PATH: ClassVar[str | Path] = Config.DATA_PATH
DATA_SUB_PATH: ClassVar[str] = "metadata"
+ DATA_SUB_PATH_SUB: ClassVar[str] = "project_requests"
def __init__(self, session: Session, project: dict) -> None:
"""Initializer method."""
@@ -71,6 +72,55 @@ def __new__(cls, session: Session, project: dict):
else:
return super(ProjectService, cls).__new__(cls)
+ @staticmethod
+ def create_project_request(project: dict):
+ """Project request constructor.
+
+ :param project: Project description (json template)
+ :type project: dict
+ """
+ project_request_path = Path(
+ ProjectService.DATA_PATH,
+ ProjectService.DATA_SUB_PATH,
+ ProjectService.DATA_SUB_PATH_SUB,
+ )
+ if not project_request_path.is_dir():
+ msg = f"DATA PATH {project_request_path} not found! Terminating!"
+ raise FileNotFoundError(msg)
+
+ # reformat project template
+ forename = project["forename"]
+ surname = project["surname"]
+ project["contact_name"] = f"{surname}, {forename}"
+ project["external_sources"] = project.get("external_sources", None)
+ for d in utils.to_list(project["external_sources"]):
+ d["doi"] = d["doi"] if d["doi"] != "" else None
+ d["pmid"] = d["pmid"] if d["pmid"] != "" else None
+ project["date_published"] = project.get("date_published", None)
+ for d in utils.to_list(project["metadata"]):
+ d["organism"] = {
+ "taxa_id": d["taxa_id"],
+ "cto": d["cto"],
+ "assembly": d["assembly_name"],
+ }
+ d["extra"] = {
+ "assembly_id": d.get("assembly", None),
+ "note": d["note"] if d["note"] != "" else None,
+ }
+ del d["taxa_id"]
+ del d["cto"]
+ del d["assembly_name"]
+ del d["assembly"]
+ del d["note"]
+ del project["forename"]
+ del project["surname"]
+
+ # hard coded length = 12
+ uuid = utils.gen_short_uuid(12, [])
+ with open(Path(project_request_path, f"{uuid}.json"), "w") as f:
+ json.dump(project, f, indent="\t")
+ return uuid
+
def create_project(self, wo_assembly: bool = False) -> None:
"""Project constructor."""
self._validate_keys()
diff --git a/server/src/scimodom/utils/url_routes.py b/server/src/scimodom/utils/url_routes.py
index 5495cbbd..0b665544 100644
--- a/server/src/scimodom/utils/url_routes.py
+++ b/server/src/scimodom/utils/url_routes.py
@@ -7,6 +7,7 @@
USER_API_ROUTE = f"/{API_PREFIX}/user"
ACCESS_API_ROUTE = f"/{API_PREFIX}/access"
UPLOAD_API_ROUTE = f"/{API_PREFIX}/upload"
+DATA_MANAGEMENT_API_ROUTE = f"/{API_PREFIX}/management"
CONFIRM_USER_REGISTRATION_URI = "confirm_user_registration"
REQUEST_PASSWORD_RESET_URI = "request_password_reset"
From 8f6b8242bc63aa90f8292e091565ed25e8fc739b Mon Sep 17 00:00:00 2001
From: Etienne Boileau
Date: Fri, 19 Apr 2024 15:14:12 +0200
Subject: [PATCH 10/20] FIX project refmt
---
server/src/scimodom/services/project.py | 34 ++++++++++++-------------
1 file changed, 17 insertions(+), 17 deletions(-)
diff --git a/server/src/scimodom/services/project.py b/server/src/scimodom/services/project.py
index 7973c1df..c419285c 100644
--- a/server/src/scimodom/services/project.py
+++ b/server/src/scimodom/services/project.py
@@ -97,23 +97,23 @@ def create_project_request(project: dict):
d["doi"] = d["doi"] if d["doi"] != "" else None
d["pmid"] = d["pmid"] if d["pmid"] != "" else None
project["date_published"] = project.get("date_published", None)
- for d in utils.to_list(project["metadata"]):
- d["organism"] = {
- "taxa_id": d["taxa_id"],
- "cto": d["cto"],
- "assembly": d["assembly_name"],
- }
- d["extra"] = {
- "assembly_id": d.get("assembly", None),
- "note": d["note"] if d["note"] != "" else None,
- }
- del d["taxa_id"]
- del d["cto"]
- del d["assembly_name"]
- del d["assembly"]
- del d["note"]
- del project["forename"]
- del project["surname"]
+ for d in utils.to_list(project["metadata"]):
+ d["organism"] = {
+ "taxa_id": d["taxa_id"],
+ "cto": d["cto"],
+ "assembly": d["assembly_name"],
+ }
+ d["extra"] = {
+ "assembly_id": d.get("assembly", None),
+ "note": d["note"] if d["note"] != "" else None,
+ }
+ del d["taxa_id"]
+ del d["cto"]
+ del d["assembly_name"]
+ del d["assembly"]
+ del d["note"]
+ del project["forename"]
+ del project["surname"]
# hard coded length = 12
uuid = utils.gen_short_uuid(12, [])
From d575b174944efd495d9fbb70675a024ec979bbeb Mon Sep 17 00:00:00 2001
From: Etienne Boileau
Date: Mon, 22 Apr 2024 17:09:39 +0200
Subject: [PATCH 11/20] WIP #78 dirty
---
client/src/components/project/ProjectList.vue | 69 +++++
client/src/components/ui/FormMultiSelect.vue | 75 +++++
client/src/components/upload/UploadForm.vue | 275 ++++++++++++++++++
client/src/main.js | 4 +
client/src/views/UploadView.vue | 45 +--
server/src/scimodom/api/public.py | 7 +
server/src/scimodom/services/public.py | 24 ++
7 files changed, 482 insertions(+), 17 deletions(-)
create mode 100644 client/src/components/project/ProjectList.vue
create mode 100644 client/src/components/ui/FormMultiSelect.vue
create mode 100644 client/src/components/upload/UploadForm.vue
diff --git a/client/src/components/project/ProjectList.vue b/client/src/components/project/ProjectList.vue
new file mode 100644
index 00000000..4089fca4
--- /dev/null
+++ b/client/src/components/project/ProjectList.vue
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/client/src/components/ui/FormMultiSelect.vue b/client/src/components/ui/FormMultiSelect.vue
new file mode 100644
index 00000000..0e985f3b
--- /dev/null
+++ b/client/src/components/ui/FormMultiSelect.vue
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
+
+
+
+ {{ error }}
+
+
+
diff --git a/client/src/components/upload/UploadForm.vue b/client/src/components/upload/UploadForm.vue
new file mode 100644
index 00000000..8f04be7b
--- /dev/null
+++ b/client/src/components/upload/UploadForm.vue
@@ -0,0 +1,275 @@
+
+
+
+
+
diff --git a/client/src/main.js b/client/src/main.js
index 75a91a7a..fde04b2c 100644
--- a/client/src/main.js
+++ b/client/src/main.js
@@ -17,9 +17,11 @@ import Column from 'primevue/column'
import ColumnGroup from 'primevue/columngroup'
import DataTable from 'primevue/datatable'
import Dialog from 'primevue/dialog'
+import DialogService from 'primevue/dialogservice'
import Divider from 'primevue/divider'
import Dropdown from 'primevue/dropdown'
import FileUpload from 'primevue/fileupload'
+import DynamicDialog from 'primevue/dynamicdialog'
import InputNumber from 'primevue/inputnumber'
import InputText from 'primevue/inputtext'
import Menu from 'primevue/menu'
@@ -48,6 +50,7 @@ const app = createApp(App)
app.use(PrimeVue, { unstyled: true, pt: WindScm, ripple: true })
app.use(ToastService)
+app.use(DialogService)
app.use(router)
app.use(VueCookies)
app.use(createPinia())
@@ -60,6 +63,7 @@ app.component('DataTable', DataTable)
app.component('Dialog', Dialog)
app.component('Divider', Divider)
app.component('Dropdown', Dropdown)
+app.component('DynamicDialog', DynamicDialog)
app.component('FileUpload', FileUpload)
app.component('InputNumber', InputNumber)
app.component('InputText', InputText)
diff --git a/client/src/views/UploadView.vue b/client/src/views/UploadView.vue
index 68e2677f..98c51b33 100644
--- a/client/src/views/UploadView.vue
+++ b/client/src/views/UploadView.vue
@@ -1,21 +1,5 @@
@@ -43,6 +27,33 @@ const onSubmit = handleSubmit((values) => {
+
+
+
+
+
+ Upload EUF (bedRMod)
+
+
+
+
+
+
+
+
+ Attach other files
+
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
+ incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud
+ exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure
+ dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
+ Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt
+ mollit anim id est laborum.
+
+
+
diff --git a/server/src/scimodom/api/public.py b/server/src/scimodom/api/public.py
index 398c17d2..216071f2 100644
--- a/server/src/scimodom/api/public.py
+++ b/server/src/scimodom/api/public.py
@@ -16,6 +16,13 @@ def get_modification():
return public_service.get_modomics()
+@api.route("/smid", methods=["GET"])
+@cross_origin(supports_credentials=True)
+def get_smid():
+ public_service = get_public_service()
+ return public_service.get_project()
+
+
@api.route("/method", methods=["GET"])
@cross_origin(supports_credentials=True)
def get_method():
diff --git a/server/src/scimodom/services/public.py b/server/src/scimodom/services/public.py
index dbbd1de9..a11e5471 100644
--- a/server/src/scimodom/services/public.py
+++ b/server/src/scimodom/services/public.py
@@ -20,6 +20,7 @@
Modomics,
Organism,
Project,
+ ProjectContact,
ProjectSource,
Taxonomy,
Taxa,
@@ -63,6 +64,29 @@ class PublicService:
def __init__(self, session: Session):
self._session = session
+ def get_project(self):
+ """Get all projects.
+
+ :returns: Query result
+ :rtype: list of dict
+ """
+
+ query = (
+ select(
+ Project.id,
+ Project.title,
+ Project.summary,
+ Project.date_added,
+ ProjectContact.contact_name,
+ ProjectContact.contact_institution,
+ func.group_concat(ProjectSource.doi.distinct()).label("doi"),
+ func.group_concat(ProjectSource.pmid.distinct()).label("pmid"),
+ )
+ .join_from(Project, ProjectContact, Project.inst_contact)
+ .join_from(Project, ProjectSource, Project.sources)
+ ).group_by(Project.id)
+ return self._dump(query)
+
def get_modomics(self):
"""Get all modifications.
From f34f02c8f4227be48105c72578c67ed1e80caee0 Mon Sep 17 00:00:00 2001
From: Etienne Boileau
Date: Tue, 23 Apr 2024 11:56:06 +0200
Subject: [PATCH 12/20] UPD dropdown form
---
.../components/project/ProjectMetaData.vue | 7 +-
client/src/components/ui/FormDropdown.vue | 30 +++----
client/src/components/ui/FormMultiSelect.vue | 4 +-
client/src/components/upload/UploadForm.vue | 79 ++++++++-----------
4 files changed, 53 insertions(+), 67 deletions(-)
diff --git a/client/src/components/project/ProjectMetaData.vue b/client/src/components/project/ProjectMetaData.vue
index a7dea19b..065f6ab3 100644
--- a/client/src/components/project/ProjectMetaData.vue
+++ b/client/src/components/project/ProjectMetaData.vue
@@ -166,6 +166,7 @@ onMounted(() => {
RNA type
@@ -173,7 +174,8 @@ onMounted(() => {
Modification
@@ -208,7 +210,8 @@ onMounted(() => {
Assembly (select from existing assemblies)
diff --git a/client/src/components/ui/FormDropdown.vue b/client/src/components/ui/FormDropdown.vue
index 792cc032..5902fc61 100644
--- a/client/src/components/ui/FormDropdown.vue
+++ b/client/src/components/ui/FormDropdown.vue
@@ -1,12 +1,11 @@
@@ -65,9 +59,11 @@ const computedModel = computed({
diff --git a/client/src/components/ui/FormMultiSelect.vue b/client/src/components/ui/FormMultiSelect.vue
index 0e985f3b..60406992 100644
--- a/client/src/components/ui/FormMultiSelect.vue
+++ b/client/src/components/ui/FormMultiSelect.vue
@@ -3,7 +3,7 @@
// to be used in a form
import { ref, computed } from 'vue'
-const emit = defineEmits(['onChange'])
+const emit = defineEmits(['change'])
const model = defineModel()
const props = defineProps({
error: {
@@ -60,7 +60,7 @@ const props = defineProps({
{
})
}
-const options = ref()
-/* const modification = ref() */
+const options = ref([])
+const modification = ref([])
+
/* -- */
/* const method = ref([]) */
@@ -105,35 +106,17 @@ const onSubmit = handleSubmit((values) => {
console.log('SUBMIT', values)
})
-/* const updModification = () => {
- * console.log('RNA', rna_type)
- * let opts = options.value.filter(
- * (item) => item.rna == rna_type.id
- * )
- * modification.value = Object.fromEntries(
- * ['modomics_sname', 'modification_id']
- * .filter(key => key in obj)
- * .map(key => [key, obj[key]])
- * )
- * console.log(modification.value)
- * } */
const pick = (obj, keys) =>
Object.keys(obj)
.filter((k) => keys.includes(k))
.reduce((res, k) => Object.assign(res, { [k]: obj[k] }), {})
-const modification = computed(() => {
- if (!(options.value == undefined)) {
- let opts = options.value.filter((item) => item.rna == rna_type.value)
- let subset = opts.map((obj) => pick(obj, ['modomics_sname', 'modification_id']))
- /* rename modification+id to key to use with predefined functions
- * const { k1, ...rest } = old_obj
- * const new_obj = { kA: k1, ...rest} */
- return [...new Map(subset.map((item) => [item.modification_id, item])).values()]
- }
- return []
- /* modification_id.value = undefined
- * return undefined */
-})
+
+const updModification = (event) => {
+ let opts = options.value
+ .filter((item) => item.rna == event)
+ .map((obj) => pick(obj, ['modomics_sname', 'modification_id']))
+ modification.value = [...new Map(opts.map((item) => [item.modification_id, item])).values()]
+}
/* const updateOrganism = () => {
* organism_id.value = undefined
@@ -147,23 +130,24 @@ const modification = computed(() => {
* organism.value = updOrganismFromMod(options.value, selectedModification.value)
* } */
-const technology = ref()
-const organism = computed(() => {
- /* if (!(options.value == undefined)) {
- * let opts = options.value.filter(
- * (item) => item.rna == rna_type.value
- * )
- * let subset = opts.map(obj => pick(obj, ["modomics_sname", "modification_id"]))
- * /* rename modification+id to key to use with predefined functions
- * * const { k1, ...rest } = old_obj
- * * const new_obj = { kA: k1, ...rest} */
- /* * return [...new Map(subset.map(item =>
- * * [item.modification_id, item])).values()]
- * * } */
- return []
- /* modification_id.value = undefined
- * return undefined */
-})
+const technology = ref([])
+const organism = ref([])
+/* const organism = computed(() => {
+ * if (!(options.value == undefined)) {
+ * let opts = options.value.filter(
+ * (item) => item.rna == rna_type.value
+ * )
+ * let subset = opts.map(obj => pick(obj, ["modomics_sname", "modification_id"]))
+ * rename modification+id to key to use with predefined functions
+ * const { k1, ...rest } = old_obj
+ * const new_obj = { kA: k1, ...rest}
+ * return [...new Map(subset.map(item =>
+ * [item.modification_id, item])).values()]
+ * }
+ * return []
+ * /* modification_id.value = undefined
+ * return undefined
+ * }) */
const updOrganism = (value) => {
console.log(value)
@@ -222,6 +206,8 @@ onMounted(() => {
RNA type
@@ -230,7 +216,7 @@ onMounted(() => {
{
Assembly (select from existing assemblies)
From 128ae2943a4eab6ed24fb0a7174469f4b2535f10 Mon Sep 17 00:00:00 2001
From: Etienne Boileau
Date: Tue, 23 Apr 2024 13:36:43 +0200
Subject: [PATCH 13/20] FIX cascade
---
.../components/project/ProjectMetaData.vue | 4 +-
client/src/components/ui/FormCascade.vue | 40 ++++++-------------
client/src/components/upload/UploadForm.vue | 21 ++++++----
client/src/utils/selection.js | 8 +++-
4 files changed, 36 insertions(+), 37 deletions(-)
diff --git a/client/src/components/project/ProjectMetaData.vue b/client/src/components/project/ProjectMetaData.vue
index 065f6ab3..28007d32 100644
--- a/client/src/components/project/ProjectMetaData.vue
+++ b/client/src/components/project/ProjectMetaData.vue
@@ -183,6 +183,7 @@ onMounted(() => {
Method
@@ -196,7 +197,8 @@ onMounted(() => {
Organism
diff --git a/client/src/components/ui/FormCascade.vue b/client/src/components/ui/FormCascade.vue
index ce9aa172..3735f863 100644
--- a/client/src/components/ui/FormCascade.vue
+++ b/client/src/components/ui/FormCascade.vue
@@ -1,12 +1,11 @@
@@ -84,10 +69,11 @@ const computedModel = computed({
{
const options = ref([])
const modification = ref([])
+const organism = ref([])
/* -- */
@@ -111,13 +112,20 @@ const pick = (obj, keys) =>
.filter((k) => keys.includes(k))
.reduce((res, k) => Object.assign(res, { [k]: obj[k] }), {})
-const updModification = (event) => {
+const updModification = (value) => {
let opts = options.value
- .filter((item) => item.rna == event)
+ .filter((item) => item.rna == value)
.map((obj) => pick(obj, ['modomics_sname', 'modification_id']))
modification.value = [...new Map(opts.map((item) => [item.modification_id, item])).values()]
}
+const updOrganism = (value) => {
+ organism_id.value = undefined
+ technology_id.value = undefined
+ organism.value = updOrganismFromMod(options.value, value)
+ console.log(organism.value)
+}
+
/* const updateOrganism = () => {
* organism_id.value = undefined
* .value = undefined
@@ -131,7 +139,6 @@ const updModification = (event) => {
* } */
const technology = ref([])
-const organism = ref([])
/* const organism = computed(() => {
* if (!(options.value == undefined)) {
* let opts = options.value.filter(
@@ -149,10 +156,6 @@ const organism = ref([])
* return undefined
* }) */
-const updOrganism = (value) => {
- console.log(value)
-}
-
const getAssemblies = (taxid) => {
HTTP.get(`/assembly/${taxid}`)
.then(function (response) {
@@ -226,9 +229,11 @@ onMounted(() => {
Organism
+ >Organism RETURNED {{ organism_id }}
{
const kingdom = Object.is(item.kingdom, null) ? item.domain : item.kingdom
return { ...item, kingdom }
})
- opts = selection.filter((item) => item.modification_id == slctMod.key)
+ // if array of modification ids
+ if (Array.isArray(slctMod)) {
+ opts = selection.filter((item) => slctMod.includes(item.modification_id))
+ } else {
+ // single object
+ opts = selection.filter((item) => item.modification_id == slctMod.key)
+ }
let tree = toTree(opts, ['kingdom', 'taxa_sname', 'cto'], 'organism_id')
tree = toCascade(tree)
nestedSort(tree, ['child1', 'child2'])
From bb38e24df50be9d6a5e2c314b1c424bd004cca70 Mon Sep 17 00:00:00 2001
From: Etienne Boileau
Date: Tue, 23 Apr 2024 15:37:50 +0200
Subject: [PATCH 14/20] WIP #78 upload form v1
---
client/src/components/upload/UploadForm.vue | 131 +++++++++-----------
client/src/utils/selection.js | 15 ++-
2 files changed, 73 insertions(+), 73 deletions(-)
diff --git a/client/src/components/upload/UploadForm.vue b/client/src/components/upload/UploadForm.vue
index 3982e238..e78ce630 100644
--- a/client/src/components/upload/UploadForm.vue
+++ b/client/src/components/upload/UploadForm.vue
@@ -5,9 +5,7 @@ import { useForm, useFieldArray } from 'vee-validate'
import { object, array, string, number } from 'yup'
import { HTTP } from '@/services/API'
import { toTree, toCascade, nestedSort } from '@/utils/index.js'
-
import {
- /* updModification, */
updOrganismFromMod,
updTechnologyFromModAndOrg,
updSelectionFromAll
@@ -20,6 +18,17 @@ import FormTextInput from '@/components/ui/FormTextInput.vue'
import FormTextArea from '@/components/ui/FormTextArea.vue'
import FormButton from '@/components/ui/FormButton.vue'
+// TODO define in BE
+const rna = ref([
+ { id: 'mRNA', label: 'mRNA' },
+ { id: 'rRNA', label: 'rRNA' }
+])
+const options = ref([])
+const modification = ref([])
+const organism = ref([])
+const technology = ref([])
+const assembly = ref([])
+
const ProjectList = defineAsyncComponent(() => import('@/components/project/ProjectList.vue'))
const dialog = useDialog()
const showProjects = () => {
@@ -37,9 +46,6 @@ const showProjects = () => {
ptOptions: { mergeProps: true },
modal: true
},
- /* templates: {
- * footer: markRaw(FooterDemo)
- * }, */
onClose: (options) => {
const data = options.data
if (data) {
@@ -53,38 +59,31 @@ const showProjects = () => {
})
}
-const options = ref([])
-const modification = ref([])
-const organism = ref([])
-
-/* -- */
-
-/* const method = ref([]) */
-const taxid = ref([])
-const assembly = ref([])
-// TODO define in BE
-const rna = ref([
- { id: 'mRNA', label: 'mRNA' },
- { id: 'rRNA', label: 'rRNA' }
-])
-
const validationSchema = object({
smid: string().max(8, 'At most 8 characters allowed!').required('SMID is required!'),
rna_type: string().max(32, 'At most 32 characters allowed!').required('RNA type is required!'),
- modification_id: number()
- .integer()
- .typeError('Modification ID must be a number!')
- .transform((_, val) => (val !== '' ? Number(val) : null)),
+ modification_id: array()
+ .of(
+ number()
+ .integer()
+ .typeError('Modification ID must be a number!')
+ .transform((_, val) => (val !== '' ? Number(val) : null))
+ )
+ .required('Modification is required!')
+ .min(1, 'Modification is required!'),
organism_id: number()
.integer()
+ .required('Organism is required!')
.typeError('Organism ID must be a number!')
.transform((_, val) => (val !== '' ? Number(val) : null)),
assembly_id: number()
.integer()
+ .required('Assembly is required!')
.typeError('Assembly ID must be a number!')
.transform((_, val) => (val !== '' ? Number(val) : null)),
technology_id: number()
.integer()
+ .required('Technology is required!')
.typeError('Technology ID must be a number!')
.transform((_, val) => (val !== '' ? Number(val) : null)),
title: string().required('Title is required!').max(255, 'At most 255 characters allowed!')
@@ -113,6 +112,10 @@ const pick = (obj, keys) =>
.reduce((res, k) => Object.assign(res, { [k]: obj[k] }), {})
const updModification = (value) => {
+ modification_id.value = undefined
+ organism_id.value = undefined
+ technology_id.value = undefined
+ assembly_id.value = undefined
let opts = options.value
.filter((item) => item.rna == value)
.map((obj) => pick(obj, ['modomics_sname', 'modification_id']))
@@ -120,43 +123,29 @@ const updModification = (value) => {
}
const updOrganism = (value) => {
+ organism.value = []
organism_id.value = undefined
technology_id.value = undefined
- organism.value = updOrganismFromMod(options.value, value)
- console.log(organism.value)
+ assembly_id.value = undefined
+ if (value.length > 0) {
+ organism.value = updOrganismFromMod(options.value, value)
+ }
}
-/* const updateOrganism = () => {
- * organism_id.value = undefined
- * .value = undefined
- * selectedChrom.value = undefined
- * selectedChromStart.value = undefined
- * selectedChromEnd.value = undefined
- * technology.value = undefined
- * selection.value = undefined
- * records.value = undefined
- * organism.value = updOrganismFromMod(options.value, selectedModification.value)
- * } */
-
-const technology = ref([])
-/* const organism = computed(() => {
- * if (!(options.value == undefined)) {
- * let opts = options.value.filter(
- * (item) => item.rna == rna_type.value
- * )
- * let subset = opts.map(obj => pick(obj, ["modomics_sname", "modification_id"]))
- * rename modification+id to key to use with predefined functions
- * const { k1, ...rest } = old_obj
- * const new_obj = { kA: k1, ...rest}
- * return [...new Map(subset.map(item =>
- * [item.modification_id, item])).values()]
- * }
- * return []
- * /* modification_id.value = undefined
- * return undefined
- * }) */
+const updTechnology = (value) => {
+ technology.value = []
+ technology_id.value = undefined
+ technology.value = updTechnologyFromModAndOrg(options.value, modification_id.value, {
+ key: value
+ })
+}
-const getAssemblies = (taxid) => {
+const getAssemblies = (value) => {
+ assembly.value = []
+ assembly_id.value = undefined
+ let taxid = options.value
+ .filter((item) => item.organism_id == value)
+ .map((obj) => pick(obj, ['taxa_id']))[0].taxa_id
HTTP.get(`/assembly/${taxid}`)
.then(function (response) {
assembly.value = response.data
@@ -215,7 +204,6 @@ onMounted(() => {
placeholder="Select RNA type"
>RNA type
- RNA: {{ rna_type }} MODS: {{ modification_id }}
{
Organism RETURNED {{ organism_id }}
+ >Organism
+ Assembly
+
Technology
@@ -246,21 +246,12 @@ onMounted(() => {
v-model="title"
:error="errors.title"
placeholder="Wild type mouse heart (Tech-seq) treatment X ..."
- >Dataset title
- Assembly (select from existing assemblies)
-
+ >Dataset title
+
-
+
diff --git a/client/src/utils/selection.js b/client/src/utils/selection.js
index 50cae846..49ea75bc 100644
--- a/client/src/utils/selection.js
+++ b/client/src/utils/selection.js
@@ -32,9 +32,18 @@ export const updOrganismFromMod = (selection, slctMod) => {
export const updTechnologyFromModAndOrg = (selection, slctMod, slctOrg) => {
// update technology from modification and organism
- let opts = selection.filter(
- (item) => item.modification_id == slctMod.key && item.organism_id == slctOrg.key
- )
+ let opts = selection
+ // if array of modification ids
+ if (Array.isArray(slctMod)) {
+ opts = selection.filter(
+ (item) => slctMod.includes(item.modification_id) && item.organism_id == slctOrg.key
+ )
+ } else {
+ // single object
+ opts = selection.filter(
+ (item) => item.modification_id == slctMod.key && item.organism_id == slctOrg.key
+ )
+ }
let tree = toTree(opts, ['meth', 'tech'], 'technology_id')
nestedSort(tree, ['children'])
return tree
From f66b1093252ed3f3bac1f8c9f6a5f3358f1af5e1 Mon Sep 17 00:00:00 2001
From: Etienne Boileau
Date: Tue, 23 Apr 2024 17:13:26 +0200
Subject: [PATCH 15/20] UPD #78 upload form functional
---
client/src/components/ui/FormTextInput.vue | 6 ++
client/src/components/upload/UploadForm.vue | 70 ++++++++++++++++++---
server/src/scimodom/api/management.py | 67 ++++++++++++++++++++
3 files changed, 133 insertions(+), 10 deletions(-)
diff --git a/client/src/components/ui/FormTextInput.vue b/client/src/components/ui/FormTextInput.vue
index 7e418a69..fe3ab3be 100644
--- a/client/src/components/ui/FormTextInput.vue
+++ b/client/src/components/ui/FormTextInput.vue
@@ -19,6 +19,11 @@ const props = defineProps({
required: false,
default: false
},
+ disabled: {
+ type: Boolean,
+ required: false,
+ default: false
+ },
placeholder: {
type: String,
required: false,
@@ -83,6 +88,7 @@ const ptOptions = props.isLogin || props.isSignIn ? { mergeProps: true } : {}
v-model="model"
:type="type"
:placeholder="props.placeholder"
+ :disabled="props.disabled"
:pt="pt"
:ptOptions="ptOptions"
:class="error ? props.errCls : ''"
diff --git a/client/src/components/upload/UploadForm.vue b/client/src/components/upload/UploadForm.vue
index e78ce630..704d8fb2 100644
--- a/client/src/components/upload/UploadForm.vue
+++ b/client/src/components/upload/UploadForm.vue
@@ -1,9 +1,10 @@
@@ -33,12 +33,17 @@ const submitForm = () => {
- The project form has been successfully validated. Click
- "Submit" to finalise the submission. A
- notification will be sent to your email address as soon as the project is created.
+ Click "Submit" to send a project request. A
+ notification will be sent to your email address as soon as the project is created. Click
+ "Cancel" to drop the request. In the latter case,
+ all information that you entered will be lost.
-
diff --git a/client/src/main.js b/client/src/main.js
index fde04b2c..4660efbf 100644
--- a/client/src/main.js
+++ b/client/src/main.js
@@ -25,6 +25,7 @@ import DynamicDialog from 'primevue/dynamicdialog'
import InputNumber from 'primevue/inputnumber'
import InputText from 'primevue/inputtext'
import Menu from 'primevue/menu'
+import Message from 'primevue/message'
import MultiSelect from 'primevue/multiselect'
import Panel from 'primevue/panel'
import ProgressSpinner from 'primevue/progressspinner'
@@ -68,6 +69,7 @@ app.component('FileUpload', FileUpload)
app.component('InputNumber', InputNumber)
app.component('InputText', InputText)
app.component('Menu', Menu)
+app.component('Message', Message)
app.component('MultiSelect', MultiSelect)
app.component('Panel', Panel)
app.component('ProgressSpinner', ProgressSpinner)
diff --git a/client/src/presets/windscm/message/index.js b/client/src/presets/windscm/message/index.js
index 72150436..481828d7 100644
--- a/client/src/presets/windscm/message/index.js
+++ b/client/src/presets/windscm/message/index.js
@@ -4,7 +4,7 @@ export default {
// Spacing and Shape
'my-2 mx-0',
'rounded-md',
- 'ring-1 ring-inset ring-surface-200 dark:ring-surface-700 ring-offset-0',
+ // 'ring-1 ring-inset ring-surface-200 dark:ring-surface-700 ring-offset-0',
// Colors
'bg-surface-0 dark:bg-surface-800',
diff --git a/docker/app_container/files/entry_point.py b/docker/app_container/files/entry_point.py
index 75198f92..f743edae 100755
--- a/docker/app_container/files/entry_point.py
+++ b/docker/app_container/files/entry_point.py
@@ -9,6 +9,7 @@
SMTP_SERVER = environ.get("SMTP_SERVER")
SMTP_FROM_ADDRESS = environ.get("SMTP_FROM_ADDRESS")
+SMTP_TO_ADDRESS = environ.get("SMTP_TO_ADDRESS")
HTTP_PUBLIC_URL = environ.get("HTTP_PUBLIC_URL")
@@ -24,6 +25,7 @@ def write_env_file():
SESSION_COOKIE_SECURE=True
SMTP_SERVER={SMTP_SERVER}
SMTP_FROM_ADDRESS={SMTP_FROM_ADDRESS}
+SMTP_TO_ADDRESS={SMTP_TO_ADDRESS}
HTTP_PUBLIC_URL={HTTP_PUBLIC_URL}
UPLOAD_PATH=/uploads
IMPORT_PATH=/import
diff --git a/docker/scripts/__create_local_folders.py b/docker/scripts/__create_local_folders.py
index 28c3ddb8..06437046 100755
--- a/docker/scripts/__create_local_folders.py
+++ b/docker/scripts/__create_local_folders.py
@@ -20,6 +20,7 @@
HOST_IMPORT_DIR,
HOST_DATA_DIR,
Path(HOST_DATA_DIR, "metadata"),
+ Path(HOST_DATA_DIR, "metadata", "project_requests"),
Path(HOST_DATA_DIR, "annotation"),
Path(HOST_DATA_DIR, "assembly"),
]
diff --git a/server/src/scimodom/api/management.py b/server/src/scimodom/api/management.py
index 45c3bd53..3b1b537c 100644
--- a/server/src/scimodom/api/management.py
+++ b/server/src/scimodom/api/management.py
@@ -34,7 +34,7 @@ def get_project():
return (
jsonify(
{
- "result": "Failed to save the project submission form. Contact the administrator."
+ "message": "Failed to save the project submission form. Contact the administrator."
}
),
500,
@@ -44,16 +44,16 @@ def get_project():
try:
mail_service.send_project_request_notification(uuid)
except SMTPException as exc:
- logger.error(f"Failed to sent out notification email: {exc}")
+ logger.error(f"Failed to send out notification email: {exc}")
return (
jsonify(
{
- "result": "Failed to sent out notification email. Contact the administrator."
+ "message": f"Project form successfully submitted, but failed to send out notification email. Contact the administrator with this ID: {uuid}."
}
),
500,
)
- return jsonify({"result": "Ok"}), 200
+ return jsonify({"message": "OK"}), 200
@management_api.route("/dataset", methods=["POST"])
From 387efb149ea15a6146ed678cbbc9096f62c74ab8 Mon Sep 17 00:00:00 2001
From: Etienne Boileau
Date: Wed, 24 Apr 2024 12:54:05 +0200
Subject: [PATCH 17/20] ADD upload path
---
server/src/scimodom/config.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/server/src/scimodom/config.py b/server/src/scimodom/config.py
index e5d8e622..22c40a4a 100644
--- a/server/src/scimodom/config.py
+++ b/server/src/scimodom/config.py
@@ -60,6 +60,7 @@ class Config:
IMPORT_PATH: ClassVar[str | Path] = os.getenv("IMPORT_PATH", "import")
DATA_PATH: ClassVar[str | Path] = os.getenv("DATA_PATH", "data")
+ UPLOAD_PATH: ClassVar[str | Path] = os.getenv("UPLOAD_PATH", "uploads")
FRONTEND_PATH: ClassVar[Path] = Path(
os.getenv("FRONTEND_PATH", DEFAULT_FRONTEND_PATH)
)
From 390df89150a8f42ca5158eef8daed465d473c94d Mon Sep 17 00:00:00 2001
From: Etienne Boileau
Date: Wed, 24 Apr 2024 14:25:58 +0200
Subject: [PATCH 18/20] UPD project table, FIX alembic
---
server/migrations/env.py | 2 ++
.../versions/1e684fab4e13_add_user_table.py | 9 ++++--
.../versions/2030d7195a9b_upd_project.py | 32 +++++++++++++++++++
server/src/scimodom/database/models.py | 6 ++--
server/src/scimodom/services/project.py | 8 +++--
5 files changed, 50 insertions(+), 7 deletions(-)
create mode 100644 server/migrations/versions/2030d7195a9b_upd_project.py
diff --git a/server/migrations/env.py b/server/migrations/env.py
index 1a93e75f..6e6d2da7 100644
--- a/server/migrations/env.py
+++ b/server/migrations/env.py
@@ -6,8 +6,10 @@
from alembic import context
from scimodom.database.database import Base
+import scimodom.database.models # noqa
from scimodom.config import Config
+
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
diff --git a/server/migrations/versions/1e684fab4e13_add_user_table.py b/server/migrations/versions/1e684fab4e13_add_user_table.py
index 5d58943a..5ce075da 100644
--- a/server/migrations/versions/1e684fab4e13_add_user_table.py
+++ b/server/migrations/versions/1e684fab4e13_add_user_table.py
@@ -30,8 +30,13 @@ def upgrade() -> None:
sa.Column(
"email", sa.String(length=320), nullable=False, index=True, unique=True
),
- sa.Column("state", sa.Enum(UserState), default=UserState.wait_for_confirmation),
- sa.Column("password_hash", sa.String(length=128)),
+ sa.Column(
+ "state",
+ sa.Enum(UserState),
+ default=UserState.wait_for_confirmation,
+ nullable=False,
+ ),
+ sa.Column("password_hash", sa.String(length=128), nullable=True),
sa.Column("confirmation_token", sa.String(length=32), nullable=True),
)
diff --git a/server/migrations/versions/2030d7195a9b_upd_project.py b/server/migrations/versions/2030d7195a9b_upd_project.py
new file mode 100644
index 00000000..c2993273
--- /dev/null
+++ b/server/migrations/versions/2030d7195a9b_upd_project.py
@@ -0,0 +1,32 @@
+"""upd_project
+
+Revision ID: 2030d7195a9b
+Revises: 1e684fab4e13
+Create Date: 2024-04-24 14:10:45.898326
+
+"""
+from alembic import op
+import sqlalchemy as sa
+from sqlalchemy.dialects import mysql
+
+# revision identifiers, used by Alembic.
+revision = "2030d7195a9b"
+down_revision = "1e684fab4e13"
+branch_labels = None
+depends_on = None
+
+
+def upgrade() -> None:
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.alter_column(
+ "project", "date_published", existing_type=mysql.DATETIME(), nullable=True
+ )
+ # ### end Alembic commands ###
+
+
+def downgrade() -> None:
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.alter_column(
+ "project", "date_published", existing_type=mysql.DATETIME(), nullable=False
+ )
+ # ### end Alembic commands ###
diff --git a/server/src/scimodom/database/models.py b/server/src/scimodom/database/models.py
index 0cacb2c7..6f1304af 100644
--- a/server/src/scimodom/database/models.py
+++ b/server/src/scimodom/database/models.py
@@ -242,7 +242,7 @@ class Project(Base):
ForeignKey("project_contact.id"), index=True
)
date_published: Mapped[datetime] = mapped_column(
- DateTime, nullable=False
+ DateTime, nullable=True
) # datetime declaration/default format ? YYYY-MM-DD ISO 8601
date_added: Mapped[datetime] = mapped_column(DateTime, nullable=False)
@@ -389,6 +389,6 @@ class User(Base):
email: Mapped[str] = mapped_column(
String(320), nullable=False, index=True, unique=True
)
- state: Mapped[UserState] = mapped_column(Enum(UserState))
- password_hash: Mapped[str] = mapped_column(String(128))
+ state: Mapped[UserState] = mapped_column(Enum(UserState), nullable=False)
+ password_hash: Mapped[str] = mapped_column(String(128), nullable=True)
confirmation_token: Mapped[str] = mapped_column(String(32), nullable=True)
diff --git a/server/src/scimodom/services/project.py b/server/src/scimodom/services/project.py
index c419285c..59849f4c 100644
--- a/server/src/scimodom/services/project.py
+++ b/server/src/scimodom/services/project.py
@@ -92,11 +92,11 @@ def create_project_request(project: dict):
forename = project["forename"]
surname = project["surname"]
project["contact_name"] = f"{surname}, {forename}"
+ project["date_published"] = project.get("date_published", None)
project["external_sources"] = project.get("external_sources", None)
for d in utils.to_list(project["external_sources"]):
d["doi"] = d["doi"] if d["doi"] != "" else None
d["pmid"] = d["pmid"] if d["pmid"] != "" else None
- project["date_published"] = project.get("date_published", None)
for d in utils.to_list(project["metadata"]):
d["organism"] = {
"taxa_id": d["taxa_id"],
@@ -325,13 +325,17 @@ def _create_smid(self) -> None:
contact_id = self._add_contact()
stamp = datetime.now(timezone.utc).replace(microsecond=0) # .isoformat()
+ try:
+ date_published = datetime.fromisoformat(self._project["date_published"])
+ except TypeError:
+ date_published = None
project = Project(
id=self._smid,
title=self._project["title"],
summary=self._project["summary"],
contact_id=contact_id,
- date_published=datetime.fromisoformat(self._project["date_published"]),
+ date_published=date_published,
date_added=stamp,
)
From d16fe277b6fa6fab2f75d21c3889be4b7fb0b499 Mon Sep 17 00:00:00 2001
From: Etienne Boileau
Date: Wed, 24 Apr 2024 14:30:51 +0200
Subject: [PATCH 19/20] ADD test nullable date published
---
server/tests/unit/services/test_project.py | 29 +++++++++++++++++++---
1 file changed, 26 insertions(+), 3 deletions(-)
diff --git a/server/tests/unit/services/test_project.py b/server/tests/unit/services/test_project.py
index 8b0b2a90..7c6fb418 100644
--- a/server/tests/unit/services/test_project.py
+++ b/server/tests/unit/services/test_project.py
@@ -14,7 +14,11 @@
def _get_project(
- project_template, external_sources_fmt="list", metadata_fmt="list", missing_key=None
+ project_template,
+ external_sources_fmt="list",
+ metadata_fmt="list",
+ missing_key=None,
+ missing_date=False,
):
"""\
2023-08-25 Project template (JSON format).
@@ -31,6 +35,8 @@ def _get_project(
"metadata" format (list or dict)
missing_key: str or None
missing_key
+ missing_date: Bool
+ missing_date
Returns
-------
@@ -50,6 +56,9 @@ def _get_project(
else:
raise ValueError
+ if missing_date:
+ project["date_published"] = None
+
metadata = project["metadata"]
if metadata_fmt == "list":
pass
@@ -236,7 +245,16 @@ def test_project_validate_entry(Session, project_template, data_path):
assert ProjectService(Session(), project)._validate_entry() is None
-def test_project_create_project(Session, setup, project_template, data_path):
+@pytest.mark.parametrize(
+ "missing_date",
+ [
+ False,
+ True,
+ ],
+)
+def test_project_create_project(
+ missing_date, Session, setup, project_template, data_path
+):
with Session() as session, session.begin():
session.add_all(setup)
@@ -246,17 +264,22 @@ def test_project_create_project(Session, setup, project_template, data_path):
external_sources_fmt="list",
metadata_fmt="list",
missing_key=None,
+ missing_date=missing_date,
)
# AssemblyService tested in test_assembly.py
project_instance = ProjectService(Session(), project)
project_instance.create_project(wo_assembly=True)
project_smid = project_instance.get_smid()
+ date_published = datetime.fromisoformat("2024-01-01")
+ if missing_date:
+ date_published = None
+
with Session() as session, session.begin():
records = session.execute(select(Project)).scalar()
assert records.title == "Title"
assert records.summary == "Summary"
- assert records.date_published == datetime.fromisoformat("2024-01-01")
+ assert records.date_published == date_published
assert records.date_added.year == stamp.year
assert records.date_added.month == stamp.month
assert records.date_added.day == stamp.day
From f844d0085d164e1c75ca7959b553d59ee250e2a5 Mon Sep 17 00:00:00 2001
From: Etienne Boileau
Date: Wed, 24 Apr 2024 17:18:19 +0200
Subject: [PATCH 20/20] WIP #78 error handling
---
.../components/project/ProjectSubmission.vue | 1 +
client/src/components/upload/UploadForm.vue | 29 +++++----
server/src/scimodom/api/management.py | 60 +++++++++++--------
server/src/scimodom/services/dataset.py | 2 +-
4 files changed, 55 insertions(+), 37 deletions(-)
diff --git a/client/src/components/project/ProjectSubmission.vue b/client/src/components/project/ProjectSubmission.vue
index 4680c765..ad398c8e 100644
--- a/client/src/components/project/ProjectSubmission.vue
+++ b/client/src/components/project/ProjectSubmission.vue
@@ -16,6 +16,7 @@ const submitForm = () => {
})
.catch((error) => {
message.value = error.response.data.message
+ console.log(error)
})
}
diff --git a/client/src/components/upload/UploadForm.vue b/client/src/components/upload/UploadForm.vue
index 704d8fb2..174c3c7a 100644
--- a/client/src/components/upload/UploadForm.vue
+++ b/client/src/components/upload/UploadForm.vue
@@ -31,6 +31,7 @@ const modification = ref([])
const organism = ref([])
const technology = ref([])
const assembly = ref([])
+const message = ref()
const uploadURL = HTTP.getUri() + '/upload'
@@ -118,12 +119,8 @@ const onSubmit = handleSubmit((values) => {
}
})
.catch((error) => {
- return {
- status: error.response ? error.response.status : 0,
- data: {},
- error: error.message
- }
- // on error what to do next?
+ message.value = error.response.data.message
+ console.log(error)
})
})
@@ -133,6 +130,10 @@ const onUpload = (event) => {
path.value = event.xhr.response
}
+const dropForm = () => {
+ router.push({ name: 'home' })
+}
+
const pick = (obj, keys) =>
Object.keys(obj)
.filter((k) => keys.includes(k))
@@ -197,7 +198,7 @@ onMounted(() => {