@openneuro/app 4.15.2-demo.1 → 4.16.1-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 +5 -5
- package/src/scripts/authentication/regular-user.tsx +20 -0
- package/src/scripts/datalad/dataset/comments-fragments.js +2 -1
- package/src/scripts/dataset/comments/__tests__/__snapshots__/comment.spec.jsx.snap +211 -2
- package/src/scripts/dataset/comments/__tests__/comment.spec.jsx +25 -1
- package/src/scripts/dataset/comments/comment.jsx +3 -3
- package/src/scripts/dataset/dataset-query.jsx +8 -0
- package/src/scripts/dataset/files/file-tree.tsx +5 -3
- package/src/scripts/dataset/files/file.tsx +1 -1
- package/src/scripts/dataset/mutations/__tests__/__snapshots__/delete.spec.jsx.snap +0 -34
- package/src/scripts/dataset/mutations/__tests__/delete-file.spec.jsx +195 -0
- package/src/scripts/dataset/mutations/__tests__/delete.spec.jsx +0 -47
- package/src/scripts/dataset/mutations/delete-file.jsx +94 -21
- package/src/scripts/dataset/routes/delete-page.tsx +31 -21
- package/src/scripts/uploader/upload-issues.jsx +1 -1
- package/src/scripts/users/username.tsx +2 -2
- package/src/scripts/validation/validation-results.issues.jsx +2 -2
- package/src/scripts/validation/validation-results.jsx +1 -1
- package/src/scripts/workers/validate.worker.ts +9 -12
- package/src/scripts/workers/worker-interface.ts +1 -4
- package/vite.config.js +0 -1
- package/src/scripts/datalad/mutations/delete-dir.jsx +0 -43
- package/src/scripts/datalad/mutations/delete-file.jsx +0 -41
- package/src/scripts/dataset/mutations/delete-dir.jsx +0 -43
- package/src/scripts/utils/schema-validator.js +0 -196477
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import PropTypes from 'prop-types'
|
|
3
|
-
import { gql } from '@apollo/client'
|
|
4
|
-
import { Mutation } from '@apollo/client/react/components'
|
|
3
|
+
import { gql, useMutation } from '@apollo/client'
|
|
5
4
|
import { WarnButton } from '@openneuro/components/warn-button'
|
|
6
5
|
|
|
7
6
|
const DELETE_FILE = gql`
|
|
@@ -10,25 +9,99 @@ const DELETE_FILE = gql`
|
|
|
10
9
|
}
|
|
11
10
|
`
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
12
|
+
/**
|
|
13
|
+
* 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
|
+
*/
|
|
15
|
+
export function fileCacheDeleteFilter(file, path, filename, cachedFileObjects) {
|
|
16
|
+
const fullPath = [path, filename].filter(Boolean).join(':')
|
|
17
|
+
if (file.filename === fullPath) {
|
|
18
|
+
return false
|
|
19
|
+
} else {
|
|
20
|
+
if (file.directory && fullPath.startsWith(file.filename)) {
|
|
21
|
+
// If a file other than the deletion target is removed
|
|
22
|
+
// And no other files match this directory prefix
|
|
23
|
+
for (const f of cachedFileObjects) {
|
|
24
|
+
if (f.directory || f.filename === fullPath) {
|
|
25
|
+
continue
|
|
26
|
+
} else {
|
|
27
|
+
if (f.filename.startsWith(path)) {
|
|
28
|
+
return true
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return false
|
|
33
|
+
}
|
|
34
|
+
return true
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const DeleteFile = ({ datasetId, path, filename }) => {
|
|
39
|
+
const [deleteFiles] = useMutation(DELETE_FILE, {
|
|
40
|
+
awaitRefetchQueries: true,
|
|
41
|
+
update(cache, { data: { deleteFiles } }) {
|
|
42
|
+
if (deleteFiles) {
|
|
43
|
+
cache.modify({
|
|
44
|
+
id: `Draft:${datasetId}`,
|
|
45
|
+
fields: {
|
|
46
|
+
files(cachedFiles) {
|
|
47
|
+
// Filter any removed files from the Draft.files cache
|
|
48
|
+
const cachedFileObjects = cachedFiles.map(f =>
|
|
49
|
+
cache.readFragment({
|
|
50
|
+
id: cache.identify(f),
|
|
51
|
+
fragment: gql`
|
|
52
|
+
fragment DeletedFile on DatasetFile {
|
|
53
|
+
id
|
|
54
|
+
key
|
|
55
|
+
filename
|
|
56
|
+
directory
|
|
57
|
+
}
|
|
58
|
+
`,
|
|
59
|
+
}),
|
|
60
|
+
)
|
|
61
|
+
const remainingFiles = cachedFiles.filter(f => {
|
|
62
|
+
// Get the cache key for each file we have loaded
|
|
63
|
+
const file = cache.readFragment({
|
|
64
|
+
id: cache.identify(f),
|
|
65
|
+
fragment: gql`
|
|
66
|
+
fragment DeletedFile on DatasetFile {
|
|
67
|
+
id
|
|
68
|
+
key
|
|
69
|
+
filename
|
|
70
|
+
directory
|
|
71
|
+
}
|
|
72
|
+
`,
|
|
73
|
+
})
|
|
74
|
+
return fileCacheDeleteFilter(
|
|
75
|
+
file,
|
|
76
|
+
path,
|
|
77
|
+
filename,
|
|
78
|
+
cachedFileObjects,
|
|
79
|
+
)
|
|
80
|
+
})
|
|
81
|
+
return remainingFiles
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
})
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<span className="delete-file">
|
|
91
|
+
<WarnButton
|
|
92
|
+
message=""
|
|
93
|
+
iconOnly={true}
|
|
94
|
+
icon="fa-trash"
|
|
95
|
+
className="edit-file"
|
|
96
|
+
onConfirmedClick={() => {
|
|
97
|
+
deleteFiles({
|
|
98
|
+
variables: { datasetId, files: [{ path, filename }] },
|
|
99
|
+
})
|
|
100
|
+
}}
|
|
101
|
+
/>
|
|
102
|
+
</span>
|
|
103
|
+
)
|
|
104
|
+
}
|
|
32
105
|
|
|
33
106
|
DeleteFile.propTypes = {
|
|
34
107
|
datasetId: PropTypes.string,
|
|
@@ -3,7 +3,9 @@ import { useCookies } from 'react-cookie'
|
|
|
3
3
|
import DeleteDatasetForm from '../mutations/delete-dataset-form.jsx'
|
|
4
4
|
import DeleteDataset from '../mutations/delete.jsx'
|
|
5
5
|
import LoggedIn from '../../authentication/logged-in.jsx'
|
|
6
|
-
import {
|
|
6
|
+
import { getProfile } from '../../authentication/profile.js'
|
|
7
|
+
import AdminUser from '../../authentication/admin-user.jsx'
|
|
8
|
+
import { RegularUser } from '../../authentication/regular-user'
|
|
7
9
|
import { DatasetPageBorder } from './styles/dataset-page-border'
|
|
8
10
|
import { HeaderRow3 } from './styles/header-row'
|
|
9
11
|
|
|
@@ -28,30 +30,38 @@ const DeletePage = ({ dataset }: DeletePageProps): React.ReactElement => {
|
|
|
28
30
|
}
|
|
29
31
|
const [cookies] = useCookies()
|
|
30
32
|
const user = getProfile(cookies)
|
|
31
|
-
const hasEdit =
|
|
32
|
-
(user && user.admin) ||
|
|
33
|
-
hasEditPermissions(dataset.permissions, user && user.sub)
|
|
33
|
+
const hasEdit = user && user.admin
|
|
34
34
|
const datasetId = dataset.id
|
|
35
35
|
return (
|
|
36
36
|
<DatasetPageBorder>
|
|
37
37
|
<HeaderRow3>Delete Dataset</HeaderRow3>
|
|
38
|
-
<
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
<
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
<
|
|
52
|
-
<
|
|
53
|
-
|
|
54
|
-
|
|
38
|
+
<AdminUser>
|
|
39
|
+
<DeleteDatasetForm
|
|
40
|
+
values={values}
|
|
41
|
+
onChange={handleInputChange}
|
|
42
|
+
hideDisabled={false}
|
|
43
|
+
hasEdit={hasEdit}
|
|
44
|
+
/>
|
|
45
|
+
<p>
|
|
46
|
+
<small className="warning-text">
|
|
47
|
+
* Warning: this action will permanently remove this dataset along
|
|
48
|
+
with associated snapshots.
|
|
49
|
+
</small>
|
|
50
|
+
</p>
|
|
51
|
+
<div className="dataset-form-controls">
|
|
52
|
+
<LoggedIn>
|
|
53
|
+
<DeleteDataset datasetId={datasetId} metadata={values} />
|
|
54
|
+
</LoggedIn>
|
|
55
|
+
</div>
|
|
56
|
+
</AdminUser>
|
|
57
|
+
<RegularUser>
|
|
58
|
+
<p>
|
|
59
|
+
Please contact support to permanently remove a dataset and all
|
|
60
|
+
versions of it. Provide a reason for the removal request and if the
|
|
61
|
+
dataset is a duplicate or has been supplanted by another provide that
|
|
62
|
+
information for a redirect to be created.
|
|
63
|
+
</p>
|
|
64
|
+
</RegularUser>
|
|
55
65
|
</DatasetPageBorder>
|
|
56
66
|
)
|
|
57
67
|
}
|
|
@@ -4,7 +4,7 @@ import ORCIDiDLogo from '../../assets/ORCIDiD_iconvector.svg'
|
|
|
4
4
|
/**
|
|
5
5
|
* Display component for usernames showing ORCID linking if connected
|
|
6
6
|
*/
|
|
7
|
-
export const Username = ({ user }) => {
|
|
7
|
+
export const Username = ({ user }): JSX.Element => {
|
|
8
8
|
if (user.orcid) {
|
|
9
9
|
return (
|
|
10
10
|
<>
|
|
@@ -15,6 +15,6 @@ export const Username = ({ user }) => {
|
|
|
15
15
|
</>
|
|
16
16
|
)
|
|
17
17
|
} else {
|
|
18
|
-
return user.name
|
|
18
|
+
return user.name as JSX.Element
|
|
19
19
|
}
|
|
20
20
|
}
|
|
@@ -11,7 +11,7 @@ class Issues extends React.Component {
|
|
|
11
11
|
render() {
|
|
12
12
|
const issueFiles = this.props.issues
|
|
13
13
|
const issues = issueFiles.map((issue, index) => {
|
|
14
|
-
let totalFiles = issue.files.length
|
|
14
|
+
let totalFiles = issue.files.length
|
|
15
15
|
if (issue.additionalFileCount) {
|
|
16
16
|
totalFiles += issue.additionalFileCount
|
|
17
17
|
}
|
|
@@ -32,7 +32,7 @@ class Issues extends React.Component {
|
|
|
32
32
|
)
|
|
33
33
|
|
|
34
34
|
// issue sub-errors
|
|
35
|
-
const subErrors =
|
|
35
|
+
const subErrors = issue.files.map((error, index2) => {
|
|
36
36
|
return error ? (
|
|
37
37
|
<Issue
|
|
38
38
|
type={this.props.issueType}
|
|
@@ -78,7 +78,7 @@ class ValidationResults extends React.Component {
|
|
|
78
78
|
_countFiles(issues) {
|
|
79
79
|
let numFiles = 0
|
|
80
80
|
for (const issue of issues) {
|
|
81
|
-
numFiles +=
|
|
81
|
+
numFiles += issue.files.length
|
|
82
82
|
if (issue.additionalFileCount) {
|
|
83
83
|
numFiles += issue.additionalFileCount
|
|
84
84
|
}
|
|
@@ -1,25 +1,22 @@
|
|
|
1
1
|
/* eslint-env worker */
|
|
2
|
-
import
|
|
2
|
+
import validate from 'bids-validator'
|
|
3
3
|
import { BIDSValidatorIssues } from './worker-interface'
|
|
4
4
|
|
|
5
|
+
const asyncValidateBIDS = (files, options): Promise<BIDSValidatorIssues> =>
|
|
6
|
+
new Promise(resolve => {
|
|
7
|
+
validate.BIDS(files, options, (issues, summary) =>
|
|
8
|
+
resolve({ issues, summary }),
|
|
9
|
+
)
|
|
10
|
+
})
|
|
11
|
+
|
|
5
12
|
export async function runValidator(
|
|
6
13
|
files,
|
|
7
14
|
options,
|
|
8
15
|
cb,
|
|
9
16
|
): Promise<BIDSValidatorIssues> {
|
|
10
17
|
let error, output: BIDSValidatorIssues
|
|
11
|
-
output = { issues: { errors: [], warnings: [] }, summary: {} }
|
|
12
18
|
try {
|
|
13
|
-
|
|
14
|
-
const result = await validate(tree, { json: true })
|
|
15
|
-
const issues = Array.from(result.issues, ([key, value]) => value)
|
|
16
|
-
console.log(issues)
|
|
17
|
-
output.issues.warnings = issues.filter(
|
|
18
|
-
issue => issue.severity === 'warning',
|
|
19
|
-
)
|
|
20
|
-
output.issues.errors = issues.filter(issue => issue.severity === 'error')
|
|
21
|
-
output.summary = result.summary
|
|
22
|
-
console.log(output)
|
|
19
|
+
output = await asyncValidateBIDS(files, options)
|
|
23
20
|
} catch (err) {
|
|
24
21
|
error = err
|
|
25
22
|
}
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
// Shared interface for BIDSValidatorIssues shared without cross import for validate and validate.worker
|
|
2
2
|
export interface BIDSValidatorIssues {
|
|
3
|
-
issues:
|
|
4
|
-
errors: Record<string, unknown>[]
|
|
5
|
-
warnings: Record<string, unknown>[]
|
|
6
|
-
}
|
|
3
|
+
issues: Record<string, unknown>[]
|
|
7
4
|
summary: Record<string, unknown>
|
|
8
5
|
}
|
package/vite.config.js
CHANGED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import PropTypes from 'prop-types'
|
|
3
|
-
import { gql } from '@apollo/client'
|
|
4
|
-
import { Mutation } from '@apollo/client/react/components'
|
|
5
|
-
import WarnButton from '../../common/forms/warn-button.jsx'
|
|
6
|
-
|
|
7
|
-
export const DELETE_FILES = gql`
|
|
8
|
-
mutation deleteFiles($datasetId: ID!, $files: [DeleteFile]!) {
|
|
9
|
-
deleteFiles(datasetId: $datasetId, files: $files)
|
|
10
|
-
}
|
|
11
|
-
`
|
|
12
|
-
|
|
13
|
-
const DeleteDir = ({ datasetId, path }) => (
|
|
14
|
-
<Mutation mutation={DELETE_FILES} awaitRefetchQueries={true}>
|
|
15
|
-
{deleteFiles => (
|
|
16
|
-
<span className="delete-file">
|
|
17
|
-
<WarnButton
|
|
18
|
-
message="Delete"
|
|
19
|
-
icon="fa-trash"
|
|
20
|
-
warn={true}
|
|
21
|
-
className="edit-file"
|
|
22
|
-
action={cb => {
|
|
23
|
-
deleteFiles({
|
|
24
|
-
variables: {
|
|
25
|
-
datasetId,
|
|
26
|
-
files: [{ path }],
|
|
27
|
-
},
|
|
28
|
-
}).then(() => {
|
|
29
|
-
cb()
|
|
30
|
-
})
|
|
31
|
-
}}
|
|
32
|
-
/>
|
|
33
|
-
</span>
|
|
34
|
-
)}
|
|
35
|
-
</Mutation>
|
|
36
|
-
)
|
|
37
|
-
|
|
38
|
-
DeleteDir.propTypes = {
|
|
39
|
-
datasetId: PropTypes.string,
|
|
40
|
-
path: PropTypes.string,
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export default DeleteDir
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import PropTypes from 'prop-types'
|
|
3
|
-
import { gql } from '@apollo/client'
|
|
4
|
-
import { Mutation } from '@apollo/client/react/components'
|
|
5
|
-
import WarnButton from '../../common/forms/warn-button.jsx'
|
|
6
|
-
|
|
7
|
-
const DELETE_FILE = gql`
|
|
8
|
-
mutation deleteFiles($datasetId: ID!, $files: [DeleteFile]) {
|
|
9
|
-
deleteFiles(datasetId: $datasetId, files: $files)
|
|
10
|
-
}
|
|
11
|
-
`
|
|
12
|
-
|
|
13
|
-
const DeleteFile = ({ datasetId, path, filename }) => (
|
|
14
|
-
<Mutation mutation={DELETE_FILE} awaitRefetchQueries={true}>
|
|
15
|
-
{deleteFiles => (
|
|
16
|
-
<span className="delete-file">
|
|
17
|
-
<WarnButton
|
|
18
|
-
message="Delete"
|
|
19
|
-
icon="fa-trash"
|
|
20
|
-
warn={true}
|
|
21
|
-
className="edit-file"
|
|
22
|
-
action={cb => {
|
|
23
|
-
deleteFiles({
|
|
24
|
-
variables: { datasetId, files: [{ path, filename }] },
|
|
25
|
-
}).then(() => {
|
|
26
|
-
cb()
|
|
27
|
-
})
|
|
28
|
-
}}
|
|
29
|
-
/>
|
|
30
|
-
</span>
|
|
31
|
-
)}
|
|
32
|
-
</Mutation>
|
|
33
|
-
)
|
|
34
|
-
|
|
35
|
-
DeleteFile.propTypes = {
|
|
36
|
-
datasetId: PropTypes.string,
|
|
37
|
-
path: PropTypes.string,
|
|
38
|
-
filename: PropTypes.string,
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export default DeleteFile
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import PropTypes from 'prop-types'
|
|
3
|
-
import { gql } from '@apollo/client'
|
|
4
|
-
import { Mutation } from '@apollo/client/react/components'
|
|
5
|
-
import { WarnButton } from '@openneuro/components/warn-button'
|
|
6
|
-
|
|
7
|
-
export const DELETE_FILES = gql`
|
|
8
|
-
mutation deleteFiles($datasetId: ID!, $files: [DeleteFile]!) {
|
|
9
|
-
deleteFiles(datasetId: $datasetId, files: $files)
|
|
10
|
-
}
|
|
11
|
-
`
|
|
12
|
-
|
|
13
|
-
const DeleteDir = ({ datasetId, path, name }) => (
|
|
14
|
-
<Mutation mutation={DELETE_FILES} awaitRefetchQueries={true}>
|
|
15
|
-
{deleteFiles => (
|
|
16
|
-
<span className="delete-file">
|
|
17
|
-
<WarnButton
|
|
18
|
-
message=""
|
|
19
|
-
iconOnly={true}
|
|
20
|
-
icon="fa-trash"
|
|
21
|
-
className="edit-file"
|
|
22
|
-
tooltip={'Delete ' + name}
|
|
23
|
-
onConfirmedClick={() => {
|
|
24
|
-
deleteFiles({
|
|
25
|
-
variables: {
|
|
26
|
-
datasetId,
|
|
27
|
-
files: [{ path }],
|
|
28
|
-
},
|
|
29
|
-
})
|
|
30
|
-
}}
|
|
31
|
-
/>
|
|
32
|
-
</span>
|
|
33
|
-
)}
|
|
34
|
-
</Mutation>
|
|
35
|
-
)
|
|
36
|
-
|
|
37
|
-
DeleteDir.propTypes = {
|
|
38
|
-
datasetId: PropTypes.string,
|
|
39
|
-
path: PropTypes.string,
|
|
40
|
-
name: PropTypes.string,
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export default DeleteDir
|