@openneuro/server 4.14.3 → 4.15.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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openneuro/server",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.15.0",
|
|
4
4
|
"description": "Core service for the OpenNeuro platform.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "src/server.js",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"@apollo/client": "3.7.2",
|
|
19
19
|
"@elastic/elasticsearch": "7.15.0",
|
|
20
|
-
"@openneuro/search": "^4.
|
|
20
|
+
"@openneuro/search": "^4.15.0",
|
|
21
21
|
"@passport-next/passport-google-oauth2": "^1.0.0",
|
|
22
22
|
"@sentry/node": "^4.5.3",
|
|
23
23
|
"apollo-server": "2.25.4",
|
|
@@ -92,5 +92,5 @@
|
|
|
92
92
|
"publishConfig": {
|
|
93
93
|
"access": "public"
|
|
94
94
|
},
|
|
95
|
-
"gitHead": "
|
|
95
|
+
"gitHead": "8cc8a5270176df742abf30b121f7b39e1295ec44"
|
|
96
96
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import request from 'superagent'
|
|
2
1
|
import {
|
|
3
2
|
getDescriptionObject,
|
|
4
3
|
repairDescriptionTypes,
|
|
@@ -6,7 +5,6 @@ import {
|
|
|
6
5
|
} from '../description.js'
|
|
7
6
|
|
|
8
7
|
// Mock requests to Datalad service
|
|
9
|
-
vi.mock('superagent')
|
|
10
8
|
vi.mock('ioredis')
|
|
11
9
|
vi.mock('../../config.js')
|
|
12
10
|
|
|
@@ -89,25 +87,51 @@ describe('datalad dataset descriptions', () => {
|
|
|
89
87
|
expect(Array.isArray(repaired.Funding)).toBe(true)
|
|
90
88
|
})
|
|
91
89
|
})
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
body: { Name: 'Balloon Analog Risk-taking Task' },
|
|
96
|
-
type: 'application/json',
|
|
90
|
+
describe('getDescriptionObject()', () => {
|
|
91
|
+
beforeAll(() => {
|
|
92
|
+
global.fetch = vi.fn()
|
|
97
93
|
})
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
94
|
+
it('returns the parsed dataset_description.json object', async () => {
|
|
95
|
+
fetch.mockResolvedValue({
|
|
96
|
+
json: () =>
|
|
97
|
+
Promise.resolve({ Name: 'Balloon Analog Risk-taking Task' }),
|
|
98
|
+
headers: {
|
|
99
|
+
get: () => 'application/json',
|
|
100
|
+
},
|
|
101
|
+
status: 200,
|
|
102
|
+
})
|
|
103
|
+
const description = await getDescriptionObject('ds000001', '1.0.0')
|
|
104
|
+
expect(description).toEqual({ Name: 'Balloon Analog Risk-taking Task' })
|
|
105
|
+
})
|
|
106
|
+
it('handles a corrupted response', async () => {
|
|
107
|
+
global.fetch = vi.fn()
|
|
108
|
+
fetch.mockResolvedValue({
|
|
109
|
+
json: () => Promise.reject('JSON could not be parsed'),
|
|
110
|
+
headers: {
|
|
111
|
+
get: () => 'application/json',
|
|
112
|
+
},
|
|
113
|
+
status: 400,
|
|
114
|
+
})
|
|
115
|
+
await expect(getDescriptionObject('ds000001', '1.0.0')).rejects.toEqual(
|
|
116
|
+
Error(
|
|
117
|
+
'Backend request failed, dataset_description.json may not exist or may be non-JSON (type: application/json, status: 400)',
|
|
118
|
+
),
|
|
119
|
+
)
|
|
120
|
+
})
|
|
121
|
+
it('throws an error when nothing is returned', async () => {
|
|
122
|
+
global.fetch = vi.fn()
|
|
123
|
+
fetch.mockResolvedValue({
|
|
124
|
+
json: () => Promise.reject('JSON could not be parsed'),
|
|
125
|
+
headers: {
|
|
126
|
+
get: () => 'application/json',
|
|
127
|
+
},
|
|
128
|
+
status: 404,
|
|
129
|
+
})
|
|
130
|
+
await expect(getDescriptionObject('ds000001', '1.0.0')).rejects.toEqual(
|
|
131
|
+
Error(
|
|
132
|
+
'Backend request failed, dataset_description.json may not exist or may be non-JSON (type: application/json, status: 404)',
|
|
133
|
+
),
|
|
134
|
+
)
|
|
105
135
|
})
|
|
106
|
-
const description = await getDescriptionObject('ds000001', '1.0.0')
|
|
107
|
-
expect(description).toEqual({ Name: 'ds000001', BIDSVersion: '1.8.0' })
|
|
108
|
-
})
|
|
109
|
-
it('works without a dataset_description.json being present', async () => {
|
|
110
|
-
const description = await getDescriptionObject('ds000001', '1.0.0')
|
|
111
|
-
expect(description).toEqual({ Name: 'ds000001', BIDSVersion: '1.8.0' })
|
|
112
136
|
})
|
|
113
137
|
})
|
|
@@ -16,22 +16,18 @@ import { datasetOrSnapshot } from '../utils/datasetOrSnapshot'
|
|
|
16
16
|
* @param {string} datasetId
|
|
17
17
|
* @returns {Promise<Record<string, unknown>>} Promise resolving to dataset_description.json contents or defaults
|
|
18
18
|
*/
|
|
19
|
-
export const getDescriptionObject = (datasetId, revision) => {
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
export const getDescriptionObject = async (datasetId, revision) => {
|
|
20
|
+
const res = await fetch(
|
|
21
|
+
fileUrl(datasetId, '', 'dataset_description.json', revision),
|
|
22
|
+
)
|
|
23
|
+
const contentType = res.headers.get('content-type')
|
|
24
|
+
if (res.status === 200 && contentType.includes('application/json')) {
|
|
25
|
+
return await res.json()
|
|
26
|
+
} else {
|
|
27
|
+
throw new Error(
|
|
28
|
+
`Backend request failed, dataset_description.json may not exist or may be non-JSON (type: ${contentType}, status: ${res.status})`,
|
|
29
|
+
)
|
|
23
30
|
}
|
|
24
|
-
return request
|
|
25
|
-
.get(fileUrl(datasetId, '', 'dataset_description.json', revision))
|
|
26
|
-
.then(({ body, type }) => {
|
|
27
|
-
// Guard against non-JSON responses
|
|
28
|
-
if (type === 'application/json') return body
|
|
29
|
-
else throw new Error('dataset_description.json is not JSON')
|
|
30
|
-
})
|
|
31
|
-
.catch(() => {
|
|
32
|
-
// dataset_description does not exist or is not JSON, return default fields
|
|
33
|
-
return defaultDescription
|
|
34
|
-
})
|
|
35
31
|
}
|
|
36
32
|
|
|
37
33
|
export const descriptionCacheKey = (datasetId, revision) => {
|
|
@@ -111,21 +107,28 @@ export const appendSeniorAuthor = description => {
|
|
|
111
107
|
* Get a parsed dataset_description.json
|
|
112
108
|
* @param {object} obj dataset or snapshot object
|
|
113
109
|
*/
|
|
114
|
-
export const description = obj => {
|
|
110
|
+
export const description = async obj => {
|
|
115
111
|
// Obtain datasetId from Dataset or Snapshot objects
|
|
116
112
|
const { datasetId, revision } = datasetOrSnapshot(obj)
|
|
113
|
+
// Default fallback if dataset_description.json is not valid or missing
|
|
114
|
+
const defaultDescription = {
|
|
115
|
+
Name: datasetId,
|
|
116
|
+
BIDSVersion: '1.8.0',
|
|
117
|
+
}
|
|
117
118
|
const cache = new CacheItem(redis, CacheType.datasetDescription, [
|
|
118
119
|
datasetId,
|
|
119
120
|
revision.substring(0, 7),
|
|
120
121
|
])
|
|
121
|
-
|
|
122
|
-
.get(() => {
|
|
122
|
+
try {
|
|
123
|
+
const datasetDescription = await cache.get(() => {
|
|
123
124
|
return getDescriptionObject(datasetId, revision).then(
|
|
124
125
|
uncachedDescription => ({ id: revision, ...uncachedDescription }),
|
|
125
126
|
)
|
|
126
127
|
})
|
|
127
|
-
|
|
128
|
-
|
|
128
|
+
return appendSeniorAuthor(repairDescriptionTypes(datasetDescription))
|
|
129
|
+
} catch (err) {
|
|
130
|
+
return defaultDescription
|
|
131
|
+
}
|
|
129
132
|
}
|
|
130
133
|
|
|
131
134
|
export const setDescription = (datasetId, user, descriptionFieldUpdates) => {
|