From 7282419174d111b995796ce930e10bd4e8c41042 Mon Sep 17 00:00:00 2001 From: Dan Date: Fri, 11 Jun 2021 08:27:50 -0400 Subject: [PATCH] modified the tools api to return the expression and data with the result, making it easier to cash results on the front end, This should help drastically reduce the calls to the python_eval from the front end. The post processors for form submissions needs to take repeating sections into account, or it won't find the files it is looking for. --- Pipfile.lock | 2 +- crc/api/tools.py | 7 ++++-- crc/services/workflow_service.py | 37 ++++++++++++++++++++++---------- tests/test_tools_api.py | 14 ++++++++++++ 4 files changed, 46 insertions(+), 14 deletions(-) diff --git a/Pipfile.lock b/Pipfile.lock index 76f7471c..33d23b19 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -957,7 +957,7 @@ }, "spiffworkflow": { "git": "https://github.com/sartography/SpiffWorkflow.git", - "ref": "ce939de158246e9d10e7e154c92230669354bc64" + "ref": "da79e8b0f66df1cb8372435bdff3b294e5f3a336" }, "sqlalchemy": { "hashes": [ diff --git a/crc/api/tools.py b/crc/api/tools.py index 43c01b0c..7904dfe4 100644 --- a/crc/api/tools.py +++ b/crc/api/tools.py @@ -1,3 +1,4 @@ +import hashlib import io import json @@ -78,11 +79,13 @@ def send_email(subject, address, body, data=None): def evaluate_python_expression(body): """Evaluate the given python expression, returning its result. This is useful if the front end application needs to do real-time processing on task data. If for instance - there is a hide expression that is based on a previous value in the same form.""" + there is a hide expression that is based on a previous value in the same form. + The response includes both the result, and a hash of the original query, subsequent calls + of the same hash are unnecessary. """ try: script_engine = CustomBpmnScriptEngine() result = script_engine.eval(body['expression'], body['data']) - return {"result": result} + return {"result": result, "expression": body['expression'], "data": body['data']} except Exception as e: raise ApiError("expression_error", f"Failed to evaluate the expression '%s'. %s" % (body['expression'], str(e)), diff --git a/crc/services/workflow_service.py b/crc/services/workflow_service.py index 395048b4..e3924064 100644 --- a/crc/services/workflow_service.py +++ b/crc/services/workflow_service.py @@ -259,19 +259,34 @@ class WorkflowService(object): """Looks through the fields in a submitted form, acting on any properties.""" if not hasattr(task.task_spec, 'form'): return for field in task.task_spec.form.fields: - if field.has_property(Task.FIELD_PROP_DOC_CODE) and \ - field.type == Task.FIELD_TYPE_FILE: - file_id = task.data[field.id] - file = db.session.query(FileModel).filter(FileModel.id == file_id).first() - doc_code = WorkflowService.evaluate_property(Task.FIELD_PROP_DOC_CODE, field, task) + data = task.data + if field.has_property(Task.FIELD_PROP_REPEAT): + repeat_array = task.data[field.get_property(Task.FIELD_PROP_REPEAT)] + for repeat_data in repeat_array: + WorkflowService.__post_process_field(task, field, repeat_data) + else: + WorkflowService.__post_process_field(task, field, data) + + @staticmethod + def __post_process_field(task, field, data): + if field.has_property(Task.FIELD_PROP_DOC_CODE) and field.id in data: + # This is generally handled by the front end, but it is possible that the file was uploaded BEFORE + # the doc_code was correctly set, so this is a stop gap measure to assure we still hit it correctly. + file_id = data[field.id] + doc_code = task.workflow.script_engine.eval(field.get_property(Task.FIELD_PROP_DOC_CODE), data) + file = db.session.query(FileModel).filter(FileModel.id == file_id).first() + if(file): file.irb_doc_code = doc_code db.session.commit() - # Set the doc code on the file. - if field.has_property(Task.FIELD_PROP_FILE_DATA) and \ - field.get_property(Task.FIELD_PROP_FILE_DATA) in task.data: - file_id = task.data[field.get_property(Task.FIELD_PROP_FILE_DATA)] - data_store = DataStoreModel(file_id=file_id, key=field.id, value=task.data[field.id]) - db.session.add(data_store) + else: + # We have a problem, the file doesn't exist, and was removed, but it is still referenced in the data + # At least attempt to clear out the data. + data = {} + if field.has_property(Task.FIELD_PROP_FILE_DATA) and \ + field.get_property(Task.FIELD_PROP_FILE_DATA) in data: + file_id = data[field.get_property(Task.FIELD_PROP_FILE_DATA)] + data_store = DataStoreModel(file_id=file_id, key=field.id, value=data[field.id]) + db.session.add(data_store) @staticmethod def evaluate_property(property_name, field, task): diff --git a/tests/test_tools_api.py b/tests/test_tools_api.py index 5e7f6c8c..a918e17f 100644 --- a/tests/test_tools_api.py +++ b/tests/test_tools_api.py @@ -48,6 +48,20 @@ class TestStudyApi(BaseTest): response = json.loads(rv.get_data(as_text=True)) self.assertEqual(True, response['result']) + def test_eval_returns_query(self): + """Assures that along with the result, we get the original data and expression. + This can be useful if the calling client is caching results and needs to hash the expression and data + when it gets returned.""" + data = '{"expression": "x.y==2", "data": {"x":{"y":2}}}' + rv = self.app.put('/v1.0/eval', + data=data, follow_redirects=True, + content_type='application/json', + headers=self.logged_in_headers()) + self.assert_success(rv) + response = json.loads(rv.get_data(as_text=True)) + self.assertEqual("x.y==2", response['expression']) + self.assertEqual({'x': {'y': 2}}, response['data']) + def test_eval_expression_with_strings(self): """Assures we can use python to process a value expression from the front end"""