diff --git a/api/dao/containerstorage.py b/api/dao/containerstorage.py index c302fbb04..3483f5bc2 100644 --- a/api/dao/containerstorage.py +++ b/api/dao/containerstorage.py @@ -1,5 +1,5 @@ import datetime - +import pymongo import bson from . import APIStorageException, APINotFoundException @@ -401,6 +401,15 @@ class SearchStorage(ContainerStorage): def __init__(self): super(SearchStorage, self).__init__('savesearches', use_object_id=True) + def create_el(self, payload): + log.debug(payload) + payload = self._to_mongo(payload) + try: + result = self.dbc.insert_one(payload, bypass_document_validation=True) + except pymongo.errors.DuplicateKeyError: + raise APIConflictException('Object with id {} already exists.'.format(payload['_id'])) + return result + def replace_el(self, search): self.delete_el(search['_id']) return self.create_el(search) diff --git a/api/handlers/savesearchhandler.py b/api/handlers/savesearchhandler.py index a64344729..2b60e8bbd 100644 --- a/api/handlers/savesearchhandler.py +++ b/api/handlers/savesearchhandler.py @@ -1,4 +1,5 @@ import bson +from ast import literal_eval from ..web import base from .. import config, validators from ..auth import require_login @@ -11,6 +12,23 @@ log = config.log storage = SearchStorage() + +def string_filters(payload): + if payload.get('search') and payload['search'].get('filters'): + filters = [] + for filter_ in payload['search'].get('filters',[]): + filters.append(str(filter_)) + payload['search']['filters'] = filters + return payload + +def unstring_filters(payload): + if payload['search'].get('filters'): + filters= [] + for filter_ in payload['search'].get('filters',[]): + filters.append(literal_eval(filter_)) + payload['search']['filters']= filters + return payload + class SaveSearchHandler(base.RequestHandler): def __init__(self, request=None, response=None): @@ -20,6 +38,7 @@ def __init__(self, request=None, response=None): def post(self): payload = self.request.json_body validators.validate_data(payload, 'search-input.json', 'input', 'POST') + payload = string_filters(payload) payload['permissions'] = [{"_id": self.uid, "access": "admin"}] payload['creator'] = self.uid result = storage.create_el(payload) @@ -36,6 +55,7 @@ def get(self, sid): result = storage.get_el(sid) if result is None: self.abort(404, 'Element {} not found'.format(sid)) + unstring_filters(result) return result def delete(self, sid): @@ -52,6 +72,7 @@ def replace_search(self, sid): payload = self.request.json_body payload = self._scrub_replace(payload) validators.validate_data(payload, 'search-input.json', 'input', 'POST') + payload = string_filters(payload) payload['_id'] = bson.ObjectId(sid) search = storage.get_container(sid) payload['permissions'] = search['permissions'] @@ -74,3 +95,4 @@ def _scrub_replace(self, payload): if payload.get('creator'): del(payload['creator']) return payload + diff --git a/raml/examples/output/search-list.json b/raml/examples/output/search-list.json new file mode 100644 index 000000000..6d4ccf812 --- /dev/null +++ b/raml/examples/output/search-list.json @@ -0,0 +1,4 @@ +[{ + "label": "Test Search", + "_id": "57e452791cff88b85f9f9c23" +}] \ No newline at end of file diff --git a/raml/examples/output/search.json b/raml/examples/output/search.json new file mode 100644 index 000000000..189e06ce7 --- /dev/null +++ b/raml/examples/output/search.json @@ -0,0 +1,10 @@ +{ + "label": "Test Search", + "_id": "57e452791cff88b85f9f9c23", + "search": { + "return_type": "file", + "filters": [{"terms": {"file.type":["nifti"]}}] + }, + "permissions": [{"access": "admin", "_id": "harshakethineni@invenshure.com"}], + "creator": "harshakethineni@invenshure.com" +} \ No newline at end of file diff --git a/raml/resources/savesearch.raml b/raml/resources/savesearch.raml index 2e6751783..49dde59b3 100644 --- a/raml/resources/savesearch.raml +++ b/raml/resources/savesearch.raml @@ -5,6 +5,7 @@ get: 200: body: application/json: + example: !include ../examples/output/search-list.json schema: !include ../schemas/output/search-list.json post: body: @@ -18,6 +19,15 @@ post: body: application/json: schema: !include ../schemas/output/search-output.json + post: + description: Replace saved search with a new search + body: + application/json: + schema: !include ../schemas/input/search-input.json + /permissions: + type: permissions-list + /{UserId}: + type: permissions-item delete: description: Delete a saved search responses: @@ -26,4 +36,3 @@ post: application/json: schema: !include ../schemas/output/container-delete.json example: !include ../examples/output/container-delete.json - diff --git a/raml/schemas/output/search-list.json b/raml/schemas/output/search-list.json index 058612f9c..529c9744c 100644 --- a/raml/schemas/output/search-list.json +++ b/raml/schemas/output/search-list.json @@ -5,7 +5,7 @@ "type":"object", "allOf":[{"$ref":"../definitions/search.json#/definitions/search-output"}], "required":[ - "_id", "label", "permissions", "search" + "_id", "label" ] } } \ No newline at end of file diff --git a/test/integration_tests/abao/abao_test_hooks.js b/test/integration_tests/abao/abao_test_hooks.js index b39380ccd..94f0aea60 100644 --- a/test/integration_tests/abao/abao_test_hooks.js +++ b/test/integration_tests/abao/abao_test_hooks.js @@ -24,6 +24,8 @@ var delete_project_id = ''; var device_id = 'bootstrapper_Bootstrapper'; var injected_api_key = 'XZpXI40Uk85eozjQkU1zHJ6yZHpix+j0mo1TMeGZ4dPzIqVPVGPmyfeK'; var search_id = ''; +var delete_search_id = ''; +var user_id = 'user@user.com' // Tests we're skipping, fix these @@ -1474,7 +1476,7 @@ hooks.before("POST /savesearches -> 200", function(test, done) { done(); }) hooks.after("POST /savesearches -> 200", function(test, done) { - search_id = test.response.body['_id']; + delete_search_id = test.response.body['_id']; done(); }) @@ -1485,6 +1487,11 @@ hooks.before("POST /savesearches -> 400", function(test, done) { done(); }) +hooks.after("GET /savesearches -> 200", function(test, done) { + search_id = test.response.body[0]._id; + done(); +}) + hooks.before("GET /savesearches/{SearchId} -> 200", function(test, done) { test.request.params = { SearchId: search_id @@ -1516,9 +1523,69 @@ hooks.before("POST /savesearches/{SearchId} -> 400", function(test, done) { done(); }) -hooks.before("DELETE /savesearches/{SearchId} -> 200", function(test, done) { +hooks.before("POST /savesearches/{SearchId}/permissions -> 200", function(test, done) { + test.request.params = { + SearchId: search_id + }; + test.request.body = { + "access" : "admin", + "_id": user_id + }; + done(); +}) + +hooks.before("POST /savesearches/{SearchId}/permissions -> 400", function(test, done) { test.request.params = { SearchId: search_id }; + test.request.body = { + "not-access" : "admin", + "not_id": user_id + }; + done(); +}) + +hooks.before("GET /savesearches/{SearchId}/permissions/{UserId} -> 200", function(test, done) { + test.request.params = { + SearchId: search_id, + UserId: user_id + }; + done(); +}) + +hooks.before("PUT /savesearches/{SearchId}/permissions/{UserId} -> 200", function(test, done) { + test.request.params = { + SearchId: search_id, + UserId: user_id + }; + test.request.body = { + "access" : "ro" + }; + done(); +}) + +hooks.before("PUT /savesearches/{SearchId}/permissions/{UserId} -> 400", function(test, done) { + test.request.params = { + SearchId: search_id, + UserId: user_id + }; + test.request.body = { + "access" : "not_an_access_level" + }; + done(); +}) + +hooks.before("DELETE /savesearches/{SearchId}/permissions/{UserId} -> 200", function(test, done) { + test.request.params = { + SearchId: search_id, + UserId: user_id + }; + done(); +}) + +hooks.before("DELETE /savesearches/{SearchId} -> 200", function(test, done) { + test.request.params = { + SearchId: delete_search_id + }; done(); }) diff --git a/test/integration_tests/abao/load_fixture.py b/test/integration_tests/abao/load_fixture.py index 18a8a675a..4e3906d37 100644 --- a/test/integration_tests/abao/load_fixture.py +++ b/test/integration_tests/abao/load_fixture.py @@ -81,6 +81,19 @@ def main(): }) assert r.ok + # create a saved search + r = as_root.post('/savesearches', json={ + "label": "Test Search", + "search": { + "return_type": "file", + "filters": [{"terms": {"file.type":["nifti"]}}] + }, + }) + assert r.ok + r = as_root.get('/savesearches') + assert r.ok + assert r.json()[0]['label'] == 'Test Search' + # list projects # depends on 'upload file to test-project-1/test-session-1/test-acquisition-1' r = as_root.get('/projects')