@openneuro/app 4.3.0-alpha.5 → 4.4.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openneuro/app",
3
- "version": "4.3.0-alpha.5",
3
+ "version": "4.4.0-alpha.0",
4
4
  "description": "React JS web frontend for the OpenNeuro platform.",
5
5
  "license": "MIT",
6
6
  "main": "public/client.js",
@@ -19,8 +19,8 @@
19
19
  "@elastic/apm-rum": "5.9.1",
20
20
  "@emotion/react": "11.6.0",
21
21
  "@emotion/styled": "11.6.0",
22
- "@openneuro/client": "^4.3.0-alpha.5",
23
- "@openneuro/components": "^4.3.0-alpha.5",
22
+ "@openneuro/client": "^4.4.0-alpha.0",
23
+ "@openneuro/components": "^4.4.0-alpha.0",
24
24
  "babel-runtime": "^6.26.0",
25
25
  "bids-validator": "1.8.8",
26
26
  "bytes": "^3.0.0",
@@ -116,5 +116,5 @@
116
116
  "publishConfig": {
117
117
  "access": "public"
118
118
  },
119
- "gitHead": "6ce7aecd560416297947a40014fe246df40a92f5"
119
+ "gitHead": "732d5f59ef4723e6236974679eabe3cc9c3ad09f"
120
120
  }
