From 9a8bc6effdc197c12e626972a5a63a1d28dae928 Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Mon, 3 Jun 2024 11:48:46 -0500 Subject: [PATCH 01/48] Allow user to specify a timezone to use in the GUIDE --- .../pages/guided-mode/setup/Preform.js | 7 ++++ src/schemas/base-metadata.schema.ts | 32 +++++++++++---- src/schemas/timezone.schema.ts | 41 +++++++++++++++++++ 3 files changed, 72 insertions(+), 8 deletions(-) create mode 100644 src/schemas/timezone.schema.ts diff --git a/src/electron/frontend/core/components/pages/guided-mode/setup/Preform.js b/src/electron/frontend/core/components/pages/guided-mode/setup/Preform.js index 81f0a63756..5c359ce646 100644 --- a/src/electron/frontend/core/components/pages/guided-mode/setup/Preform.js +++ b/src/electron/frontend/core/components/pages/guided-mode/setup/Preform.js @@ -3,6 +3,8 @@ import { JSONSchemaForm } from "../../../JSONSchemaForm.js"; import { Page } from "../../Page.js"; import { onThrow } from "../../../../errors"; +import timezoneSchema from '../../../../../../../schemas/timezone.schema' + // ------------------------------------------------------------------------------ // ------------------------ Preform Configuration ------------------------------- // ------------------------------------------------------------------------------ @@ -55,6 +57,11 @@ const questions = { }, }, + timezone: { + ...timezoneSchema, + title: "What timezone is your data in?", + }, + upload_to_dandi: { type: "boolean", title: "Would you like to upload your data to DANDI?", diff --git a/src/schemas/base-metadata.schema.ts b/src/schemas/base-metadata.schema.ts index 1a05c58c81..18fe9782b5 100644 --- a/src/schemas/base-metadata.schema.ts +++ b/src/schemas/base-metadata.schema.ts @@ -6,6 +6,7 @@ import baseMetadataSchema from './json/base_metadata_schema.json' assert { type: import { merge } from '../electron/frontend/core/components/pages/utils' import { drillSchemaProperties } from '../electron/frontend/core/components/pages/guided-mode/data/utils' +import { localTimeZone } from './timezone.schema' const UV_MATH_FORMAT = `µV`; //`µV` const UV_PROPERTIES = ["gain_to_uV", "offset_to_uV"] @@ -41,11 +42,17 @@ function getSpeciesInfo(species: any[][] = []) { } -// Borrowed from https://stackoverflow.com/a/29774197/7290573 -function getCurrentDate() { - const date = new Date() - const offset = date.getTimezoneOffset(); - return (new Date(date.getTime() - (offset*60*1000))).toISOString(); + +function getISODateInTimezone( + timezone = localTimeZone +) { + const date = new Date(); + const utcDate = new Date(date.toLocaleString('en-US', { timeZone: 'UTC' })); + const tzDate = new Date(date.toLocaleString('en-US', { timeZone: timezone })); + const offset = utcDate.getTime() - tzDate.getTime(); + + const adjustedDate = new Date(date.getTime() - offset); + return adjustedDate.toISOString(); } @@ -101,6 +108,10 @@ export const preprocessMetadataSchema = (schema: any = baseMetadataSchema, globa copy.order = [ "NWBFile", "Subject" ] + const minDate = "1900-01-01T00:00" + const maxDate = getISODateInTimezone().slice(0, -2) + + // Add unit to weight const subjectProps = copy.properties.Subject.properties subjectProps.weight.unit = 'kg' @@ -121,13 +132,18 @@ export const preprocessMetadataSchema = (schema: any = baseMetadataSchema, globa strict: false, description: 'The species of your subject.' } - subjectProps.date_of_birth.minimum = "1900-01-01T00:00" - subjectProps.date_of_birth.maximum = getCurrentDate().slice(0, -2) + + subjectProps.date_of_birth.minimum = minDate + subjectProps.date_of_birth.maximum = maxDate // copy.order = ['NWBFile', 'Subject'] - copy.properties.NWBFile.title = 'General Metadata' const nwbProps = copy.properties.NWBFile.properties + copy.properties.NWBFile.title = 'General Metadata' + + nwbProps.session_start_time.minimum = minDate + nwbProps.session_start_time.maximum = maxDate + nwbProps.keywords.items.description = "Provide a single keyword (e.g. Neural circuits, V1, etc.)" // Resolve species suggestions diff --git a/src/schemas/timezone.schema.ts b/src/schemas/timezone.schema.ts new file mode 100644 index 0000000000..a9c5fd9a91 --- /dev/null +++ b/src/schemas/timezone.schema.ts @@ -0,0 +1,41 @@ + +// const timezones = Intl.supportedValuesOf('timeZone'); + +const mostCommonTimezonesWithUTCOffset = { + 'Pacific/Honolulu': '-10:00', + 'America/Anchorage': '-09:00', + 'America/Los_Angeles': '-08:00', + 'America/Denver': '-07:00', + 'America/Chicago': '-06:00', + 'America/New_York': '-05:00', + 'America/Sao_Paulo': '-03:00', + 'Atlantic/Azores': '-01:00', + 'Europe/London': '+00:00', + 'Europe/Paris': '+01:00', + 'Europe/Athens': '+02:00', + 'Asia/Jerusalem': '+02:00', + 'Europe/Moscow': '+03:00', + 'Asia/Dubai': '+04:00', + 'Asia/Karachi': '+05:00', + 'Asia/Dhaka': '+06:00', + 'Asia/Jakarta': '+07:00', + 'Asia/Shanghai': '+08:00', + 'Asia/Tokyo': '+09:00', + 'Australia/Sydney': '+10:00', + 'Pacific/Auckland': '+12:00' +}; + + +export const localTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; + +export default { + type: "string", + description: "Provide a base timezone for all date and time operations in the GUIDE.", + default: localTimeZone, + enum: Object.keys(mostCommonTimezonesWithUTCOffset), + enumLabels: Object.entries(mostCommonTimezonesWithUTCOffset).reduce((acc, [ name, offset ]) => { + acc[name] = `${name} (${offset})` + return acc + }), + strict: true +} \ No newline at end of file From 5b826f5fe0416b80dbf23116978c58410a28e4da Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 3 Jun 2024 16:51:45 +0000 Subject: [PATCH 02/48] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../frontend/core/components/pages/guided-mode/setup/Preform.js | 2 +- src/schemas/base-metadata.schema.ts | 2 +- src/schemas/timezone.schema.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/electron/frontend/core/components/pages/guided-mode/setup/Preform.js b/src/electron/frontend/core/components/pages/guided-mode/setup/Preform.js index 5c359ce646..4a5592684a 100644 --- a/src/electron/frontend/core/components/pages/guided-mode/setup/Preform.js +++ b/src/electron/frontend/core/components/pages/guided-mode/setup/Preform.js @@ -3,7 +3,7 @@ import { JSONSchemaForm } from "../../../JSONSchemaForm.js"; import { Page } from "../../Page.js"; import { onThrow } from "../../../../errors"; -import timezoneSchema from '../../../../../../../schemas/timezone.schema' +import timezoneSchema from "../../../../../../../schemas/timezone.schema"; // ------------------------------------------------------------------------------ // ------------------------ Preform Configuration ------------------------------- diff --git a/src/schemas/base-metadata.schema.ts b/src/schemas/base-metadata.schema.ts index 18fe9782b5..abfaf72b13 100644 --- a/src/schemas/base-metadata.schema.ts +++ b/src/schemas/base-metadata.schema.ts @@ -132,7 +132,7 @@ export const preprocessMetadataSchema = (schema: any = baseMetadataSchema, globa strict: false, description: 'The species of your subject.' } - + subjectProps.date_of_birth.minimum = minDate subjectProps.date_of_birth.maximum = maxDate diff --git a/src/schemas/timezone.schema.ts b/src/schemas/timezone.schema.ts index a9c5fd9a91..4ba1085f29 100644 --- a/src/schemas/timezone.schema.ts +++ b/src/schemas/timezone.schema.ts @@ -38,4 +38,4 @@ export default { return acc }), strict: true -} \ No newline at end of file +} From 4dc776f80a8dc501fa324e00c282f4e0365ad599 Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Mon, 3 Jun 2024 12:21:11 -0500 Subject: [PATCH 03/48] Generate timezone list --- src/schemas/timezone.schema.ts | 33 ++------------------------------- 1 file changed, 2 insertions(+), 31 deletions(-) diff --git a/src/schemas/timezone.schema.ts b/src/schemas/timezone.schema.ts index 4ba1085f29..2528281f66 100644 --- a/src/schemas/timezone.schema.ts +++ b/src/schemas/timezone.schema.ts @@ -1,30 +1,5 @@ -// const timezones = Intl.supportedValuesOf('timeZone'); - -const mostCommonTimezonesWithUTCOffset = { - 'Pacific/Honolulu': '-10:00', - 'America/Anchorage': '-09:00', - 'America/Los_Angeles': '-08:00', - 'America/Denver': '-07:00', - 'America/Chicago': '-06:00', - 'America/New_York': '-05:00', - 'America/Sao_Paulo': '-03:00', - 'Atlantic/Azores': '-01:00', - 'Europe/London': '+00:00', - 'Europe/Paris': '+01:00', - 'Europe/Athens': '+02:00', - 'Asia/Jerusalem': '+02:00', - 'Europe/Moscow': '+03:00', - 'Asia/Dubai': '+04:00', - 'Asia/Karachi': '+05:00', - 'Asia/Dhaka': '+06:00', - 'Asia/Jakarta': '+07:00', - 'Asia/Shanghai': '+08:00', - 'Asia/Tokyo': '+09:00', - 'Australia/Sydney': '+10:00', - 'Pacific/Auckland': '+12:00' -}; - +const timezones = Intl.supportedValuesOf('timeZone'); export const localTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; @@ -32,10 +7,6 @@ export default { type: "string", description: "Provide a base timezone for all date and time operations in the GUIDE.", default: localTimeZone, - enum: Object.keys(mostCommonTimezonesWithUTCOffset), - enumLabels: Object.entries(mostCommonTimezonesWithUTCOffset).reduce((acc, [ name, offset ]) => { - acc[name] = `${name} (${offset})` - return acc - }), + enum: timezones, strict: true } From 303e275c27c5f7797263f5abee26fb4d24e137cf Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Mon, 3 Jun 2024 13:51:26 -0500 Subject: [PATCH 04/48] Provide datetime values with a timezone offset specified. Render differently --- .../core/components/DateTimeSelector.js | 88 +++++++++++++------ .../core/components/JSONSchemaInput.js | 19 ++-- src/schemas/base-metadata.schema.ts | 2 +- 3 files changed, 72 insertions(+), 37 deletions(-) diff --git a/src/electron/frontend/core/components/DateTimeSelector.js b/src/electron/frontend/core/components/DateTimeSelector.js index 4da3a5fbee..3a536111a3 100644 --- a/src/electron/frontend/core/components/DateTimeSelector.js +++ b/src/electron/frontend/core/components/DateTimeSelector.js @@ -1,13 +1,38 @@ import { LitElement, css } from "lit"; -const convertToDateTimeLocalString = (date) => { +export function extractISOString(date, { + // timezone = false, + offset = false, +} = {}) { + + // Function to format the GMT offset + function formatOffset(date) { + let offset = -date.getTimezoneOffset(); // getTimezoneOffset returns the difference in minutes from UTC + const sign = offset >= 0 ? "+" : "-"; + offset = Math.abs(offset); + const hours = String(Math.floor(offset / 60)).padStart(2, '0'); + const minutes = String(offset % 60).padStart(2, '0'); + return `${sign}${hours}:${minutes}`; + } + + // Extract the GMT offset + const gmtOffset = formatOffset(date); + + // Format the date back to the original format with GMT offset const year = date.getFullYear(); - const month = (date.getMonth() + 1).toString().padStart(2, "0"); - const day = date.getDate().toString().padStart(2, "0"); - const hours = date.getHours().toString().padStart(2, "0"); - const minutes = date.getMinutes().toString().padStart(2, "0"); - return `${year}-${month}-${day}T${hours}:${minutes}`; -}; + const month = String(date.getMonth() + 1).padStart(2, '0'); // Months are zero-indexed + const day = String(date.getDate()).padStart(2, '0'); + const hours = String(date.getHours()).padStart(2, '0'); + const minutes = String(date.getMinutes()).padStart(2, '0'); + const seconds = String(date.getSeconds()).padStart(2, '0'); + + // Recreate the ISO string with the GMT offset + let formattedDate = `${year}-${month}-${day}T${hours}:${minutes}:${seconds}`; + if (offset) formattedDate += gmtOffset; + + return formattedDate; +} + export class DateTimeSelector extends LitElement { static get styles() { @@ -19,47 +44,52 @@ export class DateTimeSelector extends LitElement { `; } + + // Manually handle value property get value() { - return this.input.value; + const date = new Date(this.input.value); + return extractISOString(date, { offset: true }); } + + // Render the date without timezone offset set value(newValue) { - if (newValue) this.input.value = newValue; + + if (newValue) this.input.value = extractISOString(new Date(newValue)); + else { const d = new Date(); d.setHours(0, 0, 0, 0); - this.input.value = convertToDateTimeLocalString(d); + this.input.value = extractISOString(d); } } - get min() { - return this.input.min; - } - set min(newValue) { - this.input.min = newValue; + static get properties() { + return { + min: { type: String, reflect: true }, + max: { type: String, reflect: true }, + timezone: { type: String, reflect: true }, + }; } - get max() { - return this.input.max; - } - - set max(newValue) { - this.input.max = newValue; - } - - constructor({ value, min, max } = {}) { + constructor({ + value, + min, + max, + } = {}) { super(); this.input = document.createElement("input"); this.input.type = "datetime-local"; - this.input.min = min; - this.input.max = max; + this.min = min; + this.max = max; + this.addEventListener("click", () => { this.input.focus(); this.input.showPicker(); }); - this.value = value ? convertToDateTimeLocalString(value) : value; + this.value = value; } focus() { @@ -71,6 +101,10 @@ export class DateTimeSelector extends LitElement { } render() { + + this.input.min = min; + this.input.max = max; + return this.input; } } diff --git a/src/electron/frontend/core/components/JSONSchemaInput.js b/src/electron/frontend/core/components/JSONSchemaInput.js index 5e65737232..325afa48a0 100644 --- a/src/electron/frontend/core/components/JSONSchemaInput.js +++ b/src/electron/frontend/core/components/JSONSchemaInput.js @@ -16,18 +16,17 @@ import tippy from "tippy.js"; import { merge } from "./pages/utils"; import { OptionalSection } from "./OptionalSection"; import { InspectorListItem } from "./preview/inspector/InspectorList"; +import { extractISOString } from "./DateTimeSelector"; const isDevelopment = !!import.meta.env; -const dateTimeRegex = /(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})/; - function resolveDateTime(value) { - if (typeof value === "string") { - const match = value.match(dateTimeRegex); - if (match) return `${match[1]}-${match[2]}-${match[3]}T${match[4]}:${match[5]}`; - return value; - } + if (typeof value === "string") return extractISOString(new Date(value), { offset: true }) + return value; +} +function renderDateTime(value) { + if (typeof value === "string") return extractISOString(new Date(value)); return value; } @@ -1226,7 +1225,7 @@ export class JSONSchemaInput extends LitElement { ? "datetime-local" : schema.format ?? (schema.type === "string" ? "text" : schema.type); - const value = isDateTime ? resolveDateTime(this.value) : this.value; + const value = isDateTime ? renderDateTime(this.value) : this.value; const { minimum, maximum, exclusiveMax, exclusiveMin } = schema; const min = exclusiveMin ?? minimum; @@ -1242,6 +1241,7 @@ export class JSONSchemaInput extends LitElement { .min="${min}" .max="${max}" @input=${(ev) => { + let value = ev.target.value; let newValue = value; @@ -1249,6 +1249,7 @@ export class JSONSchemaInput extends LitElement { if (isInteger) value = newValue = parseInt(value); else if (isNumber) value = newValue = parseFloat(value); + else if (isDateTime) value = newValue = resolveDateTime(value) if (isNumber) { if ("min" in schema && newValue < schema.min) newValue = schema.min; @@ -1274,7 +1275,7 @@ export class JSONSchemaInput extends LitElement { const nanHandler = ev.target.parentNode.querySelector(".nan-handler"); if (!(newValue && Number.isNaN(newValue))) nanHandler.checked = false; } - + this.#updateData(fullPath, value); }} @change=${(ev) => validateOnChange && this.#triggerValidation(name, path)} diff --git a/src/schemas/base-metadata.schema.ts b/src/schemas/base-metadata.schema.ts index abfaf72b13..83d5ac830b 100644 --- a/src/schemas/base-metadata.schema.ts +++ b/src/schemas/base-metadata.schema.ts @@ -109,7 +109,7 @@ export const preprocessMetadataSchema = (schema: any = baseMetadataSchema, globa copy.order = [ "NWBFile", "Subject" ] const minDate = "1900-01-01T00:00" - const maxDate = getISODateInTimezone().slice(0, -2) + const maxDate = getISODateInTimezone().slice(0, -2) // Restrict date to current date with timezone awareness // Add unit to weight From ad1e9c9a16fa768226b7c5d0919c131a0ba8089f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 3 Jun 2024 18:52:02 +0000 Subject: [PATCH 05/48] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../core/components/DateTimeSelector.js | 40 ++++++++----------- .../core/components/JSONSchemaInput.js | 7 ++-- 2 files changed, 19 insertions(+), 28 deletions(-) diff --git a/src/electron/frontend/core/components/DateTimeSelector.js b/src/electron/frontend/core/components/DateTimeSelector.js index 3a536111a3..075e031ac2 100644 --- a/src/electron/frontend/core/components/DateTimeSelector.js +++ b/src/electron/frontend/core/components/DateTimeSelector.js @@ -1,17 +1,19 @@ import { LitElement, css } from "lit"; -export function extractISOString(date, { - // timezone = false, - offset = false, -} = {}) { - +export function extractISOString( + date, + { + // timezone = false, + offset = false, + } = {} +) { // Function to format the GMT offset function formatOffset(date) { let offset = -date.getTimezoneOffset(); // getTimezoneOffset returns the difference in minutes from UTC const sign = offset >= 0 ? "+" : "-"; offset = Math.abs(offset); - const hours = String(Math.floor(offset / 60)).padStart(2, '0'); - const minutes = String(offset % 60).padStart(2, '0'); + const hours = String(Math.floor(offset / 60)).padStart(2, "0"); + const minutes = String(offset % 60).padStart(2, "0"); return `${sign}${hours}:${minutes}`; } @@ -20,11 +22,11 @@ export function extractISOString(date, { // Format the date back to the original format with GMT offset const year = date.getFullYear(); - const month = String(date.getMonth() + 1).padStart(2, '0'); // Months are zero-indexed - const day = String(date.getDate()).padStart(2, '0'); - const hours = String(date.getHours()).padStart(2, '0'); - const minutes = String(date.getMinutes()).padStart(2, '0'); - const seconds = String(date.getSeconds()).padStart(2, '0'); + const month = String(date.getMonth() + 1).padStart(2, "0"); // Months are zero-indexed + const day = String(date.getDate()).padStart(2, "0"); + const hours = String(date.getHours()).padStart(2, "0"); + const minutes = String(date.getMinutes()).padStart(2, "0"); + const seconds = String(date.getSeconds()).padStart(2, "0"); // Recreate the ISO string with the GMT offset let formattedDate = `${year}-${month}-${day}T${hours}:${minutes}:${seconds}`; @@ -33,7 +35,6 @@ export function extractISOString(date, { return formattedDate; } - export class DateTimeSelector extends LitElement { static get styles() { return css` @@ -44,19 +45,15 @@ export class DateTimeSelector extends LitElement { `; } - // Manually handle value property get value() { const date = new Date(this.input.value); return extractISOString(date, { offset: true }); } - // Render the date without timezone offset set value(newValue) { - if (newValue) this.input.value = extractISOString(new Date(newValue)); - else { const d = new Date(); d.setHours(0, 0, 0, 0); @@ -72,18 +69,14 @@ export class DateTimeSelector extends LitElement { }; } - constructor({ - value, - min, - max, - } = {}) { + constructor({ value, min, max } = {}) { super(); this.input = document.createElement("input"); this.input.type = "datetime-local"; this.min = min; this.max = max; - + this.addEventListener("click", () => { this.input.focus(); this.input.showPicker(); @@ -101,7 +94,6 @@ export class DateTimeSelector extends LitElement { } render() { - this.input.min = min; this.input.max = max; diff --git a/src/electron/frontend/core/components/JSONSchemaInput.js b/src/electron/frontend/core/components/JSONSchemaInput.js index 325afa48a0..12114bdc0a 100644 --- a/src/electron/frontend/core/components/JSONSchemaInput.js +++ b/src/electron/frontend/core/components/JSONSchemaInput.js @@ -21,7 +21,7 @@ import { extractISOString } from "./DateTimeSelector"; const isDevelopment = !!import.meta.env; function resolveDateTime(value) { - if (typeof value === "string") return extractISOString(new Date(value), { offset: true }) + if (typeof value === "string") return extractISOString(new Date(value), { offset: true }); return value; } @@ -1241,7 +1241,6 @@ export class JSONSchemaInput extends LitElement { .min="${min}" .max="${max}" @input=${(ev) => { - let value = ev.target.value; let newValue = value; @@ -1249,7 +1248,7 @@ export class JSONSchemaInput extends LitElement { if (isInteger) value = newValue = parseInt(value); else if (isNumber) value = newValue = parseFloat(value); - else if (isDateTime) value = newValue = resolveDateTime(value) + else if (isDateTime) value = newValue = resolveDateTime(value); if (isNumber) { if ("min" in schema && newValue < schema.min) newValue = schema.min; @@ -1275,7 +1274,7 @@ export class JSONSchemaInput extends LitElement { const nanHandler = ev.target.parentNode.querySelector(".nan-handler"); if (!(newValue && Number.isNaN(newValue))) nanHandler.checked = false; } - + this.#updateData(fullPath, value); }} @change=${(ev) => validateOnChange && this.#triggerValidation(name, path)} From 31b5fece38676eb369458cfa73ee360b8bd0635b Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Mon, 3 Jun 2024 14:02:26 -0500 Subject: [PATCH 06/48] More uniform time handling --- .../core/components/DateTimeSelector.js | 42 +++++++++++++------ .../core/components/JSONSchemaInput.js | 12 +----- src/schemas/timezone.schema.ts | 3 +- 3 files changed, 33 insertions(+), 24 deletions(-) diff --git a/src/electron/frontend/core/components/DateTimeSelector.js b/src/electron/frontend/core/components/DateTimeSelector.js index 075e031ac2..3d622ef091 100644 --- a/src/electron/frontend/core/components/DateTimeSelector.js +++ b/src/electron/frontend/core/components/DateTimeSelector.js @@ -35,6 +35,18 @@ export function extractISOString( return formattedDate; } +export const renderDateTime = (value) => { + if (typeof value === "string") return extractISOString(new Date(value)); + return value; +} + +export const resolveDateTime = renderDateTime +// const resolveDateTime = (value) => { +// if (typeof value === "string") return extractISOString(new Date(value), { offset: true }); +// return value; +// } + + export class DateTimeSelector extends LitElement { static get styles() { return css` @@ -48,27 +60,36 @@ export class DateTimeSelector extends LitElement { // Manually handle value property get value() { const date = new Date(this.input.value); - return extractISOString(date, { offset: true }); + return resolveDateTime(date); } // Render the date without timezone offset set value(newValue) { - if (newValue) this.input.value = extractISOString(new Date(newValue)); + if (newValue) this.input.value = renderDateTime(new Date(newValue)); else { const d = new Date(); d.setHours(0, 0, 0, 0); - this.input.value = extractISOString(d); + this.input.value = renderDateTime(d); } } - static get properties() { - return { - min: { type: String, reflect: true }, - max: { type: String, reflect: true }, - timezone: { type: String, reflect: true }, - }; + get min() { + return this.input.min + } + + set min(value) { + this.input.min = value } + get max() { + return this.input.max + } + + set max(value) { + this.input.max = value + } + + constructor({ value, min, max } = {}) { super(); this.input = document.createElement("input"); @@ -94,9 +115,6 @@ export class DateTimeSelector extends LitElement { } render() { - this.input.min = min; - this.input.max = max; - return this.input; } } diff --git a/src/electron/frontend/core/components/JSONSchemaInput.js b/src/electron/frontend/core/components/JSONSchemaInput.js index 12114bdc0a..9e186fc381 100644 --- a/src/electron/frontend/core/components/JSONSchemaInput.js +++ b/src/electron/frontend/core/components/JSONSchemaInput.js @@ -16,20 +16,10 @@ import tippy from "tippy.js"; import { merge } from "./pages/utils"; import { OptionalSection } from "./OptionalSection"; import { InspectorListItem } from "./preview/inspector/InspectorList"; -import { extractISOString } from "./DateTimeSelector"; +import { renderDateTime, resolveDateTime } from "./DateTimeSelector"; const isDevelopment = !!import.meta.env; -function resolveDateTime(value) { - if (typeof value === "string") return extractISOString(new Date(value), { offset: true }); - return value; -} - -function renderDateTime(value) { - if (typeof value === "string") return extractISOString(new Date(value)); - return value; -} - export function createTable(fullPath, { onUpdate, onThrow, overrides = {} }) { const name = fullPath.slice(-1)[0]; const path = fullPath.slice(0, -1); diff --git a/src/schemas/timezone.schema.ts b/src/schemas/timezone.schema.ts index 2528281f66..bcb089ce2f 100644 --- a/src/schemas/timezone.schema.ts +++ b/src/schemas/timezone.schema.ts @@ -8,5 +8,6 @@ export default { description: "Provide a base timezone for all date and time operations in the GUIDE.", default: localTimeZone, enum: timezones, - strict: true + strict: true, + search: true } From 977950fc555c4f8a6342be3909b5ab6bb01b5f95 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 3 Jun 2024 19:04:37 +0000 Subject: [PATCH 07/48] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../frontend/core/components/DateTimeSelector.js | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/electron/frontend/core/components/DateTimeSelector.js b/src/electron/frontend/core/components/DateTimeSelector.js index 3d622ef091..8ee2c62611 100644 --- a/src/electron/frontend/core/components/DateTimeSelector.js +++ b/src/electron/frontend/core/components/DateTimeSelector.js @@ -38,14 +38,13 @@ export function extractISOString( export const renderDateTime = (value) => { if (typeof value === "string") return extractISOString(new Date(value)); return value; -} +}; -export const resolveDateTime = renderDateTime +export const resolveDateTime = renderDateTime; // const resolveDateTime = (value) => { // if (typeof value === "string") return extractISOString(new Date(value), { offset: true }); // return value; -// } - +// } export class DateTimeSelector extends LitElement { static get styles() { @@ -74,22 +73,21 @@ export class DateTimeSelector extends LitElement { } get min() { - return this.input.min + return this.input.min; } set min(value) { - this.input.min = value + this.input.min = value; } get max() { - return this.input.max + return this.input.max; } set max(value) { - this.input.max = value + this.input.max = value; } - constructor({ value, min, max } = {}) { super(); this.input = document.createElement("input"); From f521e572e986bd97452307a3759c4446f2767635 Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Mon, 3 Jun 2024 14:48:11 -0500 Subject: [PATCH 08/48] Extract timezone offset using timezone awareness. Remove timezone awareness from components --- .../core/components/DateTimeSelector.js | 71 ++++++++++--------- src/schemas/base-metadata.schema.ts | 17 +---- src/schemas/timezone.schema.ts | 34 ++++++++- 3 files changed, 70 insertions(+), 52 deletions(-) diff --git a/src/electron/frontend/core/components/DateTimeSelector.js b/src/electron/frontend/core/components/DateTimeSelector.js index 3d622ef091..fac1c8f7d6 100644 --- a/src/electron/frontend/core/components/DateTimeSelector.js +++ b/src/electron/frontend/core/components/DateTimeSelector.js @@ -1,4 +1,16 @@ import { LitElement, css } from "lit"; +import { getTimezoneOffset, formatTimezoneOffset } from "../../../../schemas/timezone.schema"; + + +// Function to format the GMT offset +function formatOffset(date) { + let offset = -date.getTimezoneOffset(); // getTimezoneOffset returns the difference in minutes from UTC + const sign = offset >= 0 ? "+" : "-"; + offset = Math.abs(offset); + const hours = String(Math.floor(offset / 60)).padStart(2, "0"); + const minutes = String(offset % 60).padStart(2, "0"); + return `${sign}${hours}:${minutes}`; +} export function extractISOString( date, @@ -7,18 +19,10 @@ export function extractISOString( offset = false, } = {} ) { - // Function to format the GMT offset - function formatOffset(date) { - let offset = -date.getTimezoneOffset(); // getTimezoneOffset returns the difference in minutes from UTC - const sign = offset >= 0 ? "+" : "-"; - offset = Math.abs(offset); - const hours = String(Math.floor(offset / 60)).padStart(2, "0"); - const minutes = String(offset % 60).padStart(2, "0"); - return `${sign}${hours}:${minutes}`; - } // Extract the GMT offset - const gmtOffset = formatOffset(date); + const offsetMs = getTimezoneOffset(date) + const gmtOffset = formatTimezoneOffset(offsetMs) // Format the date back to the original format with GMT offset const year = date.getFullYear(); @@ -29,14 +33,13 @@ export function extractISOString( const seconds = String(date.getSeconds()).padStart(2, "0"); // Recreate the ISO string with the GMT offset - let formattedDate = `${year}-${month}-${day}T${hours}:${minutes}:${seconds}`; - if (offset) formattedDate += gmtOffset; - - return formattedDate; + const formattedDate = `${year}-${month}-${day}T${hours}:${minutes}:${seconds}`; + return offset ? formattedDate + gmtOffset : formattedDate; } export const renderDateTime = (value) => { if (typeof value === "string") return extractISOString(new Date(value)); + else if (value instanceof Date) return extractISOString(value) return value; } @@ -57,53 +60,51 @@ export class DateTimeSelector extends LitElement { `; } - // Manually handle value property get value() { const date = new Date(this.input.value); - return resolveDateTime(date); + const resolved = resolveDateTime(date); + + console.log(this.input.value, resolved) + // return this.input.value; + return resolved } + - // Render the date without timezone offset set value(newValue) { - if (newValue) this.input.value = renderDateTime(new Date(newValue)); - else { - const d = new Date(); - d.setHours(0, 0, 0, 0); - this.input.value = renderDateTime(d); - } - } + const date = newValue ? new Date(newValue) : new Date() + if (!newValue) date.setHours(0, 0, 0, 0); + this.input.value = resolveDateTime(date); + } get min() { - return this.input.min + return this.input.min; } - set min(value) { - this.input.min = value + set min(newValue) { + this.input.min = newValue; } get max() { - return this.input.max + return this.input.max; } - set max(value) { - this.input.max = value + set max(newValue) { + this.input.max = newValue; } - constructor({ value, min, max } = {}) { super(); this.input = document.createElement("input"); this.input.type = "datetime-local"; - - this.min = min; - this.max = max; + this.input.min = min; + this.input.max = max; this.addEventListener("click", () => { this.input.focus(); this.input.showPicker(); }); - this.value = value; + this.value = value ? convertToDateTimeLocalString(value) : value; } focus() { diff --git a/src/schemas/base-metadata.schema.ts b/src/schemas/base-metadata.schema.ts index 83d5ac830b..dc17ce9820 100644 --- a/src/schemas/base-metadata.schema.ts +++ b/src/schemas/base-metadata.schema.ts @@ -6,7 +6,8 @@ import baseMetadataSchema from './json/base_metadata_schema.json' assert { type: import { merge } from '../electron/frontend/core/components/pages/utils' import { drillSchemaProperties } from '../electron/frontend/core/components/pages/guided-mode/data/utils' -import { localTimeZone } from './timezone.schema' +import { getISODateInTimezone } from './timezone.schema' + const UV_MATH_FORMAT = `µV`; //`µV` const UV_PROPERTIES = ["gain_to_uV", "offset_to_uV"] @@ -42,20 +43,6 @@ function getSpeciesInfo(species: any[][] = []) { } - -function getISODateInTimezone( - timezone = localTimeZone -) { - const date = new Date(); - const utcDate = new Date(date.toLocaleString('en-US', { timeZone: 'UTC' })); - const tzDate = new Date(date.toLocaleString('en-US', { timeZone: timezone })); - const offset = utcDate.getTime() - tzDate.getTime(); - - const adjustedDate = new Date(date.getTime() - offset); - return adjustedDate.toISOString(); -} - - function updateEcephysTable(propName, schema, schemaToMerge) { const ecephys = schema.properties.Ecephys diff --git a/src/schemas/timezone.schema.ts b/src/schemas/timezone.schema.ts index bcb089ce2f..e638758361 100644 --- a/src/schemas/timezone.schema.ts +++ b/src/schemas/timezone.schema.ts @@ -1,8 +1,38 @@ - const timezones = Intl.supportedValuesOf('timeZone'); export const localTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; +export const getTimezoneOffset = ( + date = new Date(), + timezone = localTimeZone +) => { + const utcDate = new Date(date.toLocaleString('en-US', { timeZone: 'UTC' })); + const tzDate = new Date(date.toLocaleString('en-US', { timeZone: timezone })); + return utcDate.getTime() - tzDate.getTime(); +} + +export const formatTimezoneOffset = ( + milliseconds: number +) => { + let offsetInMinutes = -((milliseconds / 1000) / 60); // getTimezoneOffset returns the difference in minutes from UTC + const sign = offsetInMinutes >= 0 ? "+" : "-"; + offsetInMinutes = Math.abs(offsetInMinutes); + const hours = String(Math.floor(offsetInMinutes / 60)).padStart(2, "0"); + const minutes = String(offsetInMinutes % 60).padStart(2, "0"); + return `${sign}${hours}:${minutes}`; +} + +export function getISODateInTimezone( + date = new Date(), + timezone = localTimeZone +) { + + const offset = getTimezoneOffset(date, timezone) + const adjustedDate = new Date(date.getTime() - offset); + return adjustedDate.toISOString(); +} + + export default { type: "string", description: "Provide a base timezone for all date and time operations in the GUIDE.", @@ -10,4 +40,4 @@ export default { enum: timezones, strict: true, search: true -} +} \ No newline at end of file From 4c69fab66f9230df44133acd215c4084747f0461 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 3 Jun 2024 19:49:56 +0000 Subject: [PATCH 09/48] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../frontend/core/components/DateTimeSelector.js | 16 ++++++---------- src/schemas/timezone.schema.ts | 2 +- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/electron/frontend/core/components/DateTimeSelector.js b/src/electron/frontend/core/components/DateTimeSelector.js index 58c1ec5235..00cde98a8a 100644 --- a/src/electron/frontend/core/components/DateTimeSelector.js +++ b/src/electron/frontend/core/components/DateTimeSelector.js @@ -1,7 +1,6 @@ import { LitElement, css } from "lit"; import { getTimezoneOffset, formatTimezoneOffset } from "../../../../schemas/timezone.schema"; - // Function to format the GMT offset function formatOffset(date) { let offset = -date.getTimezoneOffset(); // getTimezoneOffset returns the difference in minutes from UTC @@ -19,10 +18,9 @@ export function extractISOString( offset = false, } = {} ) { - // Extract the GMT offset - const offsetMs = getTimezoneOffset(date) - const gmtOffset = formatTimezoneOffset(offsetMs) + const offsetMs = getTimezoneOffset(date); + const gmtOffset = formatTimezoneOffset(offsetMs); // Format the date back to the original format with GMT offset const year = date.getFullYear(); @@ -39,7 +37,7 @@ export function extractISOString( export const renderDateTime = (value) => { if (typeof value === "string") return extractISOString(new Date(value)); - else if (value instanceof Date) return extractISOString(value) + else if (value instanceof Date) return extractISOString(value); return value; }; @@ -63,17 +61,15 @@ export class DateTimeSelector extends LitElement { const date = new Date(this.input.value); const resolved = resolveDateTime(date); - console.log(this.input.value, resolved) + console.log(this.input.value, resolved); // return this.input.value; - return resolved + return resolved; } - set value(newValue) { - const date = newValue ? new Date(newValue) : new Date() + const date = newValue ? new Date(newValue) : new Date(); if (!newValue) date.setHours(0, 0, 0, 0); this.input.value = resolveDateTime(date); - } get min() { return this.input.min; diff --git a/src/schemas/timezone.schema.ts b/src/schemas/timezone.schema.ts index e638758361..a668c5e6e7 100644 --- a/src/schemas/timezone.schema.ts +++ b/src/schemas/timezone.schema.ts @@ -40,4 +40,4 @@ export default { enum: timezones, strict: true, search: true -} \ No newline at end of file +} From a195173acf99d31e141f7d1293bee6b309f408e3 Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Mon, 3 Jun 2024 15:37:28 -0500 Subject: [PATCH 10/48] Set timezone when converting data --- .../core/components/DateTimeSelector.js | 18 +-- .../frontend/core/components/pages/Page.js | 142 ++++++++++-------- src/schemas/timezone.schema.ts | 13 ++ 3 files changed, 102 insertions(+), 71 deletions(-) diff --git a/src/electron/frontend/core/components/DateTimeSelector.js b/src/electron/frontend/core/components/DateTimeSelector.js index 00cde98a8a..11c302e49b 100644 --- a/src/electron/frontend/core/components/DateTimeSelector.js +++ b/src/electron/frontend/core/components/DateTimeSelector.js @@ -2,24 +2,18 @@ import { LitElement, css } from "lit"; import { getTimezoneOffset, formatTimezoneOffset } from "../../../../schemas/timezone.schema"; // Function to format the GMT offset -function formatOffset(date) { - let offset = -date.getTimezoneOffset(); // getTimezoneOffset returns the difference in minutes from UTC - const sign = offset >= 0 ? "+" : "-"; - offset = Math.abs(offset); - const hours = String(Math.floor(offset / 60)).padStart(2, "0"); - const minutes = String(offset % 60).padStart(2, "0"); - return `${sign}${hours}:${minutes}`; -} - export function extractISOString( - date, + date = new Date(), { - // timezone = false, offset = false, + timezone = undefined } = {} ) { + + if (typeof date === 'string') date = new Date() + // Extract the GMT offset - const offsetMs = getTimezoneOffset(date); + const offsetMs = getTimezoneOffset(date, timezone); const gmtOffset = formatTimezoneOffset(offsetMs); // Format the date back to the original format with GMT offset diff --git a/src/electron/frontend/core/components/pages/Page.js b/src/electron/frontend/core/components/pages/Page.js index e883814ef7..d939502466 100644 --- a/src/electron/frontend/core/components/pages/Page.js +++ b/src/electron/frontend/core/components/pages/Page.js @@ -10,6 +10,8 @@ import { randomizeElements, mapSessions, merge } from "./utils"; import { resolveMetadata } from "./guided-mode/data/utils.js"; import Swal from "sweetalert2"; import { createProgressPopup } from "../utils/progress.js"; +import { timezoneProperties } from "../../../../../schemas/timezone.schema"; +import { extractISOString } from "../DateTimeSelector.js"; export class Page extends LitElement { // static get styles() { @@ -159,71 +161,93 @@ export class Page extends LitElement { const { close: closeProgressPopup } = swalOpts; const fileConfiguration = []; - for (let info of toRun) { - const { subject, session, globalState = this.info.globalState } = info; - const file = `sub-${subject}/sub-${subject}_ses-${session}.nwb`; - - const { conversion_output_folder, name, SourceData, alignment } = globalState.project; - - const sessionResults = globalState.results[subject][session]; - - const sourceDataCopy = structuredClone(sessionResults.source_data); - - // Resolve the correct session info from all of the metadata for this conversion - const sessionInfo = { - ...sessionResults, - metadata: resolveMetadata(subject, session, globalState), - source_data: merge(SourceData, sourceDataCopy), - }; - - const payload = { - output_folder: conversionOptions.stub_test ? undefined : conversion_output_folder, - project_name: name, - nwbfile_path: file, - overwrite: true, // We assume override is true because the native NWB file dialog will not allow the user to select an existing file (unless they approve the overwrite) - ...sessionInfo, // source_data and metadata are passed in here - ...conversionOptions, // Any additional conversion options override the defaults - - interfaces: globalState.interfaces, - }; + try { + + for (let info of toRun) { + const { subject, session, globalState = this.info.globalState } = info; + const file = `sub-${subject}/sub-${subject}_ses-${session}.nwb`; + + const { conversion_output_folder, name, SourceData, alignment } = globalState.project; + + const sessionResults = globalState.results[subject][session]; + + const sourceDataCopy = structuredClone(sessionResults.source_data); + + // Resolve the correct session info from all of the metadata for this conversion + const metadata = resolveMetadata(subject, session, globalState) + + // // Add timezone information to relevant metadata + // timezoneProperties.forEach(path => { + // const name = path.slice(-1)[0] + // const pathTo = path.slice(0, -1) + // const parent = pathTo.reduce((acc, key) => acc[key], metadata) + // parent[name] = extractISOString( + // parent[name], + // { + // offset: true, + // timezone: this.workflow.timezone + // } + // ) + // }) + + const sessionInfo = { + ...sessionResults, + metadata, + source_data: merge(SourceData, sourceDataCopy), + }; + + const payload = { + output_folder: conversionOptions.stub_test ? undefined : conversion_output_folder, + project_name: name, + nwbfile_path: file, + overwrite: true, // We assume override is true because the native NWB file dialog will not allow the user to select an existing file (unless they approve the overwrite) + ...sessionInfo, // source_data and metadata are passed in here + ...conversionOptions, // Any additional conversion options override the defaults + + interfaces: globalState.interfaces, + alignment + }; + + fileConfiguration.push(payload); + } - fileConfiguration.push(payload); - } + const conversionResults = await run( + `neuroconv/convert`, + { + files: fileConfiguration, + max_workers: 2, // TODO: Make this configurable and confirm default value + request_id: swalOpts.id, + }, + { + title: "Running the conversion", + onError: () => "Conversion failed with current metadata. Please try again.", + ...swalOpts, + } + ).catch(async (error) => { + let message = error.message; - const conversionResults = await run( - `neuroconv/convert`, - { - files: fileConfiguration, - max_workers: 2, // TODO: Make this configurable and confirm default value - request_id: swalOpts.id, - }, - { - title: "Running the conversion", - onError: () => "Conversion failed with current metadata. Please try again.", - ...swalOpts, - } - ).catch(async (error) => { - let message = error.message; + if (message.includes("The user aborted a request.")) { + this.notify("Conversion was cancelled.", "warning"); + throw error; + } - if (message.includes("The user aborted a request.")) { - this.notify("Conversion was cancelled.", "warning"); + this.notify(message, "error"); throw error; - } + }); + + conversionResults.forEach((info) => { + const { file } = info; + const fileName = file.split("/").pop(); + const [subject, session] = fileName.match(/sub-(.+)_ses-(.+)\.nwb/).slice(1); + const subRef = results[subject] ?? (results[subject] = {}); + subRef[session] = info; + }); - this.notify(message, "error"); + } + + finally { await closeProgressPopup(); - throw error; - }); - - conversionResults.forEach((info) => { - const { file } = info; - const fileName = file.split("/").pop(); - const [subject, session] = fileName.match(/sub-(.+)_ses-(.+)\.nwb/).slice(1); - const subRef = results[subject] ?? (results[subject] = {}); - subRef[session] = info; - }); - - await closeProgressPopup(); + } return results; } diff --git a/src/schemas/timezone.schema.ts b/src/schemas/timezone.schema.ts index a668c5e6e7..4cb4861339 100644 --- a/src/schemas/timezone.schema.ts +++ b/src/schemas/timezone.schema.ts @@ -1,11 +1,22 @@ const timezones = Intl.supportedValuesOf('timeZone'); + +// NOTE: Used before validation and conversion to add timezone information to the data +export const timezoneProperties = [ + [ "NWBFile", "session_start_time" ], + [ "Subject", "date_of_birth" ] +] + + export const localTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; export const getTimezoneOffset = ( date = new Date(), timezone = localTimeZone ) => { + + if (typeof date === 'string') date = new Date(date) + const utcDate = new Date(date.toLocaleString('en-US', { timeZone: 'UTC' })); const tzDate = new Date(date.toLocaleString('en-US', { timeZone: timezone })); return utcDate.getTime() - tzDate.getTime(); @@ -27,6 +38,8 @@ export function getISODateInTimezone( timezone = localTimeZone ) { + if (typeof date === 'string') date = new Date(date) + const offset = getTimezoneOffset(date, timezone) const adjustedDate = new Date(date.getTime() - offset); return adjustedDate.toISOString(); From 7af02228e693a2264bd5040e69095f1ef605df10 Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Mon, 3 Jun 2024 16:04:35 -0500 Subject: [PATCH 11/48] Add timestamp awareness to forms and conversion process --- .../core/components/JSONSchemaForm.js | 38 ++++++++++++++++--- src/electron/frontend/core/components/Main.js | 12 +++--- .../frontend/core/components/pages/Page.js | 26 ++++++------- .../pages/guided-mode/data/GuidedMetadata.js | 3 ++ src/electron/frontend/core/progress/update.js | 2 - 5 files changed, 54 insertions(+), 27 deletions(-) diff --git a/src/electron/frontend/core/components/JSONSchemaForm.js b/src/electron/frontend/core/components/JSONSchemaForm.js index 2acc454116..8feef732b2 100644 --- a/src/electron/frontend/core/components/JSONSchemaForm.js +++ b/src/electron/frontend/core/components/JSONSchemaForm.js @@ -12,6 +12,14 @@ import { resolveProperties } from "./pages/guided-mode/data/utils"; import { JSONSchemaInput, getEditableItems } from "./JSONSchemaInput"; import { InspectorListItem } from "./preview/inspector/InspectorList"; +import { Validator } from "jsonschema"; +import { successHue, warningHue, errorHue } from "./globals"; +import { Button } from "./Button"; +import { extractISOString } from "./DateTimeSelector"; +import { timezoneProperties } from "../../../../schemas/timezone.schema"; +const timezonePropertyPaths = timezoneProperties.map(arr => arr.join('.')) + + const encode = (str) => { try { document.querySelector(`#${str}`); @@ -65,10 +73,6 @@ const additionalPropPattern = "additional"; const templateNaNMessage = `
Type NaN to represent an unknown value.`; -import { Validator } from "jsonschema"; -import { successHue, warningHue, errorHue } from "./globals"; -import { Button } from "./Button"; - var validator = new Validator(); const isObject = (item) => { @@ -248,6 +252,7 @@ export class JSONSchemaForm extends LitElement { dialogOptions: { type: Object, reflect: false }, globals: { type: Object, reflect: false }, validateEmptyValues: { type: Boolean, reflect: true }, + timezone: { type: String, reflect: true} }; } @@ -280,6 +285,8 @@ export class JSONSchemaForm extends LitElement { this.results = (props.base ? structuredClone(props.results) : props.results) ?? {}; // Deep clone results in nested forms this.globals = props.globals ?? {}; + this.timezone = props.timezone ?? undefined + this.ignore = props.ignore ?? {}; this.required = props.required ?? {}; this.dialogOptions = props.dialogOptions; @@ -698,7 +705,7 @@ export class JSONSchemaForm extends LitElement { pattern: propertyType === "pattern" ? name : propertyType ?? undefined, renderTable: this.renderTable, renderCustomHTML: this.renderCustomHTML, - showLabel: !("title" in info && !info.title), + showLabel: !("title" in info && !info.title) }); this.inputs[localPath.join("-")] = interactiveInput; @@ -902,7 +909,22 @@ export class JSONSchemaForm extends LitElement { if (!parent) parent = this.#get(path, this.resolved); if (!schema) schema = this.getSchema(localPath); - const value = parent[name]; + let value = parent[name]; + + const { timezone } = this + + // Validate with timezone awareness + const isTimezoneProperty = timezonePropertyPaths.includes(externalPath.join('.')) + if (timezone && isTimezoneProperty) { + value = extractISOString( + value, + { + offset: true, + timezone + } + ) + } + const skipValidation = this.validateEmptyValues === null && value === undefined; @@ -910,6 +932,8 @@ export class JSONSchemaForm extends LitElement { // Run validation functions const jsonSchemaErrors = validateArgs.length === 2 ? this.validateSchema(...validateArgs, name) : []; + + const valid = skipValidation ? true : await this.validateOnChange(name, parent, pathToValidate, value); if (valid === null) return null; // Skip validation / data change if the value is null @@ -1205,6 +1229,8 @@ export class JSONSchemaForm extends LitElement { results: { ...nestedResults }, globals: this.globals?.[name], + timezone: this.timezone, + controls: this.controls[name], onUpdate: (internalPath, value, forceUpdate) => { diff --git a/src/electron/frontend/core/components/Main.js b/src/electron/frontend/core/components/Main.js index 14e6b61e09..74465691a5 100644 --- a/src/electron/frontend/core/components/Main.js +++ b/src/electron/frontend/core/components/Main.js @@ -77,13 +77,13 @@ export class Main extends LitElement { const workflowConfig = page.workflow ?? (page.workflow = {}); const workflowValues = page.info.globalState?.project?.workflow ?? {}; - Object.entries(workflowConfig).forEach(([key, state]) => { - workflowConfig[key].value = workflowValues[key]; + Object.entries(workflowValues).forEach(([ key, value ]) => { + + const config = workflowConfig[key] ?? (workflowConfig[key] = {}) + config.value = value; - const value = workflowValues[key]; - - if (state.elements) { - const elements = state.elements; + const { elements } = config + if (elements) { if (value) elements.forEach((el) => el.removeAttribute("hidden")); else elements.forEach((el) => el.setAttribute("hidden", true)); } diff --git a/src/electron/frontend/core/components/pages/Page.js b/src/electron/frontend/core/components/pages/Page.js index d939502466..c488a7e272 100644 --- a/src/electron/frontend/core/components/pages/Page.js +++ b/src/electron/frontend/core/components/pages/Page.js @@ -176,19 +176,19 @@ export class Page extends LitElement { // Resolve the correct session info from all of the metadata for this conversion const metadata = resolveMetadata(subject, session, globalState) - // // Add timezone information to relevant metadata - // timezoneProperties.forEach(path => { - // const name = path.slice(-1)[0] - // const pathTo = path.slice(0, -1) - // const parent = pathTo.reduce((acc, key) => acc[key], metadata) - // parent[name] = extractISOString( - // parent[name], - // { - // offset: true, - // timezone: this.workflow.timezone - // } - // ) - // }) + // Add timezone information to relevant metadata + timezoneProperties.forEach(path => { + const name = path.slice(-1)[0] + const pathTo = path.slice(0, -1) + const parent = pathTo.reduce((acc, key) => acc[key], metadata) + parent[name] = extractISOString( + parent[name], + { + offset: true, + timezone: this.workflow.timezone + } + ) + }) const sessionInfo = { ...sessionResults, 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..b1cdd48ba1 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 @@ -279,6 +279,7 @@ export class GuidedMetadataPage extends ManagedPage { delete results.__generated; // Ignore generated results. NOTE: See if this fails + // Create the form const form = new JSONSchemaForm({ identifier: instanceId, @@ -286,6 +287,8 @@ export class GuidedMetadataPage extends ManagedPage { results, globals: aggregateGlobalMetadata, + timezone: this.workflow.timezone.value, + ignore: propsToIgnore, onOverride: (name) => { this.notify(`${header(name)} has been overridden with a global value.`, "warning", 3000); diff --git a/src/electron/frontend/core/progress/update.js b/src/electron/frontend/core/progress/update.js index f07ea80b19..650d4999a6 100644 --- a/src/electron/frontend/core/progress/update.js +++ b/src/electron/frontend/core/progress/update.js @@ -52,8 +52,6 @@ export const updateFile = (projectName, callback) => { var guidedFilePath = joinPath(guidedProgressFilePath, projectName + ".json"); - console.log(guidedProgressFilePath); - // Save the file through the available mechanisms if (!fs.existsSync(guidedProgressFilePath)) fs.mkdirSync(guidedProgressFilePath, { recursive: true }); //create progress folder if one does not exist fs.writeFileSync(guidedFilePath, JSON.stringify(data, null, 2)); From 3e74935174a4ba3397863478667a94b152a98f06 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 3 Jun 2024 23:26:55 +0000 Subject: [PATCH 12/48] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../core/components/DateTimeSelector.js | 11 ++----- .../core/components/JSONSchemaForm.js | 26 ++++++---------- src/electron/frontend/core/components/Main.js | 7 ++--- .../frontend/core/components/pages/Page.js | 31 +++++++------------ .../pages/guided-mode/data/GuidedMetadata.js | 1 - 5 files changed, 27 insertions(+), 49 deletions(-) diff --git a/src/electron/frontend/core/components/DateTimeSelector.js b/src/electron/frontend/core/components/DateTimeSelector.js index 11c302e49b..c8f58d6d60 100644 --- a/src/electron/frontend/core/components/DateTimeSelector.js +++ b/src/electron/frontend/core/components/DateTimeSelector.js @@ -2,15 +2,8 @@ import { LitElement, css } from "lit"; import { getTimezoneOffset, formatTimezoneOffset } from "../../../../schemas/timezone.schema"; // Function to format the GMT offset -export function extractISOString( - date = new Date(), - { - offset = false, - timezone = undefined - } = {} -) { - - if (typeof date === 'string') date = new Date() +export function extractISOString(date = new Date(), { offset = false, timezone = undefined } = {}) { + if (typeof date === "string") date = new Date(); // Extract the GMT offset const offsetMs = getTimezoneOffset(date, timezone); diff --git a/src/electron/frontend/core/components/JSONSchemaForm.js b/src/electron/frontend/core/components/JSONSchemaForm.js index 8feef732b2..c50f91ab36 100644 --- a/src/electron/frontend/core/components/JSONSchemaForm.js +++ b/src/electron/frontend/core/components/JSONSchemaForm.js @@ -17,8 +17,7 @@ import { successHue, warningHue, errorHue } from "./globals"; import { Button } from "./Button"; import { extractISOString } from "./DateTimeSelector"; import { timezoneProperties } from "../../../../schemas/timezone.schema"; -const timezonePropertyPaths = timezoneProperties.map(arr => arr.join('.')) - +const timezonePropertyPaths = timezoneProperties.map((arr) => arr.join(".")); const encode = (str) => { try { @@ -252,7 +251,7 @@ export class JSONSchemaForm extends LitElement { dialogOptions: { type: Object, reflect: false }, globals: { type: Object, reflect: false }, validateEmptyValues: { type: Boolean, reflect: true }, - timezone: { type: String, reflect: true} + timezone: { type: String, reflect: true }, }; } @@ -285,7 +284,7 @@ export class JSONSchemaForm extends LitElement { this.results = (props.base ? structuredClone(props.results) : props.results) ?? {}; // Deep clone results in nested forms this.globals = props.globals ?? {}; - this.timezone = props.timezone ?? undefined + this.timezone = props.timezone ?? undefined; this.ignore = props.ignore ?? {}; this.required = props.required ?? {}; @@ -705,7 +704,7 @@ export class JSONSchemaForm extends LitElement { pattern: propertyType === "pattern" ? name : propertyType ?? undefined, renderTable: this.renderTable, renderCustomHTML: this.renderCustomHTML, - showLabel: !("title" in info && !info.title) + showLabel: !("title" in info && !info.title), }); this.inputs[localPath.join("-")] = interactiveInput; @@ -911,20 +910,16 @@ export class JSONSchemaForm extends LitElement { let value = parent[name]; - const { timezone } = this + const { timezone } = this; // Validate with timezone awareness - const isTimezoneProperty = timezonePropertyPaths.includes(externalPath.join('.')) + const isTimezoneProperty = timezonePropertyPaths.includes(externalPath.join(".")); if (timezone && isTimezoneProperty) { - value = extractISOString( - value, - { - offset: true, - timezone - } - ) + value = extractISOString(value, { + offset: true, + timezone, + }); } - const skipValidation = this.validateEmptyValues === null && value === undefined; @@ -933,7 +928,6 @@ export class JSONSchemaForm extends LitElement { // Run validation functions const jsonSchemaErrors = validateArgs.length === 2 ? this.validateSchema(...validateArgs, name) : []; - const valid = skipValidation ? true : await this.validateOnChange(name, parent, pathToValidate, value); if (valid === null) return null; // Skip validation / data change if the value is null diff --git a/src/electron/frontend/core/components/Main.js b/src/electron/frontend/core/components/Main.js index 74465691a5..8421ffebec 100644 --- a/src/electron/frontend/core/components/Main.js +++ b/src/electron/frontend/core/components/Main.js @@ -77,12 +77,11 @@ export class Main extends LitElement { const workflowConfig = page.workflow ?? (page.workflow = {}); const workflowValues = page.info.globalState?.project?.workflow ?? {}; - Object.entries(workflowValues).forEach(([ key, value ]) => { - - const config = workflowConfig[key] ?? (workflowConfig[key] = {}) + Object.entries(workflowValues).forEach(([key, value]) => { + const config = workflowConfig[key] ?? (workflowConfig[key] = {}); config.value = value; - const { elements } = config + const { elements } = config; if (elements) { if (value) elements.forEach((el) => el.removeAttribute("hidden")); else elements.forEach((el) => el.setAttribute("hidden", true)); diff --git a/src/electron/frontend/core/components/pages/Page.js b/src/electron/frontend/core/components/pages/Page.js index c488a7e272..cf87eb04f3 100644 --- a/src/electron/frontend/core/components/pages/Page.js +++ b/src/electron/frontend/core/components/pages/Page.js @@ -162,7 +162,6 @@ export class Page extends LitElement { const fileConfiguration = []; try { - for (let info of toRun) { const { subject, session, globalState = this.info.globalState } = info; const file = `sub-${subject}/sub-${subject}_ses-${session}.nwb`; @@ -174,21 +173,18 @@ export class Page extends LitElement { const sourceDataCopy = structuredClone(sessionResults.source_data); // Resolve the correct session info from all of the metadata for this conversion - const metadata = resolveMetadata(subject, session, globalState) + const metadata = resolveMetadata(subject, session, globalState); // Add timezone information to relevant metadata - timezoneProperties.forEach(path => { - const name = path.slice(-1)[0] - const pathTo = path.slice(0, -1) - const parent = pathTo.reduce((acc, key) => acc[key], metadata) - parent[name] = extractISOString( - parent[name], - { - offset: true, - timezone: this.workflow.timezone - } - ) - }) + timezoneProperties.forEach((path) => { + const name = path.slice(-1)[0]; + const pathTo = path.slice(0, -1); + const parent = pathTo.reduce((acc, key) => acc[key], metadata); + parent[name] = extractISOString(parent[name], { + offset: true, + timezone: this.workflow.timezone, + }); + }); const sessionInfo = { ...sessionResults, @@ -205,7 +201,7 @@ export class Page extends LitElement { ...conversionOptions, // Any additional conversion options override the defaults interfaces: globalState.interfaces, - alignment + alignment, }; fileConfiguration.push(payload); @@ -242,10 +238,7 @@ export class Page extends LitElement { const subRef = results[subject] ?? (results[subject] = {}); subRef[session] = info; }); - - } - - finally { + } finally { await closeProgressPopup(); } 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 b1cdd48ba1..eb99fa1b28 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 @@ -279,7 +279,6 @@ export class GuidedMetadataPage extends ManagedPage { delete results.__generated; // Ignore generated results. NOTE: See if this fails - // Create the form const form = new JSONSchemaForm({ identifier: instanceId, From fae3596cb60f8193a97292b3b04c41e46e055810 Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Mon, 3 Jun 2024 18:35:52 -0500 Subject: [PATCH 13/48] Ensure default values are carried in the preform --- .../components/pages/guided-mode/setup/Preform.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/electron/frontend/core/components/pages/guided-mode/setup/Preform.js b/src/electron/frontend/core/components/pages/guided-mode/setup/Preform.js index 4a5592684a..8c00ff1437 100644 --- a/src/electron/frontend/core/components/pages/guided-mode/setup/Preform.js +++ b/src/electron/frontend/core/components/pages/guided-mode/setup/Preform.js @@ -60,6 +60,7 @@ const questions = { timezone: { ...timezoneSchema, title: "What timezone is your data in?", + required: true, }, upload_to_dandi: { @@ -93,6 +94,11 @@ const dependents = Object.entries(questions).reduce((acc, [name, info]) => { return acc; }, {}); +const defaults = Object.entries(questions).reduce((acc, [name, info]) => { + acc[name] = info.default; + return acc; +}, {}); + const projectWorkflowSchema = { type: "object", properties: Object.entries(questions).reduce((acc, [name, info]) => { @@ -135,6 +141,12 @@ export class GuidedPreform extends Page { const schema = structuredClone(projectWorkflowSchema); const projectState = this.info.globalState.project ?? {}; if (!projectState.workflow) projectState.workflow = {}; + + // Set defaults for missing values + Object.entries(defaults).forEach(([key, value]) => { + if (!(key in projectState.workflow)) projectState.workflow[key] = value; + }); + this.state = structuredClone(projectState.workflow); this.form = new JSONSchemaForm({ From 389c1130cb68d0e91e7e273beb81333bdf8b7607 Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Tue, 4 Jun 2024 07:45:43 -0700 Subject: [PATCH 14/48] Use keywords, categories, and labels to improve the rendering of timezones --- src/schemas/timezone.schema.ts | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/schemas/timezone.schema.ts b/src/schemas/timezone.schema.ts index 4cb4861339..bbdc60aa62 100644 --- a/src/schemas/timezone.schema.ts +++ b/src/schemas/timezone.schema.ts @@ -1,5 +1,25 @@ +import { header } from "../electron/frontend/core/components/forms/utils"; + const timezones = Intl.supportedValuesOf('timeZone'); +const enumCategories = timezones.reduce((acc, timezone) => { + const category = timezone.split("/")[0]; + acc[timezone] = category; + return acc; +}, {}); + +const enumLabels = timezones.reduce((acc, timezone) => { + const parts = timezone.split("/"); + acc[timezone] = `${header(parts[parts.length - 1])}`; + return acc; +}, {}); + + +const enumKeywords = timezones.reduce((acc, timezone) => { + acc[timezone] = [ timezone ] + return acc; +}, {}); + // NOTE: Used before validation and conversion to add timezone information to the data export const timezoneProperties = [ @@ -51,6 +71,9 @@ export default { description: "Provide a base timezone for all date and time operations in the GUIDE.", default: localTimeZone, enum: timezones, + enumLabels, + enumKeywords, + enumCategories, strict: true, search: true } From 74856c997e83c10d0cd02cde406068bc608621c9 Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Tue, 4 Jun 2024 07:52:12 -0700 Subject: [PATCH 15/48] Require timezone and move workflow value resolution into the Dashboard (like backend configuration) --- .../frontend/core/components/Dashboard.js | 20 +++++++++++++++++++ src/electron/frontend/core/components/Main.js | 17 ---------------- .../frontend/core/components/pages/Page.js | 2 +- .../pages/guided-mode/setup/Preform.js | 6 ++++++ 4 files changed, 27 insertions(+), 18 deletions(-) diff --git a/src/electron/frontend/core/components/Dashboard.js b/src/electron/frontend/core/components/Dashboard.js index d2a72d0e83..a60a54dac3 100644 --- a/src/electron/frontend/core/components/Dashboard.js +++ b/src/electron/frontend/core/components/Dashboard.js @@ -245,6 +245,26 @@ export class Dashboard extends LitElement { this.page.set(toPass, false); + // Constrain based on workflow configuration + const workflowConfig = page.workflow ?? (page.workflow = {}); + const workflowValues = page.info.globalState?.project?.workflow ?? {}; + + Object.entries(workflowValues).forEach(([key, value]) => { + const config = workflowConfig[key] ?? (workflowConfig[key] = {}); + config.value = value; + + const { elements } = config; + if (elements) { + if (value) elements.forEach((el) => el.removeAttribute("hidden")); + else elements.forEach((el) => el.setAttribute("hidden", true)); + } + }); + + console.log("Workflow Config", workflowConfig) + + page.requestUpdate(); // Ensure the page is re-rendered with new workflow configurations + + this.page .checkSyncState() .then(async () => { diff --git a/src/electron/frontend/core/components/Main.js b/src/electron/frontend/core/components/Main.js index 8421ffebec..a718756107 100644 --- a/src/electron/frontend/core/components/Main.js +++ b/src/electron/frontend/core/components/Main.js @@ -73,23 +73,6 @@ export class Main extends LitElement { page.onTransition = this.onTransition; page.updatePages = this.updatePages; - // Constrain based on workflow configuration - const workflowConfig = page.workflow ?? (page.workflow = {}); - const workflowValues = page.info.globalState?.project?.workflow ?? {}; - - Object.entries(workflowValues).forEach(([key, value]) => { - const config = workflowConfig[key] ?? (workflowConfig[key] = {}); - config.value = value; - - const { elements } = config; - if (elements) { - if (value) elements.forEach((el) => el.removeAttribute("hidden")); - else elements.forEach((el) => el.setAttribute("hidden", true)); - } - }); - - page.requestUpdate(); // Ensure the page is re-rendered with new workflow configurations - if (this.content) this.toRender = toRender.page ? toRender : { page }; // Ensure re-render in either case else this.#queue.push(page); diff --git a/src/electron/frontend/core/components/pages/Page.js b/src/electron/frontend/core/components/pages/Page.js index cf87eb04f3..69a436f715 100644 --- a/src/electron/frontend/core/components/pages/Page.js +++ b/src/electron/frontend/core/components/pages/Page.js @@ -182,7 +182,7 @@ export class Page extends LitElement { const parent = pathTo.reduce((acc, key) => acc[key], metadata); parent[name] = extractISOString(parent[name], { offset: true, - timezone: this.workflow.timezone, + timezone: this.workflow.timezone.value, }); }); diff --git a/src/electron/frontend/core/components/pages/guided-mode/setup/Preform.js b/src/electron/frontend/core/components/pages/guided-mode/setup/Preform.js index 8c00ff1437..5f88af6bd9 100644 --- a/src/electron/frontend/core/components/pages/guided-mode/setup/Preform.js +++ b/src/electron/frontend/core/components/pages/guided-mode/setup/Preform.js @@ -99,6 +99,11 @@ const defaults = Object.entries(questions).reduce((acc, [name, info]) => { return acc; }, {}); +const required = Object.entries(questions).reduce((acc, [name, info]) => { + if (info.required) acc.push(name); + return acc; +}, []); + const projectWorkflowSchema = { type: "object", properties: Object.entries(questions).reduce((acc, [name, info]) => { @@ -106,6 +111,7 @@ const projectWorkflowSchema = { return acc; }, {}), order: Object.keys(questions), + required, additionalProperties: false, }; From f868b1e008dd8c40409a8e4c5499a47387e136c6 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 4 Jun 2024 14:52:47 +0000 Subject: [PATCH 16/48] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/electron/frontend/core/components/Dashboard.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/electron/frontend/core/components/Dashboard.js b/src/electron/frontend/core/components/Dashboard.js index a60a54dac3..f57cddee6e 100644 --- a/src/electron/frontend/core/components/Dashboard.js +++ b/src/electron/frontend/core/components/Dashboard.js @@ -260,10 +260,9 @@ export class Dashboard extends LitElement { } }); - console.log("Workflow Config", workflowConfig) + console.log("Workflow Config", workflowConfig); page.requestUpdate(); // Ensure the page is re-rendered with new workflow configurations - this.page .checkSyncState() From ca3ea2dd903a81dcdaff91c75f72d255318ac23f Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Tue, 4 Jun 2024 07:53:57 -0700 Subject: [PATCH 17/48] Update Dashboard.js --- src/electron/frontend/core/components/Dashboard.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/electron/frontend/core/components/Dashboard.js b/src/electron/frontend/core/components/Dashboard.js index a60a54dac3..a8bde36322 100644 --- a/src/electron/frontend/core/components/Dashboard.js +++ b/src/electron/frontend/core/components/Dashboard.js @@ -260,8 +260,6 @@ export class Dashboard extends LitElement { } }); - console.log("Workflow Config", workflowConfig) - page.requestUpdate(); // Ensure the page is re-rendered with new workflow configurations From d0d49c10c690cf3cc075730cbe231159e0f638f1 Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Tue, 4 Jun 2024 07:58:20 -0700 Subject: [PATCH 18/48] Add non-category timezone information to the end --- src/schemas/timezone.schema.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/schemas/timezone.schema.ts b/src/schemas/timezone.schema.ts index bbdc60aa62..a89de9e53c 100644 --- a/src/schemas/timezone.schema.ts +++ b/src/schemas/timezone.schema.ts @@ -10,7 +10,9 @@ const enumCategories = timezones.reduce((acc, timezone) => { const enumLabels = timezones.reduce((acc, timezone) => { const parts = timezone.split("/"); - acc[timezone] = `${header(parts[parts.length - 1])}`; + const nonCategoryParts = parts.slice(1, parts.length - 1); + acc[timezone] = header(parts[parts.length - 1]); + if (nonCategoryParts.length) acc[timezone] += ` — ${nonCategoryParts.map(str => header(str)).join(", ")}` return acc; }, {}); From ed416e181392579e0fdf02f687493c4966926d38 Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Tue, 4 Jun 2024 08:16:40 -0700 Subject: [PATCH 19/48] Show long and short name --- src/schemas/timezone.schema.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/schemas/timezone.schema.ts b/src/schemas/timezone.schema.ts index a89de9e53c..7408bd1391 100644 --- a/src/schemas/timezone.schema.ts +++ b/src/schemas/timezone.schema.ts @@ -1,9 +1,16 @@ import { header } from "../electron/frontend/core/components/forms/utils"; + +export const localTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; + +export const getTimeZoneName = (timezone, timeZoneName = 'long') => new Date().toLocaleDateString(undefined, {day:'2-digit', timeZone: timezone, timeZoneName }).substring(4) + const timezones = Intl.supportedValuesOf('timeZone'); +timezones.push("UTC"); const enumCategories = timezones.reduce((acc, timezone) => { - const category = timezone.split("/")[0]; + const parts = timezone.split("/"); + const category = parts.length === 1 ? "Other" : parts[0]; acc[timezone] = category; return acc; }, {}); @@ -18,7 +25,7 @@ const enumLabels = timezones.reduce((acc, timezone) => { const enumKeywords = timezones.reduce((acc, timezone) => { - acc[timezone] = [ timezone ] + acc[timezone] = [ getTimeZoneName(timezone, 'long'), getTimeZoneName(timezone, 'short') ] return acc; }, {}); @@ -29,9 +36,6 @@ export const timezoneProperties = [ [ "Subject", "date_of_birth" ] ] - -export const localTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; - export const getTimezoneOffset = ( date = new Date(), timezone = localTimeZone From 70420947d5add168311c13051e84c1da8de0b9c8 Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Tue, 4 Jun 2024 08:27:29 -0700 Subject: [PATCH 20/48] Instead of always including UTC, add it if the timezone detector has chosen it --- src/schemas/timezone.schema.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/schemas/timezone.schema.ts b/src/schemas/timezone.schema.ts index 7408bd1391..43728615c3 100644 --- a/src/schemas/timezone.schema.ts +++ b/src/schemas/timezone.schema.ts @@ -6,7 +6,7 @@ export const localTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; export const getTimeZoneName = (timezone, timeZoneName = 'long') => new Date().toLocaleDateString(undefined, {day:'2-digit', timeZone: timezone, timeZoneName }).substring(4) const timezones = Intl.supportedValuesOf('timeZone'); -timezones.push("UTC"); +if (!timezones.includes(localTimeZone)) timezones.push(localTimeZone); // Add the local timezone if it's not already in the list const enumCategories = timezones.reduce((acc, timezone) => { const parts = timezone.split("/"); From fa29aa417383b00f32010c8b1ad3b1781aeffa9e Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Tue, 4 Jun 2024 08:51:16 -0700 Subject: [PATCH 21/48] Properly resolve promises --- .../frontend/core/components/Dashboard.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/electron/frontend/core/components/Dashboard.js b/src/electron/frontend/core/components/Dashboard.js index d4d82af9cb..29fd4cab60 100644 --- a/src/electron/frontend/core/components/Dashboard.js +++ b/src/electron/frontend/core/components/Dashboard.js @@ -273,8 +273,6 @@ export class Dashboard extends LitElement { this.updateSections({ sidebar: false, main: true }); - if (this.#transitionPromise.value) this.#transitionPromise.trigger(page); // This ensures calls to page.to() can be properly awaited until the next page is ready - const { skipped } = this.subSidebar.sections[info.section]?.pages?.[info.id] ?? {}; if (skipped) { @@ -300,7 +298,10 @@ export class Dashboard extends LitElement { : `

