@openneuro/app 4.35.0-alpha.1 → 4.36.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.
Files changed (42) hide show
  1. package/package.json +3 -3
  2. package/src/client.jsx +1 -0
  3. package/src/scripts/components/button/button.scss +14 -0
  4. package/src/scripts/components/search-page/SearchResultItem.tsx +3 -1
  5. package/src/scripts/components/search-page/search-page.scss +1 -13
  6. package/src/scripts/config.ts +6 -0
  7. package/src/scripts/dataset/files/__tests__/__snapshots__/file.spec.jsx.snap +2 -2
  8. package/src/scripts/dataset/files/file.tsx +2 -2
  9. package/src/scripts/dataset/mutations/__tests__/update-file.spec.tsx +126 -0
  10. package/src/scripts/dataset/mutations/update-file.jsx +20 -5
  11. package/src/scripts/dataset/routes/snapshot.tsx +1 -1
  12. package/src/scripts/errors/errorRoute.tsx +2 -0
  13. package/src/scripts/pages/orcid-link.tsx +9 -0
  14. package/src/scripts/queries/user.ts +120 -3
  15. package/src/scripts/types/user-types.ts +11 -13
  16. package/src/scripts/uploader/file-select.tsx +42 -57
  17. package/src/scripts/uploader/upload-select.jsx +1 -1
  18. package/src/scripts/users/__tests__/dataset-card.spec.tsx +127 -0
  19. package/src/scripts/users/__tests__/user-account-view.spec.tsx +150 -67
  20. package/src/scripts/users/__tests__/user-card.spec.tsx +6 -17
  21. package/src/scripts/users/__tests__/user-query.spec.tsx +133 -38
  22. package/src/scripts/users/__tests__/user-routes.spec.tsx +156 -27
  23. package/src/scripts/users/__tests__/user-tabs.spec.tsx +7 -7
  24. package/src/scripts/users/components/edit-list.tsx +26 -5
  25. package/src/scripts/users/components/edit-string.tsx +40 -13
  26. package/src/scripts/users/components/editable-content.tsx +10 -3
  27. package/src/scripts/users/components/user-dataset-filters.tsx +205 -121
  28. package/src/scripts/users/dataset-card.tsx +3 -2
  29. package/src/scripts/users/github-auth-button.tsx +98 -0
  30. package/src/scripts/users/scss/datasetcard.module.scss +65 -12
  31. package/src/scripts/users/scss/useraccountview.module.scss +1 -1
  32. package/src/scripts/users/user-account-view.tsx +43 -34
  33. package/src/scripts/users/user-card.tsx +12 -17
  34. package/src/scripts/users/user-container.tsx +9 -5
  35. package/src/scripts/users/user-datasets-view.tsx +350 -40
  36. package/src/scripts/users/user-menu.tsx +4 -9
  37. package/src/scripts/users/user-notifications-view.tsx +9 -7
  38. package/src/scripts/users/user-query.tsx +3 -3
  39. package/src/scripts/users/user-routes.tsx +11 -5
  40. package/src/scripts/users/user-tabs.tsx +4 -2
  41. package/src/scripts/users/__tests__/datasest-card.spec.tsx +0 -201
  42. package/src/scripts/users/fragments/query.js +0 -42
@@ -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 = ({ user }) => {
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/${user.orcid}/notifications/unread`, { replace: true })
53
+ navigate(`/user/${orcidUser.orcid}/notifications/unread`, {
54
+ replace: true,
55
+ })
54
56
  }
55
- }, [tab, user.orcid, navigate])
57
+ }, [tab, orcidUser.orcid, navigate])
56
58
 
57
59
  return (
58
60
  <div data-testid="user-notifications-view">
59
- <h3>Notifications for {user.name}</h3>
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/${user.orcid}/notifications/unread`}
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/${user.orcid}/notifications/saved`}
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/${user.orcid}/notifications/archived`}
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,7 +23,7 @@ export const UserQuery: React.FC = () => {
23
23
 
24
24
  if (loading) return <div>Loading...</div>
25
25
 
26
- if (error || !user || user?.orcid !== orcid) {
26
+ if (error || !user) {
27
27
  return <FourOFourPage />
28
28
  }
29
29
 
@@ -34,5 +34,5 @@ export const UserQuery: React.FC = () => {
34
34
  const isUser = (user?.id === profile?.sub) ? true : false
35
35
  const hasEdit = isAdminUser || (user?.id === profile?.sub) ? true : false
36
36
  // Render user data with UserRoutes
37
- return <UserRoutes user={user} hasEdit={hasEdit} isUser={isUser} />
37
+ return <UserRoutes orcidUser={user} hasEdit={hasEdit} isUser={isUser} />
38
38
  }
@@ -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
- { user, hasEdit, isUser },
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 user={user} hasEdit={hasEdit} isUser={isUser} />
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 user={user} hasEdit={hasEdit} />}
35
+ element={<UserDatasetsView orcidUser={orcidUser} hasEdit={hasEdit} />}
32
36
  />
33
37
  <Route
34
38
  path="account"
35
- element={hasEdit ? <UserAccountView /> : <FourOThreePage />}
39
+ element={hasEdit
40
+ ? <UserAccountView orcidUser={orcidUser} />
41
+ : <FourOThreePage />}
36
42
  />
37
43
  <Route
38
44
  path="notifications/*"
39
45
  element={hasEdit
40
- ? <UserNotificationsView user={user} />
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
- <li>
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
- `