@openneuro/app 4.47.7 → 5.0.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 +2 -2
- package/src/client.jsx +3 -0
- package/src/scripts/contributors/contributor.tsx +3 -2
- package/src/scripts/contributors/contributors-list.tsx +0 -1
- package/src/scripts/datalad/dataset/dataset-query-fragments.js +0 -2
- package/src/scripts/datalad/mutations/delete-comment.jsx +1 -1
- package/src/scripts/dataset/__tests__/dataset-canonical-url.spec.ts +20 -0
- package/src/scripts/dataset/components/dataset-event-item.tsx +4 -4
- package/src/scripts/dataset/dataset-canonical-url.ts +14 -0
- package/src/scripts/dataset/dataset-query.jsx +8 -0
- package/src/scripts/dataset/download/__tests__/download-script.spec.tsx +1 -1
- package/src/scripts/dataset/download/download-script.tsx +21 -10
- package/src/scripts/dataset/files/__tests__/file-tree-unloaded-directory.spec.jsx +4 -4
- package/src/scripts/dataset/files/file-tree-unloaded-directory.jsx +2 -5
- package/src/scripts/dataset/files/file-tree.tsx +1 -1
- package/src/scripts/dataset/mutations/delete-file.jsx +11 -16
- package/src/scripts/dataset/mutations/hold-deletion.tsx +57 -0
- package/src/scripts/dataset/routes/__tests__/snapshot.spec.tsx +56 -0
- package/src/scripts/dataset/routes/admin-datalad.jsx +10 -0
- package/src/scripts/dataset/routes/snapshot.tsx +8 -2
- package/src/scripts/pages/front-page/aggregate-queries/use-publicDatasets-count.ts +2 -4
- package/src/scripts/queries/dataset.ts +1 -1
- package/src/scripts/queries/datasetEvents.ts +2 -2
- package/src/scripts/queries/user.ts +1 -3
- package/src/scripts/search/inputs/__tests__/sort-by-select.spec.tsx +4 -26
- package/src/scripts/search/inputs/sort-by-select.tsx +6 -6
- package/src/scripts/search/use-search-results.tsx +38 -256
- package/src/scripts/types/event-types.ts +21 -8
- package/src/scripts/users/__tests__/user-routes.spec.tsx +2 -11
- package/src/scripts/users/notifications/user-notification-accordion-actions.tsx +5 -5
- package/src/scripts/users/notifications/user-notification-accordion-header.tsx +4 -1
- package/src/scripts/users/notifications/user-notification-accordion.tsx +12 -5
- package/src/scripts/users/notifications/user-notification-reason-input.tsx +6 -3
- package/src/scripts/users/notifications/user-notifications-accordion-body.tsx +3 -3
- package/src/scripts/users/user-datasets-view.tsx +83 -164
- package/src/scripts/datalad/dataset/comments-fragments.js +0 -22
- package/src/scripts/datalad/mutations/follow.jsx +0 -54
- package/src/scripts/datalad/mutations/publish.jsx +0 -58
- package/src/scripts/datalad/mutations/star.jsx +0 -54
- package/src/scripts/pages/admin/user-fragment.ts +0 -21
- package/src/scripts/search/es-query-builders.ts +0 -107
|
@@ -10,7 +10,7 @@ const providerProps = {
|
|
|
10
10
|
|
|
11
11
|
describe("SortBySelect component", () => {
|
|
12
12
|
it("displays Newest when parameters are set to default", () => {
|
|
13
|
-
searchRender(<SortBySelect variables={{ query: {
|
|
13
|
+
searchRender(<SortBySelect variables={{ query: {} }} />, {
|
|
14
14
|
providerProps,
|
|
15
15
|
})
|
|
16
16
|
expect(screen.getByText("SORT BY:").closest("div")).toHaveTextContent(
|
|
@@ -22,17 +22,7 @@ describe("SortBySelect component", () => {
|
|
|
22
22
|
<SortBySelect
|
|
23
23
|
variables={{
|
|
24
24
|
query: {
|
|
25
|
-
|
|
26
|
-
filter: [
|
|
27
|
-
{
|
|
28
|
-
match: {
|
|
29
|
-
"latestSnapshot.summary.modalities": {
|
|
30
|
-
query: "mri",
|
|
31
|
-
},
|
|
32
|
-
},
|
|
33
|
-
},
|
|
34
|
-
],
|
|
35
|
-
},
|
|
25
|
+
modality: "mri",
|
|
36
26
|
},
|
|
37
27
|
}}
|
|
38
28
|
/>,
|
|
@@ -46,7 +36,7 @@ describe("SortBySelect component", () => {
|
|
|
46
36
|
})
|
|
47
37
|
it("displays Relevance when any non-modality parameters are set away from default", () => {
|
|
48
38
|
searchRender(
|
|
49
|
-
<SortBySelect variables={{ query: {
|
|
39
|
+
<SortBySelect variables={{ query: { species: "Human" } }} />,
|
|
50
40
|
{ providerProps },
|
|
51
41
|
)
|
|
52
42
|
expect(screen.getByText("SORT BY:").closest("div")).toHaveTextContent(
|
|
@@ -58,19 +48,7 @@ describe("SortBySelect component", () => {
|
|
|
58
48
|
<SortBySelect
|
|
59
49
|
variables={{
|
|
60
50
|
query: {
|
|
61
|
-
|
|
62
|
-
filter: [
|
|
63
|
-
{
|
|
64
|
-
range: {
|
|
65
|
-
"latestSnapshot.summary.subjectMetadata.age": {
|
|
66
|
-
gte: 10,
|
|
67
|
-
lte: 100,
|
|
68
|
-
relation: "INTERSECTS",
|
|
69
|
-
},
|
|
70
|
-
},
|
|
71
|
-
},
|
|
72
|
-
],
|
|
73
|
-
},
|
|
51
|
+
ageRange: [10, 100],
|
|
74
52
|
},
|
|
75
53
|
}}
|
|
76
54
|
/>,
|
|
@@ -20,13 +20,13 @@ const SortBySelect: FC<SortBySelectProps> = ({
|
|
|
20
20
|
sortBy_selected,
|
|
21
21
|
}))
|
|
22
22
|
|
|
23
|
-
// If no query or only modality is set, ignore "relevance" option
|
|
23
|
+
// If no query or only modality/sortBy is set, ignore "relevance" option
|
|
24
|
+
const queryKeys = Object.keys(variables.query).filter(
|
|
25
|
+
(k) => k !== "sortBy",
|
|
26
|
+
)
|
|
24
27
|
if (
|
|
25
|
-
|
|
26
|
-
(
|
|
27
|
-
variables.query.bool?.filter?.every(
|
|
28
|
-
(f) => f?.match?.["latestSnapshot.summary.modalities"],
|
|
29
|
-
))
|
|
28
|
+
queryKeys.length === 0 ||
|
|
29
|
+
(queryKeys.length === 1 && queryKeys[0] === "modality")
|
|
30
30
|
) {
|
|
31
31
|
const available = sortBy_available.filter(
|
|
32
32
|
(item) => item.value !== "relevance",
|
|
@@ -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:
|
|
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
|
-
|
|
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
|
-
|
|
171
|
-
|
|
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 (
|
|
227
|
-
|
|
228
|
-
|
|
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
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
}
|
|
242
|
-
if (
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
if (
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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
|
|
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 = "
|
|
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?:
|
|
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 ?? "
|
|
98
|
-
needsReview = approval === "
|
|
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 ?? "
|
|
114
|
-
needsReview = approval === "
|
|
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 ?? "
|
|
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 ?? "
|
|
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
|
-
|
|
39
|
-
|
|
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: "
|
|
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("
|
|
36
|
-
disabled={approval === "
|
|
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("
|
|
44
|
-
disabled={approval === "
|
|
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"
|
|
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
|
|
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
|
-
"
|
|
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: "
|
|
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 ${
|
|
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 ${
|
|
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: "
|
|
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
|
|
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 ${
|
|
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 === "
|
|
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 === "
|
|
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 === "
|
|
80
|
+
if (approval === "ACCEPTED" && isContributorResponse) {
|
|
81
81
|
return (
|
|
82
82
|
<div>
|
|
83
83
|
Admin <Username user={adminUser} />{" "}
|