From 55a1850e7ced343bce05bb22df9ce88f54e9bd92 Mon Sep 17 00:00:00 2001 From: Dan Funk Date: Thu, 14 May 2020 13:43:23 -0400 Subject: [PATCH 1/5] adding a navigation component to the Workflow Model. running all extension/properties through the Jinja template processor so you can have custom display names using data, very helpful for building multi-instance displays. Properties was returned as an array of key/value pairs, which is just mean. Switched this to a dictionary. --- Pipfile.lock | 2 +- crc/api.yml | 35 ++++++++++++++++++ crc/api/workflow.py | 1 + crc/models/api_models.py | 13 ++----- crc/services/workflow_service.py | 19 ++++++++-- tests/data/multi_instance/multi_instance.bpmn | 37 ++++++++++--------- tests/test_tasks_api.py | 13 +++++++ 7 files changed, 89 insertions(+), 31 deletions(-) diff --git a/Pipfile.lock b/Pipfile.lock index 90465cc6..97a58a73 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -783,7 +783,7 @@ "spiffworkflow": { "editable": true, "git": "https://github.com/sartography/SpiffWorkflow.git", - "ref": "bb83497f5128f6221113b090d8de84401b0f108f" + "ref": "635afdec60dcb46dcef8c47cd4ba455cb0877f8b" }, "sqlalchemy": { "hashes": [ diff --git a/crc/api.yml b/crc/api.yml index 081bf676..2e323598 100644 --- a/crc/api.yml +++ b/crc/api.yml @@ -939,6 +939,10 @@ components: status: type: enum enum: ['new','user_input_required','waiting','complete'] + navigation: + type: array + items: + $ref: "#/components/schemas/NavigationItem" user_tasks: type: array items: @@ -1160,5 +1164,36 @@ components: example: "Chuck Norris" data: type: any + NavigationItem: + properties: + id: + type: number + format: integer + example: 5 + name: + type: string + example: "Task_Has_bananas" + description: + type: string + example: "Has Bananas?" + backtracks: + type: booean + example: false + level: + type: integer + example: 1 + indent: + type: integer + example: 2 + childcount: + type: integer + example: 4 + state: + type: enum + enum: ['FUTURE', 'WAITING', 'READY', 'CANCELLED', 'COMPLETED','LIKELY','MAYBE'] + readOnly: true + + + diff --git a/crc/api/workflow.py b/crc/api/workflow.py index de67c87f..49cd2fc1 100644 --- a/crc/api/workflow.py +++ b/crc/api/workflow.py @@ -89,6 +89,7 @@ def __get_workflow_api_model(processor: WorkflowProcessor): workflow_api = WorkflowApi( id=processor.get_workflow_id(), status=processor.get_status(), + navigation=processor.bpmn_workflow.get_nav_list(), last_task=WorkflowService.spiff_task_to_api_task(processor.bpmn_workflow.last_task), next_task=None, previous_task=processor.previous_task(), diff --git a/crc/models/api_models.py b/crc/models/api_models.py index 020a1c45..40d3c042 100644 --- a/crc/models/api_models.py +++ b/crc/models/api_models.py @@ -50,10 +50,6 @@ class ValidationSchema(ma.Schema): fields = ["name", "config"] -class PropertiesSchema(ma.Schema): - class Meta: - fields = ["id", "value"] - class FormFieldSchema(ma.Schema): class Meta: @@ -64,7 +60,6 @@ class FormFieldSchema(ma.Schema): default_value = marshmallow.fields.String(required=False, allow_none=True) options = marshmallow.fields.List(marshmallow.fields.Nested(OptionSchema)) validation = marshmallow.fields.List(marshmallow.fields.Nested(ValidationSchema)) - properties = marshmallow.fields.List(marshmallow.fields.Nested(PropertiesSchema)) class FormSchema(ma.Schema): @@ -81,7 +76,6 @@ class TaskSchema(ma.Schema): documentation = marshmallow.fields.String(required=False, allow_none=True) form = marshmallow.fields.Nested(FormSchema, required=False, allow_none=True) title = marshmallow.fields.String(required=False, allow_none=True) - properties = marshmallow.fields.List(marshmallow.fields.Nested(PropertiesSchema)) process_name = marshmallow.fields.String(required=False, allow_none=True) @marshmallow.post_load @@ -90,10 +84,11 @@ class TaskSchema(ma.Schema): class WorkflowApi(object): - def __init__(self, id, status, user_tasks, last_task, next_task, previous_task, + def __init__(self, id, status, navigation, user_tasks, last_task, next_task, previous_task, spec_version, is_latest_spec, workflow_spec_id, total_tasks, completed_tasks, last_updated): self.id = id self.status = status + self.navigation = navigation self.user_tasks = user_tasks self.last_task = last_task # The last task that was completed, may be different than previous. self.next_task = next_task # The next task that requires user input. @@ -108,7 +103,7 @@ class WorkflowApi(object): class WorkflowApiSchema(ma.Schema): class Meta: model = WorkflowApi - fields = ["id", "status", "user_tasks", "last_task", "next_task", "previous_task", + fields = ["id", "status", "navigation", "user_tasks", "last_task", "next_task", "previous_task", "workflow_spec_id", "spec_version", "is_latest_spec", "total_tasks", "completed_tasks", "last_updated"] unknown = INCLUDE @@ -121,7 +116,7 @@ class WorkflowApiSchema(ma.Schema): @marshmallow.post_load def make_workflow(self, data, **kwargs): - keys = ['id', 'status', 'user_tasks', 'last_task', 'next_task', 'previous_task', + keys = ['id', 'status', 'navigation', 'user_tasks', 'last_task', 'next_task', 'previous_task', 'workflow_spec_id', 'spec_version', 'is_latest_spec', "total_tasks", "completed_tasks", "last_updated"] filtered_fields = {key: data[key] for key in keys} diff --git a/crc/services/workflow_service.py b/crc/services/workflow_service.py index f9fe308e..bc8f47a5 100644 --- a/crc/services/workflow_service.py +++ b/crc/services/workflow_service.py @@ -10,7 +10,7 @@ from flask import g from pandas import ExcelFile from sqlalchemy import func -from crc import db +from crc import db, app from crc.api.common import ApiError from crc.models.api_models import Task, MultiInstanceType import jinja2 @@ -90,10 +90,10 @@ class WorkflowService(object): else: mi_type = MultiInstanceType.none - props = [] + props = {} if hasattr(spiff_task.task_spec, 'extensions'): for id, val in spiff_task.task_spec.extensions.items(): - props.append({"id": id, "value": val}) + props[id] = val task = Task(spiff_task.id, spiff_task.task_spec.name, @@ -117,10 +117,21 @@ class WorkflowService(object): task.form = spiff_task.task_spec.form for field in task.form.fields: WorkflowService.process_options(spiff_task, field) - task.documentation = WorkflowService._process_documentation(spiff_task) + task.props = WorkflowService._process_properties(spiff_task, props) + return task + @staticmethod + def _process_properties(spiff_task, props): + """Runs all the property values through the Jinja2 processor to inject data.""" + for k,v in props.items(): + try: + template = Template(v) + props[k] = template.render(**spiff_task.data) + except jinja2.exceptions.TemplateError as ue: + app.logger.error("Failed to process task property %s " % str(ue)) + @staticmethod def _process_documentation(spiff_task): """Runs the given documentation string through the Jinja2 processor to inject data diff --git a/tests/data/multi_instance/multi_instance.bpmn b/tests/data/multi_instance/multi_instance.bpmn index df932364..834a6956 100644 --- a/tests/data/multi_instance/multi_instance.bpmn +++ b/tests/data/multi_instance/multi_instance.bpmn @@ -1,5 +1,5 @@ - + Flow_0t6p1sb @@ -8,8 +8,8 @@ Flow_0ugjw69 - - + + # Please provide addtional information about: ## Investigator ID: {{investigator.NETBADGEID}} ## Role: {{investigator.INVESTIGATORTYPEFULL}} @@ -17,12 +17,15 @@ + + + SequenceFlow_1p568pp Flow_0ugjw69 - + Flow_0t6p1sb SequenceFlow_1p568pp @@ -31,33 +34,33 @@ + + + + + + + + + + + + - - - - - - - - - + - - - - diff --git a/tests/test_tasks_api.py b/tests/test_tasks_api.py index af994275..8c9a3dc3 100644 --- a/tests/test_tasks_api.py +++ b/tests/test_tasks_api.py @@ -140,6 +140,16 @@ class TestTasksApi(BaseTest): workflow_api = self.get_workflow_api(workflow) self.assertEqual("Task_Num_Bananas", workflow_api.next_task['name']) + def test_navigation_with_parallel_forms(self): + self.load_example_data() + workflow = self.create_workflow('exclusive_gateway') + + # get the first form in the two form workflow. + workflow_api = self.get_workflow_api(workflow) + + self.assertIsNotNone(workflow_api.navigation) + + def test_get_workflow_contains_details_about_last_task_data(self): self.load_example_data() workflow = self.create_workflow('exclusive_gateway') @@ -300,6 +310,9 @@ class TestTasksApi(BaseTest): self.assertEquals(MultiInstanceType.sequential, tasks[0].mi_type) self.assertEquals(9, tasks[0].mi_count) + # Assure that the names for each task are properly updated, so they aren't all the same. + self.assertEquals("Primary Investigator", tasks[0].properties['display_name']) + def test_lookup_endpoint_for_task_field_enumerations(self): self.load_example_data() From 6d4348d64440816f3a3420c8537baecf99811097 Mon Sep 17 00:00:00 2001 From: Dan Funk Date: Thu, 14 May 2020 14:39:14 -0400 Subject: [PATCH 2/5] Fixing some failing tests. Moved the task properties into a dictionary, but moving the form field properties to a dictionary will be a larger effort that we don't want to get into on either the back or front end right this moment. --- crc/models/api_models.py | 7 + .../data_security_plan.bpmn | 345 ++++++++++++------ tests/data/multi_instance/multi_instance.bpmn | 8 +- tests/test_tasks_api.py | 3 +- 4 files changed, 243 insertions(+), 120 deletions(-) diff --git a/crc/models/api_models.py b/crc/models/api_models.py index 40d3c042..f8704492 100644 --- a/crc/models/api_models.py +++ b/crc/models/api_models.py @@ -49,6 +49,11 @@ class ValidationSchema(ma.Schema): class Meta: fields = ["name", "config"] +class FormFieldPropertySchema(ma.Schema): + class Meta: + fields = [ + "id", "value" + ] class FormFieldSchema(ma.Schema): @@ -60,6 +65,7 @@ class FormFieldSchema(ma.Schema): default_value = marshmallow.fields.String(required=False, allow_none=True) options = marshmallow.fields.List(marshmallow.fields.Nested(OptionSchema)) validation = marshmallow.fields.List(marshmallow.fields.Nested(ValidationSchema)) + properties = marshmallow.fields.List(marshmallow.fields.Nested(FormFieldPropertySchema)) class FormSchema(ma.Schema): @@ -93,6 +99,7 @@ class WorkflowApi(object): self.last_task = last_task # The last task that was completed, may be different than previous. self.next_task = next_task # The next task that requires user input. self.previous_task = previous_task # The opposite of next task. + self.workflow_spec_id = workflow_spec_id self.spec_version = spec_version self.is_latest_spec = is_latest_spec diff --git a/crc/static/bpmn/data_security_plan/data_security_plan.bpmn b/crc/static/bpmn/data_security_plan/data_security_plan.bpmn index b24e6ea5..fc6704fa 100644 --- a/crc/static/bpmn/data_security_plan/data_security_plan.bpmn +++ b/crc/static/bpmn/data_security_plan/data_security_plan.bpmn @@ -1,5 +1,5 @@ - + SequenceFlow_100w7co @@ -372,7 +372,7 @@ SequenceFlow_0nc6lcs SequenceFlow_0gp2pjm - + @@ -389,7 +389,7 @@ Indicate all the possible formats in which you will transmit your data outside o - SequenceFlow_0gp2pjm + Flow_0cpwkms SequenceFlow_0mgwas4 @@ -623,222 +623,339 @@ Indicate all the possible formats in which you will collect or receive your orig SequenceFlow_0blyor8 SequenceFlow_1oq4w2h + + SequenceFlow_0gp2pjm + Flow_0cpwkms + + + + + > Instructions +o Hippa Instructions +o Hippa Indentifiers +o Vuew Definitions and Instructions +o Paper Documents +o Emailed to UVA Personnel +o EMC (EPIC) +o UVA Approvled eCRF +o UVA Servers +o Web or Cloud Server +o Individual Use Devices +o Device Details +0 Outside of UVA + +o Outside of UVA? +     o Yes  +           o Email Methods +           o Data Management +           o Transmission Method +           o Generate DSP  +    o No +           o Generate DSP + + + + *  Instructions +* Hippa Instructions +* Hippa Indentifiers +o Vuew Definitions and Instructions +>> Paper Documents +> Emailed to UVA Personnel +> EMC (EPIC) +> UVA Approvled eCRF +> UVA Servers +> Web or Cloud Server +o Individual Use Devices +o Device Details +o Outside of UVA + +o Outside of UVA? +     o Yes  +           o Email Methods +           o Data Management +           o Transmission Method +           o Generate DSP  +    o No +           o Generate DSP + + + + * Instructions +* Hippa Instructions +* Hippa Indentifiers +* View Definitions and Instructions + + +* Paper Documents (Parallel creates spaces) +* Emailed to UVA Personnel +* EMC (EPIC) +* UVA Approvled eCRF +* UVA Servers +* Web or Cloud Server +* Individual Use Devices + +o Device Details (MultiInstance Indents, Parallel creates spaces)) + > Desktop + >> Laptop + > Cell Phone + > Other + +o Outside of UVA + +o Outside of UVA? +     o Yes  +           o Email Methods +           o Data Management +           o Transmission Method +           o Generate DSP  +    o No +           o Generate DSP + + + + + + + + + + + - - + + - - - - + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - + - - - + + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - + + - - + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + + + + + - + - + - + - + - + - + - - - - - - - + - - - - - - - + - + - + - + - + - + - + - + - + - + - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/data/multi_instance/multi_instance.bpmn b/tests/data/multi_instance/multi_instance.bpmn index 834a6956..d53f7b17 100644 --- a/tests/data/multi_instance/multi_instance.bpmn +++ b/tests/data/multi_instance/multi_instance.bpmn @@ -8,8 +8,8 @@ Flow_0ugjw69 - - + + # Please provide addtional information about: ## Investigator ID: {{investigator.NETBADGEID}} ## Role: {{investigator.INVESTIGATORTYPEFULL}} @@ -25,7 +25,7 @@ Flow_0ugjw69 - + Flow_0t6p1sb SequenceFlow_1p568pp @@ -58,7 +58,7 @@ - + diff --git a/tests/test_tasks_api.py b/tests/test_tasks_api.py index 8c9a3dc3..76342012 100644 --- a/tests/test_tasks_api.py +++ b/tests/test_tasks_api.py @@ -290,8 +290,7 @@ class TestTasksApi(BaseTest): # get the first form in the two form workflow. tasks = self.get_workflow_api(workflow).user_tasks - self.assertEquals("JustAKey", tasks[0].properties[0]['id']) - self.assertEquals("JustAValue", tasks[0].properties[0]['value']) + self.assertEquals("JustAValue", tasks[0].properties['JustAKey']) @patch('crc.services.protocol_builder.requests.get') From b63ee8159e1e077a9604bc34e14d68e15c7207e0 Mon Sep 17 00:00:00 2001 From: Dan Funk Date: Thu, 14 May 2020 17:13:47 -0400 Subject: [PATCH 3/5] We now only return the ready user tasks, not all tasks, and even then the ready user tasks don't come back with the forms and details, just the bare minimum. Speeds things up considerably, and most of this information wasn't used anyway. --- crc/api/workflow.py | 4 ++-- crc/services/workflow_service.py | 6 ++++++ example_data.py | 11 +++-------- tests/test_tasks_api.py | 17 +++-------------- 4 files changed, 14 insertions(+), 24 deletions(-) diff --git a/crc/api/workflow.py b/crc/api/workflow.py index 49cd2fc1..d1ad6d72 100644 --- a/crc/api/workflow.py +++ b/crc/api/workflow.py @@ -84,8 +84,8 @@ def delete_workflow_specification(spec_id): def __get_workflow_api_model(processor: WorkflowProcessor): - spiff_tasks = processor.get_all_user_tasks() - user_tasks = [WorkflowService.spiff_task_to_api_task(t, add_docs_and_forms=True) for t in spiff_tasks] + spiff_tasks = processor.get_ready_user_tasks() + user_tasks = [WorkflowService.spiff_task_to_api_task(t, add_docs_and_forms=False) for t in spiff_tasks] workflow_api = WorkflowApi( id=processor.get_workflow_id(), status=processor.get_status(), diff --git a/crc/services/workflow_service.py b/crc/services/workflow_service.py index bc8f47a5..f5d39ca9 100644 --- a/crc/services/workflow_service.py +++ b/crc/services/workflow_service.py @@ -118,8 +118,14 @@ class WorkflowService(object): for field in task.form.fields: WorkflowService.process_options(spiff_task, field) task.documentation = WorkflowService._process_documentation(spiff_task) + + # All ready tasks should have a valid name, and this can be computed for + # some tasks, particularly multi-instance tasks that all have the same spec + # but need different labels. + if spiff_task.state == SpiffTask.READY: task.props = WorkflowService._process_properties(spiff_task, props) + return task @staticmethod diff --git a/example_data.py b/example_data.py index 7cf8246b..22e6f95b 100644 --- a/example_data.py +++ b/example_data.py @@ -1,16 +1,11 @@ -import datetime +import glob import glob import os -import xml.etree.ElementTree as ElementTree from crc import app, db, session -from crc.models.file import FileType, FileModel, FileDataModel, CONTENT_TYPES -from crc.models.study import StudyModel -from crc.models.user import UserModel +from crc.models.file import CONTENT_TYPES from crc.models.workflow import WorkflowSpecModel, WorkflowSpecCategoryModel from crc.services.file_service import FileService -from crc.services.workflow_processor import WorkflowProcessor -from crc.models.protocol_builder import ProtocolBuilderStatus class ExampleDataLoader: @@ -19,7 +14,7 @@ class ExampleDataLoader: session.flush() # Clear out any transactions before deleting it all to avoid spurious errors. for table in reversed(db.metadata.sorted_tables): session.execute(table.delete()) - session.flush() + session.flush() def load_all(self): diff --git a/tests/test_tasks_api.py b/tests/test_tasks_api.py index 76342012..53b48bd0 100644 --- a/tests/test_tasks_api.py +++ b/tests/test_tasks_api.py @@ -101,7 +101,7 @@ class TestTasksApi(BaseTest): # get the first form in the two form workflow. workflow_api = self.get_workflow_api(workflow) self.assertEqual('two_forms', workflow_api.workflow_spec_id) - self.assertEqual(2, len(workflow_api.user_tasks)) + self.assertEqual(1, len(workflow_api.user_tasks)) self.assertIsNotNone(workflow_api.next_task['form']) self.assertEqual("UserTask", workflow_api.next_task['type']) self.assertEqual("StepOne", workflow_api.next_task['name']) @@ -334,7 +334,7 @@ class TestTasksApi(BaseTest): workflow = self.create_workflow('subprocess') tasks = self.get_workflow_api(workflow).user_tasks - self.assertEquals(2, len(tasks)) + self.assertEquals(1, len(tasks)) self.assertEquals("UserTask", tasks[0].type) self.assertEquals("Activity_A", tasks[0].name) self.assertEquals("My Sub Process", tasks[0].process_name) @@ -412,19 +412,8 @@ class TestTasksApi(BaseTest): for i in random.sample(range(9), 9): self.complete_form(workflow, tasks[i], {"investigator":{"email": "dhf8r@virginia.edu"}}) - tasks = self.get_workflow_api(workflow).user_tasks + #tasks = self.get_workflow_api(workflow).user_tasks workflow = self.get_workflow_api(workflow) self.assertEquals(WorkflowStatus.complete, workflow.status) - # def test_parent_task_set_on_tasks(self): - # self.load_example_data() - # workflow = self.create_workflow('exclusive_gateway') - # - # # Start the workflow. - # workflow = self.get_workflow_api(workflow) - # self.assertEquals(None, workflow.previous_task) - # self.complete_form(workflow, workflow.next_task, {"has_bananas": True}) - # workflow = self.get_workflow_api(workflow) - # self.assertEquals('Task_Num_Bananas', workflow.next_task['name']) - # self.assertEquals('has_bananas', workflow.previous_task['name']) From 53255ef35ed2ee32ec1a1f6e9d96464722bff079 Mon Sep 17 00:00:00 2001 From: Dan Funk Date: Fri, 15 May 2020 15:54:53 -0400 Subject: [PATCH 4/5] massive overhaul of the Workflow API endpoint. No Previous Task, No Last Task, No Task List. Just the current task, and the Navigation. Use the token endpoint to set the current task, even if it is a "READY" task in the api. Previous Task can be set by identifying the prior task in the Navigation (I'm hoping) Prefering camel case to snake case on all new apis. Maybe clean the rest up later. --- Pipfile.lock | 51 +++-- crc/api.yml | 34 ++- crc/api/workflow.py | 46 +++- crc/models/api_models.py | 61 ++++-- crc/services/workflow_processor.py | 16 +- crc/services/workflow_service.py | 37 ++-- .../exclusive_gateway/exclusive_gateway.bpmn | 69 +++--- tests/test_tasks_api.py | 205 +++++++++--------- .../test_workflow_processor_multi_instance.py | 16 +- 9 files changed, 309 insertions(+), 226 deletions(-) diff --git a/Pipfile.lock b/Pipfile.lock index 97a58a73..58285c07 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -783,31 +783,40 @@ "spiffworkflow": { "editable": true, "git": "https://github.com/sartography/SpiffWorkflow.git", - "ref": "635afdec60dcb46dcef8c47cd4ba455cb0877f8b" + "ref": "661902387021f7130ae27fa35434eb3b2c138610" }, "sqlalchemy": { "hashes": [ - "sha256:083e383a1dca8384d0ea6378bd182d83c600ed4ff4ec8247d3b2442cf70db1ad", - "sha256:0a690a6486658d03cc6a73536d46e796b6570ac1f8a7ec133f9e28c448b69828", - "sha256:114b6ace30001f056e944cebd46daef38fdb41ebb98f5e5940241a03ed6cad43", - "sha256:128f6179325f7597a46403dde0bf148478f868df44841348dfc8d158e00db1f9", - "sha256:13d48cd8b925b6893a4e59b2dfb3e59a5204fd8c98289aad353af78bd214db49", - "sha256:211a1ce7e825f7142121144bac76f53ac28b12172716a710f4bf3eab477e730b", - "sha256:2dc57ee80b76813759cccd1a7affedf9c4dbe5b065a91fb6092c9d8151d66078", - "sha256:3e625e283eecc15aee5b1ef77203bfb542563fa4a9aa622c7643c7b55438ff49", - "sha256:43078c7ec0457387c79b8d52fff90a7ad352ca4c7aa841c366238c3e2cf52fdf", - "sha256:5b1bf3c2c2dca738235ce08079783ef04f1a7fc5b21cf24adaae77f2da4e73c3", - "sha256:6056b671aeda3fc451382e52ab8a753c0d5f66ef2a5ccc8fa5ba7abd20988b4d", - "sha256:68d78cf4a9dfade2e6cf57c4be19f7b82ed66e67dacf93b32bb390c9bed12749", - "sha256:7025c639ce7e170db845e94006cf5f404e243e6fc00d6c86fa19e8ad8d411880", - "sha256:7224e126c00b8178dfd227bc337ba5e754b197a3867d33b9f30dc0208f773d70", - "sha256:7d98e0785c4cd7ae30b4a451416db71f5724a1839025544b4edbd92e00b91f0f", - "sha256:8d8c21e9d4efef01351bf28513648ceb988031be4159745a7ad1b3e28c8ff68a", - "sha256:bbb545da054e6297242a1bb1ba88e7a8ffb679f518258d66798ec712b82e4e07", - "sha256:d00b393f05dbd4ecd65c989b7f5a81110eae4baea7a6a4cdd94c20a908d1456e", - "sha256:e18752cecaef61031252ca72031d4d6247b3212ebb84748fc5d1a0d2029c23ea" + "sha256:128bc917ed20d78143a45024455ff0aed7d3b96772eba13d5dbaf9cc57e5c41b", + "sha256:156a27548ba4e1fed944ff9fcdc150633e61d350d673ae7baaf6c25c04ac1f71", + "sha256:27e2efc8f77661c9af2681755974205e7462f1ae126f498f4fe12a8b24761d15", + "sha256:2a12f8be25b9ea3d1d5b165202181f2b7da4b3395289000284e5bb86154ce87c", + "sha256:31c043d5211aa0e0773821fcc318eb5cbe2ec916dfbc4c6eea0c5188971988eb", + "sha256:65eb3b03229f684af0cf0ad3bcc771970c1260a82a791a8d07bffb63d8c95bcc", + "sha256:6cd157ce74a911325e164441ff2d9b4e244659a25b3146310518d83202f15f7a", + "sha256:703c002277f0fbc3c04d0ae4989a174753a7554b2963c584ce2ec0cddcf2bc53", + "sha256:869bbb637de58ab0a912b7f20e9192132f9fbc47fc6b5111cd1e0f6cdf5cf9b0", + "sha256:8a0e0cd21da047ea10267c37caf12add400a92f0620c8bc09e4a6531a765d6d7", + "sha256:8d01e949a5d22e5c4800d59b50617c56125fc187fbeb8fa423e99858546de616", + "sha256:925b4fe5e7c03ed76912b75a9a41dfd682d59c0be43bce88d3b27f7f5ba028fb", + "sha256:9cb1819008f0225a7c066cac8bb0cf90847b2c4a6eb9ebb7431dbd00c56c06c5", + "sha256:a87d496884f40c94c85a647c385f4fd5887941d2609f71043e2b73f2436d9c65", + "sha256:a9030cd30caf848a13a192c5e45367e3c6f363726569a56e75dc1151ee26d859", + "sha256:a9e75e49a0f1583eee0ce93270232b8e7bb4b1edc89cc70b07600d525aef4f43", + "sha256:b50f45d0e82b4562f59f0e0ca511f65e412f2a97d790eea5f60e34e5f1aabc9a", + "sha256:b7878e59ec31f12d54b3797689402ee3b5cfcb5598f2ebf26491732758751908", + "sha256:ce1ddaadee913543ff0154021d31b134551f63428065168e756d90bdc4c686f5", + "sha256:ce2646e4c0807f3461be0653502bb48c6e91a5171d6e450367082c79e12868bf", + "sha256:ce6c3d18b2a8ce364013d47b9cad71db815df31d55918403f8db7d890c9d07ae", + "sha256:e4e2664232005bd306f878b0f167a31f944a07c4de0152c444f8c61bbe3cfb38", + "sha256:e8aa395482728de8bdcca9cc0faf3765ab483e81e01923aaa736b42f0294f570", + "sha256:eb4fcf7105bf071c71068c6eee47499ab8d4b8f5a11fc35147c934f0faa60f23", + "sha256:ed375a79f06cad285166e5be74745df1ed6845c5624aafadec4b7a29c25866ef", + "sha256:f35248f7e0d63b234a109dd72fbfb4b5cb6cb6840b221d0df0ecbf54ab087654", + "sha256:f502ef245c492b391e0e23e94cba030ab91722dcc56963c85bfd7f3441ea2bbe", + "sha256:fe01bac7226499aedf472c62fa3b85b2c619365f3f14dd222ffe4f3aa91e5f98" ], - "version": "==1.3.16" + "version": "==1.3.17" }, "swagger-ui-bundle": { "hashes": [ diff --git a/crc/api.yml b/crc/api.yml index 2e323598..c9542885 100644 --- a/crc/api.yml +++ b/crc/api.yml @@ -943,12 +943,6 @@ components: type: array items: $ref: "#/components/schemas/NavigationItem" - user_tasks: - type: array - items: - $ref: "#/components/schemas/Task" - last_task: - $ref: "#/components/schemas/Task" next_task: $ref: "#/components/schemas/Task" workflow_spec_id: @@ -994,6 +988,19 @@ components: $ref: "#/components/schemas/Form" documentation: type: string + data: + type: object + multiInstanceType: + type: enum + enum: ['none', 'looping', 'parallel', 'sequential'] + multiInstanceCount: + type: number + multiInstanceIndex: + type: number + processName: + type: string + properties: + type: object example: id: study_identification name: Study Identification @@ -1170,6 +1177,10 @@ components: type: number format: integer example: 5 + task_id: + type: string + format: uuid + example: "1234123uuid1234" name: type: string example: "Task_Has_bananas" @@ -1177,7 +1188,7 @@ components: type: string example: "Has Bananas?" backtracks: - type: booean + type: boolean example: false level: type: integer @@ -1185,14 +1196,19 @@ components: indent: type: integer example: 2 - childcount: + childCount: type: integer example: 4 state: type: enum enum: ['FUTURE', 'WAITING', 'READY', 'CANCELLED', 'COMPLETED','LIKELY','MAYBE'] readOnly: true - + isDecision: + type: boolean + example: False + readOnly: true + task: + $ref: "#/components/schemas/Task" diff --git a/crc/api/workflow.py b/crc/api/workflow.py index d1ad6d72..b8597da9 100644 --- a/crc/api/workflow.py +++ b/crc/api/workflow.py @@ -2,7 +2,7 @@ import uuid from crc import session from crc.api.common import ApiError, ApiErrorSchema -from crc.models.api_models import WorkflowApi, WorkflowApiSchema +from crc.models.api_models import WorkflowApi, WorkflowApiSchema, NavigationItem, NavigationItemSchema from crc.models.file import FileModel, LookupDataSchema from crc.models.workflow import WorkflowModel, WorkflowSpecModelSchema, WorkflowSpecModel, WorkflowSpecCategoryModel, \ WorkflowSpecCategoryModelSchema @@ -83,17 +83,36 @@ def delete_workflow_specification(spec_id): session.commit() -def __get_workflow_api_model(processor: WorkflowProcessor): - spiff_tasks = processor.get_ready_user_tasks() - user_tasks = [WorkflowService.spiff_task_to_api_task(t, add_docs_and_forms=False) for t in spiff_tasks] +def __get_workflow_api_model(processor: WorkflowProcessor, next_task = None): + """Returns an API model representing the state of the current workflow, if requested, and + possible, next_task is set to the current_task.""" + + nav_dict = processor.bpmn_workflow.get_nav_list() + navigation = [] + for nav_item in nav_dict: + spiff_task = processor.bpmn_workflow.get_task(nav_item['task_id']) + if 'description' in nav_item: + nav_item['title'] = nav_item.pop('description') + else: + nav_item['title'] = "" + if spiff_task: + nav_item['task'] = WorkflowService.spiff_task_to_api_task(spiff_task, add_docs_and_forms=False) + nav_item['title'] = nav_item['task'].title # Prefer the task title. + else: + nav_item['task'] = None + nav_item['childCount'] = nav_item.pop('child_count') + if 'is_decision' in nav_item: + nav_item['isDecision'] = nav_item.pop('is_decision') + else: + nav_item['isDecision'] = False + + navigation.append(NavigationItem(**nav_item)) + NavigationItemSchema().dump(nav_item) workflow_api = WorkflowApi( id=processor.get_workflow_id(), status=processor.get_status(), - navigation=processor.bpmn_workflow.get_nav_list(), - last_task=WorkflowService.spiff_task_to_api_task(processor.bpmn_workflow.last_task), next_task=None, - previous_task=processor.previous_task(), - user_tasks=user_tasks, + navigation=navigation, workflow_spec_id=processor.workflow_spec_id, spec_version=processor.get_spec_version(), is_latest_spec=processor.get_spec_version() == processor.get_latest_version_string(processor.workflow_spec_id), @@ -101,7 +120,9 @@ def __get_workflow_api_model(processor: WorkflowProcessor): completed_tasks=processor.workflow_model.completed_tasks, last_updated=processor.workflow_model.last_updated ) - next_task = processor.next_task() + if not next_task: # The Next Task can be requested to be a certain task, useful for parallel tasks. + # This may or may not work, sometimes there is no next task to complete. + next_task = processor.next_task() if next_task: workflow_api.next_task = WorkflowService.spiff_task_to_api_task(next_task, add_docs_and_forms=True) @@ -118,19 +139,20 @@ def get_workflow(workflow_id, soft_reset=False, hard_reset=False): def delete_workflow(workflow_id): StudyService.delete_workflow(workflow_id) + def set_current_task(workflow_id, task_id): workflow_model = session.query(WorkflowModel).filter_by(id=workflow_id).first() processor = WorkflowProcessor(workflow_model) task_id = uuid.UUID(task_id) task = processor.bpmn_workflow.get_task(task_id) - if task.state != task.COMPLETED: + if task.state != task.COMPLETED and task.state != task.READY: raise ApiError("invalid_state", "You may not move the token to a task who's state is not " - "currently set to COMPLETE.") + "currently set to COMPLETE or READY.") task.reset_token(reset_data=False) # we could optionally clear the previous data. processor.save() WorkflowService.log_task_action(processor, task, WorkflowService.TASK_ACTION_TOKEN_RESET) - workflow_api_model = __get_workflow_api_model(processor) + workflow_api_model = __get_workflow_api_model(processor, task) return WorkflowApiSchema().dump(workflow_api_model) diff --git a/crc/models/api_models.py b/crc/models/api_models.py index f8704492..2222bc8d 100644 --- a/crc/models/api_models.py +++ b/crc/models/api_models.py @@ -15,6 +15,20 @@ class MultiInstanceType(enum.Enum): sequential = "sequential" +class NavigationItem(object): + def __init__(self, id, task_id, name, title, backtracks, level, indent, childCount, state, isDecision, task=None): + self.id = id + self.task_id = task_id + self.name = name, + self.title = title + self.backtracks = backtracks + self.level = level + self.indent = indent + self.childCount = childCount + self.state = state + self.isDecision = isDecision + self.task = task + class Task(object): ENUM_OPTIONS_FILE_PROP = "enum.options.file" @@ -24,7 +38,7 @@ class Task(object): def __init__(self, id, name, title, type, state, form, documentation, data, - mi_type, mi_count, mi_index, process_name, properties): + multiInstanceType, multiInstanceCount, multiInstanceIndex, processName, properties): self.id = id self.name = name self.title = title @@ -33,10 +47,10 @@ class Task(object): self.form = form self.documentation = documentation self.data = data - self.mi_type = mi_type # Some tasks have a repeat behavior. - self.mi_count = mi_count # This is the number of times the task could repeat. - self.mi_index = mi_index # And the index of the currently repeating task. - self.process_name = process_name + self.multiInstanceType = multiInstanceType # Some tasks have a repeat behavior. + self.multiInstanceCount = multiInstanceCount # This is the number of times the task could repeat. + self.multiInstanceIndex = multiInstanceIndex # And the index of the currently repeating task. + self.processName = processName self.properties = properties # Arbitrary extension properties from BPMN editor. @@ -55,7 +69,6 @@ class FormFieldPropertySchema(ma.Schema): "id", "value" ] - class FormFieldSchema(ma.Schema): class Meta: fields = [ @@ -75,31 +88,38 @@ class FormSchema(ma.Schema): class TaskSchema(ma.Schema): class Meta: - fields = ["id", "name", "title", "type", "state", "form", "documentation", "data", "mi_type", - "mi_count", "mi_index", "process_name", "properties"] + fields = ["id", "name", "title", "type", "state", "form", "documentation", "data", "multiInstanceType", + "multiInstanceCount", "multiInstanceIndex", "processName", "properties"] - mi_type = EnumField(MultiInstanceType) + multiInstanceType = EnumField(MultiInstanceType) documentation = marshmallow.fields.String(required=False, allow_none=True) form = marshmallow.fields.Nested(FormSchema, required=False, allow_none=True) title = marshmallow.fields.String(required=False, allow_none=True) - process_name = marshmallow.fields.String(required=False, allow_none=True) + processName = marshmallow.fields.String(required=False, allow_none=True) @marshmallow.post_load def make_task(self, data, **kwargs): return Task(**data) +class NavigationItemSchema(ma.Schema): + class Meta: + fields = ["id", "task_id", "name", "title", "backtracks", "level", "indent", "childCount", "state", + "isDecision", "task"] + unknown = INCLUDE + task = marshmallow.fields.Nested(TaskSchema, dump_only=True, required=False, allow_none=True) + backtracks = marshmallow.fields.String(required=False, allow_none=True) + title = marshmallow.fields.String(required=False, allow_none=True) + task_id = marshmallow.fields.String(required=False, allow_none=True) + + class WorkflowApi(object): - def __init__(self, id, status, navigation, user_tasks, last_task, next_task, previous_task, + def __init__(self, id, status, next_task, navigation, spec_version, is_latest_spec, workflow_spec_id, total_tasks, completed_tasks, last_updated): self.id = id self.status = status - self.navigation = navigation - self.user_tasks = user_tasks - self.last_task = last_task # The last task that was completed, may be different than previous. self.next_task = next_task # The next task that requires user input. - self.previous_task = previous_task # The opposite of next task. - + self.navigation = navigation self.workflow_spec_id = workflow_spec_id self.spec_version = spec_version self.is_latest_spec = is_latest_spec @@ -110,21 +130,20 @@ class WorkflowApi(object): class WorkflowApiSchema(ma.Schema): class Meta: model = WorkflowApi - fields = ["id", "status", "navigation", "user_tasks", "last_task", "next_task", "previous_task", + fields = ["id", "status", "next_task", "navigation", "workflow_spec_id", "spec_version", "is_latest_spec", "total_tasks", "completed_tasks", "last_updated"] unknown = INCLUDE status = EnumField(WorkflowStatus) - user_tasks = marshmallow.fields.List(marshmallow.fields.Nested(TaskSchema, dump_only=True)) - last_task = marshmallow.fields.Nested(TaskSchema, dump_only=True, required=False) next_task = marshmallow.fields.Nested(TaskSchema, dump_only=True, required=False) - previous_task = marshmallow.fields.Nested(TaskSchema, dump_only=True, required=False) + navigation = marshmallow.fields.List(marshmallow.fields.Nested(NavigationItemSchema, dump_only=True)) @marshmallow.post_load def make_workflow(self, data, **kwargs): - keys = ['id', 'status', 'navigation', 'user_tasks', 'last_task', 'next_task', 'previous_task', + keys = ['id', 'status', 'next_task', 'navigation', 'workflow_spec_id', 'spec_version', 'is_latest_spec', "total_tasks", "completed_tasks", "last_updated"] filtered_fields = {key: data[key] for key in keys} + filtered_fields['next_task'] = TaskSchema().make_task(data['next_task']) return WorkflowApi(**filtered_fields) diff --git a/crc/services/workflow_processor.py b/crc/services/workflow_processor.py index ce85b43d..7f06c47b 100644 --- a/crc/services/workflow_processor.py +++ b/crc/services/workflow_processor.py @@ -392,6 +392,17 @@ class WorkflowProcessor(object): def get_ready_user_tasks(self): return self.bpmn_workflow.get_ready_user_tasks() + def get_current_user_tasks(self): + """Return a list of all user tasks that are READY or + COMPLETE and are parallel to the READY Task.""" + ready_tasks = self.bpmn_workflow.get_ready_user_tasks() + additional_tasks = [] + if len(ready_tasks) > 0: + for child in ready_tasks[0].parent.children: + if child.state == SpiffTask.COMPLETED: + additional_tasks.append(child) + return ready_tasks + additional_tasks + def get_all_user_tasks(self): all_tasks = self.bpmn_workflow.get_tasks(SpiffTask.ANY_MASK) return [t for t in all_tasks if not self.bpmn_workflow._is_engine_task(t.task_spec)] @@ -425,5 +436,8 @@ class WorkflowProcessor(object): return process_elements[0].attrib['id'] - + def get_nav_item(self, task): + for nav_item in self.bpmn_workflow.get_nav_list(): + if nav_item['task_id'] == task.id: + return nav_item diff --git a/crc/services/workflow_service.py b/crc/services/workflow_service.py index f5d39ca9..750654f9 100644 --- a/crc/services/workflow_service.py +++ b/crc/services/workflow_service.py @@ -24,7 +24,6 @@ from SpiffWorkflow import Task as SpiffTask, WorkflowException class WorkflowService(object): - TASK_ACTION_COMPLETE = "Complete" TASK_ACTION_TOKEN_RESET = "Backwards Move" TASK_ACTION_HARD_RESET = "Restart (Hard)" @@ -54,7 +53,8 @@ class WorkflowService(object): tasks = bpmn_workflow.get_tasks(SpiffTask.READY) for task in tasks: task_api = WorkflowService.spiff_task_to_api_task( - task, add_docs_and_forms=True) # Assure we try to process the documenation, and raise those errors. + task, + add_docs_and_forms=True) # Assure we try to process the documenation, and raise those errors. WorkflowProcessor.populate_form_with_random_data(task, task_api) task.complete() except WorkflowException as we: @@ -102,17 +102,19 @@ class WorkflowService(object): spiff_task.get_state_name(), None, "", - spiff_task.data, + {}, mi_type, info["mi_count"], info["mi_index"], - process_name=spiff_task.task_spec._wf_spec.description, - properties=props) + processName=spiff_task.task_spec._wf_spec.description, + properties=props + ) # Only process the form and documentation if requested. # The task should be in a completed or a ready state, and should # not be a previously completed MI Task. if add_docs_and_forms: + task.data = spiff_task.data if hasattr(spiff_task.task_spec, "form"): task.form = spiff_task.task_spec.form for field in task.form.fields: @@ -123,20 +125,28 @@ class WorkflowService(object): # some tasks, particularly multi-instance tasks that all have the same spec # but need different labels. if spiff_task.state == SpiffTask.READY: - task.props = WorkflowService._process_properties(spiff_task, props) + task.properties = WorkflowService._process_properties(spiff_task, props) + # Replace the title with the display name if it is set in the task properties, + # otherwise strip off the first word of the task, as that should be following + # a BPMN standard, and should not be included in the display. + if task.properties and "display_name" in task.properties: + task.title = task.properties['display_name'] + elif task.title and ' ' in task.title: + task.title = task.title.partition(' ')[2] return task @staticmethod def _process_properties(spiff_task, props): """Runs all the property values through the Jinja2 processor to inject data.""" - for k,v in props.items(): + for k, v in props.items(): try: template = Template(v) props[k] = template.render(**spiff_task.data) except jinja2.exceptions.TemplateError as ue: app.logger.error("Failed to process task property %s " % str(ue)) + return props @staticmethod def _process_documentation(spiff_task): @@ -161,7 +171,7 @@ class WorkflowService(object): return template.render(**spiff_task.data) except jinja2.exceptions.TemplateError as ue: -# return "Error processing template. %s" % ue.message + # return "Error processing template. %s" % ue.message raise ApiError(code="template_error", message="Error processing template for task %s: %s" % (spiff_task.task_spec.name, str(ue)), status_code=500) # TODO: Catch additional errors and report back. @@ -278,14 +288,11 @@ class WorkflowService(object): task_title=task.title, task_type=str(task.type), task_state=task.state, - mi_type=task.mi_type.value, # Some tasks have a repeat behavior. - mi_count=task.mi_count, # This is the number of times the task could repeat. - mi_index=task.mi_index, # And the index of the currently repeating task. - process_name=task.process_name, + mi_type=task.multiInstanceType.value, # Some tasks have a repeat behavior. + mi_count=task.multiInstanceCount, # This is the number of times the task could repeat. + mi_index=task.multiInstanceIndex, # And the index of the currently repeating task. + process_name=task.processName, date=datetime.now(), ) db.session.add(task_event) db.session.commit() - - - diff --git a/tests/data/exclusive_gateway/exclusive_gateway.bpmn b/tests/data/exclusive_gateway/exclusive_gateway.bpmn index 26ba0e7b..1c7e55fe 100644 --- a/tests/data/exclusive_gateway/exclusive_gateway.bpmn +++ b/tests/data/exclusive_gateway/exclusive_gateway.bpmn @@ -1,11 +1,11 @@ - + SequenceFlow_1pnq3kg - + @@ -15,7 +15,7 @@ SequenceFlow_1lmkn99 - + SequenceFlow_1lmkn99 SequenceFlow_Yes_Bananas SequenceFlow_No_Bananas @@ -55,29 +55,13 @@ - - - - - - + + + - - - - - - - - - - - - - - - - + + + @@ -87,6 +71,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -96,17 +107,9 @@ - - - - - - - - diff --git a/tests/test_tasks_api.py b/tests/test_tasks_api.py index 53b48bd0..f0ddd623 100644 --- a/tests/test_tasks_api.py +++ b/tests/test_tasks_api.py @@ -66,10 +66,15 @@ class TestTasksApi(BaseTest): self.assertEquals(task_in.title, event.task_title) self.assertEquals(task_in.type, event.task_type) self.assertEquals("COMPLETED", event.task_state) - self.assertEquals(task_in.mi_type.value, event.mi_type) - self.assertEquals(task_in.mi_count, event.mi_count) - self.assertEquals(task_in.mi_index, event.mi_index) - self.assertEquals(task_in.process_name, event.process_name) + # Not sure what vodoo is happening inside of marshmallow to get me in this state. + if isinstance(task_in.multiInstanceType,MultiInstanceType): + self.assertEquals(task_in.multiInstanceType.value, event.mi_type) + else: + self.assertEquals(task_in.multiInstanceType, event.mi_type) + + self.assertEquals(task_in.multiInstanceCount, event.mi_count) + self.assertEquals(task_in.multiInstanceIndex, event.mi_index) + self.assertEquals(task_in.processName, event.process_name) self.assertIsNotNone(event.date) @@ -82,9 +87,9 @@ class TestTasksApi(BaseTest): workflow = self.create_workflow('random_fact') workflow = self.get_workflow_api(workflow) task = workflow.next_task - self.assertEqual("Task_User_Select_Type", task['name']) - self.assertEqual(3, len(task['form']["fields"][0]["options"])) - self.assertIsNotNone(task['documentation']) + self.assertEqual("Task_User_Select_Type", task.name) + self.assertEqual(3, len(task.form["fields"][0]["options"])) + self.assertIsNotNone(task.documentation) expected_docs = """# h1 Heading 8-) ## h2 Heading ### h3 Heading @@ -92,7 +97,7 @@ class TestTasksApi(BaseTest): ##### h5 Heading ###### h6 Heading """ - self.assertTrue(str.startswith(task['documentation'], expected_docs)) + self.assertTrue(str.startswith(task.documentation, expected_docs)) def test_two_forms_task(self): # Set up a new workflow @@ -101,44 +106,44 @@ class TestTasksApi(BaseTest): # get the first form in the two form workflow. workflow_api = self.get_workflow_api(workflow) self.assertEqual('two_forms', workflow_api.workflow_spec_id) - self.assertEqual(1, len(workflow_api.user_tasks)) - self.assertIsNotNone(workflow_api.next_task['form']) - self.assertEqual("UserTask", workflow_api.next_task['type']) - self.assertEqual("StepOne", workflow_api.next_task['name']) - self.assertEqual(1, len(workflow_api.next_task['form']['fields'])) + self.assertEqual(2, len(workflow_api.navigation)) + self.assertIsNotNone(workflow_api.next_task.form) + self.assertEqual("UserTask", workflow_api.next_task.type) + self.assertEqual("StepOne", workflow_api.next_task.name) + self.assertEqual(1, len(workflow_api.next_task.form['fields'])) # Complete the form for Step one and post it. - self.complete_form(workflow, workflow_api.user_tasks[0], {"color": "blue"}) + self.complete_form(workflow, workflow_api.next_task, {"color": "blue"}) # Get the next Task workflow_api = self.get_workflow_api(workflow) - self.assertEqual("StepTwo", workflow_api.next_task['name']) + self.assertEqual("StepTwo", workflow_api.next_task.name) # Get all user Tasks and check that the data have been saved - for task in workflow_api.user_tasks: - self.assertIsNotNone(task.data) - for val in task.data.values(): - self.assertIsNotNone(val) + task = workflow_api.next_task + self.assertIsNotNone(task.data) + for val in task.data.values(): + self.assertIsNotNone(val) def test_error_message_on_bad_gateway_expression(self): self.load_example_data() workflow = self.create_workflow('exclusive_gateway') # get the first form in the two form workflow. - tasks = self.get_workflow_api(workflow).user_tasks - self.complete_form(workflow, tasks[0], {"has_bananas": True}) + task = self.get_workflow_api(workflow).next_task + self.complete_form(workflow, task, {"has_bananas": True}) def test_workflow_with_parallel_forms(self): self.load_example_data() workflow = self.create_workflow('exclusive_gateway') # get the first form in the two form workflow. - tasks = self.get_workflow_api(workflow).user_tasks - self.complete_form(workflow, tasks[0], {"has_bananas": True}) + task = self.get_workflow_api(workflow).next_task + self.complete_form(workflow, task, {"has_bananas": True}) # Get the next Task workflow_api = self.get_workflow_api(workflow) - self.assertEqual("Task_Num_Bananas", workflow_api.next_task['name']) + self.assertEqual("Task_Num_Bananas", workflow_api.next_task.name) def test_navigation_with_parallel_forms(self): self.load_example_data() @@ -148,30 +153,14 @@ class TestTasksApi(BaseTest): workflow_api = self.get_workflow_api(workflow) self.assertIsNotNone(workflow_api.navigation) - - - def test_get_workflow_contains_details_about_last_task_data(self): - self.load_example_data() - workflow = self.create_workflow('exclusive_gateway') - - # get the first form in the two form workflow. - tasks = self.get_workflow_api(workflow).user_tasks - workflow_api = self.complete_form(workflow, tasks[0], {"has_bananas": True}) - - self.assertIsNotNone(workflow_api.last_task) - self.assertEqual({"has_bananas": True}, workflow_api.last_task['data']) - - def test_get_workflow_contains_reference_to_last_task_and_next_task(self): - self.load_example_data() - workflow = self.create_workflow('exclusive_gateway') - - # get the first form in the two form workflow. - tasks = self.get_workflow_api(workflow).user_tasks - self.complete_form(workflow, tasks[0], {"has_bananas": True}) - - workflow_api = self.get_workflow_api(workflow) - self.assertIsNotNone(workflow_api.last_task) - self.assertIsNotNone(workflow_api.next_task) + nav = workflow_api.navigation + self.assertEquals(5, len(nav)) + self.assertEquals("Do You Have Bananas", nav[0]['title']) + self.assertEquals("READY", nav[0]['state']) + self.assertEquals("Bananas?", nav[1]['title']) + self.assertEquals("FUTURE", nav[1]['state']) + self.assertEquals("yes", nav[2]['title']) + self.assertEquals("NOOP", nav[2]['state']) def test_document_added_to_workflow_shows_up_in_file_list(self): @@ -179,7 +168,7 @@ class TestTasksApi(BaseTest): self.create_reference_document() workflow = self.create_workflow('docx') # get the first form in the two form workflow. - tasks = self.get_workflow_api(workflow).user_tasks + task = self.get_workflow_api(workflow).next_task data = { "full_name": "Buck of the Wild", "date": "5/1/2020", @@ -187,9 +176,9 @@ class TestTasksApi(BaseTest): "company": "In the company of wolves", "last_name": "Mr. Wolf" } - workflow_api = self.complete_form(workflow, tasks[0], data) + workflow_api = self.complete_form(workflow, task, data) self.assertIsNotNone(workflow_api.next_task) - self.assertEqual("EndEvent_0evb22x", workflow_api.next_task['name']) + self.assertEqual("EndEvent_0evb22x", workflow_api.next_task.name) self.assertTrue(workflow_api.status == WorkflowStatus.complete) rv = self.app.get('/v1.0/file?workflow_id=%i' % workflow.id, headers=self.logged_in_headers()) self.assert_success(rv) @@ -207,14 +196,14 @@ class TestTasksApi(BaseTest): workflow = self.create_workflow('random_fact') workflow_api = self.get_workflow_api(workflow) task = workflow_api.next_task - self.assertEqual("Task_User_Select_Type", task['name']) - self.assertEqual(3, len(task['form']["fields"][0]["options"])) - self.assertIsNotNone(task['documentation']) - self.complete_form(workflow, workflow_api.user_tasks[0], {"type": "norris"}) + self.assertEqual("Task_User_Select_Type", task.name) + self.assertEqual(3, len(task.form["fields"][0]["options"])) + self.assertIsNotNone(task.documentation) + self.complete_form(workflow, workflow_api.next_task, {"type": "norris"}) workflow_api = self.get_workflow_api(workflow) - self.assertEqual("EndEvent_0u1cgrf", workflow_api.next_task['name']) - self.assertIsNotNone(workflow_api.next_task['documentation']) - self.assertTrue("norris" in workflow_api.next_task['documentation']) + self.assertEqual("EndEvent_0u1cgrf", workflow_api.next_task.name) + self.assertIsNotNone(workflow_api.next_task.documentation) + self.assertTrue("norris" in workflow_api.next_task.documentation) def test_load_workflow_from_outdated_spec(self): @@ -222,7 +211,7 @@ class TestTasksApi(BaseTest): self.load_example_data() workflow = self.create_workflow('two_forms') workflow_api = self.get_workflow_api(workflow) - self.complete_form(workflow, workflow_api.user_tasks[0], {"color": "blue"}) + self.complete_form(workflow, workflow_api.next_task, {"color": "blue"}) self.assertTrue(workflow_api.is_latest_spec) # Modify the specification, with a major change that alters the flow and can't be deserialized @@ -249,7 +238,7 @@ class TestTasksApi(BaseTest): self.load_example_data() workflow = self.create_workflow('two_forms') workflow_api = self.get_workflow_api(workflow) - self.complete_form(workflow, workflow_api.user_tasks[0], {"color": "blue"}) + self.complete_form(workflow, workflow_api.next_task, {"color": "blue"}) self.assertTrue(workflow_api.is_latest_spec) # Modify the specification, with a major change that alters the flow and can't be deserialized @@ -275,22 +264,22 @@ class TestTasksApi(BaseTest): workflow = self.create_workflow('manual_task_with_external_documentation') # get the first form in the two form workflow. - tasks = self.get_workflow_api(workflow).user_tasks - workflow_api = self.complete_form(workflow, tasks[0], {"name": "Dan"}) + task = self.get_workflow_api(workflow).next_task + workflow_api = self.complete_form(workflow, task, {"name": "Dan"}) workflow = self.get_workflow_api(workflow) - self.assertEquals('Task_Manual_One', workflow.next_task['name']) - self.assertEquals('ManualTask', workflow_api.next_task['type']) - self.assertTrue('Markdown' in workflow_api.next_task['documentation']) - self.assertTrue('Dan' in workflow_api.next_task['documentation']) + self.assertEquals('Task_Manual_One', workflow.next_task.name) + self.assertEquals('ManualTask', workflow_api.next_task.type) + self.assertTrue('Markdown' in workflow_api.next_task.documentation) + self.assertTrue('Dan' in workflow_api.next_task.documentation) def test_bpmn_extension_properties_are_populated(self): self.load_example_data() workflow = self.create_workflow('manual_task_with_external_documentation') # get the first form in the two form workflow. - tasks = self.get_workflow_api(workflow).user_tasks - self.assertEquals("JustAValue", tasks[0].properties['JustAKey']) + task = self.get_workflow_api(workflow).next_task + self.assertEquals("JustAValue", task.properties['JustAKey']) @patch('crc.services.protocol_builder.requests.get') @@ -303,14 +292,15 @@ class TestTasksApi(BaseTest): workflow = self.create_workflow('multi_instance') # get the first form in the two form workflow. - tasks = self.get_workflow_api(workflow).user_tasks - self.assertEquals(1, len(tasks)) - self.assertEquals("UserTask", tasks[0].type) - self.assertEquals(MultiInstanceType.sequential, tasks[0].mi_type) - self.assertEquals(9, tasks[0].mi_count) + workflow = self.get_workflow_api(workflow) + navigation = self.get_workflow_api(workflow).navigation + self.assertEquals(4, len(navigation)) # Start task, form_task, multi_task, end task + self.assertEquals("UserTask", workflow.next_task.type) + self.assertEquals(MultiInstanceType.sequential.value, workflow.next_task.multiInstanceType) + self.assertEquals(9, workflow.next_task.multiInstanceCount) # Assure that the names for each task are properly updated, so they aren't all the same. - self.assertEquals("Primary Investigator", tasks[0].properties['display_name']) + self.assertEquals("Primary Investigator", workflow.next_task.properties['display_name']) def test_lookup_endpoint_for_task_field_enumerations(self): @@ -319,9 +309,9 @@ class TestTasksApi(BaseTest): # get the first form in the two form workflow. workflow = self.get_workflow_api(workflow) task = workflow.next_task - field_id = task['form']['fields'][0]['id'] + field_id = task.form['fields'][0]['id'] rv = self.app.get('/v1.0/workflow/%i/task/%s/lookup/%s?query=%s&limit=5' % - (workflow.id, task['id'], field_id, 'c'), # All records with a word that starts with 'c' + (workflow.id, task.id, field_id, 'c'), # All records with a word that starts with 'c' headers=self.logged_in_headers(), content_type="application/json") self.assert_success(rv) @@ -333,17 +323,20 @@ class TestTasksApi(BaseTest): self.load_example_data() workflow = self.create_workflow('subprocess') - tasks = self.get_workflow_api(workflow).user_tasks - self.assertEquals(1, len(tasks)) - self.assertEquals("UserTask", tasks[0].type) - self.assertEquals("Activity_A", tasks[0].name) - self.assertEquals("My Sub Process", tasks[0].process_name) - workflow_api = self.complete_form(workflow, tasks[0], {"name": "Dan"}) - task = TaskSchema().load(workflow_api.next_task) + workflow_api = self.get_workflow_api(workflow) + navigation = workflow_api.navigation + task = workflow_api.next_task + + self.assertEquals(2, len(navigation)) + self.assertEquals("UserTask", task.type) + self.assertEquals("Activity_A", task.name) + self.assertEquals("My Sub Process", task.processName) + workflow_api = self.complete_form(workflow, task, {"name": "Dan"}) + task = workflow_api.next_task self.assertIsNotNone(task) self.assertEquals("Activity_B", task.name) - self.assertEquals("Sub Workflow Example", task.process_name) + self.assertEquals("Sub Workflow Example", task.processName) workflow_api = self.complete_form(workflow, task, {"name": "Dan"}) self.assertEquals(WorkflowStatus.complete, workflow_api.status) @@ -352,46 +345,42 @@ class TestTasksApi(BaseTest): workflow = self.create_workflow('exclusive_gateway') # Start the workflow. - tasks = self.get_workflow_api(workflow).user_tasks - self.complete_form(workflow, tasks[0], {"has_bananas": True}) + first_task = self.get_workflow_api(workflow).next_task + self.complete_form(workflow, first_task, {"has_bananas": True}) workflow = self.get_workflow_api(workflow) - self.assertEquals('Task_Num_Bananas', workflow.next_task['name']) + self.assertEquals('Task_Num_Bananas', workflow.next_task.name) # Trying to re-submit the initial task, and answer differently, should result in an error. - self.complete_form(workflow, tasks[0], {"has_bananas": False}, error_code="invalid_state") + self.complete_form(workflow, first_task, {"has_bananas": False}, error_code="invalid_state") # Go ahead and set the number of bananas. workflow = self.get_workflow_api(workflow) - task = TaskSchema().load(workflow.next_task) + task = workflow.next_task self.complete_form(workflow, task, {"num_bananas": 4}) # We are now at the end of the workflow. # Make the old task the current task. - rv = self.app.put('/v1.0/workflow/%i/task/%s/set_token' % (workflow.id, tasks[0].id), + rv = self.app.put('/v1.0/workflow/%i/task/%s/set_token' % (workflow.id, first_task.id), headers=self.logged_in_headers(), content_type="application/json") self.assert_success(rv) json_data = json.loads(rv.get_data(as_text=True)) workflow = WorkflowApiSchema().load(json_data) - # Assure the last task is the task we were on before the reset, - # and the Next Task is the one we just reset the token to be on. - self.assertEquals("Task_Has_Bananas", workflow.next_task['name']) - self.assertEquals("End", workflow.last_task['name']) + # Assure the Next Task is the one we just reset the token to be on. + self.assertEquals("Task_Has_Bananas", workflow.next_task.name) # Go ahead and get that workflow one more time, it should still be right. workflow = self.get_workflow_api(workflow) - # Assure the last task is the task we were on before the reset, - # and the Next Task is the one we just reset the token to be on. - self.assertEquals("Task_Has_Bananas", workflow.next_task['name']) - self.assertEquals("End", workflow.last_task['name']) + # Assure the Next Task is the one we just reset the token to be on. + self.assertEquals("Task_Has_Bananas", workflow.next_task.name) # The next task should be a different value. - self.complete_form(workflow, tasks[0], {"has_bananas": False}) + self.complete_form(workflow, workflow.next_task, {"has_bananas": False}) workflow = self.get_workflow_api(workflow) - self.assertEquals('Task_Why_No_Bananas', workflow.next_task['name']) + self.assertEquals('Task_Why_No_Bananas', workflow.next_task.name) @patch('crc.services.protocol_builder.requests.get') def test_parallel_multi_instance(self, mock_get): @@ -404,14 +393,18 @@ class TestTasksApi(BaseTest): self.load_example_data() workflow = self.create_workflow('multi_instance_parallel') - tasks = self.get_workflow_api(workflow).user_tasks - self.assertEquals(9, len(tasks)) - self.assertEquals("UserTask", tasks[0].type) - self.assertEquals("MutiInstanceTask", tasks[0].name) - self.assertEquals("Gather more information", tasks[0].title) + workflow_api = self.get_workflow_api(workflow) + self.assertEquals(12, len(workflow_api.navigation)) + ready_items = [nav for nav in workflow_api.navigation if nav['state'] == "READY"] + self.assertEquals(9, len(ready_items)) + + self.assertEquals("UserTask", workflow_api.next_task.type) + self.assertEquals("MutiInstanceTask",workflow_api.next_task.name) + self.assertEquals("more information", workflow_api.next_task.title) for i in random.sample(range(9), 9): - self.complete_form(workflow, tasks[i], {"investigator":{"email": "dhf8r@virginia.edu"}}) + task = TaskSchema().load(ready_items[i]['task']) + self.complete_form(workflow, task, {"investigator":{"email": "dhf8r@virginia.edu"}}) #tasks = self.get_workflow_api(workflow).user_tasks workflow = self.get_workflow_api(workflow) diff --git a/tests/test_workflow_processor_multi_instance.py b/tests/test_workflow_processor_multi_instance.py index f2252129..cad925fa 100644 --- a/tests/test_workflow_processor_multi_instance.py +++ b/tests/test_workflow_processor_multi_instance.py @@ -61,9 +61,9 @@ class TestWorkflowProcessorMultiInstance(BaseTest): self.assertEqual("MutiInstanceTask", task.get_name()) api_task = WorkflowService.spiff_task_to_api_task(task) - self.assertEquals(MultiInstanceType.sequential, api_task.mi_type) - self.assertEquals(3, api_task.mi_count) - self.assertEquals(1, api_task.mi_index) + self.assertEquals(MultiInstanceType.sequential, api_task.multiInstanceType) + self.assertEquals(3, api_task.multiInstanceCount) + self.assertEquals(1, api_task.multiInstanceIndex) task.update_data({"investigator":{"email":"asd3v@virginia.edu"}}) processor.complete_task(task) processor.do_engine_steps() @@ -72,8 +72,8 @@ class TestWorkflowProcessorMultiInstance(BaseTest): api_task = WorkflowService.spiff_task_to_api_task(task) self.assertEqual("MutiInstanceTask", api_task.name) task.update_data({"investigator":{"email":"asdf32@virginia.edu"}}) - self.assertEquals(3, api_task.mi_count) - self.assertEquals(2, api_task.mi_index) + self.assertEquals(3, api_task.multiInstanceCount) + self.assertEquals(2, api_task.multiInstanceIndex) processor.complete_task(task) processor.do_engine_steps() @@ -81,8 +81,8 @@ class TestWorkflowProcessorMultiInstance(BaseTest): api_task = WorkflowService.spiff_task_to_api_task(task) self.assertEqual("MutiInstanceTask", task.get_name()) task.update_data({"investigator":{"email":"dhf8r@virginia.edu"}}) - self.assertEquals(3, api_task.mi_count) - self.assertEquals(3, api_task.mi_index) + self.assertEquals(3, api_task.multiInstanceCount) + self.assertEquals(3, api_task.multiInstanceIndex) processor.complete_task(task) processor.do_engine_steps() task = processor.bpmn_workflow.last_task @@ -120,7 +120,7 @@ class TestWorkflowProcessorMultiInstance(BaseTest): self.assertEquals("asd3v", task.data["investigator"]["user_id"]) # The last of the tasks api_task = WorkflowService.spiff_task_to_api_task(task) - self.assertEquals(MultiInstanceType.parallel, api_task.mi_type) + self.assertEquals(MultiInstanceType.parallel, api_task.multiInstanceType) task.update_data({"investigator":{"email":"dhf8r@virginia.edu"}}) processor.complete_task(task) processor.do_engine_steps() From de435bd961636d952c3d3bb973b9a6d884a02561 Mon Sep 17 00:00:00 2001 From: Dan Funk Date: Fri, 15 May 2020 16:38:37 -0400 Subject: [PATCH 5/5] the heck with camel case, what the heck TypeScript? Get a grip. This is a python API. --- crc/api.yml | 12 ++++---- crc/api/workflow.py | 7 ++--- crc/models/api_models.py | 30 +++++++++---------- crc/services/workflow_service.py | 10 +++---- tests/test_tasks_api.py | 20 ++++++------- .../test_workflow_processor_multi_instance.py | 16 +++++----- 6 files changed, 46 insertions(+), 49 deletions(-) diff --git a/crc/api.yml b/crc/api.yml index c9542885..52abbde4 100644 --- a/crc/api.yml +++ b/crc/api.yml @@ -990,14 +990,14 @@ components: type: string data: type: object - multiInstanceType: + multi_instance_type: type: enum enum: ['none', 'looping', 'parallel', 'sequential'] - multiInstanceCount: + multi_instance_count: type: number - multiInstanceIndex: + multi_instance_index: type: number - processName: + process_name: type: string properties: type: object @@ -1196,14 +1196,14 @@ components: indent: type: integer example: 2 - childCount: + child_count: type: integer example: 4 state: type: enum enum: ['FUTURE', 'WAITING', 'READY', 'CANCELLED', 'COMPLETED','LIKELY','MAYBE'] readOnly: true - isDecision: + is_decision: type: boolean example: False readOnly: true diff --git a/crc/api/workflow.py b/crc/api/workflow.py index b8597da9..8f5b751c 100644 --- a/crc/api/workflow.py +++ b/crc/api/workflow.py @@ -100,11 +100,8 @@ def __get_workflow_api_model(processor: WorkflowProcessor, next_task = None): nav_item['title'] = nav_item['task'].title # Prefer the task title. else: nav_item['task'] = None - nav_item['childCount'] = nav_item.pop('child_count') - if 'is_decision' in nav_item: - nav_item['isDecision'] = nav_item.pop('is_decision') - else: - nav_item['isDecision'] = False + if not 'is_decision' in nav_item: + nav_item['is_decision'] = False navigation.append(NavigationItem(**nav_item)) NavigationItemSchema().dump(nav_item) diff --git a/crc/models/api_models.py b/crc/models/api_models.py index 2222bc8d..4e7d4304 100644 --- a/crc/models/api_models.py +++ b/crc/models/api_models.py @@ -16,7 +16,7 @@ class MultiInstanceType(enum.Enum): class NavigationItem(object): - def __init__(self, id, task_id, name, title, backtracks, level, indent, childCount, state, isDecision, task=None): + def __init__(self, id, task_id, name, title, backtracks, level, indent, child_count, state, is_decision, task=None): self.id = id self.task_id = task_id self.name = name, @@ -24,9 +24,9 @@ class NavigationItem(object): self.backtracks = backtracks self.level = level self.indent = indent - self.childCount = childCount + self.child_count = child_count self.state = state - self.isDecision = isDecision + self.is_decision = is_decision self.task = task class Task(object): @@ -36,9 +36,8 @@ class Task(object): EMUM_OPTIONS_LABEL_COL_PROP = "enum.options.label.column" EMUM_OPTIONS_AS_LOOKUP = "enum.options.lookup" - def __init__(self, id, name, title, type, state, form, documentation, data, - multiInstanceType, multiInstanceCount, multiInstanceIndex, processName, properties): + multi_instance_type, multi_instance_count, multi_instance_index, process_name, properties): self.id = id self.name = name self.title = title @@ -47,10 +46,10 @@ class Task(object): self.form = form self.documentation = documentation self.data = data - self.multiInstanceType = multiInstanceType # Some tasks have a repeat behavior. - self.multiInstanceCount = multiInstanceCount # This is the number of times the task could repeat. - self.multiInstanceIndex = multiInstanceIndex # And the index of the currently repeating task. - self.processName = processName + self.multi_instance_type = multi_instance_type # Some tasks have a repeat behavior. + self.multi_instance_count = multi_instance_count # This is the number of times the task could repeat. + self.multi_instance_index = multi_instance_index # And the index of the currently repeating task. + self.process_name = process_name self.properties = properties # Arbitrary extension properties from BPMN editor. @@ -63,6 +62,7 @@ class ValidationSchema(ma.Schema): class Meta: fields = ["name", "config"] + class FormFieldPropertySchema(ma.Schema): class Meta: fields = [ @@ -88,14 +88,14 @@ class FormSchema(ma.Schema): class TaskSchema(ma.Schema): class Meta: - fields = ["id", "name", "title", "type", "state", "form", "documentation", "data", "multiInstanceType", - "multiInstanceCount", "multiInstanceIndex", "processName", "properties"] + fields = ["id", "name", "title", "type", "state", "form", "documentation", "data", "multi_instance_type", + "multi_instance_count", "multi_instance_index", "process_name", "properties"] - multiInstanceType = EnumField(MultiInstanceType) + multi_instance_type = EnumField(MultiInstanceType) documentation = marshmallow.fields.String(required=False, allow_none=True) form = marshmallow.fields.Nested(FormSchema, required=False, allow_none=True) title = marshmallow.fields.String(required=False, allow_none=True) - processName = marshmallow.fields.String(required=False, allow_none=True) + process_name = marshmallow.fields.String(required=False, allow_none=True) @marshmallow.post_load def make_task(self, data, **kwargs): @@ -104,8 +104,8 @@ class TaskSchema(ma.Schema): class NavigationItemSchema(ma.Schema): class Meta: - fields = ["id", "task_id", "name", "title", "backtracks", "level", "indent", "childCount", "state", - "isDecision", "task"] + fields = ["id", "task_id", "name", "title", "backtracks", "level", "indent", "child_count", "state", + "is_decision", "task"] unknown = INCLUDE task = marshmallow.fields.Nested(TaskSchema, dump_only=True, required=False, allow_none=True) backtracks = marshmallow.fields.String(required=False, allow_none=True) diff --git a/crc/services/workflow_service.py b/crc/services/workflow_service.py index 750654f9..faee089a 100644 --- a/crc/services/workflow_service.py +++ b/crc/services/workflow_service.py @@ -106,7 +106,7 @@ class WorkflowService(object): mi_type, info["mi_count"], info["mi_index"], - processName=spiff_task.task_spec._wf_spec.description, + process_name=spiff_task.task_spec._wf_spec.description, properties=props ) @@ -288,10 +288,10 @@ class WorkflowService(object): task_title=task.title, task_type=str(task.type), task_state=task.state, - mi_type=task.multiInstanceType.value, # Some tasks have a repeat behavior. - mi_count=task.multiInstanceCount, # This is the number of times the task could repeat. - mi_index=task.multiInstanceIndex, # And the index of the currently repeating task. - process_name=task.processName, + mi_type=task.multi_instance_type.value, # Some tasks have a repeat behavior. + mi_count=task.multi_instance_count, # This is the number of times the task could repeat. + mi_index=task.multi_instance_index, # And the index of the currently repeating task. + process_name=task.process_name, date=datetime.now(), ) db.session.add(task_event) diff --git a/tests/test_tasks_api.py b/tests/test_tasks_api.py index f0ddd623..5edfa686 100644 --- a/tests/test_tasks_api.py +++ b/tests/test_tasks_api.py @@ -67,14 +67,14 @@ class TestTasksApi(BaseTest): self.assertEquals(task_in.type, event.task_type) self.assertEquals("COMPLETED", event.task_state) # Not sure what vodoo is happening inside of marshmallow to get me in this state. - if isinstance(task_in.multiInstanceType,MultiInstanceType): - self.assertEquals(task_in.multiInstanceType.value, event.mi_type) + if isinstance(task_in.multi_instance_type, MultiInstanceType): + self.assertEquals(task_in.multi_instance_type.value, event.mi_type) else: - self.assertEquals(task_in.multiInstanceType, event.mi_type) + self.assertEquals(task_in.multi_instance_type, event.mi_type) - self.assertEquals(task_in.multiInstanceCount, event.mi_count) - self.assertEquals(task_in.multiInstanceIndex, event.mi_index) - self.assertEquals(task_in.processName, event.process_name) + self.assertEquals(task_in.multi_instance_count, event.mi_count) + self.assertEquals(task_in.multi_instance_index, event.mi_index) + self.assertEquals(task_in.process_name, event.process_name) self.assertIsNotNone(event.date) @@ -296,8 +296,8 @@ class TestTasksApi(BaseTest): navigation = self.get_workflow_api(workflow).navigation self.assertEquals(4, len(navigation)) # Start task, form_task, multi_task, end task self.assertEquals("UserTask", workflow.next_task.type) - self.assertEquals(MultiInstanceType.sequential.value, workflow.next_task.multiInstanceType) - self.assertEquals(9, workflow.next_task.multiInstanceCount) + self.assertEquals(MultiInstanceType.sequential.value, workflow.next_task.multi_instance_type) + self.assertEquals(9, workflow.next_task.multi_instance_count) # Assure that the names for each task are properly updated, so they aren't all the same. self.assertEquals("Primary Investigator", workflow.next_task.properties['display_name']) @@ -330,13 +330,13 @@ class TestTasksApi(BaseTest): self.assertEquals(2, len(navigation)) self.assertEquals("UserTask", task.type) self.assertEquals("Activity_A", task.name) - self.assertEquals("My Sub Process", task.processName) + self.assertEquals("My Sub Process", task.process_name) workflow_api = self.complete_form(workflow, task, {"name": "Dan"}) task = workflow_api.next_task self.assertIsNotNone(task) self.assertEquals("Activity_B", task.name) - self.assertEquals("Sub Workflow Example", task.processName) + self.assertEquals("Sub Workflow Example", task.process_name) workflow_api = self.complete_form(workflow, task, {"name": "Dan"}) self.assertEquals(WorkflowStatus.complete, workflow_api.status) diff --git a/tests/test_workflow_processor_multi_instance.py b/tests/test_workflow_processor_multi_instance.py index cad925fa..21fc3b43 100644 --- a/tests/test_workflow_processor_multi_instance.py +++ b/tests/test_workflow_processor_multi_instance.py @@ -61,9 +61,9 @@ class TestWorkflowProcessorMultiInstance(BaseTest): self.assertEqual("MutiInstanceTask", task.get_name()) api_task = WorkflowService.spiff_task_to_api_task(task) - self.assertEquals(MultiInstanceType.sequential, api_task.multiInstanceType) - self.assertEquals(3, api_task.multiInstanceCount) - self.assertEquals(1, api_task.multiInstanceIndex) + self.assertEquals(MultiInstanceType.sequential, api_task.multi_instance_type) + self.assertEquals(3, api_task.multi_instance_count) + self.assertEquals(1, api_task.multi_instance_index) task.update_data({"investigator":{"email":"asd3v@virginia.edu"}}) processor.complete_task(task) processor.do_engine_steps() @@ -72,8 +72,8 @@ class TestWorkflowProcessorMultiInstance(BaseTest): api_task = WorkflowService.spiff_task_to_api_task(task) self.assertEqual("MutiInstanceTask", api_task.name) task.update_data({"investigator":{"email":"asdf32@virginia.edu"}}) - self.assertEquals(3, api_task.multiInstanceCount) - self.assertEquals(2, api_task.multiInstanceIndex) + self.assertEquals(3, api_task.multi_instance_count) + self.assertEquals(2, api_task.multi_instance_index) processor.complete_task(task) processor.do_engine_steps() @@ -81,8 +81,8 @@ class TestWorkflowProcessorMultiInstance(BaseTest): api_task = WorkflowService.spiff_task_to_api_task(task) self.assertEqual("MutiInstanceTask", task.get_name()) task.update_data({"investigator":{"email":"dhf8r@virginia.edu"}}) - self.assertEquals(3, api_task.multiInstanceCount) - self.assertEquals(3, api_task.multiInstanceIndex) + self.assertEquals(3, api_task.multi_instance_count) + self.assertEquals(3, api_task.multi_instance_index) processor.complete_task(task) processor.do_engine_steps() task = processor.bpmn_workflow.last_task @@ -120,7 +120,7 @@ class TestWorkflowProcessorMultiInstance(BaseTest): self.assertEquals("asd3v", task.data["investigator"]["user_id"]) # The last of the tasks api_task = WorkflowService.spiff_task_to_api_task(task) - self.assertEquals(MultiInstanceType.parallel, api_task.multiInstanceType) + self.assertEquals(MultiInstanceType.parallel, api_task.multi_instance_type) task.update_data({"investigator":{"email":"dhf8r@virginia.edu"}}) processor.complete_task(task) processor.do_engine_steps()