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

Dataset Publication Tutorial #711

Merged
merged 28 commits into from
Apr 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
fd7ab48
Add dataset publication documentation
garrettmflynn Mar 28, 2024
5f5e034
Follow the same workflow as the tutorial in the e2e tests
garrettmflynn Mar 28, 2024
f50a7be
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Mar 28, 2024
1870e3c
Ensure fine-grained updates from structure list
garrettmflynn Mar 28, 2024
04935e8
Merge branch 'publication-tutorial' of https://github.com/NeurodataWi…
garrettmflynn Mar 28, 2024
6bce4e5
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Mar 28, 2024
49ff4a5
Merge branch 'multisession-tutorial' into publication-tutorial
garrettmflynn Apr 3, 2024
855041c
Fix refs
garrettmflynn Apr 3, 2024
2eb04d2
Merge branch 'main' into publication-tutorial
garrettmflynn Apr 3, 2024
a7033ed
Restore external links
garrettmflynn Apr 3, 2024
4186e2c
Merge branch 'main' into publication-tutorial
CodyCBakerPhD Apr 3, 2024
dcae095
Update config.ts
garrettmflynn Apr 3, 2024
1236d7b
Apply suggestions from code review
garrettmflynn Apr 4, 2024
9d58958
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 4, 2024
aa4d2d3
Update intro and dandi image
garrettmflynn Apr 4, 2024
f465d03
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 4, 2024
bd9b563
Emphasize how to create a dandiset on the main archive
garrettmflynn Apr 4, 2024
5221e1b
Merge branch 'publication-tutorial' of https://github.com/NeurodataWi…
garrettmflynn Apr 4, 2024
08e292c
Update dataset_publication.rst
garrettmflynn Apr 4, 2024
5618b03
Add server note at the top instead of the bottom
garrettmflynn Apr 4, 2024
bd1a7a0
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 4, 2024
ca9d49b
Update docs/tutorials/dataset_publication.rst
CodyCBakerPhD Apr 4, 2024
a15da96
Add review page
garrettmflynn Apr 4, 2024
779089d
Merge branch 'publication-tutorial' of https://github.com/NeurodataWi…
garrettmflynn Apr 4, 2024
03a977a
Add dandiset creation popup
garrettmflynn Apr 4, 2024
b6f951c
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 4, 2024
70530f6
Apply suggestions from code review
garrettmflynn Apr 4, 2024
c137ab7
Apply suggestions from code review
CodyCBakerPhD Apr 4, 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
Binary file added docs/assets/dandi/api-token-location.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/tutorials/dandi/api-token-added.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/tutorials/dandi/api-tokens.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/tutorials/dandi/create-dandiset.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/tutorials/dandi/dandiset-id.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/tutorials/dandi/review-page.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/assets/tutorials/home-page.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/assets/tutorials/multiple/home-page-complete.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/assets/tutorials/single/intro-page.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"sphinx.ext.intersphinx", # Allows links to other sphinx project documentation sites
"sphinx_search.extension", # Allows for auto search function the documentation
"sphinx.ext.viewcode", # Shows source code in the documentation
"sphinx.ext.extlinks", # Allows to use shorter external links defined in the extlinks variable.
"sphinx.ext.extlinks", # Allows to use shorter external links defined in the extlinks variable.
]