Fallback to previous page after error occurred

${e}`, "error" ); - }); + }) + .finally(() => { + if (this.#transitionPromise.value) this.#transitionPromise.trigger(page); // This ensures calls to page.to() can be properly awaited until the next page is ready + }) } // Populate the sections tracked for this page by using the global state as a model @@ -359,8 +360,12 @@ export class Dashboard extends LitElement { if (!active) active = this.activePage; // default to active page this.main.onTransition = async (transition) => { - const promise = (this.#transitionPromise.value = new Promise( - (resolve) => (this.#transitionPromise.trigger = resolve) + const promise = this.#transitionPromise.value ?? (this.#transitionPromise.value = new Promise( + (resolve) => (this.#transitionPromise.trigger = (value) => { + delete this.#transitionPromise.value; + resolve(value); + + }) )); if (typeof transition === "number") { From b619ce3665216d5e8b938eb09be753ee8d56edaf Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 4 Jun 2024 15:54:11 +0000 Subject: [PATCH 22/48] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../frontend/core/components/Dashboard.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/electron/frontend/core/components/Dashboard.js b/src/electron/frontend/core/components/Dashboard.js index 29fd4cab60..f96e2153b7 100644 --- a/src/electron/frontend/core/components/Dashboard.js +++ b/src/electron/frontend/core/components/Dashboard.js @@ -301,7 +301,7 @@ export class Dashboard extends LitElement { }) .finally(() => { if (this.#transitionPromise.value) this.#transitionPromise.trigger(page); // This ensures calls to page.to() can be properly awaited until the next page is ready - }) + }); } // Populate the sections tracked for this page by using the global state as a model @@ -360,13 +360,15 @@ export class Dashboard extends LitElement { if (!active) active = this.activePage; // default to active page this.main.onTransition = async (transition) => { - const promise = this.#transitionPromise.value ?? (this.#transitionPromise.value = new Promise( - (resolve) => (this.#transitionPromise.trigger = (value) => { - delete this.#transitionPromise.value; - resolve(value); - - }) - )); + const promise = + this.#transitionPromise.value ?? + (this.#transitionPromise.value = new Promise( + (resolve) => + (this.#transitionPromise.trigger = (value) => { + delete this.#transitionPromise.value; + resolve(value); + }) + )); if (typeof transition === "number") { const info = this.page.info; From f02a80057bfb2715cfb53659700410847ee9bd99 Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Tue, 4 Jun 2024 09:54:31 -0700 Subject: [PATCH 23/48] Bring dashboard changes directly from the backend configuration PR --- .../frontend/core/components/Dashboard.js | 52 ++++++++++++++----- 1 file changed, 40 insertions(+), 12 deletions(-) diff --git a/src/electron/frontend/core/components/Dashboard.js b/src/electron/frontend/core/components/Dashboard.js index f96e2153b7..96f6de1fae 100644 --- a/src/electron/frontend/core/components/Dashboard.js +++ b/src/electron/frontend/core/components/Dashboard.js @@ -261,7 +261,7 @@ export class Dashboard extends LitElement { }); page.requestUpdate(); // Ensure the page is re-rendered with new workflow configurations - + // Ensure that all states are synced to the proper state for this page (e.g. conversions have been run) this.page .checkSyncState() .then(async () => { @@ -271,23 +271,34 @@ export class Dashboard extends LitElement { ? `

