mirror of
https://github.com/sartography/cr-connect-workflow.git
synced 2025-02-20 11:48:16 +00:00
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.
This commit is contained in:
parent
b63ee8159e
commit
53255ef35e
51
Pipfile.lock
generated
51
Pipfile.lock
generated
@ -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": [
|
||||
|
34
crc/api.yml
34
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"
|
||||
|
||||
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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()
|
||||
|
||||
|
||||
|
||||
|
@ -1,11 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_1j7idla" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.4.1">
|
||||
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_1j7idla" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.7.3">
|
||||
<bpmn:process id="Process_18biih5" isExecutable="true">
|
||||
<bpmn:startEvent id="StartEvent_1">
|
||||
<bpmn:outgoing>SequenceFlow_1pnq3kg</bpmn:outgoing>
|
||||
</bpmn:startEvent>
|
||||
<bpmn:sequenceFlow id="SequenceFlow_1pnq3kg" sourceRef="StartEvent_1" targetRef="Task_Has_Bananas" />
|
||||
<bpmn:userTask id="Task_Has_Bananas" name="Has Bananas?" camunda:formKey="bananas_form">
|
||||
<bpmn:userTask id="Task_Has_Bananas" name="Enter Do You Have Bananas" camunda:formKey="bananas_form">
|
||||
<bpmn:extensionElements>
|
||||
<camunda:formData>
|
||||
<camunda:formField id="has_bananas" label="Do you have bananas?" type="boolean" />
|
||||
@ -15,7 +15,7 @@
|
||||
<bpmn:outgoing>SequenceFlow_1lmkn99</bpmn:outgoing>
|
||||
</bpmn:userTask>
|
||||
<bpmn:sequenceFlow id="SequenceFlow_1lmkn99" sourceRef="Task_Has_Bananas" targetRef="ExclusiveGateway_003amsm" />
|
||||
<bpmn:exclusiveGateway id="ExclusiveGateway_003amsm">
|
||||
<bpmn:exclusiveGateway id="ExclusiveGateway_003amsm" name="Has Bananas?">
|
||||
<bpmn:incoming>SequenceFlow_1lmkn99</bpmn:incoming>
|
||||
<bpmn:outgoing>SequenceFlow_Yes_Bananas</bpmn:outgoing>
|
||||
<bpmn:outgoing>SequenceFlow_No_Bananas</bpmn:outgoing>
|
||||
@ -55,29 +55,13 @@
|
||||
</bpmn:process>
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_18biih5">
|
||||
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
|
||||
<dc:Bounds x="179" y="99" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge id="SequenceFlow_1pnq3kg_di" bpmnElement="SequenceFlow_1pnq3kg">
|
||||
<di:waypoint x="215" y="117" />
|
||||
<di:waypoint x="270" y="117" />
|
||||
<bpmndi:BPMNEdge id="SequenceFlow_08djf6q_di" bpmnElement="SequenceFlow_08djf6q">
|
||||
<di:waypoint x="660" y="230" />
|
||||
<di:waypoint x="752" y="230" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNShape id="UserTask_0u8fjmw_di" bpmnElement="Task_Has_Bananas">
|
||||
<dc:Bounds x="270" y="77" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge id="SequenceFlow_1lmkn99_di" bpmnElement="SequenceFlow_1lmkn99">
|
||||
<di:waypoint x="370" y="117" />
|
||||
<di:waypoint x="425" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNShape id="ExclusiveGateway_14wqqsi_di" bpmnElement="ExclusiveGateway_003amsm" isMarkerVisible="true">
|
||||
<dc:Bounds x="425" y="92" width="50" height="50" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge id="SequenceFlow_0f3vx1l_di" bpmnElement="SequenceFlow_Yes_Bananas">
|
||||
<di:waypoint x="475" y="117" />
|
||||
<di:waypoint x="560" y="117" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="509" y="99" width="18" height="40" />
|
||||
</bpmndi:BPMNLabel>
|
||||
<bpmndi:BPMNEdge id="SequenceFlow_02z84p5_di" bpmnElement="SequenceFlow_02z84p5">
|
||||
<di:waypoint x="660" y="117" />
|
||||
<di:waypoint x="752" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="SequenceFlow_12acevn_di" bpmnElement="SequenceFlow_No_Bananas">
|
||||
<di:waypoint x="450" y="142" />
|
||||
@ -87,6 +71,33 @@
|
||||
<dc:Bounds x="459" y="183" width="13" height="14" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="SequenceFlow_0f3vx1l_di" bpmnElement="SequenceFlow_Yes_Bananas">
|
||||
<di:waypoint x="475" y="117" />
|
||||
<di:waypoint x="560" y="117" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="509" y="99" width="18" height="40" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="SequenceFlow_1lmkn99_di" bpmnElement="SequenceFlow_1lmkn99">
|
||||
<di:waypoint x="370" y="117" />
|
||||
<di:waypoint x="425" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="SequenceFlow_1pnq3kg_di" bpmnElement="SequenceFlow_1pnq3kg">
|
||||
<di:waypoint x="215" y="117" />
|
||||
<di:waypoint x="270" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
|
||||
<dc:Bounds x="179" y="99" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="UserTask_0u8fjmw_di" bpmnElement="Task_Has_Bananas">
|
||||
<dc:Bounds x="270" y="77" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="ExclusiveGateway_14wqqsi_di" bpmnElement="ExclusiveGateway_003amsm" isMarkerVisible="true">
|
||||
<dc:Bounds x="425" y="92" width="50" height="50" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="415" y="62" width="73" height="14" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="UserTask_0ht939a_di" bpmnElement="Task_Num_Bananas">
|
||||
<dc:Bounds x="560" y="77" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
@ -96,17 +107,9 @@
|
||||
<bpmndi:BPMNShape id="EndEvent_063bpg6_di" bpmnElement="EndEvent_063bpg6">
|
||||
<dc:Bounds x="752" y="99" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge id="SequenceFlow_02z84p5_di" bpmnElement="SequenceFlow_02z84p5">
|
||||
<di:waypoint x="660" y="117" />
|
||||
<di:waypoint x="752" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNShape id="EndEvent_1hwtug4_di" bpmnElement="EndEvent_1hwtug4">
|
||||
<dc:Bounds x="752" y="212" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge id="SequenceFlow_08djf6q_di" bpmnElement="SequenceFlow_08djf6q">
|
||||
<di:waypoint x="660" y="230" />
|
||||
<di:waypoint x="752" y="230" />
|
||||
</bpmndi:BPMNEdge>
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
</bpmn:definitions>
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
Loading…
x
Reference in New Issue
Block a user