templates_path = ["_templates"]
Expand Down
2 changes: 2 additions & 0 deletions docs/conf_extlinks.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
"matnwb-src": ("https://github.com/NeurodataWithoutBorders/matnwb/%s", "%s"),
"nwb-overview": ("https://nwb-overview.readthedocs.io/en/latest/%s", "%s"),
"path-expansion-guide": ("https://neuroconv.readthedocs.io/en/main/user_guide/expand_path.html%s", "%s"),
"dandi-staging": ("https://gui-staging.dandiarchive.org/%s", "%s"),
garrettmflynn marked this conversation as resolved.
Show resolved Hide resolved
"dandi-archive": ("https://dandiarchive.org/%s", "%s"),
"conda-install": (
"https://docs.conda.io/projects/conda/en/latest/user-guide/install/index.html#regular-installation%s",
"%s",
Expand Down
79 changes: 78 additions & 1 deletion docs/tutorials/dataset_publication.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,80 @@
Dataset Publication
=======================================
Coming soon...

For this tutorial, we'll be adapting the previous :doc:`Multi-Session Tutorial </tutorials/multiple_sessions>` to publish our data to the DANDI Archive.

.. note::
This tutorial focuses on uploading to the Staging server.

**When working with real data, you'll want to publish to the Main Archive**. In this case, follow the same steps outlined here—except replace the Staging server with the Main Archive.

.. note::
CodyCBakerPhD marked this conversation as resolved.
Show resolved Hide resolved
Gaining access to DANDI requires approval from the archive administrators. Separate approval is required for both the main archive and the staging server.

**This tutorial requires an account on the** :dandi-staging:`DANDI staging server <>`.

We’re going to use the Staging server for this tutorial so we don’t crowd the main DANDI Archive with `synthetic` datasets! However, you’ll want to publish your `real` data on the main server—which will require a separate approval process.

Once you receive notice that your account was approved, you can move on to the next steps.

Workflow Setup
--------------
1. Resume the conversion via the **Convert** page

2. Navigate to the **Workflow** page.

a. Specify that you’d like to publish your data to the :dandi-archive:`DANDI Archive <>`.

3. Navigate back to the **Conversion Review** page

You'll now notice that the **Exit Pipeline** button has been replaced with **Next**, allowing you to move forward with publication on the DANDI Archive.

DANDI Upload
------------
You’ll need to specify your DANDI API keys if you haven’t uploaded from the GUIDE before. These keys are unique between the Main and Staging servers.

.. figure:: ../assets/tutorials/dandi/api-tokens.png
:align: center
:alt: A pop-up asking for DANDI API keys

To get your API key, visit the :dandi-staging:`staging website <>` and click on the profile icon in the top-right corner. This will show a dropdown with a copy button, which will assign your API key to the clipboard.

.. figure:: ../assets/dandi/api-token-location.png
:align: center
:alt: DANDI staging API key added

Submit this to the Staging API Key input on the GUIDE.

.. figure:: ../assets/tutorials/dandi/api-token-added.png
:align: center
:alt: DANDI staging API key added


Once you have specified your Staging API Key, the **Dandiset** input will allow you to select any existing Dandiset associated with your account by ID (e.g., "207698") or name (e.g., "NWB GUIDE Test").

Continue to the next page to trigger your upload to the DANDI Archive.

.. figure:: ../assets/tutorials/dandi/dandiset-id.png
:align: center
:alt: DANDI upload page with Dandiset ID specified

Creating a Dandiset from the GUIDE
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
If you do not already own a Dandiset on staging, you will need to create one. Press the Create New Dandiset button to open a pop-up that guides you through the required fields for Dandiset creation.

.. figure:: ../assets/tutorials/dandi/create-dandiset.png
:align: center
:alt: Dandiset creation pop-up


Once this pop-up form is submitted, the Dandiset input will now contain your new Dandiset.

Final Review
CodyCBakerPhD marked this conversation as resolved.
Show resolved Hide resolved
------------
Once your upload to the DANDI Archive is complete, you will be able to review a quick overview of the associated Dandiset and a list of the uploaded files from this pipeline.
CodyCBakerPhD marked this conversation as resolved.
Show resolved Hide resolved

.. figure:: ../assets/tutorials/dandi/review-page.png
:align: center
:alt: DANDI upload review page

Congratulations on your first upload to the DANDI Archive from the GUIDE!
24 changes: 16 additions & 8 deletions src/renderer/src/stories/List.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,14 +156,18 @@ export class List extends LitElement {
const oldObject = this.object
this.#updateObject()

this.onChange({
items: this.#items,
object: this.object
},
{
items: oldList,
object: oldObject
})
if (this.#initialized) {

this.onChange({
items: this.#items,
object: this.object
},
{
items: oldList,
object: oldObject
})
}

this.requestUpdate('items', oldList)
}

Expand All @@ -177,6 +181,8 @@ export class List extends LitElement {

declare listStyles: any

#initialized = false

allowDrop = (ev) => ev.preventDefault();


Expand Down Expand Up @@ -230,6 +236,8 @@ export class List extends LitElement {

if (props.onChange) this.onChange = props.onChange

this.#initialized = true

}

add = (item: ListItemType) => {
Expand Down
23 changes: 16 additions & 7 deletions src/renderer/src/stories/pages/guided-mode/data/GuidedStructure.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,7 @@ export class GuidedStructurePage extends Page {
},
});

list = new List({
emptyMessage: defaultEmptyMessage,
onChange: () => (this.unsavedUpdates = "conversions"),
});
list = new List({ emptyMessage: defaultEmptyMessage });

addButton = new Button();

Expand Down Expand Up @@ -146,6 +143,8 @@ export class GuidedStructurePage extends Page {

this.list.emptyMessage = defaultEmptyMessage;

const items = [];

for (const [key, name] of Object.entries(interfaces)) {
let found = this.search.options?.find((item) => item.value === name);

Expand All @@ -158,17 +157,27 @@ export class GuidedStructurePage extends Page {
};
}

this.list.add({ ...found, key }); // Add previously selected items
items.push({ ...found, key });
}

const ogList = this.list;

this.list = new List({
items,
emptyMessage: defaultEmptyMessage,
onChange: () => (this.unsavedUpdates = "conversions"),
});

this.list.style.display = "inline-block";

ogList.replaceWith(this.list);

this.addButton.removeAttribute("hidden");
super.updated(); // Call if updating data
}

