Skip to content

Commit

Permalink
Merge branch 'main' into improve_validation
Browse files Browse the repository at this point in the history
  • Loading branch information
CodyCBakerPhD authored Jun 12, 2024
2 parents 9617231 + 1bab832 commit 6f7f3c5
Show file tree
Hide file tree
Showing 14 changed files with 188 additions and 196 deletions.
5 changes: 3 additions & 2 deletions .github/workflows/testing_pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ jobs:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
label: environments/environment-Linux.yml
# Fails randomly on daily tests
# - os: ubuntu-latest
# label: environments/environment-Linux.yml

- os: macos-latest # Mac arm64 runner
label: environments/environment-MAC-apple-silicon.yml
Expand Down
17 changes: 10 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@
"@vitest/coverage-v8": "^1.6.0",
"chokidar": "^3.5.3",
"concurrently": "^7.6.0",
"dandi": "^0.0.4",
"dandi": "^0.0.6",
"find-free-port": "^2.0.0",
"fomantic-ui": "^2.8.8",
"fs-extra": "^10.0.0",
Expand Down
12 changes: 9 additions & 3 deletions src/electron/frontend/core/components/DandiResults.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { LitElement, css, html } from "lit";

import { get } from "dandi";
import { isStaging } from "./pages/uploads/utils";
import { isStaging, getAPIKey } from "./pages/uploads/utils";

export class DandiResults extends LitElement {
static get styles() {
Expand Down Expand Up @@ -38,8 +38,14 @@ export class DandiResults extends LitElement {

const otherElIds = ["embargo_status"];

const type = isStaging(this.id) ? "staging" : undefined;
const dandiset = await get(this.id, { type });
const staging = isStaging(this.id);
const type = staging ? "staging" : undefined;
const api_key = await getAPIKey.call(this, staging);

const dandiset = await get(this.id, {
type,
token: api_key,
});

otherElIds.forEach((str) => handleClass(str, dandiset));
elIds.forEach((str) => handleClass(str, dandiset.draft_version));
Expand Down
63 changes: 39 additions & 24 deletions src/electron/frontend/core/components/JSONSchemaForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -554,7 +554,32 @@ export class JSONSchemaForm extends LitElement {
};

validate = async (resolved = this.resolved) => {
if (this.validateEmptyValues === false) this.validateEmptyValues = true;
if (this.validateEmptyValues === false) {
this.validateEmptyValues = true;
await new Promise((resolve) => setTimeout(resolve, 0)); // Wait for next tick (re-render start)
await this.rendered; // Wait for re-render
}

// Validate nested forms (skip disabled)
for (let name in this.forms) {
const accordion = this.accordions[name];
if (!accordion || !accordion.disabled)
await this.forms[name].validate(resolved ? resolved[name] : undefined); // Validate nested forms too
}

for (let key in this.tables) {
try {
this.tables[key].validate(resolved ? resolved[key] : undefined); // Validate nested tables too
} catch (error) {
const title = this.tables[key].schema.title;
const message = error.message.replace(
"this table",
`the <b>${header(title ?? [...this.base, key].join("."))}</b> table`
);
this.throw(message);
break;
}
}

// Validate against the entire JSON Schema
const copy = structuredClone(resolved);
Expand Down Expand Up @@ -616,27 +641,6 @@ export class JSONSchemaForm extends LitElement {

if (message) this.throw(message);

// Validate nested forms (skip disabled)
for (let name in this.forms) {
const accordion = this.accordions[name];
if (!accordion || !accordion.disabled)
await this.forms[name].validate(resolved ? resolved[name] : undefined); // Validate nested forms too
}

for (let key in this.tables) {
try {
this.tables[key].validate(resolved ? resolved[key] : undefined); // Validate nested tables too
} catch (error) {
const title = this.tables[key].schema.title;
const message = error.message.replace(
"this table",
`the <b>${header(title ?? [...this.base, key].join("."))}</b> table`
);
this.throw(message);
break;
}
}

return true;
};

Expand Down Expand Up @@ -1007,8 +1011,19 @@ export class JSONSchemaForm extends LitElement {
const groupEl = this.#getGroupElement(externalPath);

if (groupEl) {
groupEl.classList[resolvedErrors.length ? "add" : "remove"]("error");
groupEl.classList[warnings.length ? "add" : "remove"]("warning");
groupEl.setAttribute(`data-${name}-errors`, updatedErrors.length);
groupEl.setAttribute(`data-${name}-warnings`, updatedWarnings.length);

const allFormSections = groupEl.querySelectorAll(".form-section");
const inputs = Array.from(allFormSections).map((section) => section.id);
const allErrors = inputs.reduce((acc, id) => acc + parseInt(groupEl.getAttribute(`data-${id}-errors`)), 0);
const allWarnings = inputs.reduce(
(acc, id) => acc + parseInt(groupEl.getAttribute(`data-${id}-warnings`)),
0
);

groupEl.classList[allErrors ? "add" : "remove"]("error");
groupEl.classList[allWarnings ? "add" : "remove"]("warning");
}

const clearAllErrors = isValid && updatedErrors.length === 0;
Expand Down
1 change: 0 additions & 1 deletion src/electron/frontend/core/components/Search.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ 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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ export class GuidedUploadPage extends Page {
},
onUpdate: () => (this.unsavedUpdates = true),
onThrow,
validateOnChange: validate,
validateOnChange: (...args) => validate.call(this, ...args),
}));
})
.catch((error) => html`<p>${error}</p>`);
Expand Down
89 changes: 6 additions & 83 deletions src/electron/frontend/core/components/pages/uploads/UploadsPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,13 @@ import { Modal } from "../../Modal";
import { DandiResults } from "../../DandiResults.js";

