@openneuro/app 4.2.5 → 4.3.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.
Files changed (22) hide show
  1. package/package.json +5 -4
  2. package/src/scripts/datalad/dataset/dataset-query-fragments.js +6 -0
  3. package/src/scripts/refactor_2021/dataset/dataset-query.jsx +2 -4
  4. package/src/scripts/refactor_2021/dataset/dataset-routes.jsx +6 -0
  5. package/src/scripts/refactor_2021/dataset/draft-container.tsx +6 -23
  6. package/src/scripts/refactor_2021/dataset/fragments/__tests__/dataset-alert-draft.spec.tsx +72 -0
  7. package/src/scripts/refactor_2021/dataset/fragments/dataset-alert-draft.tsx +79 -0
  8. package/src/scripts/refactor_2021/dataset/fragments/dataset-alert-version.tsx +24 -0
  9. package/src/scripts/refactor_2021/dataset/mutations/__tests__/__snapshots__/deprecate-snapshot.spec.tsx.snap +14 -0
  10. package/src/scripts/refactor_2021/dataset/mutations/__tests__/__snapshots__/deprecate-version.spec.tsx.snap +14 -0
  11. package/src/scripts/refactor_2021/dataset/mutations/__tests__/deprecate-snapshot.spec.tsx +71 -0
  12. package/src/scripts/refactor_2021/dataset/mutations/__tests__/deprecate-version.spec.tsx +71 -0
  13. package/src/scripts/refactor_2021/dataset/mutations/deprecate-version.tsx +46 -0
  14. package/src/scripts/refactor_2021/dataset/mutations/description.jsx +1 -1
  15. package/src/scripts/refactor_2021/dataset/mutations/readme.jsx +1 -1
  16. package/src/scripts/refactor_2021/dataset/mutations/submit-metadata.jsx +1 -1
  17. package/src/scripts/refactor_2021/dataset/mutations/undo-deprecate-version.tsx +43 -0
  18. package/src/scripts/refactor_2021/dataset/routes/add-metadata.jsx +0 -1
  19. package/src/scripts/refactor_2021/dataset/routes/deprecate-snapshot-page.tsx +46 -0
  20. package/src/scripts/refactor_2021/dataset/snapshot-container.tsx +19 -23
  21. package/src/scripts/refactor_2021/dataset/dataset-query-fragments.js +0 -220
  22. package/src/scripts/refactor_2021/dataset/queries/dataset-query-fragments.js +0 -220
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openneuro/app",
3
- "version": "4.2.5",
3
+ "version": "4.3.0-alpha.0",
4
4
  "description": "React JS web frontend for the OpenNeuro platform.",
5
5
  "license": "MIT",
6
6
  "main": "public/client.js",
@@ -19,8 +19,8 @@
19
19
  "@elastic/apm-rum": "5.9.1",
20
20
  "@emotion/react": "11.6.0",
21
21
  "@emotion/styled": "11.6.0",
22
- "@openneuro/client": "^4.2.5",
23
- "@openneuro/components": "^4.2.5",
22
+ "@openneuro/client": "^4.3.0-alpha.0",
23
+ "@openneuro/components": "^4.3.0-alpha.0",
24
24
  "babel-runtime": "^6.26.0",
25
25
  "bids-validator": "1.8.8",
26
26
  "bytes": "^3.0.0",
@@ -32,6 +32,7 @@
32
32
  "email-validator": "^2.0.4",
33
33
  "express": "^4.17.1",
34
34
  "graphql": "14.7.0",
35
+ "history": "^5.1.0",
35
36
  "jwt-decode": "^2.2.0",
36
37
  "markdown-to-jsx": "^7.1.2",
37
38
  "os-browserify": "^0.3.0",
@@ -115,5 +116,5 @@
115
116
  "publishConfig": {
116
117
  "access": "public"
117
118
  },
118
- "gitHead": "d797003217a41a7755c6f8b721b7139dc1e52242"
119
+ "gitHead": "966931efa8d06bbf0b9540038ab3decab086d745"
119
120
  }
