@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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openneuro/app",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"description": "React JS web frontend for the OpenNeuro platform.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "public/client.js",
|
|
@@ -79,5 +79,5 @@
|
|
|
79
79
|
"publishConfig": {
|
|
80
80
|
"access": "public"
|
|
81
81
|
},
|
|
82
|
-
"gitHead": "
|
|
82
|
+
"gitHead": "437b7f1a10abf79d6e49058b86cf0ddb625de684"
|
|
83
83
|
}
|
package/src/client.jsx
CHANGED
|
@@ -2,11 +2,12 @@ import React from "react"
|
|
|
2
2
|
import { Link } from "react-router-dom"
|
|
3
3
|
import { useUser } from "../queries/user"
|
|
4
4
|
import type { Contributor } from "../types/datacite"
|
|
5
|
+
import type { RequestStatus } from "../types/event-types.ts"
|
|
5
6
|
import ORCIDiDLogo from "../../assets/ORCIDiD_iconvector.svg"
|
|
6
7
|
|
|
7
8
|
interface SingleContributorDisplayProps {
|
|
8
9
|
contributor: Contributor & {
|
|
9
|
-
resolutionStatus?:
|
|
10
|
+
resolutionStatus?: RequestStatus
|
|
10
11
|
}
|
|
11
12
|
isLast: boolean
|
|
12
13
|
separator: React.ReactNode
|
|
@@ -34,7 +35,7 @@ export const SingleContributorDisplay: React.FC<SingleContributorDisplayProps> =
|
|
|
34
35
|
const userExists = !!user?.id
|
|
35
36
|
|
|
36
37
|
// TODO get resolutionStatus from event/contributor
|
|
37
|
-
const resolutionAccepted = true //contributor.resolutionStatus === "
|
|
38
|
+
const resolutionAccepted = true //contributor.resolutionStatus === "ACCEPTED"
|
|
38
39
|
|
|
39
40
|
return (
|
|
40
41
|
<>
|
|
@@ -2,7 +2,7 @@ import React from "react"
|
|
|
2
2
|
import PropTypes from "prop-types"
|
|
3
3
|
import { gql } from "@apollo/client"
|
|
4
4
|
import { Mutation } from "@apollo/client/react/components"
|
|
5
|
-
import { DATASET_COMMENTS } from "
|
|
5
|
+
import { DATASET_COMMENTS } from "../../dataset/fragments/comments-fragments.js"
|
|
6
6
|
import { datasetCacheId } from "./cache-id.js"
|
|
7
7
|
|
|
8
8
|
const deleteComment = gql`
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { datasetCanonicalUrl } from "../dataset-canonical-url"
|
|
2
|
+
import { dataset } from "../../fixtures/dataset-query"
|
|
3
|
+
import { vitest } from "vitest"
|
|
4
|
+
|
|
5
|
+
vitest.mock("../../config", () => ({
|
|
6
|
+
config: { url: "http://localhost:9876" },
|
|
7
|
+
}))
|
|
8
|
+
|
|
9
|
+
describe("datasetCanonicalUrl", () => {
|
|
10
|
+
it("returns the canonical URL for a dataset with snapshots", () => {
|
|
11
|
+
const url = datasetCanonicalUrl(dataset)
|
|
12
|
+
expect(url.href).toBe("http://localhost:9876/datasets/ds001032/versions/2.0.0")
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it("returns the canonical URL for a draft dataset", () => {
|
|
16
|
+
const draftDataset = { ...dataset, snapshots: [] }
|
|
17
|
+
const url = datasetCanonicalUrl(draftDataset)
|
|
18
|
+
expect(url.href).toBe("http://localhost:9876/datasets/ds001032")
|
|
19
|
+
})
|
|
20
|
+
})
|
|
@@ -102,7 +102,7 @@ export const DatasetEventItem: React.FC<DatasetEventItemProps> = ({
|
|
|
102
102
|
)
|
|
103
103
|
} else {
|
|
104
104
|
if (event.event.type === "contributorResponse") {
|
|
105
|
-
const statusText = event.event.resolutionStatus === "
|
|
105
|
+
const statusText = event.event.resolutionStatus === "ACCEPTED"
|
|
106
106
|
? "Accepted"
|
|
107
107
|
: "Denied"
|
|
108
108
|
return (
|
|
@@ -160,7 +160,7 @@ export const DatasetEventItem: React.FC<DatasetEventItemProps> = ({
|
|
|
160
160
|
}
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
-
const handleSaveOrProcessRequest = async (status?: "
|
|
163
|
+
const handleSaveOrProcessRequest = async (status?: "ACCEPTED" | "DENIED") => {
|
|
164
164
|
if (status) {
|
|
165
165
|
if (!event.user?.id) {
|
|
166
166
|
toast.error("Cannot process request: User ID not found.")
|
|
@@ -239,14 +239,14 @@ export const DatasetEventItem: React.FC<DatasetEventItemProps> = ({
|
|
|
239
239
|
{editingNoteId === event.id && (
|
|
240
240
|
<>
|
|
241
241
|
<button
|
|
242
|
-
onClick={() => handleSaveOrProcessRequest("
|
|
242
|
+
onClick={() => handleSaveOrProcessRequest("ACCEPTED")}
|
|
243
243
|
className={`${styles.eventActionButton} on-button on-button--small on-button--secondary`}
|
|
244
244
|
style={{ marginBottom: "5px" }}
|
|
245
245
|
>
|
|
246
246
|
Accept
|
|
247
247
|
</button>
|
|
248
248
|
<button
|
|
249
|
-
onClick={() => handleSaveOrProcessRequest("
|
|
249
|
+
onClick={() => handleSaveOrProcessRequest("DENIED")}
|
|
250
250
|
className={`${styles.eventActionButton} on-button on-button--small`}
|
|
251
251
|
>
|
|
252
252
|
Deny
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { config } from "../config"
|
|
2
|
+
|
|
3
|
+
export function datasetCanonicalUrl(dataset): URL {
|
|
4
|
+
const siteUrl = config.url
|
|
5
|
+
if (dataset.snapshots.length) {
|
|
6
|
+
const latestSnapshot = dataset.snapshots.slice(-1)[0]
|
|
7
|
+
return new URL(
|
|
8
|
+
`/datasets/${dataset.id}/versions/${latestSnapshot.tag}`,
|
|
9
|
+
siteUrl,
|
|
10
|
+
)
|
|
11
|
+
} else {
|
|
12
|
+
return new URL(`/datasets/${dataset.id}`, siteUrl)
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -3,6 +3,7 @@ import * as Sentry from "@sentry/react"
|
|
|
3
3
|
import PropTypes from "prop-types"
|
|
4
4
|
import { useNavigate, useParams } from "react-router-dom"
|
|
5
5
|
import { useApolloClient, useQuery } from "@apollo/client"
|
|
6
|
+
import Helmet from "react-helmet"
|
|
6
7
|
import { Loading } from "../components/loading/Loading"
|
|
7
8
|
|
|
8
9
|
import DatasetQueryContext from "../datalad/dataset/dataset-query-context.js"
|
|
@@ -16,6 +17,7 @@ import { trackAnalytics } from "../utils/datalad"
|
|
|
16
17
|
import FourOFourPage from "../errors/404page"
|
|
17
18
|
import FourOThreePage from "../errors/403page"
|
|
18
19
|
import { getDatasetPage, getDraftPage } from "../queries/dataset"
|
|
20
|
+
import { datasetCanonicalUrl } from "./dataset-canonical-url"
|
|
19
21
|
|
|
20
22
|
/**
|
|
21
23
|
* Query to load and render dataset page - most dataset loading is done here
|
|
@@ -63,6 +65,12 @@ export const DatasetQueryHook = ({ datasetId, draft }) => {
|
|
|
63
65
|
}
|
|
64
66
|
return (
|
|
65
67
|
<DatasetContext.Provider value={data.dataset}>
|
|
68
|
+
<Helmet>
|
|
69
|
+
<link
|
|
70
|
+
rel="canonical"
|
|
71
|
+
href={datasetCanonicalUrl(data.dataset).toString()}
|
|
72
|
+
/>
|
|
73
|
+
</Helmet>
|
|
66
74
|
<ErrorBoundary subject={"error in dataset page"}>
|
|
67
75
|
<DatasetQueryContext.Provider
|
|
68
76
|
value={{
|
|
@@ -24,7 +24,7 @@ export function generateDownloadScript(data): string {
|
|
|
24
24
|
// ds000001:1.0.0 -> ds000001-1.0.0 for directories
|
|
25
25
|
const directory = data.snapshot.id.split(":").join("-")
|
|
26
26
|
let script = "#!/bin/sh\n"
|
|
27
|
-
for (const f of data.snapshot.
|
|
27
|
+
for (const f of data.snapshot.files) {
|
|
28
28
|
script += `curl --create-dirs ${f.urls[0]} -o ${directory}/${f.filename}\n`
|
|
29
29
|
}
|
|
30
30
|
return script
|
|
@@ -39,7 +39,7 @@ export const getSnapshotDownload = gql`
|
|
|
39
39
|
query snapshot($datasetId: ID!, $tag: String!) {
|
|
40
40
|
snapshot(datasetId: $datasetId, tag: $tag) {
|
|
41
41
|
id
|
|
42
|
-
|
|
42
|
+
files(recursive: true) {
|
|
43
43
|
id
|
|
44
44
|
directory
|
|
45
45
|
filename
|
|
@@ -54,14 +54,17 @@ export const DownloadScript = ({
|
|
|
54
54
|
datasetId,
|
|
55
55
|
snapshotTag,
|
|
56
56
|
}: DownloadS3DerivativesProps): JSX.Element => {
|
|
57
|
-
const [getDownload, { loading, data }] = useLazyQuery(
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
57
|
+
const [getDownload, { loading, data, error }] = useLazyQuery(
|
|
58
|
+
getSnapshotDownload,
|
|
59
|
+
{
|
|
60
|
+
variables: {
|
|
61
|
+
datasetId: datasetId,
|
|
62
|
+
tag: snapshotTag,
|
|
63
|
+
},
|
|
64
|
+
errorPolicy: "all",
|
|
61
65
|
},
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
if (data) {
|
|
66
|
+
)
|
|
67
|
+
if (data?.snapshot?.files) {
|
|
65
68
|
const script = generateDownloadScript(data)
|
|
66
69
|
inlineDownload(`${datasetId}-${snapshotTag}.sh`, script)
|
|
67
70
|
}
|
|
@@ -76,10 +79,18 @@ export const DownloadScript = ({
|
|
|
76
79
|
Node.js.
|
|
77
80
|
</p>
|
|
78
81
|
<p>
|
|
79
|
-
{
|
|
82
|
+
{error
|
|
83
|
+
? (
|
|
84
|
+
"Failed to generate download script."
|
|
85
|
+
)
|
|
86
|
+
: loading
|
|
80
87
|
? (
|
|
81
88
|
"Loading..."
|
|
82
89
|
)
|
|
90
|
+
: data?.snapshot && !data.snapshot.files
|
|
91
|
+
? (
|
|
92
|
+
"Download is not yet available for this version. Please try again later."
|
|
93
|
+
)
|
|
83
94
|
: (
|
|
84
95
|
<a
|
|
85
96
|
href="#"
|
|
@@ -38,7 +38,7 @@ describe("FileTreeUnloadedDirectory component", () => {
|
|
|
38
38
|
expect(
|
|
39
39
|
mergeNewFiles(dir)(defaultObj, { fetchMoreResult: updatedObj }).dataset
|
|
40
40
|
.draft.files,
|
|
41
|
-
).toEqual([a, b, { ...c, id: "
|
|
41
|
+
).toEqual([a, b, { ...c, id: "91011", filename: "sub-01:c" }])
|
|
42
42
|
})
|
|
43
43
|
it("works with snapshots", () => {
|
|
44
44
|
const dir = { filename: "sub-01", directory: true }
|
|
@@ -52,7 +52,7 @@ describe("FileTreeUnloadedDirectory component", () => {
|
|
|
52
52
|
.snapshot.files,
|
|
53
53
|
).toEqual([dir, a, b, {
|
|
54
54
|
...c,
|
|
55
|
-
id: "
|
|
55
|
+
id: "91011",
|
|
56
56
|
filename: "sub-01:c",
|
|
57
57
|
}])
|
|
58
58
|
})
|
|
@@ -67,7 +67,7 @@ describe("FileTreeUnloadedDirectory component", () => {
|
|
|
67
67
|
.snapshot.files,
|
|
68
68
|
).toEqual([{
|
|
69
69
|
...c,
|
|
70
|
-
id: "
|
|
70
|
+
id: "91011",
|
|
71
71
|
filename: "sub-01:c",
|
|
72
72
|
}])
|
|
73
73
|
})
|
|
@@ -83,7 +83,7 @@ describe("FileTreeUnloadedDirectory component", () => {
|
|
|
83
83
|
.dataset.draft.files,
|
|
84
84
|
).toEqual([{
|
|
85
85
|
...c,
|
|
86
|
-
id: "
|
|
86
|
+
id: "91011",
|
|
87
87
|
filename: "sub-01:c",
|
|
88
88
|
}])
|
|
89
89
|
})
|
|
@@ -10,7 +10,6 @@ export const DRAFT_FILES_QUERY = gql`
|
|
|
10
10
|
draft {
|
|
11
11
|
files(tree: $tree) {
|
|
12
12
|
id
|
|
13
|
-
key
|
|
14
13
|
filename
|
|
15
14
|
size
|
|
16
15
|
directory
|
|
@@ -27,7 +26,6 @@ export const SNAPSHOT_FILES_QUERY = gql`
|
|
|
27
26
|
snapshot(datasetId: $datasetId, tag: $snapshotTag) {
|
|
28
27
|
files(tree: $tree) {
|
|
29
28
|
id
|
|
30
|
-
key
|
|
31
29
|
filename
|
|
32
30
|
size
|
|
33
31
|
directory
|
|
@@ -43,8 +41,7 @@ export const SNAPSHOT_FILES_QUERY = gql`
|
|
|
43
41
|
*/
|
|
44
42
|
export const nestFiles = (path) => (file) => ({
|
|
45
43
|
...file,
|
|
46
|
-
|
|
47
|
-
id: `${path}:${file.id}`,
|
|
44
|
+
id: file.id,
|
|
48
45
|
filename: `${path}:${file.filename}`,
|
|
49
46
|
})
|
|
50
47
|
|
|
@@ -85,7 +82,7 @@ export const fetchMoreDirectory = (
|
|
|
85
82
|
) =>
|
|
86
83
|
fetchMore({
|
|
87
84
|
query: snapshotTag ? SNAPSHOT_FILES_QUERY : DRAFT_FILES_QUERY,
|
|
88
|
-
variables: { datasetId, snapshotTag, tree: directory.
|
|
85
|
+
variables: { datasetId, snapshotTag, tree: directory.id },
|
|
89
86
|
updateQuery: mergeNewFiles(directory, snapshotTag),
|
|
90
87
|
})
|
|
91
88
|
|
|
@@ -146,7 +146,7 @@ const FileTree = ({
|
|
|
146
146
|
toggleFileToDelete={toggleFileToDelete}
|
|
147
147
|
isFileToBeDeleted={isFileToBeDeleted}
|
|
148
148
|
filename={file.filename.split(":").pop()}
|
|
149
|
-
annexKey={file.
|
|
149
|
+
annexKey={file.id}
|
|
150
150
|
datasetPermissions={datasetPermissions}
|
|
151
151
|
annexed={file.annexed}
|
|
152
152
|
isMobile={false}
|
|
@@ -9,6 +9,15 @@ const DELETE_FILE = gql`
|
|
|
9
9
|
}
|
|
10
10
|
`
|
|
11
11
|
|
|
12
|
+
const DELETED_FILE_FRAGMENT = gql`
|
|
13
|
+
fragment DeletedFile on DatasetFile {
|
|
14
|
+
id
|
|
15
|
+
key
|
|
16
|
+
filename
|
|
17
|
+
directory
|
|
18
|
+
}
|
|
19
|
+
`
|
|
20
|
+
|
|
12
21
|
/**
|
|
13
22
|
* Given a file object, path/filename for deletion, and a list of currently loaded files, filter any that will be deleted and orphan directories
|
|
14
23
|
*/
|
|
@@ -48,28 +57,14 @@ const DeleteFile = ({ datasetId, path, filename }) => {
|
|
|
48
57
|
const cachedFileObjects = cachedFiles.map((f) =>
|
|
49
58
|
cache.readFragment({
|
|
50
59
|
id: cache.identify(f),
|
|
51
|
-
fragment:
|
|
52
|
-
fragment DeletedFile on DatasetFile {
|
|
53
|
-
id
|
|
54
|
-
key
|
|
55
|
-
filename
|
|
56
|
-
directory
|
|
57
|
-
}
|
|
58
|
-
`,
|
|
60
|
+
fragment: DELETED_FILE_FRAGMENT,
|
|
59
61
|
})
|
|
60
62
|
)
|
|
61
63
|
const remainingFiles = cachedFiles.filter((f) => {
|
|
62
64
|
// Get the cache key for each file we have loaded
|
|
63
65
|
const file = cache.readFragment({
|
|
64
66
|
id: cache.identify(f),
|
|
65
|
-
fragment:
|
|
66
|
-
fragment DeletedFile on DatasetFile {
|
|
67
|
-
id
|
|
68
|
-
key
|
|
69
|
-
filename
|
|
70
|
-
directory
|
|
71
|
-
}
|
|
72
|
-
`,
|
|
67
|
+
fragment: DELETED_FILE_FRAGMENT,
|
|
73
68
|
})
|
|
74
69
|
return fileCacheDeleteFilter(
|
|
75
70
|
file,
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import { gql, useMutation, useQuery } from "@apollo/client"
|
|
3
|
+
import { Button } from "../../components/button/Button"
|
|
4
|
+
|
|
5
|
+
const GET_HOLD_DELETION = gql`
|
|
6
|
+
query getHoldDeletion($datasetId: ID!) {
|
|
7
|
+
dataset(id: $datasetId) {
|
|
8
|
+
id
|
|
9
|
+
holdDeletion
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
`
|
|
13
|
+
|
|
14
|
+
const HOLD_DELETION = gql`
|
|
15
|
+
mutation holdDeletion($datasetId: ID!, $hold: Boolean!) {
|
|
16
|
+
holdDeletion(datasetId: $datasetId, hold: $hold)
|
|
17
|
+
}
|
|
18
|
+
`
|
|
19
|
+
|
|
20
|
+
export const HoldDeletion = ({ datasetId }: { datasetId: string }) => {
|
|
21
|
+
const { data, loading: queryLoading } = useQuery(GET_HOLD_DELETION, {
|
|
22
|
+
variables: { datasetId },
|
|
23
|
+
})
|
|
24
|
+
const [holdDeletion, { loading: mutationLoading }] = useMutation(
|
|
25
|
+
HOLD_DELETION,
|
|
26
|
+
{
|
|
27
|
+
refetchQueries: [{ query: GET_HOLD_DELETION, variables: { datasetId } }],
|
|
28
|
+
},
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
const held = data?.dataset?.holdDeletion ?? false
|
|
32
|
+
const loading = queryLoading || mutationLoading
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<span>
|
|
36
|
+
<Button
|
|
37
|
+
icon={loading
|
|
38
|
+
? "fa fa-spin fa-repeat"
|
|
39
|
+
: held
|
|
40
|
+
? "fa fa-lock"
|
|
41
|
+
: "fa fa-unlock"}
|
|
42
|
+
label={loading
|
|
43
|
+
? "Updating..."
|
|
44
|
+
: held
|
|
45
|
+
? "Deletion Held"
|
|
46
|
+
: "Hold Deletion"}
|
|
47
|
+
primary={!held}
|
|
48
|
+
secondary={held}
|
|
49
|
+
size="small"
|
|
50
|
+
disabled={loading}
|
|
51
|
+
onClick={() => {
|
|
52
|
+
holdDeletion({ variables: { datasetId, hold: !held } })
|
|
53
|
+
}}
|
|
54
|
+
/>
|
|
55
|
+
</span>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import { cleanup, render, screen } from "@testing-library/react"
|
|
3
|
+
import { vi } from "vitest"
|
|
4
|
+
import { NoErrors } from "../snapshot"
|
|
5
|
+
|
|
6
|
+
const mockUseUser = vi.fn()
|
|
7
|
+
|
|
8
|
+
vi.mock("../../../queries/user", () => ({
|
|
9
|
+
useUser: () => mockUseUser(),
|
|
10
|
+
}))
|
|
11
|
+
|
|
12
|
+
vi.mock("../../mutations/fsck-dataset", () => ({
|
|
13
|
+
FsckDataset: ({ disabled }) => (
|
|
14
|
+
<button data-testid="mock-fsck-button" disabled={disabled}>
|
|
15
|
+
Rerun File Checks
|
|
16
|
+
</button>
|
|
17
|
+
),
|
|
18
|
+
}))
|
|
19
|
+
|
|
20
|
+
vi.mock("../../fragments/file-check-list", () => ({
|
|
21
|
+
FileCheckList: () => <div>Mock File Check List</div>,
|
|
22
|
+
}))
|
|
23
|
+
|
|
24
|
+
describe("NoErrors Component", () => {
|
|
25
|
+
afterEach(() => {
|
|
26
|
+
cleanup()
|
|
27
|
+
vi.clearAllMocks()
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
const baseProps = {
|
|
31
|
+
datasetId: "ds000001",
|
|
32
|
+
modified: new Date().toISOString(), // Ensures recheckEnabled is false
|
|
33
|
+
validation: { errors: 0 },
|
|
34
|
+
authors: [{ name: "Author" }],
|
|
35
|
+
fileCheck: { annexFsck: ["badfile.nii.gz"] }, // Ensures !noBadFiles is true
|
|
36
|
+
children: <div data-testid="children">Children</div>,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
it("does not disable FsckDataset for admin users", () => {
|
|
40
|
+
mockUseUser.mockReturnValue({ user: { admin: true } })
|
|
41
|
+
render(<NoErrors {...baseProps} />)
|
|
42
|
+
const fsckButton = screen.getByTestId("mock-fsck-button")
|
|
43
|
+
expect(fsckButton).not.toBeDisabled()
|
|
44
|
+
expect(screen.queryByText(/A recheck can be requested in/)).not
|
|
45
|
+
.toBeInTheDocument()
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it("disables FsckDataset for non-admin users", () => {
|
|
49
|
+
mockUseUser.mockReturnValue({ user: { admin: false } })
|
|
50
|
+
render(<NoErrors {...baseProps} />)
|
|
51
|
+
const fsckButton = screen.getByTestId("mock-fsck-button")
|
|
52
|
+
expect(fsckButton).toBeDisabled()
|
|
53
|
+
expect(screen.getByText(/A recheck can be requested in/))
|
|
54
|
+
.toBeInTheDocument()
|
|
55
|
+
})
|
|
56
|
+
})
|
|
@@ -2,6 +2,7 @@ import React from "react"
|
|
|
2
2
|
import PropTypes from "prop-types"
|
|
3
3
|
import DatasetHistory from "../fragments/dataset-history.jsx"
|
|
4
4
|
import CacheClear from "../mutations/cache-clear.jsx"
|
|
5
|
+
import { HoldDeletion } from "../mutations/hold-deletion"
|
|
5
6
|
import AdminExports from "../mutations/admin-exports"
|
|
6
7
|
import { DatasetPageBorder } from "./styles/dataset-page-border"
|
|
7
8
|
import { HeaderRow3, HeaderRow4 } from "./styles/header-row"
|
|
@@ -19,6 +20,15 @@ const AdminDataset = ({ dataset }) => (
|
|
|
19
20
|
<CacheClear datasetId={dataset.id} />
|
|
20
21
|
</div>
|
|
21
22
|
<hr />
|
|
23
|
+
<HeaderRow4>Hold Automated Draft Deletion</HeaderRow4>
|
|
24
|
+
<p>
|
|
25
|
+
Pause automated deletion of stale draft data and prevent sending a final
|
|
26
|
+
notice for this dataset.
|
|
27
|
+
</p>
|
|
28
|
+
<div className="dataset-form-controls">
|
|
29
|
+
<HoldDeletion datasetId={dataset.id} />
|
|
30
|
+
</div>
|
|
31
|
+
<hr />
|
|
22
32
|
<HeaderRow4>Rerun Exports</HeaderRow4>
|
|
23
33
|
<p>
|
|
24
34
|
Correct most temporary issues with a dataset export by reattempting to
|
|
@@ -11,6 +11,7 @@ import styled from "@emotion/styled"
|
|
|
11
11
|
import { apiPath } from "../files/file"
|
|
12
12
|
import { FileCheckList } from "../fragments/file-check-list"
|
|
13
13
|
import { FsckDataset } from "../mutations/fsck-dataset"
|
|
14
|
+
import { useUser } from "../../queries/user"
|
|
14
15
|
|
|
15
16
|
const FormRow = styled.div`
|
|
16
17
|
margin-top: 0;
|
|
@@ -20,6 +21,8 @@ const FormRow = styled.div`
|
|
|
20
21
|
export const NoErrors = (
|
|
21
22
|
{ datasetId, modified, validation, authors, fileCheck, children },
|
|
22
23
|
) => {
|
|
24
|
+
const { user } = useUser()
|
|
25
|
+
const adminUser = user?.admin || false
|
|
23
26
|
const noErrors = validation?.errors === 0
|
|
24
27
|
// zero authors will cause DOI minting to fail
|
|
25
28
|
const hasAuthor = authors?.length > 0
|
|
@@ -58,8 +61,11 @@ export const NoErrors = (
|
|
|
58
61
|
)}
|
|
59
62
|
{!noBadFiles && (
|
|
60
63
|
<span>
|
|
61
|
-
<FsckDataset
|
|
62
|
-
|
|
64
|
+
<FsckDataset
|
|
65
|
+
datasetId={datasetId}
|
|
66
|
+
disabled={!recheckEnabled && !adminUser}
|
|
67
|
+
/>
|
|
68
|
+
{!recheckEnabled && !adminUser && (
|
|
63
69
|
<p>A recheck can be requested in {minutesDiff} minutes.</p>
|
|
64
70
|
)}
|
|
65
71
|
</span>
|
|
@@ -11,7 +11,7 @@ const PUBLIC_DATASETS_COUNT = gql`
|
|
|
11
11
|
`
|
|
12
12
|
|
|
13
13
|
const BRAIN_INITIATIVE_COUNT = gql`
|
|
14
|
-
query AdvancedSearch($query:
|
|
14
|
+
query AdvancedSearch($query: DatasetSearchInput!, $datasetType: String!) {
|
|
15
15
|
advancedSearch(query: $query, datasetType: $datasetType) {
|
|
16
16
|
pageInfo {
|
|
17
17
|
count
|
|
@@ -26,9 +26,7 @@ const usePublicDatasetsCount = (modality?: string) => {
|
|
|
26
26
|
const query = isNIH ? BRAIN_INITIATIVE_COUNT : PUBLIC_DATASETS_COUNT
|
|
27
27
|
const variables = isNIH
|
|
28
28
|
? {
|
|
29
|
-
query: {
|
|
30
|
-
bool: { filter: [{ match: { brainInitiative: { query: "true" } } }] },
|
|
31
|
-
},
|
|
29
|
+
query: { brainInitiative: true },
|
|
32
30
|
datasetType: "public",
|
|
33
31
|
}
|
|
34
32
|
: { modality }
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { gql } from "@apollo/client"
|
|
5
5
|
import * as DatasetQueryFragments from "../datalad/dataset/dataset-query-fragments.js"
|
|
6
|
-
import { DATASET_COMMENTS } from "../
|
|
6
|
+
import { DATASET_COMMENTS } from "../dataset/fragments/comments-fragments.js"
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Generate the dataset page query
|
|
@@ -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
|
|
@@ -96,18 +96,16 @@ export const UPDATE_USER = gql`
|
|
|
96
96
|
|
|
97
97
|
export const ADVANCED_SEARCH_DATASETS_QUERY = gql`
|
|
98
98
|
query advancedSearchDatasets(
|
|
99
|
-
$query:
|
|
99
|
+
$query: DatasetSearchInput!
|
|
100
100
|
$cursor: String
|
|
101
101
|
$allDatasets: Boolean
|
|
102
102
|
$datasetStatus: String
|
|
103
|
-
$sortBy: JSON
|
|
104
103
|
$first: Int!
|
|
105
104
|
) {
|
|
106
105
|
datasets: advancedSearch(
|
|
107
106
|
query: $query
|
|
108
107
|
allDatasets: $allDatasets
|
|
109
108
|
datasetStatus: $datasetStatus
|
|
110
|
-
sortBy: $sortBy
|
|
111
109
|
first: $first
|
|
112
110
|
after: $cursor
|
|
113
111
|
) {
|