${projectName}

Conversion Pipeline` : projectName; - this.updateSections({ sidebar: false, main: true }); - const { skipped } = this.subSidebar.sections[info.section]?.pages?.[info.id] ?? {}; if (skipped) { if (isStorybook) return; // Do not skip on storybook - // Run skip functions - Object.entries(page.workflow).forEach(([key, state]) => { - if (typeof state.skip === "function") state.skip(); - }); - - // Skip right over the page if configured as such - if (previous && previous.info.previous === this.page) await this.page.onTransition(-1); - else await this.page.onTransition(1); + const backwards = previous && previous.info.previous === this.page; + + return ( + Promise.all( + Object.entries(page.workflow).map(async ([_, state]) => { + if (typeof state.skip === "function" && !backwards) return await state.skip(); // Run skip functions + }) + ) + + // Skip right over the page if configured as such + .then(async () => { + if (backwards) await this.main.onTransition(-1); + else await this.main.onTransition(1); + }) + ); } + + page.requestUpdate(); // Re-render the page on each load + + // Update main to render page + this.updateSections({ sidebar: false, main: true }); }) + .catch((e) => { const previousId = previous?.info?.id ?? -1; this.main.onTransition(previousId); // Revert back to previous page @@ -300,7 +311,7 @@ export class Dashboard extends LitElement { ); }) .finally(() => { - if (this.#transitionPromise.value) this.#transitionPromise.trigger(page); // This ensures calls to page.to() can be properly awaited until the next page is ready + if (this.#transitionPromise.value) this.#transitionPromise.trigger(this.main.page); // This ensures calls to page.to() can be properly awaited until the next page is ready }); } @@ -370,6 +381,23 @@ export class Dashboard extends LitElement { }) )); + + let resolved; + promise.then(res => { + resolved = true + console.log("Transitioned to", res.info.id); + }) + .catch(e => { + console.error('SOMETHIG THAPPENED WOTH', res.info.id, e) + }) + + setTimeout(() => { + if (!resolved) { + console.warn("Transition Promise Timed Out", res.info.id) + // this.#transitionPromise.trigger(this.page) + } + }, 1000) + if (typeof transition === "number") { const info = this.page.info; const sign = Math.sign(transition); From bf96a464d8a8f16873a1f8bf7f66872d6ba15b3e Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Tue, 4 Jun 2024 09:54:50 -0700 Subject: [PATCH 24/48] Update Dashboard.js --- src/electron/frontend/core/components/Dashboard.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/electron/frontend/core/components/Dashboard.js b/src/electron/frontend/core/components/Dashboard.js index 96f6de1fae..f68bb36819 100644 --- a/src/electron/frontend/core/components/Dashboard.js +++ b/src/electron/frontend/core/components/Dashboard.js @@ -259,8 +259,7 @@ export class Dashboard extends LitElement { else elements.forEach((el) => el.setAttribute("hidden", true)); } }); - - page.requestUpdate(); // Ensure the page is re-rendered with new workflow configurations + // Ensure that all states are synced to the proper state for this page (e.g. conversions have been run) this.page .checkSyncState() From 28bc09055823238070b4fda2cf63d7a36688fb8a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 4 Jun 2024 16:55:05 +0000 Subject: [PATCH 25/48] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../frontend/core/components/Dashboard.js | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/electron/frontend/core/components/Dashboard.js b/src/electron/frontend/core/components/Dashboard.js index f68bb36819..15dd5b43c3 100644 --- a/src/electron/frontend/core/components/Dashboard.js +++ b/src/electron/frontend/core/components/Dashboard.js @@ -380,22 +380,22 @@ export class Dashboard extends LitElement { }) )); - let resolved; - promise.then(res => { - resolved = true - console.log("Transitioned to", res.info.id); - }) - .catch(e => { - console.error('SOMETHIG THAPPENED WOTH', res.info.id, e) - }) + promise + .then((res) => { + resolved = true; + console.log("Transitioned to", res.info.id); + }) + .catch((e) => { + console.error("SOMETHIG THAPPENED WOTH", res.info.id, e); + }); setTimeout(() => { if (!resolved) { - console.warn("Transition Promise Timed Out", res.info.id) + console.warn("Transition Promise Timed Out", res.info.id); // this.#transitionPromise.trigger(this.page) } - }, 1000) + }, 1000); if (typeof transition === "number") { const info = this.page.info; From 90fa005339279f52493650663419c86cd4f9cfad Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 4 Jun 2024 16:55:20 +0000 Subject: [PATCH 26/48] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/electron/frontend/core/components/Dashboard.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/electron/frontend/core/components/Dashboard.js b/src/electron/frontend/core/components/Dashboard.js index 15dd5b43c3..6b636f2cf8 100644 --- a/src/electron/frontend/core/components/Dashboard.js +++ b/src/electron/frontend/core/components/Dashboard.js @@ -259,7 +259,7 @@ export class Dashboard extends LitElement { else elements.forEach((el) => el.setAttribute("hidden", true)); } }); - + // Ensure that all states are synced to the proper state for this page (e.g. conversions have been run) this.page .checkSyncState() From b82875dd8395bcff212910705ffe8a9a41bcb24d Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Tue, 4 Jun 2024 11:52:17 -0700 Subject: [PATCH 27/48] Rely on Python for timezone information --- environments/environment-Linux.yml | 1 + .../environment-MAC-apple-silicon.yml | 1 + environments/environment-MAC-intel.yml | 1 + environments/environment-Windows.yml | 1 + .../frontend/core/components/Dashboard.js | 17 ---- .../core/components/DateTimeSelector.js | 5 +- .../core/components/JSONSchemaForm.js | 21 +--- .../frontend/core/components/Search.js | 1 + .../frontend/core/components/pages/Page.js | 14 +-- .../pages/guided-mode/setup/Preform.js | 95 ++++++++++-------- src/pyflask/app.py | 2 + .../manageNeuroconv/manage_neuroconv.py | 3 + src/pyflask/namespaces/__init__.py | 1 + src/pyflask/namespaces/time.py | 29 ++++++ src/schemas/timezone.schema.ts | 96 +++++++++++++------ 15 files changed, 165 insertions(+), 123 deletions(-) create mode 100644 src/pyflask/namespaces/time.py diff --git a/environments/environment-Linux.yml b/environments/environment-Linux.yml index 54b78ea4e7..ea486f110c 100644 --- a/environments/environment-Linux.yml +++ b/environments/environment-Linux.yml @@ -21,3 +21,4 @@ dependencies: - pytest-cov == 4.1.0 - scikit-learn == 1.4.0 - tqdm_publisher >= 0.0.1 + - tzlocal >= 5.2 diff --git a/environments/environment-MAC-apple-silicon.yml b/environments/environment-MAC-apple-silicon.yml index 3100f7f765..84e29d4f33 100644 --- a/environments/environment-MAC-apple-silicon.yml +++ b/environments/environment-MAC-apple-silicon.yml @@ -27,3 +27,4 @@ dependencies: - pytest-cov == 4.1.0 - scikit-learn == 1.4.0 - tqdm_publisher >= 0.0.1 + - tzlocal >= 5.2 diff --git a/environments/environment-MAC-intel.yml b/environments/environment-MAC-intel.yml index 7e5933c15b..3cb9377672 100644 --- a/environments/environment-MAC-intel.yml +++ b/environments/environment-MAC-intel.yml @@ -24,3 +24,4 @@ dependencies: - pytest-cov == 4.1.0 - scikit-learn == 1.4.0 - tqdm_publisher >= 0.0.1 + - tzlocal >= 5.2 diff --git a/environments/environment-Windows.yml b/environments/environment-Windows.yml index 10bef56c95..1cb6f2e23d 100644 --- a/environments/environment-Windows.yml +++ b/environments/environment-Windows.yml @@ -24,3 +24,4 @@ dependencies: - pytest-cov == 4.1.0 - scikit-learn == 1.4.0 - tqdm_publisher >= 0.0.1 + - tzlocal >= 5.2 diff --git a/src/electron/frontend/core/components/Dashboard.js b/src/electron/frontend/core/components/Dashboard.js index 6b636f2cf8..c112388854 100644 --- a/src/electron/frontend/core/components/Dashboard.js +++ b/src/electron/frontend/core/components/Dashboard.js @@ -380,23 +380,6 @@ export class Dashboard extends LitElement { }) )); - let resolved; - promise - .then((res) => { - resolved = true; - console.log("Transitioned to", res.info.id); - }) - .catch((e) => { - console.error("SOMETHIG THAPPENED WOTH", res.info.id, e); - }); - - setTimeout(() => { - if (!resolved) { - console.warn("Transition Promise Timed Out", res.info.id); - // this.#transitionPromise.trigger(this.page) - } - }, 1000); - if (typeof transition === "number") { const info = this.page.info; const sign = Math.sign(transition); diff --git a/src/electron/frontend/core/components/DateTimeSelector.js b/src/electron/frontend/core/components/DateTimeSelector.js index c8f58d6d60..3d6c010624 100644 --- a/src/electron/frontend/core/components/DateTimeSelector.js +++ b/src/electron/frontend/core/components/DateTimeSelector.js @@ -29,10 +29,7 @@ export const renderDateTime = (value) => { }; export const resolveDateTime = renderDateTime; -// const resolveDateTime = (value) => { -// if (typeof value === "string") return extractISOString(new Date(value), { offset: true }); -// return value; -// } + export class DateTimeSelector extends LitElement { static get styles() { diff --git a/src/electron/frontend/core/components/JSONSchemaForm.js b/src/electron/frontend/core/components/JSONSchemaForm.js index c50f91ab36..56dd4b7230 100644 --- a/src/electron/frontend/core/components/JSONSchemaForm.js +++ b/src/electron/frontend/core/components/JSONSchemaForm.js @@ -15,9 +15,6 @@ import { InspectorListItem } from "./preview/inspector/InspectorList"; import { Validator } from "jsonschema"; import { successHue, warningHue, errorHue } from "./globals"; import { Button } from "./Button"; -import { extractISOString } from "./DateTimeSelector"; -import { timezoneProperties } from "../../../../schemas/timezone.schema"; -const timezonePropertyPaths = timezoneProperties.map((arr) => arr.join(".")); const encode = (str) => { try { @@ -250,8 +247,7 @@ export class JSONSchemaForm extends LitElement { dialogType: { type: String, reflect: false }, dialogOptions: { type: Object, reflect: false }, globals: { type: Object, reflect: false }, - validateEmptyValues: { type: Boolean, reflect: true }, - timezone: { type: String, reflect: true }, + validateEmptyValues: { type: Boolean, reflect: true } }; } @@ -284,8 +280,6 @@ export class JSONSchemaForm extends LitElement { this.results = (props.base ? structuredClone(props.results) : props.results) ?? {}; // Deep clone results in nested forms this.globals = props.globals ?? {}; - this.timezone = props.timezone ?? undefined; - this.ignore = props.ignore ?? {}; this.required = props.required ?? {}; this.dialogOptions = props.dialogOptions; @@ -910,17 +904,6 @@ export class JSONSchemaForm extends LitElement { let value = parent[name]; - const { timezone } = this; - - // Validate with timezone awareness - const isTimezoneProperty = timezonePropertyPaths.includes(externalPath.join(".")); - if (timezone && isTimezoneProperty) { - value = extractISOString(value, { - offset: true, - timezone, - }); - } - const skipValidation = this.validateEmptyValues === null && value === undefined; const validateArgs = input.pattern || skipValidation ? [] : [value, schema]; @@ -1223,8 +1206,6 @@ export class JSONSchemaForm extends LitElement { results: { ...nestedResults }, globals: this.globals?.[name], - timezone: this.timezone, - controls: this.controls[name], onUpdate: (internalPath, value, forceUpdate) => { diff --git a/src/electron/frontend/core/components/Search.js b/src/electron/frontend/core/components/Search.js index 5b133e2f03..29c3433e2e 100644 --- a/src/electron/frontend/core/components/Search.js +++ b/src/electron/frontend/core/components/Search.js @@ -33,6 +33,7 @@ export class Search extends LitElement { } #close = () => { + console.log('CLOSING', this.getSelectedOption()) if (this.listMode === "input" && this.getAttribute("interacted") === "true") { this.setAttribute("interacted", false); this.#onSelect(this.getSelectedOption()); diff --git a/src/electron/frontend/core/components/pages/Page.js b/src/electron/frontend/core/components/pages/Page.js index 69a436f715..42f830c57e 100644 --- a/src/electron/frontend/core/components/pages/Page.js +++ b/src/electron/frontend/core/components/pages/Page.js @@ -10,8 +10,6 @@ import { randomizeElements, mapSessions, merge } from "./utils"; import { resolveMetadata } from "./guided-mode/data/utils.js"; import Swal from "sweetalert2"; import { createProgressPopup } from "../utils/progress.js"; -import { timezoneProperties } from "../../../../../schemas/timezone.schema"; -import { extractISOString } from "../DateTimeSelector.js"; export class Page extends LitElement { // static get styles() { @@ -175,21 +173,11 @@ export class Page extends LitElement { // Resolve the correct session info from all of the metadata for this conversion const metadata = resolveMetadata(subject, session, globalState); - // Add timezone information to relevant metadata - timezoneProperties.forEach((path) => { - const name = path.slice(-1)[0]; - const pathTo = path.slice(0, -1); - const parent = pathTo.reduce((acc, key) => acc[key], metadata); - parent[name] = extractISOString(parent[name], { - offset: true, - timezone: this.workflow.timezone.value, - }); - }); - const sessionInfo = { ...sessionResults, metadata, source_data: merge(SourceData, sourceDataCopy), + timezone: this.workflow.timezone.value }; const payload = { diff --git a/src/electron/frontend/core/components/pages/guided-mode/setup/Preform.js b/src/electron/frontend/core/components/pages/guided-mode/setup/Preform.js index 5f88af6bd9..88a410ab6b 100644 --- a/src/electron/frontend/core/components/pages/guided-mode/setup/Preform.js +++ b/src/electron/frontend/core/components/pages/guided-mode/setup/Preform.js @@ -74,46 +74,60 @@ const questions = { // ------------------------ Derived from the above information ------------------------------- // ------------------------------------------------------------------------------------------- -const dependents = Object.entries(questions).reduce((acc, [name, info]) => { - acc[name] = []; - - const deps = info.dependencies; - - if (deps) { - if (Array.isArray(deps)) - deps.forEach((dep) => { - if (!acc[dep]) acc[dep] = []; - acc[dep].push({ name }); - }); - else - Object.entries(deps).forEach(([dep, opts]) => { - if (!acc[dep]) acc[dep] = []; - acc[dep].push({ name, ...opts }); - }); - } - return acc; -}, {}); - -const defaults = Object.entries(questions).reduce((acc, [name, info]) => { - acc[name] = info.default; - return acc; -}, {}); - -const required = Object.entries(questions).reduce((acc, [name, info]) => { - if (info.required) acc.push(name); - return acc; -}, []); - -const projectWorkflowSchema = { - type: "object", - properties: Object.entries(questions).reduce((acc, [name, info]) => { - acc[name] = info; +const getSchema = (questions) => { + + // Inject latest timezone schema each render + questions.timezone = { ...questions.timezone, ...timezoneSchema }; + + const dependents = Object.entries(questions).reduce((acc, [name, info]) => { + acc[name] = []; + + const deps = info.dependencies; + + if (deps) { + if (Array.isArray(deps)) + deps.forEach((dep) => { + if (!acc[dep]) acc[dep] = []; + acc[dep].push({ name }); + }); + else + Object.entries(deps).forEach(([dep, opts]) => { + if (!acc[dep]) acc[dep] = []; + acc[dep].push({ name, ...opts }); + }); + } return acc; - }, {}), - order: Object.keys(questions), - required, - additionalProperties: false, -}; + }, {}); + + const defaults = Object.entries(questions).reduce((acc, [name, info]) => { + acc[name] = info.default; + return acc; + }, {}); + + const required = Object.entries(questions).reduce((acc, [name, info]) => { + if (info.required) acc.push(name); + return acc; + }, []); + + const projectWorkflowSchema = { + type: "object", + properties: Object.entries(questions).reduce((acc, [name, info]) => { + acc[name] = info; + return acc; + }, {}), + order: Object.keys(questions), + required, + additionalProperties: false, + }; + + return { + schema: structuredClone(projectWorkflowSchema), + defaults, + dependents + } + + +} // ---------------------------------------------------------------------- // ------------------------ Preform Class ------------------------------- @@ -144,7 +158,8 @@ export class GuidedPreform extends Page { }; updateForm = () => { - const schema = structuredClone(projectWorkflowSchema); + + const { schema, dependents, defaults } = getSchema(questions); const projectState = this.info.globalState.project ?? {}; if (!projectState.workflow) projectState.workflow = {}; diff --git a/src/pyflask/app.py b/src/pyflask/app.py index 28b0f8a308..1cac80005a 100644 --- a/src/pyflask/app.py +++ b/src/pyflask/app.py @@ -34,6 +34,7 @@ neurosift_namespace, startup_namespace, system_namespace, + time_namespace, ) neurosift_file_registry = collections.defaultdict(bool) @@ -65,6 +66,7 @@ api.add_namespace(data_namespace) api.add_namespace(system_namespace) api.add_namespace(dandi_namespace) +api.add_namespace(time_namespace) # api.add_namespace(neurosift_namespace) # TODO: enable later api.init_app(flask_app) diff --git a/src/pyflask/manageNeuroconv/manage_neuroconv.py b/src/pyflask/manageNeuroconv/manage_neuroconv.py index dc9bfa63ca..c24e4d08b4 100644 --- a/src/pyflask/manageNeuroconv/manage_neuroconv.py +++ b/src/pyflask/manageNeuroconv/manage_neuroconv.py @@ -844,6 +844,9 @@ def convert_to_nwb( default_output_base = STUB_SAVE_FOLDER_PATH if run_stub_test else CONVERSION_SAVE_FOLDER_PATH default_output_directory = default_output_base / project_name + timezone = info.get("timezone") + + try: # add a subdirectory to a filepath if stub_test is true diff --git a/src/pyflask/namespaces/__init__.py b/src/pyflask/namespaces/__init__.py index 0f1edb2741..d3a0853781 100644 --- a/src/pyflask/namespaces/__init__.py +++ b/src/pyflask/namespaces/__init__.py @@ -4,3 +4,4 @@ from .neurosift import neurosift_namespace from .startup import startup_namespace from .system import system_namespace +from .time import time_namespace diff --git a/src/pyflask/namespaces/time.py b/src/pyflask/namespaces/time.py new file mode 100644 index 0000000000..58ba513620 --- /dev/null +++ b/src/pyflask/namespaces/time.py @@ -0,0 +1,29 @@ +"""An API for handling general time information.""" + +from typing import Dict, Union + +import flask_restx + +time_namespace = flask_restx.Namespace(name="time", description="Request time-related information.") + + +@time_namespace.route("/timezones") +class GetTimezones(flask_restx.Resource): + + @time_namespace.doc( + description="Request the available timezones on the system.", + ) + def get(self) -> Union[Dict[str, int], None]: + from zoneinfo import available_timezones + return list(available_timezones()) + + +@time_namespace.route("/timezone") +class GetTimezones(flask_restx.Resource): + + @time_namespace.doc( + description="Request the current timezone on the system.", + ) + def get(self) -> Union[Dict[str, int], None]: + from tzlocal import get_localzone + return str(get_localzone()) diff --git a/src/schemas/timezone.schema.ts b/src/schemas/timezone.schema.ts index 43728615c3..704f37f035 100644 --- a/src/schemas/timezone.schema.ts +++ b/src/schemas/timezone.schema.ts @@ -1,34 +1,47 @@ -import { header } from "../electron/frontend/core/components/forms/utils"; +import { baseUrl, onServerOpen } from "../electron/frontend/core/server/globals"; +import { isStorybook } from '../electron/frontend/core/globals' +const setReady: any = {} -export const localTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; +const createPromise = (prop: string) => new Promise((resolve) => setReady[prop] = resolve) -export const getTimeZoneName = (timezone, timeZoneName = 'long') => new Date().toLocaleDateString(undefined, {day:'2-digit', timeZone: timezone, timeZoneName }).substring(4) +export const ready = { + timezones: createPromise("timezones"), + timezone: createPromise("timezone"), +} -const timezones = Intl.supportedValuesOf('timeZone'); -if (!timezones.includes(localTimeZone)) timezones.push(localTimeZone); // Add the local timezone if it's not already in the list +// Get timezones +onServerOpen(async () => { + await fetch(new URL("/time/timezones", baseUrl)) + .then((res) => res.json()) + .then((timezones) => { + console.log(timezones); + setReady.timezones(timezones) + }) + .catch(() => { + if (isStorybook) setReady.timezones([]) + }); +}); + +// Get timezone +onServerOpen(async () => { + await fetch(new URL("/time/timezone", baseUrl)) + .then((res) => res.json()) + .then((timezone) => { + console.log(timezone); + setReady.timezone(timezone) + }) + .catch(() => { + if (isStorybook) setReady.timezone(null) + }); +}); -const enumCategories = timezones.reduce((acc, timezone) => { - const parts = timezone.split("/"); - const category = parts.length === 1 ? "Other" : parts[0]; - acc[timezone] = category; - return acc; -}, {}); -const enumLabels = timezones.reduce((acc, timezone) => { - const parts = timezone.split("/"); - const nonCategoryParts = parts.slice(1, parts.length - 1); - acc[timezone] = header(parts[parts.length - 1]); - if (nonCategoryParts.length) acc[timezone] += ` — ${nonCategoryParts.map(str => header(str)).join(", ")}` - return acc; -}, {}); -const enumKeywords = timezones.reduce((acc, timezone) => { - acc[timezone] = [ getTimeZoneName(timezone, 'long'), getTimeZoneName(timezone, 'short') ] - return acc; -}, {}); +export const localTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; +// export const getTimeZoneName = (timezone, timeZoneName = 'long') => new Date().toLocaleDateString(undefined, {day:'2-digit', timeZone: timezone, timeZoneName }).substring(4) // NOTE: Used before validation and conversion to add timezone information to the data export const timezoneProperties = [ @@ -72,14 +85,39 @@ export function getISODateInTimezone( } -export default { +const timezoneSchema = { type: "string", description: "Provide a base timezone for all date and time operations in the GUIDE.", + enum: [ localTimeZone ], default: localTimeZone, - enum: timezones, - enumLabels, - enumKeywords, - enumCategories, - strict: true, - search: true + strict: true } + +ready.timezones.then((timezones) => { + ready.timezone.then((timezone) => { + + timezoneSchema.strict = true + timezoneSchema.search = true + + const filteredTimezones = timezoneSchema.enum = timezones.filter(tz => { + return tz.split('/').length > 1 + && !tz.toLowerCase().includes('etc/') + }); + + timezoneSchema.enumLabels = filteredTimezones.reduce((acc, tz) => { + const [ region, city ] = tz.split('/') + acc[tz] = `${city}, ${region}` + return acc + }) + + timezoneSchema.enumCategories = filteredTimezones.reduce((acc, tz) => { + const [ region ] = tz.split('/') + acc[tz] = region + return acc + }) + + timezoneSchema.default = timezone; + }) +}) + +export default timezoneSchema From c9370ad203564e01158829bc29dfca21167c64b9 Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Tue, 4 Jun 2024 11:52:28 -0700 Subject: [PATCH 28/48] Update GuidedMetadata.js --- .../core/components/pages/guided-mode/data/GuidedMetadata.js | 2 -- 1 file changed, 2 deletions(-) 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 eb99fa1b28..724327498b 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 @@ -286,8 +286,6 @@ export class GuidedMetadataPage extends ManagedPage { results, globals: aggregateGlobalMetadata, - timezone: this.workflow.timezone.value, - ignore: propsToIgnore, onOverride: (name) => { this.notify(`${header(name)} has been overridden with a global value.`, "warning", 3000); From c8a69fc806704dfc17837fe290c22d3f743bd659 Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Tue, 4 Jun 2024 11:54:34 -0700 Subject: [PATCH 29/48] Update GuidedSourceData.js --- .../core/components/pages/guided-mode/data/GuidedSourceData.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/electron/frontend/core/components/pages/guided-mode/data/GuidedSourceData.js b/src/electron/frontend/core/components/pages/guided-mode/data/GuidedSourceData.js index 4a84177ba6..8bda2a9e54 100644 --- a/src/electron/frontend/core/components/pages/guided-mode/data/GuidedSourceData.js +++ b/src/electron/frontend/core/components/pages/guided-mode/data/GuidedSourceData.js @@ -289,15 +289,12 @@ export class GuidedSourceDataPage extends ManagedPage { alignment: alignmentInfo, }; - console.warn("Sending", sessionInfo); - const data = await run("neuroconv/alignment", sessionInfo, { title: "Checking Alignment", message: "Please wait...", }); const { metadata } = data; - console.warn("GOT", data); if (Object.keys(metadata).length === 0) { this.notify( From 6476dafb6a670727a461b721c3e51e15ead71ad1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 4 Jun 2024 18:54:37 +0000 Subject: [PATCH 30/48] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../frontend/core/components/DateTimeSelector.js | 1 - .../frontend/core/components/JSONSchemaForm.js | 2 +- src/electron/frontend/core/components/Search.js | 2 +- src/electron/frontend/core/components/pages/Page.js | 2 +- .../core/components/pages/guided-mode/setup/Preform.js | 10 +++------- src/pyflask/manageNeuroconv/manage_neuroconv.py | 1 - src/pyflask/namespaces/time.py | 4 +++- src/schemas/timezone.schema.ts | 2 +- 8 files changed, 10 insertions(+), 14 deletions(-) diff --git a/src/electron/frontend/core/components/DateTimeSelector.js b/src/electron/frontend/core/components/DateTimeSelector.js index 3d6c010624..a8ba90cf8e 100644 --- a/src/electron/frontend/core/components/DateTimeSelector.js +++ b/src/electron/frontend/core/components/DateTimeSelector.js @@ -30,7 +30,6 @@ export const renderDateTime = (value) => { export const resolveDateTime = renderDateTime; - export class DateTimeSelector extends LitElement { static get styles() { return css` diff --git a/src/electron/frontend/core/components/JSONSchemaForm.js b/src/electron/frontend/core/components/JSONSchemaForm.js index 56dd4b7230..9c9572d3fb 100644 --- a/src/electron/frontend/core/components/JSONSchemaForm.js +++ b/src/electron/frontend/core/components/JSONSchemaForm.js @@ -247,7 +247,7 @@ export class JSONSchemaForm extends LitElement { dialogType: { type: String, reflect: false }, dialogOptions: { type: Object, reflect: false }, globals: { type: Object, reflect: false }, - validateEmptyValues: { type: Boolean, reflect: true } + validateEmptyValues: { type: Boolean, reflect: true }, }; } diff --git a/src/electron/frontend/core/components/Search.js b/src/electron/frontend/core/components/Search.js index 29c3433e2e..dcd2647618 100644 --- a/src/electron/frontend/core/components/Search.js +++ b/src/electron/frontend/core/components/Search.js @@ -33,7 +33,7 @@ export class Search extends LitElement { } #close = () => { - console.log('CLOSING', this.getSelectedOption()) + console.log("CLOSING", this.getSelectedOption()); if (this.listMode === "input" && this.getAttribute("interacted") === "true") { this.setAttribute("interacted", false); this.#onSelect(this.getSelectedOption()); diff --git a/src/electron/frontend/core/components/pages/Page.js b/src/electron/frontend/core/components/pages/Page.js index 26a2e5f1d0..e340ad6ef5 100644 --- a/src/electron/frontend/core/components/pages/Page.js +++ b/src/electron/frontend/core/components/pages/Page.js @@ -188,7 +188,7 @@ export class Page extends LitElement { ...conversionOptions, // Any additional conversion options override the defaults interfaces: globalState.interfaces, alignment, - timezone: this.workflow.timezone.value + timezone: this.workflow.timezone.value, }; fileConfiguration.push(payload); diff --git a/src/electron/frontend/core/components/pages/guided-mode/setup/Preform.js b/src/electron/frontend/core/components/pages/guided-mode/setup/Preform.js index 88a410ab6b..7ba682da87 100644 --- a/src/electron/frontend/core/components/pages/guided-mode/setup/Preform.js +++ b/src/electron/frontend/core/components/pages/guided-mode/setup/Preform.js @@ -75,7 +75,6 @@ const questions = { // ------------------------------------------------------------------------------------------- const getSchema = (questions) => { - // Inject latest timezone schema each render questions.timezone = { ...questions.timezone, ...timezoneSchema }; @@ -123,11 +122,9 @@ const getSchema = (questions) => { return { schema: structuredClone(projectWorkflowSchema), defaults, - dependents - } - - -} + dependents, + }; +}; // ---------------------------------------------------------------------- // ------------------------ Preform Class ------------------------------- @@ -158,7 +155,6 @@ export class GuidedPreform extends Page { }; updateForm = () => { - const { schema, dependents, defaults } = getSchema(questions); const projectState = this.info.globalState.project ?? {}; if (!projectState.workflow) projectState.workflow = {}; diff --git a/src/pyflask/manageNeuroconv/manage_neuroconv.py b/src/pyflask/manageNeuroconv/manage_neuroconv.py index c971700dbe..e8589a4b8c 100644 --- a/src/pyflask/manageNeuroconv/manage_neuroconv.py +++ b/src/pyflask/manageNeuroconv/manage_neuroconv.py @@ -870,7 +870,6 @@ def convert_to_nwb( timezone = info.get("timezone") - try: # add a subdirectory to a filepath if stub_test is true diff --git a/src/pyflask/namespaces/time.py b/src/pyflask/namespaces/time.py index 58ba513620..6602667b7c 100644 --- a/src/pyflask/namespaces/time.py +++ b/src/pyflask/namespaces/time.py @@ -15,8 +15,9 @@ class GetTimezones(flask_restx.Resource): ) def get(self) -> Union[Dict[str, int], None]: from zoneinfo import available_timezones + return list(available_timezones()) - + @time_namespace.route("/timezone") class GetTimezones(flask_restx.Resource): @@ -26,4 +27,5 @@ class GetTimezones(flask_restx.Resource): ) def get(self) -> Union[Dict[str, int], None]: from tzlocal import get_localzone + return str(get_localzone()) diff --git a/src/schemas/timezone.schema.ts b/src/schemas/timezone.schema.ts index 704f37f035..2bb05541b4 100644 --- a/src/schemas/timezone.schema.ts +++ b/src/schemas/timezone.schema.ts @@ -100,7 +100,7 @@ ready.timezones.then((timezones) => { timezoneSchema.search = true const filteredTimezones = timezoneSchema.enum = timezones.filter(tz => { - return tz.split('/').length > 1 + return tz.split('/').length > 1 && !tz.toLowerCase().includes('etc/') }); From b3fc8714163ae385eeae665b1267e8c50c2f8bab Mon Sep 17 00:00:00 2001 From: Cody Baker Date: Tue, 4 Jun 2024 15:08:47 -0400 Subject: [PATCH 31/48] cleanup modules --- src/pyflask/app.py | 2 -- src/pyflask/namespaces/__init__.py | 1 - src/pyflask/namespaces/system.py | 23 ++++++++++++++++++++++ src/pyflask/namespaces/time.py | 31 ------------------------------ 4 files changed, 23 insertions(+), 34 deletions(-) delete mode 100644 src/pyflask/namespaces/time.py diff --git a/src/pyflask/app.py b/src/pyflask/app.py index 1cac80005a..28b0f8a308 100644 --- a/src/pyflask/app.py +++ b/src/pyflask/app.py @@ -34,7 +34,6 @@ neurosift_namespace, startup_namespace, system_namespace, - time_namespace, ) neurosift_file_registry = collections.defaultdict(bool) @@ -66,7 +65,6 @@ api.add_namespace(data_namespace) api.add_namespace(system_namespace) api.add_namespace(dandi_namespace) -api.add_namespace(time_namespace) # api.add_namespace(neurosift_namespace) # TODO: enable later api.init_app(flask_app) diff --git a/src/pyflask/namespaces/__init__.py b/src/pyflask/namespaces/__init__.py index d3a0853781..0f1edb2741 100644 --- a/src/pyflask/namespaces/__init__.py +++ b/src/pyflask/namespaces/__init__.py @@ -4,4 +4,3 @@ from .neurosift import neurosift_namespace from .startup import startup_namespace from .system import system_namespace -from .time import time_namespace diff --git a/src/pyflask/namespaces/system.py b/src/pyflask/namespaces/system.py index 491cf36aa2..1bdef06da9 100644 --- a/src/pyflask/namespaces/system.py +++ b/src/pyflask/namespaces/system.py @@ -20,3 +20,26 @@ def get(self) -> Union[Dict[str, int], None]: logical = cpu_count() return dict(physical=physical, logical=logical) + +@system_namespace.route("/all_timezones") +class GetTimezones(flask_restx.Resource): + + @time_namespace.doc( + description="Request the available timezones available to the backend.", + ) + def get(self) -> List[str]: + import zoneinfo + + return list(zoneinfo.available_timezones()) + + +@system_namespace.route("/local_timezone") +class GetTimezones(flask_restx.Resource): + + @time_namespace.doc( + description="Request the current timezone on the system.", + ) + def get(self) -> str: + import tzlocal + + return tzlocal.get_localzone_name() \ No newline at end of file diff --git a/src/pyflask/namespaces/time.py b/src/pyflask/namespaces/time.py deleted file mode 100644 index 6602667b7c..0000000000 --- a/src/pyflask/namespaces/time.py +++ /dev/null @@ -1,31 +0,0 @@ -"""An API for handling general time information.""" - -from typing import Dict, Union - -import flask_restx - -time_namespace = flask_restx.Namespace(name="time", description="Request time-related information.") - - -@time_namespace.route("/timezones") -class GetTimezones(flask_restx.Resource): - - @time_namespace.doc( - description="Request the available timezones on the system.", - ) - def get(self) -> Union[Dict[str, int], None]: - from zoneinfo import available_timezones - - return list(available_timezones()) - - -@time_namespace.route("/timezone") -class GetTimezones(flask_restx.Resource): - - @time_namespace.doc( - description="Request the current timezone on the system.", - ) - def get(self) -> Union[Dict[str, int], None]: - from tzlocal import get_localzone - - return str(get_localzone()) From 6d03f2e9fb2d209c7c9b73d6940398757f7b031a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 4 Jun 2024 19:11:20 +0000 Subject: [PATCH 32/48] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/pyflask/namespaces/system.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pyflask/namespaces/system.py b/src/pyflask/namespaces/system.py index 1bdef06da9..052b2742fb 100644 --- a/src/pyflask/namespaces/system.py +++ b/src/pyflask/namespaces/system.py @@ -21,6 +21,7 @@ def get(self) -> Union[Dict[str, int], None]: return dict(physical=physical, logical=logical) + @system_namespace.route("/all_timezones") class GetTimezones(flask_restx.Resource): @@ -42,4 +43,4 @@ class GetTimezones(flask_restx.Resource): def get(self) -> str: import tzlocal - return tzlocal.get_localzone_name() \ No newline at end of file + return tzlocal.get_localzone_name() From 48d8e7676d276d10540e7517ef4d28aaad070588 Mon Sep 17 00:00:00 2001 From: Cody Baker Date: Tue, 4 Jun 2024 15:17:32 -0400 Subject: [PATCH 33/48] add call to management helpers --- src/pyflask/manageNeuroconv/manage_neuroconv.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/pyflask/manageNeuroconv/manage_neuroconv.py b/src/pyflask/manageNeuroconv/manage_neuroconv.py index e8589a4b8c..35e3bb9c82 100644 --- a/src/pyflask/manageNeuroconv/manage_neuroconv.py +++ b/src/pyflask/manageNeuroconv/manage_neuroconv.py @@ -993,6 +993,11 @@ def update_conversion_progress(message): del ecephys_metadata["ElectrodeColumns"] + # Correct timezone in metadata fields + 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"].replace(tzinfo=zoneinfo.ZoneInfo(info["timezone"])) + # Actually run the conversion converter.run_conversion( metadata=resolved_metadata, From b4406440a4a67c2ee73de364cbfdfcf8ee74cbed Mon Sep 17 00:00:00 2001 From: Cody Baker Date: Tue, 4 Jun 2024 15:18:33 -0400 Subject: [PATCH 34/48] remove placeholder --- src/pyflask/manageNeuroconv/manage_neuroconv.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/pyflask/manageNeuroconv/manage_neuroconv.py b/src/pyflask/manageNeuroconv/manage_neuroconv.py index 35e3bb9c82..c3778b8f04 100644 --- a/src/pyflask/manageNeuroconv/manage_neuroconv.py +++ b/src/pyflask/manageNeuroconv/manage_neuroconv.py @@ -868,8 +868,6 @@ def convert_to_nwb( default_output_base = STUB_SAVE_FOLDER_PATH if run_stub_test else CONVERSION_SAVE_FOLDER_PATH default_output_directory = default_output_base / project_name - timezone = info.get("timezone") - try: # add a subdirectory to a filepath if stub_test is true From 5f948c9d6f12577f9d39f58fc0dca00f3220fbce Mon Sep 17 00:00:00 2001 From: Cody Baker Date: Tue, 4 Jun 2024 15:54:21 -0400 Subject: [PATCH 35/48] object does not mutate --- src/pyflask/manageNeuroconv/manage_neuroconv.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pyflask/manageNeuroconv/manage_neuroconv.py b/src/pyflask/manageNeuroconv/manage_neuroconv.py index c3778b8f04..a322b602a5 100644 --- a/src/pyflask/manageNeuroconv/manage_neuroconv.py +++ b/src/pyflask/manageNeuroconv/manage_neuroconv.py @@ -992,9 +992,10 @@ def update_conversion_progress(message): del ecephys_metadata["ElectrodeColumns"] # Correct timezone in metadata fields - resolved_metadata["NWBFile"]["session_start_time"].replace(tzinfo=zoneinfo.ZoneInfo(info["timezone"])) + resolved_metadata["NWBFile"]["session_start_time"] = 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"].replace(tzinfo=zoneinfo.ZoneInfo(info["timezone"])) + resolved_metadata["Subject"]["date_of_birth"] = resolved_metadata["Subject"]["date_of_birth"].replace(tzinfo=zoneinfo.ZoneInfo(info["timezone"])) # Actually run the conversion converter.run_conversion( From eaf29a10cce7b97f53d91d1008b643aaf46020f3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 4 Jun 2024 19:55:30 +0000 Subject: [PATCH 36/48] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/pyflask/manageNeuroconv/manage_neuroconv.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/pyflask/manageNeuroconv/manage_neuroconv.py b/src/pyflask/manageNeuroconv/manage_neuroconv.py index a322b602a5..22bceede37 100644 --- a/src/pyflask/manageNeuroconv/manage_neuroconv.py +++ b/src/pyflask/manageNeuroconv/manage_neuroconv.py @@ -992,10 +992,13 @@ def update_conversion_progress(message): del ecephys_metadata["ElectrodeColumns"] # Correct timezone in metadata fields - resolved_metadata["NWBFile"]["session_start_time"] = resolved_metadata["NWBFile"][ - "session_start_time"].replace(tzinfo=zoneinfo.ZoneInfo(info["timezone"])) + resolved_metadata["NWBFile"]["session_start_time"] = 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"] = resolved_metadata["Subject"]["date_of_birth"].replace(tzinfo=zoneinfo.ZoneInfo(info["timezone"])) + resolved_metadata["Subject"]["date_of_birth"] = resolved_metadata["Subject"]["date_of_birth"].replace( + tzinfo=zoneinfo.ZoneInfo(info["timezone"]) + ) # Actually run the conversion converter.run_conversion( From 8e1b83534a01522d2d5b1fffe9aca3781db98798 Mon Sep 17 00:00:00 2001 From: Cody Baker Date: Tue, 4 Jun 2024 17:00:33 -0400 Subject: [PATCH 37/48] adjust fetch calls --- src/schemas/timezone.schema.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/schemas/timezone.schema.ts b/src/schemas/timezone.schema.ts index 2bb05541b4..b752c633c8 100644 --- a/src/schemas/timezone.schema.ts +++ b/src/schemas/timezone.schema.ts @@ -12,7 +12,7 @@ export const ready = { // Get timezones onServerOpen(async () => { - await fetch(new URL("/time/timezones", baseUrl)) + await fetch(new URL("/system/all_timezones", baseUrl)) .then((res) => res.json()) .then((timezones) => { console.log(timezones); @@ -25,7 +25,7 @@ onServerOpen(async () => { // Get timezone onServerOpen(async () => { - await fetch(new URL("/time/timezone", baseUrl)) + await fetch(new URL("/system/local_timezone", baseUrl)) .then((res) => res.json()) .then((timezone) => { console.log(timezone); From 987e1b21abf36c475921e7645a0fd2b350596ff2 Mon Sep 17 00:00:00 2001 From: Cody Baker Date: Tue, 4 Jun 2024 17:01:58 -0400 Subject: [PATCH 38/48] fix namespace for docs --- src/pyflask/namespaces/system.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pyflask/namespaces/system.py b/src/pyflask/namespaces/system.py index 052b2742fb..a50a7af43a 100644 --- a/src/pyflask/namespaces/system.py +++ b/src/pyflask/namespaces/system.py @@ -25,7 +25,7 @@ def get(self) -> Union[Dict[str, int], None]: @system_namespace.route("/all_timezones") class GetTimezones(flask_restx.Resource): - @time_namespace.doc( + @system_namespace.doc( description="Request the available timezones available to the backend.", ) def get(self) -> List[str]: @@ -37,7 +37,7 @@ def get(self) -> List[str]: @system_namespace.route("/local_timezone") class GetTimezones(flask_restx.Resource): - @time_namespace.doc( + @system_namespace.doc( description="Request the current timezone on the system.", ) def get(self) -> str: From b75e55d892d233c7642eac86816887189ff57229 Mon Sep 17 00:00:00 2001 From: Cody Baker Date: Tue, 4 Jun 2024 17:03:12 -0400 Subject: [PATCH 39/48] debug import --- src/pyflask/namespaces/system.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyflask/namespaces/system.py b/src/pyflask/namespaces/system.py index a50a7af43a..0dc7f3ccf1 100644 --- a/src/pyflask/namespaces/system.py +++ b/src/pyflask/namespaces/system.py @@ -1,6 +1,6 @@ """An API for handling general system information.""" -from typing import Dict, Union +from typing import Dict, Union, List import flask_restx From 6bd7823acb276057b7ac2a3795772716980bbf6a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 4 Jun 2024 21:04:31 +0000 Subject: [PATCH 40/48] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/pyflask/namespaces/system.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyflask/namespaces/system.py b/src/pyflask/namespaces/system.py index 0dc7f3ccf1..bd4382f150 100644 --- a/src/pyflask/namespaces/system.py +++ b/src/pyflask/namespaces/system.py @@ -1,6 +1,6 @@ """An API for handling general system information.""" -from typing import Dict, Union, List +from typing import Dict, List, Union import flask_restx From d3ba6d10982dc9414bb28dbd3d30c0c94cf625ed Mon Sep 17 00:00:00 2001 From: Cody Baker Date: Tue, 4 Jun 2024 17:06:39 -0400 Subject: [PATCH 41/48] fix import and parse datetime object --- src/pyflask/manageNeuroconv/manage_neuroconv.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/pyflask/manageNeuroconv/manage_neuroconv.py b/src/pyflask/manageNeuroconv/manage_neuroconv.py index 22bceede37..6442a2ae5e 100644 --- a/src/pyflask/manageNeuroconv/manage_neuroconv.py +++ b/src/pyflask/manageNeuroconv/manage_neuroconv.py @@ -7,6 +7,7 @@ import os import re import traceback +import zoneinfo from datetime import datetime from pathlib import Path from shutil import copytree, rmtree @@ -992,11 +993,13 @@ def update_conversion_progress(message): del ecephys_metadata["ElectrodeColumns"] # Correct timezone in metadata fields - resolved_metadata["NWBFile"]["session_start_time"] = resolved_metadata["NWBFile"]["session_start_time"].replace( + 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"] = resolved_metadata["Subject"]["date_of_birth"].replace( + resolved_metadata["Subject"]["date_of_birth"] = datetime.fromisoformat(resolved_metadata["Subject"][ + "date_of_birth"]).replace( tzinfo=zoneinfo.ZoneInfo(info["timezone"]) ) From 3180bc320e745afb610abc27cde084411b8b316a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 4 Jun 2024 21:07:49 +0000 Subject: [PATCH 42/48] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/pyflask/manageNeuroconv/manage_neuroconv.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/pyflask/manageNeuroconv/manage_neuroconv.py b/src/pyflask/manageNeuroconv/manage_neuroconv.py index 6442a2ae5e..cd23882eb9 100644 --- a/src/pyflask/manageNeuroconv/manage_neuroconv.py +++ b/src/pyflask/manageNeuroconv/manage_neuroconv.py @@ -993,15 +993,13 @@ def update_conversion_progress(message): del ecephys_metadata["ElectrodeColumns"] # Correct timezone in metadata fields - resolved_metadata["NWBFile"]["session_start_time"] = datetime.fromisoformat(resolved_metadata["NWBFile"][ - "session_start_time"]).replace( - tzinfo=zoneinfo.ZoneInfo(info["timezone"]) - ) + 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"]).replace( - tzinfo=zoneinfo.ZoneInfo(info["timezone"]) - ) + resolved_metadata["Subject"]["date_of_birth"] = datetime.fromisoformat( + resolved_metadata["Subject"]["date_of_birth"] + ).replace(tzinfo=zoneinfo.ZoneInfo(info["timezone"])) # Actually run the conversion converter.run_conversion( From 10aa18be118eefcce87bed39895ca50a7abc6281 Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Tue, 4 Jun 2024 16:34:56 -0700 Subject: [PATCH 43/48] Sort by category --- src/schemas/timezone.schema.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/schemas/timezone.schema.ts b/src/schemas/timezone.schema.ts index b752c633c8..6d643f0be3 100644 --- a/src/schemas/timezone.schema.ts +++ b/src/schemas/timezone.schema.ts @@ -1,5 +1,6 @@ import { baseUrl, onServerOpen } from "../electron/frontend/core/server/globals"; import { isStorybook } from '../electron/frontend/core/globals' +import { header } from "../electron/frontend/core/components/forms/utils"; const setReady: any = {} @@ -105,16 +106,23 @@ ready.timezones.then((timezones) => { }); timezoneSchema.enumLabels = filteredTimezones.reduce((acc, tz) => { - const [ region, city ] = tz.split('/') - acc[tz] = `${city}, ${region}` + const [ _, ...other ] = tz.split('/') + acc[tz] = other.map(part => header(part)).join(' — ') return acc - }) + }, {}) + + timezoneSchema.enumKeywords = filteredTimezones.reduce((acc, tz) => { + const [ region ] = tz.split('/') + acc[tz] = [ header(region) ] + return acc + }, {}) timezoneSchema.enumCategories = filteredTimezones.reduce((acc, tz) => { const [ region ] = tz.split('/') acc[tz] = region return acc - }) + }, {}) + console.log(timezone); timezoneSchema.default = timezone; }) From 0cc837ca1745b01ed440c3f24c968d05dbd15c39 Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Tue, 4 Jun 2024 16:39:09 -0700 Subject: [PATCH 44/48] Update timezone.schema.ts --- src/schemas/timezone.schema.ts | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/schemas/timezone.schema.ts b/src/schemas/timezone.schema.ts index 6d643f0be3..445a66e4cf 100644 --- a/src/schemas/timezone.schema.ts +++ b/src/schemas/timezone.schema.ts @@ -15,10 +15,7 @@ export const ready = { onServerOpen(async () => { await fetch(new URL("/system/all_timezones", baseUrl)) .then((res) => res.json()) - .then((timezones) => { - console.log(timezones); - setReady.timezones(timezones) - }) + .then((timezones) => setReady.timezones(timezones)) .catch(() => { if (isStorybook) setReady.timezones([]) }); @@ -28,10 +25,7 @@ onServerOpen(async () => { onServerOpen(async () => { await fetch(new URL("/system/local_timezone", baseUrl)) .then((res) => res.json()) - .then((timezone) => { - console.log(timezone); - setReady.timezone(timezone) - }) + .then((timezone) => setReady.timezone(timezone)) .catch(() => { if (isStorybook) setReady.timezone(null) }); @@ -122,7 +116,6 @@ ready.timezones.then((timezones) => { acc[tz] = region return acc }, {}) - console.log(timezone); timezoneSchema.default = timezone; }) From 37d75d6cafd292373b4681692b7e262e41785109 Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Tue, 4 Jun 2024 18:41:15 -0700 Subject: [PATCH 45/48] Add current timezone if filtered out (e.g. in Actions) --- src/schemas/timezone.schema.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/schemas/timezone.schema.ts b/src/schemas/timezone.schema.ts index 445a66e4cf..84dcf581cd 100644 --- a/src/schemas/timezone.schema.ts +++ b/src/schemas/timezone.schema.ts @@ -99,6 +99,8 @@ ready.timezones.then((timezones) => { && !tz.toLowerCase().includes('etc/') }); + if (!filteredTimezones.includes(timezone)) filteredTimezones.push(timezone) // Add the local timezone if it's not in the list + timezoneSchema.enumLabels = filteredTimezones.reduce((acc, tz) => { const [ _, ...other ] = tz.split('/') acc[tz] = other.map(part => header(part)).join(' — ') From 518dd96b4a3e1565ef3b43fab642cb37dca3cd9a Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Wed, 5 Jun 2024 07:59:49 -0700 Subject: [PATCH 46/48] Fix workflow loading. Add localstorage data handling back on the web --- .../frontend/core/components/Dashboard.js | 8 ++++++-- .../guided-mode/options/GuidedStubPreview.js | 2 +- src/electron/frontend/core/progress/index.js | 6 +++--- .../frontend/core/progress/operations.js | 14 +++++++++----- src/electron/frontend/core/progress/update.js | 18 ++++++++++++++---- src/schemas/timezone.schema.ts | 2 +- stories/pages/Preform.stories.js | 14 ++++++++++++++ 7 files changed, 48 insertions(+), 16 deletions(-) create mode 100644 stories/pages/Preform.stories.js diff --git a/src/electron/frontend/core/components/Dashboard.js b/src/electron/frontend/core/components/Dashboard.js index c112388854..bff5b9d134 100644 --- a/src/electron/frontend/core/components/Dashboard.js +++ b/src/electron/frontend/core/components/Dashboard.js @@ -249,16 +249,20 @@ export class Dashboard extends LitElement { const workflowConfig = page.workflow ?? (page.workflow = {}); const workflowValues = page.info.globalState?.project?.workflow ?? {}; + // Define the value for each workflow value Object.entries(workflowValues).forEach(([key, value]) => { const config = workflowConfig[key] ?? (workflowConfig[key] = {}); config.value = value; + }); - const { elements } = config; + // Toggle elements based on workflow configuration + Object.entries(workflowConfig).forEach(([key, config]) => { + const { value, elements } = config; if (elements) { if (value) elements.forEach((el) => el.removeAttribute("hidden")); else elements.forEach((el) => el.setAttribute("hidden", true)); } - }); + }) // Ensure that all states are synced to the proper state for this page (e.g. conversions have been run) this.page diff --git a/src/electron/frontend/core/components/pages/guided-mode/options/GuidedStubPreview.js b/src/electron/frontend/core/components/pages/guided-mode/options/GuidedStubPreview.js index 55979eaa5a..c82d45bc7c 100644 --- a/src/electron/frontend/core/components/pages/guided-mode/options/GuidedStubPreview.js +++ b/src/electron/frontend/core/components/pages/guided-mode/options/GuidedStubPreview.js @@ -45,7 +45,7 @@ export class GuidedStubPreviewPage extends Page { }; render() { - const { preview, project } = this.info.globalState; + const { preview = {}, project } = this.info.globalState; return preview.stubs ? new NWBFilePreview({ project: project.name, files: preview.stubs }) diff --git a/src/electron/frontend/core/progress/index.js b/src/electron/frontend/core/progress/index.js index aede8554b0..3ece730979 100644 --- a/src/electron/frontend/core/progress/index.js +++ b/src/electron/frontend/core/progress/index.js @@ -87,8 +87,8 @@ class GlobalAppConfig { save() { const encoded = encodeObject(this.data); - - fs.writeFileSync(this.path, JSON.stringify(encoded, null, 2)); + if (fs) fs.writeFileSync(this.path, JSON.stringify(encoded, null, 2)); + else localStorage.setItem(this.path, JSON.stringify(encoded)); } } @@ -115,7 +115,7 @@ export const save = (page, overrides = {}) => { }; export const getEntries = () => { - if (!fs.existsSync(guidedProgressFilePath)) fs.mkdirSync(guidedProgressFilePath, { recursive: true }); //Check if progress folder exists. If not, create it. + if (fs && !fs.existsSync(guidedProgressFilePath)) fs.mkdirSync(guidedProgressFilePath, { recursive: true }); //Check if progress folder exists. If not, create it. const progressFiles = fs ? fs.readdirSync(guidedProgressFilePath) : Object.keys(localStorage); return progressFiles.filter((path) => path.slice(-5) === ".json"); }; diff --git a/src/electron/frontend/core/progress/operations.js b/src/electron/frontend/core/progress/operations.js index 3154e349d8..8d60e238ef 100644 --- a/src/electron/frontend/core/progress/operations.js +++ b/src/electron/frontend/core/progress/operations.js @@ -7,13 +7,17 @@ export const remove = (name) => { const progressFilePathToDelete = joinPath(guidedProgressFilePath, name + ".json"); //delete the progress file - if (fs.existsSync(progressFilePathToDelete)) fs.unlinkSync(progressFilePathToDelete); + if (fs) { + if (fs.existsSync(progressFilePathToDelete)) fs.unlinkSync(progressFilePathToDelete); + } else localStorage.removeItem(progressFilePathToDelete); - // delete default preview location - fs.rmSync(joinPath(previewSaveFolderPath, name), { recursive: true, force: true }); + if (fs) { + // delete default preview location + fs.rmSync(joinPath(previewSaveFolderPath, name), { recursive: true, force: true }); - // delete default conversion location - fs.rmSync(joinPath(conversionSaveFolderPath, name), { recursive: true, force: true }); + // delete default conversion location + fs.rmSync(joinPath(conversionSaveFolderPath, name), { recursive: true, force: true }); + } return true; }; diff --git a/src/electron/frontend/core/progress/update.js b/src/electron/frontend/core/progress/update.js index 650d4999a6..5ddfe9b5da 100644 --- a/src/electron/frontend/core/progress/update.js +++ b/src/electron/frontend/core/progress/update.js @@ -15,7 +15,13 @@ export const rename = (newDatasetName, previousDatasetName) => { // update old progress file with new dataset name const oldProgressFilePath = `${guidedProgressFilePath}/${previousDatasetName}.json`; const newProgressFilePath = `${guidedProgressFilePath}/${newDatasetName}.json`; - fs.renameSync(oldProgressFilePath, newProgressFilePath); + + if (fs) fs.renameSync(oldProgressFilePath, newProgressFilePath); + else { + localStorage.setItem(newProgressFilePath, localStorage.getItem(oldProgressFilePath)); + localStorage.removeItem(oldProgressFilePath); + } + } else throw new Error("No previous project name provided"); }; @@ -50,9 +56,13 @@ export const updateFile = (projectName, callback) => { data["last-modified"] = new Date(); // Always update the last modified time - var guidedFilePath = joinPath(guidedProgressFilePath, projectName + ".json"); + const projectFileName = projectName + ".json" + const guidedFilePath = joinPath(guidedProgressFilePath, projectFileName); // Save the file through the available mechanisms - if (!fs.existsSync(guidedProgressFilePath)) fs.mkdirSync(guidedProgressFilePath, { recursive: true }); //create progress folder if one does not exist - fs.writeFileSync(guidedFilePath, JSON.stringify(data, null, 2)); + if (fs) { + if (!fs.existsSync(guidedProgressFilePath)) fs.mkdirSync(guidedProgressFilePath, { recursive: true }); //create progress folder if one does not exist + fs.writeFileSync(guidedFilePath, JSON.stringify(data, null, 2)); + } else localStorage.setItem(guidedFilePath, JSON.stringify(data)); + }; diff --git a/src/schemas/timezone.schema.ts b/src/schemas/timezone.schema.ts index 84dcf581cd..8455387fe5 100644 --- a/src/schemas/timezone.schema.ts +++ b/src/schemas/timezone.schema.ts @@ -27,7 +27,7 @@ onServerOpen(async () => { .then((res) => res.json()) .then((timezone) => setReady.timezone(timezone)) .catch(() => { - if (isStorybook) setReady.timezone(null) + if (isStorybook) setReady.timezone(Intl.DateTimeFormat().resolvedOptions().timeZone) }); }); diff --git a/stories/pages/Preform.stories.js b/stories/pages/Preform.stories.js new file mode 100644 index 0000000000..30eac6a97c --- /dev/null +++ b/stories/pages/Preform.stories.js @@ -0,0 +1,14 @@ +import { globalState, PageTemplate } from "./storyStates"; + +export default { + title: "Pages/Guided Mode/Workflow", + parameters: { + chromatic: { disableSnapshot: false }, + }, +}; + +export const Default = PageTemplate.bind({}); +Default.args = { + activePage: "//worklflow", + globalState, +}; From aaaeef7dd48afa4613204e63c8dfd49a95b2a4f8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 5 Jun 2024 15:00:07 +0000 Subject: [PATCH 47/48] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/electron/frontend/core/components/Dashboard.js | 2 +- src/electron/frontend/core/progress/update.js | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/electron/frontend/core/components/Dashboard.js b/src/electron/frontend/core/components/Dashboard.js index bff5b9d134..126aa62704 100644 --- a/src/electron/frontend/core/components/Dashboard.js +++ b/src/electron/frontend/core/components/Dashboard.js @@ -262,7 +262,7 @@ export class Dashboard extends LitElement { if (value) elements.forEach((el) => el.removeAttribute("hidden")); else elements.forEach((el) => el.setAttribute("hidden", true)); } - }) + }); // Ensure that all states are synced to the proper state for this page (e.g. conversions have been run) this.page diff --git a/src/electron/frontend/core/progress/update.js b/src/electron/frontend/core/progress/update.js index 5ddfe9b5da..1b73bbc0e7 100644 --- a/src/electron/frontend/core/progress/update.js +++ b/src/electron/frontend/core/progress/update.js @@ -21,7 +21,6 @@ export const rename = (newDatasetName, previousDatasetName) => { localStorage.setItem(newProgressFilePath, localStorage.getItem(oldProgressFilePath)); localStorage.removeItem(oldProgressFilePath); } - } else throw new Error("No previous project name provided"); }; @@ -56,7 +55,7 @@ export const updateFile = (projectName, callback) => { data["last-modified"] = new Date(); // Always update the last modified time - const projectFileName = projectName + ".json" + const projectFileName = projectName + ".json"; const guidedFilePath = joinPath(guidedProgressFilePath, projectFileName); // Save the file through the available mechanisms @@ -64,5 +63,4 @@ export const updateFile = (projectName, callback) => { if (!fs.existsSync(guidedProgressFilePath)) fs.mkdirSync(guidedProgressFilePath, { recursive: true }); //create progress folder if one does not exist fs.writeFileSync(guidedFilePath, JSON.stringify(data, null, 2)); } else localStorage.setItem(guidedFilePath, JSON.stringify(data)); - }; From 4f3056f17c7f930f1ad0b27f0a606ca4f1971141 Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Wed, 5 Jun 2024 08:04:24 -0700 Subject: [PATCH 48/48] Fix preform page. Add multi-session workflow to show other pages --- src/electron/frontend/core/progress/index.js | 2 +- stories/pages/Preform.stories.js | 2 +- stories/pages/storyStates.ts | 6 ++++++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/electron/frontend/core/progress/index.js b/src/electron/frontend/core/progress/index.js index 3ece730979..617ebf6086 100644 --- a/src/electron/frontend/core/progress/index.js +++ b/src/electron/frontend/core/progress/index.js @@ -143,7 +143,7 @@ export const getAll = (progressFiles) => { return progressFiles.map((progressFile) => { let progressFilePath = joinPath(guidedProgressFilePath, progressFile); return transformProgressFile( - JSON.parse(fs ? fs.readFileSync(progressFilePath) : localStorage.getItem(progressFilePath)) + JSON.parse(fs ? fs.readFileSync(progressFilePath) : localStorage.getItem(progressFile)) ); }); }; diff --git a/stories/pages/Preform.stories.js b/stories/pages/Preform.stories.js index 30eac6a97c..eec7622253 100644 --- a/stories/pages/Preform.stories.js +++ b/stories/pages/Preform.stories.js @@ -9,6 +9,6 @@ export default { export const Default = PageTemplate.bind({}); Default.args = { - activePage: "//worklflow", + activePage: "//workflow", globalState, }; diff --git a/stories/pages/storyStates.ts b/stories/pages/storyStates.ts index d25ef602f5..c2f62a301d 100644 --- a/stories/pages/storyStates.ts +++ b/stories/pages/storyStates.ts @@ -23,6 +23,12 @@ export const globalState = { Subject: { species: "Mus musculus", }, + workflow: { + multiple_sessions: true, + locate_data: true, + base_directory: "path/to/data", + upload_to_dandi: true, + } }, structure: { results: {},