@openneuro/app 4.47.7 → 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 (38) hide show
  1. package/package.json +2 -2
  2. package/src/client.jsx +3 -0
  3. package/src/scripts/contributors/contributor.tsx +3 -2
  4. package/src/scripts/contributors/contributors-list.tsx +0 -1
  5. package/src/scripts/datalad/dataset/dataset-query-fragments.js +0 -2
  6. package/src/scripts/datalad/mutations/delete-comment.jsx +1 -1
  7. package/src/scripts/dataset/components/dataset-event-item.tsx +4 -4
  8. package/src/scripts/dataset/download/__tests__/download-script.spec.tsx +1 -1
  9. package/src/scripts/dataset/download/download-script.tsx +21 -10
  10. package/src/scripts/dataset/files/__tests__/file-tree-unloaded-directory.spec.jsx +4 -4
  11. package/src/scripts/dataset/files/file-tree-unloaded-directory.jsx +2 -5
  12. package/src/scripts/dataset/files/file-tree.tsx +1 -1
  13. package/src/scripts/dataset/mutations/delete-file.jsx +11 -16
  14. package/src/scripts/dataset/mutations/hold-deletion.tsx +57 -0
  15. package/src/scripts/dataset/routes/__tests__/snapshot.spec.tsx +56 -0
  16. package/src/scripts/dataset/routes/admin-datalad.jsx +10 -0
  17. package/src/scripts/dataset/routes/snapshot.tsx +8 -2
  18. package/src/scripts/pages/front-page/aggregate-queries/use-publicDatasets-count.ts +2 -4
  19. package/src/scripts/queries/dataset.ts +1 -1
  20. package/src/scripts/queries/datasetEvents.ts +2 -2
  21. package/src/scripts/queries/user.ts +1 -3
  22. package/src/scripts/search/inputs/__tests__/sort-by-select.spec.tsx +4 -26
  23. package/src/scripts/search/inputs/sort-by-select.tsx +6 -6
  24. package/src/scripts/search/use-search-results.tsx +38 -256
  25. package/src/scripts/types/event-types.ts +21 -8
  26. package/src/scripts/users/__tests__/user-routes.spec.tsx +2 -11
  27. package/src/scripts/users/notifications/user-notification-accordion-actions.tsx +5 -5
  28. package/src/scripts/users/notifications/user-notification-accordion-header.tsx +4 -1
  29. package/src/scripts/users/notifications/user-notification-accordion.tsx +12 -5
  30. package/src/scripts/users/notifications/user-notification-reason-input.tsx +6 -3
  31. package/src/scripts/users/notifications/user-notifications-accordion-body.tsx +3 -3
  32. package/src/scripts/users/user-datasets-view.tsx +83 -164
  33. package/src/scripts/datalad/dataset/comments-fragments.js +0 -22
  34. package/src/scripts/datalad/mutations/follow.jsx +0 -54
  35. package/src/scripts/datalad/mutations/publish.jsx +0 -58
  36. package/src/scripts/datalad/mutations/star.jsx +0 -54
  37. package/src/scripts/pages/admin/user-fragment.ts +0 -21
  38. package/src/scripts/search/es-query-builders.ts +0 -107
@@ -1,33 +1,20 @@
1
1
  import { useContext } from "react"
2
2
  import { gql, useQuery } from "@apollo/client"
3
3
  import { SearchParamsCtx } from "./search-params-ctx"
4
- import initialSearchParams from "./initial-search-params"
5
- import {
6
- BoolQuery,
7
- joinWithOR,
8
- matchQuery,
9
- multiMatchQuery,
10
- rangeListLengthQuery,
11
- rangeQuery,
12
- simpleQueryString,
13
- sqsJoinWithAND,
14
- } from "./es-query-builders"
15
4
 
