@openneuro/app 4.35.0 → 4.36.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 +3 -3
- package/src/client.jsx +1 -0
- package/src/scripts/components/button/button.scss +14 -0
- package/src/scripts/components/page/page.scss +2 -70
- package/src/scripts/components/search-page/SearchResultItem.tsx +3 -1
- package/src/scripts/components/search-page/search-page.scss +1 -13
- package/src/scripts/config.ts +6 -0
- package/src/scripts/dataset/files/__tests__/__snapshots__/file.spec.jsx.snap +2 -2
- package/src/scripts/dataset/files/file.tsx +2 -2
- package/src/scripts/dataset/mutations/__tests__/update-file.spec.tsx +126 -0
- package/src/scripts/dataset/mutations/update-file.jsx +20 -5
- package/src/scripts/dataset/routes/snapshot.tsx +1 -1
- package/src/scripts/errors/errorRoute.tsx +2 -0
- package/src/scripts/pages/admin/__tests__/users.spec.tsx +51 -18
- package/src/scripts/pages/admin/admin.jsx +2 -2
- package/src/scripts/pages/admin/user-fragment.ts +10 -3
- package/src/scripts/pages/admin/user-summary.tsx +100 -0
- package/src/scripts/pages/admin/user-tools.tsx +81 -58
- package/src/scripts/pages/admin/users.module.scss +277 -0
- package/src/scripts/pages/admin/users.tsx +351 -152
- package/src/scripts/queries/user.ts +120 -3
- package/src/scripts/queries/users.ts +247 -0
- package/src/scripts/routes.tsx +7 -15
- package/src/scripts/types/user-types.ts +12 -13
- package/src/scripts/uploader/file-select.tsx +42 -57
- package/src/scripts/uploader/upload-select.jsx +1 -1
- package/src/scripts/users/__tests__/dataset-card.spec.tsx +127 -0
- package/src/scripts/users/__tests__/user-account-view.spec.tsx +150 -67
- package/src/scripts/users/__tests__/user-card.spec.tsx +6 -17
- package/src/scripts/users/__tests__/user-query.spec.tsx +133 -38
- package/src/scripts/users/__tests__/user-routes.spec.tsx +156 -27
- package/src/scripts/users/__tests__/user-tabs.spec.tsx +7 -7
- package/src/scripts/users/components/edit-list.tsx +26 -5
- package/src/scripts/users/components/edit-string.tsx +40 -13
- package/src/scripts/users/components/editable-content.tsx +10 -3
- package/src/scripts/users/components/user-dataset-filters.tsx +205 -121
- package/src/scripts/users/dataset-card.tsx +3 -2
- package/src/scripts/users/github-auth-button.tsx +98 -0
- package/src/scripts/users/scss/datasetcard.module.scss +65 -12
- package/src/scripts/users/scss/useraccountview.module.scss +1 -1
- package/src/scripts/users/user-account-view.tsx +43 -34
- package/src/scripts/users/user-card.tsx +23 -22
- package/src/scripts/users/user-container.tsx +9 -5
- package/src/scripts/users/user-datasets-view.tsx +350 -40
- package/src/scripts/users/user-menu.tsx +4 -9
- package/src/scripts/users/user-notifications-view.tsx +9 -7
- package/src/scripts/users/user-query.tsx +3 -6
- package/src/scripts/users/user-routes.tsx +11 -5
- package/src/scripts/users/user-tabs.tsx +4 -2
- package/src/scripts/users/__tests__/datasest-card.spec.tsx +0 -201
- package/src/scripts/users/fragments/query.js +0 -42
|
@@ -64,23 +64,18 @@ export const UserMenu = (
|
|
|
64
64
|
</p>
|
|
65
65
|
</li>
|
|
66
66
|
<li>
|
|
67
|
-
{
|
|
68
|
-
/* {user?.orcid
|
|
67
|
+
{user?.orcid
|
|
69
68
|
? <Link to={`/user/${user?.orcid}`}>My Datasets</Link>
|
|
70
|
-
: <Link to="/search?mydatasets">My Datasets</Link>}
|
|
71
|
-
}
|
|
72
|
-
<Link to="/search?mydatasets">My Datasets</Link>
|
|
69
|
+
: <Link to="/search?mydatasets">My Datasets</Link>}
|
|
73
70
|
</li>
|
|
74
71
|
|
|
75
|
-
{
|
|
76
|
-
/* {user?.orcid && (
|
|
72
|
+
{user?.orcid && (
|
|
77
73
|
<li>
|
|
78
74
|
<Link to={`/user/${user?.orcid}/account`}>
|
|
79
75
|
Account Info
|
|
80
76
|
</Link>
|
|
81
77
|
</li>
|
|
82
|
-
)}
|
|
83
|
-
}
|
|
78
|
+
)}
|
|
84
79
|
|
|
85
80
|
<li className="user-menu-link">
|
|
86
81
|
<Link to="/keygen">Obtain an API Key</Link>
|
|
@@ -11,7 +11,7 @@ import iconUnread from "../../assets/icon-unread.png"
|
|
|
11
11
|
import iconSaved from "../../assets/icon-saved.png"
|
|
12
12
|
import iconArchived from "../../assets/icon-archived.png"
|
|
13
13
|
|
|
14
|
-
export const UserNotificationsView = ({
|
|
14
|
+
export const UserNotificationsView = ({ orcidUser }) => {
|
|
15
15
|
const tabsRef = useRef<HTMLUListElement | null>(null)
|
|
16
16
|
const { tab = "unread" } = useParams()
|
|
17
17
|
const navigate = useNavigate()
|
|
@@ -50,18 +50,20 @@ export const UserNotificationsView = ({ user }) => {
|
|
|
50
50
|
// Redirect to default tab if no tab is specified
|
|
51
51
|
useEffect(() => {
|
|
52
52
|
if (!["unread", "saved", "archived"].includes(tab)) {
|
|
53
|
-
navigate(`/user/${
|
|
53
|
+
navigate(`/user/${orcidUser.orcid}/notifications/unread`, {
|
|
54
|
+
replace: true,
|
|
55
|
+
})
|
|
54
56
|
}
|
|
55
|
-
}, [tab,
|
|
57
|
+
}, [tab, orcidUser.orcid, navigate])
|
|
56
58
|
|
|
57
59
|
return (
|
|
58
60
|
<div data-testid="user-notifications-view">
|
|
59
|
-
<h3>Notifications for {
|
|
61
|
+
<h3>Notifications for {orcidUser.name}</h3>
|
|
60
62
|
<div className={styles.tabContainer}>
|
|
61
63
|
<ul className={styles.tabs} ref={tabsRef}>
|
|
62
64
|
<li>
|
|
63
65
|
<NavLink
|
|
64
|
-
to={`/user/${
|
|
66
|
+
to={`/user/${orcidUser.orcid}/notifications/unread`}
|
|
65
67
|
className={({ isActive }) =>
|
|
66
68
|
isActive
|
|
67
69
|
? `${styles.active} ${styles.tabUnread}`
|
|
@@ -73,7 +75,7 @@ export const UserNotificationsView = ({ user }) => {
|
|
|
73
75
|
</li>
|
|
74
76
|
<li>
|
|
75
77
|
<NavLink
|
|
76
|
-
to={`/user/${
|
|
78
|
+
to={`/user/${orcidUser.orcid}/notifications/saved`}
|
|
77
79
|
className={({ isActive }) =>
|
|
78
80
|
isActive
|
|
79
81
|
? `${styles.active} ${styles.tabSaved}`
|
|
@@ -85,7 +87,7 @@ export const UserNotificationsView = ({ user }) => {
|
|
|
85
87
|
</li>
|
|
86
88
|
<li>
|
|
87
89
|
<NavLink
|
|
88
|
-
to={`/user/${
|
|
90
|
+
to={`/user/${orcidUser.orcid}/notifications/archived`}
|
|
89
91
|
className={({ isActive }) =>
|
|
90
92
|
isActive
|
|
91
93
|
? `${styles.active} ${styles.tabArchived}`
|
|
@@ -11,7 +11,7 @@ import { useUser } from "../queries/user"
|
|
|
11
11
|
export const UserQuery: React.FC = () => {
|
|
12
12
|
const { orcid } = useParams()
|
|
13
13
|
const isOrcidValid = orcid && isValidOrcid(orcid)
|
|
14
|
-
const { user, loading, error } = useUser()
|
|
14
|
+
const { user, loading, error } = useUser(orcid)
|
|
15
15
|
|
|
16
16
|
const [cookies] = useCookies()
|
|
17
17
|
const profile = getProfile(cookies)
|
|
@@ -23,16 +23,13 @@ export const UserQuery: React.FC = () => {
|
|
|
23
23
|
|
|
24
24
|
if (loading) return <div>Loading...</div>
|
|
25
25
|
|
|
26
|
-
if (error || !user
|
|
26
|
+
if (error || !user) {
|
|
27
27
|
return <FourOFourPage />
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
if (!profile || !profile.sub) {
|
|
31
|
-
return <FourOFourPage />
|
|
32
|
-
}
|
|
33
30
|
// is admin or profile matches id from the user data being returned
|
|
34
31
|
const isUser = (user?.id === profile?.sub) ? true : false
|
|
35
32
|
const hasEdit = isAdminUser || (user?.id === profile?.sub) ? true : false
|
|
36
33
|
// Render user data with UserRoutes
|
|
37
|
-
return <UserRoutes
|
|
34
|
+
return <UserRoutes orcidUser={user} hasEdit={hasEdit} isUser={isUser} />
|
|
38
35
|
}
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
import type { UserRoutesProps } from "../types/user-types"
|
|
16
16
|
|
|
17
17
|
export const UserRoutes: React.FC<UserRoutesProps> = (
|
|
18
|
-
{
|
|
18
|
+
{ orcidUser, hasEdit, isUser },
|
|
19
19
|
) => {
|
|
20
20
|
return (
|
|
21
21
|
<Routes>
|
|
@@ -23,21 +23,27 @@ export const UserRoutes: React.FC<UserRoutesProps> = (
|
|
|
23
23
|
<Route
|
|
24
24
|
path="*"
|
|
25
25
|
element={
|
|
26
|
-
<UserAccountContainer
|
|
26
|
+
<UserAccountContainer
|
|
27
|
+
orcidUser={orcidUser}
|
|
28
|
+
hasEdit={hasEdit}
|
|
29
|
+
isUser={isUser}
|
|
30
|
+
/>
|
|
27
31
|
}
|
|
28
32
|
>
|
|
29
33
|
<Route
|
|
30
34
|
path=""
|
|
31
|
-
element={<UserDatasetsView
|
|
35
|
+
element={<UserDatasetsView orcidUser={orcidUser} hasEdit={hasEdit} />}
|
|
32
36
|
/>
|
|
33
37
|
<Route
|
|
34
38
|
path="account"
|
|
35
|
-
element={hasEdit
|
|
39
|
+
element={hasEdit
|
|
40
|
+
? <UserAccountView orcidUser={orcidUser} />
|
|
41
|
+
: <FourOThreePage />}
|
|
36
42
|
/>
|
|
37
43
|
<Route
|
|
38
44
|
path="notifications/*"
|
|
39
45
|
element={hasEdit
|
|
40
|
-
? <UserNotificationsView
|
|
46
|
+
? <UserNotificationsView orcidUser={orcidUser} />
|
|
41
47
|
: <FourOThreePage />}
|
|
42
48
|
>
|
|
43
49
|
<Route index element={<UnreadNotifications />} />
|
|
@@ -50,7 +50,8 @@ export const UserAccountTabs: React.FC<UserAccountTabsProps> = (
|
|
|
50
50
|
{isUser ? "My" : "User"} Datasets
|
|
51
51
|
</NavLink>
|
|
52
52
|
</li>
|
|
53
|
-
|
|
53
|
+
{
|
|
54
|
+
/* <li>
|
|
54
55
|
<NavLink
|
|
55
56
|
data-testid="user-notifications-tab"
|
|
56
57
|
to="notifications"
|
|
@@ -59,7 +60,8 @@ export const UserAccountTabs: React.FC<UserAccountTabsProps> = (
|
|
|
59
60
|
>
|
|
60
61
|
Notifications
|
|
61
62
|
</NavLink>
|
|
62
|
-
</li>
|
|
63
|
+
</li> */
|
|
64
|
+
}
|
|
63
65
|
<li>
|
|
64
66
|
<NavLink
|
|
65
67
|
to="account"
|
|
@@ -1,201 +0,0 @@
|
|
|
1
|
-
import React from "react"
|
|
2
|
-
import { fireEvent, render, screen, waitFor } from "@testing-library/react"
|
|
3
|
-
import { DATASETS_QUERY, UserDatasetsView } from "../user-datasets-view"
|
|
4
|
-
import { MockedProvider } from "@apollo/client/testing"
|
|
5
|
-
import DatasetCard from "../dataset-card"
|
|
6
|
-
|
|
7
|
-
// Mocked datasets
|
|
8
|
-
const mockDatasets = [
|
|
9
|
-
{
|
|
10
|
-
node: {
|
|
11
|
-
id: "ds000001",
|
|
12
|
-
name: "The DBS-fMRI dataset",
|
|
13
|
-
created: "2025-01-22T19:55:49.997Z",
|
|
14
|
-
followers: [
|
|
15
|
-
{ userId: "user1", datasetId: "ds000001" },
|
|
16
|
-
{ userId: "user2", datasetId: "ds000001" },
|
|
17
|
-
],
|
|
18
|
-
stars: [
|
|
19
|
-
{ userId: "user1", datasetId: "ds000001" },
|
|
20
|
-
],
|
|
21
|
-
latestSnapshot: {
|
|
22
|
-
id: "ds000001:1.0.0",
|
|
23
|
-
size: 6000,
|
|
24
|
-
created: "2025-01-22T19:55:49.997Z",
|
|
25
|
-
issues: [{ severity: "low" }],
|
|
26
|
-
description: {
|
|
27
|
-
Name: "DBS-FMRI",
|
|
28
|
-
Authors: ["John Doe"],
|
|
29
|
-
SeniorAuthor: "Dr. Smith",
|
|
30
|
-
DatasetType: "fMRI",
|
|
31
|
-
},
|
|
32
|
-
},
|
|
33
|
-
},
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
node: {
|
|
37
|
-
id: "ds000002",
|
|
38
|
-
name: "The DBS-fMRI dataset 2",
|
|
39
|
-
created: "2025-01-22T19:55:49.997Z",
|
|
40
|
-
followers: [
|
|
41
|
-
{ userId: "user1", datasetId: "ds000002" },
|
|
42
|
-
{ userId: "user2", datasetId: "ds000002" },
|
|
43
|
-
],
|
|
44
|
-
stars: [
|
|
45
|
-
{ userId: "user1", datasetId: "ds000002" },
|
|
46
|
-
],
|
|
47
|
-
latestSnapshot: {
|
|
48
|
-
id: "ds000002:1.0.0",
|
|
49
|
-
size: 6000,
|
|
50
|
-
created: "2025-01-22T19:55:49.997Z",
|
|
51
|
-
issues: [{ severity: "medium" }],
|
|
52
|
-
description: {
|
|
53
|
-
Name: "DBS-FMRI 2",
|
|
54
|
-
Authors: ["Jane Doe"],
|
|
55
|
-
SeniorAuthor: "Dr. Johnson",
|
|
56
|
-
DatasetType: "fMRI",
|
|
57
|
-
},
|
|
58
|
-
},
|
|
59
|
-
},
|
|
60
|
-
},
|
|
61
|
-
]
|
|
62
|
-
|
|
63
|
-
describe("<UserDatasetsView />", () => {
|
|
64
|
-
const mockUser = {
|
|
65
|
-
id: "user1",
|
|
66
|
-
name: "John Doe",
|
|
67
|
-
location: "Somewhere",
|
|
68
|
-
institution: "Some University",
|
|
69
|
-
email: "john.doe@example.com",
|
|
70
|
-
}
|
|
71
|
-
const mockHasEdit = true
|
|
72
|
-
|
|
73
|
-
it("renders loading state", () => {
|
|
74
|
-
const mockLoadingQuery = {
|
|
75
|
-
request: {
|
|
76
|
-
query: DATASETS_QUERY,
|
|
77
|
-
variables: { first: 25 },
|
|
78
|
-
},
|
|
79
|
-
result: { data: { datasets: { edges: [] } } },
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
render(
|
|
83
|
-
<MockedProvider mocks={[mockLoadingQuery]} addTypename={false}>
|
|
84
|
-
<UserDatasetsView user={mockUser} hasEdit={mockHasEdit} />
|
|
85
|
-
</MockedProvider>,
|
|
86
|
-
)
|
|
87
|
-
|
|
88
|
-
expect(screen.getByText("Loading datasets...")).toBeInTheDocument()
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
it("renders error state", async () => {
|
|
92
|
-
const mockErrorQuery = {
|
|
93
|
-
request: {
|
|
94
|
-
query: DATASETS_QUERY,
|
|
95
|
-
variables: { first: 25 },
|
|
96
|
-
},
|
|
97
|
-
error: new Error("Failed to fetch datasets"),
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
render(
|
|
101
|
-
<MockedProvider mocks={[mockErrorQuery]} addTypename={false}>
|
|
102
|
-
<UserDatasetsView user={mockUser} hasEdit={mockHasEdit} />
|
|
103
|
-
</MockedProvider>,
|
|
104
|
-
)
|
|
105
|
-
|
|
106
|
-
await waitFor(() => {
|
|
107
|
-
expect(
|
|
108
|
-
screen.getByText("Failed to fetch datasets: Failed to fetch datasets"),
|
|
109
|
-
).toBeInTheDocument()
|
|
110
|
-
})
|
|
111
|
-
})
|
|
112
|
-
|
|
113
|
-
it("filters datasets by public filter", async () => {
|
|
114
|
-
const mockDatasetQuery = {
|
|
115
|
-
request: {
|
|
116
|
-
query: DATASETS_QUERY,
|
|
117
|
-
variables: { first: 25 },
|
|
118
|
-
},
|
|
119
|
-
result: { data: { datasets: { edges: mockDatasets } } },
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
render(
|
|
123
|
-
<MockedProvider mocks={[mockDatasetQuery]} addTypename={false}>
|
|
124
|
-
<UserDatasetsView user={mockUser} hasEdit={mockHasEdit} />
|
|
125
|
-
</MockedProvider>,
|
|
126
|
-
)
|
|
127
|
-
|
|
128
|
-
await waitFor(() => screen.getByTestId("public-filter"))
|
|
129
|
-
fireEvent.click(screen.getByTestId("public-filter"))
|
|
130
|
-
await waitFor(() => screen.getByText("Public"))
|
|
131
|
-
fireEvent.click(screen.getByText("Public"))
|
|
132
|
-
|
|
133
|
-
expect(screen.getByTestId("public-filter")).toHaveTextContent(
|
|
134
|
-
"Filter by: Public",
|
|
135
|
-
)
|
|
136
|
-
})
|
|
137
|
-
|
|
138
|
-
it("handles sorting datasets", async () => {
|
|
139
|
-
const mockDatasetQuery = {
|
|
140
|
-
request: {
|
|
141
|
-
query: DATASETS_QUERY,
|
|
142
|
-
variables: { first: 25 },
|
|
143
|
-
},
|
|
144
|
-
result: { data: { datasets: { edges: mockDatasets } } },
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
render(
|
|
148
|
-
<MockedProvider mocks={[mockDatasetQuery]} addTypename={false}>
|
|
149
|
-
<UserDatasetsView user={mockUser} hasEdit={mockHasEdit} />
|
|
150
|
-
</MockedProvider>,
|
|
151
|
-
)
|
|
152
|
-
|
|
153
|
-
await waitFor(() => screen.queryByText(mockDatasets[0].node.name))
|
|
154
|
-
|
|
155
|
-
fireEvent.click(screen.getByTestId("sort-order"))
|
|
156
|
-
await waitFor(() => screen.getByText("Name (A-Z)"))
|
|
157
|
-
fireEvent.click(screen.getByText("Name (Z-A)"))
|
|
158
|
-
|
|
159
|
-
expect(screen.getByTestId("sort-order")).toHaveTextContent("Name (Z-A)")
|
|
160
|
-
})
|
|
161
|
-
})
|
|
162
|
-
|
|
163
|
-
const mockDataset = {
|
|
164
|
-
id: "ds000001",
|
|
165
|
-
name: "Test Dataset",
|
|
166
|
-
created: "2025-01-01T00:00:00Z",
|
|
167
|
-
date: "2025-01-01T00:00:00Z",
|
|
168
|
-
public: true,
|
|
169
|
-
analytics: {
|
|
170
|
-
downloads: 12345,
|
|
171
|
-
views: 67890,
|
|
172
|
-
},
|
|
173
|
-
followers: [
|
|
174
|
-
{ userId: "user1", datasetId: "ds000001" },
|
|
175
|
-
{ userId: "user2", datasetId: "ds000001" },
|
|
176
|
-
],
|
|
177
|
-
stars: [
|
|
178
|
-
{ userId: "user1", datasetId: "ds000001" },
|
|
179
|
-
],
|
|
180
|
-
latestSnapshot: {
|
|
181
|
-
id: "ds000001:1.0.0",
|
|
182
|
-
size: 1024 ** 3,
|
|
183
|
-
issues: [{ severity: "low" }],
|
|
184
|
-
created: "2025-01-01T00:00:00Z",
|
|
185
|
-
description: {
|
|
186
|
-
Authors: ["John Doe"],
|
|
187
|
-
SeniorAuthor: "Dr. Smith",
|
|
188
|
-
DatasetType: "fMRI",
|
|
189
|
-
},
|
|
190
|
-
},
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
describe("DatasetCard", () => {
|
|
194
|
-
it("should render dataset information correctly", () => {
|
|
195
|
-
render(<DatasetCard dataset={mockDataset} hasEdit={false} />)
|
|
196
|
-
|
|
197
|
-
expect(screen.getByText("Test Dataset")).toBeInTheDocument()
|
|
198
|
-
expect(screen.getByText("ds000001")).toBeInTheDocument()
|
|
199
|
-
expect(screen.getByText("1.00 GB")).toBeInTheDocument()
|
|
200
|
-
})
|
|
201
|
-
})
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { gql } from "@apollo/client"
|
|
2
|
-
|
|
3
|
-
export const INDEX_DATASET_FRAGMENT = gql`
|
|
4
|
-
fragment DatasetIndex on Dataset {
|
|
5
|
-
id
|
|
6
|
-
created
|
|
7
|
-
name
|
|
8
|
-
public
|
|
9
|
-
analytics {
|
|
10
|
-
views
|
|
11
|
-
downloads
|
|
12
|
-
}
|
|
13
|
-
stars {
|
|
14
|
-
userId
|
|
15
|
-
datasetId
|
|
16
|
-
}
|
|
17
|
-
followers {
|
|
18
|
-
userId
|
|
19
|
-
datasetId
|
|
20
|
-
}
|
|
21
|
-
latestSnapshot {
|
|
22
|
-
id
|
|
23
|
-
size
|
|
24
|
-
created
|
|
25
|
-
issues {
|
|
26
|
-
severity
|
|
27
|
-
}
|
|
28
|
-
description {
|
|
29
|
-
Name
|
|
30
|
-
Authors
|
|
31
|
-
SeniorAuthor
|
|
32
|
-
DatasetType
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
draft {
|
|
36
|
-
id
|
|
37
|
-
}
|
|
38
|
-
uploader {
|
|
39
|
-
id
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
`
|