@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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openneuro/app",
|
|
3
|
-
"version": "4.
|
|
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.
|
|
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",
|
|
@@ -69,11 +69,11 @@
|
|
|
69
69
|
"sass": "^1.32.8",
|
|
70
70
|
"stream-browserify": "^3.0.0",
|
|
71
71
|
"typescript": "5.6.3",
|
|
72
|
-
"vite": "5.4.
|
|
72
|
+
"vite": "5.4.19",
|
|
73
73
|
"vitest": "2.1.2"
|
|
74
74
|
},
|
|
75
75
|
"publishConfig": {
|
|
76
76
|
"access": "public"
|
|
77
77
|
},
|
|
78
|
-
"gitHead": "
|
|
78
|
+
"gitHead": "ac9c013877e5d9c1fa9615a177f79e0da0747f84"
|
|
79
79
|
}
|
|
@@ -2,12 +2,10 @@ import jwtDecode from "jwt-decode"
|
|
|
2
2
|
|
|
3
3
|
export interface OpenNeuroTokenProfile {
|
|
4
4
|
sub: string
|
|
5
|
-
email: string
|
|
6
|
-
provider: string
|
|
7
|
-
name: string
|
|
8
5
|
admin: boolean
|
|
9
6
|
iat: number
|
|
10
7
|
exp: number
|
|
8
|
+
scopes?: string[]
|
|
11
9
|
}
|
|
12
10
|
|
|
13
11
|
/**
|
|
@@ -21,9 +19,9 @@ export const parseJwt = jwtDecode
|
|
|
21
19
|
*/
|
|
22
20
|
export function getProfile(cookies): OpenNeuroTokenProfile {
|
|
23
21
|
const accessToken = cookies["accessToken"]
|
|
24
|
-
|
|
22
|
+
if (!accessToken) return null
|
|
23
|
+
return parseJwt(accessToken) as OpenNeuroTokenProfile
|
|
25
24
|
}
|
|
26
|
-
|
|
27
25
|
/**
|
|
28
26
|
* Return profile if token is not expired.
|
|
29
27
|
* @param {*} cookies
|
|
@@ -105,9 +105,8 @@ export const HeaderContainer: FC = () => {
|
|
|
105
105
|
</UploaderContext.Consumer>
|
|
106
106
|
)}
|
|
107
107
|
renderOnFreshDeskWidget={() => <FreshdeskWidget />}
|
|
108
|
-
renderOnExpanded={(
|
|
108
|
+
renderOnExpanded={() => (
|
|
109
109
|
<LandingExpandedHeader
|
|
110
|
-
user={profile}
|
|
111
110
|
loginUrls={loginUrls}
|
|
112
111
|
renderAggregateCounts={(modality: string) => (
|
|
113
112
|
<AggregateCountsContainer modality={modality} />
|
|
@@ -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,11 +32,14 @@ function FreshdeskWidget({ subject, error, sentryId, description }) {
|
|
|
31
32
|
screenshot: "No",
|
|
32
33
|
captcha: "yes",
|
|
33
34
|
}
|
|
35
|
+
const { user } = useUser()
|
|
36
|
+
|
|
34
37
|
const prepopulatedFields = {
|
|
35
|
-
requester: profile &&
|
|
38
|
+
requester: profile && user?.email,
|
|
36
39
|
subject,
|
|
37
40
|
description: joinedDescription,
|
|
38
41
|
}
|
|
42
|
+
|
|
39
43
|
return (
|
|
40
44
|
<>
|
|
41
45
|
<script
|
|
@@ -3,16 +3,12 @@ import { NavLink } from "react-router-dom"
|
|
|
3
3
|
import { Button } from "../button/Button"
|
|
4
4
|
import { Logo } from "../logo/Logo"
|
|
5
5
|
import { Modal } from "../modal/Modal"
|
|
6
|
-
import { UserMenu } from "
|
|
6
|
+
import { UserMenu } from "../../users/user-menu"
|
|
7
|
+
import type { OpenNeuroTokenProfile } from "../../authentication/profile"
|
|
7
8
|
import "./header.scss"
|
|
8
9
|
|
|
9
10
|
export interface HeaderProps {
|
|
10
|
-
profile
|
|
11
|
-
name: string
|
|
12
|
-
admin: boolean
|
|
13
|
-
email: string
|
|
14
|
-
provider: string
|
|
15
|
-
}
|
|
11
|
+
profile: OpenNeuroTokenProfile
|
|
16
12
|
expanded?: boolean
|
|
17
13
|
isOpenSupport: boolean
|
|
18
14
|
toggleLoginModal: (
|
|
@@ -39,7 +35,6 @@ export const Header = ({
|
|
|
39
35
|
renderUploader,
|
|
40
36
|
}: HeaderProps) => {
|
|
41
37
|
const [isOpen, setOpen] = React.useState(false)
|
|
42
|
-
|
|
43
38
|
return (
|
|
44
39
|
<>
|
|
45
40
|
<header>
|
|
@@ -109,7 +104,6 @@ export const Header = ({
|
|
|
109
104
|
? (
|
|
110
105
|
<div className="header-account-btn">
|
|
111
106
|
<UserMenu
|
|
112
|
-
profile={profile}
|
|
113
107
|
signOutAndRedirect={signOutAndRedirect}
|
|
114
108
|
/>
|
|
115
109
|
</div>
|
|
@@ -120,7 +114,7 @@ export const Header = ({
|
|
|
120
114
|
navbar
|
|
121
115
|
onClick={toggleLoginModal}
|
|
122
116
|
label="Sign in"
|
|
123
|
-
size="
|
|
117
|
+
size="small"
|
|
124
118
|
/>
|
|
125
119
|
</>
|
|
126
120
|
)}
|
|
@@ -6,11 +6,12 @@ import { ModalityCube } from "../modality-cube/ModalityCube"
|
|
|
6
6
|
import { frontPage } from "../../common/content/front-page-content"
|
|
7
7
|
import { cubeData } from "../../common/content/modality-cube-content"
|
|
8
8
|
import orcidIcon from "../../../assets/orcid_24x24.png"
|
|
9
|
+
import { loginCheck } from "../../authentication/loginCheck"
|
|
10
|
+
import { useCookies } from "react-cookie"
|
|
9
11
|
|
|
10
12
|
import "./header.scss"
|
|
11
13
|
|
|
12
14
|
export interface LandingExpandedHeaderProps {
|
|
13
|
-
user?: object
|
|
14
15
|
loginUrls?: Record<string, string>
|
|
15
16
|
renderAggregateCounts?: (modality?: string) => React.ReactNode
|
|
16
17
|
renderFacetSelect: () => React.ReactNode
|
|
@@ -19,7 +20,6 @@ export interface LandingExpandedHeaderProps {
|
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
export const LandingExpandedHeader: React.FC<LandingExpandedHeaderProps> = ({
|
|
22
|
-
user,
|
|
23
23
|
loginUrls,
|
|
24
24
|
renderAggregateCounts,
|
|
25
25
|
renderFacetSelect,
|
|
@@ -29,6 +29,7 @@ export const LandingExpandedHeader: React.FC<LandingExpandedHeaderProps> = ({
|
|
|
29
29
|
const aggregateCounts = (modality: string): React.ReactNode =>
|
|
30
30
|
renderAggregateCounts ? renderAggregateCounts(modality) : null
|
|
31
31
|
const navigate = useNavigate()
|
|
32
|
+
const [cookies] = useCookies()
|
|
32
33
|
const hexGrid = (
|
|
33
34
|
<ul id="hexGrid">
|
|
34
35
|
{cubeData.map((item, index) => (
|
|
@@ -47,6 +48,7 @@ export const LandingExpandedHeader: React.FC<LandingExpandedHeaderProps> = ({
|
|
|
47
48
|
))}
|
|
48
49
|
</ul>
|
|
49
50
|
)
|
|
51
|
+
const isLoggedIn = loginCheck(cookies)
|
|
50
52
|
|
|
51
53
|
return (
|
|
52
54
|
<div className="expaned-header" style={{ minHeight: "720px" }}>
|
|
@@ -72,28 +74,28 @@ export const LandingExpandedHeader: React.FC<LandingExpandedHeaderProps> = ({
|
|
|
72
74
|
</div>
|
|
73
75
|
</div>
|
|
74
76
|
|
|
75
|
-
{!
|
|
77
|
+
{!isLoggedIn
|
|
76
78
|
? (
|
|
77
79
|
<div className="grid grid-start hero-signin">
|
|
78
80
|
<div className=" hero-sigin-label">
|
|
79
81
|
<h3>SIGN IN</h3>
|
|
80
82
|
</div>
|
|
81
83
|
<div>
|
|
82
|
-
<a href={loginUrls.
|
|
84
|
+
<a href={loginUrls.orcid}>
|
|
83
85
|
<Button
|
|
84
|
-
label="
|
|
86
|
+
label="ORCID"
|
|
85
87
|
color="#fff"
|
|
86
|
-
|
|
88
|
+
imgSrc={orcidIcon}
|
|
87
89
|
iconSize="23px"
|
|
88
90
|
/>
|
|
89
91
|
</a>
|
|
90
92
|
</div>
|
|
91
93
|
<div>
|
|
92
|
-
<a href={loginUrls.
|
|
94
|
+
<a href={loginUrls.google}>
|
|
93
95
|
<Button
|
|
94
|
-
label="
|
|
96
|
+
label="Google"
|
|
95
97
|
color="#fff"
|
|
96
|
-
|
|
98
|
+
icon="fab fa-google"
|
|
97
99
|
iconSize="23px"
|
|
98
100
|
/>
|
|
99
101
|
</a>
|
|
@@ -34,6 +34,8 @@ header {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
.navbar-navigation {
|
|
37
|
+
margin-left: auto;
|
|
38
|
+
margin-right: 20px;
|
|
37
39
|
@media (max-width: 800px) {
|
|
38
40
|
margin-top: 20px;
|
|
39
41
|
margin-left: auto;
|
|
@@ -133,6 +135,7 @@ header {
|
|
|
133
135
|
right: 8px;
|
|
134
136
|
top: 8px;
|
|
135
137
|
}
|
|
138
|
+
|
|
136
139
|
&.nav-open {
|
|
137
140
|
.navbar-navigation ul {
|
|
138
141
|
transform: translateY(0);
|
|
@@ -200,8 +203,7 @@ header {
|
|
|
200
203
|
align-items: center;
|
|
201
204
|
}
|
|
202
205
|
> div {
|
|
203
|
-
flex-basis:
|
|
204
|
-
max-width: 50%;
|
|
206
|
+
flex-basis: 100%;
|
|
205
207
|
margin: 0 10px 0 0;
|
|
206
208
|
&:last-child {
|
|
207
209
|
margin: 0 0 0 10px;
|
|
@@ -29,17 +29,6 @@ export const UserLoginModal = ({
|
|
|
29
29
|
<h2>Sign in</h2>
|
|
30
30
|
</div>
|
|
31
31
|
<div className="sign-in-modal-content">
|
|
32
|
-
<div>
|
|
33
|
-
<a href={loginUrls.google + `?redirectPath=${btoa(redirectPath)}`}>
|
|
34
|
-
<Button
|
|
35
|
-
className="login-button"
|
|
36
|
-
primary
|
|
37
|
-
label="Google"
|
|
38
|
-
icon="fab fa-google"
|
|
39
|
-
iconSize="23px"
|
|
40
|
-
/>
|
|
41
|
-
</a>
|
|
42
|
-
</div>
|
|
43
32
|
<div>
|
|
44
33
|
<a href={loginUrls.orcid + `?redirectPath=${btoa(redirectPath)}`}>
|
|
45
34
|
<Button
|
|
@@ -66,6 +55,18 @@ export const UserLoginModal = ({
|
|
|
66
55
|
}
|
|
67
56
|
/>
|
|
68
57
|
</AccordionWrap>
|
|
58
|
+
<div>
|
|
59
|
+
<a
|
|
60
|
+
href={loginUrls.google + `?redirectPath=${btoa(redirectPath)}`}
|
|
61
|
+
>
|
|
62
|
+
<Button
|
|
63
|
+
className="login-button"
|
|
64
|
+
label="Migrate Google to ORCID"
|
|
65
|
+
icon="fab fa-google"
|
|
66
|
+
iconSize="23px"
|
|
67
|
+
/>
|
|
68
|
+
</a>
|
|
69
|
+
</div>
|
|
69
70
|
</div>
|
|
70
71
|
</div>
|
|
71
72
|
</Modal>
|
|
@@ -31,7 +31,7 @@ describe("UserLoginModal component", () => {
|
|
|
31
31
|
</MemoryRouter>,
|
|
32
32
|
)
|
|
33
33
|
expect(
|
|
34
|
-
screen.getByRole("link", { name:
|
|
34
|
+
screen.getByRole("link", { name: "ORCID" }).getAttribute("href"),
|
|
35
35
|
).toBe("https://openneuro.org/crn/auth/orcid?redirectPath=L2ltcG9ydA==")
|
|
36
36
|
})
|
|
37
37
|
})
|
|
@@ -32,9 +32,8 @@ export const Page = ({ children, headerArgs, className }: PageProps) => {
|
|
|
32
32
|
navigateToNewSearch={() => console.log("go to /search")}
|
|
33
33
|
renderUploader={() => <li>Upload</li>}
|
|
34
34
|
renderOnFreshDeskWidget={() => <>This is a freshdesk widget</>}
|
|
35
|
-
renderOnExpanded={(
|
|
35
|
+
renderOnExpanded={() => (
|
|
36
36
|
<LandingExpandedHeader
|
|
37
|
-
user={profile}
|
|
38
37
|
renderFacetSelect={() => <>front facet example</>}
|
|
39
38
|
renderSearchInput={() => (
|
|
40
39
|
<Input
|
|
@@ -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;
|
|
@@ -3,14 +3,15 @@ import bytes from "bytes"
|
|
|
3
3
|
import parseISO from "date-fns/parseISO"
|
|
4
4
|
import formatDistanceToNow from "date-fns/formatDistanceToNow"
|
|
5
5
|
import { Link } from "react-router-dom"
|
|
6
|
-
|
|
7
6
|
import { Tooltip } from "../../components/tooltip/Tooltip"
|
|
8
7
|
import { Icon } from "../../components/icon/Icon"
|
|
9
|
-
|
|
8
|
+
import { useCookies } from "react-cookie"
|
|
9
|
+
import { getProfile } from "../../authentication/profile"
|
|
10
|
+
import { useUser } from "../../queries/user"
|
|
10
11
|
import "./search-result.scss"
|
|
11
12
|
import activityPulseIcon from "../../../assets/activity-icon.png"
|
|
12
13
|
import { ModalityLabel } from "../../components/formatting/modality-label"
|
|
13
|
-
|
|
14
|
+
import { hasEditPermissions } from "../../authentication/profile"
|
|
14
15
|
/**
|
|
15
16
|
* Return an equivalent to moment(date).format('L') without moment
|
|
16
17
|
* @param {*} dateObject
|
|
@@ -110,20 +111,20 @@ export interface SearchResultItemProps {
|
|
|
110
111
|
},
|
|
111
112
|
]
|
|
112
113
|
}
|
|
113
|
-
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
|
114
|
-
profile: Record<string, any> // TODO - Use the actual user type here
|
|
115
114
|
datasetTypeSelected?: string
|
|
116
|
-
hasEditPermissions: (permissions: object, userId: string) => boolean
|
|
117
115
|
}
|
|
118
116
|
|
|
119
117
|
export const SearchResultItem = ({
|
|
120
118
|
node,
|
|
121
|
-
profile,
|
|
122
119
|
datasetTypeSelected,
|
|
123
|
-
hasEditPermissions,
|
|
124
120
|
}: SearchResultItemProps) => {
|
|
125
|
-
const
|
|
126
|
-
const
|
|
121
|
+
const { user } = useUser()
|
|
122
|
+
const [cookies] = useCookies()
|
|
123
|
+
const profile = getProfile(cookies)
|
|
124
|
+
const profileSub = profile?.sub
|
|
125
|
+
|
|
126
|
+
const isAdmin = user?.admin
|
|
127
|
+
const hasEdit = hasEditPermissions(node.permissions, profileSub) || isAdmin
|
|
127
128
|
|
|
128
129
|
const heading = node.latestSnapshot.description?.Name
|
|
129
130
|
const summary = node.latestSnapshot?.summary
|
|
@@ -209,7 +210,7 @@ export const SearchResultItem = ({
|
|
|
209
210
|
const uploader = (
|
|
210
211
|
<div className="uploader">
|
|
211
212
|
<span>Uploaded by:</span>
|
|
212
|
-
{node.uploader
|
|
213
|
+
{node.uploader?.name} on {dateAdded} - {dateAddedDifference} ago
|
|
213
214
|
</div>
|
|
214
215
|
)
|
|
215
216
|
const downloads = node.analytics.downloads
|
|
@@ -318,11 +319,11 @@ export const SearchResultItem = ({
|
|
|
318
319
|
// Test if there's any schema validator errors
|
|
319
320
|
invalid = node.latestSnapshot.validation?.errors > 0
|
|
320
321
|
}
|
|
321
|
-
const shared = !node.public && node.uploader
|
|
322
|
+
const shared = !node.public && node.uploader?.id !== profileSub
|
|
322
323
|
|
|
323
324
|
const MyDatasetsPage = datasetTypeSelected === "My Datasets"
|
|
324
325
|
const datasetPerms = node.permissions.userPermissions.map((item) => {
|
|
325
|
-
if (item.user.id ===
|
|
326
|
+
if (item.user.id === profileSub && item.access !== null) {
|
|
326
327
|
if (item.access === "ro") {
|
|
327
328
|
return "Read Only"
|
|
328
329
|
} else if (item.access === "rw") {
|
|
@@ -1,32 +1,15 @@
|
|
|
1
1
|
import React from "react"
|
|
2
|
-
|
|
3
2
|
import { SearchResultItem } from "./SearchResultItem"
|
|
4
|
-
|
|
5
3
|
import "./search-page.scss"
|
|
6
4
|
|
|
7
|
-
// TODO - unify this type with the one in the app package
|
|
8
|
-
export interface OpenNeuroTokenProfile {
|
|
9
|
-
sub: string
|
|
10
|
-
email: string
|
|
11
|
-
provider: string
|
|
12
|
-
name: string
|
|
13
|
-
admin: boolean
|
|
14
|
-
iat: number
|
|
15
|
-
exp: number
|
|
16
|
-
}
|
|
17
|
-
|
|
18
5
|
export interface SearchResultsListProps {
|
|
19
6
|
items
|
|
20
|
-
profile?: OpenNeuroTokenProfile
|
|
21
7
|
datasetTypeSelected: string
|
|
22
|
-
hasEditPermissions: (permissions: object, userId: string) => boolean
|
|
23
8
|
}
|
|
24
9
|
|
|
25
10
|
export const SearchResultsList = ({
|
|
26
11
|
items,
|
|
27
|
-
profile,
|
|
28
12
|
datasetTypeSelected,
|
|
29
|
-
hasEditPermissions,
|
|
30
13
|
}: SearchResultsListProps) => {
|
|
31
14
|
return (
|
|
32
15
|
<div className="search-results">
|
|
@@ -36,8 +19,6 @@ export const SearchResultsList = ({
|
|
|
36
19
|
<SearchResultItem
|
|
37
20
|
node={data.node}
|
|
38
21
|
key={data.node.id}
|
|
39
|
-
profile={profile}
|
|
40
|
-
hasEditPermissions={hasEditPermissions}
|
|
41
22
|
datasetTypeSelected={datasetTypeSelected}
|
|
42
23
|
/>
|
|
43
24
|
)
|
|
@@ -50,7 +50,6 @@ const DraftContainer: React.FC<DraftContainerProps> = ({ dataset }) => {
|
|
|
50
50
|
const activeDataset = snapshotVersion(location) || "draft"
|
|
51
51
|
|
|
52
52
|
const [selectedVersion, setSelectedVersion] = React.useState(activeDataset)
|
|
53
|
-
|
|
54
53
|
const summary = dataset.draft.summary
|
|
55
54
|
const description = dataset.draft.description
|
|
56
55
|
const datasetId = dataset.id
|
|
@@ -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
|
|
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
|
|
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
|
|
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
|
)
|