16
5
  const searchQuery = gql`
17
6
  query advancedSearchDatasets(
18
- $query: JSON!
7
+ $query: DatasetSearchInput!
19
8
  $cursor: String
20
9
  $allDatasets: Boolean
21
10
  $datasetType: String
22
11
  $datasetStatus: String
23
- $sortBy: JSON
24
12
  ) {
25
13
  datasets: advancedSearch(
26
14
  query: $query
27
15
  allDatasets: $allDatasets
28
16
  datasetType: $datasetType
29
17
  datasetStatus: $datasetStatus
30
- sortBy: $sortBy
31
18
  first: 25
32
19
  after: $cursor
33
20
  ) {
@@ -100,9 +87,9 @@ const searchQuery = gql`
100
87
  }
101
88
  contributors {
102
89
  name
103
- givenName
104
- familyName
105
- orcid
90
+ givenName
91
+ familyName
92
+ orcid
106
93
  contributorType
107
94
  }
108
95
  }
@@ -136,8 +123,8 @@ const searchQuery = gql`
136
123
  }
137
124
  `
138
125
 
139
- const isActiveRange = (range) =>
140
- JSON.stringify(range) !== JSON.stringify([null, null])
126
+ const isActiveRange = (range: (number | null)[]): boolean =>
127
+ range[0] !== null || range[1] !== null
141
128
 
