@openneuro/app 4.31.2 → 4.32.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 +4 -4
- package/src/@types/custom.d.ts +6 -0
- package/src/scripts/common/content/image-attribution.tsx +72 -0
- package/src/scripts/datalad/dataset/dataset-query-fragments.js +6 -4
- package/src/scripts/dataset/__tests__/__snapshots__/snapshot-container.spec.tsx.snap +3 -13
- package/src/scripts/dataset/components/ValidationBlock.tsx +11 -4
- package/src/scripts/dataset/components/__tests__/ValidationBlock.spec.tsx +7 -4
- package/src/scripts/dataset/draft-container.tsx +1 -1
- package/src/scripts/dataset/snapshot-container.tsx +1 -1
- package/src/scripts/pages/image-attribution.tsx +32 -0
- package/src/scripts/routes.tsx +2 -0
- package/src/scripts/search/__tests__/search-container.spec.tsx +8 -0
- package/src/scripts/search/filters-block-container.tsx +24 -5
- package/src/scripts/search/initial-search-params.tsx +6 -0
- package/src/scripts/search/inputs/__tests__/{nihselect.spec.tsx → initiative-select.spec.tsx} +6 -4
- package/src/scripts/search/inputs/index.ts +2 -2
- package/src/scripts/search/inputs/{nih-select.tsx → initiative-select.tsx} +23 -11
- package/src/scripts/search/inputs/show-datasets-radios.tsx +24 -19
- package/src/scripts/search/search-container.tsx +34 -10
- package/src/scripts/search/search-routes.tsx +4 -0
- package/src/scripts/validation-legacy/validation-legacy-query.ts +26 -0
- package/src/scripts/validation-legacy/{validation-results.jsx → validation-results.tsx} +35 -36
- package/src/scripts/validation-legacy/validation-status.jsx +36 -22
- package/src/scripts/validation-legacy/validation.jsx +6 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openneuro/app",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.32.0",
|
|
4
4
|
"description": "React JS web frontend for the OpenNeuro platform.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "public/client.js",
|
|
@@ -16,11 +16,11 @@
|
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"@apollo/client": "3.11.8",
|
|
18
18
|
"@artsy/fresnel": "^1.3.1",
|
|
19
|
-
"@bids/validator": "npm:@jsr/bids__validator@^2.0.
|
|
19
|
+
"@bids/validator": "npm:@jsr/bids__validator@^2.0.3",
|
|
20
20
|
"@emotion/react": "11.11.1",
|
|
21
21
|
"@emotion/styled": "11.11.0",
|
|
22
22
|
"@niivue/niivue": "0.45.1",
|
|
23
|
-
"@openneuro/components": "^4.
|
|
23
|
+
"@openneuro/components": "^4.32.0",
|
|
24
24
|
"@sentry/react": "^8.25.0",
|
|
25
25
|
"@tanstack/react-table": "^8.9.3",
|
|
26
26
|
"buffer": "^6.0.3",
|
|
@@ -74,5 +74,5 @@
|
|
|
74
74
|
"publishConfig": {
|
|
75
75
|
"access": "public"
|
|
76
76
|
},
|
|
77
|
-
"gitHead": "
|
|
77
|
+
"gitHead": "3dd549a9ec43bbbf00a295a35b30f43cb7c29ffd"
|
|
78
78
|
}
|
package/src/@types/custom.d.ts
CHANGED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import styled from "@emotion/styled"
|
|
3
|
+
import { Link } from "react-router-dom"
|
|
4
|
+
|
|
5
|
+
import nirs from "../../../../../openneuro-components/src/assets/nirs.jpg"
|
|
6
|
+
import nih from "../../../../../openneuro-components/src/assets/nih_cube.jpg"
|
|
7
|
+
|
|
8
|
+
/** Image attribution content. */
|
|
9
|
+
export function Attribution(): React.ReactElement {
|
|
10
|
+
const AtributionBlock = styled.div`
|
|
11
|
+
display: flex;
|
|
12
|
+
align-items: flex-start;
|
|
13
|
+
margin-bottom: 20px;
|
|
14
|
+
img {
|
|
15
|
+
width: 30%;
|
|
16
|
+
max-width: 200px;
|
|
17
|
+
margin-right: 20px;
|
|
18
|
+
height: auto;
|
|
19
|
+
}
|
|
20
|
+
label {
|
|
21
|
+
font-weight: bold;
|
|
22
|
+
}
|
|
23
|
+
p:first-of-type{ margin-top:0}
|
|
24
|
+
`
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<>
|
|
28
|
+
<AtributionBlock>
|
|
29
|
+
<img src={nirs} alt="" />
|
|
30
|
+
<p>
|
|
31
|
+
<div>
|
|
32
|
+
<label>Used with permission:</label>{" "}
|
|
33
|
+
<a href="https://mne.tools/mne-nirs/stable/index.html">
|
|
34
|
+
MNE-NIRS
|
|
35
|
+
</a>
|
|
36
|
+
</div>
|
|
37
|
+
<div>
|
|
38
|
+
<label>Locations used: {" "}</label>
|
|
39
|
+
<Link to="/">
|
|
40
|
+
OpenNeuro
|
|
41
|
+
</Link>
|
|
42
|
+
{", "}
|
|
43
|
+
<Link to="/search/modality/nirs?query=%7B%22modality_selected%22%3A%22NIRS%22%7D">
|
|
44
|
+
NIRS Modality Search
|
|
45
|
+
</Link>
|
|
46
|
+
</div>
|
|
47
|
+
</p>
|
|
48
|
+
</AtributionBlock>
|
|
49
|
+
<AtributionBlock>
|
|
50
|
+
<img src={nih} alt="" />
|
|
51
|
+
<p>
|
|
52
|
+
<div>
|
|
53
|
+
<label>Used with permission:</label>{" "}
|
|
54
|
+
<a href="https://braininitiative.nih.gov/">
|
|
55
|
+
NIH BRAIN Initiative<sup>®</sup>
|
|
56
|
+
</a>
|
|
57
|
+
</div>
|
|
58
|
+
<div>
|
|
59
|
+
<label>Locations used: {" "}</label>
|
|
60
|
+
<Link to="/">
|
|
61
|
+
OpenNeuro
|
|
62
|
+
</Link>
|
|
63
|
+
{", "}
|
|
64
|
+
<Link to="/search/nih?query=%7B%22brain_initiative%22%3A%22true%22%7D">
|
|
65
|
+
NIH BRAIN Initiative Portal
|
|
66
|
+
</Link>
|
|
67
|
+
</div>
|
|
68
|
+
</p>
|
|
69
|
+
</AtributionBlock>
|
|
70
|
+
</>
|
|
71
|
+
)
|
|
72
|
+
}
|
|
@@ -131,8 +131,9 @@ export const DATASET_ISSUES = gql`
|
|
|
131
131
|
id
|
|
132
132
|
draft {
|
|
133
133
|
id
|
|
134
|
-
|
|
135
|
-
|
|
134
|
+
issuesStatus {
|
|
135
|
+
errors
|
|
136
|
+
warnings
|
|
136
137
|
}
|
|
137
138
|
validation {
|
|
138
139
|
errors
|
|
@@ -145,8 +146,9 @@ export const DATASET_ISSUES = gql`
|
|
|
145
146
|
export const SNAPSHOT_ISSUES = gql`
|
|
146
147
|
fragment SnapshotIssues on Snapshot {
|
|
147
148
|
id
|
|
148
|
-
|
|
149
|
-
|
|
149
|
+
issuesStatus {
|
|
150
|
+
errors
|
|
151
|
+
warnings
|
|
150
152
|
}
|
|
151
153
|
validation {
|
|
152
154
|
errors
|
|
@@ -159,23 +159,13 @@ exports[`SnapshotContainer component > renders successfully 1`] = `
|
|
|
159
159
|
role="switch"
|
|
160
160
|
>
|
|
161
161
|
<div>
|
|
162
|
-
<h3
|
|
163
|
-
class="metaheader"
|
|
164
|
-
>
|
|
165
|
-
BIDS Validation
|
|
166
|
-
</h3>
|
|
167
|
-
<span
|
|
168
|
-
class="label text-warning pull-right"
|
|
169
|
-
>
|
|
170
|
-
1 Warning
|
|
171
|
-
</span>
|
|
172
162
|
<span
|
|
173
|
-
class="dataset-status ds-
|
|
163
|
+
class="dataset-status ds-warning ds-validation-pending"
|
|
174
164
|
>
|
|
175
165
|
<i
|
|
176
|
-
class="fa fa-
|
|
166
|
+
class="fa fa-circle-o-notch fa-spin"
|
|
177
167
|
/>
|
|
178
|
-
|
|
168
|
+
Validation Pending
|
|
179
169
|
</span>
|
|
180
170
|
</div>
|
|
181
171
|
</div>
|
|
@@ -20,7 +20,10 @@ interface ValidationFragment {
|
|
|
20
20
|
export interface ValidationBlockProps {
|
|
21
21
|
datasetId: string
|
|
22
22
|
version?: string
|
|
23
|
-
|
|
23
|
+
issuesStatus?: {
|
|
24
|
+
errors: number
|
|
25
|
+
warnings: number
|
|
26
|
+
}
|
|
24
27
|
validation?: ValidationFragment
|
|
25
28
|
}
|
|
26
29
|
|
|
@@ -32,13 +35,17 @@ export interface ValidationBlockProps {
|
|
|
32
35
|
export const ValidationBlock: React.FC<ValidationBlockProps> = ({
|
|
33
36
|
datasetId,
|
|
34
37
|
version,
|
|
35
|
-
|
|
38
|
+
issuesStatus,
|
|
36
39
|
validation,
|
|
37
40
|
}) => {
|
|
38
|
-
if (
|
|
41
|
+
if (issuesStatus) {
|
|
39
42
|
return (
|
|
40
43
|
<div className="validation-accordion">
|
|
41
|
-
<LegacyValidation
|
|
44
|
+
<LegacyValidation
|
|
45
|
+
datasetId={datasetId}
|
|
46
|
+
version={version}
|
|
47
|
+
issuesStatus={issuesStatus}
|
|
48
|
+
/>
|
|
42
49
|
</div>
|
|
43
50
|
)
|
|
44
51
|
} else {
|
|
@@ -7,10 +7,13 @@ vi.mock("../../../config.ts")
|
|
|
7
7
|
|
|
8
8
|
describe("ValidationBlock component", () => {
|
|
9
9
|
it("renders legacy validation if issues prop is present", () => {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
render(
|
|
11
|
+
<ValidationBlock
|
|
12
|
+
datasetId="ds000031"
|
|
13
|
+
issuesStatus={{ warnings: 3, errors: 0 }}
|
|
14
|
+
version="1.0.0"
|
|
15
|
+
/>,
|
|
16
|
+
)
|
|
14
17
|
expect(screen.getByText("BIDS Validation")).toBeInTheDocument()
|
|
15
18
|
})
|
|
16
19
|
it("renders schema validation if validation prop is present", () => {
|
|
@@ -136,7 +136,7 @@ const DraftContainer: React.FC<DraftContainerProps> = ({ dataset }) => {
|
|
|
136
136
|
<ValidationBlock
|
|
137
137
|
datasetId={dataset.id}
|
|
138
138
|
version={dataset.draft.head}
|
|
139
|
-
|
|
139
|
+
issuesStatus={dataset.draft.issuesStatus}
|
|
140
140
|
validation={dataset.draft.validation}
|
|
141
141
|
/>
|
|
142
142
|
<CloneDropdown
|
|
@@ -136,7 +136,7 @@ export const SnapshotContainer: React.FC<SnapshotContainerProps> = ({
|
|
|
136
136
|
<ValidationBlock
|
|
137
137
|
datasetId={datasetId}
|
|
138
138
|
version={snapshot.tag}
|
|
139
|
-
|
|
139
|
+
issuesStatus={snapshot.issuesStatus}
|
|
140
140
|
validation={snapshot.validation}
|
|
141
141
|
/>
|
|
142
142
|
<AnalyzeDropdown
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import type { ReactElement } from "react"
|
|
3
|
+
import { Attribution } from "../common/content/image-attribution"
|
|
4
|
+
import Helmet from "react-helmet"
|
|
5
|
+
import { frontPage } from "./front-page/front-page-content"
|
|
6
|
+
import styled from "@emotion/styled"
|
|
7
|
+
|
|
8
|
+
const ImageAttributionStyle = styled.div`
|
|
9
|
+
background: white;
|
|
10
|
+
|
|
11
|
+
.container {
|
|
12
|
+
max-width: 60em;
|
|
13
|
+
}
|
|
14
|
+
`
|
|
15
|
+
|
|
16
|
+
export function ImageAttribution(): ReactElement {
|
|
17
|
+
return (
|
|
18
|
+
<ImageAttributionStyle>
|
|
19
|
+
<Helmet>
|
|
20
|
+
<title>Image Attribution - {frontPage.pageTitle}</title>
|
|
21
|
+
<meta
|
|
22
|
+
name="description"
|
|
23
|
+
content={`Image Attribution of the ${frontPage.pageTitle} data archive`}
|
|
24
|
+
/>
|
|
25
|
+
</Helmet>
|
|
26
|
+
<div className="container">
|
|
27
|
+
<h2>OpenNeuro Image Attribution</h2>
|
|
28
|
+
<Attribution />
|
|
29
|
+
</div>
|
|
30
|
+
</ImageAttributionStyle>
|
|
31
|
+
)
|
|
32
|
+
}
|
package/src/scripts/routes.tsx
CHANGED
|
@@ -17,6 +17,7 @@ import FourOFourPage from "./errors/404page"
|
|
|
17
17
|
import { ImportDataset } from "./pages/import-dataset"
|
|
18
18
|
import { DatasetMetadata } from "./pages/metadata/dataset-metadata"
|
|
19
19
|
import { TermsPage } from "./pages/terms"
|
|
20
|
+
import { ImageAttribution } from "./pages/image-attribution"
|
|
20
21
|
import { UserQuery } from "./users/user-query"
|
|
21
22
|
import LoggedIn from "../scripts/authentication/logged-in"
|
|
22
23
|
import LoggedOut from "../scripts/authentication/logged-out"
|
|
@@ -34,6 +35,7 @@ const AppRoutes: React.VoidFunctionComponent = () => (
|
|
|
34
35
|
<Route path="/pet" element={<PETRedirect />} />
|
|
35
36
|
<Route path="/cite" element={<Citation />} />
|
|
36
37
|
<Route path="/terms" element={<TermsPage />} />
|
|
38
|
+
<Route path="/image-attribution" element={<ImageAttribution />} />
|
|
37
39
|
<Route path="/import" element={<ImportDataset />} />
|
|
38
40
|
<Route path="/metadata" element={<DatasetMetadata />} />
|
|
39
41
|
<Route path="/public" element={<Navigate to="/search" replace />} />
|
|
@@ -8,6 +8,8 @@ describe("SearchContainer component", () => {
|
|
|
8
8
|
const setContext = vi.fn()
|
|
9
9
|
setDefaultSearch(
|
|
10
10
|
"MRI",
|
|
11
|
+
"",
|
|
12
|
+
false,
|
|
11
13
|
context,
|
|
12
14
|
setContext,
|
|
13
15
|
new URLSearchParams("/search"),
|
|
@@ -15,6 +17,8 @@ describe("SearchContainer component", () => {
|
|
|
15
17
|
expect(setContext).not.toHaveBeenCalled()
|
|
16
18
|
setDefaultSearch(
|
|
17
19
|
"PET",
|
|
20
|
+
"",
|
|
21
|
+
false,
|
|
18
22
|
context,
|
|
19
23
|
setContext,
|
|
20
24
|
new URLSearchParams("/search"),
|
|
@@ -31,6 +35,8 @@ describe("SearchContainer component", () => {
|
|
|
31
35
|
})
|
|
32
36
|
setDefaultSearch(
|
|
33
37
|
"MRI",
|
|
38
|
+
"",
|
|
39
|
+
false,
|
|
34
40
|
context,
|
|
35
41
|
setContext,
|
|
36
42
|
new URLSearchParams("mydatasets"),
|
|
@@ -47,6 +53,8 @@ describe("SearchContainer component", () => {
|
|
|
47
53
|
})
|
|
48
54
|
setDefaultSearch(
|
|
49
55
|
"MRI",
|
|
56
|
+
"",
|
|
57
|
+
false,
|
|
50
58
|
context,
|
|
51
59
|
setContext,
|
|
52
60
|
new URLSearchParams("bookmarks"),
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { useContext } from "react"
|
|
2
|
+
import * as Sentry from "@sentry/react"
|
|
2
3
|
import type { FC } from "react"
|
|
3
4
|
import { useNavigate, useParams } from "react-router-dom"
|
|
4
5
|
import {
|
|
@@ -15,6 +16,8 @@ interface FiltersBlockContainerProps {
|
|
|
15
16
|
loading: boolean
|
|
16
17
|
}
|
|
17
18
|
|
|
19
|
+
type FilterValue = string | number | boolean | string[] | number[]
|
|
20
|
+
|
|
18
21
|
const FiltersBlockContainer: FC<FiltersBlockContainerProps> = ({
|
|
19
22
|
numTotalResults,
|
|
20
23
|
loading,
|
|
@@ -28,10 +31,25 @@ const FiltersBlockContainer: FC<FiltersBlockContainerProps> = ({
|
|
|
28
31
|
const { path } = useParams()
|
|
29
32
|
const globalSearchPath = "/search"
|
|
30
33
|
|
|
31
|
-
const removeFilter =
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
const removeFilter =
|
|
35
|
+
(isModality?: boolean) => (param: string, value: FilterValue): void => {
|
|
36
|
+
if (isModality && param === "modality_selected") {
|
|
37
|
+
removeFilterItem(setSearchParams)(param, value)
|
|
38
|
+
const queryParams = new URLSearchParams(location.search)
|
|
39
|
+
const query = queryParams.get("query")
|
|
40
|
+
if (query) {
|
|
41
|
+
try {
|
|
42
|
+
navigate(`${globalSearchPath}?${queryParams.toString()}`, {
|
|
43
|
+
replace: true,
|
|
44
|
+
})
|
|
45
|
+
} catch (error) {
|
|
46
|
+
Sentry.captureException(error)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
} else {
|
|
50
|
+
removeFilterItem(setSearchParams)(param, value)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
35
53
|
|
|
36
54
|
const removeAllFilters = (): void => {
|
|
37
55
|
// reset params to default values
|
|
@@ -39,8 +57,9 @@ const FiltersBlockContainer: FC<FiltersBlockContainerProps> = ({
|
|
|
39
57
|
...prevState,
|
|
40
58
|
...getSelectParams(initialSearchParams),
|
|
41
59
|
}))
|
|
42
|
-
if (path !== globalSearchPath) navigate(globalSearchPath)
|
|
60
|
+
if (path !== globalSearchPath) navigate(globalSearchPath, { replace: true })
|
|
43
61
|
}
|
|
62
|
+
|
|
44
63
|
return (
|
|
45
64
|
<FiltersBlock
|
|
46
65
|
noFilters={noFilters}
|
|
@@ -92,6 +92,12 @@ export const modality_available: ModalityOption[] = [
|
|
|
92
92
|
portalPath: "/search/modality/meg",
|
|
93
93
|
count: null,
|
|
94
94
|
},
|
|
95
|
+
{
|
|
96
|
+
label: "NIRS",
|
|
97
|
+
value: "NIRS",
|
|
98
|
+
portalPath: "/search/modality/nirs",
|
|
99
|
+
count: null,
|
|
100
|
+
},
|
|
95
101
|
]
|
|
96
102
|
|
|
97
103
|
export const flattenedModalities = modality_available.reduce(
|
package/src/scripts/search/inputs/__tests__/{nihselect.spec.tsx → initiative-select.spec.tsx}
RENAMED
|
@@ -2,7 +2,7 @@ import React from "react"
|
|
|
2
2
|
import { fireEvent, render, screen, waitFor } from "@testing-library/react"
|
|
3
3
|
import { MemoryRouter, Route, Routes } from "react-router-dom"
|
|
4
4
|
import { SearchParamsCtx } from "../../search-params-ctx"
|
|
5
|
-
import
|
|
5
|
+
import InitiativeSelect from "../initiative-select"
|
|
6
6
|
|
|
7
7
|
const customRender = (
|
|
8
8
|
ui,
|
|
@@ -24,13 +24,15 @@ const customRender = (
|
|
|
24
24
|
|
|
25
25
|
describe("NIHSelect Component", () => {
|
|
26
26
|
it("updates search params when NIH is selected and navigates to the NIH page", async () => {
|
|
27
|
-
customRender(
|
|
27
|
+
customRender(
|
|
28
|
+
<InitiativeSelect label="Initiative" />,
|
|
29
|
+
)
|
|
28
30
|
|
|
29
|
-
const nihOption = screen.getByText("
|
|
31
|
+
const nihOption = screen.getByText("Initiative")
|
|
30
32
|
fireEvent.click(nihOption)
|
|
31
33
|
|
|
32
34
|
await waitFor(() => {
|
|
33
|
-
expect(screen.getByText("
|
|
35
|
+
expect(screen.getByText("Initiative")).toBeInTheDocument()
|
|
34
36
|
})
|
|
35
37
|
})
|
|
36
38
|
})
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import KeywordInput from "./keyword-input"
|
|
2
2
|
import AllDatasetsToggle from "./admin-allDatasets-toggle"
|
|
3
3
|
import ModalitySelect from "./modality-select"
|
|
4
|
-
import
|
|
4
|
+
import InitiativeSelect from "./initiative-select"
|
|
5
5
|
import ShowDatasetRadios from "./show-datasets-radios"
|
|
6
6
|
import AgeRangeInput from "./age-range-input"
|
|
7
7
|
import SubjectCountRangeInput from "./subject-count-range-input"
|
|
@@ -29,9 +29,9 @@ export {
|
|
|
29
29
|
DatasetTypeSelect,
|
|
30
30
|
DateRadios,
|
|
31
31
|
DiagnosisSelect,
|
|
32
|
+
InitiativeSelect,
|
|
32
33
|
KeywordInput,
|
|
33
34
|
ModalitySelect,
|
|
34
|
-
NIHSelect,
|
|
35
35
|
ScannerManufacturers,
|
|
36
36
|
ScannerManufacturersModelNames,
|
|
37
37
|
SectionSelect,
|
|
@@ -2,13 +2,14 @@ import React, { useContext, useEffect } from "react"
|
|
|
2
2
|
import type { FC } from "react"
|
|
3
3
|
import { useLocation, useNavigate } from "react-router-dom"
|
|
4
4
|
import { SearchParamsCtx } from "../search-params-ctx"
|
|
5
|
+
import { AccordionTab, AccordionWrap } from "@openneuro/components/accordion"
|
|
5
6
|
import { SingleSelect } from "@openneuro/components/facets"
|
|
6
7
|
|
|
7
|
-
interface
|
|
8
|
-
label
|
|
8
|
+
interface InitiativeSelectProps {
|
|
9
|
+
label: string
|
|
9
10
|
}
|
|
10
11
|
|
|
11
|
-
const
|
|
12
|
+
const InitiativeSelect: FC<InitiativeSelectProps> = ({ label }) => {
|
|
12
13
|
const { searchParams, setSearchParams } = useContext(SearchParamsCtx)
|
|
13
14
|
const navigate = useNavigate()
|
|
14
15
|
const location = useLocation()
|
|
@@ -50,14 +51,25 @@ const NIHSelect: FC<NIHSelectProps> = ({ label }) => {
|
|
|
50
51
|
}, [brain_initiative, location.search, navigate])
|
|
51
52
|
|
|
52
53
|
return (
|
|
53
|
-
|
|
54
|
-
className="
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
54
|
+
<>
|
|
55
|
+
<AccordionWrap className="modality-facet facet-accordion">
|
|
56
|
+
<AccordionTab
|
|
57
|
+
accordionStyle="plain"
|
|
58
|
+
label={label}
|
|
59
|
+
startOpen={false}
|
|
60
|
+
dropdown={false}
|
|
61
|
+
>
|
|
62
|
+
<SingleSelect
|
|
63
|
+
className="nih-facet facet-open"
|
|
64
|
+
selected={brain_initiative === "true" ? "NIH" : ""}
|
|
65
|
+
setSelected={setBrainInitiative}
|
|
66
|
+
items={["NIH"]}
|
|
67
|
+
label="NIH BRAIN Initiative"
|
|
68
|
+
/>
|
|
69
|
+
</AccordionTab>
|
|
70
|
+
</AccordionWrap>
|
|
71
|
+
</>
|
|
60
72
|
)
|
|
61
73
|
}
|
|
62
74
|
|
|
63
|
-
export default
|
|
75
|
+
export default InitiativeSelect
|
|
@@ -19,16 +19,23 @@ const ShowDatasetsRadios: FC = () => {
|
|
|
19
19
|
datasetStatus_available,
|
|
20
20
|
datasetStatus_selected,
|
|
21
21
|
} = searchParams
|
|
22
|
-
|
|
22
|
+
|
|
23
|
+
const setShowSelected = (datasetType_selected) => {
|
|
23
24
|
setSearchParams((prevState) => ({
|
|
24
25
|
...prevState,
|
|
25
26
|
datasetType_selected,
|
|
27
|
+
datasetStatus_selected: datasetType_selected === "My Datasets"
|
|
28
|
+
? prevState.datasetStatus_selected
|
|
29
|
+
: undefined,
|
|
26
30
|
}))
|
|
27
|
-
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const setShowMyUploadsSelected = (datasetStatus_selected) => {
|
|
28
34
|
setSearchParams((prevState) => ({
|
|
29
35
|
...prevState,
|
|
30
36
|
datasetStatus_selected,
|
|
31
37
|
}))
|
|
38
|
+
}
|
|
32
39
|
|
|
33
40
|
return loggedOut ? null : (
|
|
34
41
|
<>
|
|
@@ -44,23 +51,21 @@ const ShowDatasetsRadios: FC = () => {
|
|
|
44
51
|
setSelected={setShowSelected}
|
|
45
52
|
/>
|
|
46
53
|
</div>
|
|
47
|
-
{datasetType_selected
|
|
48
|
-
|
|
49
|
-
<
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
)
|
|
63
|
-
: null}
|
|
54
|
+
{datasetType_selected === "My Datasets" && (
|
|
55
|
+
<AccordionWrap className="facet-accordion">
|
|
56
|
+
<AccordionTab
|
|
57
|
+
accordionStyle="plain"
|
|
58
|
+
label="My Datasets Status"
|
|
59
|
+
startOpen={true}
|
|
60
|
+
>
|
|
61
|
+
<FacetSelect
|
|
62
|
+
selected={datasetStatus_selected}
|
|
63
|
+
setSelected={setShowMyUploadsSelected}
|
|
64
|
+
items={datasetStatus_available}
|
|
65
|
+
/>
|
|
66
|
+
</AccordionTab>
|
|
67
|
+
</AccordionWrap>
|
|
68
|
+
)}
|
|
64
69
|
</>
|
|
65
70
|
)
|
|
66
71
|
}
|
|
@@ -20,9 +20,9 @@ import {
|
|
|
20
20
|
DatasetTypeSelect,
|
|
21
21
|
DateRadios,
|
|
22
22
|
DiagnosisSelect,
|
|
23
|
+
InitiativeSelect,
|
|
23
24
|
KeywordInput,
|
|
24
25
|
ModalitySelect,
|
|
25
|
-
NIHSelect,
|
|
26
26
|
ScannerManufacturers,
|
|
27
27
|
ScannerManufacturersModelNames,
|
|
28
28
|
SectionSelect,
|
|
@@ -55,6 +55,8 @@ export interface SearchContainerProps {
|
|
|
55
55
|
*/
|
|
56
56
|
export const setDefaultSearch = (
|
|
57
57
|
modality: string,
|
|
58
|
+
grant: string,
|
|
59
|
+
is_grant_portal: boolean,
|
|
58
60
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
59
61
|
searchParams: Record<string, any>,
|
|
60
62
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -84,20 +86,31 @@ export const setDefaultSearch = (
|
|
|
84
86
|
EEG: ["EEG"],
|
|
85
87
|
iEEG: ["iEEG"],
|
|
86
88
|
MEG: ["MEG"],
|
|
89
|
+
NIRS: ["NIRS"],
|
|
87
90
|
NIH: ["NIH"],
|
|
88
91
|
}
|
|
92
|
+
|
|
89
93
|
if (
|
|
90
94
|
modality &&
|
|
91
|
-
!modalitiesWithSecondaries[modality]
|
|
95
|
+
!modalitiesWithSecondaries[modality]?.includes(
|
|
92
96
|
searchParams.modality_selected,
|
|
93
97
|
)
|
|
94
98
|
) {
|
|
95
|
-
setSearchParams(
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
99
|
+
setSearchParams((prevState) => ({
|
|
100
|
+
...prevState,
|
|
101
|
+
modality_selected: modality,
|
|
102
|
+
}))
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Check for grant-related conditions
|
|
106
|
+
if (
|
|
107
|
+
is_grant_portal && grant === "nih" &&
|
|
108
|
+
searchParams.brain_initiative !== "true"
|
|
109
|
+
) {
|
|
110
|
+
setSearchParams((prevState) => ({
|
|
111
|
+
...prevState,
|
|
112
|
+
brain_initiative: "true",
|
|
113
|
+
}))
|
|
101
114
|
}
|
|
102
115
|
}
|
|
103
116
|
|
|
@@ -109,15 +122,26 @@ const SearchContainer: FC<SearchContainerProps> = ({ portalContent }) => {
|
|
|
109
122
|
const { searchParams, setSearchParams } = useContext(SearchParamsCtx)
|
|
110
123
|
const modality = portalContent?.modality || null
|
|
111
124
|
const selected_grant = portalContent?.portalName || null
|
|
125
|
+
const is_grant_portal = portalContent?.portal || false
|
|
126
|
+
const grant = portalContent?.grant || null
|
|
112
127
|
|
|
113
128
|
useEffect(() => {
|
|
114
129
|
setDefaultSearch(
|
|
115
130
|
modality,
|
|
131
|
+
grant,
|
|
132
|
+
is_grant_portal,
|
|
116
133
|
searchParams,
|
|
117
134
|
setSearchParams,
|
|
118
135
|
new URLSearchParams(location.search),
|
|
119
136
|
)
|
|
120
|
-
}, [
|
|
137
|
+
}, [
|
|
138
|
+
modality,
|
|
139
|
+
grant,
|
|
140
|
+
is_grant_portal,
|
|
141
|
+
searchParams,
|
|
142
|
+
setSearchParams,
|
|
143
|
+
location.search,
|
|
144
|
+
])
|
|
121
145
|
|
|
122
146
|
const { loading, data, fetchMore, variables } = useSearchResults()
|
|
123
147
|
const loadMore = loading ? () => {} : () => {
|
|
@@ -166,7 +190,6 @@ const SearchContainer: FC<SearchContainerProps> = ({ portalContent }) => {
|
|
|
166
190
|
)}
|
|
167
191
|
renderSearchFacets={() => (
|
|
168
192
|
<>
|
|
169
|
-
<NIHSelect label={"Search NIH Brain Initiative Datasets"} />
|
|
170
193
|
<NeurobagelSearch />
|
|
171
194
|
<KeywordInput />
|
|
172
195
|
<AdminUser>
|
|
@@ -176,6 +199,7 @@ const SearchContainer: FC<SearchContainerProps> = ({ portalContent }) => {
|
|
|
176
199
|
{!portalContent
|
|
177
200
|
? <ModalitySelect portalStyles={true} label="Modalities" />
|
|
178
201
|
: <ModalitySelect portalStyles={false} label="Choose Modality" />}
|
|
202
|
+
<InitiativeSelect label="Initiatives" />
|
|
179
203
|
<DatasetTypeSelect />
|
|
180
204
|
<AgeRangeInput />
|
|
181
205
|
<SubjectCountRangeInput />
|
|
@@ -30,6 +30,10 @@ const SearchRoutes: FC = () => (
|
|
|
30
30
|
path="/modality/pet"
|
|
31
31
|
element={<SearchContainer portalContent={portalContent.pet} />}
|
|
32
32
|
/>
|
|
33
|
+
<Route
|
|
34
|
+
path="/modality/nirs"
|
|
35
|
+
element={<SearchContainer portalContent={portalContent.nirs} />}
|
|
36
|
+
/>
|
|
33
37
|
<Route
|
|
34
38
|
path="/nih"
|
|
35
39
|
element={<SearchContainer portalContent={portalGrantContent.nih} />}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { gql, useQuery } from "@apollo/client"
|
|
2
|
+
import { ISSUE_FIELDS } from "../datalad/dataset/dataset-query-fragments"
|
|
3
|
+
|
|
4
|
+
export const LEGACY_VALIDATION_DATA_QUERY = gql`
|
|
5
|
+
query ($datasetId: ID!, $version: String!) {
|
|
6
|
+
snapshot(datasetId: $datasetId, tag: $version) {
|
|
7
|
+
issues {
|
|
8
|
+
${ISSUE_FIELDS}
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
`
|
|
13
|
+
|
|
14
|
+
export const useLegacyValidationResults = (
|
|
15
|
+
datasetId: string,
|
|
16
|
+
version: string,
|
|
17
|
+
) => {
|
|
18
|
+
const { loading, error, data } = useQuery(LEGACY_VALIDATION_DATA_QUERY, {
|
|
19
|
+
variables: { datasetId, version },
|
|
20
|
+
})
|
|
21
|
+
if (!loading && data) {
|
|
22
|
+
return { loading, error, issues: data.snapshot.issues }
|
|
23
|
+
} else {
|
|
24
|
+
return { loading, error, issues: [] }
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -1,29 +1,51 @@
|
|
|
1
1
|
// dependencies -----------------------------------------------------------
|
|
2
2
|
|
|
3
3
|
import React from "react"
|
|
4
|
-
import PropTypes from "prop-types"
|
|
5
4
|
import pluralize from "pluralize"
|
|
6
5
|
import { AccordionTab, AccordionWrap } from "@openneuro/components/accordion"
|
|
7
|
-
|
|
8
6
|
import Issues from "./validation-results.issues.jsx"
|
|
7
|
+
import { useLegacyValidationResults } from "./validation-legacy-query.js"
|
|
8
|
+
import { Loading } from "@openneuro/components/loading"
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
function countFiles(issues) {
|
|
11
|
+
let numFiles = 0
|
|
12
|
+
for (const issue of issues) {
|
|
13
|
+
numFiles += Array.from(issue.files).length
|
|
14
|
+
if (issue.additionalFileCount) {
|
|
15
|
+
numFiles += issue.additionalFileCount
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return numFiles
|
|
19
|
+
}
|
|
11
20
|
|
|
12
|
-
|
|
13
|
-
|
|
21
|
+
interface ValidationResultsProp {
|
|
22
|
+
datasetId: string
|
|
23
|
+
version: string
|
|
24
|
+
}
|
|
14
25
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
26
|
+
export function ValidationResults(
|
|
27
|
+
{ datasetId, version }: ValidationResultsProp,
|
|
28
|
+
) {
|
|
29
|
+
const { loading, issues, error } = useLegacyValidationResults(
|
|
30
|
+
datasetId,
|
|
31
|
+
version,
|
|
32
|
+
)
|
|
18
33
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
34
|
+
if (loading || error) {
|
|
35
|
+
return (
|
|
36
|
+
<>
|
|
37
|
+
<Loading />
|
|
38
|
+
<span className="message">Loading validation results...</span>
|
|
39
|
+
</>
|
|
40
|
+
)
|
|
41
|
+
} else {
|
|
42
|
+
const warnings = issues.filter((issue) => issue.severity === "warning")
|
|
43
|
+
const errors = issues.filter((issue) => issue.severity === "error")
|
|
22
44
|
|
|
23
45
|
// errors
|
|
24
46
|
let errorsWrap
|
|
25
47
|
if (errors.length > 0) {
|
|
26
|
-
const fileCount =
|
|
48
|
+
const fileCount = countFiles(errors)
|
|
27
49
|
const errorHeader = (
|
|
28
50
|
<span>
|
|
29
51
|
view {errors.length} {pluralize("error", errors.length)} in{" "}
|
|
@@ -45,7 +67,7 @@ class ValidationResults extends React.Component {
|
|
|
45
67
|
//warnings
|
|
46
68
|
let warningWrap
|
|
47
69
|
if (warnings && warnings.length > 0) {
|
|
48
|
-
const fileCount =
|
|
70
|
+
const fileCount = countFiles(warnings)
|
|
49
71
|
const warningHeader = (
|
|
50
72
|
<span>
|
|
51
73
|
view {warnings.length} {pluralize("warning", warnings.length)} in{" "}
|
|
@@ -72,29 +94,6 @@ class ValidationResults extends React.Component {
|
|
|
72
94
|
</AccordionWrap>
|
|
73
95
|
)
|
|
74
96
|
}
|
|
75
|
-
|
|
76
|
-
// custom methods ---------------------------------------------------------
|
|
77
|
-
|
|
78
|
-
_countFiles(issues) {
|
|
79
|
-
let numFiles = 0
|
|
80
|
-
for (const issue of issues) {
|
|
81
|
-
numFiles += Array.from(issue.files).length
|
|
82
|
-
if (issue.additionalFileCount) {
|
|
83
|
-
numFiles += issue.additionalFileCount
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
return numFiles
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
ValidationResults.Props = {
|
|
91
|
-
errors: [],
|
|
92
|
-
warnings: [],
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
ValidationResults.propTypes = {
|
|
96
|
-
errors: PropTypes.oneOfType([PropTypes.array, PropTypes.string]),
|
|
97
|
-
warnings: PropTypes.array,
|
|
98
97
|
}
|
|
99
98
|
|
|
100
99
|
export default ValidationResults
|
|
@@ -2,7 +2,7 @@ import React from "react"
|
|
|
2
2
|
import PropTypes from "prop-types"
|
|
3
3
|
import pluralize from "pluralize"
|
|
4
4
|
import ValidationPanel from "./validation-panel.jsx"
|
|
5
|
-
import Results from "./validation-results
|
|
5
|
+
import Results from "./validation-results"
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* These can't be React components due to legacy react-bootstrap
|
|
@@ -52,54 +52,68 @@ const Valid = () => (
|
|
|
52
52
|
</div>
|
|
53
53
|
)
|
|
54
54
|
|
|
55
|
-
const Warnings = ({
|
|
56
|
-
<ValidationPanel heading={warningHeader(warnings
|
|
55
|
+
const Warnings = ({ datasetId, version, issuesStatus }) => (
|
|
56
|
+
<ValidationPanel heading={warningHeader(issuesStatus.warnings)}>
|
|
57
57
|
<div>
|
|
58
58
|
<span className="message error fade-in">
|
|
59
59
|
We found{" "}
|
|
60
60
|
<strong>
|
|
61
|
-
{warnings
|
|
61
|
+
{issuesStatus.warnings + " " +
|
|
62
|
+
pluralize("Warning", issuesStatus.warnings)}
|
|
62
63
|
</strong>{" "}
|
|
63
64
|
in your dataset. You are not required to fix warnings, but doing so will
|
|
64
65
|
make your dataset more BIDS compliant.
|
|
65
66
|
</span>
|
|
66
67
|
</div>
|
|
67
68
|
<br />
|
|
68
|
-
<Results
|
|
69
|
+
<Results datasetId={datasetId} version={version} />
|
|
69
70
|
</ValidationPanel>
|
|
70
71
|
)
|
|
71
72
|
|
|
72
73
|
Warnings.propTypes = {
|
|
73
|
-
|
|
74
|
-
|
|
74
|
+
issuesStatus: PropTypes.object,
|
|
75
|
+
datasetId: PropTypes.string,
|
|
76
|
+
version: PropTypes.string,
|
|
75
77
|
}
|
|
76
78
|
|
|
77
|
-
const Errors = ({
|
|
78
|
-
<ValidationPanel heading={errorHeader(errors
|
|
79
|
+
const Errors = ({ datasetId, version, issuesStatus }) => (
|
|
80
|
+
<ValidationPanel heading={errorHeader(issuesStatus.errors)}>
|
|
79
81
|
<span className="message error fade-in">
|
|
80
82
|
Your dataset is no longer valid. You must fix the{" "}
|
|
81
|
-
<strong>
|
|
82
|
-
|
|
83
|
+
<strong>
|
|
84
|
+
{issuesStatus.errors + " " + pluralize("Error", issuesStatus.errors)}
|
|
85
|
+
</strong>{" "}
|
|
83
86
|
to use all of the site features.
|
|
84
87
|
</span>
|
|
85
88
|
<br />
|
|
86
|
-
<Results
|
|
89
|
+
<Results datasetId={datasetId} version={version} />
|
|
87
90
|
</ValidationPanel>
|
|
88
91
|
)
|
|
89
92
|
|
|
90
93
|
Errors.propTypes = {
|
|
91
|
-
|
|
92
|
-
|
|
94
|
+
issuesStatus: PropTypes.object,
|
|
95
|
+
datasetId: PropTypes.string,
|
|
96
|
+
version: PropTypes.string,
|
|
93
97
|
}
|
|
94
98
|
|
|
95
|
-
const ValidationStatus = ({
|
|
96
|
-
if (
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
99
|
+
const ValidationStatus = ({ issuesStatus, datasetId, version }) => {
|
|
100
|
+
if (issuesStatus) {
|
|
101
|
+
if (issuesStatus.errors > 0) {
|
|
102
|
+
return (
|
|
103
|
+
<Errors
|
|
104
|
+
issuesStatus={issuesStatus}
|
|
105
|
+
datasetId={datasetId}
|
|
106
|
+
version={version}
|
|
107
|
+
/>
|
|
108
|
+
)
|
|
109
|
+
} else if (issuesStatus.warnings > 0) {
|
|
110
|
+
return (
|
|
111
|
+
<Warnings
|
|
112
|
+
issuesStatus={issuesStatus}
|
|
113
|
+
datasetId={datasetId}
|
|
114
|
+
version={version}
|
|
115
|
+
/>
|
|
116
|
+
)
|
|
103
117
|
} else {
|
|
104
118
|
return <Valid />
|
|
105
119
|
}
|
|
@@ -3,10 +3,14 @@ import PropTypes from "prop-types"
|
|
|
3
3
|
import ValidationStatus from "./validation-status.jsx"
|
|
4
4
|
import ErrorBoundary from "../errors/errorBoundary.jsx"
|
|
5
5
|
|
|
6
|
-
const Validation = ({
|
|
6
|
+
const Validation = ({ issuesStatus, datasetId, version }) => (
|
|
7
7
|
<>
|
|
8
8
|
<ErrorBoundary subject={"error in dataset validation component"}>
|
|
9
|
-
<ValidationStatus
|
|
9
|
+
<ValidationStatus
|
|
10
|
+
issuesStatus={issuesStatus}
|
|
11
|
+
datasetId={datasetId}
|
|
12
|
+
version={version}
|
|
13
|
+
/>
|
|
10
14
|
</ErrorBoundary>
|
|
11
15
|
</>
|
|
12
16
|
)
|