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

Fix Embargo #837

Merged
merged 21 commits into from
Jun 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
834b1c9
Re-enable embargo and ensure all requests are made with a token
garrettmflynn Jun 5, 2024
a05b0ee
Delete create_no_embargo.json
garrettmflynn Jun 5, 2024
e96cfe9
Merge branch 'main' into fix-embargo
garrettmflynn Jun 5, 2024
8d141af
Update utils.ts
garrettmflynn Jun 5, 2024
247c742
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 5, 2024
ef4c9d7
Merge branch 'main' into fix-embargo
CodyCBakerPhD Jun 5, 2024
775b636
Update dandi api
garrettmflynn Jun 5, 2024
68d974b
Merge branch 'fix-embargo' of https://github.com/NeurodataWithoutBord…
garrettmflynn Jun 5, 2024
6de9b15
Update dandi api version
garrettmflynn Jun 5, 2024
e43da41
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 5, 2024
935b286
Merge branch 'main' into fix-embargo
CodyCBakerPhD Jun 10, 2024
2bc1b75
Wait properly for asynchronous validation to occur before submitting …
garrettmflynn Jun 10, 2024
fc9cce2
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 10, 2024
80a5b47
Fix validation of groups
garrettmflynn Jun 10, 2024
6f0dce7
Merge branch 'fix-embargo' of https://github.com/NeurodataWithoutBord…
garrettmflynn Jun 10, 2024
a3870f0
Update JSONSchemaForm.js
garrettmflynn Jun 10, 2024
bef7529
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 10, 2024
86fd05c
Fix tests
garrettmflynn Jun 10, 2024
0d7633a
Merge branch 'fix-embargo' of https://github.com/NeurodataWithoutBord…
garrettmflynn Jun 10, 2024
755ae17
Merge branch 'main' into fix-embargo
CodyCBakerPhD Jun 10, 2024
d36cf46
Merge branch 'main' into fix-embargo
CodyCBakerPhD Jun 11, 2024
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
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
Loading