From 1ba13a9f8601e05826ddbd255c712202e4ec9e8e Mon Sep 17 00:00:00 2001
From: Megan Henning <meganhenning@flywheel.io>
Date: Wed, 28 Sep 2016 12:05:37 -0500
Subject: [PATCH] Add endpoint for session compliance calc

---
 api/api.py                                |  3 +-
 api/dao/containerstorage.py               | 31 +++++++
 api/handlers/containerhandler.py          | 11 ++-
 raml/examples/input/project-template.json | 99 +++++++++--------------
 raml/resources/projects.raml              | 40 +++++++++
 5 files changed, 118 insertions(+), 66 deletions(-)
 create mode 100644 raml/resources/projects.raml

diff --git a/api/api.py b/api/api.py
index 1c53a028..c51c2708 100644
--- a/api/api.py
+++ b/api/api.py
@@ -172,7 +172,8 @@ routes = [
     webapp2.Route(_format(r'/api/users/<uid:{user_id_re}>/<cont_name:{cont_name_re}>'), containerhandler.ContainerHandler, name='user_conts', handler_method='get_all_for_user', methods=['GET']),
 
     webapp2.Route(r'/api/projects/groups',                                              containerhandler.ContainerHandler, handler_method='get_groups_with_project', methods=['GET']),
-    webapp2.Route(_format(r'/api/projects/<cid:{cid_re}>/template'),                             containerhandler.ContainerHandler, handler_method='set_project_template', methods=['POST']),
+    webapp2.Route(_format(r'/api/projects/<cid:{cid_re}>/template'),                    containerhandler.ContainerHandler, handler_method='set_project_template', methods=['POST']),
+    webapp2.Route(_format(r'/api/projects/<cid:{cid_re}>/template/recalc'),             containerhandler.ContainerHandler, handler_method='calculate_project_compliance', methods=['POST']),
 
     webapp2.Route(_format(r'/api/<par_cont_name:groups>/<par_id:{group_id_re}>/<cont_name:projects>'),          containerhandler.ContainerHandler, name='cont_sublist_groups', handler_method='get_all', methods=['GET']),
     webapp2.Route(_format(r'/api/<par_cont_name:{cont_name_re}>/<par_id:{cid_re}>/<cont_name:{cont_name_re}>'), containerhandler.ContainerHandler, name='cont_sublist', handler_method='get_all', methods=['GET']),
diff --git a/api/dao/containerstorage.py b/api/dao/containerstorage.py
index 6b4c4eac..cf1218ca 100644
--- a/api/dao/containerstorage.py
+++ b/api/dao/containerstorage.py
@@ -39,6 +39,8 @@ class ContainerStorage(object):
         """
         if cont_name == 'groups':
             return GroupStorage()
+        elif cont_name == 'projects':
+            return ProjectStorage()
         elif cont_name == 'sessions':
             return SessionStorage()
         elif cont_name == 'acquisitions':
@@ -158,6 +160,28 @@ class GroupStorage(ContainerStorage):
             },
             upsert=True)
 
+
+class ProjectStorage(ContainerStorage):
+
+    def __init__(self):
+        super(ProjectStorage,self).__init__('projects', use_object_id=True)
+
+    def recalc_sessions_compliance(self, project_id):
+        project = self.get_container(project_id, get_children=True)
+        if not project:
+            raise APINotFoundException('Could not find project {}'.format(project_id))
+        template = json.loads(project.get('template',{}))
+        if not template:
+            return
+        else:
+            changed_sessions = []
+            session_storage = SessionStorage()
+            for s in project.get('sessions', []):
+                changed = session_storage.recalc_session_compliance(s['_id'], session=s, template=template)
+                if changed:
+                    changed_sessions.append(s['_id'])
+
+
 class SessionStorage(ContainerStorage):
 
     def __init__(self):
@@ -181,6 +205,10 @@ class SessionStorage(ContainerStorage):
         return super(SessionStorage, self).update_el(_id, payload, recursive, r_payload, replace_metadata)
 
     def recalc_session_compliance(self, session_id, session=None, template=None):
+        """
+        Calculates a session's compliance with the project's session template.
+        Returns True if the status changed, False otherwise
+        """
         if session is None:
             session = self.get_container(session_id)
         if session is None:
@@ -192,6 +220,9 @@ class SessionStorage(ContainerStorage):
             if session.get('satisfies_template') != satisfies_template:
                 update = {'satisfies_template': satisfies_template}
                 super(SessionStorage, self).update_el(session_id, update)
+                return True
+        return False
+
 
 class AcquisitionStorage(ContainerStorage):
 
diff --git a/api/handlers/containerhandler.py b/api/handlers/containerhandler.py
index d1c02b77..ee44a4b6 100644
--- a/api/handlers/containerhandler.py
+++ b/api/handlers/containerhandler.py
@@ -50,7 +50,7 @@ class ContainerHandler(base.RequestHandler):
     # "use_object_id" implies that the container ids are converted to ObjectId
     container_handler_configurations = {
         'projects': {
-            'storage': containerstorage.ContainerStorage('projects', use_object_id=use_object_id['projects']),
+            'storage': containerstorage.ProjectStorage(),
             'permchecker': containerauth.default_container,
             'parent_storage': containerstorage.GroupStorage(),
             'storage_schema_file': 'project.json',
@@ -62,7 +62,7 @@ class ContainerHandler(base.RequestHandler):
         'sessions': {
             'storage': containerstorage.SessionStorage(),
             'permchecker': containerauth.default_container,
-            'parent_storage': containerstorage.ContainerStorage('projects', use_object_id=use_object_id['projects']),
+            'parent_storage': containerstorage.ProjectStorage(),
             'storage_schema_file': 'session.json',
             'payload_schema_file': 'session.json',
             'list_projection': {'metadata': 0},
@@ -485,7 +485,6 @@ class ContainerHandler(base.RequestHandler):
 
     def set_project_template(self, **kwargs):
         project_id = kwargs.pop('cid')
-        log.debug('the project_id is {}'.format(project_id))
         self.config = self.container_handler_configurations['projects']
         self.storage = self.config['storage']
         container = self._get_container(project_id)
@@ -509,6 +508,12 @@ class ContainerHandler(base.RequestHandler):
         else:
             self.abort(404, 'Could not find project {}'.format(project_id))
 
+    def calculate_project_compliance(self, **kwargs):
+        project_id = kwargs.pop('cid')
+        self.config = self.container_handler_configurations['projects']
+        self.storage = self.config['storage']
+        return {'sessions_changed': self.storage.recalc_sesssions_compliance(project_id)}
+
     def _get_validators(self):
         mongo_schema_uri = validators.schema_uri('mongo', self.config.get('storage_schema_file'))
         mongo_validator = validators.decorator_from_schema_path(mongo_schema_uri)
diff --git a/raml/examples/input/project-template.json b/raml/examples/input/project-template.json
index 9d0f93d7..6e5b25d4 100644
--- a/raml/examples/input/project-template.json
+++ b/raml/examples/input/project-template.json
@@ -5,76 +5,51 @@
             "properties": {
                 "label": {
                     "type":     "string",
-                    "pattern":  "^(?i)functional$" }
+                    "pattern":  "^(?i)test_pattern$" }
             }
         }
     },
     "acquisitions": [
-        {
-            "schema": {
-                "$schema": "http://json-schema.org/draft-04/schema#",
-                "type": "object",
-                "properties": {
-                    "measurement": {
-                        "type":     "string",
-                        "pattern":  "^[aA]natomical$" }
-                },
-                "required": ["measurement"]
-            },
-            "minimum": 2,
-            "files": [
-                {
-                    "schema": {
-                        "$schema": "http://json-schema.org/draft-04/schema#",
-                        "type": "object",
-                        "properties": {
-
-                            "type": { "enum": ["nifti"] }
-                        },
-                        "required": ["type"]
+            {
+                "schema": {
+                    "$schema": "http://json-schema.org/draft-04/schema#",
+                    "type": "object",
+                    "properties": {
+                        "measurement": {
+                            "type":     "string",
+                            "pattern":  "^[aA]natomical$" }
                     },
-                    "minimum": 2
+                    "required": ["measurement"]
                 },
-                {
-                    "schema": {
-                        "$schema": "http://json-schema.org/draft-04/schema#",
-                        "type": "object",
-                        "properties": {
-                            "type": { "enum": ["dicom"] }
-                        },
-                        "required": ["type"]
+                "minimum": 2
+            },
+            {
+                "schema": {
+                    "$schema": "http://json-schema.org/draft-04/schema#",
+                    "type": "object",
+                    "properties": {
+                        "measurement": {
+                            "type":     "string",
+                            "pattern":  "^(?i)functional$" }
                     },
-                    "minimum": 2
-                }
-            ]
-        },
-        {
-            "schema": {
-                "$schema": "http://json-schema.org/draft-04/schema#",
-                "type": "object",
-                "properties": {
-                    "measurement": {
-                        "type":     "string",
-                        "pattern":  "^(?i)functional$" }
+                    "required": ["measurement"]
                 },
-                "required": ["measurement"]
+                "minimum": 1
             },
-            "minimum": 3
-        }
-        {
-            "schema": {
-                "$schema": "http://json-schema.org/draft-04/schema#",
-                "type": "object",
-                "properties": {
-                    "measurement": { "enum": ["Localizer"] },
-                    "label":  {
-                        "type": "string",
-                        "pattern": "t1"
-                    }
+            {
+                "schema": {
+                    "$schema": "http://json-schema.org/draft-04/schema#",
+                    "type": "object",
+                    "properties": {
+                        "measurement": { "enum": ["Localizer"] },
+                        "label":  {
+                            "type": "string",
+                            "pattern": "t1"
+                        }
+                    },
+                    "required": ["label", "measurement"]
                 },
-                "required": ["label", "measurement"]
-            },
-            "minimum": 2
-        }
-    ]
+                "minimum": 1
+            }
+        ]
 }
diff --git a/raml/resources/projects.raml b/raml/resources/projects.raml
new file mode 100644
index 00000000..58864787
--- /dev/null
+++ b/raml/resources/projects.raml
@@ -0,0 +1,40 @@
+/{ProjectId}/template:
+  uriParameters:
+    ProjectId:
+      type: string
+      required: true
+  post:
+    description: Set the session template for a project
+    body:
+      application/json:
+        example: !include ../examples/input/project-template.json
+        schema: !include ../schemas/input/project-template.json
+    responses:
+      200:
+        description: Template was saved
+        body:
+          application/json:
+            example: |
+              {"modified": 1}
+      404:
+        description: Project was not found
+
+/{ProjectId}/template/recalc:
+  uriParameters:
+    ProjectId:
+      type: string
+      required: true
+  post:
+    description: |
+      Recalculate if sessions in the project satisfy the template.
+      Returns list of modified sessions
+    body:
+      application/json:
+        example: !include ../examples/input/project-template.json
+        schema: !include ../schemas/input/project-template.json
+    responses:
+      200:
+        description: Project's sessions' compliance was recalculated, returns list of
+      404:
+        description: Project was not found
+
-- 
GitLab