@openneuro/app 4.16.1 → 4.17.1
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 +4 -4
- package/src/scripts/datalad/dataset/comments-fragments.js +2 -1
- package/src/scripts/dataset/__tests__/__snapshots__/snapshot-container.spec.tsx.snap +2 -2
- 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/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/users/username.tsx +2 -2
- 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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openneuro/app",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.17.1",
|
|
4
4
|
"description": "React JS web frontend for the OpenNeuro platform.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "public/client.js",
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
"@emotion/react": "11.6.0",
|
|
21
21
|
"@emotion/styled": "11.6.0",
|
|
22
22
|
"@niivue/niivue": "0.23.1",
|
|
23
|
-
"@openneuro/client": "^4.
|
|
24
|
-
"@openneuro/components": "^4.
|
|
23
|
+
"@openneuro/client": "^4.17.1",
|
|
24
|
+
"@openneuro/components": "^4.17.1",
|
|
25
25
|
"bids-validator": "1.10.0",
|
|
26
26
|
"bytes": "^3.0.0",
|
|
27
27
|
"comlink": "^4.0.5",
|
|
@@ -81,5 +81,5 @@
|
|
|
81
81
|
"publishConfig": {
|
|
82
82
|
"access": "public"
|
|
83
83
|
},
|
|
84
|
-
"gitHead": "
|
|
84
|
+
"gitHead": "c93f1fd308cddfbaef9990be47e77497a277b35e"
|
|
85
85
|
}
|
|
@@ -1454,7 +1454,7 @@ OCI-1131441 (R. Poldrack, PI) in any publications.
|
|
|
1454
1454
|
>
|
|
1455
1455
|
Uploaded by
|
|
1456
1456
|
</h2>
|
|
1457
|
-
Test User on 2021-12-17 -
|
|
1457
|
+
Test User on 2021-12-17 - over 1 year ago
|
|
1458
1458
|
</div>
|
|
1459
1459
|
<div
|
|
1460
1460
|
class="dataset-meta-block undefined"
|
|
@@ -1464,7 +1464,7 @@ OCI-1131441 (R. Poldrack, PI) in any publications.
|
|
|
1464
1464
|
>
|
|
1465
1465
|
Last Updated
|
|
1466
1466
|
</h2>
|
|
1467
|
-
2021-12-17 -
|
|
1467
|
+
2021-12-17 - over 1 year ago
|
|
1468
1468
|
</div>
|
|
1469
1469
|
<div
|
|
1470
1470
|
class="dataset-meta-block undefined"
|
|
@@ -1,5 +1,210 @@
|
|
|
1
1
|
// Vitest Snapshot v1
|
|
2
2
|
|
|
3
|
+
exports[`Comment component > renders an ORCID user comment 1`] = `
|
|
4
|
+
{
|
|
5
|
+
"asFragment": [Function],
|
|
6
|
+
"baseElement": <body>
|
|
7
|
+
<div>
|
|
8
|
+
<div
|
|
9
|
+
class="comment"
|
|
10
|
+
>
|
|
11
|
+
<div
|
|
12
|
+
class="row comment-header"
|
|
13
|
+
>
|
|
14
|
+
By
|
|
15
|
+
Example Exampler
|
|
16
|
+
|
|
17
|
+
<a
|
|
18
|
+
href="https://orcid.org/1234-5678-9101"
|
|
19
|
+
>
|
|
20
|
+
<img
|
|
21
|
+
alt="ORCID logo"
|
|
22
|
+
height="16"
|
|
23
|
+
src="/packages/openneuro-app/src/assets/ORCIDiD_iconvector.svg"
|
|
24
|
+
width="16"
|
|
25
|
+
/>
|
|
26
|
+
</a>
|
|
27
|
+
- almost 2 years ago
|
|
28
|
+
</div>
|
|
29
|
+
<div
|
|
30
|
+
class="row comment-body"
|
|
31
|
+
>
|
|
32
|
+
<div
|
|
33
|
+
class="DraftEditor-root"
|
|
34
|
+
>
|
|
35
|
+
<div
|
|
36
|
+
class="DraftEditor-editorContainer"
|
|
37
|
+
>
|
|
38
|
+
<div
|
|
39
|
+
class="public-DraftEditor-content"
|
|
40
|
+
contenteditable="false"
|
|
41
|
+
spellcheck="false"
|
|
42
|
+
style="outline: none; user-select: text; white-space: pre-wrap; word-wrap: break-word;"
|
|
43
|
+
>
|
|
44
|
+
<div
|
|
45
|
+
data-contents="true"
|
|
46
|
+
>
|
|
47
|
+
<div
|
|
48
|
+
class=""
|
|
49
|
+
data-block="true"
|
|
50
|
+
data-editor="9001"
|
|
51
|
+
data-offset-key="3sm42-0-0"
|
|
52
|
+
>
|
|
53
|
+
<div
|
|
54
|
+
class="public-DraftStyleDefault-block public-DraftStyleDefault-ltr"
|
|
55
|
+
data-offset-key="3sm42-0-0"
|
|
56
|
+
>
|
|
57
|
+
<span
|
|
58
|
+
data-offset-key="3sm42-0-0"
|
|
59
|
+
>
|
|
60
|
+
<br
|
|
61
|
+
data-text="true"
|
|
62
|
+
/>
|
|
63
|
+
</span>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
<div
|
|
73
|
+
class="row replies"
|
|
74
|
+
>
|
|
75
|
+
<div
|
|
76
|
+
class="comment-reply"
|
|
77
|
+
/>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
</body>,
|
|
81
|
+
"container": <div>
|
|
82
|
+
<div
|
|
83
|
+
class="comment"
|
|
84
|
+
>
|
|
85
|
+
<div
|
|
86
|
+
class="row comment-header"
|
|
87
|
+
>
|
|
88
|
+
By
|
|
89
|
+
Example Exampler
|
|
90
|
+
|
|
91
|
+
<a
|
|
92
|
+
href="https://orcid.org/1234-5678-9101"
|
|
93
|
+
>
|
|
94
|
+
<img
|
|
95
|
+
alt="ORCID logo"
|
|
96
|
+
height="16"
|
|
97
|
+
src="/packages/openneuro-app/src/assets/ORCIDiD_iconvector.svg"
|
|
98
|
+
width="16"
|
|
99
|
+
/>
|
|
100
|
+
</a>
|
|
101
|
+
- almost 2 years ago
|
|
102
|
+
</div>
|
|
103
|
+
<div
|
|
104
|
+
class="row comment-body"
|
|
105
|
+
>
|
|
106
|
+
<div
|
|
107
|
+
class="DraftEditor-root"
|
|
108
|
+
>
|
|
109
|
+
<div
|
|
110
|
+
class="DraftEditor-editorContainer"
|
|
111
|
+
>
|
|
112
|
+
<div
|
|
113
|
+
class="public-DraftEditor-content"
|
|
114
|
+
contenteditable="false"
|
|
115
|
+
spellcheck="false"
|
|
116
|
+
style="outline: none; user-select: text; white-space: pre-wrap; word-wrap: break-word;"
|
|
117
|
+
>
|
|
118
|
+
<div
|
|
119
|
+
data-contents="true"
|
|
120
|
+
>
|
|
121
|
+
<div
|
|
122
|
+
class=""
|
|
123
|
+
data-block="true"
|
|
124
|
+
data-editor="9001"
|
|
125
|
+
data-offset-key="3sm42-0-0"
|
|
126
|
+
>
|
|
127
|
+
<div
|
|
128
|
+
class="public-DraftStyleDefault-block public-DraftStyleDefault-ltr"
|
|
129
|
+
data-offset-key="3sm42-0-0"
|
|
130
|
+
>
|
|
131
|
+
<span
|
|
132
|
+
data-offset-key="3sm42-0-0"
|
|
133
|
+
>
|
|
134
|
+
<br
|
|
135
|
+
data-text="true"
|
|
136
|
+
/>
|
|
137
|
+
</span>
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
<div
|
|
147
|
+
class="row replies"
|
|
148
|
+
>
|
|
149
|
+
<div
|
|
150
|
+
class="comment-reply"
|
|
151
|
+
/>
|
|
152
|
+
</div>
|
|
153
|
+
</div>,
|
|
154
|
+
"debug": [Function],
|
|
155
|
+
"findAllByAltText": [Function],
|
|
156
|
+
"findAllByDisplayValue": [Function],
|
|
157
|
+
"findAllByLabelText": [Function],
|
|
158
|
+
"findAllByPlaceholderText": [Function],
|
|
159
|
+
"findAllByRole": [Function],
|
|
160
|
+
"findAllByTestId": [Function],
|
|
161
|
+
"findAllByText": [Function],
|
|
162
|
+
"findAllByTitle": [Function],
|
|
163
|
+
"findByAltText": [Function],
|
|
164
|
+
"findByDisplayValue": [Function],
|
|
165
|
+
"findByLabelText": [Function],
|
|
166
|
+
"findByPlaceholderText": [Function],
|
|
167
|
+
"findByRole": [Function],
|
|
168
|
+
"findByTestId": [Function],
|
|
169
|
+
"findByText": [Function],
|
|
170
|
+
"findByTitle": [Function],
|
|
171
|
+
"getAllByAltText": [Function],
|
|
172
|
+
"getAllByDisplayValue": [Function],
|
|
173
|
+
"getAllByLabelText": [Function],
|
|
174
|
+
"getAllByPlaceholderText": [Function],
|
|
175
|
+
"getAllByRole": [Function],
|
|
176
|
+
"getAllByTestId": [Function],
|
|
177
|
+
"getAllByText": [Function],
|
|
178
|
+
"getAllByTitle": [Function],
|
|
179
|
+
"getByAltText": [Function],
|
|
180
|
+
"getByDisplayValue": [Function],
|
|
181
|
+
"getByLabelText": [Function],
|
|
182
|
+
"getByPlaceholderText": [Function],
|
|
183
|
+
"getByRole": [Function],
|
|
184
|
+
"getByTestId": [Function],
|
|
185
|
+
"getByText": [Function],
|
|
186
|
+
"getByTitle": [Function],
|
|
187
|
+
"queryAllByAltText": [Function],
|
|
188
|
+
"queryAllByDisplayValue": [Function],
|
|
189
|
+
"queryAllByLabelText": [Function],
|
|
190
|
+
"queryAllByPlaceholderText": [Function],
|
|
191
|
+
"queryAllByRole": [Function],
|
|
192
|
+
"queryAllByTestId": [Function],
|
|
193
|
+
"queryAllByText": [Function],
|
|
194
|
+
"queryAllByTitle": [Function],
|
|
195
|
+
"queryByAltText": [Function],
|
|
196
|
+
"queryByDisplayValue": [Function],
|
|
197
|
+
"queryByLabelText": [Function],
|
|
198
|
+
"queryByPlaceholderText": [Function],
|
|
199
|
+
"queryByRole": [Function],
|
|
200
|
+
"queryByTestId": [Function],
|
|
201
|
+
"queryByText": [Function],
|
|
202
|
+
"queryByTitle": [Function],
|
|
203
|
+
"rerender": [Function],
|
|
204
|
+
"unmount": [Function],
|
|
205
|
+
}
|
|
206
|
+
`;
|
|
207
|
+
|
|
3
208
|
exports[`Comment component > renders with an empty comment 1`] = `
|
|
4
209
|
{
|
|
5
210
|
"asFragment": [Function],
|
|
@@ -11,7 +216,9 @@ exports[`Comment component > renders with an empty comment 1`] = `
|
|
|
11
216
|
<div
|
|
12
217
|
class="row comment-header"
|
|
13
218
|
>
|
|
14
|
-
By
|
|
219
|
+
By
|
|
220
|
+
Example Exampler
|
|
221
|
+
- almost 2 years ago
|
|
15
222
|
</div>
|
|
16
223
|
<div
|
|
17
224
|
class="row comment-body"
|
|
@@ -72,7 +279,9 @@ exports[`Comment component > renders with an empty comment 1`] = `
|
|
|
72
279
|
<div
|
|
73
280
|
class="row comment-header"
|
|
74
281
|
>
|
|
75
|
-
By
|
|
282
|
+
By
|
|
283
|
+
Example Exampler
|
|
284
|
+
- almost 2 years ago
|
|
76
285
|
</div>
|
|
77
286
|
<div
|
|
78
287
|
class="row comment-body"
|
|
@@ -17,7 +17,31 @@ describe('Comment component', () => {
|
|
|
17
17
|
data={{
|
|
18
18
|
id: '9001',
|
|
19
19
|
text: emptyState,
|
|
20
|
-
user: {
|
|
20
|
+
user: {
|
|
21
|
+
id: '1234',
|
|
22
|
+
email: 'example@example.com',
|
|
23
|
+
name: 'Example Exampler',
|
|
24
|
+
},
|
|
25
|
+
createDate: new Date('2019-04-02T19:56:41.222Z').toISOString(),
|
|
26
|
+
}}
|
|
27
|
+
/>,
|
|
28
|
+
)
|
|
29
|
+
expect(wrapper).toMatchSnapshot()
|
|
30
|
+
})
|
|
31
|
+
it('renders an ORCID user comment', () => {
|
|
32
|
+
formatDistanceToNow.mockReturnValueOnce('almost 2 years')
|
|
33
|
+
|
|
34
|
+
const wrapper = render(
|
|
35
|
+
<Comment
|
|
36
|
+
data={{
|
|
37
|
+
id: '9001',
|
|
38
|
+
text: emptyState,
|
|
39
|
+
user: {
|
|
40
|
+
id: '1234',
|
|
41
|
+
email: 'example@example.com',
|
|
42
|
+
name: 'Example Exampler',
|
|
43
|
+
orcid: '1234-5678-9101',
|
|
44
|
+
},
|
|
21
45
|
createDate: new Date('2019-04-02T19:56:41.222Z').toISOString(),
|
|
22
46
|
}}
|
|
23
47
|
/>,
|
|
@@ -11,6 +11,7 @@ import LoggedIn from '../../authentication/logged-in.jsx'
|
|
|
11
11
|
import { toast } from 'react-toastify'
|
|
12
12
|
import ToastContent from '../../common/partials/toast-content'
|
|
13
13
|
import { Icon } from '@openneuro/components/icon'
|
|
14
|
+
import { Username } from '../../users/username'
|
|
14
15
|
|
|
15
16
|
const Comment = ({ datasetId, data, children }) => {
|
|
16
17
|
const [replyMode, setReplyMode] = useState(false)
|
|
@@ -21,9 +22,8 @@ const Comment = ({ datasetId, data, children }) => {
|
|
|
21
22
|
<>
|
|
22
23
|
<div className="comment">
|
|
23
24
|
<div className="row comment-header">
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
)} ago`}
|
|
25
|
+
By <Username user={data.user} />
|
|
26
|
+
{` - ${formatDistanceToNow(parseISO(data.createDate))} ago`}
|
|
27
27
|
</div>
|
|
28
28
|
<div className="row comment-body">
|
|
29
29
|
{editMode ? (
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import File from './file'
|
|
3
3
|
import UpdateFile from '../mutations/update-file.jsx'
|
|
4
|
-
import
|
|
4
|
+
import DeleteFile from '../mutations/delete-file.jsx'
|
|
5
5
|
import FileTreeUnloadedDirectory from './file-tree-unloaded-directory.jsx'
|
|
6
6
|
import { Media } from '../../styles/media'
|
|
7
7
|
import { AccordionTab } from '@openneuro/components/accordion'
|
|
@@ -91,8 +91,10 @@ const FileTree = ({
|
|
|
91
91
|
directory>
|
|
92
92
|
<i className="fa fa-plus" /> Add Directory
|
|
93
93
|
</UpdateFile>
|
|
94
|
-
{
|
|
95
|
-
|
|
94
|
+
{path === '' ? (
|
|
95
|
+
bulkDeleteButton
|
|
96
|
+
) : (
|
|
97
|
+
<DeleteFile datasetId={datasetId} path={path} />
|
|
96
98
|
)}
|
|
97
99
|
</span>
|
|
98
100
|
</Media>
|
|
@@ -117,7 +117,7 @@ const File = ({
|
|
|
117
117
|
datasetPermissions,
|
|
118
118
|
toggleFileToDelete,
|
|
119
119
|
isFileToBeDeleted,
|
|
120
|
-
}: FileProps) => {
|
|
120
|
+
}: FileProps): JSX.Element => {
|
|
121
121
|
const { icon, color } = getFileIcon(filename)
|
|
122
122
|
const snapshotVersionPath = snapshotTag ? `/versions/${snapshotTag}` : ''
|
|
123
123
|
// React route to display the file
|
|
@@ -14,37 +14,3 @@ exports[`DeleteDataset mutation > renders with common props 1`] = `
|
|
|
14
14
|
</span>
|
|
15
15
|
</DocumentFragment>
|
|
16
16
|
`;
|
|
17
|
-
|
|
18
|
-
exports[`DeleteDir mutation > renders with common props 1`] = `
|
|
19
|
-
<DocumentFragment>
|
|
20
|
-
<span
|
|
21
|
-
class="delete-file"
|
|
22
|
-
>
|
|
23
|
-
<div
|
|
24
|
-
class="warn-btn edit-file"
|
|
25
|
-
>
|
|
26
|
-
<span
|
|
27
|
-
class=" "
|
|
28
|
-
data-flow="up"
|
|
29
|
-
data-tooltip="Delete undefined"
|
|
30
|
-
>
|
|
31
|
-
<span
|
|
32
|
-
class="warn-btn-click"
|
|
33
|
-
>
|
|
34
|
-
<button
|
|
35
|
-
aria-label=""
|
|
36
|
-
class="on-button on-button--medium on-button--default btn-warn-component"
|
|
37
|
-
role="button"
|
|
38
|
-
type="button"
|
|
39
|
-
>
|
|
40
|
-
<i
|
|
41
|
-
aria-hidden="true"
|
|
42
|
-
class="fa fa-trash css-1qxtz39"
|
|
43
|
-
/>
|
|
44
|
-
</button>
|
|
45
|
-
</span>
|
|
46
|
-
</span>
|
|
47
|
-
</div>
|
|
48
|
-
</span>
|
|
49
|
-
</DocumentFragment>
|
|
50
|
-
`;
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { fileCacheDeleteFilter } from '../delete-file.jsx'
|
|
2
|
+
|
|
3
|
+
describe('DeleteFile mutation', () => {
|
|
4
|
+
describe('fileCacheDeleteFilter', () => {
|
|
5
|
+
it('removes a deleted file', () => {
|
|
6
|
+
expect(
|
|
7
|
+
fileCacheDeleteFilter(
|
|
8
|
+
{
|
|
9
|
+
id: 'DatasetFile:abcdef',
|
|
10
|
+
key: 'cdefgh',
|
|
11
|
+
filename: 'sub-01:anat:sub-01_T1w.nii.gz',
|
|
12
|
+
directory: false,
|
|
13
|
+
},
|
|
14
|
+
'sub-01:anat',
|
|
15
|
+
'sub-01_T1w.nii.gz',
|
|
16
|
+
[],
|
|
17
|
+
),
|
|
18
|
+
).toBe(false)
|
|
19
|
+
})
|
|
20
|
+
it('does not remove a present file', () => {
|
|
21
|
+
expect(
|
|
22
|
+
fileCacheDeleteFilter(
|
|
23
|
+
{
|
|
24
|
+
id: 'DatasetFile:abcdef',
|
|
25
|
+
key: 'cdefgh',
|
|
26
|
+
filename: 'sub-02:anat:sub-02_T1w.nii.gz',
|
|
27
|
+
directory: false,
|
|
28
|
+
},
|
|
29
|
+
'sub-01:anat',
|
|
30
|
+
'sub-01_T1w.nii.gz',
|
|
31
|
+
[],
|
|
32
|
+
),
|
|
33
|
+
).toBe(true)
|
|
34
|
+
})
|
|
35
|
+
it('removes a matching directory', () => {
|
|
36
|
+
expect(
|
|
37
|
+
fileCacheDeleteFilter(
|
|
38
|
+
{
|
|
39
|
+
id: 'DatasetFile:abcdef',
|
|
40
|
+
key: 'cdefgh',
|
|
41
|
+
filename: 'sub-01:anat',
|
|
42
|
+
directory: true,
|
|
43
|
+
},
|
|
44
|
+
'sub-01:anat',
|
|
45
|
+
'',
|
|
46
|
+
[],
|
|
47
|
+
),
|
|
48
|
+
).toBe(false)
|
|
49
|
+
})
|
|
50
|
+
it('removes empty directories with a child being deleted', () => {
|
|
51
|
+
const cachedFileObjects = [
|
|
52
|
+
{
|
|
53
|
+
__typename: 'DatasetFile',
|
|
54
|
+
id: '3d9b15b3ef4e9da06e265e6078d3b4ddf8495102',
|
|
55
|
+
key: 'c2ee90bf6c477b9e808e2b649a9492e947493297',
|
|
56
|
+
filename: 'CHANGES',
|
|
57
|
+
directory: false,
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
__typename: 'DatasetFile',
|
|
61
|
+
id: '63888a199a5ce37377b1cd708cda59577dad218f',
|
|
62
|
+
key: 'fa84e5f958ec72d42b3e196e592f6db9f7104b19',
|
|
63
|
+
filename: 'README',
|
|
64
|
+
directory: false,
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
__typename: 'DatasetFile',
|
|
68
|
+
id: 'aef1717a00106adc115f64990944d86e154d3e03',
|
|
69
|
+
key: 'e652e8add021fe2684ce2404b431dee4315c9c95',
|
|
70
|
+
filename: 'dataset_description.json',
|
|
71
|
+
directory: false,
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
__typename: 'DatasetFile',
|
|
75
|
+
id: '0a2a5d8d72a31f03608db59c4cfd650aba77c363',
|
|
76
|
+
key: 'b08aa0ec5b5e716479824859524a22140fb2af82',
|
|
77
|
+
filename: 'T1w.json',
|
|
78
|
+
directory: false,
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
__typename: 'DatasetFile',
|
|
82
|
+
id: 'f682a32c9538082fa6c8ad11e9a536dc07d1d0cf',
|
|
83
|
+
key: '37ecbdc7ab8ffaf2cddecc338092f6679089287d',
|
|
84
|
+
filename: 'participants.tsv',
|
|
85
|
+
directory: false,
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
__typename: 'DatasetFile',
|
|
89
|
+
id: 'c2ffc386e99bb26fbfc0d6bb33713b91b95a51f2',
|
|
90
|
+
key: null,
|
|
91
|
+
filename: 'sub-01',
|
|
92
|
+
directory: true,
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
__typename: 'DatasetFile',
|
|
96
|
+
id: '141c63f3373f17477c83f42c9fab01e6825052a0',
|
|
97
|
+
key: null,
|
|
98
|
+
filename: 'sub-02',
|
|
99
|
+
directory: true,
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
__typename: 'DatasetFile',
|
|
103
|
+
id: '635818b25263badb6d105aab8e33822f54ebbecf',
|
|
104
|
+
key: null,
|
|
105
|
+
filename: 'sub-02:anat',
|
|
106
|
+
directory: true,
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
__typename: 'DatasetFile',
|
|
110
|
+
id: 'b6f937773aa2130aa9d06fc3024cd1b150baa70b',
|
|
111
|
+
key: 'SHA256E-s311112--c3527d7944a9619afb57863a34e6af7ec3fe4f108e56c860d9e700699ff806fb.nii.gz',
|
|
112
|
+
filename: 'sub-02:anat:sub-02_T1w.nii.gz',
|
|
113
|
+
directory: false,
|
|
114
|
+
},
|
|
115
|
+
]
|
|
116
|
+
expect(
|
|
117
|
+
fileCacheDeleteFilter(
|
|
118
|
+
{
|
|
119
|
+
id: '141c63f3373f17477c83f42c9fab01e6825052a0',
|
|
120
|
+
key: null,
|
|
121
|
+
filename: 'sub-02',
|
|
122
|
+
directory: true,
|
|
123
|
+
},
|
|
124
|
+
'sub-02:anat',
|
|
125
|
+
'sub-02_T1w.nii.gz',
|
|
126
|
+
cachedFileObjects,
|
|
127
|
+
),
|
|
128
|
+
).toBe(false)
|
|
129
|
+
expect(
|
|
130
|
+
fileCacheDeleteFilter(
|
|
131
|
+
{
|
|
132
|
+
id: '635818b25263badb6d105aab8e33822f54ebbecf',
|
|
133
|
+
key: null,
|
|
134
|
+
filename: 'sub-02:anat',
|
|
135
|
+
directory: true,
|
|
136
|
+
},
|
|
137
|
+
'sub-02:anat',
|
|
138
|
+
'sub-02_T1w.nii.gz',
|
|
139
|
+
cachedFileObjects,
|
|
140
|
+
),
|
|
141
|
+
).toBe(false)
|
|
142
|
+
expect(
|
|
143
|
+
fileCacheDeleteFilter(
|
|
144
|
+
{
|
|
145
|
+
id: 'b6f937773aa2130aa9d06fc3024cd1b150baa70b',
|
|
146
|
+
key: 'SHA256E-s311112--c3527d7944a9619afb57863a34e6af7ec3fe4f108e56c860d9e700699ff806fb.nii.gz',
|
|
147
|
+
filename: 'sub-02:anat:sub-02_T1w.nii.gz',
|
|
148
|
+
directory: false,
|
|
149
|
+
},
|
|
150
|
+
'sub-02:anat',
|
|
151
|
+
'sub-02_T1w.nii.gz',
|
|
152
|
+
cachedFileObjects,
|
|
153
|
+
),
|
|
154
|
+
).toBe(false)
|
|
155
|
+
})
|
|
156
|
+
it('does not remove non-empty directories with a child being deleted', () => {
|
|
157
|
+
expect(
|
|
158
|
+
fileCacheDeleteFilter(
|
|
159
|
+
{
|
|
160
|
+
id: 'DatasetFile:abcdef',
|
|
161
|
+
key: 'cdefgh',
|
|
162
|
+
filename: 'sub-02:anat',
|
|
163
|
+
directory: true,
|
|
164
|
+
},
|
|
165
|
+
'',
|
|
166
|
+
'sub-02:anat:sub-02_T1w.json',
|
|
167
|
+
[
|
|
168
|
+
{
|
|
169
|
+
id: 'DatasetFile:123456',
|
|
170
|
+
key: 'cdefgh',
|
|
171
|
+
filename: 'sub-02:anat:sub-02_T1w.nii.gz',
|
|
172
|
+
directory: false,
|
|
173
|
+
},
|
|
174
|
+
],
|
|
175
|
+
),
|
|
176
|
+
).toBe(true)
|
|
177
|
+
})
|
|
178
|
+
it('does not remove directories that are empty but not having a child deleted', () => {
|
|
179
|
+
// This is also false because the directory has no files in cachedFileObjects
|
|
180
|
+
expect(
|
|
181
|
+
fileCacheDeleteFilter(
|
|
182
|
+
{
|
|
183
|
+
id: 'DatasetFile:abcdef',
|
|
184
|
+
key: 'cdefgh',
|
|
185
|
+
filename: 'sub-02:anat',
|
|
186
|
+
directory: true,
|
|
187
|
+
},
|
|
188
|
+
'sub-01:anat',
|
|
189
|
+
'',
|
|
190
|
+
[],
|
|
191
|
+
),
|
|
192
|
+
).toBe(true)
|
|
193
|
+
})
|
|
194
|
+
})
|
|
195
|
+
})
|
|
@@ -2,10 +2,8 @@ import React from 'react'
|
|
|
2
2
|
import { render, fireEvent, screen } from '@testing-library/react'
|
|
3
3
|
import { MockedProvider } from '@apollo/client/testing'
|
|
4
4
|
import DeleteDataset, { DELETE_DATASET } from '../delete.jsx'
|
|
5
|
-
import DeleteDir, { DELETE_FILES } from '../delete-dir.jsx'
|
|
6
5
|
|
|
7
6
|
const datasetId = 'ds999999'
|
|
8
|
-
const path = 'sub-99'
|
|
9
7
|
|
|
10
8
|
const deleteDatasetMock = {
|
|
11
9
|
request: {
|
|
@@ -20,19 +18,6 @@ const deleteDatasetMock = {
|
|
|
20
18
|
})),
|
|
21
19
|
}
|
|
22
20
|
|
|
23
|
-
const deleteDirMock = {
|
|
24
|
-
request: {
|
|
25
|
-
query: DELETE_FILES,
|
|
26
|
-
variables: {
|
|
27
|
-
datasetId,
|
|
28
|
-
files: [{ path: 'sub-99' }],
|
|
29
|
-
},
|
|
30
|
-
},
|
|
31
|
-
newData: vi.fn(() => ({
|
|
32
|
-
data: {},
|
|
33
|
-
})),
|
|
34
|
-
}
|
|
35
|
-
|
|
36
21
|
describe('DeleteDataset mutation', () => {
|
|
37
22
|
it('renders with common props', () => {
|
|
38
23
|
const { asFragment } = render(
|
|
@@ -43,35 +28,3 @@ describe('DeleteDataset mutation', () => {
|
|
|
43
28
|
expect(asFragment()).toMatchSnapshot()
|
|
44
29
|
})
|
|
45
30
|
})
|
|
46
|
-
|
|
47
|
-
describe('DeleteDir mutation', () => {
|
|
48
|
-
it('renders with common props', () => {
|
|
49
|
-
const { asFragment } = render(
|
|
50
|
-
<MockedProvider mocks={[deleteDirMock]} addTypename={false}>
|
|
51
|
-
<DeleteDir
|
|
52
|
-
datasetId="ds002"
|
|
53
|
-
fileTree={{
|
|
54
|
-
files: [],
|
|
55
|
-
directories: [],
|
|
56
|
-
path: '',
|
|
57
|
-
}}
|
|
58
|
-
/>
|
|
59
|
-
</MockedProvider>,
|
|
60
|
-
)
|
|
61
|
-
expect(asFragment()).toMatchSnapshot()
|
|
62
|
-
})
|
|
63
|
-
it('fires the correct mutation', async () => {
|
|
64
|
-
render(
|
|
65
|
-
<MockedProvider mocks={[deleteDirMock]} addTypename={false}>
|
|
66
|
-
<DeleteDir {...{ datasetId, path }} />
|
|
67
|
-
</MockedProvider>,
|
|
68
|
-
)
|
|
69
|
-
|
|
70
|
-
// click "Delete" button
|
|
71
|
-
await fireEvent.click(screen.getByRole('button'))
|
|
72
|
-
// confirm delete
|
|
73
|
-
await fireEvent.click(screen.getByLabelText('confirm'))
|
|
74
|
-
|
|
75
|
-
expect(deleteDirMock.newData).toHaveBeenCalled()
|
|
76
|
-
})
|
|
77
|
-
})
|
|
@@ -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,
|
|
@@ -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
|
}
|
|
@@ -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
|