@openneuro/app 4.22.0 → 4.23.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 +12 -10
- package/src/client.jsx +4 -3
- package/src/scripts/components/data-table.tsx +25 -3
- package/src/scripts/dataset/__tests__/__snapshots__/snapshot-container.spec.tsx.snap +35 -33
- package/src/scripts/dataset/components/DatasetTools.tsx +1 -0
- package/src/scripts/dataset/routes/derivatives.tsx +8 -0
- package/src/scripts/scss/dataset/dataset-page.scss +3 -0
- package/src/scripts/utils/__tests__/__snapshots__/markdown.spec.tsx.snap +27 -15
- package/src/scripts/utils/markdown.tsx +6 -2
- package/src/scripts/validation/validation-results.issues.issue.jsx +1 -1
- package/src/scripts/validation/validation-results.issues.jsx +23 -18
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openneuro/app",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.23.0",
|
|
4
4
|
"description": "React JS web frontend for the OpenNeuro platform.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "public/client.js",
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
"@emotion/react": "11.11.1",
|
|
21
21
|
"@emotion/styled": "11.11.0",
|
|
22
22
|
"@niivue/niivue": "0.34.0",
|
|
23
|
-
"@openneuro/client": "^4.
|
|
24
|
-
"@openneuro/components": "^4.
|
|
23
|
+
"@openneuro/client": "^4.23.0",
|
|
24
|
+
"@openneuro/components": "^4.23.0",
|
|
25
25
|
"@tanstack/react-table": "^8.9.3",
|
|
26
26
|
"bids-validator": "1.13.0",
|
|
27
27
|
"bytes": "^3.0.0",
|
|
@@ -32,29 +32,31 @@
|
|
|
32
32
|
"email-validator": "^2.0.4",
|
|
33
33
|
"graphql": "16.8.1",
|
|
34
34
|
"jwt-decode": "^2.2.0",
|
|
35
|
-
"markdown-to-jsx": "^7.4.0",
|
|
36
35
|
"pluralize": "8.0.0",
|
|
37
36
|
"prop-types": "^15.6.0",
|
|
38
|
-
"react": "^
|
|
37
|
+
"react": "^18.2.0",
|
|
39
38
|
"react-cookie": "4.0.3",
|
|
40
39
|
"react-copy-to-clipboard": "^5.0.1",
|
|
41
|
-
"react-dom": "^
|
|
40
|
+
"react-dom": "^18.2.0",
|
|
42
41
|
"react-helmet": "6.1.0",
|
|
42
|
+
"react-markdown": "^9.0.1",
|
|
43
43
|
"react-router-dom": "6.3.0",
|
|
44
44
|
"react-toastify": "6.0.9",
|
|
45
45
|
"react-usestateref": "^1.0.8",
|
|
46
|
+
"rehype-raw": "^7.0.0",
|
|
47
|
+
"remark-gfm": "^4.0.0",
|
|
46
48
|
"semver": "^5.5.0",
|
|
47
49
|
"subscriptions-transport-ws": "0.11.0",
|
|
48
50
|
"universal-cookie": "^4.0.4"
|
|
49
51
|
},
|
|
50
52
|
"devDependencies": {
|
|
51
|
-
"@testing-library/jest-dom": "6.
|
|
52
|
-
"@testing-library/react": "^
|
|
53
|
+
"@testing-library/jest-dom": "6.4.2",
|
|
54
|
+
"@testing-library/react": "^14.2.1",
|
|
53
55
|
"@types/dompurify": "^3",
|
|
54
56
|
"@types/jsdom": "^16",
|
|
55
57
|
"@types/node": "18.11.9",
|
|
56
58
|
"@types/react": "^17.0.8",
|
|
57
|
-
"@types/react-dom": "^
|
|
59
|
+
"@types/react-dom": "^18.2.19",
|
|
58
60
|
"@types/react-router-dom": "5.3.3",
|
|
59
61
|
"@types/testing-library__jest-dom": "5.14.5",
|
|
60
62
|
"core-js": "3.25.1",
|
|
@@ -72,5 +74,5 @@
|
|
|
72
74
|
"publishConfig": {
|
|
73
75
|
"access": "public"
|
|
74
76
|
},
|
|
75
|
-
"gitHead": "
|
|
77
|
+
"gitHead": "e86df78635d7ac6cfa03a2add5760b8539103583"
|
|
76
78
|
}
|
package/src/client.jsx
CHANGED
|
@@ -6,7 +6,7 @@ import "./scripts/apm.js"
|
|
|
6
6
|
import { ApolloProvider, InMemoryCache } from "@apollo/client"
|
|
7
7
|
import { createClient } from "@openneuro/client"
|
|
8
8
|
import React from "react"
|
|
9
|
-
import
|
|
9
|
+
import { createRoot } from "react-dom/client"
|
|
10
10
|
import { BrowserRouter, Route, Routes } from "react-router-dom"
|
|
11
11
|
import App from "./scripts/app"
|
|
12
12
|
import Index from "./scripts/index"
|
|
@@ -18,7 +18,9 @@ import "@openneuro/components/page/page.scss"
|
|
|
18
18
|
|
|
19
19
|
gtag.initialize(config.analytics.trackingIds)
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
const mainElement = document.getElementById("main")
|
|
22
|
+
const container = createRoot(mainElement)
|
|
23
|
+
container.render(
|
|
22
24
|
<App>
|
|
23
25
|
<ApolloProvider
|
|
24
26
|
client={createClient(`${config.url}/crn/graphql`, {
|
|
@@ -41,5 +43,4 @@ ReactDOM.render(
|
|
|
41
43
|
</BrowserRouter>
|
|
42
44
|
</ApolloProvider>
|
|
43
45
|
</App>,
|
|
44
|
-
document.getElementById("main"),
|
|
45
46
|
)
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
useReactTable,
|
|
9
9
|
} from "@tanstack/react-table"
|
|
10
10
|
import styled from "@emotion/styled"
|
|
11
|
-
import { format, isValid, parseISO } from "date-fns"
|
|
11
|
+
import { format, isValid, parse, parseISO } from "date-fns"
|
|
12
12
|
|
|
13
13
|
interface DataTableProps {
|
|
14
14
|
data: any[]
|
|
@@ -33,10 +33,32 @@ const TD = styled.td`
|
|
|
33
33
|
padding: 3px;
|
|
34
34
|
`
|
|
35
35
|
|
|
36
|
+
function extractDateString(dateString) {
|
|
37
|
+
const formats = [
|
|
38
|
+
"yyyy-MM-dd", // ISO 8601
|
|
39
|
+
"yyyy-MM-ddTHH:mm:ss", // ISO 8601 with time
|
|
40
|
+
"MM/dd/yyyy", // US (M/D/YYYY)
|
|
41
|
+
"dd/MM/yyyy", // European (D/M/YYYY)
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
for (const format of formats) {
|
|
45
|
+
const parsedDate = parse(dateString, format, new Date())
|
|
46
|
+
if (isValid(parsedDate)) {
|
|
47
|
+
return parsedDate
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return false
|
|
52
|
+
}
|
|
53
|
+
|
|
36
54
|
function CellFormat(props): any {
|
|
37
55
|
const value = props.getValue()
|
|
38
|
-
|
|
39
|
-
|
|
56
|
+
let extractedDate
|
|
57
|
+
if (typeof value === "string") {
|
|
58
|
+
extractedDate = extractDateString(value)
|
|
59
|
+
}
|
|
60
|
+
if (extractedDate instanceof Date) {
|
|
61
|
+
return format(extractedDate, "yyyy-MM-dd")
|
|
40
62
|
} else if (typeof value === "string" && /^ds[0-9]{6}$/.exec(value)) {
|
|
41
63
|
return <a href={`/datasets/${value}`}>{value}</a>
|
|
42
64
|
} else if (Array.isArray(value)) {
|
|
@@ -498,46 +498,48 @@ exports[`SnapshotContainer component > renders successfully 1`] = `
|
|
|
498
498
|
<div>
|
|
499
499
|
<article>
|
|
500
500
|
<section>
|
|
501
|
-
<
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
).
|
|
501
|
+
<p>
|
|
502
|
+
This dataset was obtained from the OpenfMRI project (
|
|
503
|
+
<a
|
|
504
|
+
href="http://www.openfmri.org"
|
|
505
|
+
>
|
|
506
|
+
http://www.openfmri.org
|
|
507
|
+
</a>
|
|
508
|
+
).
|
|
510
509
|
Accession #: ds003
|
|
511
510
|
Description: Rhyme judgment
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
511
|
+
</p>
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
<p>
|
|
515
|
+
Release history:
|
|
515
516
|
10/06/2011: initial release
|
|
516
517
|
3/21/2013: Updated release with QA information
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
This dataset is made available under the Public Domain Dedication and License
|
|
520
|
-
v1.0, whose full text can be found at
|
|
518
|
+
</p>
|
|
519
|
+
|
|
521
520
|
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
.
|
|
528
|
-
|
|
521
|
+
<p>
|
|
522
|
+
This dataset is made available under the Public Domain Dedication and License
|
|
523
|
+
v1.0, whose full text can be found at
|
|
524
|
+
|
|
525
|
+
<a
|
|
526
|
+
href="http://www.opendatacommons.org/licenses/pddl/1.0/"
|
|
527
|
+
>
|
|
528
|
+
http://www.opendatacommons.org/licenses/pddl/1.0/
|
|
529
|
+
</a>
|
|
530
|
+
.
|
|
531
|
+
We hope that all users will follow the ODC Attribution/Share-Alike
|
|
529
532
|
Community Norms (
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
in particular, while not legally required, we hope that all users
|
|
537
|
-
of the data will acknowledge the OpenfMRI project and NSF Grant
|
|
533
|
+
<a
|
|
534
|
+
href="http://www.opendatacommons.org/norms/odc-by-sa/"
|
|
535
|
+
>
|
|
536
|
+
http://www.opendatacommons.org/norms/odc-by-sa/
|
|
537
|
+
</a>
|
|
538
|
+
);
|
|
539
|
+
in particular, while not legally required, we hope that all users
|
|
540
|
+
of the data will acknowledge the OpenfMRI project and NSF Grant
|
|
538
541
|
OCI-1131441 (R. Poldrack, PI) in any publications.
|
|
539
|
-
|
|
540
|
-
</div>
|
|
542
|
+
</p>
|
|
541
543
|
</section>
|
|
542
544
|
</article>
|
|
543
545
|
</div>
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import React from "react"
|
|
2
|
+
import { Navigate, useParams } from "react-router-dom"
|
|
2
3
|
import DownloadS3Derivative from "../download/download-derivative-s3"
|
|
3
4
|
import DownloadDataLadDerivative from "../download/download-derivative-datalad"
|
|
4
5
|
import { DatasetPageBorder } from "./styles/dataset-page-border"
|
|
5
6
|
import { HeaderRow3 } from "./styles/header-row"
|
|
7
|
+
import { useAgreement } from "../../components/agreement"
|
|
6
8
|
|
|
7
9
|
interface DerivativeElementProps {
|
|
8
10
|
name: string
|
|
@@ -38,6 +40,12 @@ interface DerivativesProps {
|
|
|
38
40
|
}
|
|
39
41
|
|
|
40
42
|
const Derivatives = ({ derivatives }: DerivativesProps): JSX.Element => {
|
|
43
|
+
const { datasetId, tag: snapshotTag } = useParams()
|
|
44
|
+
const [agreed] = useAgreement()
|
|
45
|
+
// If the derivatives page is directly visited without the agreement, return to the dataset page
|
|
46
|
+
if (!agreed) {
|
|
47
|
+
return <Navigate to={`/datasets/${datasetId}`} replace={true} />
|
|
48
|
+
}
|
|
41
49
|
return (
|
|
42
50
|
<DatasetPageBorder>
|
|
43
51
|
<HeaderRow3>Available Derivatives</HeaderRow3>
|
|
@@ -2,38 +2,46 @@
|
|
|
2
2
|
|
|
3
3
|
exports[`Test <Markdown> component > allows a href with certain protocols 1`] = `
|
|
4
4
|
<DocumentFragment>
|
|
5
|
-
<
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
<p>
|
|
6
|
+
<a
|
|
7
|
+
href="https://example.com"
|
|
8
|
+
>
|
|
9
|
+
Example link that should work.
|
|
10
|
+
</a>
|
|
11
|
+
</p>
|
|
10
12
|
</DocumentFragment>
|
|
11
13
|
`;
|
|
12
14
|
|
|
13
15
|
exports[`Test <Markdown> component > does not allow href with unknown protocols 1`] = `
|
|
14
16
|
<DocumentFragment>
|
|
15
|
-
<
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
<p>
|
|
18
|
+
<a>
|
|
19
|
+
Example link that should not work.
|
|
20
|
+
</a>
|
|
21
|
+
</p>
|
|
18
22
|
</DocumentFragment>
|
|
19
23
|
`;
|
|
20
24
|
|
|
21
25
|
exports[`Test <Markdown> component > filters close-break tags 1`] = `
|
|
22
26
|
<DocumentFragment>
|
|
23
|
-
<
|
|
27
|
+
<p>
|
|
24
28
|
<br />
|
|
25
29
|
sample text
|
|
26
30
|
<br />
|
|
27
|
-
</
|
|
31
|
+
</p>
|
|
28
32
|
</DocumentFragment>
|
|
29
33
|
`;
|
|
30
34
|
|
|
31
35
|
exports[`Test <Markdown> component > filters out disallowed tags 1`] = `
|
|
32
36
|
<DocumentFragment>
|
|
33
37
|
<ul>
|
|
38
|
+
|
|
39
|
+
|
|
34
40
|
<li>
|
|
35
41
|
Markdown document
|
|
36
42
|
</li>
|
|
43
|
+
|
|
44
|
+
|
|
37
45
|
</ul>
|
|
38
46
|
</DocumentFragment>
|
|
39
47
|
`;
|
|
@@ -41,17 +49,21 @@ exports[`Test <Markdown> component > filters out disallowed tags 1`] = `
|
|
|
41
49
|
exports[`Test <Markdown> component > safely handles broken HTML tags 1`] = `
|
|
42
50
|
<DocumentFragment>
|
|
43
51
|
<ul>
|
|
52
|
+
|
|
53
|
+
|
|
44
54
|
<li>
|
|
45
55
|
Markdown document
|
|
46
56
|
|
|
47
57
|
<br />
|
|
48
58
|
<br />
|
|
49
|
-
<ul>
|
|
50
|
-
<li>
|
|
51
|
-
test content
|
|
52
|
-
</li>
|
|
53
|
-
</ul>
|
|
54
59
|
</li>
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
<li>
|
|
63
|
+
test content
|
|
64
|
+
</li>
|
|
65
|
+
|
|
66
|
+
|
|
55
67
|
</ul>
|
|
56
68
|
</DocumentFragment>
|
|
57
69
|
`;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import React from "react"
|
|
2
|
-
import
|
|
2
|
+
import ReactMarkdown from "react-markdown"
|
|
3
3
|
import DOMPurify from "dompurify"
|
|
4
|
+
import rehypeRaw from "rehype-raw"
|
|
5
|
+
import remarkGfm from "remark-gfm"
|
|
4
6
|
|
|
5
7
|
interface MarkdownProps {
|
|
6
8
|
children: string
|
|
@@ -161,7 +163,9 @@ export function Markdown({ children }: MarkdownProps) {
|
|
|
161
163
|
})
|
|
162
164
|
return (
|
|
163
165
|
<>
|
|
164
|
-
<
|
|
166
|
+
<ReactMarkdown rehypePlugins={[rehypeRaw]} remarkPlugins={[remarkGfm]}>
|
|
167
|
+
{sanitizedMarkdown}
|
|
168
|
+
</ReactMarkdown>
|
|
165
169
|
</>
|
|
166
170
|
)
|
|
167
171
|
}
|
|
@@ -31,23 +31,28 @@ class Issues extends React.Component {
|
|
|
31
31
|
</span>
|
|
32
32
|
)
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
// Schema validator returns multiple files here
|
|
34
|
+
let subErrors = []
|
|
35
|
+
if (issue.files instanceof Map) {
|
|
36
|
+
// Schema validator returns multiple files here as a map
|
|
37
37
|
// map those to the old sub-issue model to display them
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
38
|
+
let index = 0
|
|
39
|
+
for (const [path, fObj] of issue.files) {
|
|
40
|
+
const error = {
|
|
41
|
+
reason: path,
|
|
42
|
+
file: fObj,
|
|
43
|
+
}
|
|
44
|
+
subErrors.push(
|
|
45
|
+
(<Issue
|
|
46
|
+
type={this.props.issueType}
|
|
47
|
+
error={error}
|
|
48
|
+
index={index}
|
|
49
|
+
key={index}
|
|
50
|
+
/>),
|
|
51
|
+
)
|
|
52
|
+
index += 1
|
|
53
|
+
}
|
|
54
|
+
} else {
|
|
55
|
+
subErrors = Array.from(issue.files).map((error, index2) => {
|
|
51
56
|
return error
|
|
52
57
|
? (
|
|
53
58
|
<Issue
|
|
@@ -59,8 +64,8 @@ class Issues extends React.Component {
|
|
|
59
64
|
/>
|
|
60
65
|
)
|
|
61
66
|
: null
|
|
62
|
-
}
|
|
63
|
-
}
|
|
67
|
+
})
|
|
68
|
+
}
|
|
64
69
|
|
|
65
70
|
if (issue.additionalFileCount > 0) {
|
|
66
71
|
subErrors.push(
|