@openneuro/app 4.35.0-alpha.0 → 4.35.0-alpha.1

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.35.0-alpha.0",
3
+ "version": "4.35.0-alpha.1",
4
4
  "description": "React JS web frontend for the OpenNeuro platform.",
5
5
  "license": "MIT",
6
6
  "main": "public/client.js",
@@ -19,7 +19,7 @@
19
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
- "@niivue/niivue": "0.45.1",
22
+ "@niivue/niivue": "0.57.0",
23
23
  "@openneuro/components": "^4.33.4",
24
24
  "@sentry/react": "^8.25.0",
25
25
  "@tanstack/react-table": "^8.9.3",
@@ -75,5 +75,5 @@
75
75
  "publishConfig": {
76
76
  "access": "public"
77
77
  },
78
- "gitHead": "aef6b8ec1097cd7cd19ca9de82e17d8dfa87be71"
78
+ "gitHead": "ac9c013877e5d9c1fa9615a177f79e0da0747f84"
79
79
  }
@@ -146,6 +146,12 @@
146
146
  &:hover {
147
147
  background-color: var(--current-theme-primary-hover);
148
148
  }
149
+ &.disabled {
150
+ background-color: $rock;
151
+ &:hover {
152
+ background-color: $rock;
153
+ }
154
+ }
149
155
  }
