@openneuro/app 4.47.6 → 5.0.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 (41) hide show
  1. package/package.json +2 -2
  2. package/src/client.jsx +6 -4
  3. package/src/scripts/authentication/__tests__/profile.spec.js +79 -13
  4. package/src/scripts/authentication/profile.ts +9 -0
  5. package/src/scripts/contributors/contributor.tsx +3 -2
  6. package/src/scripts/contributors/contributors-list.tsx +0 -1
  7. package/src/scripts/datalad/dataset/dataset-query-fragments.js +0 -2
  8. package/src/scripts/datalad/mutations/delete-comment.jsx +1 -1
  9. package/src/scripts/dataset/components/dataset-event-item.tsx +4 -4
  10. package/src/scripts/dataset/download/__tests__/download-script.spec.tsx +1 -1
  11. package/src/scripts/dataset/download/download-script.tsx +21 -10
  12. package/src/scripts/dataset/files/__tests__/file-tree-unloaded-directory.spec.jsx +4 -4
  13. package/src/scripts/dataset/files/file-tree-unloaded-directory.jsx +2 -5
  14. package/src/scripts/dataset/files/file-tree.tsx +1 -1
  15. package/src/scripts/dataset/mutations/delete-file.jsx +11 -16
  16. package/src/scripts/dataset/mutations/hold-deletion.tsx +57 -0
  17. package/src/scripts/dataset/routes/__tests__/snapshot.spec.tsx +56 -0
  18. package/src/scripts/dataset/routes/admin-datalad.jsx +10 -0
  19. package/src/scripts/dataset/routes/snapshot.tsx +8 -2
  20. package/src/scripts/pages/front-page/aggregate-queries/use-publicDatasets-count.ts +2 -4
  21. package/src/scripts/queries/dataset.ts +1 -1
  22. package/src/scripts/queries/datasetEvents.ts +2 -2
  23. package/src/scripts/queries/user.ts +2 -12
  24. package/src/scripts/search/inputs/__tests__/sort-by-select.spec.tsx +4 -26
  25. package/src/scripts/search/inputs/sort-by-select.tsx +6 -6
  26. package/src/scripts/search/use-search-results.tsx +38 -256
  27. package/src/scripts/types/event-types.ts +21 -8
  28. package/src/scripts/users/__tests__/user-routes.spec.tsx +2 -11
  29. package/src/scripts/users/notifications/user-notification-accordion-actions.tsx +5 -5
  30. package/src/scripts/users/notifications/user-notification-accordion-header.tsx +4 -1
  31. package/src/scripts/users/notifications/user-notification-accordion.tsx +12 -5
  32. package/src/scripts/users/notifications/user-notification-reason-input.tsx +6 -3
  33. package/src/scripts/users/notifications/user-notifications-accordion-body.tsx +3 -3
  34. package/src/scripts/users/user-datasets-view.tsx +83 -164
  35. package/src/scripts/users/user-menu.tsx +1 -1
  36. package/src/scripts/datalad/dataset/comments-fragments.js +0 -22
  37. package/src/scripts/datalad/mutations/follow.jsx +0 -54
  38. package/src/scripts/datalad/mutations/publish.jsx +0 -58
  39. package/src/scripts/datalad/mutations/star.jsx +0 -54
  40. package/src/scripts/pages/admin/user-fragment.ts +0 -21
  41. package/src/scripts/search/es-query-builders.ts +0 -107
@@ -1,10 +1,11 @@
1
1
  import React from "react"
2
+ import { formatStatusForDisplay } from "../../types/event-types"
2
3
  import styles from "./scss/usernotifications.module.scss"
3
4
 
