@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.
- package/package.json +2 -2
- package/src/client.jsx +6 -4
- package/src/scripts/authentication/__tests__/profile.spec.js +79 -13
- package/src/scripts/authentication/profile.ts +9 -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/components/dataset-event-item.tsx +4 -4
- 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 +2 -12
- 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/users/user-menu.tsx +1 -1
- 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
|
@@ -53,7 +53,7 @@ export const PROCESS_CONTRIBUTOR_REQUEST_MUTATION = gql`
|
|
|
53
53
|
$datasetId: ID!
|
|
54
54
|
$requestId: ID!
|
|
55
55
|
$targetUserId: ID!
|
|
56
|
-
$resolutionStatus:
|
|
56
|
+
$resolutionStatus: ResponseStatusType!
|
|
57
57
|
$reason: String
|
|
58
58
|
) {
|
|
59
59
|
processContributorRequest(
|
|
@@ -153,7 +153,7 @@ export const CREATE_CONTRIBUTOR_CITATION_EVENT = gql`
|
|
|
153
153
|
`
|
|
154
154
|
|
|
155
155
|
export const PROCESS_CONTRIBUTOR_CITATION_MUTATION = gql`
|
|
156
|
-
mutation ProcessContributorCitation($eventId: ID!, $status:
|
|
156
|
+
mutation ProcessContributorCitation($eventId: ID!, $status: ResponseStatusType!) {
|
|
157
157
|
processContributorCitation(eventId: $eventId, status: $status) {
|
|
158
158
|
id
|
|
159
159
|
timestamp
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { useEffect } from "react"
|
|
2
1
|
import { gql, useQuery } from "@apollo/client"
|
|
3
2
|
import { useCookies } from "react-cookie"
|
|
4
3
|
import { getProfile } from "../authentication/profile"
|
|
@@ -97,18 +96,16 @@ export const UPDATE_USER = gql`
|
|
|
97
96
|
|
|
98
97
|
export const ADVANCED_SEARCH_DATASETS_QUERY = gql`
|
|
99
98
|
query advancedSearchDatasets(
|
|
100
|
-
$query:
|
|
99
|
+
$query: DatasetSearchInput!
|
|
101
100
|
$cursor: String
|
|
102
101
|
$allDatasets: Boolean
|
|
103
102
|
$datasetStatus: String
|
|
104
|
-
$sortBy: JSON
|
|
105
103
|
$first: Int!
|
|
106
104
|
) {
|
|
107
105
|
datasets: advancedSearch(
|
|
108
106
|
query: $query
|
|
109
107
|
allDatasets: $allDatasets
|
|
110
108
|
datasetStatus: $datasetStatus
|
|
111
|
-
sortBy: $sortBy
|
|
112
109
|
first: $first
|
|
113
110
|
after: $cursor
|
|
114
111
|
) {
|
|
@@ -212,7 +209,7 @@ export const ADVANCED_SEARCH_DATASETS_QUERY = gql`
|
|
|
212
209
|
|
|
213
210
|
// Reusable hook to fetch user data
|
|
214
211
|
export const useUser = (userId?: string) => {
|
|
215
|
-
const [cookies
|
|
212
|
+
const [cookies] = useCookies()
|
|
216
213
|
const profile = getProfile(cookies)
|
|
217
214
|
const profileSub = profile?.sub
|
|
218
215
|
|
|
@@ -231,13 +228,6 @@ export const useUser = (userId?: string) => {
|
|
|
231
228
|
Sentry.captureException(userError)
|
|
232
229
|
}
|
|
233
230
|
|
|
234
|
-
// Clear invalid session: cookie exists but query failed or returned no user
|
|
235
|
-
useEffect(() => {
|
|
236
|
-
if (!userLoading && profile && (userError || !userData?.user)) {
|
|
237
|
-
removeCookie("accessToken", { path: "/" })
|
|
238
|
-
}
|
|
239
|
-
}, [userLoading, profile, userError, userData?.user, removeCookie])
|
|
240
|
-
|
|
241
231
|
return {
|
|
242
232
|
user: userData?.user,
|
|
243
233
|
loading: userLoading,
|
|
@@ -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
|
}
|