Returning better cleaner information about workflow with the workflow endpoint. Removes the get_all_tasks and get_user_tasks endpoints as tasks are returned with the workflow. Workflow endpoint also includes the last task and next_task, which may or may not be user tasks. The task "type" returned is now the class name of the task_spec, rather than just the word "task".

This commit is contained in:
Dan Funk 2020-02-07 11:34:44 -05:00
parent 349dac6e44
commit 9bd93748be
9 changed files with 197 additions and 194 deletions

18
Pipfile.lock generated
View File

@ -25,10 +25,10 @@
},
"alembic": {
"hashes": [
"sha256:d412982920653db6e5a44bfd13b1d0db5685cbaaccaf226195749c706e1e862a"
"sha256:2df2519a5b002f881517693b95626905a39c5faf4b5a1f94de4f1441095d1d26"
],
"index": "pypi",
"version": "==1.3.3"
"version": "==1.4.0"
},
"amqp": {
"hashes": [
@ -293,11 +293,11 @@
},
"flask-restful": {
"hashes": [
"sha256:ecd620c5cc29f663627f99e04f17d1f16d095c83dc1d618426e2ad68b03092f8",
"sha256:f8240ec12349afe8df1db168ea7c336c4e5b0271a36982bff7394f93275f2ca9"
"sha256:5ea9a5991abf2cb69b4aac19793faac6c032300505b325687d7c305ffaa76915",
"sha256:d891118b951921f1cec80cabb4db98ea6058a35e6404788f9e70d5b243813ec2"
],
"index": "pypi",
"version": "==0.3.7"
"version": "==0.3.8"
},
"flask-sqlalchemy": {
"hashes": [
@ -723,7 +723,7 @@
"spiffworkflow": {
"editable": true,
"git": "https://github.com/sartography/SpiffWorkflow.git",
"ref": "7640c6e32d3894b13f8a078849922cf7cb6884a5"
"ref": "6091825791a5e9f11c12639ac07688377bad697e"
},
"sqlalchemy": {
"hashes": [
@ -778,10 +778,10 @@
},
"werkzeug": {
"hashes": [
"sha256:1e0dedc2acb1f46827daa2e399c1485c8fa17c0d8e70b6b875b4e7f54bf408d2",
"sha256:b353856d37dec59d6511359f97f6a4b2468442e454bd1c98298ddce53cac1f04"
"sha256:169ba8a33788476292d04186ab33b01d6add475033dfc07215e6d219cc077096",
"sha256:6dc65cf9091cf750012f56f2cad759fa9e879f511b5ff8685e456b4e3bf90d16"
],
"version": "==0.16.1"
"version": "==1.0.0"
},
"xlsxwriter": {
"hashes": [

View File

@ -487,64 +487,6 @@ paths:
responses:
'204':
description: The workflow was removed
/workflow/{workflow_id}/all_tasks:
get:
operationId: crc.api.workflow.get_all_tasks
summary: Return a list of all tasks for this workflow
tags:
- Workflows and Tasks
parameters:
- name: workflow_id
in: path
required: true
description: The id of the workflow
schema:
type: integer
format: int32
responses:
'200':
description: Expected response to a valid request
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Task"
default:
description: unexpected error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/workflow/{workflow_id}/tasks:
get:
operationId: crc.api.workflow.get_ready_user_tasks
summary: Returns the list of ready user tasks for this workflow
tags:
- Workflows and Tasks
parameters:
- name: workflow_id
in: path
required: true
description: The id of the workflow
schema:
type: integer
format: int32
responses:
'200':
description: Expected response to a valid request
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Task"
default:
description: unexpected error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
# /v1.0/workflow/0/task/0
/workflow/{workflow_id}/task/{task_id}:
parameters:
@ -693,21 +635,16 @@ components:
format: int64
status:
type: enum
enum: ['user_input_required','waiting','complete']
study_id:
readOnly: true
enum: ['new','user_input_required','waiting','complete']
last_task_id:
type: integer
workflow_spec_id:
readOnly: true
type: String
current_task_ids:
next_task_id:
type: integer
user_tasks:
type: array
items:
type: String
messages:
type: array
items:
type: String
$ref: "#/components/schemas/Task"
example:
id: 291234
status: 'user_input_required'

View File

@ -2,9 +2,10 @@ from connexion import NoContent
from crc import session
from crc.api.common import ApiError, ApiErrorSchema
from crc.api.workflow import __get_workflow_api_model
from crc.models.study import StudyModelSchema, StudyModel
from crc.models.workflow import WorkflowModel, WorkflowModelSchema, WorkflowSpecModel
from crc.workflow_processor import WorkflowProcessor
from crc.models.workflow import WorkflowModel, WorkflowApiSchema, WorkflowSpecModel
from crc.workflow_processor import Workflow, WorkflowProcessor
def all_studies():
@ -19,7 +20,11 @@ def add_study(body):
session.commit()
# FIXME: We need to ask the protocol builder what workflows to add to the study, not just add them all.
for spec in session.query(WorkflowSpecModel).all():
workflow = __get_workflow_instance(study.id, spec)
processor = WorkflowProcessor.create(spec.id)
workflow = WorkflowModel(bpmn_workflow_json=processor.serialize(),
status=processor.get_status(),
study_id=study.id,
workflow_spec_id=spec.id)
session.add(workflow)
session.commit()
return StudyModelSchema().dump(study)
@ -56,9 +61,14 @@ def post_update_study_from_protocol_builder(study_id):
def get_study_workflows(study_id):
workflows = session.query(WorkflowModel).filter_by(study_id=study_id).all()
schema = WorkflowModelSchema(many=True)
return schema.dump(workflows)
workflow_models = session.query(WorkflowModel).filter_by(study_id=study_id).all()
api_models = []
for workflow_model in workflow_models:
processor = WorkflowProcessor(workflow_model.workflow_spec_id,
workflow_model.bpmn_workflow_json)
api_models.append( __get_workflow_api_model(workflow_model, processor))
schema = WorkflowApiSchema(many=True)
return schema.dump(api_models)
def add_workflow_to_study(study_id, body):
@ -66,15 +76,12 @@ def add_workflow_to_study(study_id, body):
if workflow_spec_model is None:
error = ApiError('unknown_spec', 'The specification "' + body['id'] + '" is not recognized.')
return ApiErrorSchema.dump(error), 404
workflow = __get_workflow_instance(study_id, workflow_spec_model)
session.add(workflow)
session.commit()
return WorkflowModelSchema().dump(workflow)
def __get_workflow_instance(study_id, workflow_spec_model):
processor = WorkflowProcessor.create(workflow_spec_model.id)
workflow = WorkflowModel(bpmn_workflow_json=processor.serialize(),
status=processor.get_status(),
study_id=study_id,
workflow_spec_id=workflow_spec_model.id)
return workflow
session.add(workflow)
session.commit()
return WorkflowApiSchema().dump(__get_workflow_api_model(workflow, processor))

View File

@ -1,10 +1,12 @@
import uuid
from flask import json
from crc.api.file import delete_file
from crc import session
from crc.api.common import ApiError, ApiErrorSchema
from crc.models.workflow import WorkflowModel, WorkflowModelSchema, WorkflowSpecModelSchema, WorkflowSpecModel, \
Task, TaskSchema
from crc.models.workflow import WorkflowModel, WorkflowSpecModelSchema, WorkflowSpecModel, \
Task, TaskSchema, WorkflowApiSchema, WorkflowApi
from crc.workflow_processor import WorkflowProcessor
from crc.models.file import FileModel
@ -65,37 +67,26 @@ def delete_workflow_specification(spec_id):
session.commit()
def __get_workflow_api_model(workflow_model: WorkflowModel, processor: WorkflowProcessor):
spiff_tasks = processor.get_all_user_tasks()
user_tasks = map(Task.from_spiff, spiff_tasks)
return WorkflowApi(id=workflow_model.id, status=workflow_model.status,
last_task=Task.from_spiff(processor.bpmn_workflow.last_task),
next_task=Task.from_spiff(processor.next_task()),
user_tasks=user_tasks)
def get_workflow(workflow_id):
schema = WorkflowModelSchema()
workflow = session.query(WorkflowModel).filter_by(id=workflow_id).first()
return schema.dump(workflow)
schema = WorkflowApiSchema()
workflow_model = session.query(WorkflowModel).filter_by(id=workflow_id).first()
processor = WorkflowProcessor(workflow_model.workflow_spec_id,
workflow_model.bpmn_workflow_json)
return schema.dump(__get_workflow_api_model(workflow_model, processor))
def delete(workflow_id):
session.query(WorkflowModel).filter_by(id=workflow_id).delete()
session.commit()
3
def get_all_tasks(workflow_id):
workflow = session.query(WorkflowModel).filter_by(id=workflow_id).first()
processor = WorkflowProcessor(workflow.workflow_spec_id, workflow.bpmn_workflow_json)
spiff_tasks = processor.get_all_user_tasks()
tasks = []
for st in spiff_tasks:
tasks.append(Task.from_spiff(st))
return TaskSchema(many=True).dump(tasks)
def get_ready_user_tasks(workflow_id):
workflow = session.query(WorkflowModel).filter_by(id=workflow_id).first()
processor = WorkflowProcessor(workflow.workflow_spec_id, workflow.bpmn_workflow_json)
spiff_tasks = processor.get_ready_user_tasks()
tasks = []
for st in spiff_tasks:
tasks.append(Task.from_spiff(st))
return TaskSchema(many=True).dump(tasks)
def get_task(workflow_id, task_id):
workflow = session.query(WorkflowModel).filter_by(id=workflow_id).first()
@ -103,14 +94,16 @@ def get_task(workflow_id, task_id):
def update_task(workflow_id, task_id, body):
workflow = session.query(WorkflowModel).filter_by(id=workflow_id).first()
processor = WorkflowProcessor(workflow.workflow_spec_id, workflow.bpmn_workflow_json)
workflow_model = session.query(WorkflowModel).filter_by(id=workflow_id).first()
processor = WorkflowProcessor(workflow_model.workflow_spec_id, workflow_model.bpmn_workflow_json)
task_id = uuid.UUID(task_id)
task = processor.bpmn_workflow.get_task(task_id)
task.data = body
processor.complete_task(task)
processor.do_engine_steps()
workflow.bpmn_workflow_json = processor.serialize()
session.add(workflow)
workflow_model.last_completed_task_id = task.id
workflow_model.bpmn_workflow_json = processor.serialize()
session.add(workflow_model)
session.commit()
return WorkflowModelSchema().dump(workflow)
return WorkflowApiSchema().dump(__get_workflow_api_model(workflow_model, processor)
)

View File

@ -1,6 +1,7 @@
import enum
import marshmallow
from marshmallow import post_dump, pre_dump, EXCLUDE, INCLUDE
from marshmallow_enum import EnumField
from marshmallow_sqlalchemy import ModelSchema
@ -15,7 +16,6 @@ class WorkflowSpecModel(db.Model):
description = db.Column(db.Text)
primary_process_id = db.Column(db.String)
class WorkflowSpecModelSchema(ModelSchema):
class Meta:
model = WorkflowSpecModel
@ -35,17 +35,10 @@ class WorkflowModel(db.Model):
status = db.Column(db.Enum(WorkflowStatus))
study_id = db.Column(db.Integer, db.ForeignKey('study.id'))
workflow_spec_id = db.Column(db.String, db.ForeignKey('workflow_spec.id'))
last_completed_task_id = db.Column(db.String)
class WorkflowModelSchema(ModelSchema):
class Meta:
model = WorkflowModel
include_fk = True # Includes foreign keys
status = EnumField(WorkflowStatus)
class Task:
class Task(object):
def __init__(self, id, name, title, type, state, form, documentation, data):
self.id = id
self.name = name
@ -61,13 +54,12 @@ class Task:
instance = cls(spiff_task.id,
spiff_task.task_spec.name,
spiff_task.task_spec.description,
"task",
spiff_task.task_spec.__class__.__name__,
spiff_task.get_state_name(),
{},
None,
spiff_task.task_spec.documentation,
spiff_task.data)
if hasattr(spiff_task.task_spec, "form"):
instance.type = "form"
instance.form = spiff_task.task_spec.form
return instance
@ -108,8 +100,32 @@ class TaskSchema(ma.Schema):
fields = ["id", "name", "title", "type", "state", "form", "documentation", "data"]
documentation = marshmallow.fields.String(required=False, allow_none=True)
form = marshmallow.fields.Nested(FormSchema)
form = marshmallow.fields.Nested(FormSchema, required=False, allow_none=True)
title = marshmallow.fields.String(required=False, allow_none=True)
@marshmallow.post_load
def make_task(self, data, **kwargs):
return Task(**data)
class WorkflowApi(object):
def __init__(self, id, status, user_tasks, last_task, next_task):
self.id = id
self.status = status
self.user_tasks = user_tasks
self.last_task = last_task
self.next_task = next_task
class WorkflowApiSchema(ma.Schema):
class Meta:
model = WorkflowApi
fields = ["id", "status", "user_tasks", "last_task", "next_task"]
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)
next_task = marshmallow.fields.Nested(TaskSchema, dump_only=True)
@marshmallow.post_load
def make_workflow(self, data, **kwargs):
return WorkflowApi(**data)

View File

@ -1,9 +1,7 @@
import xml.etree.ElementTree as ElementTree
from SpiffWorkflow import Task as SpiffTask
from SpiffWorkflow import Task as SpiffTask, Workflow
from SpiffWorkflow.bpmn.BpmnScriptEngine import BpmnScriptEngine
from SpiffWorkflow.bpmn.parser.task_parsers import UserTaskParser
from SpiffWorkflow.bpmn.parser.util import full_tag
from SpiffWorkflow.bpmn.serializer.BpmnSerializer import BpmnSerializer
from SpiffWorkflow.bpmn.workflow import BpmnWorkflow
from SpiffWorkflow.camunda.parser.CamundaParser import CamundaParser
@ -54,6 +52,7 @@ class CustomBpmnScriptEngine(BpmnScriptEngine):
details=str(ne))
class MyCustomParser(BpmnDmnParser):
"""
A BPMN and DMN parser that can also parse Camunda forms.
@ -61,8 +60,7 @@ class MyCustomParser(BpmnDmnParser):
OVERRIDE_PARSER_CLASSES = BpmnDmnParser.OVERRIDE_PARSER_CLASSES
OVERRIDE_PARSER_CLASSES.update(CamundaParser.OVERRIDE_PARSER_CLASSES)
class WorkflowProcessor:
class WorkflowProcessor(object):
_script_engine = CustomBpmnScriptEngine()
_serializer = BpmnSerializer()
@ -96,6 +94,8 @@ class WorkflowProcessor:
raise(Exception("There is no primary BPMN model defined for workflow %s" % workflow_spec_id))
return parser.get_spec(process_id)
@classmethod
def create(cls, workflow_spec_id):
spec = WorkflowProcessor.get_spec(workflow_spec_id)
@ -123,6 +123,21 @@ class WorkflowProcessor:
def next_user_tasks(self):
return self.bpmn_workflow.get_ready_user_tasks()
def next_task(self):
"""Returns the next user task that should be completed
even if there are parallel tasks and mulitple options are
available."""
ready_tasks = self.bpmn_workflow.get_ready_user_tasks()
if len(ready_tasks) == 0:
return None
elif len(ready_tasks) == 1:
return ready_tasks[0]
else:
for task in ready_tasks:
if task.parent == self.bpmn_workflow.last_task:
return task;
return ready_tasks[0]
def complete_task(self, task):
self.bpmn_workflow.complete_task_from_id(task.id)

View File

@ -5,7 +5,7 @@ from crc import session
from crc.models.file import FileModel
from crc.models.study import StudyModel, StudyModelSchema, ProtocolBuilderStatus
from crc.models.workflow import WorkflowSpecModel, WorkflowSpecModelSchema, WorkflowModel, WorkflowStatus, \
WorkflowModelSchema, TaskSchema
WorkflowApiSchema
from tests.base_test import BaseTest
@ -162,15 +162,15 @@ class TestStudy(BaseTest):
data=json.dumps(WorkflowSpecModelSchema().dump(spec)))
self.assert_success(rv)
self.assertEqual(1, session.query(WorkflowModel).count())
workflow = session.query(WorkflowModel).first()
self.assertEqual(study.id, workflow.study_id)
self.assertEqual(WorkflowStatus.user_input_required, workflow.status)
self.assertIsNotNone(workflow.bpmn_workflow_json)
self.assertEqual(spec.id, workflow.workflow_spec_id)
workflow_model = session.query(WorkflowModel).first()
self.assertEqual(study.id, workflow_model.study_id)
self.assertEqual(WorkflowStatus.user_input_required, workflow_model.status)
self.assertIsNotNone(workflow_model.bpmn_workflow_json)
self.assertEqual(spec.id, workflow_model.workflow_spec_id)
json_data = json.loads(rv.get_data(as_text=True))
workflow2 = WorkflowModelSchema().load(json_data, session=session)
self.assertEqual(workflow.id, workflow2.id)
workflow2 = WorkflowApiSchema().load(json_data)
self.assertEqual(workflow_model.id, workflow2.id)
def test_delete_workflow(self):
self.load_example_data()
@ -180,8 +180,7 @@ class TestStudy(BaseTest):
data=json.dumps(WorkflowSpecModelSchema().dump(spec)))
self.assertEqual(1, session.query(WorkflowModel).count())
json_data = json.loads(rv.get_data(as_text=True))
workflow = WorkflowModelSchema().load(json_data, session=session)
workflow = WorkflowApiSchema().load(json_data)
rv = self.app.delete('/v1.0/workflow/%i' % workflow.id)
self.assert_success(rv)
self.assertEqual(0, session.query(WorkflowModel).count())

View File

@ -4,8 +4,8 @@ from datetime import datetime
from crc import session
from crc.models.file import FileModel
from crc.models.study import StudyModel, StudyModelSchema, ProtocolBuilderStatus
from crc.models.workflow import WorkflowSpecModel, WorkflowSpecModelSchema, WorkflowModel, WorkflowStatus, \
WorkflowModelSchema, TaskSchema
from crc.models.workflow import WorkflowSpecModel, WorkflowSpecModelSchema, WorkflowModel, \
WorkflowStatus, TaskSchema, WorkflowApiSchema
from tests.base_test import BaseTest
@ -19,18 +19,11 @@ class TestTasksApi(BaseTest):
workflow = session.query(WorkflowModel).filter_by(study_id = study.id, workflow_spec_id=workflow_name).first()
return workflow
def get_tasks(self, workflow):
rv = self.app.get('/v1.0/workflow/%i/tasks' % workflow.id, content_type="application/json")
def get_workflow_api(self, workflow):
rv = self.app.get('/v1.0/workflow/%i' % workflow.id, content_type="application/json")
json_data = json.loads(rv.get_data(as_text=True))
tasks = TaskSchema(many=True).load(json_data)
return tasks
def get_all_tasks(self, workflow):
rv = self.app.get('/v1.0/workflow/%i/all_tasks' % workflow.id, content_type="application/json")
self.assert_success(rv)
json_data = json.loads(rv.get_data(as_text=True))
all_tasks = TaskSchema(many=True).load(json_data)
return all_tasks
workflow_api = WorkflowApiSchema().load(json_data)
return workflow_api
def complete_form(self, workflow, task, dict_data):
rv = self.app.put('/v1.0/workflow/%i/task/%s/data' % (workflow.id, task.id),
@ -38,13 +31,13 @@ class TestTasksApi(BaseTest):
data=json.dumps(dict_data))
self.assert_success(rv)
json_data = json.loads(rv.get_data(as_text=True))
workflow = WorkflowModelSchema().load(json_data, session=session)
workflow = WorkflowApiSchema().load(json_data)
return workflow
def test_get_current_user_tasks(self):
self.load_example_data()
workflow = self.create_workflow('random_fact')
tasks = self.get_tasks(workflow)
tasks = self.get_workflow_api(workflow).user_tasks
self.assertEqual("Task_User_Select_Type", tasks[0].name)
self.assertEqual(3, len(tasks[0].form["fields"][0]["options"]))
@ -53,22 +46,22 @@ class TestTasksApi(BaseTest):
self.load_example_data()
workflow = self.create_workflow('two_forms')
# get the first form in the two form workflow.
tasks = self.get_tasks(workflow)
self.assertEqual(1, len(tasks))
self.assertIsNotNone(tasks[0].form)
self.assertEqual("StepOne", tasks[0].name)
self.assertEqual(1, len(tasks[0].form['fields']))
workflow_api = self.get_workflow_api(workflow)
self.assertEqual(2, len(workflow_api.user_tasks))
self.assertIsNotNone(workflow_api.user_tasks[0].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, tasks[0], {"color": "blue"})
self.complete_form(workflow, workflow_api.user_tasks[0], {"color": "blue"})
# Get the next Task
tasks = self.get_tasks(workflow)
self.assertEqual("StepTwo", tasks[0].name)
workflow_api = self.get_workflow_api(workflow)
self.assertEqual("StepTwo", workflow_api.next_task['name'])
# Get all user Tasks and check that the data have been saved
all_tasks = self.get_all_tasks(workflow)
for task in all_tasks:
for task in workflow_api.user_tasks:
self.assertIsNotNone(task.data)
for val in task.data.values():
self.assertIsNotNone(val)
@ -78,18 +71,40 @@ class TestTasksApi(BaseTest):
workflow = self.create_workflow('exclusive_gateway')
# get the first form in the two form workflow.
tasks = self.get_tasks(workflow)
tasks = self.get_workflow_api(workflow).user_tasks
self.complete_form(workflow, tasks[0], {"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_tasks(workflow)
tasks = self.get_workflow_api(workflow).user_tasks
self.complete_form(workflow, tasks[0], {"has_bananas": True})
# Get the next Task
tasks = self.get_tasks(workflow)
self.assertEqual("Task_Num_Bananas", tasks[0].name)
workflow_api = self.get_workflow_api(workflow)
self.assertEqual("Task_Num_Bananas", workflow_api.next_task['name'])
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.assertEquals({"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)

View File

@ -6,7 +6,7 @@ from crc.api.rest_exception import RestException
from crc.models.file import FileModel
from crc.models.workflow import WorkflowSpecModel, WorkflowStatus
from tests.base_test import BaseTest
from crc.workflow_processor import WorkflowProcessor
from crc.workflow_processor import Workflow, WorkflowProcessor
class TestWorkflowProcessor(BaseTest):
@ -17,7 +17,7 @@ class TestWorkflowProcessor(BaseTest):
letters = string.ascii_lowercase
return ''.join(random.choice(letters) for i in range(stringLength))
def _complete_form_with_random_data(self, task):
def _populate_form_with_random_data(self, task):
form_data = {}
for field in task.task_spec.form.fields:
form_data[field.id] = self._randomString()
@ -79,10 +79,10 @@ class TestWorkflowProcessor(BaseTest):
self.assertEqual(WorkflowStatus.user_input_required, processor.get_status())
next_user_tasks = processor.next_user_tasks()
self.assertEqual(4, len(next_user_tasks))
self._complete_form_with_random_data(next_user_tasks[0])
self._complete_form_with_random_data(next_user_tasks[1])
self._complete_form_with_random_data(next_user_tasks[2])
self._complete_form_with_random_data(next_user_tasks[3])
self._populate_form_with_random_data(next_user_tasks[0])
self._populate_form_with_random_data(next_user_tasks[1])
self._populate_form_with_random_data(next_user_tasks[2])
self._populate_form_with_random_data(next_user_tasks[3])
processor.complete_task(next_user_tasks[0])
processor.complete_task(next_user_tasks[1])
processor.complete_task(next_user_tasks[2])
@ -90,10 +90,10 @@ class TestWorkflowProcessor(BaseTest):
# There are another 4 tasks to complete (each task, had a follow up task in the parallel list)
next_user_tasks = processor.next_user_tasks()
self.assertEqual(4, len(next_user_tasks))
self._complete_form_with_random_data(next_user_tasks[0])
self._complete_form_with_random_data(next_user_tasks[1])
self._complete_form_with_random_data(next_user_tasks[2])
self._complete_form_with_random_data(next_user_tasks[3])
self._populate_form_with_random_data(next_user_tasks[0])
self._populate_form_with_random_data(next_user_tasks[1])
self._populate_form_with_random_data(next_user_tasks[2])
self._populate_form_with_random_data(next_user_tasks[3])
processor.complete_task(next_user_tasks[0])
processor.complete_task(next_user_tasks[1])
processor.complete_task(next_user_tasks[2])
@ -101,13 +101,34 @@ class TestWorkflowProcessor(BaseTest):
processor.do_engine_steps()
self.assertTrue(processor.bpmn_workflow.is_completed())
def test_workflow_processor_knows_the_text_task_even_when_parallel(self):
self.load_example_data()
workflow_spec_model = session.query(WorkflowSpecModel).filter_by(id="parallel_tasks").first()
processor = WorkflowProcessor.create(workflow_spec_model.id)
self.assertEqual(WorkflowStatus.user_input_required, processor.get_status())
next_user_tasks = processor.next_user_tasks()
self.assertEqual(4, len(next_user_tasks))
self.assertEqual(next_user_tasks[0], processor.next_task(), "First task in list of 4")
# Complete the third open task, so do things out of order
# this should cause the system to recommend the first ready task that is a
# child of the last completed task.
task = next_user_tasks[2]
self._populate_form_with_random_data(task)
processor.complete_task(task)
next_user_tasks = processor.next_user_tasks()
self.assertEqual(processor.bpmn_workflow.last_task, task)
self.assertEqual(4, len(next_user_tasks))
self.assertEqual(task.children[0], processor.next_task())
def test_workflow_with_bad_expression_raises_sensible_error(self):
workflow_spec_model = self.load_test_spec("invalid_expression")
processor = WorkflowProcessor.create(workflow_spec_model.id)
processor.do_engine_steps()
next_user_tasks = processor.next_user_tasks()
self.assertEqual(1, len(next_user_tasks))
self._complete_form_with_random_data(next_user_tasks[0])
self._populate_form_with_random_data(next_user_tasks[0])
processor.complete_task(next_user_tasks[0])
with self.assertRaises(RestException) as context:
processor.do_engine_steps()