142
129
  export const useSearchResults = () => {
143
130
  const { searchParams } = useContext(SearchParamsCtx)
@@ -167,248 +154,43 @@ export const useSearchResults = () => {
167
154
  brain_initiative,
168
155
  } = searchParams
169
156
 
170
- const boolQuery = new BoolQuery()
171
- if (keywords.length) {
172
- boolQuery.addClause(
173
- "must",
174
- simpleQueryString(sqsJoinWithAND(keywords), [
175
- "id^20",
176
- "latestSnapshot.readme",
177
- "latestSnapshot.description.Name^6",
178
- "latestSnapshot.description.Authors^3", // TODO: Nell - do we need this still?
179
- "latestSnapshot.contributors.name^2",
180
- ]),
181
- )
182
- }
183
- if (modality_selected) {
184
- const secondaryModalities = {
185
- Diffusion: {
186
- secondary: "mri_diffusion",
187
- primary: "mri",
188
- },
189
- Structural: {
190
- secondary: "mri_structural",
191
- primary: "mri",
192
- },
193
- Functional: {
194
- secondary: "mri_functional",
195
- primary: "mri",
196
- },
197
- Perfusion: {
198
- secondary: "mri_perfusion",
199
- primary: "mri",
200
- },
201
- Static: {
202
- secondary: "pet_static",
203
- primary: "pet",
204
- },
205
- Dynamic: {
206
- secondary: "pet_dynamic",
207
- primary: "pet",
208
- },
209
- }
210
- if (Object.keys(secondaryModalities).includes(modality_selected)) {
211
- boolQuery.addClause(
212
- "filter",
213
- matchQuery(
214
- "latestSnapshot.summary.secondaryModalities",
215
- secondaryModalities[modality_selected].secondary,
216
- ),
217
- )
218
- } else {
219
- boolQuery.addClause(
220
- "filter",
221
- matchQuery("latestSnapshot.summary.modalities", modality_selected),
222
- )
223
- }
224
- }
157
+ // Build the structured search input
158
+ const query: Record<string, unknown> = {}
225
159
 
226
- if (isActiveRange(ageRange)) {
227
- boolQuery.addClause(
228
- "filter",
229
- rangeQuery("latestSnapshot.summary.subjectMetadata.age", ...ageRange),
230
- )
231
- }
160
+ if (keywords.length) query.keywords = keywords
161
+ if (modality_selected) query.modality = modality_selected
162
+ if (isActiveRange(ageRange)) query.ageRange = ageRange
232
163
  if (isActiveRange(subjectCountRange)) {
233
- boolQuery.addClause(
234
- "filter",
235
- rangeListLengthQuery(
236
- "latestSnapshot.summary.subjects",
237
- subjectCountRange[0] || 0,
238
- subjectCountRange[1] || 1000000,
239
- ),
240
- )
241
- }
242
- if (diagnosis_selected) {
243
- boolQuery.addClause(
244
- "filter",
245
- matchQuery("metadata.dxStatus", diagnosis_selected),
246
- )
247
- }
248
- if (bidsDatasetType_selected) {
249
- boolQuery.addClause(
250
- "filter",
251
- matchQuery(
252
- "latestSnapshot.description.DatasetType",
253
- bidsDatasetType_selected,
254
- ),
255
- )
256
- }
257
- if (brain_initiative) {
258
- boolQuery.addClause(
259
- "filter",
260
- matchQuery(
261
- "brainInitiative",
262
- brain_initiative,
263
- ),
264
- )
265
- }
266
- if (tasks.length) {
267
- boolQuery.addClause(
268
- "must",
269
- simpleQueryString(sqsJoinWithAND(tasks), [
270
- "latestSnapshot.summary.tasks",
271
- ]),
272
- )
273
- }
274
- if (authors.length) { // TODO - NELL - does this look right?
275
- const authorQuery = matchQuery(
276
- "latestSnapshot.contributors.name",
277
- joinWithOR(authors),
278
- "2",
279
- )
280
- boolQuery.addClause(
281
- "must",
282
- {
283
- bool: {
284
- should: [authorQuery],
285
- },
286
- },
287
- )
288
- }
289
- if (sex_selected !== "All") {
290
- // Possible values for this field are specified here:
291
- // https://bids-specification.readthedocs.io/en/stable/glossary.html#objects.columns.sex
292
- let queryStrings = []
293
- if (sex_selected == "Male") {
294
- queryStrings = ["male", "m", "M", "MALE", "Male"]
295
- } else if (sex_selected == "Female") {
296
- queryStrings = ["female", "f", "F", "FEMALE", "Female"]
297
- }
298
- boolQuery.addClause(
299
- "filter",
300
- multiMatchQuery(
301
- "latestSnapshot.summary.subjectMetadata.sex",
302
- queryStrings,
303
- ),
304
- )
305
- }
306
- if (date_selected !== "All Time") {
307
- let d: number
308
- if (date_selected === "Last 30 days") {
309
- d = 30
310
- } else if (date_selected === "Last 180 days") {
311
- d = 180
312
- } else {
313
- d = 365
314
- }
315
- boolQuery.addClause("filter", rangeQuery("created", `now-${d}d/d`, "now/d"))
316
- }
317
- if (species_selected) {
318
- if (species_selected === "Other") {
319
- // if species is 'Other', search for every species that isn't an available option
320
- const species = initialSearchParams.species_available
321
- .filter((s) => s !== "Other")
322
- .join(" ")
323
- boolQuery.addClause(
324
- "must_not",
325
- matchQuery("metadata.species", species, "AUTO", "OR"),
326
- )
327
- } else if (species_selected === "Human") {
328
- // if species is 'Human', search for Human or null values (BIDS assumes human by default)
329
- boolQuery.query.bool["should"] = [
330
- matchQuery("metadata.species", "Human", "AUTO"),
331
- { term: { _content: "" } },
332
- ]
333
- } else {
334
- boolQuery.addClause(
335
- "filter",
336
- matchQuery("metadata.species", species_selected, "AUTO"),
337
- )
338
- }
339
- }
340
- if (section_selected) {
341
- boolQuery.addClause(
342
- "filter",
343
- matchQuery("metadata.studyLongitudinal", section_selected, "AUTO"),
344
- )
345
- }
346
- if (studyDomains.length) {
347
- boolQuery.addClause(
348
- "must",
349
- matchQuery("metadata.studyDomain", joinWithOR(studyDomains)),
350
- )
351
- }
352
- if (modality_selected === "pet" || modality_selected === null) {
353
- if (bodyParts.length) {
354
- boolQuery.addClause(
355
- "must",
356
- simpleQueryString(sqsJoinWithAND(bodyParts), [
357
- "latestSnapshot.summary.pet.BodyPart",
358
- ]),
359
- )
360
- }
361
- if (scannerManufacturers.length) {
362
- boolQuery.addClause(
363
- "must",
364
- simpleQueryString(sqsJoinWithAND(scannerManufacturers), [
365
- "latestSnapshot.summary.pet.ScannerManufacturer",
366
- ]),
367
- )
368
- }
369
- if (scannerManufacturersModelNames.length) {
370
- boolQuery.addClause(
371
- "must",
372
- simpleQueryString(sqsJoinWithAND(scannerManufacturersModelNames), [
373
- "latestSnapshot.summary.pet.ScannerManufacturersModelName",
374
- ]),
375
- )
376
- }
377
- if (tracerNames.length) {
378
- boolQuery.addClause(
379
- "must",
380
- simpleQueryString(sqsJoinWithAND(tracerNames), [
381
- "latestSnapshot.summary.pet.TracerName",
382
- ]),
383
- )
384
- }
385
- if (tracerRadionuclides.length) {
386
- boolQuery.addClause(
387
- "must",
388
- simpleQueryString(sqsJoinWithAND(tracerRadionuclides), [
389
- "latestSnapshot.summary.pet.TracerRadionuclide",
390
- ]),
391
- )
392
- }
393
- }
164
+ query.subjectCountRange = subjectCountRange
165
+ }
166
+ if (diagnosis_selected) query.diagnosis = diagnosis_selected
167
+ if (tasks.length) query.tasks = tasks
168
+ if (authors.length) query.authors = authors
169
+ if (sex_selected && sex_selected !== "All") query.sex = sex_selected
170
+ if (date_selected && date_selected !== "All Time") {
171
+ query.dateRange = date_selected
172
+ }
173
+ if (species_selected) query.species = species_selected
174
+ if (section_selected) query.studyStructure = section_selected
175
+ if (studyDomains.length) query.studyDomains = studyDomains
176
+ if (bidsDatasetType_selected) query.bidsDatasetType = bidsDatasetType_selected
177
+ if (brain_initiative) query.brainInitiative = true
178
+ if (bodyParts.length) query.bodyParts = bodyParts
179
+ if (scannerManufacturers.length) {
180
+ query.scannerManufacturers = scannerManufacturers
181
+ }
182
+ if (scannerManufacturersModelNames.length) {
183
+ query.scannerManufacturersModelNames = scannerManufacturersModelNames
184
+ }
185
+ if (tracerNames.length) query.tracerNames = tracerNames
186
+ if (tracerRadionuclides.length) {
187
+ query.tracerRadionuclides = tracerRadionuclides
188
+ }
189
+ if (sortBy_selected) query.sortBy = sortBy_selected.value
394
190
 
