diff --git a/src/electron/frontend/core/components/pages/guided-mode/data/GuidedMetadata.js b/src/electron/frontend/core/components/pages/guided-mode/data/GuidedMetadata.js index 724327498b..f1deca9a48 100644 --- a/src/electron/frontend/core/components/pages/guided-mode/data/GuidedMetadata.js +++ b/src/electron/frontend/core/components/pages/guided-mode/data/GuidedMetadata.js @@ -333,7 +333,7 @@ export class GuidedMetadataPage extends ManagedPage { onUpdate: () => (this.unsavedUpdates = "conversions"), - validateOnChange, + validateOnChange: (...args) => validateOnChange.call(this, ...args), onlyRequired: false, onStatusChange: (state) => this.manager.updateState(`sub-${subject}/ses-${session}`, state), diff --git a/src/electron/frontend/core/components/pages/guided-mode/setup/GuidedSubjects.js b/src/electron/frontend/core/components/pages/guided-mode/setup/GuidedSubjects.js index 7d1d516773..e385b843f1 100644 --- a/src/electron/frontend/core/components/pages/guided-mode/setup/GuidedSubjects.js +++ b/src/electron/frontend/core/components/pages/guided-mode/setup/GuidedSubjects.js @@ -111,7 +111,7 @@ export class GuidedSubjectsPage extends Page { schema, formProps: { validateOnChange: (localPath, parent, path) => { - return validateOnChange(localPath, parent, ["Subject", ...path]); + return validateOnChange.call(this, localPath, parent, ["Subject", ...path]); }, }, })); diff --git a/src/electron/frontend/core/validation/index.js b/src/electron/frontend/core/validation/index.js index d21e1bb24e..f4fd1b4db8 100644 --- a/src/electron/frontend/core/validation/index.js +++ b/src/electron/frontend/core/validation/index.js @@ -63,10 +63,12 @@ export async function validateOnChange(name, parent, path, value) { return func.call(this, name, copy, path, value); // Can specify alternative client-side validation } else { const resolvedFunctionName = func.replace(`{*}`, `${name}`); + return fetch(`${baseUrl}/neuroconv/validate`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ + timezone: this.workflow?.timezone?.value, parent: copy, function_name: resolvedFunctionName, }), diff --git a/src/pyflask/manageNeuroconv/manage_neuroconv.py b/src/pyflask/manageNeuroconv/manage_neuroconv.py index 343cea5788..2b70c7d3c6 100644 --- a/src/pyflask/manageNeuroconv/manage_neuroconv.py +++ b/src/pyflask/manageNeuroconv/manage_neuroconv.py @@ -647,34 +647,48 @@ def run_check_function(check_function: callable, arg: dict) -> dict: def validate_subject_metadata( - subject_metadata: dict, check_function_name: str + subject_metadata: dict, check_function_name: str, timezone: Optional[str] = None ): # -> Union[None, InspectorMessage, List[InspectorMessage]]: """Function used to validate subject metadata.""" + import pytz from pynwb.file import Subject check_function = get_check_function(check_function_name) if isinstance(subject_metadata.get("date_of_birth"), str): subject_metadata["date_of_birth"] = datetime.fromisoformat(subject_metadata["date_of_birth"]) + if timezone is not None: + subject_metadata["date_of_birth"] = subject_metadata["date_of_birth"].replace( + tzinfo=pytz.timezone(timezone) + ) return run_check_function(check_function, Subject(**subject_metadata)) def validate_nwbfile_metadata( - nwbfile_metadata: dict, check_function_name: str + nwbfile_metadata: dict, check_function_name: str, timezone: Optional[str] = None ): # -> Union[None, InspectorMessage, List[InspectorMessage]]: """Function used to validate NWBFile metadata.""" + import pytz from pynwb.testing.mock.file import mock_NWBFile check_function = get_check_function(check_function_name) if isinstance(nwbfile_metadata.get("session_start_time"), str): nwbfile_metadata["session_start_time"] = datetime.fromisoformat(nwbfile_metadata["session_start_time"]) + if timezone is not None: + nwbfile_metadata["session_start_time"] = nwbfile_metadata["session_start_time"].replace( + tzinfo=pytz.timezone(timezone) + ) return run_check_function(check_function, mock_NWBFile(**nwbfile_metadata)) -def validate_metadata(metadata: dict, check_function_name: str) -> dict: +def validate_metadata( + metadata: dict, + check_function_name: str, + timezone: Optional[str] = None, +) -> dict: """Function used to validate data using an arbitrary NWB Inspector function.""" from nwbinspector.nwbinspector import InspectorOutputJSONEncoder from pynwb.file import NWBFile, Subject @@ -682,9 +696,9 @@ def validate_metadata(metadata: dict, check_function_name: str) -> dict: check_function = get_check_function(check_function_name) if issubclass(check_function.neurodata_type, Subject): - result = validate_subject_metadata(metadata, check_function_name) + result = validate_subject_metadata(metadata, check_function_name, timezone) elif issubclass(check_function.neurodata_type, NWBFile): - result = validate_nwbfile_metadata(metadata, check_function_name) + result = validate_nwbfile_metadata(metadata, check_function_name, timezone) else: raise ValueError( f"Function {check_function_name} with neurodata_type {check_function.neurodata_type} " @@ -1002,6 +1016,7 @@ def update_conversion_progress(message): resolved_metadata["NWBFile"]["session_start_time"] = datetime.fromisoformat( resolved_metadata["NWBFile"]["session_start_time"] ).replace(tzinfo=zoneinfo.ZoneInfo(info["timezone"])) + if "date_of_birth" in resolved_metadata["Subject"]: resolved_metadata["Subject"]["date_of_birth"] = datetime.fromisoformat( resolved_metadata["Subject"]["date_of_birth"] diff --git a/src/pyflask/namespaces/neuroconv.py b/src/pyflask/namespaces/neuroconv.py index 24b16f3077..3d37dcc156 100644 --- a/src/pyflask/namespaces/neuroconv.py +++ b/src/pyflask/namespaces/neuroconv.py @@ -96,6 +96,7 @@ def post(self): validate_parser = neuroconv_namespace.parser() validate_parser.add_argument("parent", type=dict, required=True) validate_parser.add_argument("function_name", type=str, required=True) +validate_parser.add_argument("timezone", type=str, required=False) @neuroconv_namespace.route("/validate") @@ -104,7 +105,7 @@ class Validate(Resource): @neuroconv_namespace.doc(responses={200: "Success", 400: "Bad Request", 500: "Internal server error"}) def post(self): args = validate_parser.parse_args() - return validate_metadata(args.get("parent"), args.get("function_name")) + return validate_metadata(args.get("parent"), args.get("function_name"), args.get("timezone")) @neuroconv_namespace.route("/upload/project") diff --git a/src/schemas/base-metadata.schema.ts b/src/schemas/base-metadata.schema.ts index dc17ce9820..466993ebe3 100644 --- a/src/schemas/base-metadata.schema.ts +++ b/src/schemas/base-metadata.schema.ts @@ -96,7 +96,11 @@ export const preprocessMetadataSchema = (schema: any = baseMetadataSchema, globa copy.order = [ "NWBFile", "Subject" ] const minDate = "1900-01-01T00:00" - const maxDate = getISODateInTimezone().slice(0, -2) // Restrict date to current date with timezone awareness + + // Set the maximum at tomorrow + const nextDay = new Date() + nextDay.setDate(nextDay.getDate() + 1) + const maxDate = getISODateInTimezone(nextDay).slice(0, -2) // Restrict date to tomorrow (with timezone awareness) // Add unit to weight