Skip to content

Commit

Permalink
Merge pull request #193 from NeurodataWithoutBorders/basic-progress-bar
Browse files Browse the repository at this point in the history
Add basic progress bar for multi-session conversions
  • Loading branch information
CodyCBakerPhD authored May 19, 2023
2 parents 0c27825 + 51cc079 commit 887d543
Show file tree
Hide file tree
Showing 7 changed files with 184 additions and 32 deletions.
32 changes: 32 additions & 0 deletions src/renderer/src/stories/ProgressBar.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { ProgressBar } from "./ProgressBar";

export default {
title: "Components/Progress Bar",
};

const Template = (args) => new ProgressBar(args);

const b = 0;
const tsize = 50;
const bsize = 1;

export const Default = Template.bind({});
Default.args = {
value: {
b,
bsize,
tsize,
},
};

function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}

Default.play = async ({ canvasElement }) => {
const progressBar = canvasElement.querySelector("nwb-progress");
for (let i = 1; i <= tsize; i++) {
await sleep(1000);
progressBar.value = { b: i, tsize };
}
};
69 changes: 69 additions & 0 deletions src/renderer/src/stories/ProgressBar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@


import { LitElement, html, css } from 'lit';

export type ProgressProps = {
value?: {
b: number,
bsize?: number,
tsize: number
},
}

export class ProgressBar extends LitElement {

static get styles() {
return css`
:host {
display: flex;
align-items: center;
}
:host > div {
width: 100%;
height: 10px;
border: 1px solid lightgray;
overflow: hidden;
}
:host > div > div {
width: 0%;
height: 100%;
background-color: #029CFD;
transition: width 0.5s;
}
small {
white-space: nowrap;
margin-left: 10px;
}
`;
}

static get properties() {
return {
value: {
type: Object,
reflect: true,
}
};
}

declare value: any

constructor(props: ProgressProps = {}) {
super();

this.value = props.value ?? {}
}

render() {

const value = this.value.b / this.value.tsize * 100
return html`<div><div style="width: ${value}%"></div></div><small>${this.value.b}/${this.value.tsize} (${value.toFixed(0)}%)</small>`
}
}