395
- let sortBy
396
- if (sortBy_selected.label === "Relevance") {
397
- // If filters are set, sort by relevance (default sort),
398
- // otherwise, sort from newest to oldest
399
- sortBy = boolQuery.isEmpty() ? { created: "desc" } : null
400
- } else if (sortBy_selected.label === "Newest") {
401
- sortBy = { created: "desc" }
402
- } else if (sortBy_selected.label === "Oldest") {
403
- sortBy = { created: "asc" }
404
- } else if (sortBy_selected.label === "Activity") {
405
- // TODO: figure out
406
- sortBy = { "analytics.downloads": "desc" }
407
- }
408
191
  return useQuery(searchQuery, {
409
192
  variables: {
410
- query: boolQuery.get(),
411
- sortBy,
193
+ query,
412
194
  allDatasets: searchAllDatasets,
413
195
  datasetType: datasetType_selected,
414
196
  datasetStatus: datasetStatus_selected,
@@ -1,6 +1,19 @@
1
1
  import type { User } from "./user-types"
2
2
 
3
- export type RequestStatus = "pending" | "accepted" | "denied"
3
+ export type RequestStatus = "PENDING" | "ACCEPTED" | "DENIED"
4
+
5
+ /**
6
+ * Format a RequestStatus or compatible string for display in user-facing UI.
7
+ * The type-level representation uses uppercase (matching the GraphQL
8
+ * ResponseStatusType enum), but UI rendering uses lowercase ("accepted",
9
+ * "denied", "pending") for readability. Call this at every display site
10
+ * so that the convention stays consistent if new sites are added.
11
+ */
12
+ export function formatStatusForDisplay(
13
+ status: string | null | undefined,
14
+ ): string {
15
+ return status ? status.toLowerCase() : ""
16
+ }
4
17
 
5
18
  export interface ContributorData {
6
19
  name?: string
@@ -53,7 +66,7 @@ export interface MappedNotification {
53
66
  content: string
54
67
  status: "unread" | "saved" | "archived"
55
68
  type: EventDescription["type"]
56
- approval?: "pending" | "accepted" | "denied"
69
+ approval?: RequestStatus
57
70
  originalNotification: Event
58
71
  datasetId?: string
59
72
  needsReview?: boolean
@@ -94,8 +107,8 @@ export const mapRawEventToMappedNotification = (
94
107
  title = "is requesting to be added to"
95
108
  requesterUser = user // user initiating the request (admin)
96
109
  targetUser = target || rawNotification.user // fallback
97
- approval = resolutionStatus ?? "pending"
98
- needsReview = approval === "pending"
110
+ approval = resolutionStatus ?? "PENDING"
111
+ needsReview = approval === "PENDING"
99
112
  break
100
113
  case "contributorCitation":
101
114
  title = "added"
@@ -110,22 +123,22 @@ export const mapRawEventToMappedNotification = (
110
123
  email: "",
111
124
  orcid: "",
112
125
  })
113
- approval = resolutionStatus ?? "pending"
114
- needsReview = approval === "pending"
126
+ approval = resolutionStatus ?? "PENDING"
127
+ needsReview = approval === "PENDING"
115
128
  break
116
129
 
117
130
  case "contributorRequestResponse":
118
131
  title = "responded to a contributor request"
119
132
  adminUser = user
120
133
  targetUser = target
121
- approval = resolutionStatus ?? "pending"
134
+ approval = resolutionStatus ?? "PENDING"
122
135
  break
123
136
 
124
137
  case "contributorCitationResponse":
125
138
  title = "had their citation request for"
126
139
  adminUser = user
127
140
  targetUser = target
128
- approval = resolutionStatus ?? "pending"
141
+ approval = resolutionStatus ?? "PENDING"
129
142
  break
130
143
 
131
144
  case "note":
@@ -35,18 +35,9 @@ const setupUserRoutes = (
35
35
  variables: {
36
36
  first: 26,
37
37
  query: {
38
- bool: {
39
- filter: [
40
- {
41
- terms: {
42
- "permissions.userPermissions.user.id": [orcidUser.id],
43
- },
44
- },
45
- ],
46
- must: [{ match_all: {} }],
47
- },
38
+ userId: orcidUser.id,
39
+ sortBy: "last_updated",
48
40
  },
49
- sortBy: null,
50
41
  cursor: null,
51
42
  allDatasets: true,
52
43
  datasetStatus: undefined,
@@ -10,7 +10,7 @@ interface NotificationActionButtonsProps {
10
10
  notification: MappedNotification
11
11
  isProcessing: boolean
12
12
  onUpdate: (id: string, updates: Partial<MappedNotification>) => void
13
- handleProcessAction: (action: "accepted" | "denied") => void
13
+ handleProcessAction: (action: "ACCEPTED" | "DENIED") => void
14
14
  handleStatusChange: (
15
15
  newStatus: "unread" | "saved" | "archived",
16
16
  ) => Promise<void>
@@ -32,16 +32,16 @@ export const NotificationActionButtons: React.FC<
32
32
  <>
33
33
  <button
34
34
  className={`${styles.notificationapprove}`}
35
- onClick={() => handleProcessAction("accepted")}
36
- disabled={approval === "accepted" || isProcessing}
35
+ onClick={() => handleProcessAction("ACCEPTED")}
36
+ disabled={approval === "ACCEPTED" || isProcessing}
37
37
  >
38
38
  <i className="fa fa-check" /> Accept
39
39
  </button>
40
40
 
41
41
  <button
42
42
  className={`${styles.notificationdeny}`}
43
- onClick={() => handleProcessAction("denied")}
44
- disabled={approval === "denied" || isProcessing}
43
+ onClick={() => handleProcessAction("DENIED")}
44
+ disabled={approval === "DENIED" || isProcessing}
45
45
  >
46
46
  <i className="fa fa-times" /> Deny
47
47
  </button>
@@ -1,4 +1,5 @@
1
1
  import React from "react"
2
+ import { formatStatusForDisplay } from "../../types/event-types"
2
3
  import type { User } from "../../types/user-types"
3
4
  import { Username } from "../username"
4
5
  import styles from "./scss/usernotifications.module.scss"
@@ -63,7 +64,9 @@ export const NotificationHeader: React.FC<NotificationHeaderProps> = ({
63
64
  <a href={datasetLink} className={styles.titlelink}>
64
65
  {datasetId}
65
66
  </a>{" "}
66
- {type != "contributorCitation" ? resStatus : ""}
67
+ {type != "contributorCitation"
68
+ ? formatStatusForDisplay(resStatus)
69
+ : ""}
67
70
  </span>
68
71
  )
69
72
  }
@@ -15,7 +15,10 @@ import { NotificationActionButtons } from "./user-notification-accordion-actions
15
15
  import ToastContent from "../../common/partials/toast-content"
16
16
  import styles from "./scss/usernotifications.module.scss"
17
17
 
18
- import type { MappedNotification } from "../../types/event-types"
18
+ import {
19
+ formatStatusForDisplay,
20
+ type MappedNotification,
21
+ } from "../../types/event-types"
19
22
 
20
23
  export const NotificationAccordion = ({
21
24
  notification,
@@ -53,7 +56,7 @@ export const NotificationAccordion = ({
53
56
  const [showReasonInput, setShowReasonInput] = useState(false)
54
57
  const [reasonInput, setReasonInput] = useState("")
55
58
  const [currentApprovalAction, setCurrentApprovalAction] = useState<
56
- "accepted" | "denied" | null
59
+ "ACCEPTED" | "DENIED" | null
57
60
  >(null)
58
61
 
59
62
  const [processContributorRequest, { loading: processRequestLoading }] =
@@ -78,7 +81,7 @@ export const NotificationAccordion = ({
78
81
  }
79
82
  }, [isOpen])
80
83
 
81
- const handleProcessAction = useCallback((action: "accepted" | "denied") => {
84
+ const handleProcessAction = useCallback((action: "ACCEPTED" | "DENIED") => {
82
85
  setIsOpen(true)
83
86
  setShowReasonInput(true)
84
87
  setReasonInput("")
@@ -120,7 +123,9 @@ export const NotificationAccordion = ({
120
123
  toast.success(
121
124
  <ToastContent
122
125
  title="Contributor Request Processed"
123
- body={`Request has been ${currentApprovalAction}.`}
126
+ body={`Request has been ${
127
+ formatStatusForDisplay(currentApprovalAction)
128
+ }.`}
124
129
  />,
125
130
  )
126
131
  } else if (isContributorCitation) {
@@ -144,7 +149,9 @@ export const NotificationAccordion = ({
144
149
  toast.success(
145
150
  <ToastContent
146
151
  title="Contributor Citation Processed"
147
- body={`Citation has been ${currentApprovalAction}.`}
152
+ body={`Citation has been ${
153
+ formatStatusForDisplay(currentApprovalAction)
154
+ }.`}
148
155
  />,
149
156
  )
150
157
  }
@@ -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} />{" "}