@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.
Files changed (25) hide show
  1. package/package.json +5 -5
  2. package/src/scripts/authentication/regular-user.tsx +20 -0
  3. package/src/scripts/datalad/dataset/comments-fragments.js +2 -1
  4. package/src/scripts/dataset/comments/__tests__/__snapshots__/comment.spec.jsx.snap +211 -2
  5. package/src/scripts/dataset/comments/__tests__/comment.spec.jsx +25 -1
  6. package/src/scripts/dataset/comments/comment.jsx +3 -3
  7. package/src/scripts/dataset/dataset-query.jsx +8 -0
  8. package/src/scripts/dataset/files/file-tree.tsx +5 -3
  9. package/src/scripts/dataset/files/file.tsx +1 -1
  10. package/src/scripts/dataset/mutations/__tests__/__snapshots__/delete.spec.jsx.snap +0 -34
  11. package/src/scripts/dataset/mutations/__tests__/delete-file.spec.jsx +195 -0
  12. package/src/scripts/dataset/mutations/__tests__/delete.spec.jsx +0 -47
  13. package/src/scripts/dataset/mutations/delete-file.jsx +94 -21
  14. package/src/scripts/dataset/routes/delete-page.tsx +31 -21
  15. package/src/scripts/uploader/upload-issues.jsx +1 -1
  16. package/src/scripts/users/username.tsx +2 -2
  17. package/src/scripts/validation/validation-results.issues.jsx +2 -2
  18. package/src/scripts/validation/validation-results.jsx +1 -1
  19. package/src/scripts/workers/validate.worker.ts +9 -12
  20. package/src/scripts/workers/worker-interface.ts +1 -4
  21. package/vite.config.js +0 -1
  22. package/src/scripts/datalad/mutations/delete-dir.jsx +0 -43
  23. package/src/scripts/datalad/mutations/delete-file.jsx +0 -41
  24. package/src/scripts/dataset/mutations/delete-dir.jsx +0 -43
  25. 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
- const DeleteFile = ({ datasetId, path, filename }) => (
14
- <Mutation mutation={DELETE_FILE} 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
- onConfirmedClick={() => {
23
- deleteFiles({
24
- variables: { datasetId, files: [{ path, filename }] },
25
- })
26
- }}
27
- />
28
- </span>
29
- )}
30
- </Mutation>
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 { hasEditPermissions, getProfile } from '../../authentication/profile.js'
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
- <DeleteDatasetForm
39
- values={values}
40
- onChange={handleInputChange}
41
- hideDisabled={false}
42
- hasEdit={hasEdit}
43
- />
44
- <p>
45
- <small className="warning-text">
46
- * Warning: this action will permanently remove this dataset along with
47
- associated snapshots.
48
- </small>
49
- </p>
50
- <div className=" dataset-form-controls">
51
- <LoggedIn>
52
- <DeleteDataset datasetId={datasetId} metadata={values} />
53
- </LoggedIn>
54
- </div>
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
  }
@@ -65,7 +65,7 @@ class UploadValidator extends React.Component {
65
65
  }
66
66
  const options = {
67
67
  config: {
68
- error: ['NO_AUTHORS'],
68
+ error: ['NO_AUTHORS', 'EMPTY_DATASET_NAME'],
69
69
  ignoreSubjectConsistency: true,
70
70
  blacklistModalities: ['Microscopy'],
71
71
  },
@@ -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 || issue.files.size
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 = Array.from(issue.files).map((error, index2) => {
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 += Array.from(issue.files).length
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 { validate, fileListToTree } from '../utils/schema-validator.js'
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
- const tree = await fileListToTree(files)
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
@@ -7,7 +7,6 @@ export default defineConfig({
7
7
  server: {
8
8
  port: 80,
9
9
  host: '0.0.0.0',
10
- cors: true,
11
10
  },
12
11
  build: {
13
12
  sourcemap: true,
@@ -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