@@ -136,6 +136,12 @@ export const SNAPSHOT_FIELDS = gql`
136
136
  tag
137
137
  created
138
138
  readme
139
+ deprecated {
140
+ id
141
+ user
142
+ reason
143
+ timestamp
144
+ }
139
145
  description {
140
146
  Name
141
147
  Authors
@@ -172,8 +172,7 @@ export const DatasetQueryHook = ({ datasetId, draft, history }) => {
172
172
  datasetId,
173
173
  fetchMore,
174
174
  error,
175
- }}
176
- >
175
+ }}>
177
176
  <DatasetRoutes dataset={data.dataset} />
178
177
  <FilesSubscription datasetId={datasetId} />
179
178
  </DatasetQueryContext.Provider>
@@ -208,8 +207,7 @@ const DatasetQuery = ({ match, history }) => {
208
207
  <>
209
208
  <DatasetRedirect />
210
209
  <ErrorBoundaryAssertionFailureException
211
- subject={'error in dataset query'}
212
- >
210
+ subject={'error in dataset query'}>
213
211
  <DatasetQueryHook
214
212
  datasetId={datasetId}
215
213
  draft={!snapshotId}
@@ -12,6 +12,7 @@ import Share from './routes/manage-permissions.jsx'
12
12
  import Snapshot from './routes/snapshot.jsx'
13
13
  import AddMetadata from './routes/add-metadata.jsx'
14
14
  import DeletePage from './routes/delete-page'
15
+ import { DeprecateSnapshotPage } from './routes/deprecate-snapshot-page'
15
16
  import { FileDisplay } from './files'
16
17
 
17
18
  //TODO imports
@@ -119,6 +120,11 @@ const DatasetRoutes = ({ dataset, error }) => {
119
120
  />
120
121
  )}
121
122
  />
123
+ <Route
124
+ exact
125
+ path="/datasets/:datasetId/versions/:snapshotTag/deprecate"
126
+ component={() => <DeprecateSnapshotPage />}
127
+ />
122
128
  <Route
123
129
  path="/datasets/:datasetId/versions/:snapshotTag/file-display/:filePath"
124
130
  render={({
@@ -11,9 +11,11 @@ import { config } from '../../config'
11
11
  import {
12
12
  getUnexpiredProfile,
13
13
  hasEditPermissions,
14
+ hasDatasetAdminPermissions,
14
15
  } from '../authentication/profile'
15
16
  import { useCookies } from 'react-cookie'
16
17
  import Comments from './comments/comments.jsx'
18
+ import { DatasetAlertDraft } from './fragments/dataset-alert-draft'
17
19
  import {
18
20
  MetaDataBlock,
19
21
  ModalitiesMetaDataBlock,
@@ -21,14 +23,12 @@ import {
21
23
  ValidationBlock,
22
24
  CloneDropdown,
23
25
  DatasetHeader,
24
- DatasetAlert,
25
26
  DatasetHeaderMeta,
26
27
  DatasetPage,
27
28
  DatasetGitAccess,
28
29
  VersionList,
29
30
  DatasetTools,
30
31
  } from '@openneuro/components/dataset'
31
- import { Modal } from '@openneuro/components/modal'
32
32
  import { ReadMore } from '@openneuro/components/read-more'
33
33
 
34
34
  import { FollowDataset } from './mutations/follow'
@@ -56,8 +56,6 @@ const DraftContainer: React.FC<DraftContainerProps> = ({ dataset }) => {
56
56
  const activeDataset = snapshotVersion(location) || 'draft'
57
57
 
58
58
  const [selectedVersion, setSelectedVersion] = React.useState(activeDataset)
59
- const [deprecatedmodalIsOpen, setDeprecatedModalIsOpen] =
60
- React.useState(false)
61
59
 
62
60
  const summary = dataset.draft.summary
63
61
  const description = dataset.draft.description
@@ -73,12 +71,7 @@ const DraftContainer: React.FC<DraftContainerProps> = ({ dataset }) => {
73
71
  parseISO(dataset.draft.modified),
74
72
  )
75
73
  const isSnapshot = activeDataset !== 'draft'
76
- const rootPath = isSnapshot
77
- ? `/datasets/${datasetId}/versions/${activeDataset}`
78
- : `/datasets/${datasetId}`
79
74
 
80
- //TODO deprecated needs to be added to the dataset snapshot obj and an admin needs to be able to say a version is deprecated somehow.
81
- const isPublic = dataset.public === true
82
75
  const [cookies] = useCookies()
83
76
  const profile = getUnexpiredProfile(cookies)
84
77
  const isAdmin = profile?.admin
@@ -88,6 +81,8 @@ const DraftContainer: React.FC<DraftContainerProps> = ({ dataset }) => {
88
81
  dataset.snapshots.length === 0 ||
89
82
  dataset.draft.head !==
90
83
  dataset.snapshots[dataset.snapshots.length - 1].hexsha
84
+ const isDatasetAdmin =
85
+ hasDatasetAdminPermissions(dataset.permissions, profile?.sub) || isAdmin
91
86
 
92
87
  return (
93
88
  <>
@@ -122,7 +117,7 @@ const DraftContainer: React.FC<DraftContainerProps> = ({ dataset }) => {
122
117
  renderAlert={() => (
123
118
  <>
124
119
  {hasEdit && (
125
- <DatasetAlert
120
+ <DatasetAlertDraft
126
121
  isPrivate={!dataset.public}
127
122
  datasetId={dataset.id}
128
123
  hasDraftChanges={hasDraftChanges}
@@ -191,6 +186,7 @@ const DraftContainer: React.FC<DraftContainerProps> = ({ dataset }) => {
191
186
  datasetId={datasetId}
192
187
  isAdmin={isAdmin}
193
188
  hasSnapshot={dataset.snapshots.length !== 0}
189
+ isDatasetAdmin={isDatasetAdmin}
194
190
  />
195
191
  )}
196
192
  renderFiles={() => (
@@ -263,7 +259,6 @@ const DraftContainer: React.FC<DraftContainerProps> = ({ dataset }) => {
263
259
  dateModified={dateModified}
264
260
  selected={selectedVersion}
265
261
  setSelected={setSelectedVersion}
266
- setDeprecatedModalIsOpen={setDeprecatedModalIsOpen}
267
262
  />
268
263
  </div>
269
264
  }
@@ -427,18 +422,6 @@ const DraftContainer: React.FC<DraftContainerProps> = ({ dataset }) => {
427
422
  </EditDescriptionList>
428
423
  </>
429
424
  )}
430
- renderDeprecatedModal={() => (
431
- <Modal
432
- isOpen={deprecatedmodalIsOpen}
433
- toggle={() => setDeprecatedModalIsOpen(prevIsOpen => !prevIsOpen)}
434
- closeText={'close'}
435
- className="deprecated-modal">
436
- <p>
437
- You have selected a deprecated version. The author of the dataset
438
- does not recommend this specific version.
439
- </p>
440
- </Modal>
441
- )}
442
425
  renderComments={() => (
443
426
  <Comments
444
427
  datasetId={dataset.id}
@@ -0,0 +1,72 @@
1
+ import React from 'react'
2
+ import { render, screen } from '@testing-library/react'
3
+ import '@testing-library/jest-dom/extend-expect'
4
+ import { MemoryRouter } from 'react-router-dom'
5
+ import { DatasetAlertDraft } from '../dataset-alert-draft'
6
+
7
+ describe('DatasetAlertDraft component', () => {
8
+ it('renders the correct text for private drafts with changes', () => {
9
+ render(
10
+ <DatasetAlertDraft
11
+ isPrivate
12
+ hasSnapshot
13
+ hasDraftChanges
14
+ datasetId="test00001"
15
+ />,
16
+ { wrapper: MemoryRouter },
17
+ )
18
+
19
+ expect(
20
+ screen.queryByText(/there have been changes to the draft/i),
21
+ ).toBeInTheDocument()
22
+ })
23
+ it('renders the correct text for private drafts with no snapshots', () => {
24
+ render(
25
+ <DatasetAlertDraft
26
+ isPrivate
27
+ hasDraftChanges
28
+ hasSnapshot={false}
29
+ datasetId="test00001"
30
+ />,
31
+ { wrapper: MemoryRouter },
32
+ )
33
+
34
+ expect(
35
+ screen.queryByText(/before it can be published/i),
36
+ ).toBeInTheDocument()
37
+ })
38
+ it('renders the correct text for public drafts with changes', () => {
39
+ render(
40
+ <DatasetAlertDraft
41
+ hasDraftChanges
42
+ isPrivate={false}
43
+ hasSnapshot={false}
44
+ datasetId="test00001"
45
+ />,
46
+ {
47
+ wrapper: MemoryRouter,
48
+ },
49
+ )
50
+
51
+ expect(
52
+ screen.queryByText(/there are currently unsaved changes to this draft/i),
53
+ ).toBeInTheDocument()
54
+ })
55
+ it('renders the correct text for public drafts without changes', () => {
56
+ render(
57
+ <DatasetAlertDraft
58
+ hasDraftChanges={false}
59
+ isPrivate={false}
60
+ hasSnapshot={false}
61
+ datasetId="test00001"
62
+ />,
63
+ {
64
+ wrapper: MemoryRouter,
65
+ },
66
+ )
67
+
68
+ expect(
69
+ screen.queryByText(/you can make changes to this draft page/i),
70
+ ).toBeInTheDocument()
71
+ })
72
+ })
@@ -0,0 +1,79 @@
1
+ import React from 'react'
2
+ import { Link } from 'react-router-dom'
3
+ import { DatasetAlert } from '@openneuro/components/dataset'
4
+
5
+ export interface DatasetAlertDraftProps {
6
+ isPrivate: boolean
7
+ datasetId: string
8
+ hasDraftChanges: boolean
9
+ hasSnapshot: boolean
10
+ }
11
+
12
+ export const DatasetAlertDraft: React.FC<DatasetAlertDraftProps> = ({
13
+ isPrivate,
14
+ datasetId,
15
+ hasDraftChanges,
16
+ hasSnapshot,
17
+ }) => {
18
+ if (isPrivate) {
19
+ if (hasSnapshot) {
20
+ return (
21
+ <DatasetAlert
22
+ alert="This dataset has not been published!"
23
+ footer={
24
+ hasDraftChanges &&
25
+ '* There have been changes to the draft since your last version'
26
+ }
27
+ level="warning">
28
+ <>
29
+ <Link
30
+ className="dataset-tool"
31
+ to={'/datasets/' + datasetId + '/publish'}>
32
+ Publish this dataset
33
+ </Link>
34
+ &#32; to make all versions available publicly.
35
+ </>
36
+ </DatasetAlert>
37
+ )
38
+ } else {
39
+ return (
40
+ <DatasetAlert
41
+ alert="This dataset has not been published!"
42
+ level="warning">
43
+ Before it can be published, please&#32;
44
+ <Link
45
+ className="dataset-tool"
46
+ to={'/datasets/' + datasetId + '/snapshot'}>
47
+ create a version
48
+ </Link>
49
+ </DatasetAlert>
50
+ )
51
+ }
52
+ } else {
53
+ if (hasDraftChanges) {
54
+ return (
55
+ <DatasetAlert alert="This dataset has been published!" level="warning">
56
+ There are currently unsaved changes to this Draft. Changes made here
57
+ become public when you&#32;
58
+ <Link
59
+ className="dataset-tool"
60
+ to={'/datasets/' + datasetId + '/snapshot'}>
61
+ create a new version.
62
+ </Link>
63
+ </DatasetAlert>
64
+ )
65
+ } else {
66
+ return (
67
+ <DatasetAlert alert="This dataset has been published!">
68
+ You can make changes to this Draft page, then&#32;
69
+ <Link
70
+ className="dataset-tool"
71
+ to={'/datasets/' + datasetId + '/snapshot'}>
72
+ create a new version
73
+ </Link>
74
+ &#32;to make them public.
75
+ </DatasetAlert>
76
+ )
77
+ }
78
+ }
79
+ }
@@ -0,0 +1,24 @@
1
+ import React from 'react'
2
+ import { DatasetAlert } from '@openneuro/components/dataset'
3
+ import { UndoDeprecateVersion } from '../mutations/undo-deprecate-version'
4
+
5
+ export interface DatasetAlertVersionProps {
6
+ datasetId: string
7
+ tag: string
8
+ reason: string
9
+ hasEdit: boolean
10
+ }
11
+
12
+ export const DatasetAlertVersion: React.FC<DatasetAlertVersionProps> = ({
13
+ datasetId,
14
+ tag,
15
+ reason,
16
+ hasEdit,
17
+ }) => (
18
+ <DatasetAlert
19
+ alert="This version has been deprecated!"
20
+ level="error"
21
+ footer={reason}>
22
+ {hasEdit && <UndoDeprecateVersion datasetId={datasetId} tag={tag} />}
23
+ </DatasetAlert>
24
+ )
@@ -0,0 +1,14 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`DeprecateVersion mutation renders with typical props 1`] = `
4
+ <DocumentFragment>
5
+ <button
6
+ aria-label="Deprecate Version"
7
+ class="on-button on-button--small on-button--primary btn-modal-action"
8
+ role="button"
9
+ type="button"
10
+ >
11
+ Deprecate Version
12
+ </button>
13
+ </DocumentFragment>
14
+ `;
@@ -0,0 +1,14 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`DeprecateVersion mutation renders with typical props 1`] = `
4
+ <DocumentFragment>
5
+ <button
6
+ aria-label="Deprecate Version"
7
+ class="on-button on-button--small on-button--primary btn-modal-action"
8
+ role="button"
9
+ type="button"
10
+ >
11
+ Deprecate Version
12
+ </button>
13
+ </DocumentFragment>
14
+ `;
@@ -0,0 +1,71 @@
1
+ import React from 'react'
2
+ import { render, fireEvent, screen, waitFor } from '@testing-library/react'
3
+ import { MockedProvider } from '@apollo/client/testing'
4
+ import { Router } from 'react-router-dom'
5
+ import { createMemoryHistory } from 'history'
6
+ import { DeprecateVersion, DEPRECATE_VERSION } from '../deprecate-version'
7
+
8
+ describe('DeprecateVersion mutation', () => {
9
+ it('renders with typical props', () => {
10
+ const { asFragment } = render(
11
+ <MockedProvider>
12
+ <DeprecateVersion
13
+ datasetId="test00001"
14
+ tag="1.0.0"
15
+ reason="This is a test suite."
16
+ />
17
+ </MockedProvider>,
18
+ )
19
+ expect(asFragment()).toMatchSnapshot()
20
+ })
21
+ it('calls the DEPRECATE_VERSION mutation when clicked and navigates to the snapshot on success', async () => {
22
+ const datasetId = 'test00001'
23
+ const tag = '1.0.0'
24
+ const reason = 'This is a test suite.'
25
+ const history = createMemoryHistory({
26
+ initialEntries: [`/datasets/${datasetId}/versions/${tag}/deprecate`],
27
+ })
28
+ const historyPushSpy = jest.spyOn(history, 'push')
29
+ const snapshotId = `${datasetId}:${tag}`
30
+ const deprecateSnapshotMock = {
31
+ request: {
32
+ query: DEPRECATE_VERSION,
33
+ variables: {
34
+ datasetId,
35
+ tag,
36
+ reason,
37
+ },
38
+ },
39
+ result: {
40
+ data: {
41
+ deprecateSnapshot: {
42
+ __typename: 'Snapshot',
43
+ id: snapshotId,
44
+ deprecated: {
45
+ __typename: 'DeprecatedSnapshot',
46
+ id: snapshotId,
47
+ user: '1245',
48
+ reason,
49
+ },
50
+ },
51
+ },
52
+ },
53
+ }
54
+
55
+ render(
56
+ <Router history={history}>
57
+ <MockedProvider mocks={[deprecateSnapshotMock]} addTypename={false}>
58
+ <DeprecateVersion datasetId={datasetId} tag={tag} reason={reason} />
59
+ </MockedProvider>
60
+ </Router>,
61
+ )
62
+
63
+ fireEvent.click(screen.getByLabelText(/Deprecate Version/i))
64
+
65
+ await waitFor(() => expect(historyPushSpy).toHaveBeenCalledTimes(1))
66
+
67
+ expect(history.location.pathname).toBe(
68
+ `/datasets/${datasetId}/versions/${tag}`,
69
+ )
70
+ })
71
+ })
@@ -0,0 +1,71 @@
1
+ import React from 'react'
2
+ import { render, fireEvent, screen, waitFor } from '@testing-library/react'
3
+ import { MockedProvider } from '@apollo/client/testing'
4
+ import { Router } from 'react-router-dom'
5
+ import { createMemoryHistory } from 'history'
6
+ import { DeprecateVersion, DEPRECATE_VERSION } from '../deprecate-version'
7
+
8
+ describe('DeprecateVersion mutation', () => {
9
+ it('renders with typical props', () => {
10
+ const { asFragment } = render(
11
+ <MockedProvider>
12
+ <DeprecateVersion
13
+ datasetId="test00001"
14
+ tag="1.0.0"
15
+ reason="This is a test suite."
16
+ />
17
+ </MockedProvider>,
18
+ )
19
+ expect(asFragment()).toMatchSnapshot()
20
+ })
21
+ it('calls the DEPRECATE_VERSION mutation when clicked and navigates to the snapshot on success', async () => {
22
+ const datasetId = 'test00001'
23
+ const tag = '1.0.0'
24
+ const reason = 'This is a test suite.'
25
+ const history = createMemoryHistory({
26
+ initialEntries: [`/datasets/${datasetId}/versions/${tag}/deprecate`],
27
+ })
28
+ const historyPushSpy = jest.spyOn(history, 'push')
29
+ const snapshotId = `${datasetId}:${tag}`
30
+ const deprecateSnapshotMock = {
31
+ request: {
32
+ query: DEPRECATE_VERSION,
33
+ variables: {
34
+ datasetId,
35
+ tag,
36
+ reason,
37
+ },
38
+ },
39
+ result: {
40
+ data: {
41
+ deprecateSnapshot: {
42
+ __typename: 'Snapshot',
43
+ id: snapshotId,
44
+ deprecated: {
45
+ __typename: 'DeprecatedSnapshot',
46
+ id: snapshotId,
47
+ user: '1245',
48
+ reason,
49
+ },
50
+ },
51
+ },
52
+ },
53
+ }
54
+
55
+ render(
56
+ <Router history={history}>
57
+ <MockedProvider mocks={[deprecateSnapshotMock]} addTypename={false}>
58
+ <DeprecateVersion datasetId={datasetId} tag={tag} reason={reason} />
59
+ </MockedProvider>
60
+ </Router>,
61
+ )
62
+
63
+ fireEvent.click(screen.getByLabelText(/Deprecate Version/i))
64
+
65
+ await waitFor(() => expect(historyPushSpy).toHaveBeenCalledTimes(1))
66
+
67
+ expect(history.location.pathname).toBe(
68
+ `/datasets/${datasetId}/versions/${tag}`,
69
+ )
70
+ })
71
+ })
@@ -0,0 +1,46 @@
1
+ import React, { FC } from 'react'
2
+ import { gql, useMutation } from '@apollo/client'
3
+ import { Button } from '@openneuro/components/button'
4
+ import { useHistory } from 'react-router-dom'
5
+
6
+ export const DEPRECATE_VERSION = gql`
7
+ mutation deprecateSnapshot($datasetId: ID!, $tag: String!, $reason: String!) {
8
+ deprecateSnapshot(datasetId: $datasetId, tag: $tag, reason: $reason) {
9
+ id
10
+ deprecated {
11
+ reason
12
+ }
13
+ }
14
+ }
15
+ `
16
+
17
+ interface DeprecateVersionProps {
18
+ datasetId: string
19
+ tag: string
20
+ reason: string
21
+ }
22
+
23
+ export const DeprecateVersion: FC<DeprecateVersionProps> = ({
24
+ datasetId,
25
+ tag,
26
+ reason,
27
+ }) => {
28
+ const history = useHistory()
29
+ const [DeprecateVersionMutation] = useMutation(DEPRECATE_VERSION)
30
+
31
+ return (
32
+ <Button
33
+ className="btn-modal-action"
34
+ primary={true}
35
+ label="Deprecate Version"
36
+ size="small"
37
+ onClick={() =>
38
+ DeprecateVersionMutation({
39
+ variables: { datasetId, tag, reason },
40
+ }).then(() => {
41
+ history.push(`/datasets/${datasetId}/versions/${tag}`)
42
+ })
43
+ }
44
+ />
45
+ )
46
+ }
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types'
3
3
  import { gql } from '@apollo/client'
4
4
  import { Mutation } from '@apollo/client/react/components'
5
5
  import { SaveButton } from '../fragments/save-button'
6
- import { DRAFT_FRAGMENT } from '../queries/dataset-query-fragments.js'
6
+ import { DRAFT_FRAGMENT } from '../../../datalad/dataset/dataset-query-fragments'
7
7
  import { datasetCacheId } from './cache-id.js'
8
8
 
9
9
  export const UPDATE_DESCRIPTION = gql`
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types'
3
3
  import { gql } from '@apollo/client'
