@openneuro/app 4.22.0 → 4.24.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.22.0",
3
+ "version": "4.24.0-alpha.0",
4
4
  "description": "React JS web frontend for the OpenNeuro platform.",
5
5
  "license": "MIT",
6
6
  "main": "public/client.js",
@@ -16,14 +16,14 @@
16
16
  "dependencies": {
17
17
  "@apollo/client": "3.7.2",
18
18
  "@artsy/fresnel": "^1.3.1",
19
- "@elastic/apm-rum": "5.11.0",
19
+ "@elastic/apm-rum": "5.16.0",
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.22.0",
24
- "@openneuro/components": "^4.22.0",
23
+ "@openneuro/client": "^4.24.0-alpha.0",
24
+ "@openneuro/components": "^4.24.0-alpha.0",
25
25
  "@tanstack/react-table": "^8.9.3",
26
- "bids-validator": "1.13.0",
26
+ "bids-validator": "1.14.6",
27
27
  "bytes": "^3.0.0",
28
28
  "comlink": "^4.0.5",
29
29
  "date-fns": "^2.16.1",
@@ -32,45 +32,47 @@
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": "^17.0.1",
37
+ "react": "^18.2.0",
39
38
  "react-cookie": "4.0.3",
40
39
  "react-copy-to-clipboard": "^5.0.1",
41
- "react-dom": "^17.0.1",
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.1.3",
52
- "@testing-library/react": "^11.1.0",
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
- "@types/node": "18.11.9",
57
+ "@types/node": "20.12.7",
56
58
  "@types/react": "^17.0.8",
57
- "@types/react-dom": "^17.0.5",
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",
61
63
  "esbuild-plugin-globals": "^0.1.1",
62
64
  "history": "5.3.0",
63
- "jsdom": "^16.5.3",
65
+ "jsdom": "24.0.0",
64
66
  "object.fromentries": "^2.0.0",
65
67
  "rollup-plugin-polyfill-node": "0.12.0",
66
68
  "sass": "^1.32.8",
67
69
  "stream-browserify": "^3.0.0",
68
70
  "typescript": "5.1.6",
69
- "vite": "4.5.2",
70
- "vitest": "0.34.4"
71
+ "vite": "4.5.3",
72
+ "vitest": "1.5.0"
71
73
  },
72
74
  "publishConfig": {
73
75
  "access": "public"
74
76
  },