150
156
  .fileupload-btn .multifile-select-btn {
151
157
  opacity: 0;
@@ -0,0 +1,155 @@
1
+ import React from "react"
2
+ import { render, screen } from "@testing-library/react"
3
+ import { MockedProvider } from "@apollo/client/testing"
4
+ import { DatasetHistory, GET_HISTORY } from "../dataset-history"
5
+
6
+ describe("DatasetHistory", () => {
7
+ it("renders an example history correctly", async () => {
8
+ const dataset = {
9
+ id: "ds000001",
10
+ name: "Test Dataset",
11
+ created: "2023-01-01T00:00:00.000Z",
12
+ downloads: 0,
13
+ views: 0,
14
+ stars: 0,
15
+ size: 0,
16
+ history: [
17
+ {
18
+ id: "adbafb8cc26558e1fe2be02fa782bf9f1a6c2556",
19
+ date: "2023-01-01T00:00:00.000Z",
20
+ authorName: "Test Author",
21
+ authorEmail: "test@example.com",
22
+ references: "",
23
+ message: "Initial commit",
24
+ },
25
+ {
26
+ id: "adbafb8cc26558e1fe2be02fa782bf9f1a6c0313",
27
+ date: "2023-01-02T00:00:00.000Z",
28
+ authorName: "Test Author",
29
+ authorEmail: "test@example.com",
30
+ references: "1.0.0",
31
+ message: "Test snapshot",
32
+ },
33
+ ],
34
+ authors: [],
35
+ editors: [],
36
+ public: true,
37
+ uploader: null,
38
+ latestSnapshot: null,
39
+ relatedDatasets: [],
40
+ mriqcResults: null,
41
+ tasks: [],
42
+ modalities: [],
43
+ datasetSummary: null,
44
+ datasetDescription: null,
45
+ readme: null,
46
+ license: null,
47
+ funding: null,
48
+ acknowledgements: null,
49
+ howToAcknowledge: null,
50
+ ethicsApprovals: [],
51
+ publications: [],
52
+ datasetMetadata: [],
53
+ changelog: null,
54
+ issues: [],
55
+ starred: false,
56
+ followers: [],
57
+ hasOpenIssues: false,
58
+ createdAt: "2023-01-01T00:00:00.000Z",
59
+ updatedAt: "2023-01-01T00:00:00.000Z",
60
+ uploaderId: null,
61
+ orcid: null,
62
+ userId: null,
63
+ user: null,
64
+ isPrivate: false,
65
+ onboarded: false,
66
+ worker: 0,
67
+ __typename: "Dataset",
68
+ }
69
+ const mocks = [
70
+ {
71
+ request: {
72
+ query: GET_HISTORY,
73
+ variables: { datasetId: dataset.id },
74
+ },
75
+ result: {
76
+ data: {
77
+ dataset,
78
+ },
79
+ },
80
+ },
81
+ ]
82
+ await render(
83
+ <MockedProvider mocks={mocks}>
84
+ <DatasetHistory datasetId={dataset.id} />
85
+ </MockedProvider>,
86
+ )
87
+ expect(await screen.findByText("Initial commit")).toBeInTheDocument()
88
+ expect(await screen.findByText("Test snapshot")).toBeInTheDocument()
89
+ })
90
+ it("renders correctly when dataset.history is null", async () => {
91
+ const dataset = {
92
+ id: "ds000001",
93
+ name: "Test Dataset",
94
+ created: "2023-01-01T00:00:00.000Z",
95
+ downloads: 0,
96
+ views: 0,
97
+ stars: 0,
98
+ size: 0,
99
+ history: null,
100
+ authors: [],
101
+ editors: [],
102
+ public: true,
103
+ uploader: null,
104
+ latestSnapshot: null,
105
+ relatedDatasets: [],
106
+ mriqcResults: null,
107
+ tasks: [],
108
+ modalities: [],
109
+ datasetSummary: null,
110
+ datasetDescription: null,
111
+ readme: null,
112
+ license: null,
113
+ funding: null,
114
+ acknowledgements: null,
115
+ howToAcknowledge: null,
116
+ ethicsApprovals: [],
117
+ publications: [],
118
+ datasetMetadata: [],
119
+ changelog: null,
120
+ issues: [],
121
+ starred: false,
122
+ followers: [],
123
+ hasOpenIssues: false,
124
+ createdAt: "2023-01-01T00:00:00.000Z",
125
+ updatedAt: "2023-01-01T00:00:00.000Z",
126
+ uploaderId: null,
127
+ orcid: null,
128
+ userId: null,
129
+ user: null,
130
+ isPrivate: false,
131
+ onboarded: false,
132
+ worker: 0,
133
+ __typename: "Dataset",
134
+ }
135
+ const mocks = [
136
+ {
137
+ request: {
138
+ query: GET_HISTORY,
139
+ variables: { datasetId: dataset.id },
140
+ },
141
+ result: {
142
+ data: {
143
+ dataset,
144
+ },
145
+ },
146
+ },
147
+ ]
148
+ await render(
149
+ <MockedProvider mocks={mocks}>
150
+ <DatasetHistory datasetId={dataset.id} />
151
+ </MockedProvider>,
152
+ )
153
+ expect(await screen.findByText("No history available")).toBeInTheDocument()
154
+ })
155
+ })
@@ -2,10 +2,9 @@ import React from "react"
2
2
  import PropTypes from "prop-types"
3
3
  import styled from "@emotion/styled"
4
4
  import { gql, useQuery } from "@apollo/client"
5
-
6
5
  import Revalidate from "../mutations/revalidate.jsx"
7
6
 
8
- const GET_HISTORY = gql`
7
+ export const GET_HISTORY = gql`
9
8
  query getHistory($datasetId: ID!) {
10
9
  dataset(id: $datasetId) {
11
10
  id
@@ -29,16 +28,16 @@ const DatasetHistoryTable = styled.div`
29
28
  .row:nth-of-type(2n) {
30
29
  padding-top: 1em;
31
30
  }
32
- .row:nth-of-type(2n + 1) {
31
+ .row:nth-of-type(2n+1) {
33
32
  padding-bottom: 1em;
34
33
  }
35
34
  .row:nth-of-type(4n),
36
- .row:nth-of-type(4n + 1) {
35
+ .row:nth-of-type(4n+1) {
37
36
  background: #f4f4f4;
38
37
  }
39
38
  `
40
39
 
41
- const DatasetHistory = ({ datasetId }) => {
40
+ export const DatasetHistory = ({ datasetId }) => {
42
41
  const { loading, data } = useQuery(GET_HISTORY, {
43
42
  variables: { datasetId },
44
43
  errorPolicy: "all",
@@ -58,7 +57,7 @@ const DatasetHistory = ({ datasetId }) => {
58
57
  <h4 className="col-lg col col-2">References</h4>
59
58
  <h4 className="col-lg col col-2 text--right">Action</h4>
60
59
  </div>
61
- {data.dataset.history.map((commit) => (
60
+ {data.dataset?.history?.map((commit) => (
62
61
  <React.Fragment key={commit.id}>
63
62
  <div className="grid faux-table">
64
63
  <div className="commit col-lg col col-2">
@@ -83,7 +82,7 @@ const DatasetHistory = ({ datasetId }) => {
83
82
  <div className="col-lg col col-12">{commit.message}</div>
84
83
  </div>
85
84
  </React.Fragment>
86
- ))}
85
+ )) || "No history available"}
87
86
  </DatasetHistoryTable>
88
87
  </div>
89
88
  )
@@ -4,6 +4,7 @@
4
4
  import React from "react"
5
5
  import { Route, Routes } from "react-router-dom"
6
6
  import OrcidGeneral from "./orcid/general.jsx"
7
+ import { OrcidEmailWarning } from "./orcid/email-warning.js"
7
8
  import FourOFourPage from "./404page.js"
8
9
 
9
10
  function ErrorRoute() {
@@ -12,6 +13,7 @@ function ErrorRoute() {
12
13
  <div className="panel">
13
14
  <Routes>
14
15
  <Route path="orcid" element={<OrcidGeneral />} />
16
+ <Route path="email-warning" element={<OrcidEmailWarning />} />
15
17
  <Route path="*" element={<FourOFourPage />} />
16
18
  </Routes>
17
19
  </div>
@@ -0,0 +1,21 @@
1
+ import React from "react"
2
+
3
+ export const OrcidEmailWarning = () => (
4
+ <div className="panel-heading">
5
+ <h2>No Email Provided</h2>
6
+ <p>
7
+ To make contributions to OpenNeuro, please provide a contact email
8
+ address.
9
+ </p>
10
+ <p>
11
+ Verify an email and make it available either publicly or to trusted
12
+ institutions to make contributions to OpenNeuro. See our{" "}
13
+ <a href="https://docs.openneuro.org/orcid.html#enabling-trusted-access-to-emails">
14
+ ORCID documentation
15
+ </a>{" "}
16
+ for detailed instructions.
17
+ </p>
18
+ </div>
19
+ )
20
+
21
+ export default OrcidEmailWarning
@@ -236,9 +236,8 @@ const SearchContainer: FC<SearchContainerProps> = ({ portalContent }) => {
236
236
  />
237
237
  {/* TODO: make div below into display component. */}
238
238
  <div className="grid grid-nogutter" style={{ width: "100%" }}>
239
- {resultsList.length > 0 || !hasNextPage
240
- ? null
241
- : (
239
+ {hasNextPage && resultsList.length > 0 &&
240
+ (
242
241
  <div className="col col-12 load-more m-t-10">
243
242
  <Button label="Load More" onClick={loadMore} />
244
243
  </div>
@@ -24,7 +24,7 @@ export const SearchParamsProvider: React.FC<SearchParamsProviderProps> = ({
24
24
  Sentry.addBreadcrumb({
25
25
  category: "routing",
26
26
  message: "No query found in URL. Using initialSearchParams.",
27
- level: "info"
27
+ level: "info",
28
28
  })
29
29
  }
30
30
  } catch (err) {
@@ -79,8 +79,11 @@ export const removeFilterItem = (setSearchParams) => (param, value) => {
79
79
  /* Handle simple filter resets. */
80
80
  case "datasetType_selected":
81
81
  // when datasetType is unset, unset datasetStatus as well
82
- updatedParams["datasetStatus_selected"] =
83
- initialSearchParams["datasetStatus_selected"]
82
+ setSearchParams((prevState) => ({
83
+ ...prevState,
84
+ [param]: initialSearchParams[param],
85
+ "datasetStatus_selected": initialSearchParams["datasetStatus_selected"],
86
+ }))
84
87
  break
85
88
  case "modality_selected":
86
89
  case "brain_initiative":
@@ -266,7 +266,7 @@ export const useSearchResults = () => {
266
266
  matchQuery(
267
267
  "latestSnapshot.description.Authors",
268
268
  joinWithOR(authors),
269
- "3",
269
+ "2",
270
270
  ),
271
271
  )
272
272
  }
@@ -24,7 +24,9 @@ class Upload extends React.Component<UploadProps> {
24
24
  const text = this.props.resume ? "Resume" : "Select folder"
25
25
 
26
26
  return (
27
- <div className="fileupload-btn">
27
+ <div
28
+ className={"fileupload-btn" + (this.props.disabled ? " disabled" : "")}
29
+ >
28
30
  <span>
29
31
  {icon}
30
32
  {text}
@@ -1,30 +1,46 @@
1
1
  import React from "react"
2
2
  import FileSelect from "./file-select"
3
3
  import UploaderContext from "./uploader-context.js"
4
+ import { useUser } from "../queries/user"
4
5
 
5
- const UploadSelect = () => (
6
- <div>
7
- <UploaderContext.Consumer>
8
- {(uploader) => (
9
- <div className="message fade-in">
10
- <p>
11
- To protect the privacy of the individuals who have been scanned, we
12
- require that all scan data be defaced before publishing a dataset.
13
- </p>
14
- Select a{" "}
15
- <a
16
- href="http://bids.neuroimaging.io"
17
- target="_blank"
18
- rel="noopener noreferrer"
19
- >
20
- BIDS dataset
21
- </a>{" "}
22
- to upload
23
- <FileSelect onChange={uploader.selectFiles} />
24
- </div>
25
- )}
26
- </UploaderContext.Consumer>
27
- </div>
28
- )
6
+ const UploadSelect = () => {
7
+ const { user, loading, error } = useUser()
8
+ const disabled = loading || Boolean(error) || !user || !user.email
9
+ const noEmail = !user.email
10
+ return (
11
+ <div>
12
+ <UploaderContext.Consumer>
13
+ {(uploader) => (
14
+ <div className="message fade-in">
15
+ <p>
16
+ To protect the privacy of the individuals who have been scanned,
17
+ we require that all scan data be defaced before publishing a
18
+ dataset.
19
+ </p>
20
+ Select a{" "}
21
+ <a
22
+ href="http://bids.neuroimaging.io"
23
+ target="_blank"
24
+ rel="noopener noreferrer"
25
+ >
26
+ BIDS dataset
27
+ </a>{" "}
28
+ to upload
29
+ <FileSelect onChange={uploader.selectFiles} disabled={disabled} />
30
+ {noEmail && (
31
+ <p>
32
+ Connect a contact email to upload. See our{" "}
33
+ <a href="https://docs.openneuro.org/orcid.html#enabling-trusted-access-to-emails">
34
+ ORCID documentation
35
+ </a>{" "}
36
+ for instructions.
37
+ </p>
38
+ )}
39
+ </div>
40
+ )}
41
+ </UploaderContext.Consumer>
42
+ </div>
43
+ )
44
+ }
29
45
 
30
46
  export default UploadSelect