diff --git a/api/config.py b/api/config.py index 135f28081..f00152bce 100644 --- a/api/config.py +++ b/api/config.py @@ -152,6 +152,7 @@ def apply_env_variables(config): 'acquisition.json', 'acquisition-update.json', 'analysis.json', + 'analysis-legacy.json', 'analysis-update.json', 'avatars.json', 'collection.json', diff --git a/api/dao/containerstorage.py b/api/dao/containerstorage.py index 5817900ff..b9853ee7f 100644 --- a/api/dao/containerstorage.py +++ b/api/dao/containerstorage.py @@ -324,7 +324,7 @@ def create_el(self, analysis, parent_type, parent_id, origin, uid=None): * Flatten input filerefs using `FileReference.get_file()` If `analysis` has a `job` key, create a "job-based" analysis: - * Analysis inputs will are copied from the job inputs + * Analysis inputs will be copied from the job inputs * Create analysis and job, both referencing each other * Do not create (remove) analysis if can't enqueue job @@ -360,10 +360,12 @@ def create_el(self, analysis, parent_type, parent_id, origin, uid=None): for i, fileref_dict in enumerate(analysis.get('inputs', [])): try: fileref = containerutil.create_filereference_from_dictionary(fileref_dict) - analysis['inputs'][i] = fileref.get_file() except KeyError: # Legacy analyses already have fileinfos as inputs instead of filerefs pass + else: + analysis['inputs'][i] = fileref.get_file() + result = super(AnalysisStorage, self).create_el(analysis) if not result.acknowledged: diff --git a/api/dao/containerutil.py b/api/dao/containerutil.py index e6b001944..ef921f360 100644 --- a/api/dao/containerutil.py +++ b/api/dao/containerutil.py @@ -283,7 +283,7 @@ def from_dictionary(cls, d): def get_file(self): container = super(FileReference, self).get() - for file in container['files']: + for file in container.get('files', []): if file['name'] == self.name: return file diff --git a/api/placer.py b/api/placer.py index 23640b725..9be774485 100644 --- a/api/placer.py +++ b/api/placer.py @@ -705,7 +705,7 @@ def finalize(self): class AnalysisPlacer(Placer): def check(self): self.requireMetadata() - validators.validate_data(self.metadata, 'analysis.json', 'input', 'POST', optional=True) + validators.validate_data(self.metadata, 'analysis-legacy.json', 'input', 'POST', optional=True) def process_file_field(self, field, file_attrs): self.save_file(field) diff --git a/swagger/examples/input/analysis-legacy.json b/swagger/examples/input/analysis-legacy.json new file mode 100644 index 000000000..6d262998c --- /dev/null +++ b/swagger/examples/input/analysis-legacy.json @@ -0,0 +1,9 @@ +{ + "label": "Analysis label", + "description": "This is an analysis description", + "notes": [ + {"text": "some text"} + ], + "inputs": [{"name": "input.csv", "info": {"example": true}}], + "outputs": [{"name": "output.csv", "info": {"example": true}}] +} diff --git a/swagger/examples/input/analysis.json b/swagger/examples/input/analysis.json index 22712a840..e4551bb53 100644 --- a/swagger/examples/input/analysis.json +++ b/swagger/examples/input/analysis.json @@ -1,11 +1,7 @@ { "notes": [{ - "_id":"580925ce9e512c57dc8a103b", - "text":"some text", - "created": "2016-10-21T17:19:40.899000+00:00", - "modified": "2016-10-21T17:19:40.899000+00:00", - "user": "coltonlw@flywheel.io" - }] , + "text": "some text" + }], "description": "This is an analysis description", "job": { "gear_id": "aex", diff --git a/swagger/schemas/definitions/analysis.json b/swagger/schemas/definitions/analysis.json index 7855a4d48..e7142ef35 100644 --- a/swagger/schemas/definitions/analysis.json +++ b/swagger/schemas/definitions/analysis.json @@ -8,7 +8,7 @@ "type": "array", "items": {"$ref":"file.json#/definitions/file-reference"} }, - "notes": {"$ref":"note.json#/definitions/notes-list-output"}, + "notes": {"$ref":"note.json#/definitions/notes-list-input"}, "description": {"$ref":"common.json#/definitions/description"}, "label": {"$ref":"common.json#/definitions/label"} }, @@ -19,7 +19,7 @@ "type":"object", "properties":{ "job": {"$ref":"job.json#/definitions/job-input"}, - "notes": {"$ref":"note.json#/definitions/notes-list-output"}, + "notes": {"$ref":"note.json#/definitions/notes-list-input"}, "description": {"$ref":"common.json#/definitions/description"}, "label": {"$ref":"common.json#/definitions/label"} }, @@ -37,7 +37,7 @@ "type": "array", "items": {"$ref":"file.json#/definitions/file-input"} }, - "notes": {"$ref":"note.json#/definitions/notes-list-output"}, + "notes": {"$ref":"note.json#/definitions/notes-list-input"}, "description": {"$ref":"common.json#/definitions/description"}, "label": {"$ref":"common.json#/definitions/label"} }, @@ -46,8 +46,7 @@ "analysis-input-any": { "anyOf": [ {"$ref":"#/definitions/analysis-input-adhoc"}, - {"$ref":"#/definitions/analysis-input-job"}, - {"$ref":"#/definitions/analysis-input-legacy"} + {"$ref":"#/definitions/analysis-input-job"} ], "x-sdk-schema": {"$ref":"#/definitions/analysis-input-adhoc"} }, diff --git a/swagger/schemas/input/analysis-legacy.json b/swagger/schemas/input/analysis-legacy.json new file mode 100644 index 000000000..02a308596 --- /dev/null +++ b/swagger/schemas/input/analysis-legacy.json @@ -0,0 +1,7 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Analysis", + "type": "object", + "allOf": [{"$ref":"../definitions/analysis.json#/definitions/analysis-input-legacy"}], + "required": ["label"] +} diff --git a/swagger/schemas/input/analysis.json b/swagger/schemas/input/analysis.json index f6ed2acde..88272c8d7 100644 --- a/swagger/schemas/input/analysis.json +++ b/swagger/schemas/input/analysis.json @@ -2,6 +2,6 @@ "$schema": "http://json-schema.org/draft-04/schema#", "title": "Analysis", "type": "object", - "allOf":[{"$ref":"../definitions/analysis.json#/definitions/analysis-input-any"}], + "allOf": [{"$ref":"../definitions/analysis.json#/definitions/analysis-input-any"}], "required": ["label"] } diff --git a/tests/integration_tests/python/test_analyses.py b/tests/integration_tests/python/test_analyses.py index 7f130d3da..327039eda 100644 --- a/tests/integration_tests/python/test_analyses.py +++ b/tests/integration_tests/python/test_analyses.py @@ -11,6 +11,14 @@ def test_online_analysis(data_builder, as_admin, as_drone, file_form, api_db): acquisition = data_builder.create_acquisition() assert as_admin.post('/acquisitions/' + acquisition + '/files', files=file_form('input.csv')).ok + # Try to create job-based analysis with invalid fileref + r = as_admin.post('/sessions/' + session + '/analyses', json={ + 'label': 'online', + 'job': {'gear_id': gear, + 'inputs': {'csv': {'type': 'acquisition', 'id': acquisition, 'name': 'nosuch.csv'}}} + }) + assert r.status_code == 404 + # Create job-based analysis r = as_admin.post('/sessions/' + session + '/analyses', json={ 'label': 'online', @@ -47,6 +55,20 @@ def test_offline_analysis(data_builder, as_admin, file_form, api_db): acquisition = data_builder.create_acquisition() assert as_admin.post('/acquisitions/' + acquisition + '/files', files=file_form('input.csv')).ok + # Try to create ad-hoc analysis with invalid fileref + r = as_admin.post('/sessions/' + session + '/analyses', json={ + 'label': 'offline', + 'inputs': [{'type': 'acquisition', 'id': acquisition, 'name': 'nosuch.csv'}] + }) + assert r.status_code == 404 + + # Try to create ad-hoc analysis with invalid session fileref + r = as_admin.post('/sessions/' + session + '/analyses', json={ + 'label': 'offline', + 'inputs': [{'type': 'session', 'id': session, 'name': 'input.csv'}] + }) + assert r.status_code == 404 + # Create ad-hoc analysis r = as_admin.post('/sessions/' + session + '/analyses', json={ 'label': 'offline', @@ -78,6 +100,7 @@ def test_offline_analysis(data_builder, as_admin, file_form, api_db): def test_legacy_analysis(data_builder, as_admin, file_form, api_db): session = data_builder.create_session() + # Create legacy analysis (upload both inputs and outputs in the same fileform) r = as_admin.post('/sessions/' + session + '/analyses', files=file_form('input.csv', 'output.csv', meta={ 'label': 'legacy', 'inputs': [{'name': 'input.csv', 'info': {'foo': 'foo'}}], @@ -94,6 +117,7 @@ def test_legacy_analysis(data_builder, as_admin, file_form, api_db): def test_analysis_download(data_builder, as_admin, file_form, api_db): session = data_builder.create_session() + # Create legacy analysis r = as_admin.post('/sessions/' + session + '/analyses', files=file_form('input.csv', 'output.csv', meta={ 'label': 'legacy', 'inputs': [{'name': 'input.csv', 'info': {'foo': 'foo'}}], @@ -103,7 +127,7 @@ def test_analysis_download(data_builder, as_admin, file_form, api_db): analysis = r.json()['_id'] # Get download ticket for analysis via /download - r = as_admin.get('/download', params={'ticket': ''}, json={'optional':True, 'nodes': [{'level':'analysis','_id': analysis}]}) + r = as_admin.get('/download', params={'ticket': ''}, json={'optional': True, 'nodes': [{'level':'analysis','_id': analysis}]}) assert r.ok ticket = r.json()['ticket']