75
- "gitHead": "91d72c3a61f8a3041e810d957cd6f4e5440d6488"
77
+ "gitHead": "4e271231c20d21e4c8b5ba8851d67cb94b7f739a"
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 ReactDOM from "react-dom"
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
- ReactDOM.render(
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
- if (typeof value === "string" && isValid(parseISO(value))) {
39
- return format(parseISO(value), "yyyy-MM-dd")
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)) {
@@ -13,7 +13,7 @@ exports[`SnapshotContainer component > includes JSON-LD data in the header 1`] =
13
13
  "onChangeClientState": [Function],
14
14
  "scriptTags": [
15
15
  {
16
- "innerHTML": "{\\"@context\\":\\"http://schema.org\\",\\"@type\\":\\"Dataset\\",\\"name\\":\\"DS003-downsampled (only T1)\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"J. Doe\\",\\"givenName\\":\\"J.\\",\\"familyName\\":\\"Doe\\"},{\\"@type\\":\\"Person\\",\\"name\\":\\"J. Doe\\",\\"givenName\\":\\"J.\\",\\"familyName\\":\\"Doe\\"},{\\"@type\\":\\"Person\\",\\"name\\":\\"J. Doe\\",\\"givenName\\":\\"J.\\",\\"familyName\\":\\"Doe\\"}],\\"datePublished\\":\\"2021-12-17T22:32:08.000Z\\",\\"dateModified\\":\\"2021-12-17T22:32:08.000Z\\",\\"license\\":\\"https://creativecommons.org/publicdomain/zero/1.0/\\",\\"publisher\\":{\\"@type\\":\\"Organization\\",\\"name\\":\\"OpenNeuro\\"},\\"description\\":\\"This dataset was obtained from the OpenfMRI project (http://www.openfmri.org).\\\\nAccession #: ds003\\\\nDescription: Rhyme judgment\\\\n\\\\nRelease history:\\\\n10/06/2011: initial release\\\\n3/21/2013: Updated release with QA information\\\\n\\\\nThis dataset is made available under the Public Domain Dedication and License \\\\nv1.0, whose full text can be found at \\\\nhttp://www.opendatacommons.org/licenses/pddl/1.0/. \\\\nWe hope that all users will follow the ODC Attribution/Share-Alike \\\\nCommunity Norms (http://www.opendatacommons.org/norms/odc-by-sa/); \\\\nin particular, while not legally required, we hope that all users \\\\nof the data will acknowledge the OpenfMRI project and NSF Grant \\\\nOCI-1131441 (R. Poldrack, PI) in any publications.\\\\n\\",\\"version\\":\\"1.0.0\\",\\"url\\":\\"https://openneuro.org/datasets/ds001032/versions/1.0.0\\"}",
16
+ "innerHTML": "{"@context":"http://schema.org","@type":"Dataset","name":"DS003-downsampled (only T1)","author":[{"@type":"Person","name":"J. Doe","givenName":"J.","familyName":"Doe"},{"@type":"Person","name":"J. Doe","givenName":"J.","familyName":"Doe"},{"@type":"Person","name":"J. Doe","givenName":"J.","familyName":"Doe"}],"datePublished":"2021-12-17T22:32:08.000Z","dateModified":"2021-12-17T22:32:08.000Z","license":"https://creativecommons.org/publicdomain/zero/1.0/","publisher":{"@type":"Organization","name":"OpenNeuro"},"description":"This dataset was obtained from the OpenfMRI project (http://www.openfmri.org).\\nAccession #: ds003\\nDescription: Rhyme judgment\\n\\nRelease history:\\n10/06/2011: initial release\\n3/21/2013: Updated release with QA information\\n\\nThis dataset is made available under the Public Domain Dedication and License \\nv1.0, whose full text can be found at \\nhttp://www.opendatacommons.org/licenses/pddl/1.0/. \\nWe hope that all users will follow the ODC Attribution/Share-Alike \\nCommunity Norms (http://www.opendatacommons.org/norms/odc-by-sa/); \\nin particular, while not legally required, we hope that all users \\nof the data will acknowledge the OpenfMRI project and NSF Grant \\nOCI-1131441 (R. Poldrack, PI) in any publications.\\n","version":"1.0.0","url":"https://openneuro.org/datasets/ds001032/versions/1.0.0"}",
17
17
  "type": "application/ld+json",
18
18
  },
19
19
  ],
@@ -498,46 +498,48 @@ exports[`SnapshotContainer component > renders successfully 1`] = `
498
498
  <div>
499
499
  <article>
500
500
  <section>
501
- <div>
502
- <p>
503
- This dataset was obtained from the OpenfMRI project (
504
- <a
505
- href="http://www.openfmri.org"
506
- >
507
- http://www.openfmri.org
508
- </a>
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
- </p>
513
- <p>
514
- Release history:
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
- </p>
518
- <p>
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
- <a
523
- href="http://www.opendatacommons.org/licenses/pddl/1.0/"
524
- >
525
- http://www.opendatacommons.org/licenses/pddl/1.0/
526
- </a>
527
- .
528
- We hope that all users will follow the ODC Attribution/Share-Alike
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
- <a
531
- href="http://www.opendatacommons.org/norms/odc-by-sa/"
532
- >
533
- http://www.opendatacommons.org/norms/odc-by-sa/
534
- </a>
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
- </p>
540
- </div>
542
+ </p>
541
543
  </section>
542
544
  </article>
543
545
  </div>
@@ -115,6 +115,7 @@ export const DatasetTools = ({
115
115
  : `/datasets/${datasetId}/derivatives`}
116
116
  icon="fa-cubes"
117
117
  label="Derivatives"
118
+ disable={!agree}
118
119
  />
119
120
  )}
120
121
  <DatasetToolButton
@@ -16,7 +16,11 @@ export const NemarButton: React.FC<NemarButtonProps> = ({
16
16
  <>
17
17
  {onNemar && (
18
18
  <div className="brainlife-block">
19
- <Tooltip tooltip="View and analyze on NEMAR" flow="up">
19
+ <Tooltip
20
+ tooltip="View and analyze this dataset on the NEMAR OpenNeuro portal for MEG, iEEG, and EEG data"
21
+ flow="up"
22
+ wrapText={true}
23
+ >
20
24
  <Button
21
25
  className="brainlife-link"
22
26
  primary={true}
@@ -14,14 +14,23 @@ const FileViewerNifti = ({
14
14
  url: imageUrl,
15
15
  colorMap: "gray",
16
16
  opacity: 1,
17
- visible: true,
18
17
  limitFrames4D: 5,
19
18
  },
20
19
  ]
21
20
  const nv = new Niivue({ dragAndDropEnabled: false })
22
21
  ;(window as any).niivue = nv
23
22
  nv.attachToCanvas(canvas.current)
24
- nv.loadVolumes(volumeList) // press the "v" key to cycle through volumes
23
+ nv.loadVolumes(volumeList) // press the "v" key to cycle through views (axial, coronal, sagittal, 3D, etc.)
24
+ nv.graph.autoSizeMultiplanar = true // use autosizing
25
+ nv.opts.multiplanarForceRender = true // ensure that we draw the time series graph in the tile usually reserved for the 3D render
26
+ nv.graph.normalizeValues = false // use raw data values on y-axis
27
+ nv.graph.opacity = 1.0 // show the graph
28
+ // Notes:
29
+ // 1. If an image only has one volume, the timeseries graph will not be visible.
30
+ // The 3D render will be placed in the graph tile instead.
31
+ // 2. Users can navigate volumes forward and backward in the series using the left and right arrow keys on desktop devices
32
+ // 3. On touch screens, users can tap on the timeseries graph to jump to a volume index
33
+ // 4. Users can load all volumes by clicking on the "..." displayed on the timeseries graph
25
34
  }, [imageUrl])
26
35
 
27
36
  return <canvas ref={canvas} height={800} />
@@ -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>
@@ -7,22 +7,22 @@ import OrcidGeneral from "./orcid/general.jsx"
7
7
  import OrcidEmail from "./orcid/email.jsx"
8
8
  import OrcidGiven from "./orcid/given.jsx"
9
9
  import OrcidFamily from "./orcid/family.jsx"
10
+ import FourOFourPage from "./404page"
10
11
 
11
- class ErrorRoute extends React.Component {
12
- render() {
13
- return (
14
- <div className="container errors">
15
- <div className="panel">
16
- <Routes>
17
- <Route path="orcid" element={<OrcidGeneral />} />
18
- <Route path="orcid/email" element={<OrcidEmail />} />
19
- <Route path="orcid/given" element={<OrcidGiven />} />
20
- <Route path="orcid/family" element={<OrcidFamily />} />
21
- </Routes>
22
- </div>
12
+ function ErrorRoute() {
13
+ return (
14
+ <div className="container errors">
15
+ <div className="panel">
16
+ <Routes>
17
+ <Route path="orcid" element={<OrcidGeneral />} />
18
+ <Route path="orcid/email" element={<OrcidEmail />} />
19
+ <Route path="orcid/given" element={<OrcidGiven />} />
20
+ <Route path="orcid/family" element={<OrcidFamily />} />
21
+ <Route path="*" element={<FourOFourPage />} />
22
+ </Routes>
23
23
  </div>
24
- )
25
- }
24
+ </div>
25
+ )
26
26
  }
27
27
 
28
28
  export default ErrorRoute
@@ -39,7 +39,7 @@ const AppRoutes: React.VoidFunctionComponent = () => (
39
39
  path="/dashboard"
40
40
  element={<Navigate to="/search?mydatasets" replace />}
41
41
  />
42
- <Route element={<FourOFourPage />} />
42
+ <Route path="/*" element={<FourOFourPage />} />
43
43
  </Routes>
44
44
  )
45
45
 
@@ -272,6 +272,9 @@
272
272
  content: '';
273
273
  }
274
274
  }
275
+ p {
276
+ display: inline-block;
277
+ }
275
278
  }
276
279
  }
277
280
  }
@@ -155,46 +155,11 @@ export interface SearchParams {
155
155
  tracerRadionuclides: string[]
156
156
  sortBy_available
157
157
  sortBy_selected
158
+ bidsDatasetType_available: string[]
159
+ bidsDatasetType_selected: string | null
158
160
  }
159
161
 
160
- // TODO: move to this initial state
161
- // and load dynamic options on mount
162
162
  const initialSearchParams: SearchParams = {
163
- keywords: [],
164
- searchAllDatasets: false,
165
- datasetType_available,
166
- datasetType_selected: null,
167
- datasetStatus_available,
168
- datasetStatus_selected: null,
169
- modality_available,
170
- modality_selected: null,
171
- ageRange: [null, null],
172
- subjectCountRange: [null, null],
173
- diagnosis_available: [],
174
- diagnosis_selected: null,
175
- tasks: [],
176
- authors: [],
177
- // more
178
- sex_available: [],
179
- sex_selected: null,
180
- date_available: [],
181
- date_selected: null,
182
- species_available: [],
183
- species_selected: null,
184
- section_available: [],
185
- section_selected: null,
186
- studyDomains: [],
187
- bodyParts: [],
188
- scannerManufacturers: [],
189
- scannerManufacturersModelNames: [],
190
- tracerNames: [],
191
- tracerRadionuclides: [],
192
- sortBy_available: sortBy,
193
- sortBy_selected: sortBy[0],
194
- }
195
-
196
- // TODO: (stretch) delete and move to dynamically loaded initialSearchParams
197
- const TEMPORARY_initialSearchParams: SearchParams = {
198
163
  keywords: [],
199
164
  searchAllDatasets: false,
200
165
  datasetType_available,
@@ -237,6 +202,8 @@ const TEMPORARY_initialSearchParams: SearchParams = {
237
202
  tracerRadionuclides: [],
238
203
  sortBy_available: sortBy,
239
204
  sortBy_selected: sortBy[0],
205
+ bidsDatasetType_available: ["raw", "derivative"],
206
+ bidsDatasetType_selected: null,
240
207
  }
241
208
 
242
- export default TEMPORARY_initialSearchParams
209
+ export default initialSearchParams
@@ -0,0 +1,33 @@
1
+ import React, { FC, useContext } from "react"
2
+ import { SearchParamsCtx } from "../search-params-ctx"
3
+ import { FacetSelect } from "@openneuro/components/facets"
4
+ import { AccordionTab, AccordionWrap } from "@openneuro/components/accordion"
5
+
6
+ const DatasetTypeSelect: FC = () => {
7
+ const { searchParams, setSearchParams } = useContext(SearchParamsCtx)
8
+
9
+ const { bidsDatasetType_selected, bidsDatasetType_available } = searchParams
10
+ const setDatasetType = (bidsDatasetType_selected) =>
11
+ setSearchParams((prevState) => ({
12
+ ...prevState,
13
+ bidsDatasetType_selected,
14
+ }))
15
+
16
+ return (
17
+ <AccordionWrap className="facet-accordion">
18
+ <AccordionTab
19
+ accordionStyle="plain"
20
+ label="Dataset Type"
21
+ startOpen={false}
22
+ >
23
+ <FacetSelect
24
+ selected={bidsDatasetType_selected}
25
+ setSelected={setDatasetType}
26
+ items={bidsDatasetType_available}
27
+ />
28
+ </AccordionTab>
29
+ </AccordionWrap>
30
+ )
31
+ }
32
+
33
+ export default DatasetTypeSelect
@@ -4,6 +4,7 @@ import ModalitySelect from "./modality-select"
4
4
  import ShowDatasetRadios from "./show-datasets-radios"
5
5
  import AgeRangeInput from "./age-range-input"
6
6
  import SubjectCountRangeInput from "./subject-count-range-input"
7
+ import DatasetTypeSelect from "./dataset-type-select"
7
8
  import DiagnosisSelect from "./diagnosis-select"
8
9
  import TaskInput from "./task-input"
9
10
  import AuthorInput from "./author-input"
@@ -24,6 +25,7 @@ export {
24
25
  AllDatasetsToggle,
25
26
  AuthorInput,
26
27
  BodyPartsInput,
28
+ DatasetTypeSelect,
27
29
  DateRadios,
28
30
  DiagnosisSelect,
29
31
  KeywordInput,
@@ -16,6 +16,7 @@ import {
16
16
  AllDatasetsToggle,
17
17
  AuthorInput,
18
18
  BodyPartsInput,
19
+ DatasetTypeSelect,
19
20
  DateRadios,
20
21
  DiagnosisSelect,
21
22
  KeywordInput,
@@ -101,14 +102,12 @@ const SearchContainer: FC<SearchContainerProps> = ({ portalContent }) => {
101
102
 
102
103
  const { searchParams, setSearchParams } = useContext(SearchParamsCtx)
103
104
  const modality = portalContent?.modality || null
104
- useEffect(() => {
105
- setDefaultSearch(
106
- modality,
107
- searchParams,
108
- setSearchParams,
109
- new URLSearchParams(location.search),
110
- )
111
- }, [modality, searchParams.modality_selected, setSearchParams, location])
105
+ setDefaultSearch(
106
+ modality,
107
+ searchParams,
108
+ setSearchParams,
109
+ new URLSearchParams(location.search),
110
+ )
112
111
 
113
112
  const { loading, data, fetchMore, refetch, variables, error } =
114
113
  useSearchResults()
@@ -166,6 +165,7 @@ const SearchContainer: FC<SearchContainerProps> = ({ portalContent }) => {
166
165
  {!portalContent
167
166
  ? <ModalitySelect portalStyles={true} label="Modalities" />
168
167
  : <ModalitySelect portalStyles={false} label="Choose Modality" />}
168
+ <DatasetTypeSelect />
169
169
  <AgeRangeInput />
170
170
  <SubjectCountRangeInput />
171
171
  <DiagnosisSelect />
@@ -123,6 +123,7 @@ export const getSelectParams = ({
123
123
  scannerManufacturersModelNames,
124
124
  tracerNames,
125
125
  tracerRadionuclides,
126
+ bidsDatasetType_selected,
126
127
  }) => ({
127
128
  keywords,
128
129
  modality_selected,
@@ -144,6 +145,7 @@ export const getSelectParams = ({
144
145
  scannerManufacturersModelNames,
145
146
  tracerNames,
146
147
  tracerRadionuclides,
148
+ bidsDatasetType_selected,
147
149
  })
148
150
 
149
151
  /**
@@ -2,38 +2,46 @@
2
2
 
3
3
  exports[`Test <Markdown> component > allows a href with certain protocols 1`] = `
4
4
  <DocumentFragment>
5
- <a
6
- href="https://example.com"
7
- >
8
- Example link that should work.
9
- </a>
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
- <a>
16
- Example link that should not work.
17
- </a>
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
- <span>
27
+ <p>
24
28
  <br />
25
29
  sample text
26
30
  <br />
27
- </span>
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 MarkdownToJsx from "markdown-to-jsx"
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
- <MarkdownToJsx>{sanitizedMarkdown}</MarkdownToJsx>
166
+ <ReactMarkdown rehypePlugins={[rehypeRaw]} remarkPlugins={[remarkGfm]}>
167
+ {sanitizedMarkdown}
168
+ </ReactMarkdown>
165
169
  </>
166
170
  )
167
171
  }
@@ -73,7 +73,7 @@ class Issue extends React.Component {
73
73
  <span className="e-meta">
74
74
  <label>File Metadata:</label>
75
75
  <p>
76
- {file.size / 1000} KB | {file.type}
76
+ {file.size / 1000} KB {file.type ? ` | ${file.type}` : ""}
77
77
  </p>
78
78
  </span>
79
79
  )
@@ -31,23 +31,28 @@ class Issues extends React.Component {
31
31
  </span>
32
32
  )
33
33
 
34
- // issue sub-errors
35
- const subErrors = Array.from(issue.files).map((error, index2) => {
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
- if (error?.length) {
39
- return error.filter(Boolean).map((i, index3) => {
40
- return (
41
- <Issue
42
- type={this.props.issueType}
43
- file={i.file}
44
- error={i}
45
- index={index3}
46
- key={index3}
47
- />
48
- )
49
- })
50
- } else {
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(