@openneuro/app 4.34.2 → 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 +4 -4
- package/src/scripts/authentication/profile.ts +3 -5
- package/src/scripts/common/containers/header.tsx +1 -2
- package/src/scripts/common/partials/freshdesk-widget.jsx +5 -1
- package/src/scripts/components/header/Header.tsx +4 -10
- package/src/scripts/components/header/LandingExpandedHeader.tsx +11 -9
- package/src/scripts/components/header/header.scss +4 -2
- package/src/scripts/components/logo/Logo.tsx +1 -1
- package/src/scripts/components/modal/UserLoginModal.tsx +12 -11
- package/src/scripts/components/modal/__tests__/UserLoginModal.spec.tsx +1 -1
- package/src/scripts/components/page/Page.tsx +1 -2
- package/src/scripts/components/scss/upload-modal.scss +6 -0
- package/src/scripts/components/search-page/SearchResultItem.tsx +14 -13
- package/src/scripts/components/search-page/SearchResultsList.tsx +0 -19
- package/src/scripts/dataset/draft-container.tsx +0 -1
- package/src/scripts/dataset/fragments/__tests__/dataset-history.spec.tsx +155 -0
- package/src/scripts/dataset/fragments/dataset-history.jsx +6 -7
- package/src/scripts/dataset/snapshot-container.tsx +13 -11
- package/src/scripts/errors/errorRoute.tsx +2 -0
- package/src/scripts/errors/freshdesk-widget.jsx +5 -1
- package/src/scripts/errors/orcid/email-warning.tsx +21 -0
- package/src/scripts/index.tsx +15 -2
- package/src/scripts/pages/__tests__/orcid-link.spec.tsx +13 -0
- package/src/scripts/pages/orcid-link.tsx +60 -0
- package/src/scripts/queries/user.ts +71 -0
- package/src/scripts/routes.tsx +2 -0
- package/src/scripts/scss/variables.scss +13 -9
- package/src/scripts/search/search-container.tsx +2 -12
- package/src/scripts/search/search-params-ctx.tsx +6 -3
- package/src/scripts/search/use-search-results.tsx +1 -1
- package/src/scripts/types/user-types.ts +2 -0
- package/src/scripts/uploader/file-select.tsx +3 -1
- package/src/scripts/uploader/upload-select.jsx +40 -24
- package/src/scripts/users/__tests__/user-account-view.spec.tsx +36 -22
- package/src/scripts/users/__tests__/user-query.spec.tsx +3 -3
- package/src/scripts/users/__tests__/user-routes.spec.tsx +28 -11
- package/src/scripts/users/__tests__/user-tabs.spec.tsx +12 -9
- package/src/scripts/users/scss/user-menu.scss +133 -0
- package/src/scripts/users/scss/usernotifications.module.scss +1 -1
- package/src/scripts/users/user-account-view.tsx +35 -21
- package/src/scripts/users/user-container.tsx +2 -1
- package/src/scripts/users/user-menu.tsx +114 -0
- package/src/scripts/users/user-notification-accordion.tsx +2 -2
- package/src/scripts/users/user-query.tsx +6 -36
- package/src/scripts/users/user-routes.tsx +7 -5
- package/src/scripts/users/user-tabs.tsx +4 -3
- package/src/scripts/validation/__tests__/__snapshots__/validation-issues.spec.tsx.snap +10 -1
- package/src/scripts/validation/validation-issues.tsx +7 -3
- package/src/scripts/components/user/UserMenu.tsx +0 -72
- package/src/scripts/components/user/user-menu.scss +0 -88
|
@@ -80,7 +80,7 @@ export const SnapshotContainer: React.FC<SnapshotContainerProps> = ({
|
|
|
80
80
|
dataset.snapshots[dataset.snapshots.length - 1].hexsha
|
|
81
81
|
const modality: string = summary?.modalities[0] || ""
|
|
82
82
|
const hasDerivatives = dataset?.derivatives.length > 0
|
|
83
|
-
|
|
83
|
+
const isAnonymousReviewer = profile?.scopes?.includes("dataset:reviewer")
|
|
84
84
|
return (
|
|
85
85
|
<>
|
|
86
86
|
<Helmet>
|
|
@@ -263,16 +263,18 @@ export const SnapshotContainer: React.FC<SnapshotContainerProps> = ({
|
|
|
263
263
|
/>
|
|
264
264
|
</>
|
|
265
265
|
))}
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
266
|
+
{!isAnonymousReviewer &&
|
|
267
|
+
(
|
|
268
|
+
<MetaDataBlock
|
|
269
|
+
heading="Uploaded by"
|
|
270
|
+
item={
|
|
271
|
+
<>
|
|
272
|
+
<Username user={dataset.uploader} /> on{" "}
|
|
273
|
+
<DateDistance date={dataset.created} />
|
|
274
|
+
</>
|
|
275
|
+
}
|
|
276
|
+
/>
|
|
277
|
+
)}
|
|
276
278
|
|
|
277
279
|
{dataset.snapshots.length && (
|
|
278
280
|
<MetaDataBlock
|
|
@@ -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>
|
|
@@ -3,6 +3,7 @@ import PropTypes from "prop-types"
|
|
|
3
3
|
import { useCookies } from "react-cookie"
|
|
4
4
|
import { getProfile } from "../authentication/profile"
|
|
5
5
|
import { config } from "../config"
|
|
6
|
+
import { useUser } from "../queries/user"
|
|
6
7
|
|
|
7
8
|
const buildCustomQuery = (customText, prepopulatedFields) => {
|
|
8
9
|
const customizerQueries = [
|
|
@@ -31,8 +32,11 @@ function FreshdeskWidget({ subject, error, sentryId, description }) {
|
|
|
31
32
|
screenshot: "No",
|
|
32
33
|
captcha: "yes",
|
|
33
34
|
}
|
|
35
|
+
|
|
36
|
+
const { user } = useUser()
|
|
37
|
+
|
|
34
38
|
const prepopulatedFields = {
|
|
35
|
-
requester: profile &&
|
|
39
|
+
requester: profile && user?.email,
|
|
36
40
|
subject,
|
|
37
41
|
description: joinedDescription,
|
|
38
42
|
}
|
|
@@ -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
|
package/src/scripts/index.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from "react"
|
|
1
|
+
import React, { useEffect } from "react"
|
|
2
2
|
import Uploader from "./uploader/uploader.jsx"
|
|
3
3
|
import AppRoutes from "./routes"
|
|
4
4
|
import HeaderContainer from "./common/containers/header"
|
|
@@ -6,11 +6,24 @@ import FooterContainer from "./common/containers/footer"
|
|
|
6
6
|
import { SearchParamsProvider } from "./search/search-params-ctx"
|
|
7
7
|
import { UserModalOpenProvider } from "./utils/user-login-modal-ctx"
|
|
8
8
|
import { useAnalytics } from "./utils/analytics"
|
|
9
|
-
|
|
9
|
+
import { useLocation, useNavigate } from "react-router-dom"
|
|
10
10
|
import "../assets/email-header.png"
|
|
11
|
+
import { useUser } from "./queries/user.js"
|
|
11
12
|
|
|
12
13
|
const Index = (): React.ReactElement => {
|
|
13
14
|
useAnalytics()
|
|
15
|
+
// Redirect authenticated Google users to the migration step if they are in any other route
|
|
16
|
+
const navigate = useNavigate()
|
|
17
|
+
const location = useLocation()
|
|
18
|
+
const { user, loading, error } = useUser()
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
if (
|
|
21
|
+
!loading && !error && location.pathname !== "/orcid-link" &&
|
|
22
|
+
user?.provider === "google"
|
|
23
|
+
) {
|
|
24
|
+
navigate("/orcid-link")
|
|
25
|
+
}
|
|
26
|
+
}, [location.pathname, user])
|
|
14
27
|
return (
|
|
15
28
|
<Uploader>
|
|
16
29
|
<SearchParamsProvider>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import { render, screen } from "@testing-library/react"
|
|
3
|
+
import { OrcidLinkPage } from "../orcid-link"
|
|
4
|
+
import { vitest } from "vitest"
|
|
5
|
+
|
|
6
|
+
vitest.mock("../../config")
|
|
7
|
+
|
|
8
|
+
describe("OrcidLinkPage", () => {
|
|
9
|
+
it("renders orcid link button", () => {
|
|
10
|
+
render(<OrcidLinkPage />)
|
|
11
|
+
expect(screen.getByRole("button")).toHaveTextContent("Link ORCID")
|
|
12
|
+
})
|
|
13
|
+
})
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import Helmet from "react-helmet"
|
|
3
|
+
import { frontPage } from "./front-page/front-page-content"
|
|
4
|
+
import loginUrls from "../authentication/loginUrls"
|
|
5
|
+
import { Button } from "../components/button/Button"
|
|
6
|
+
import orcidIcon from "../../assets/orcid_24x24.png"
|
|
7
|
+
import styled from "@emotion/styled"
|
|
8
|
+
|
|
9
|
+
const OrcidLinkPageStyle = styled.div`
|
|
10
|
+
background: white;
|
|
11
|
+
|
|
12
|
+
.container {
|
|
13
|
+
max-width: 60em;
|
|
14
|
+
min-height: calc(100vh - 152px);
|
|
15
|
+
}
|
|
16
|
+
`
|
|
17
|
+
|
|
18
|
+
export function OrcidLinkPage() {
|
|
19
|
+
return (
|
|
20
|
+
<OrcidLinkPageStyle>
|
|
21
|
+
<Helmet>
|
|
22
|
+
<title>
|
|
23
|
+
Link ORCID to your existing account - {frontPage.pageTitle}
|
|
24
|
+
</title>
|
|
25
|
+
<meta
|
|
26
|
+
name="description"
|
|
27
|
+
content="How to link your ORCID account to your Google based OpenNeuro account"
|
|
28
|
+
/>
|
|
29
|
+
</Helmet>
|
|
30
|
+
<div className="container">
|
|
31
|
+
<h2>ORCID account migration</h2>
|
|
32
|
+
<p>
|
|
33
|
+
OpenNeuro is moving to ORCID for all accounts. Please link an ORCID to
|
|
34
|
+
your account to continue and use ORCID for future logins. If you have
|
|
35
|
+
used Google login before, any datasets, comments, and permissions you
|
|
36
|
+
have will be merged into the combined OpenNeuro account linked to your
|
|
37
|
+
ORCID iD.
|
|
38
|
+
</p>
|
|
39
|
+
<h3>Why are we making this change?</h3>
|
|
40
|
+
<p>
|
|
41
|
+
ORCID allows richer researcher metadata for contributions and
|
|
42
|
+
optionally sharing contributions to datasets as works on your ORCID
|
|
43
|
+
profile.
|
|
44
|
+
</p>
|
|
45
|
+
<h3>Will Google accounts continue to work?</h3>
|
|
46
|
+
<p>
|
|
47
|
+
To make new contributions you will need link an ORCID but any existing
|
|
48
|
+
contributions will remain available.
|
|
49
|
+
</p>
|
|
50
|
+
<a href={loginUrls.orcid + `?migrate`}>
|
|
51
|
+
<Button
|
|
52
|
+
className="login-button"
|
|
53
|
+
label="Link ORCID"
|
|
54
|
+
imgSrc={orcidIcon}
|
|
55
|
+
/>
|
|
56
|
+
</a>
|
|
57
|
+
</div>
|
|
58
|
+
</OrcidLinkPageStyle>
|
|
59
|
+
)
|
|
60
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { gql, useQuery } from "@apollo/client"
|
|
2
|
+
import { useCookies } from "react-cookie"
|
|
3
|
+
import { getProfile } from "../authentication/profile"
|
|
4
|
+
import * as Sentry from "@sentry/react"
|
|
5
|
+
|
|
6
|
+
// GraphQL query to fetch user data
|
|
7
|
+
export const GET_USER = gql`
|
|
8
|
+
query User($userId: ID!) {
|
|
9
|
+
user(id: $userId) {
|
|
10
|
+
id
|
|
11
|
+
name
|
|
12
|
+
orcid
|
|
13
|
+
email
|
|
14
|
+
avatar
|
|
15
|
+
location
|
|
16
|
+
institution
|
|
17
|
+
links
|
|
18
|
+
provider
|
|
19
|
+
admin
|
|
20
|
+
created
|
|
21
|
+
lastSeen
|
|
22
|
+
blocked
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
`
|
|
26
|
+
|
|
27
|
+
export const UPDATE_USER = gql`
|
|
28
|
+
mutation updateUser(
|
|
29
|
+
$id: ID!
|
|
30
|
+
$location: String
|
|
31
|
+
$links: [String]
|
|
32
|
+
$institution: String
|
|
33
|
+
) {
|
|
34
|
+
updateUser(
|
|
35
|
+
id: $id
|
|
36
|
+
location: $location
|
|
37
|
+
links: $links
|
|
38
|
+
institution: $institution
|
|
39
|
+
) {
|
|
40
|
+
id
|
|
41
|
+
location
|
|
42
|
+
links
|
|
43
|
+
institution
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
`
|
|
47
|
+
|
|
48
|
+
// Reusable hook to fetch user data
|
|
49
|
+
export const useUser = () => {
|
|
50
|
+
const [cookies] = useCookies()
|
|
51
|
+
const profile = getProfile(cookies)
|
|
52
|
+
const profileSub = profile?.sub
|
|
53
|
+
|
|
54
|
+
const { data: userData, loading: userLoading, error: userError } = useQuery(
|
|
55
|
+
GET_USER,
|
|
56
|
+
{
|
|
57
|
+
variables: { userId: profileSub },
|
|
58
|
+
skip: !profileSub,
|
|
59
|
+
},
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
if (userError) {
|
|
63
|
+
Sentry.captureException(userError)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
user: userData?.user,
|
|
68
|
+
loading: userLoading,
|
|
69
|
+
error: userError,
|
|
70
|
+
}
|
|
71
|
+
}
|
package/src/scripts/routes.tsx
CHANGED
|
@@ -22,6 +22,7 @@ import { UserQuery } from "./users/user-query"
|
|
|
22
22
|
import LoggedIn from "../scripts/authentication/logged-in"
|
|
23
23
|
import LoggedOut from "../scripts/authentication/logged-out"
|
|
24
24
|
import FourOThreePage from "./errors/403page"
|
|
25
|
+
import { OrcidLinkPage } from "./pages/orcid-link"
|
|
25
26
|
|
|
26
27
|
const AppRoutes: React.VoidFunctionComponent = () => (
|
|
27
28
|
<Routes>
|
|
@@ -39,6 +40,7 @@ const AppRoutes: React.VoidFunctionComponent = () => (
|
|
|
39
40
|
<Route path="/import" element={<ImportDataset />} />
|
|
40
41
|
<Route path="/metadata" element={<DatasetMetadata />} />
|
|
41
42
|
<Route path="/public" element={<Navigate to="/search" replace />} />
|
|
43
|
+
<Route path="/orcid-link" element={<OrcidLinkPage />} />
|
|
42
44
|
<Route
|
|
43
45
|
path="/user/:orcid/*"
|
|
44
46
|
element={
|
|
@@ -85,6 +85,16 @@ $on-light-orange: rgb(255, 110, 43);
|
|
|
85
85
|
$primary: $on-dark-aqua;
|
|
86
86
|
|
|
87
87
|
$chiclet-color: #a2059e;
|
|
88
|
+
//Base unit used for spacing gutters
|
|
89
|
+
$base-unit: 20px;
|
|
90
|
+
|
|
91
|
+
//Screen Sizes
|
|
92
|
+
$screen-sm: 480px;
|
|
93
|
+
$screen-md: 767px;
|
|
94
|
+
$screen-lg: 989px;
|
|
95
|
+
|
|
96
|
+
$border-radius-default: 4px;
|
|
97
|
+
|
|
88
98
|
|
|
89
99
|
/* CSS HEX */
|
|
90
100
|
:root {
|
|
@@ -105,6 +115,9 @@ $chiclet-color: #a2059e;
|
|
|
105
115
|
--current-theme-primary-light: #{$on-dark-aqua-light};
|
|
106
116
|
--current-theme-header: #333;
|
|
107
117
|
--current-theme-header-dark: #333;
|
|
118
|
+
--font-sans: #{$font-sans};
|
|
119
|
+
--border-radius-default: #{$border-radius-default};
|
|
120
|
+
--newspaper: #{$newspaper};
|
|
108
121
|
}
|
|
109
122
|
|
|
110
123
|
.mri-theme {
|
|
@@ -139,15 +152,6 @@ $chiclet-color: #a2059e;
|
|
|
139
152
|
background: #f3f3f3;
|
|
140
153
|
}
|
|
141
154
|
|
|
142
|
-
//Base unit used for spacing gutters
|
|
143
|
-
$base-unit: 20px;
|
|
144
|
-
|
|
145
|
-
//Screen Sizes
|
|
146
|
-
$screen-sm: 480px;
|
|
147
|
-
$screen-md: 767px;
|
|
148
|
-
$screen-lg: 989px;
|
|
149
|
-
|
|
150
|
-
$border-radius-default: 4px;
|
|
151
155
|
|
|
152
156
|
.search-page-mri,
|
|
153
157
|
.dataset-page-mri {
|
|
@@ -4,10 +4,6 @@ import { useLocation } from "react-router-dom"
|
|
|
4
4
|
import { SearchPage } from "../components/search-page/SearchPage"
|
|
5
5
|
import { SearchResultsList } from "../components/search-page/SearchResultsList"
|
|
6
6
|
import { NeurobagelSearch } from "../components/search-page/NeurobagelSearch"
|
|
7
|
-
import {
|
|
8
|
-
getUnexpiredProfile,
|
|
9
|
-
hasEditPermissions,
|
|
10
|
-
} from "../authentication/profile"
|
|
11
7
|
import { Button } from "../components/button/Button"
|
|
12
8
|
import { Loading } from "../components/loading/Loading"
|
|
13
9
|
import {
|
|
@@ -36,7 +32,6 @@ import {
|
|
|
36
32
|
} from "./inputs"
|
|
37
33
|
import FiltersBlockContainer from "./filters-block-container"
|
|
38
34
|
import AggregateCountsContainer from "../pages/front-page/aggregate-queries/aggregate-counts-container"
|
|
39
|
-
import { useCookies } from "react-cookie"
|
|
40
35
|
import { useSearchResults } from "./use-search-results"
|
|
41
36
|
import { SearchParamsCtx } from "./search-params-ctx"
|
|
42
37
|
import type { SearchParams } from "./initial-search-params"
|
|
@@ -113,8 +108,6 @@ export const setDefaultSearch = (
|
|
|
113
108
|
}
|
|
114
109
|
|
|
115
110
|
const SearchContainer: FC<SearchContainerProps> = ({ portalContent }) => {
|
|
116
|
-
const [cookies] = useCookies()
|
|
117
|
-
const profile = getUnexpiredProfile(cookies)
|
|
118
111
|
const location = useLocation()
|
|
119
112
|
|
|
120
113
|
const { searchParams, setSearchParams } = useContext(SearchParamsCtx)
|
|
@@ -238,16 +231,13 @@ const SearchContainer: FC<SearchContainerProps> = ({ portalContent }) => {
|
|
|
238
231
|
: (
|
|
239
232
|
<>
|
|
240
233
|
<SearchResultsList
|
|
241
|
-
hasEditPermissions={hasEditPermissions}
|
|
242
234
|
items={resultsList}
|
|
243
|
-
profile={profile}
|
|
244
235
|
datasetTypeSelected={searchParams.datasetType_selected}
|
|
245
236
|
/>
|
|
246
237
|
{/* TODO: make div below into display component. */}
|
|
247
238
|
<div className="grid grid-nogutter" style={{ width: "100%" }}>
|
|
248
|
-
{resultsList.length > 0
|
|
249
|
-
|
|
250
|
-
: (
|
|
239
|
+
{hasNextPage && resultsList.length > 0 &&
|
|
240
|
+
(
|
|
251
241
|
<div className="col col-12 load-more m-t-10">
|
|
252
242
|
<Button label="Load More" onClick={loadMore} />
|
|
253
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
|
-
|
|
83
|
-
|
|
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":
|
|
@@ -13,6 +13,7 @@ export interface User {
|
|
|
13
13
|
export interface UserRoutesProps {
|
|
14
14
|
user: User
|
|
15
15
|
hasEdit: boolean
|
|
16
|
+
isUser: boolean
|
|
16
17
|
}
|
|
17
18
|
export interface UserCardProps {
|
|
18
19
|
user: User
|
|
@@ -69,4 +70,5 @@ export interface UserDatasetsViewProps {
|
|
|
69
70
|
export interface AccountContainerProps {
|
|
70
71
|
user: User
|
|
71
72
|
hasEdit: boolean
|
|
73
|
+
isUser: boolean
|
|
72
74
|
}
|
|
@@ -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
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { vi } from "vitest"
|
|
1
2
|
import React from "react"
|
|
2
3
|
import { MockedProvider } from "@apollo/client/testing"
|
|
3
4
|
import {
|
|
@@ -8,24 +9,38 @@ import {
|
|
|
8
9
|
within,
|
|
9
10
|
} from "@testing-library/react"
|
|
10
11
|
import { UserAccountView } from "../user-account-view"
|
|
11
|
-
import {
|
|
12
|
+
import type { User } from "../../types/user-types"
|
|
13
|
+
import * as userQueries from "../../queries/user"
|
|
12
14
|
|
|
13
|
-
const baseUser = {
|
|
15
|
+
const baseUser: User = {
|
|
14
16
|
id: "1",
|
|
15
17
|
name: "John Doe",
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
location: "Unknown",
|
|
19
|
+
github: "",
|
|
20
|
+
institution: "Unknown Institution",
|
|
21
|
+
email: "john.doe@example.com",
|
|
22
|
+
avatar: "https://dummyimage.com/200x200/000/fff",
|
|
23
|
+
orcid: "0000-0000-0000-0000",
|
|
24
|
+
links: [],
|
|
22
25
|
}
|
|
23
26
|
|
|
27
|
+
vi.mock("../../queries/user", async () => {
|
|
28
|
+
const actual = await vi.importActual("../../queries/user")
|
|
29
|
+
return {
|
|
30
|
+
...actual,
|
|
31
|
+
useUser: () => ({
|
|
32
|
+
user: baseUser,
|
|
33
|
+
loading: false,
|
|
34
|
+
error: undefined,
|
|
35
|
+
}),
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
|
|
24
39
|
const mocks = [
|
|
25
40
|
{
|
|
26
41
|
request: {
|
|
27
|
-
query:
|
|
28
|
-
variables: {
|
|
42
|
+
query: userQueries.GET_USER,
|
|
43
|
+
variables: { id: baseUser.orcid },
|
|
29
44
|
},
|
|
30
45
|
result: {
|
|
31
46
|
data: {
|
|
@@ -35,9 +50,9 @@ const mocks = [
|
|
|
35
50
|
},
|
|
36
51
|
{
|
|
37
52
|
request: {
|
|
38
|
-
query: UPDATE_USER,
|
|
53
|
+
query: userQueries.UPDATE_USER,
|
|
39
54
|
variables: {
|
|
40
|
-
id: baseUser.
|
|
55
|
+
id: baseUser.orcid,
|
|
41
56
|
location: "Marin, CA",
|
|
42
57
|
links: ["https://newlink.com"],
|
|
43
58
|
institution: "New University",
|
|
@@ -46,7 +61,7 @@ const mocks = [
|
|
|
46
61
|
result: {
|
|
47
62
|
data: {
|
|
48
63
|
updateUser: {
|
|
49
|
-
id: baseUser.
|
|
64
|
+
id: baseUser.orcid,
|
|
50
65
|
location: "Marin, CA",
|
|
51
66
|
links: ["https://newlink.com"],
|
|
52
67
|
institution: "New University",
|
|
@@ -60,23 +75,22 @@ describe("<UserAccountView />", () => {
|
|
|
60
75
|
it("should render the user details correctly", () => {
|
|
61
76
|
render(
|
|
62
77
|
<MockedProvider mocks={mocks} addTypename={false}>
|
|
63
|
-
<UserAccountView
|
|
78
|
+
<UserAccountView />
|
|
64
79
|
</MockedProvider>,
|
|
65
80
|
)
|
|
66
81
|
expect(screen.getByText("Name:")).toBeInTheDocument()
|
|
67
82
|
expect(screen.getByText("John Doe")).toBeInTheDocument()
|
|
68
83
|
expect(screen.getByText("Email:")).toBeInTheDocument()
|
|
69
|
-
expect(screen.getByText("
|
|
84
|
+
expect(screen.getByText("john.doe@example.com")).toBeInTheDocument()
|
|
70
85
|
expect(screen.getByText("ORCID:")).toBeInTheDocument()
|
|
71
|
-
expect(screen.getByText("0000-
|
|
72
|
-
expect(screen.getByText("GitHub
|
|
73
|
-
expect(screen.getByText("johndoe")).toBeInTheDocument()
|
|
86
|
+
expect(screen.getByText("0000-0000-0000-0000")).toBeInTheDocument()
|
|
87
|
+
expect(screen.getByText("Connect your GitHub")).toBeInTheDocument()
|
|
74
88
|
})
|
|
75
89
|
|
|
76
90
|
it("should render location with EditableContent", async () => {
|
|
77
91
|
render(
|
|
78
92
|
<MockedProvider mocks={mocks} addTypename={false}>
|
|
79
|
-
<UserAccountView
|
|
93
|
+
<UserAccountView />
|
|
80
94
|
</MockedProvider>,
|
|
81
95
|
)
|
|
82
96
|
const locationSection = within(screen.getByTestId("location-section"))
|
|
@@ -95,7 +109,7 @@ describe("<UserAccountView />", () => {
|
|
|
95
109
|
it("should render institution with EditableContent", async () => {
|
|
96
110
|
render(
|
|
97
111
|
<MockedProvider mocks={mocks} addTypename={false}>
|
|
98
|
-
<UserAccountView
|
|
112
|
+
<UserAccountView />
|
|
99
113
|
</MockedProvider>,
|
|
100
114
|
)
|
|
101
115
|
const institutionSection = within(screen.getByTestId("institution-section"))
|
|
@@ -114,7 +128,7 @@ describe("<UserAccountView />", () => {
|
|
|
114
128
|
it("should render links with EditableContent and validation", async () => {
|
|
115
129
|
render(
|
|
116
130
|
<MockedProvider mocks={mocks} addTypename={false}>
|
|
117
|
-
<UserAccountView
|
|
131
|
+
<UserAccountView />
|
|
118
132
|
</MockedProvider>,
|
|
119
133
|
)
|
|
120
134
|
const linksSection = within(screen.getByTestId("links-section"))
|
|
@@ -133,7 +147,7 @@ describe("<UserAccountView />", () => {
|
|
|
133
147
|
it("should show an error message when invalid URL is entered in links section", async () => {
|
|
134
148
|
render(
|
|
135
149
|
<MockedProvider mocks={mocks} addTypename={false}>
|
|
136
|
-
<UserAccountView
|
|
150
|
+
<UserAccountView />
|
|
137
151
|
</MockedProvider>,
|
|
138
152
|
)
|
|
139
153
|
const linksSection = within(screen.getByTestId("links-section"))
|
|
@@ -3,7 +3,7 @@ import { render, screen, waitFor } from "@testing-library/react"
|
|
|
3
3
|
import { MockedProvider } from "@apollo/client/testing"
|
|
4
4
|
import { MemoryRouter, Route, Routes } from "react-router-dom"
|
|
5
5
|
import { UserQuery } from "../user-query"
|
|
6
|
-
import {
|
|
6
|
+
import { GET_USER } from "../../queries/user"
|
|
7
7
|
import * as ProfileUtils from "../../authentication/profile"
|
|
8
8
|
|
|
9
9
|
Object.defineProperty(ProfileUtils, "getProfile", {
|
|
@@ -15,8 +15,8 @@ const validOrcid = "0009-0001-9689-7232"
|
|
|
15
15
|
|
|
16
16
|
const userMock = {
|
|
17
17
|
request: {
|
|
18
|
-
query:
|
|
19
|
-
variables: { userId:
|
|
18
|
+
query: GET_USER,
|
|
19
|
+
variables: { userId: "1" },
|
|
20
20
|
},
|
|
21
21
|
result: {
|
|
22
22
|
data: {
|