Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow specifying subject and session IDs for single-session case #691

Merged
merged 16 commits into from
Mar 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion schemas/base-metadata.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,8 @@ export const preprocessMetadataSchema = (schema: any = baseMetadataSchema, globa
uvProperties.forEach(prop => {
electrodeItems[prop] = {}
electrodeItems[prop].title = prop.replace('uV', uvMathFormat)
console.log(electrodeItems[prop])
})

interfaceProps["Electrodes"].items.order = ["channel_name", "group_name", "shank_electrode_number", ...uvProperties];
interfaceProps["ElectrodeColumns"].items.order = ["name", "description", "data_type"];

Expand Down
1 change: 1 addition & 0 deletions src/renderer/src/stories/Dashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@ export class Dashboard extends LitElement {
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) {
if (isStorybook) return; // Do not skip on storybook

Expand Down
2 changes: 1 addition & 1 deletion src/renderer/src/stories/JSONSchemaForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ const componentCSS = `

:host {
display: inline-block;
width:100%;
width: 100%;
}


Expand Down
29 changes: 28 additions & 1 deletion src/renderer/src/stories/JSONSchemaInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,31 @@ export class JSONSchemaInput extends LitElement {
};
}

// Enforce dynamic required properties
attributeChangedCallback(key, _, latest) {
super.attributeChangedCallback(...arguments);

const formSchema = this.form.schema;

if (key === "required") {
const name = this.path.slice(-1)[0];

if (latest !== null && !this.conditional) {
const requirements = formSchema.required ?? (formSchema.required = []);
if (!requirements.includes(name)) requirements.push(name);
}

// Remove requirement from form schema (and force if conditional requirement)
else {
if (formSchema.requirements && formSchema.requirements.includes(name)) {
const set = new Set(formSchema.requirements);
set.remove(name);
formSchema.requirements = Array.from(set);
}
}
}
}

// schema,
// parent,
// path,
Expand Down Expand Up @@ -637,7 +662,9 @@ export class JSONSchemaInput extends LitElement {
${
schema.description
? html`<p class="guided--text-input-instructions">
${unsafeHTML(capitalize(schema.description))}${schema.description.slice(-1)[0] === "."
${unsafeHTML(capitalize(schema.description))}${[".", "?", "!"].includes(
schema.description.slice(-1)[0]
)
? ""
: "."}
</p>`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -263,44 +263,51 @@ export class GuidedPathExpansionPage extends Page {
#initialize = () => (this.localState = merge(this.info.globalState.structure, { results: {} }));

workflow = {
subject_id: {},
session_id: {},
locate_data: {
skip: () => {
this.#initialize();
const globalState = this.info.globalState;
merge({ structure: this.localState }, globalState); // Merge the actual entries into the structure

// Force single subject/session if not keeping existing data
if (!globalState.results) {
const existingMetadata =
globalState.results?.[this.altInfo.subject_id]?.[this.altInfo.session_id]?.metadata;
// if (!globalState.results) {

const existingSourceData =
globalState.results?.[this.altInfo.subject_id]?.[this.altInfo.session_id]?.source_data;
const subject_id = this.workflow.subject_id.value;
const session_id = this.workflow.session_id.value;

const source_data = {};
for (let key in globalState.interfaces) {
const existing = existingSourceData?.[key];
if (existing) source_data[key] = existing ?? {};
}
// Map existing results to new subject information (if available)
const existingResults = Object.values(Object.values(globalState.results ?? {})[0] ?? {})[0] ?? {};
const existingMetadata = existingResults.metadata;
const existingSourceData = existingResults.source_data;

const source_data = {};
for (let key in globalState.interfaces) {
const existing = existingSourceData?.[key];
if (existing) source_data[key] = existing ?? {};
}

globalState.results = {
[this.altInfo.subject_id]: {
[this.altInfo.session_id]: {
source_data,
metadata: {
NWBFile: {
session_id: this.altInfo.session_id,
...(existingMetadata?.NWBFile ?? {}),
},
Subject: {
subject_id: this.altInfo.subject_id,
...(existingMetadata?.Subject ?? {}),
},
globalState.results = {
[subject_id]: {
[session_id]: {
source_data,
metadata: {
NWBFile: {
session_id: session_id,
...(existingMetadata?.NWBFile ?? {}),
},
Subject: {
subject_id: subject_id,
...(existingMetadata?.Subject ?? {}),
},
},
},
};
}
},
};
// }

this.save({}, false); // Ensure this structure is saved
},
},
};
Expand Down Expand Up @@ -382,11 +389,6 @@ export class GuidedPathExpansionPage extends Page {
},
};

altInfo = {
subject_id: "001",
session_id: "1",
};

// altForm = new JSONSchemaForm({
// results: this.altInfo,
// schema: {
Expand Down Expand Up @@ -436,7 +438,7 @@ export class GuidedPathExpansionPage extends Page {
const form = (this.form = new JSONSchemaForm({
...structureState,
onThrow,
validateEmptyValues: null,
validateEmptyValues: false,

controls,

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,10 +222,6 @@ export class GuidedSourceDataPage extends ManagedPage {
updated() {
const dashboard = document.querySelector("nwb-dashboard");
const page = dashboard.page;
setTimeout(() => {
console.log(page.forms[0].form.accordions["SpikeGLX Recording"]);
});
console.log(page.forms[0].form.accordions);
}

render() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { validateOnChange } from "../../../../validation/index.js";
import { Table } from "../../../Table.js";

import { updateResultsFromSubjects } from "./utils";
import { merge } from "../../utils.js";
import { preprocessMetadataSchema } from "../../../../../../../schemas/base-metadata.schema";
import { Button } from "../../../Button.js";
import { createGlobalFormModal } from "../../../forms/GlobalFormModal";
Expand Down
82 changes: 70 additions & 12 deletions src/renderer/src/stories/pages/guided-mode/setup/Preform.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,36 @@ const questions = {
title: "Will this pipeline be run on multiple sessions?",
default: false,
},
subject_id: {
type: "string",
description: "Provide an identifier for your subject",
dependencies: {
multiple_sessions: {
condition: [false, undefined],
default: "",
required: true,
attribute: "hidden",
},
},
},
session_id: {
type: "string",
description: "Provide an identifier for your session",
dependencies: {
multiple_sessions: {
condition: [false, undefined],
default: "",
required: true,
attribute: "hidden",
},
},
},
locate_data: {
type: "boolean",
title: "Would you like to locate the source data programmatically?",
dependencies: ["multiple_sessions"],
dependencies: {
multiple_sessions: { default: false },
},
default: false,
},
};
Expand All @@ -28,11 +54,19 @@ const questions = {
const dependents = Object.entries(questions).reduce((acc, [name, info]) => {
acc[name] = [];

if (info.dependencies) {
info.dependencies.forEach((dep) => {
if (!acc[dep]) acc[dep] = [];
acc[dep].push(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;
}, {});
Expand Down Expand Up @@ -80,14 +114,38 @@ export class GuidedPreform extends Page {
this.form = new JSONSchemaForm({
schema,
results: this.state,
validateEmptyValues: false, // Only show errors after submission
validateOnChange: function (name, parent, path, value) {
dependents[name].forEach((dependent) => {
const dependencies = questions[dependent].dependencies;
const dependentEl = this.inputs[dependent];
if (dependencies.every((dep) => parent[dep])) dependentEl.removeAttribute("disabled");
else {
dependentEl.updateData(false);
dependentEl.setAttribute("disabled", true);
const dependencies = questions[dependent.name].dependencies;
const uniformDeps = Array.isArray(dependencies)
? dependencies.map((name) => {
return { name };
})
: Object.entries(dependencies).map(([name, info]) => {
return { name, ...info };
});

const dependentEl = this.inputs[dependent.name];

const attr = dependent.attribute ?? "disabled";

let condition = (v) => !!v;
if (!("condition" in dependent)) {
} else if (typeof dependent.condition === "boolean") condition = (v) => v == dependent.condition;
else if (Array.isArray(dependent.condition))
condition = (v) => dependent.condition.some((condition) => v == condition);
else console.warn("Invalid condition", dependent.condition);

if (uniformDeps.every(({ name }) => condition(parent[name]))) {
dependentEl.removeAttribute(attr);
if ("required" in dependent) dependentEl.required = dependent.required;
if ("__cached" in dependent) dependentEl.updateData(dependent.__cached);
} else {
if (dependentEl.value !== undefined) dependent.__cached = dependentEl.value;
dependentEl.updateData(dependent.default);
dependentEl.setAttribute(attr, true);
if ("required" in dependent) dependentEl.required = !dependent.required;
}
});
},
Expand Down
12 changes: 12 additions & 0 deletions src/renderer/src/stories/pages/settings/SettingsPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ function saveNewPipelineFromYaml(name, sourceData, rootFolder) {
const subjectId = "mouse1";
const sessions = ["session1"];

const hasMultipleSessions = sessions.length > 1;

const resolvedSourceData = structuredClone(sourceData);
Object.values(resolvedSourceData).forEach((info) => {
propertiesToTransform.forEach((property) => {
Expand All @@ -52,12 +54,22 @@ function saveNewPipelineFromYaml(name, sourceData, rootFolder) {

remove(updatedName, true);

const workflowInfo = {
multiple_sessions: hasMultipleSessions,
};

if (!workflowInfo.multiple_sessions) {
workflowInfo.subject_id = subjectId;
workflowInfo.session_id = sessions[0];
}

save({
info: {
globalState: {
project: {
name: updatedName,
initialized: true,
workflow: workflowInfo,
},

// provide data for all supported interfaces
Expand Down
13 changes: 13 additions & 0 deletions tests/e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,20 @@ describe('E2E Test', () => {

test('View the pre-form workflow page', async () => {

await references.page.evaluate(() => {
const dashboard = document.querySelector('nwb-dashboard')
const page = dashboard.page

const subjectId = page.form.getFormElement(['subject_id'])
subjectId.updateData('subject1')

const sessionId = page.form.getFormElement(['session_id'])
sessionId.updateData('session1')
})

await takeScreenshot('workflow-page', 300)


await toNextPage('structure')


Expand Down
Loading