import dandiGlobalSchema from "../../../../../../schemas/json/dandi/global.json";
import { JSONSchemaInput } from "../../JSONSchemaInput.js";
import { header } from "../../forms/utils";

import { validateDANDIApiKey } from "../../../validation/dandi";

import * as dandi from "dandi";

import keyIcon from "../../../../assets/icons/key.svg?raw";

import { AWARD_VALIDATION_FAIL_MESSAGE, awardNumberValidator, isStaging, validate } from "./utils";
import { AWARD_VALIDATION_FAIL_MESSAGE, awardNumberValidator, isStaging, validate, getAPIKey } from "./utils";
import { createFormModal } from "../../forms/GlobalFormModal";

export function createDandiset(results = {}) {
Expand Down Expand Up @@ -72,6 +69,7 @@ export function createDandiset(results = {}) {
schema: dandiCreateSchema,
results,
validateEmptyValues: false, // Only show errors after submission

validateOnChange: async (name, parent) => {
const value = parent[name];

Expand All @@ -93,6 +91,7 @@ export function createDandiset(results = {}) {
{
name: "Embargo your Data",
properties: [["embargo_status"], ["nih_award_number"]],
link: true,
},
],
});
Expand Down Expand Up @@ -122,7 +121,8 @@ export function createDandiset(results = {}) {
token: api_key,
type: staging ? "staging" : undefined,
});
await api.init();

await api.authorize();

const metadata = {
description: form.resolved.description,
Expand Down Expand Up @@ -173,83 +173,6 @@ export function createDandiset(results = {}) {
};
}

async function getAPIKey(staging = false) {
const whichAPIKey = staging ? "development_api_key" : "main_api_key";
const DANDI = global.data.DANDI;
let api_key = DANDI?.api_keys?.[whichAPIKey];

const errors = await validateDANDIApiKey(api_key, staging);

const isInvalid = !errors || errors.length;

if (isInvalid) {
const modal = new Modal({
header: `${api_key ? "Update" : "Provide"} your ${header(whichAPIKey)}`,
open: true,
onClose: () => modal.remove(),
});

const input = new JSONSchemaInput({
path: [whichAPIKey],
schema: dandiGlobalSchema.properties.api_keys.properties[whichAPIKey],
});

input.style.padding = "25px";

modal.append(input);

let notification;

const notify = (message, type) => {
if (notification) this.dismiss(notification);
return (notification = this.notify(message, type));
};

modal.onClose = async () => notify("The updated DANDI API key was not set", "error");

api_key = await new Promise((resolve) => {
const button = new Button({
label: "Save",
primary: true,
onClick: async () => {
const value = input.value;
if (value) {
const errors = await validateDANDIApiKey(input.value, staging);
if (!errors || !errors.length) {
modal.remove();

merge(
{
DANDI: {
api_keys: {
[whichAPIKey]: value,
},
},
},
global.data
);

global.save();
resolve(value);
} else {
notify(errors[0].message, "error");
return false;
}
} else {
notify("Your DANDI API key was not set", "error");
}
},
});

modal.footer = button;

document.body.append(modal);
});
}

return api_key;
}

export async function uploadToDandi(info, type = "project" in info ? "project" : "") {
const { dandiset } = info;

Expand Down Expand Up @@ -435,7 +358,7 @@ export class UploadsPage extends Page {
error.message = "Please select at least one file or folder to upload.";
},

validateOnChange: validate,
validateOnChange: (...args) => validate.call(this, ...args),
}));
})
.catch((error) => html`<p>${error}</p>`);
Expand Down
Loading

0 comments on commit 6f7f3c5

Please sign in to comment.