4
4
  import { Mutation } from '@apollo/client/react/components'
5
5
  import { SaveButton } from '../fragments/save-button'
6
- import { DRAFT_FRAGMENT } from '../queries/dataset-query-fragments.js'
6
+ import { DRAFT_FRAGMENT } from '../../../datalad/dataset/dataset-query-fragments'
7
7
  import { datasetCacheId } from './cache-id.js'
8
8
 
9
9
  const UPDATE_README = gql`
@@ -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_METADATA } from '../dataset-query-fragments.js'
5
+ import { DATASET_METADATA } from '../../../datalad/dataset/dataset-query-fragments'
6
6
  import { datasetCacheId } from './cache-id.js'
7
7
  import { Button } from '@openneuro/components/button'
8
8
 
@@ -0,0 +1,43 @@
1
+ import React, { FC } from 'react'
2
+ import { gql, useMutation } from '@apollo/client'
3
+ import { Button } from '@openneuro/components/button'
4
+ import { Tooltip } from '@openneuro/components/tooltip'
5
+
6
+ const UNDO_DEPRECATE_VERSION = gql`
7
+ mutation undoDeprecateSnapshot($datasetId: ID!, $tag: String!) {
8
+ undoDeprecateSnapshot(datasetId: $datasetId, tag: $tag) {
9
+ id
10
+ deprecated {
11
+ reason
12
+ }
13
+ }
14
+ }
15
+ `
16
+
17
+ interface UndoDeprecateVersionProps {
18
+ datasetId: string
19
+ tag: string
20
+ }
21
+
22
+ export const UndoDeprecateVersion: FC<UndoDeprecateVersionProps> = ({
23
+ datasetId,
24
+ tag,
25
+ }) => {
26
+ const [UndoDeprecateVersion] = useMutation(UNDO_DEPRECATE_VERSION)
27
+
28
+ return (
29
+ <Tooltip tooltip="Undo Deprecation" flow="right">
30
+ <Button
31
+ icon="fa fa-undo"
32
+ iconOnly
33
+ label="Undo"
34
+ size="xsmall"
35
+ onClick={() =>
36
+ UndoDeprecateVersion({
37
+ variables: { datasetId, tag },
38
+ })
39
+ }
40
+ />
41
+ </Tooltip>
42
+ )
43
+ }
@@ -112,7 +112,6 @@ const AddMetadata = ({ dataset }) => {
112
112
  const errors = runValidations(newValues)
113
113
  if (hasChanged(errors, validationErrors)) setValidationErrors(errors)
114
114
  }
115
- // @ts-expect-error Weak type definition for state
116
115
  const submitPath = location.state && location.state.submitPath
117
116
  const user = getProfile(cookies)
118
117
  const hasEdit =
@@ -0,0 +1,46 @@
1
+ import React, { useState } from 'react'
2
+ import { Link, useRouteMatch } from 'react-router-dom'
3
+ import { DeprecateVersion } from '../mutations/deprecate-version'
4
+ import { Input } from '@openneuro/components/input'
5
+ import LoggedIn from '../../authentication/logged-in.jsx'
6
+
7
+ interface DeprecateSnapshotRouteParams {
8
+ datasetId: string
9
+ snapshotTag: string
10
+ }
11
+
12
+ export const DeprecateSnapshotPage = (): React.ReactElement => {
13
+ const {
14
+ params: { datasetId, snapshotTag },
15
+ } = useRouteMatch<DeprecateSnapshotRouteParams>()
16
+ const [reason, setReason] = useState('')
17
+
18
+ return (
19
+ <div className="container">
20
+ <h2>Deprecate Version</h2>
21
+ <p>
22
+ {`Deprecate ${datasetId} version ${snapshotTag} to let other users know about an issue with this version. The reason provided will be displayed for users visiting the version.`}
23
+ </p>
24
+ <Input
25
+ placeholder="Explanation for deprecation"
26
+ type="text"
27
+ name="front-page-search"
28
+ labelStyle="default"
29
+ setValue={setReason}
30
+ />
31
+ <hr />
32
+ <div className="dataset-form-controls">
33
+ <LoggedIn>
34
+ <DeprecateVersion
35
+ datasetId={datasetId}
36
+ tag={snapshotTag}
37
+ reason={reason}
38
+ />
39
+ </LoggedIn>
40
+ <Link className="return-link" to={`/datasets/${datasetId}`}>
41
+ Return to Dataset
42
+ </Link>
43
+ </div>
44
+ </div>
45
+ )
46
+ }
@@ -12,6 +12,7 @@ import Validation from '../validation/validation.jsx'
12
12
  import { config } from '../../config'
13
13
  import Comments from './comments/comments.jsx'
14
14
  import DatasetCitation from './fragments/dataset-citation.jsx'
15
+ import { DatasetAlertVersion } from './fragments/dataset-alert-version'
15
16
 
16
17
  import {
17
18
  ModalitiesMetaDataBlock,
@@ -31,16 +32,16 @@ import { Loading } from '@openneuro/components/loading'
31
32
  import {
32
33
  getUnexpiredProfile,
33
34
  hasEditPermissions,
35
+ hasDatasetAdminPermissions,
34
36
  } from '../authentication/profile'
35
37
  import { useCookies } from 'react-cookie'
36
- import { Modal } from '@openneuro/components/modal'
37
38
 
38
39
  import { ReadMore } from '@openneuro/components/read-more'
39
40
 
40
41
  import { FollowDataset } from './mutations/follow'
41
42
  import { StarDataset } from './mutations/star'
42
43
 
43
- import { SNAPSHOT_FIELDS } from './queries/dataset-query-fragments.js'
44
+ import { SNAPSHOT_FIELDS } from '../../datalad/dataset/dataset-query-fragments.js'
44
45
  import { DOILink } from './fragments/doi-link'
45
46
 
46
47
  const formatDate = dateObject =>
@@ -67,8 +68,6 @@ const SnapshotContainer: React.FC<SnapshotContainerProps> = ({
67
68
  const activeDataset = snapshotVersion(location) || 'draft'
68
69
 
69
70
  const [selectedVersion, setSelectedVersion] = React.useState(activeDataset)
70
- const [deprecatedmodalIsOpen, setDeprecatedModalIsOpen] =
71
- React.useState(false)
72
71
 
73
72
  const summary = snapshot.summary
74
73
  const description = snapshot.description
@@ -81,18 +80,15 @@ const SnapshotContainer: React.FC<SnapshotContainerProps> = ({
81
80
  const dateAddedDifference = formatDistanceToNow(parseISO(dataset.created))
82
81
  const dateModified = formatDate(snapshot.created)
83
82
  const dateUpdatedDifference = formatDistanceToNow(parseISO(snapshot.created))
84
- const rootPath = `/datasets/${datasetId}/versions/${activeDataset}`
85
83
 
86
- //TODO deprecated needs to be added to the dataset snapshot obj and an admin needs to be able to say a version is deprecated somehow.
87
84
  const [cookies] = useCookies()
88
85
  const profile = getUnexpiredProfile(cookies)
89
86
  const isAdmin = profile?.admin
90
87
  const hasEdit =
91
88
  hasEditPermissions(dataset.permissions, profile?.sub) || isAdmin
92
- const hasDraftChanges =
93
- dataset.snapshots.length === 0 ||
94
- dataset.draft.head !==
95
- dataset.snapshots[dataset.snapshots.length - 1].hexsha
89
+ const isDatasetAdmin =
90
+ hasDatasetAdminPermissions(dataset.permissions, profile?.sub) || isAdmin
91
+
96
92
  return (
97
93
  <>
98
94
  <DatasetPage
@@ -107,6 +103,18 @@ const SnapshotContainer: React.FC<SnapshotContainerProps> = ({
107
103
  )}
108
104
  </>
109
105
  )}
106
+ renderAlert={() => (
107
+ <>
108
+ {snapshot?.deprecated && (
109
+ <DatasetAlertVersion
110
+ datasetId={dataset.id}
111
+ tag={snapshot.tag}
112
+ reason={snapshot.deprecated.reason}
113
+ hasEdit={hasEdit}
114
+ />
115
+ )}
116
+ </>
117
+ )}
110
118
  renderHeaderMeta={() => (
111
119
  <>
112
120
  {summary && (
@@ -166,6 +174,7 @@ const SnapshotContainer: React.FC<SnapshotContainerProps> = ({
166
174
  datasetId={datasetId}
167
175
  isSnapshot={true}
168
176
  isAdmin={isAdmin}
177
+ isDatasetAdmin={isDatasetAdmin}
169
178
  />
170
179
  )}
171
180
  renderFiles={() => (
@@ -233,7 +242,6 @@ const SnapshotContainer: React.FC<SnapshotContainerProps> = ({
233
242
  dateModified={dateModified}
234
243
  selected={selectedVersion}
235
244
  setSelected={setSelectedVersion}
236
- setDeprecatedModalIsOpen={setDeprecatedModalIsOpen}
237
245
  />
238
246
  </div>
239
247
  }
@@ -371,18 +379,6 @@ const SnapshotContainer: React.FC<SnapshotContainerProps> = ({
371
379
  />
372
380
  </>
373
381
  )}
374
- renderDeprecatedModal={() => (
375
- <Modal
376
- isOpen={deprecatedmodalIsOpen}
377
- toggle={() => setDeprecatedModalIsOpen(prevIsOpen => !prevIsOpen)}
378
- closeText={'close'}
379
- className="deprecated-modal">
380
- <p>
381
- You have selected a deprecated version. The author of the dataset
382
- does not recommend this specific version.
383
- </p>
384
- </Modal>
385
- )}
386
382
  renderComments={() => (
387
383
  <Comments
388
384
  datasetId={dataset.id}
@@ -1,220 +0,0 @@
1
- import { gql } from '@apollo/client'
2
-
3
- export const DRAFT_FRAGMENT = gql`
4
- fragment DatasetDraft on Dataset {
5
- id
6
- draft {
7
- id
8
- modified
9
- readme
10
- head
11
- description {
12
- Name
13
- Authors
14
- DatasetDOI
15
- License
16
- Acknowledgements
17
- HowToAcknowledge
18
- Funding
19
- ReferencesAndLinks
20
- EthicsApprovals
21
- }
22
- summary {
23
- modalities
24
- secondaryModalities
25
- sessions
26
- subjects
27
- subjectMetadata {
28
- participantId
29
- age
30
- sex
31
- group
32
- }
33
- tasks
34
- size
35
- totalFiles
36
- dataProcessed
37
- pet {
38
- BodyPart
39
- ScannerManufacturer
40
- ScannerManufacturersModelName
41
- TracerName
42
- TracerRadionuclide
43
- }
44
- }
45
- }
46
- }
47
- `
48
-
49
- export const DRAFT_FILES_FRAGMENT = gql`
50
- fragment DatasetDraftFiles on Dataset {
51
- id
52
- draft {
53
- id
54
- files {
55
- id
56
- key
57
- filename
58
- size
59
- directory
60
- annexed
61
- }
62
- }
63
- }
64
- `
65
-
66
- export const PERMISSION_FRAGMENT = gql`
67
- fragment DatasetPermissions on Dataset {
68
- id
69
- permissions {
70
- id
71
- userPermissions {
72
- user {
73
- id
74
- email
75
- }
76
- level
77
- }
78
- }
79
- }
80
- `
81
-
82
- export const DATASET_SNAPSHOTS = gql`
83
- fragment DatasetSnapshots on Dataset {
84
- id
85
- snapshots {
86
- id
87
- tag
88
- created
89
- hexsha
90
- }
91
- }
92
- `
93
-
94
- export const ISSUE_FIELDS = `
95
- severity
96
- code
97
- reason
98
- files {
99
- evidence
100
- line
101
- character
102
- reason
103
- file {
104
- name
105
- path
106
- relativePath
107
- }
108
- }
109
- additionalFileCount
110
- `
111
-
112
- export const DATASET_ISSUES = gql`
113
- fragment DatasetIssues on Dataset {
114
- id
115
- draft {
116
- id
117
- issues {
118
- ${ISSUE_FIELDS}
119
- }
120
- }
121
- }
122
- `
123
-
124
- export const SNAPSHOT_ISSUES = gql`
125
- fragment SnapshotIssues on Snapshot {
126
- id
127
- issues {
128
- ${ISSUE_FIELDS}
129
- }
130
- }
131
- `
132
-
133
- export const SNAPSHOT_FIELDS = gql`
134
- fragment SnapshotFields on Snapshot {
135
- id
136
- tag
137
- created
138
- readme
139
- description {
140
- Name
141
- Authors
142
- DatasetDOI
143
- License
144
- Acknowledgements
145
- HowToAcknowledge
146
- Funding
147
- ReferencesAndLinks
148
- EthicsApprovals
149
- }
150
- files {
151
- id
152
- key
153
- filename
154
- size
155
- directory
156
- annexed
157
- }
158
- summary {
159
- modalities
160
- secondaryModalities
161
- sessions
162
- subjects
163
- subjectMetadata {
164
- participantId
165
- age
166
- sex
167
- group
168
- }
169
- tasks
170
- size
171
- totalFiles
172
- dataProcessed
173
- pet {
174
- BodyPart
175
- ScannerManufacturer
176
- ScannerManufacturersModelName
177
- TracerName
178
- TracerRadionuclide
179
- }
180
- }
181
- analytics {
182
- downloads
183
- views
184
- }
185
- ...SnapshotIssues
186
- hexsha
187
- }
188
- ${SNAPSHOT_ISSUES}
189
- `
190
-
191
- export const DATASET_METADATA = gql`
192
- fragment DatasetMetadata on Dataset {
193
- id
194
- metadata {
195
- datasetId
196
- datasetUrl
197
- datasetName
198
- firstSnapshotCreatedAt
199
- latestSnapshotCreatedAt
200
- dxStatus
201
- tasksCompleted
202
- trialCount
203
- grantFunderName
204
- grantIdentifier
205
- studyDesign
206
- studyDomain
207
- studyLongitudinal
208
- dataProcessed
209
- species
210
- associatedPaperDOI
211
- openneuroPaperDOI
212
- seniorAuthor
213
- adminUsers
214
- ages
215
- modalities
216
- affirmedDefaced
217
- affirmedConsent
218
- }
219
- }
220
- `
@@ -1,220 +0,0 @@
1
- import { gql } from '@apollo/client'
2
-
3
- export const DRAFT_FRAGMENT = gql`
4
- fragment DatasetDraft on Dataset {
5
- id
6
- draft {
7
- id
8
- modified
9
- readme
10
- head
11
- description {
12
- Name
13
- Authors
14
- DatasetDOI
15
- License
16
- Acknowledgements
17
- HowToAcknowledge
18
- Funding
19
- ReferencesAndLinks
20
- EthicsApprovals
21
- }
22
- summary {
23
- modalities
24
- secondaryModalities
25
- sessions
26
- subjects
27
- subjectMetadata {
28
- participantId
29
- age
30
- sex
31
- group
32
- }
33
- tasks
34
- size
35
- totalFiles
36
- dataProcessed
37
- pet {
38
- BodyPart
39
- ScannerManufacturer
40
- ScannerManufacturersModelName
41
- TracerName
42
- TracerRadionuclide
43
- }
44
- }
45
- }
46
- }
47
- `
48
-
49
- export const DRAFT_FILES_FRAGMENT = gql`
50
- fragment DatasetDraftFiles on Dataset {
51
- id
52
- draft {
53
- id
54
- files {
55
- id
56
- key
57
- filename
58
- size
59
- directory
60
- annexed
61
- }
62
- }
63
- }
64
- `
65
-
66
- export const PERMISSION_FRAGMENT = gql`
67
- fragment DatasetPermissions on Dataset {
68
- id
69
- permissions {
70
- id
71
- userPermissions {
72
- user {
73
- id
74
- email
75
- }
76
- level
77
- }
78
- }
79
- }
80
- `
81
-
82
- export const DATASET_SNAPSHOTS = gql`
83
- fragment DatasetSnapshots on Dataset {
84
- id
85
- snapshots {
86
- id
87
- tag
88
- created
89
- hexsha
90
- }
91
- }
92
- `
93
-
94
- export const ISSUE_FIELDS = `
95
- severity
96
- code
97
- reason
98
- files {
99
- evidence
100
- line
101
- character
102
- reason
103
- file {
104
- name
105
- path
106
- relativePath
107
- }
108
- }
109
- additionalFileCount
110
- `
111
-
112
- export const DATASET_ISSUES = gql`
113
- fragment DatasetIssues on Dataset {
114
- id
115
- draft {
116
- id
117
- issues {
118
- ${ISSUE_FIELDS}
119
- }
120
- }
121
- }
122
- `
123
-
124
- export const SNAPSHOT_ISSUES = gql`
125
- fragment SnapshotIssues on Snapshot {
126
- id
127
- issues {
128
- ${ISSUE_FIELDS}
129
- }
130
- }
131
- `
132
-
133
- export const SNAPSHOT_FIELDS = gql`
134
- fragment SnapshotFields on Snapshot {
135
- id
136
- tag
137
- created
138
- readme
139
- description {
140
- Name
141
- Authors
142
- DatasetDOI
143
- License
144
- Acknowledgements
145
- HowToAcknowledge
146
- Funding
147
- ReferencesAndLinks
148
- EthicsApprovals
149
- }
150
- files {
151
- id
152
- key
153
- filename
154
- size
155
- directory
156
- annexed
157
- }
158
- summary {
159
- modalities
160
- secondaryModalities
161
- sessions
162
- subjects
163
- subjectMetadata {
164
- participantId
165
- age
166
- sex
167
- group
168
- }
169
- tasks
170
- size
171
- totalFiles
172
- dataProcessed
173
- pet {
174
- BodyPart
175
- ScannerManufacturer
176
- ScannerManufacturersModelName
177
- TracerName
178
- TracerRadionuclide
179
- }
180
- }
181
- analytics {
182
- downloads
183
- views
184
- }
185
- ...SnapshotIssues
186
- hexsha
187
- }
188
- ${SNAPSHOT_ISSUES}
189
- `
190
-
191
- export const DATASET_METADATA = gql`
192
- fragment DatasetMetadata on Dataset {
193
- id
194
- metadata {
195
- datasetId
196
- datasetUrl
197
- datasetName
198
- firstSnapshotCreatedAt
199
- latestSnapshotCreatedAt
200
- dxStatus
201
- tasksCompleted
202
- trialCount
203
- grantFunderName
204
- grantIdentifier
205
- studyDesign
206
- studyDomain
207
- studyLongitudinal
208
- dataProcessed
209
- species
210
- associatedPaperDOI
211
- openneuroPaperDOI
212
- seniorAuthor
213
- adminUsers
214
- ages
215
- modalities
216
- affirmedDefaced
217
- affirmedConsent
218
- }
219
- }
220
- `