@@ -0,0 +1,60 @@
1
+ import React, { useState } from 'react'
2
+ import styled from '@emotion/styled'
3
+ import { ImportDatasetMutation } from '../refactor_2021/dataset/mutations/import-dataset'
4
+ import { useLocation } from 'react-router-dom'
5
+ import LoggedIn from '../refactor_2021/authentication/logged-in'
6
+ import LoggedOut from '../refactor_2021/authentication/logged-out'
7
+ import { testAffirmed } from '../refactor_2021/uploader/upload-disclaimer'
8
+ import { UploadDisclaimerInput } from '../refactor_2021/uploader/upload-disclaimer-input'
9
+
10
+ function useQuery() {
11
+ const { search } = useLocation()
12
+ return React.useMemo(() => new URLSearchParams(search), [search])
13
+ }
14
+
15
+ const ImportDatasetPageStyle = styled.div`
16
+ background: white;
17
+
18
+ .container {
19
+ max-width: 60em;
20
+ min-height: calc(100vh - 125px);
21
+ }
22
+ `
23
+
24
+ export const ImportDataset: React.VoidFunctionComponent = () => {
25
+ const url = useQuery().get('url')
26
+ const [affirmedDefaced, setAffirmedDefaced] = useState(false)
27
+ const [affirmedConsent, setAffirmedConsent] = useState(false)
28
+ return (
29
+ <ImportDatasetPageStyle>
30
+ <div className="container">
31
+ <h2>Import a dataset from remote URL</h2>
32
+ <p>
33
+ Use this page to import a new OpenNeuro dataset from{' '}
34
+ <a href="https://brainlife.io/ezbids/">ezBIDS</a>. After submitting an
35
+ import, please allow some time for processing and you will receive an
36
+ email notification when complete.
37
+ </p>
38
+ <LoggedIn>
39
+ <UploadDisclaimerInput
40
+ affirmedDefaced={affirmedDefaced}
41
+ affirmedConsent={affirmedConsent}
42
+ onChange={({ affirmedDefaced, affirmedConsent }): void => {
43
+ setAffirmedDefaced(affirmedDefaced)
44
+ setAffirmedConsent(affirmedConsent)
45
+ }}
46
+ />
47
+ <ImportDatasetMutation
48
+ url={url}
49
+ disabled={testAffirmed(affirmedDefaced, affirmedConsent)}
50
+ affirmedDefaced={affirmedDefaced}
51
+ affirmedConsent={affirmedConsent}
52
+ />
53
+ </LoggedIn>
54
+ <LoggedOut>
55
+ <p>Please sign in to continue.</p>
56
+ </LoggedOut>
57
+ </div>
58
+ </ImportDatasetPageStyle>
59
+ )
60
+ }
@@ -182,7 +182,6 @@ const DraftContainer: React.FC<DraftContainerProps> = ({ dataset }) => {
182
182
  <DatasetTools
183
183
  hasEdit={hasEdit}
184
184
  isPublic={dataset.public}
185
- isSnapshot={isSnapshot}
186
185
  datasetId={datasetId}
187
186
  isAdmin={isAdmin}
188
187
  hasSnapshot={dataset.snapshots.length !== 0}
@@ -236,7 +235,7 @@ const DraftContainer: React.FC<DraftContainerProps> = ({ dataset }) => {
236
235
  heading="Authors"
237
236
  description={description.Authors}
238
237
  editMode={hasEdit}>
239
- {description.Authors?.length ? description.Authors : ['N/A']}
238
+ {description?.Authors?.length ? description.Authors : ['N/A']}
240
239
  </EditDescriptionList>
241
240
 
242
241
  {summary && (
@@ -1,5 +1,4 @@
1
1
  import React, { FC, useContext } from 'react'
2
- import { useLocation } from 'react-router-dom'
3
2
  import { gql } from '@apollo/client'
4
3
  import { Mutation } from '@apollo/client/react/components'
5
4
  import { datasetCacheId } from '../../../datalad/mutations/cache-id.js'
@@ -45,16 +44,11 @@ export const FollowDataset: FC<FollowDatasetProps> = ({
45
44
  profile,
46
45
  followers,
47
46
  }) => {
48
- const { setUserModalOpen, setLoginOptions } = useContext(UserModalOpenCtx)
49
- const location = useLocation()
47
+ const { setUserModalOpen } = useContext(UserModalOpenCtx)
50
48
  const handleToggle = followDataset => () => {
51
49
  if (!profile) {
52
50
  // if user is not logged in, give them the option to do so
53
51
  // then redirect back to this page
54
- setLoginOptions(prevState => ({
55
- ...prevState,
56
- redirect: `${location.pathname}`,
57
- }))
58
52
  setUserModalOpen(true)
59
53
  } else {
60
54
  followDataset({ variables: { datasetId } })
@@ -0,0 +1,97 @@
1
+ import React, { FC, useState } from 'react'
2
+ import { gql, useMutation, useApolloClient } from '@apollo/client'
3
+ import { Button } from '@openneuro/components/button'
4
+ import { createDataset } from '../../uploader/upload-mutation'
5
+ import styled from '@emotion/styled'
6
+
7
+ export const IMPORT_DATASET = gql`
8
+ mutation importRemoteDataset($datasetId: ID!, $url: String!) {
9
+ importRemoteDataset(datasetId: $datasetId, url: $url)
10
+ }
11
+ `
12
+
13
+ const StatusRow = styled.div`
14
+ margin-top: 1em;
15
+ `
16
+
17
+ const URLInput = styled.input`
18
+ width: 100%;
19
+ `
20
+
21
+ interface ImportDatasetMutationProps {
22
+ url: string
23
+ affirmedDefaced: boolean
24
+ affirmedConsent: boolean
25
+ disabled?: boolean | null
26
+ }
27
+
28
+ export const ImportDatasetMutation: FC<ImportDatasetMutationProps> = ({
29
+ url,
30
+ disabled,
31
+ affirmedDefaced,
32
+ affirmedConsent,
33
+ }) => {
34
+ const [importStarted, setImportStarted] = useState(false)
35
+ const [importFailed, setImportFailed] = useState(false)
36
+ const [ImportDataset] = useMutation(IMPORT_DATASET)
37
+ const apolloClient = useApolloClient()
38
+
39
+ let status = (
40
+ <Button
41
+ className="btn-modal-action"
42
+ primary={true}
43
+ label="Start Import"
44
+ size="medium"
45
+ disabled={disabled}
46
+ onClick={async () => {
47
+ const createDatasetMutation = createDataset(apolloClient)
48
+ const datasetId = await createDatasetMutation({
49
+ affirmedDefaced,
50
+ affirmedConsent,
51
+ })
52
+ try {
53
+ await ImportDataset({
54
+ variables: { datasetId, url },
55
+ })
56
+ setImportStarted(true)
57
+ } catch (err) {
58
+ setImportFailed(true)
59
+ }
60
+ }}
61
+ />
62
+ )
63
+
64
+ // User can start import
65
+
66
+ // Import is running successfully
67
+ if (importStarted && !importFailed) {
68
+ status = (
69
+ <p>
70
+ This import has been started and you will receive an email when it is
71
+ complete.
72
+ </p>
73
+ )
74
+ }
75
+
76
+ if (importFailed) {
77
+ status = (
78
+ <p>
79
+ An error was encountered importing this URL. This may indicate the URL
80
+ is inaccessible or is not the correct zip format. Please contact support
81
+ if you have verified the bundle is correct and still experience this
82
+ error.
83
+ </p>
84
+ )
85
+ }
86
+
87
+ return (
88
+ <>
89
+ <label>
90
+ Source URL
91
+ <URLInput type="text" disabled value={url} id="import-url" />
92
+ </label>
93
+
94
+ <StatusRow>{status}</StatusRow>
95
+ </>
96
+ )
97
+ }
@@ -1,5 +1,4 @@
1
1
  import React, { FC, useContext } from 'react'
2
- import { useLocation } from 'react-router-dom'
3
2
  import { gql } from '@apollo/client'
4
3
  import { Mutation } from '@apollo/client/react/components'
5
4
  import { datasetCacheId } from '../../../datalad/mutations/cache-id.js'
@@ -44,16 +43,11 @@ export const StarDataset: FC<StarDatasetProps> = ({
44
43
  profile,
45
44
  stars,
46
45
  }) => {
47
- const { setUserModalOpen, setLoginOptions } = useContext(UserModalOpenCtx)
48
- const location = useLocation()
46
+ const { setUserModalOpen } = useContext(UserModalOpenCtx)
49
47
  const handleToggle = starDataset => () => {
50
48
  if (!profile) {
51
49
  // if user is not logged in, give them the option to do so
52
50
  // then redirect back to this page
53
- setLoginOptions(prevState => ({
54
- ...prevState,
55
- redirect: `${location.pathname}`,
56
- }))
57
51
  setUserModalOpen(true)
58
52
  } else {
59
53
  starDataset({ variables: { datasetId } })
@@ -172,7 +172,7 @@ const SnapshotContainer: React.FC<SnapshotContainerProps> = ({
172
172
  hasEdit={hasEdit}
173
173
  isPublic={dataset.public}
174
174
  datasetId={datasetId}
175
- isSnapshot={true}
175
+ snapshotId={snapshot.tag}
176
176
  isAdmin={isAdmin}
177
177
  isDatasetAdmin={isDatasetAdmin}
178
178
  />
@@ -214,7 +214,7 @@ const SnapshotContainer: React.FC<SnapshotContainerProps> = ({
214
214
  <MetaDataBlock
215
215
  heading="Authors"
216
216
  item={
217
- description.Authors.length
217
+ description?.Authors?.length
218
218
  ? description.Authors.join(', ')
219
219
  : 'N/A'
220
220
  }
@@ -14,6 +14,7 @@ import ErrorRoute from '../errors/errorRoute'
14
14
  import { PETRedirect } from '../pet/redirect'
15
15
  import Citation from '../pages/citation-page'
16
16
  import FourOFourPage from '../errors/404page'
17
+ import { ImportDataset } from '../pages/import-dataset'
17
18
 
18
19
  const Routes: React.VoidFunctionComponent = () => (
19
20
  <Switch>
@@ -26,6 +27,7 @@ const Routes: React.VoidFunctionComponent = () => (
26
27
  <Route path="/error" component={ErrorRoute} />
27
28
  <Route path="/pet" component={PETRedirect} />
28
29
  <Route path="/cite" component={Citation} />
30
+ <Route path="/import" component={ImportDataset} />
29
31
  <Redirect from="/public" to="/search" />
30
32
  <Redirect from="/saved" to="/search?bookmarks" />
31
33
  <Redirect from="/dashboard" to="/search?mydatasets" />
@@ -0,0 +1,87 @@
1
+ import React from 'react'
2
+ import styled from '@emotion/styled'
3
+
4
+ interface UploadDisclaimerInputProps {
5
+ affirmedDefaced: boolean
6
+ affirmedConsent: boolean
7
+ onChange: ({
8
+ affirmedDefaced,
9
+ affirmedConsent,
10
+ }: {
11
+ affirmedDefaced: boolean
12
+ affirmedConsent: boolean
13
+ }) => void
14
+ }
15
+
16
+ const DisclaimerLabel = styled.label`
17
+ display: block;
18
+ font-weight: bold;
19
+ margin-bottom: 20px;
20
+ border-top: 1px solid;
21
+ padding-top: 10px;
22
+ `
23
+
24
+ export const UploadDisclaimerInput: React.FunctionComponent<UploadDisclaimerInputProps> =
25
+ ({ affirmedDefaced, affirmedConsent, onChange }) => {
26
+ return (
27
+ <>
28
+ <h4>
29
+ By uploading this dataset to OpenNeuro I agree to the following
30
+ conditions:
31
+ </h4>
32
+ <p>
33
+ I am the owner of this dataset and have any necessary ethics
34
+ permissions to share the data publicly. This dataset does not include
35
+ any identifiable personal health information as defined by the{' '}
36
+ <a href="https://www.hhs.gov/hipaa/for-professionals/privacy/laws-regulations/">
37
+ Health Insurance Portability and Accountability Act of 1996
38
+ </a>{' '}
39
+ (including names, zip codes, dates of birth, acquisition dates, etc).
40
+ I agree to destroy any key linking the personal identity of research
41
+ participants to the subject codes used in the dataset.
42
+ </p>
43
+ <p>
44
+ I agree that this dataset will become publicly available under a{' '}
45
+ <a href="https://wiki.creativecommons.org/wiki/CC0">
46
+ Creative Commons CC0
47
+ </a>{' '}
48
+ license after a grace period of 36 months counted from the date of the
49
+ first snapshot creation for this dataset. You will be able to apply
50
+ for up to two 6 month extensions to increase the grace period in case
51
+ the publication of a corresponding paper takes longer than expected.
52
+ See <a href="/faq">FAQ</a> for details.
53
+ </p>
54
+ <p>This dataset is not subject to GDPR protections.</p>
55
+ <p>
56
+ Generally, data should only be uploaded to a single data archive. In
57
+ the rare cases where it is necessary to upload the data to two
58
+ databases (such as the NIMH Data Archive), I agree to ensure that the
59
+ datasets are harmonized across archives.
60
+ </p>
61
+ <p>Please affirm one of the following:</p>
62
+ <DisclaimerLabel>
63
+ <input
64
+ type="checkbox"
65
+ onChange={(): void =>
66
+ onChange({ affirmedDefaced: !affirmedDefaced, affirmedConsent })
67
+ }
68
+ defaultChecked={affirmedDefaced}
69
+ />
70
+ &nbsp; All structural scans have been defaced, obscuring any tissue on
71
+ or near the face that could potentially be used to reconstruct the
72
+ facial structure.
73
+ </DisclaimerLabel>
74
+ <DisclaimerLabel>
75
+ <input
76
+ type="checkbox"
77
+ onChange={(): void =>
78
+ onChange({ affirmedDefaced, affirmedConsent: !affirmedConsent })
79
+ }
80
+ defaultChecked={affirmedConsent}
81
+ />
82
+ &nbsp; I have explicit participant consent and ethical authorization
83
+ to publish structural scans without defacing.
84
+ </DisclaimerLabel>
85
+ </>
86
+ )
87
+ }
@@ -1,5 +1,15 @@
1
1
  import React, { useState } from 'react'
2
2
  import UploaderContext from './uploader-context.js'
3
+ import { UploadDisclaimerInput } from './upload-disclaimer-input'
4
+
5
+ /**
6
+ * Defacing/consent input logic
7
+ */
8
+ export const testAffirmed = (affirmedDefaced, affirmedConsent) =>
9
+ !(
10
+ (affirmedDefaced && !affirmedConsent) ||
11
+ (!affirmedDefaced && affirmedConsent)
12
+ )
3
13
 
4
14
  const UploadDisclaimer = () => {
5
15
  const [affirmedDefaced, setAffirmedDefaced] = useState(false)
@@ -8,60 +18,14 @@ const UploadDisclaimer = () => {
8
18
  <UploaderContext.Consumer>
9
19
  {uploader => (
10
20
  <div className="disclaimer fade-in">
11
- <h4>
12
- By uploading this dataset to OpenNeuro I agree to the following
13
- conditions:
14
- </h4>
15
- <p>
16
- I am the owner of this dataset and have any necessary ethics
17
- permissions to share the data publicly. This dataset does not
18
- include any identifiable personal health information as defined by
19
- the{' '}
20
- <a href="https://www.hhs.gov/hipaa/for-professionals/privacy/laws-regulations/">
21
- Health Insurance Portability and Accountability Act of 1996
22
- </a>{' '}
23
- (including names, zip codes, dates of birth, acquisition dates,
24
- etc). I agree to destroy any key linking the personal identity of
25
- research participants to the subject codes used in the dataset.
26
- </p>
27
- <p>
28
- I agree that this dataset will become publicly available under a{' '}
29
- <a href="https://wiki.creativecommons.org/wiki/CC0">
30
- Creative Commons CC0
31
- </a>{' '}
32
- license after a grace period of 36 months counted from the date of
33
- the first snapshot creation for this dataset. You will be able to
34
- apply for up to two 6 month extensions to increase the grace period
35
- in case the publication of a corresponding paper takes longer than
36
- expected. See <a href="/faq">FAQ</a> for details.
37
- </p>
38
- <p>This dataset is not subject to GDPR protections.</p>
39
- <p>
40
- Generally, data should only be uploaded to a single data archive. In
41
- the rare cases where it is necessary to upload the data to two
42
- databases (such as the NIMH Data Archive), I agree to ensure that
43
- the datasets are harmonized across archives.
44
- </p>
45
- <p>Please affirm one of the following:</p>
46
- <label>
47
- <input
48
- type="checkbox"
49
- onChange={() => setAffirmedDefaced(!affirmedDefaced)}
50
- defaultChecked={affirmedDefaced}
51
- />
52
- &nbsp; All structural scans have been defaced, obscuring any tissue
53
- on or near the face that could potentially be used to reconstruct
54
- the facial structure.
55
- </label>
56
- <label>
57
- <input
58
- type="checkbox"
59
- onChange={() => setAffirmedConsent(!affirmedConsent)}
60
- defaultChecked={affirmedConsent}
61
- />
62
- &nbsp; I have explicit participant consent and ethical authorization
63
- to publish structural scans without defacing.
64
- </label>
21
+ <UploadDisclaimerInput
22
+ affirmedDefaced={affirmedDefaced}
23
+ affirmedConsent={affirmedConsent}
24
+ onChange={({ affirmedDefaced, affirmedConsent }) => {
25
+ setAffirmedDefaced(affirmedDefaced)
26
+ setAffirmedConsent(affirmedConsent)
27
+ }}
28
+ />
65
29
  <span className="message">
66
30
  <button
67
31
  className="fileupload-btn btn-blue"
@@ -73,13 +37,7 @@ const UploadDisclaimer = () => {
73
37
  })
74
38
  uploader.upload({ affirmedDefaced, affirmedConsent })
75
39
  }}
76
- disabled={
77
- !(
78
- (affirmedDefaced && !affirmedConsent) ||
79
- (!affirmedDefaced && affirmedConsent)
80
- )
81
- }
82
- >
40
+ disabled={testAffirmed(affirmedDefaced, affirmedConsent)}>
83
41
  I Agree
84
42
  </button>
85
43
  </span>
@@ -1,6 +1,7 @@
1
1
  import React, { createContext, useState, FC, ReactNode } from 'react'
2
2
  import { UserLoginModal } from '@openneuro/components/modal'
3
3
  import loginUrls from './authentication/loginUrls'
4
+ import { useLocation } from 'react-router-dom'
4
5
 
5
6
  export const UserModalOpenCtx = createContext(null)
6
7
 
@@ -12,20 +13,16 @@ export const UserModalOpenProvider: FC<UserModalOpenProviderProps> = ({
12
13
  children,
13
14
  }) => {
14
15
  const [userModalOpen, setUserModalOpen] = useState(false)
15
- const [loginOptions, setLoginOptions] = useState({
16
- redirect: null
17
- })
18
16
  const toggle = (): void => {
19
17
  setUserModalOpen(prevState => !prevState)
20
18
  }
21
19
  return (
22
- <UserModalOpenCtx.Provider value={{ userModalOpen, setUserModalOpen, setLoginOptions }}>
20
+ <UserModalOpenCtx.Provider value={{ userModalOpen, setUserModalOpen }}>
23
21
  {children}
24
22
  <UserLoginModal
25
23
  isOpen={userModalOpen}
26
24
  toggle={toggle}
27
25
  loginUrls={loginUrls}
28
- redirectPathParam={loginOptions.redirect}
29
26
  />
30
27
  </UserModalOpenCtx.Provider>
31
28
  )