diff --git a/pyflask/apis/neuroconv.py b/pyflask/apis/neuroconv.py index ea9ade85e..03fa85220 100644 --- a/pyflask/apis/neuroconv.py +++ b/pyflask/apis/neuroconv.py @@ -16,6 +16,9 @@ inspect_nwb_file, inspect_nwb_folder, inspect_multiple_filesystem_objects, + upload_to_dandi, + upload_folder_to_dandi, + upload_multiple_filesystem_objects_to_dandi, ) from errorHandlers import notBadRequestException @@ -112,13 +115,11 @@ def post(self): neuroconv_api.abort(500, str(e)) -@neuroconv_api.route("/upload") +@neuroconv_api.route("/upload/project") class Upload(Resource): @neuroconv_api.doc(responses={200: "Success", 400: "Bad Request", 500: "Internal server error"}) def post(self): try: - from manageNeuroconv import upload_to_dandi - return upload_to_dandi(**neuroconv_api.payload) except Exception as e: @@ -131,8 +132,6 @@ class Upload(Resource): @neuroconv_api.doc(responses={200: "Success", 400: "Bad Request", 500: "Internal server error"}) def post(self): try: - from manageNeuroconv import upload_folder_to_dandi - return upload_folder_to_dandi(**neuroconv_api.payload) except Exception as e: @@ -140,6 +139,29 @@ def post(self): neuroconv_api.abort(500, str(e)) +@neuroconv_api.route("/upload") +class Upload(Resource): + @neuroconv_api.doc(responses={200: "Success", 400: "Bad Request", 500: "Internal server error"}) + def post(self): + from os.path import isdir + + try: + paths = neuroconv_api.payload["filesystem_paths"] + + if len(paths) == 1 and isdir(paths[0]): + kwargs = {**neuroconv_api.payload} + del kwargs["filesystem_paths"] + kwargs["nwb_folder_path"] = paths[0] + return upload_folder_to_dandi(**kwargs) + + else: + return upload_multiple_filesystem_objects_to_dandi(**neuroconv_api.payload) + + except Exception as e: + if notBadRequestException(e): + neuroconv_api.abort(500, str(e)) + + @neuroconv_api.route("/inspect_file") class InspectNWBFile(Resource): @neuroconv_api.doc(responses={200: "Success", 400: "Bad Request", 500: "Internal server error"}) diff --git a/pyflask/manageNeuroconv/__init__.py b/pyflask/manageNeuroconv/__init__.py index b3fae5e89..38fd2077b 100644 --- a/pyflask/manageNeuroconv/__init__.py +++ b/pyflask/manageNeuroconv/__init__.py @@ -7,6 +7,7 @@ validate_metadata, upload_to_dandi, upload_folder_to_dandi, + upload_multiple_filesystem_objects_to_dandi, listen_to_neuroconv_events, generate_dataset, inspect_nwb_file, diff --git a/pyflask/manageNeuroconv/manage_neuroconv.py b/pyflask/manageNeuroconv/manage_neuroconv.py index 8ff0c6d11..df6aa4a3d 100644 --- a/pyflask/manageNeuroconv/manage_neuroconv.py +++ b/pyflask/manageNeuroconv/manage_neuroconv.py @@ -419,6 +419,16 @@ def update_conversion_progress(**kwargs): return dict(file=str(resolved_output_path)) +def upload_multiple_filesystem_objects_to_dandi(**kwargs): + tmp_folder_path = aggregate_symlinks_in_new_directory(kwargs["filesystem_paths"], "upload") + innerKwargs = {**kwargs} + del innerKwargs["filesystem_paths"] + innerKwargs["nwb_folder_path"] = tmp_folder_path + result = upload_folder_to_dandi(**innerKwargs) + rmtree(tmp_folder_path) + return result + + def upload_folder_to_dandi( dandiset_id: str, api_key: str, diff --git a/schemas/json/dandi/standalone.json b/schemas/json/dandi/standalone.json index e56062017..fa210901f 100644 --- a/schemas/json/dandi/standalone.json +++ b/schemas/json/dandi/standalone.json @@ -1,9 +1,12 @@ { "properties": { - "nwb_folder_path": { - "type": "string", - "format": "directory" + "filesystem_paths": { + "type": "array", + "items":{ + "type": "string", + "format": ["file", "directory"] + } } }, - "required": ["nwb_folder_path"] + "required": ["filesystem_paths"] } diff --git a/src/renderer/src/stories/FileSystemSelector.js b/src/renderer/src/stories/FileSystemSelector.js index d86c873e4..902c714f8 100644 --- a/src/renderer/src/stories/FileSystemSelector.js +++ b/src/renderer/src/stories/FileSystemSelector.js @@ -39,6 +39,10 @@ const componentCSS = css` gap: 5px; } + #button-div > nwb-button { + margin-bottom: 10px; + } + button { background: WhiteSmoke; border: 1px solid #c3c3c3; diff --git a/src/renderer/src/stories/pages/uploads/UploadsPage.js b/src/renderer/src/stories/pages/uploads/UploadsPage.js index 7da82c9a6..d50259348 100644 --- a/src/renderer/src/stories/pages/uploads/UploadsPage.js +++ b/src/renderer/src/stories/pages/uploads/UploadsPage.js @@ -2,6 +2,8 @@ import { html } from "lit"; import { JSONSchemaForm } from "../../JSONSchemaForm.js"; import { Page } from "../Page.js"; import { onThrow } from "../../../errors"; + +const folderPathKey = "filesystem_paths"; import dandiUploadSchema from "../../../../../../schemas/json/dandi/upload.json"; import dandiStandaloneSchema from "../../../../../../schemas/json/dandi/standalone.json"; const dandiSchema = merge(dandiStandaloneSchema, merge(dandiUploadSchema, {}), { arrays: true }); @@ -18,8 +20,10 @@ import { DandiResults } from "../../DandiResults.js"; export const isStaging = (id) => parseInt(id) >= 100000; -export async function uploadToDandi(info, type = "project" in info ? "" : "folder") { - const staging = isStaging(info.dandiset_id); // Automatically detect staging IDs +export async function uploadToDandi(info, type = "project" in info ? "project" : "") { + const { dandiset_id } = info; + + const staging = isStaging(dandiset_id); // Automatically detect staging IDs const whichAPIKey = staging ? "staging_api_key" : "main_api_key"; const api_key = global.data.DANDI?.api_keys?.[whichAPIKey]; @@ -54,7 +58,9 @@ export async function uploadToDandi(info, type = "project" in info ? "" : "folde if (result) notyf.open({ type: "success", - message: `${info.project ?? info.nwb_folder_path} successfully uploaded to Dandiset ${info.dandiset_id}`, + message: `${ + info.project ?? `${info[folderPathKey].length} filesystem entries` + } successfully uploaded to Dandiset ${dandiset_id}`, }); return result; @@ -89,13 +95,12 @@ export class UploadsPage extends Page { }, }); - const folderPathKey = "nwb_folder_path"; // NOTE: API Keys and Dandiset IDs persist across selected project this.form = new JSONSchemaForm({ results: globalState, schema: dandiSchema, sort: ([k1]) => { - if (k1 === "nwb_folder_path") return -1; + if (k1 === folderPathKey) return -1; }, onUpdate: ([id]) => { if (id === folderPathKey) {