customElements.get('nwb-progress') || customElements.define('nwb-progress', ProgressBar);
53 changes: 41 additions & 12 deletions src/renderer/src/stories/pages/Page.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { LitElement, html } from "lit";
import useGlobalStyles from "../utils/useGlobalStyles.js";
import { runConversion } from "./guided-mode/options/utils.js";
import { openProgressSwal, runConversion } from "./guided-mode/options/utils.js";
import { get, save } from "../../progress.js";
import { dismissNotification, notify } from "../../globals.js";
import { merge, randomizeElements, mapSessions } from "./utils.js";
Expand Down Expand Up @@ -83,7 +83,7 @@ export class Page extends LitElement {

mapSessions = (callback) => mapSessions(callback, this.info.globalState);

async runConversions(conversionOptions = {}, toRun) {
async runConversions(conversionOptions = {}, toRun, options = {}) {
let original = toRun;
if (!Array.isArray(toRun)) toRun = this.mapSessions();

Expand All @@ -95,26 +95,55 @@ export class Page extends LitElement {
const folder = this.info.globalState.conversion?.info?.output_folder; // Do not require output_folder on the frontend
let results = [];

for (let { subject, session } of toRun) {
const popup = await openProgressSwal({ title: `Running conversion`, ...options });

const isMultiple = toRun.length > 1;

let elements = {};
if (isMultiple) {
popup.hideLoading();
const element = popup.getHtmlContainer();
element.innerText = "";

const progressBar = new ProgressBar();
elements.progress = progressBar;
element.append(progressBar);
}

let completed = 0;
for (let info of toRun) {
const { subject, session } = info;
const basePath = `sub-${subject}/sub-${subject}_ses-${session}.nwb`;
const file = folder ? `${folder}/${basePath}` : basePath;

const result = await runConversion({
folder,
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)
...this.info.globalState.results[subject][session], // source_data and metadata are passed in here
...conversionOptions, // Any additional conversion options override the defaults

interfaces: this.info.globalState.interfaces,
}).catch((e) => {
const result = await runConversion(
{
folder,
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)
...this.info.globalState.results[subject][session], // source_data and metadata are passed in here
...conversionOptions, // Any additional conversion options override the defaults

interfaces: this.info.globalState.interfaces,
},
{ swal: popup, ...options }
).catch((e) => {
this.notify(e.message, "error");
popup.close();
throw e.message;
});

completed++;
if (isMultiple) {
const progressInfo = { b: completed, bsize: 1, tsize: toRun.length };
elements.progress.value = progressInfo;
}

results.push({ file, result });
}

popup.close();

return results;
}

Expand Down
16 changes: 12 additions & 4 deletions src/renderer/src/stories/pages/guided-mode/data/GuidedMetadata.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,16 @@ export class GuidedMetadataPage extends ManagedPage {

const instanceId = `sub-${subject}/ses-${session}`;

const schema = this.info.globalState.schema.metadata[subject][session];

// NOTE: This was a correction for differences in the expected values to be passed back to neuroconv
// delete schema.properties.Ecephys.properties.Electrodes
// delete results['Ecephys']['Electrodes']

const form = new JSONSchemaForm({
identifier: instanceId,
mode: "accordion",
schema: this.info.globalState.schema.metadata[subject][session],
schema,
results,
ignore: ["Ecephys", "source_script", "source_script_file_name"],
conditionalRequirements: [
Expand Down Expand Up @@ -76,9 +82,11 @@ export class GuidedMetadataPage extends ManagedPage {
if (subject.startsWith("sub-")) subject = subject.slice(4);
if (session.startsWith("ses-")) session = session.slice(4);

const [{ file, result }] = await this.runConversions({ stub_test: true }, [
{ subject, session },
]).catch((e) => this.notify(e.message, "error"));
const [{ file, result }] = await this.runConversions(
{ stub_test: true },
[{ subject, session }],
{ title: "Running conversion preview" }
).catch((e) => this.notify(e.message, "error"));

const modal = new Modal({
header: file,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ export class GuidedConversionOptionsPage extends Page {

// Preview a random conversion
delete this.info.globalState.preview; // Clear the preview results
const results = await this.runConversions({ stub_test: true }, 1);
const results = await this.runConversions({ stub_test: true }, 1, {
title: "Testing conversion on a random session",
});
this.info.globalState.preview = results; // Save the preview results

this.onTransition(1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ export class GuidedStubPreviewPage extends Page {
onNext: async () => {
this.save(); // Save in case the conversion fails
delete this.info.globalState.conversion.results;
this.info.globalState.conversion.results = await this.runConversions();
this.info.globalState.conversion.results = await this.runConversions({}, true, {
title: "Running all conversions",
});
this.onTransition(1);
},
};
Expand Down
38 changes: 24 additions & 14 deletions src/renderer/src/stories/pages/guided-mode/options/utils.js
Original file line number Diff line number Diff line change
@@ -1,34 +1,43 @@
import Swal from "sweetalert2";
import { baseUrl } from "../../../../globals.js";

export const run = async (url, payload, options = {}) => {
Swal.fire({
title: options.title ?? "Requesting data from server",
html: "Please wait...",
allowEscapeKey: false,
allowOutsideClick: false,
heightAuto: false,
backdrop: "rgba(0,0,0, 0.4)",
timerProgressBar: false,
didOpen: () => {
Swal.showLoading();
},
export const openProgressSwal = (options) => {
return new Promise((resolve) => {
Swal.fire({
title: options.title ?? "Requesting data from server",
html: `Please wait...`,
allowEscapeKey: false,
allowOutsideClick: false,
showConfirmButton: false,
heightAuto: false,
backdrop: "rgba(0,0,0, 0.4)",
timerProgressBar: false,
didOpen: () => {
Swal.showLoading();
resolve(Swal);
},
});
});
};

export const run = async (url, payload, options = {}) => {
const needsSwal = !options.swal;
if (needsSwal) openProgressSwal(options).then((swal) => (options.onOpen ? options.onOpen(swal) : undefined));

const results = await fetch(`${baseUrl}/neuroconv/${url}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
}).then((res) => res.json());

Swal.close();
if (needsSwal) Swal.close();

if (results?.message) throw new Error(`Request to ${url} failed: ${results.message}`);

return results || true;
};

export const runConversion = async (info) =>
export const runConversion = async (info, options = {}) =>
run(`convert`, info, {
title: "Running the conversion",
onError: (results) => {
Expand All @@ -38,4 +47,5 @@ export const runConversion = async (info) =>
return "Conversion failed with current metadata. Please try again.";
}
},
...options,
});

0 comments on commit 887d543

Please sign in to comment.