4
5
  interface NotificationReasonInputProps {
5
6
  reasonInput: string
6
7
  setReasonInput: (reason: string) => void
7
- currentApprovalAction: "accepted" | "denied" | null
8
+ currentApprovalAction: "ACCEPTED" | "DENIED" | null
8
9
  handleReasonCancel: () => void
9
10
  handleReasonSubmit: () => void
10
11
  isProcessing: boolean
@@ -26,14 +27,16 @@ export const NotificationReasonInput: React.FC<NotificationReasonInputProps> = (
26
27
  <div className={styles.reasonInputContainer}>
27
28
  <label htmlFor={textareaId} className="sr-only">
28
29
  {`Reason for ${
29
- currentApprovalAction ? currentApprovalAction : "approval"
30
+ formatStatusForDisplay(currentApprovalAction) || "approval"
30
31
  } action`}
31
32
  </label>
32
33
  <textarea
33
34
  id={textareaId}
34
35
  value={reasonInput}
35
36
  onChange={(e) => setReasonInput(e.target.value)}
36
- placeholder={`Reason for ${currentApprovalAction}...`}
37
+ placeholder={`Reason for ${
38
+ formatStatusForDisplay(currentApprovalAction)
39
+ }...`}
37
40
  rows={3}
38
41
  className={styles.reasonTextarea}
39
42
  style={{ width: "100%" }}
@@ -58,7 +58,7 @@ export const NotificationBodyContent: React.FC<NotificationBodyContentProps> = (
58
58
  )
59
59
  }
60
60
 
61
- if (approval === "accepted" && isContributorCitation) {
61
+ if (approval === "ACCEPTED" && isContributorCitation) {
62
62
  return (
63
63
  <div>
64
64
  The following user has been added as a contributor to {datasetId}.
@@ -67,7 +67,7 @@ export const NotificationBodyContent: React.FC<NotificationBodyContentProps> = (
67
67
  )
68
68
  }
69
69
 
70
- if (approval === "accepted" && isContributorRequest) {
70
+ if (approval === "ACCEPTED" && isContributorRequest) {
71
71
  return (
72
72
  <div>
73
73
  <Username user={requesterUser} />{" "}
@@ -77,7 +77,7 @@ export const NotificationBodyContent: React.FC<NotificationBodyContentProps> = (
77
77
  )
78
78
  }
79
79
 
80
- if (approval === "accepted" && isContributorResponse) {
80
+ if (approval === "ACCEPTED" && isContributorResponse) {
81
81
  return (
82
82
  <div>
83
83
  Admin <Username user={adminUser} />{" "}
@@ -9,36 +9,51 @@ import { Loading } from "../components/loading/Loading"
9
9
  import type { Dataset, UserDatasetsViewProps } from "../types/user-types"
10
10
  import styles from "./scss/datasetcard.module.scss"
11
11
 
12
- type SortByType = {
13
- [key: string]: "asc" | "desc"
14
- } | null
12
+ interface SearchInput {
13
+ keywords?: string[]
14
+ userId?: string
15
+ publicOnly?: boolean
16
+ sortBy?: string
17
+ }
15
18
 
16
- interface ElasticsearchQuery {
17
- bool: {
18
- filter: (
19
- | { terms: { "permissions.userPermissions.user.id": string[] } }
20
- | { term: { public: boolean | null } }
21
- )[]
22
- must: (
23
- | {
24
- bool: {
25
- should: (
26
- | {
27
- multi_match: {
28
- query: string
29
- fields: string[]
30
- fuzziness: number
31
- }
32
- }
33
- | { prefix: { name: string } }
34
- | { prefix: { "description.Name": string } }
35
- )[]
36
- minimum_should_match: number
37
- }
38
- }
39
- | { match_all: Record<string, never> }
40
- )[]
19
+ const buildSearchInput = (
20
+ searchQuery: string,
21
+ publicFilter: string,
22
+ userId: string | undefined,
23
+ hasEdit: boolean,
24
+ sortBy: string | undefined,
25
+ ): SearchInput => {
26
+ const input: SearchInput = {}
27
+
28
+ if (userId) {
29
+ input.userId = userId
30
+ }
31
+
32
+ if (hasEdit) {
33
+ if (publicFilter === "public") {
34
+ input.publicOnly = true
35
+ }
36
+ } else {
37
+ input.publicOnly = true
38
+ }
39
+
40
+ if (searchQuery) {
41
+ input.keywords = [searchQuery]
41
42
  }
43
+
44
+ if (sortBy) {
45
+ input.sortBy = sortBy
46
+ }
47
+
48
+ return input
49
+ }
50
+
51
+ const SORT_MAP: Record<string, string | undefined> = {
52
+ "name-asc": "name_asc",
53
+ "name-desc": "name_desc",
54
+ "date-newest": "newest",
55
+ "date-oldest": "oldest",
56
+ "date-updated": "last_updated",
42
57
  }
43
58
 
44
59
  export const UserDatasetsView: React.FC<UserDatasetsViewProps> = ({
@@ -52,81 +67,12 @@ export const UserDatasetsView: React.FC<UserDatasetsViewProps> = ({
52
67
  const [loadMoreLoading, setLoadMoreLoading] = useState(false)
53
68
  const [cursor, setCursor] = useState<string | null>(null)
54
69
  const [hasNextPage, setHasNextPage] = useState(false)
55
- const [sortBy, setSortBy] = useState<SortByType>(null)
56
70
  const loadAmount = 26
57
71
 
58
- const generateElasticsearchQuery = useCallback(
59
- (
60
- currentSearchQuery: string,
61
- currentPublicFilter: string,
62
- ): ElasticsearchQuery => {
63
- const baseQuery: ElasticsearchQuery = {
64
- bool: {
65
- filter: [],
66
- must: [],
67
- },
68
- }
69
-
70
- if (orcidUser?.id) {
71
- baseQuery.bool.filter.push({
72
- terms: {
73
- "permissions.userPermissions.user.id": [orcidUser.id],
74
- },
75
- })
76
- }
72
+ const sortByValue = SORT_MAP[sortOrder]
77
73
 
78
- if (hasEdit) {
79
- if (currentPublicFilter === "public") {
80
- baseQuery.bool.filter.push({ term: { public: true } })
81
- }
82
- } else {
83
- baseQuery.bool.filter.push({ term: { public: true } })
84
- }
85
-
86
- if (currentSearchQuery) {
87
- baseQuery.bool.must.push({
88
- bool: {
89
- should: [
90
- {
91
- multi_match: {
92
- query: currentSearchQuery,
93
- fields: [
94
- "id^3",
95
- "name^3",
96
- "description.Name^3",
97
- "description.Authors^3",
98
- "latestSnapshot.description.Name",
99
- "latestSnapshot.description.Authors",
100
- "latestSnapshot.readme",
101
- ],
102
- fuzziness: 1,
103
- },
104
- },
105
- {
106
- prefix: {
107
- name: currentSearchQuery.toLowerCase(),
108
- },
109
- },
110
- {
111
- prefix: {
112
- "description.Name": currentSearchQuery.toLowerCase(),
113
- },
114
- },
115
- ],
116
- minimum_should_match: 1,
117
- },
118
- })
119
- } else {
120
- baseQuery.bool.must.push({ match_all: {} })
121
- }
122
-
123
- return baseQuery
124
- },
125
- [orcidUser?.id, hasEdit],
126
- )
127
-
128
- const [elasticsearchQuery, setElasticsearchQuery] = useState(() =>
129
- generateElasticsearchQuery("", "all")
74
+ const [searchInput, setSearchInput] = useState(() =>
75
+ buildSearchInput("", "all", orcidUser?.id, hasEdit, sortByValue)
130
76
  )
131
77
 
132
78
  const { data, loading, error, fetchMore, refetch } = useQuery(
@@ -134,8 +80,7 @@ export const UserDatasetsView: React.FC<UserDatasetsViewProps> = ({
134
80
  {
135
81
  variables: {
136
82
  first: loadAmount,
137
- query: elasticsearchQuery,
138
- sortBy: sortBy,
83
+ query: searchInput,
139
84
  cursor: null,
140
85
  allDatasets: true,
141
86
  datasetStatus: undefined,
@@ -161,89 +106,63 @@ export const UserDatasetsView: React.FC<UserDatasetsViewProps> = ({
161
106
  (newSearchQuery: string, currentPublicFilter: string) => {
162
107
  setSearchQuery(newSearchQuery)
163
108
  setPublicFilter(currentPublicFilter)
164
- const newElasticsearchQuery = generateElasticsearchQuery(
109
+ const newInput = buildSearchInput(
165
110
  newSearchQuery,
166
111
  currentPublicFilter,
112
+ orcidUser?.id,
113
+ hasEdit,
114
+ sortByValue,
167
115
  )
168
- setElasticsearchQuery(newElasticsearchQuery)
116
+ setSearchInput(newInput)
169
117
  refetch({
170
- query: newElasticsearchQuery,
118
+ query: newInput,
171
119
  cursor: null,
172
- sortBy: sortBy,
173
120
  datasetStatus: undefined,
174
121
  })
175
122
  },
176
- [
177
- generateElasticsearchQuery,
178
- refetch,
179
- setSearchQuery,
180
- setPublicFilter,
181
- setElasticsearchQuery,
182
- sortBy,
183
- hasEdit,
184
- orcidUser?.id,
185
- ],
123
+ [refetch, sortByValue, hasEdit, orcidUser?.id],
186
124
  )
187
125
 
188
126
  const handlePublicFilterChange = useCallback(
189
127
  (newPublicFilter) => {
190
128
  setPublicFilter(newPublicFilter)
191
- const newElasticsearchQuery = generateElasticsearchQuery(
129
+ const newInput = buildSearchInput(
192
130
  searchQuery,
193
131
  newPublicFilter,
132
+ orcidUser?.id,
133
+ hasEdit,
134
+ sortByValue,
194
135
  )
195
- setElasticsearchQuery(newElasticsearchQuery)
136
+ setSearchInput(newInput)
196
137
  refetch({
197
- query: newElasticsearchQuery,
138
+ query: newInput,
198
139
  cursor: null,
199
- sortBy: sortBy,
200
140
  datasetStatus: undefined,
201
141
  })
202
142
  },
203
- [
204
- setPublicFilter,
205
- generateElasticsearchQuery,
206
- refetch,
207
- searchQuery,
208
- sortBy,
209
- hasEdit,
210
- orcidUser?.id,
211
- ],
143
+ [refetch, searchQuery, sortByValue, hasEdit, orcidUser?.id],
212
144
  )
213
145
 
214
146
  const handleSortOrderChange = useCallback(
215
147
  (newSortOrder) => {
216
148
  setSortOrder(newSortOrder)
217
- let newSortBy = null
218
- switch (newSortOrder) {
219
- case "name-asc":
220
- newSortBy = { "metadata.datasetName": "asc" }
221
- break
222
- case "name-desc":
223
- newSortBy = { "metadata.datasetName": "desc" }
224
- break
225
- case "date-newest":
226
- newSortBy = { created: "desc" }
227
- break
228
- case "date-oldest":
229
- newSortBy = { created: "asc" }
230
- break
231
- case "date-updated":
232
- newSortBy = { "metadata.latestSnapshotCreatedAt": "desc" }
233
- break
234
- default:
235
- newSortBy = null
236
- }
237
- setSortBy(newSortBy)
149
+ const newSortByValue = SORT_MAP[newSortOrder]
150
+ const newInput = buildSearchInput(
151
+ searchQuery,
152
+ publicFilter,
153
+ orcidUser?.id,
154
+ hasEdit,
155
+ newSortByValue,
156
+ )
157
+ setSearchInput(newInput)
238
158
  refetch({
239
- sortBy: newSortBy,
159
+ query: newInput,
240
160
  first: loadAmount,
241
161
  cursor: null,
242
- query: elasticsearchQuery,
243
162
  datasetStatus: undefined,
244
163
  })
245
164
  },
246
- [setSortOrder, refetch, setSortBy, elasticsearchQuery, hasEdit],
165
+ [refetch, searchQuery, publicFilter, hasEdit, orcidUser?.id],
247
166
  )
248
167
 
249
168
  const handleLoadMore = useCallback(() => {
@@ -256,8 +175,7 @@ export const UserDatasetsView: React.FC<UserDatasetsViewProps> = ({
256
175
  variables: {
257
176
  first: loadAmount,
258
177
  cursor: cursor,
259
- query: elasticsearchQuery,
260
- sortBy: sortBy,
178
+ query: searchInput,
261
179
  allDatasets: true,
262
180
  datasetStatus: undefined,
263
181
  },
@@ -293,8 +211,7 @@ export const UserDatasetsView: React.FC<UserDatasetsViewProps> = ({
293
211
  hasNextPage,
294
212
  loadMoreLoading,
295
213
  cursor,
296
- elasticsearchQuery,
297
- sortBy,
214
+ searchInput,
298
215
  hasEdit,
299
216
  ])
300
217
 
@@ -307,23 +224,25 @@ export const UserDatasetsView: React.FC<UserDatasetsViewProps> = ({
307
224
  }, [data, hasEdit, loadAmount])
308
225
 
309
226
  useEffect(() => {
310
- const newElasticsearchQuery = generateElasticsearchQuery(
227
+ const newInput = buildSearchInput(
311
228
  searchQuery,
312
229
  publicFilter,
230
+ orcidUser?.id,
231
+ hasEdit,
232
+ sortByValue,
313
233
  )
314
- setElasticsearchQuery(newElasticsearchQuery)
315
- }, [searchQuery, publicFilter, generateElasticsearchQuery])
234
+ setSearchInput(newInput)
235
+ }, [searchQuery, publicFilter, orcidUser?.id, hasEdit, sortByValue])
316
236
 
317
237
  useEffect(() => {
318
- if (elasticsearchQuery) {
238
+ if (searchInput) {
319
239
  refetch({
320
- query: elasticsearchQuery,
240
+ query: searchInput,
321
241
  cursor: null,
322
- sortBy: sortBy,
323
242
  datasetStatus: undefined,
324
243
  })
325
244
  }
326
- }, [elasticsearchQuery, refetch, sortBy, hasEdit])
245
+ }, [searchInput, refetch, hasEdit])
327
246
 
328
247
  if (loading) return <Loading />
329
248
  if (error) {
@@ -62,7 +62,7 @@ export const UserMenu: React.FC<UserMenuProps> = ({ signOutAndRedirect }) => {
62
62
 
63
63
  return (
64
64
  <span className="user-menu-wrap">
65
- {user.orcid && (
65
+ {user.orcid && user.id !== "reviewer" && (
66
66
  <span className="notifications-link">
67
67
  <Link to={`/user/${user.orcid}/notifications/unread`}>
68
68
  <i className="fa fa-inbox">
@@ -1,22 +0,0 @@
1
- import { gql } from "@apollo/client"
2
-
3
- export const DATASET_COMMENTS = gql`
4
- fragment DatasetComments on Dataset {
5
- id
6
- comments {
7
- id
8
- text
9
- createDate
10
- user {
11
- name
12
- orcid
13
- }
14
- parent {
15
- id
16
- }
17
- replies {
18
- id
19
- }
20
- }
21
- }
22
- `
@@ -1,54 +0,0 @@
1
- import React from "react"
2
- import PropTypes from "prop-types"
3
- import { gql } from "@apollo/client"
4
- import { Mutation } from "@apollo/client/react/components"
5
- import WarnButton from "../../common/forms/warn-button.jsx"
6
- import { datasetCacheId } from "./cache-id.js"
7
-
8
- const FOLLOW_DATASET = gql`
9
- mutation followDataset($datasetId: ID!) {
10
- followDataset(datasetId: $datasetId)
11
- }
12
- `
13
-
14
- const USER_FOLLOWING = gql`
15
- fragment UserFollowing on Dataset {
16
- id
17
- following
18
- }
19
- `
20
-
21
- const FollowDataset = ({ datasetId, following }) => (
22
- <Mutation
23
- mutation={FOLLOW_DATASET}
24
- update={(cache, { data: { followDataset } }) => {
25
- cache.writeFragment({
26
- id: datasetCacheId(datasetId),
27
- fragment: USER_FOLLOWING,
28
- data: {
29
- __typename: "Dataset",
30
- id: datasetId,
31
- following: followDataset,
32
- },
33
- })
34
- }}
35
- >
36
- {(followDataset) => (
37
- <WarnButton
38
- tooltip="Follow Dataset"
39
- icon={following ? "fa-tag icon-minus" : "fa-tag icon-plus"}
40
- warn={false}
41
- action={(cb) => {
42
- followDataset({ variables: { datasetId } }).then(() => cb())
43
- }}
44
- />
45
- )}
46
- </Mutation>
47
- )
48
-
49
- FollowDataset.propTypes = {
50
- datasetId: PropTypes.string,
51
- following: PropTypes.bool,
52
- }
53
-
54
- export default FollowDataset
@@ -1,58 +0,0 @@
1
- import React from "react"
2
- import PropTypes from "prop-types"
3
- import { gql } from "@apollo/client"
4
- import { Mutation } from "@apollo/client/react/components"
5
- import { useNavigate } from "react-router-dom"
6
- import { datasetCacheId } from "./cache-id.js"
7
-
8
- const PUBLISH_DATASET = gql`
9
- mutation publishDataset($datasetId: ID!) {
10
- publishDataset(datasetId: $datasetId)
11
- }
12
- `
13
-
14
- const DATASET_PUBLISHED = gql`
15
- fragment DatasetPublished on Dataset {
16
- id
17
- public
18
- }
19
- `
20
-
21
- const PublishDataset = ({ datasetId }) => {
22
- const navigate = useNavigate()
23
- return (
24
- <Mutation
25
- mutation={PUBLISH_DATASET}
26
- update={(cache) => {
27
- cache.writeFragment({
28
- id: datasetCacheId(datasetId),
29
- fragment: DATASET_PUBLISHED,
30
- data: {
31
- __typename: "Dataset",
32
- id: datasetId,
33
- public: true,
34
- },
35
- })
36
- }}
37
- >
38
- {(publishDataset) => (
39
- <button
40
- className="btn-modal-action"
41
- onClick={() =>
42
- publishDataset({ variables: { datasetId } }).then(() => {
43
- navigate(`/datasets/${datasetId}`)
44
- })}
45
- >
46
- Publish
47
- </button>
48
- )}
49
- </Mutation>
50
- )
51
- }
52
-
53
- PublishDataset.propTypes = {
54
- datasetId: PropTypes.string,
55
- history: PropTypes.object,
56
- }
57
-
58
- export default PublishDataset
@@ -1,54 +0,0 @@
1
- import React from "react"
2
- import PropTypes from "prop-types"
3
- import { gql } from "@apollo/client"
4
- import { Mutation } from "@apollo/client/react/components"
5
- import WarnButton from "../../common/forms/warn-button.jsx"
6
- import { datasetCacheId } from "./cache-id.js"
7
-
8
- const STAR_DATASET = gql`
9
- mutation starDataset($datasetId: ID!) {
10
- starDataset(datasetId: $datasetId)
11
- }
12
- `
13
-
14
- const USER_STARRED = gql`
15
- fragment UserStarred on Dataset {
16
- id
17
- starred
18
- }
19
- `
20
-
21
- const StarDataset = ({ datasetId, starred }) => (
22
- <Mutation
23
- mutation={STAR_DATASET}
24
- update={(cache, { data: { starDataset } }) => {
25
- cache.writeFragment({
26
- id: datasetCacheId(datasetId),
27
- fragment: USER_STARRED,
28
- data: {
29
- __typename: "Dataset",
30
- id: datasetId,
31
- starred: starDataset,
32
- },
33
- })
34
- }}
35
- >
36
- {(starDataset) => (
37
- <WarnButton
38
- tooltip="Save Dataset"
39
- icon={starred ? "fa-star icon-minus" : "fa-star icon-plus"}
40
- warn={false}
41
- action={(cb) => {
42
- starDataset({ variables: { datasetId } }).then(() => cb())
43
- }}
44
- />
45
- )}
46
- </Mutation>
47
- )
48
-
49
- StarDataset.propTypes = {
50
- datasetId: PropTypes.string,
51
- starred: PropTypes.bool,
52
- }
53
-
54
- export default StarDataset
@@ -1,21 +0,0 @@
1
- import { gql } from "@apollo/client"
2
-
3
- export const USER_FRAGMENT = gql`
4
- fragment userFields on User {
5
- id
6
- name
7
- admin
8
- blocked
9
- email
10
- provider
11
- lastSeen
12
- created
13
- avatar
14
- github
15
- institution
16
- location
17
- modified
18
- orcid
19
-
20
- }
21
- `