render() {
// Reset list
this.list.style.display = "inline-block";
this.list.clear();
this.addButton.setAttribute("hidden", "");

return html`
Expand Down
4 changes: 2 additions & 2 deletions tests/e2e/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { homedir } from 'node:os'
import { existsSync } from 'node:fs'

import paths from "../../paths.config.json" assert { type: "json" };
import { connect } from '../puppeteer';
import { connect as connectToElectron } from '../puppeteer';

// ------------------------------------------------------------------
// ------------------------ Path Definitions ------------------------
Expand Down Expand Up @@ -119,4 +119,4 @@ export const publish = dandiInfo.token ? true : false
if (!publish) console.log('No DANDI API key provided. Will skip dataset publication step...')


export const references = connect()
export const references = connectToElectron()
138 changes: 95 additions & 43 deletions tests/e2e/e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,19 @@ import { mkdirSync, existsSync, rmSync } from 'node:fs'
import { join } from 'node:path'

import * as config from './config'
import runWorkflow from './workflow'
import { evaluate, takeScreenshot } from './utils'
import runWorkflow, { uploadToDandi } from './workflow'
import { evaluate, takeScreenshot, to, toNextPage } from './utils'

const x = 250 // Sidebar size
const width = config.windowDims.width - x

const datasetScreenshotClip = {
x,
y: 0,
width,
height: 220
}


beforeAll(() => {

Expand All @@ -22,8 +33,6 @@ beforeAll(() => {

describe('E2E Test', () => {

// NOTE: This is where you should be connecting...

test('Ensure number of test pipelines starts at zero', async () => {

await sleep(500) // Wait for full notification to render
Expand All @@ -34,58 +43,49 @@ describe('E2E Test', () => {
expect(nPipelines).toBe(0)
})

describe('Manually run through the pipeline', async () => {

const datasetTestFunction = config.regenerateTestData ? test : test.skip

datasetTestFunction('Create tutorial dataset', async () => {
const datasetTestFunction = config.regenerateTestData ? test : test.skip

const x = 250 // Sidebar size
const width = config.windowDims.width - x
datasetTestFunction('Create tutorial dataset', async () => {

const screenshotClip = {
x,
y: 0,
width,
height: 220
}

await evaluate(async () => {

await evaluate(async () => {
// Transition to settings page
const dashboard = document.querySelector('nwb-dashboard')
dashboard.sidebar.select('settings')

// Transition to settings page
const dashboard = document.querySelector('nwb-dashboard')
dashboard.sidebar.select('settings')
// Generate test data
const page = dashboard.page
page.deleteTestData()
})

// Generate test data
const page = dashboard.page
page.deleteTestData()
})
await takeScreenshot('dataset-creation', 300, { clip: datasetScreenshotClip })

await takeScreenshot('dataset-creation', 300, { clip: screenshotClip })
const outputLocation = await evaluate(async () => {
const dashboard = document.querySelector('nwb-dashboard')
const page = dashboard.page
const outputLocation = await page.generateTestData()
page.requestUpdate()
return outputLocation
})

const outputLocation = await evaluate(async () => {
const dashboard = document.querySelector('nwb-dashboard')
const page = dashboard.page
const outputLocation = await page.generateTestData()
page.requestUpdate()
return outputLocation
})
// Take image after dataset generation
await takeScreenshot('dataset-created', 500, { clip: datasetScreenshotClip })

// Take image after dataset generation
await takeScreenshot('dataset-created', 500, { clip: screenshotClip })
expect(existsSync(outputLocation)).toBe(true)

expect(existsSync(outputLocation)).toBe(true)
// Navigate back to the home page
let pageId = await evaluate(() => {
const dashboard = document.querySelector('nwb-dashboard')
dashboard.sidebar.select('/')
return dashboard.page.info.id
})

// Navigate back to the home page
let pageId = await evaluate(() => {
const dashboard = document.querySelector('nwb-dashboard')
dashboard.sidebar.select('/')
return dashboard.page.info.id
})
expect(pageId).toBe('/')
})

expect(pageId).toBe('/')
})
describe('Run through several pipeline workflows', async () => {

describe('Complete a single-session workflow', async () => {
const subdirectory = 'single'
Expand All @@ -109,6 +109,58 @@ describe('E2E Test', () => {
})
})

describe('Upload the multi-session output to DANDI', async () => {

const subdirectory = 'dandi'

test('Restart pipeline', async () => {

await evaluate(async () => {
const pipelines = document.getElementById('guided-div-resume-progress-cards').children
const found = Array.from(pipelines).find(card => card.info.project.name === 'Multi Session Workflow')
console.log(found, Array.from(pipelines))
found.querySelector('button').click()
})

})

test('Update the workflow to allow DANDI upload', async () => {

await sleep(1000)
await to('//workflow')

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

for (let key in workflow) {
const input = page.form.getFormElement([ key ])
input.updateData(workflow[key])
}

page.form.requestUpdate() // Ensure the form is updated visually

await page.save()

}, { upload_to_dandi: true })

await toNextPage('structure') // Save data without a popup
await to('//conversion')

// Do not prompt to save
await evaluate(() => {
const dashboard = document.querySelector('nwb-dashboard')
const page = dashboard.page
page.unsavedUpdates = false
})

await to('//upload') // NOTE: It would be nice to avoid having to re-run the conversion...

})

uploadToDandi(subdirectory) // Upload to DANDI if the API key is provided

})

})

Expand Down
Loading
Loading