Merge pull request #570 from sartography/feature/spiffworkflow_1_2

Feature/spiffworkflow 1 2
This commit is contained in:
Dan Funk 2022-10-13 17:02:24 -04:00 committed by GitHub
commit 23bac81260
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 711 additions and 600 deletions

View File

@ -1,2 +1,3 @@
python 3.10.4
python 3.9.12
python 3.9.1

View File

@ -52,7 +52,6 @@ dateparser = "*"
# pipenv install --editable ../SpiffWorkflow (but fix things back before commiting!)
# Merged Commit https://github.com/sartography/SpiffWorkflow/pull/178 broke usage of SpiffWorkflow
# References to task states will need to be updated to allow using newest version
#spiffworkflow = {editable = true, path = "./../SpiffWorkflow"}
spiffworkflow = {git = "https://github.com/sartography/SpiffWorkflow"}
[requires]

850
Pipfile.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -6,8 +6,6 @@ import click
import sentry_sdk
import connexion
from SpiffWorkflow import WorkflowException
from SpiffWorkflow.bpmn.exceptions import WorkflowTaskExecException
from connexion import ProblemException
from flask import Response
from flask_cors import CORS

View File

@ -1,10 +1,10 @@
import json
from SpiffWorkflow import WorkflowException
from SpiffWorkflow.bpmn.exceptions import WorkflowTaskExecException
from flask import g
from jinja2 import TemplateError
from werkzeug.exceptions import InternalServerError
from SpiffWorkflow.exceptions import WorkflowException
from SpiffWorkflow.bpmn.exceptions import WorkflowTaskExecException
from crc import ma, app

View File

@ -81,9 +81,9 @@ def get_file_data(file_id):
if file_model is not None:
return send_file(
io.BytesIO(file_model.data),
attachment_filename=file_model.name,
download_name=file_model.name,
mimetype=file_model.content_type,
cache_timeout=-1 # Don't cache these files on the browser.
max_age=-1 # Don't cache these files on the browser.
)
else:
raise ApiError('missing_file_model', f'The file id you provided ({file_id}) does not exist')
@ -97,9 +97,9 @@ def get_file_data_link(file_id, auth_token):
raise ApiError('no_such_file', f'The file id you provided ({file_id}) does not exist')
return send_file(
io.BytesIO(file_model.data),
attachment_filename=file_model.name,
download_name=file_model.name,
mimetype=file_model.content_type,
cache_timeout=-1, # Don't cache these files on the browser.
max_age=-1, # Don't cache these files on the browser.
last_modified=file_model.date_created,
as_attachment=True
)
@ -136,8 +136,8 @@ def dmn_from_ss():
result = UserFileService.dmn_from_spreadsheet(file)
return send_file(
io.BytesIO(result),
attachment_filename='temp_dmn.dmn',
download_name='temp_dmn.dmn',
mimetype='text/xml',
cache_timeout=-1, # Don't cache these files on the browser.
max_age=-1, # Don't cache these files on the browser.
last_modified=datetime.now()
)

View File

@ -20,9 +20,9 @@ def get_reference_file_data(name):
file_data = ReferenceFileService().get_data(name)
return send_file(
io.BytesIO(file_data),
attachment_filename=name,
download_name=name,
mimetype=content_type,
cache_timeout=-1 # Don't cache these files on the browser.
max_age=-1 # Don't cache these files on the browser.
)

View File

@ -80,9 +80,9 @@ def get_data(spec_id, file_name):
file_info = SpecFileService.get_files(workflow_spec, file_name)[0]
return send_file(
io.BytesIO(file_data),
attachment_filename=file_name,
download_name=file_name,
mimetype=file_info.content_type,
cache_timeout=-1 # Don't cache these files on the browser.
max_age=-1 # Don't cache these files on the browser.
)
else:
raise ApiError(code='missing_data_model',

View File

@ -1,7 +1,6 @@
import json
from datetime import datetime
from SpiffWorkflow.util.metrics import timeit, firsttime, sincetime
from flask import g, send_file
from sqlalchemy.exc import IntegrityError
from crc import session
@ -140,9 +139,9 @@ def download_logs_for_study(study_id, auth_token):
return send_file(
io.BytesIO(spreadsheet),
attachment_filename='logs.xlsx',
download_name='logs.xlsx',
mimetype='xlsx',
cache_timeout=-1, # Don't cache these files on the browser.
max_age=-1, # Don't cache these files on the browser.
last_modified=datetime.now(),
as_attachment=True
)

View File

@ -47,9 +47,9 @@ def render_docx():
return send_file(
io.BytesIO(target_stream.read()),
as_attachment=True,
attachment_filename="output.docx",
download_name="output.docx",
mimetype="application/octet-stream",
cache_timeout=-1 # Don't cache these files on the browser.
max_age=-1 # Don't cache these files on the browser.
)
except ValueError as e:
raise ApiError(code="undefined_field", message=str(e))

View File

@ -1,6 +1,6 @@
import uuid
from SpiffWorkflow import TaskState
from SpiffWorkflow.task import TaskState
from flask import g
from crc import session

View File

@ -1,13 +1,14 @@
import enum
import marshmallow
from SpiffWorkflow.navigation import NavItem
from marshmallow import INCLUDE
from marshmallow_enum import EnumField
from crc import ma
from crc.models.workflow import WorkflowStatus, WorkflowModel
from crc.models.file import FileSchema
from crc.models.nav_item import NavItem
class MultiInstanceType(enum.Enum):
none = "none"
@ -181,12 +182,10 @@ class NavigationItemSchema(ma.Schema):
state = data.pop('state', None)
task_id = data.pop('task_id', None)
children = data.pop('children', [])
spec_type = data.pop('spec_type', None)
item = NavItem(**data)
item.state = state
item.task_id = task_id
item.children = children
item.spec_type = spec_type
return item
class DocumentDirectorySchema(ma.Schema):

95
crc/models/nav_item.py Normal file
View File

@ -0,0 +1,95 @@
from SpiffWorkflow.bpmn.specs.BpmnSpecMixin import BpmnSpecMixin, SequenceFlow
class NavItem(object):
"""
A waypoint in a workflow along with some key metrics
- Each list item has :
spec_id - TaskSpec or Sequence flow id
name - The name of the task spec (or sequence)
spec_type - The type of task spec (it's class name)
task_id - The uuid of the actual task instance, if it exists
description - Text description
backtrack_to - The spec_id of the task this will back track to.
indent - A hint for indentation
lane - This is the lane for the task if indicated.
state - State of the task
"""
def __init__(self, spec_id, name, description, spec_type,
lane=None, backtrack_to=None, indent=0):
self.spec_id = spec_id
self.name = name
self.spec_type = "None"
self.description = description
self.lane = lane
self.backtrack_to = backtrack_to
self.indent = indent
self.task_id = None
self.state = None
self.children = []
@classmethod
def from_spec(cls, spec: BpmnSpecMixin, backtrack_to=None, indent=None):
instance = cls(
spec_id=spec.id,
name=spec.name,
description=spec.description,
spec_type=spec.spec_type,
lane=spec.lane,
backtrack_to=backtrack_to,
indent=indent,
)
return instance
@classmethod
def from_flow(cls, flow: SequenceFlow, lane, backtrack_to, indent):
"""We include flows in the navigation if we hit a conditional gateway,
as in do this if x, do this if y...."""
instance = cls(
spec_id=flow.id,
name=flow.name,
description=flow.name,
lane=lane,
backtrack_to=backtrack_to,
indent=indent
)
instance.set_spec_type(flow)
return instance
def __eq__(self, other):
if isinstance(other, NavItem):
return self.spec_id == other.spec_id and \
self.name == other.name and \
self.spec_type == other.spec_type and \
self.description == other.description and \
self.lane == other.lane and \
self.backtrack_to == other.backtrack_to and \
self.indent == other.indent
return False
def __str__(self):
text = self.description
if self.spec_type == "StartEvent":
text = "O"
elif self.spec_type == "TaskEndEvent":
text = "@"
elif self.spec_type == "ExclusiveGateway":
text = f"X {text} X"
elif self.spec_type == "ParallelGateway":
text = f"+ {text}"
elif self.spec_type == "SequenceFlow":
text = f"-> {text}"
elif self.spec_type[-4:] == "Task":
text = f"[{text}] TASK ID: {self.task_id}"
else:
text = f"({self.spec_type}) {text}"
result = f' {"..," * self.indent} STATE: {self.state} {text}'
if self.lane:
result = f'|{self.lane}| {result}'
if self.backtrack_to:
result += f" (BACKTRACK to {self.backtrack_to}"
return result

View File

@ -74,8 +74,11 @@ And one optional argument:
else:
image_file_data = None
# Just pass along the data in task.data -- not any of the executables.
data = self.just_the_data(task.data)
try:
return JinjaService().make_template(BytesIO(file_data), task.data, image_file_data)
return JinjaService().make_template(BytesIO(file_data), data, image_file_data)
except ApiError as ae:
# In some cases we want to provide a very specific error, that does not get obscured when going
# through the python expression engine. We can do that by throwing a WorkflowTaskExecException,
@ -83,6 +86,8 @@ And one optional argument:
raise WorkflowTaskExecException(task, ae.message, exception=ae, line_number=ae.line_number,
error_line=ae.error_line)
@staticmethod
def get_image_file_data(fields_str, task):
image_file_data = []

View File

@ -1,5 +1,3 @@
from SpiffWorkflow.util.metrics import timeit
from crc.scripts.script import Script
from crc.services.user_file_service import UserFileService

View File

@ -1,8 +1,7 @@
import importlib
import os
import pkgutil
from SpiffWorkflow.util.metrics import timeit
from types import ModuleType
from crc.api.common import ApiError
@ -32,6 +31,17 @@ class Script(object):
"does must provide a validate_only option that mimics the do_task, " +
"but does not make external calls or database updates." )
@staticmethod
def just_the_data(task_data):
"""Task Data can include the full context during execution - such as libraries, builtins, and embedded functions, things you
don't generally want to be working with, this will strip out all of those details, so you are just getting
the serializable data from the Task."""
result = {k: v for (k, v) in task_data.items()
if not hasattr(v, '__call__')
and not isinstance(v, ModuleType)
and not k == '__builtins__'}
return result
@staticmethod
def generate_augmented_list(task, study_id, workflow_id):
"""

View File

@ -1,8 +1,6 @@
import json
from SpiffWorkflow.bpmn.PythonScriptEngine import Box
from SpiffWorkflow.util.metrics import timeit
from crc import session
from crc.api.common import ApiError
from crc.models.protocol_builder import ProtocolBuilderInvestigatorType

View File

@ -1,6 +1,6 @@
import time
from SpiffWorkflow import Task
from SpiffWorkflow.task import Task
cache_store = {}

View File

@ -3,7 +3,6 @@ from json import JSONDecodeError
from typing import List, Optional
import requests
from SpiffWorkflow.util.metrics import timeit
from crc import app
from crc.api.common import ApiError

View File

@ -4,10 +4,9 @@ from dateutil import parser
from typing import List
import requests
from SpiffWorkflow import WorkflowException
from SpiffWorkflow.exceptions import WorkflowException
from SpiffWorkflow.bpmn.PythonScriptEngine import Box
from SpiffWorkflow.bpmn.exceptions import WorkflowTaskExecException
from SpiffWorkflow.util.metrics import timeit, firsttime, sincetime, LOG
from flask import g
from ldap3.core.exceptions import LDAPSocketOpenError
@ -109,18 +108,14 @@ class StudyService(object):
return warnings
@staticmethod
@timeit
def get_study(study_id, categories: List[WorkflowSpecCategory], study_model: StudyModel = None,
master_workflow_results=None, process_categories=False):
"""Returns a study model that contains all the workflows organized by category.
Pass in the results of the master workflow spec, and the status of other workflows will be updated."""
last_time = firsttime()
if not study_model:
study_model = session.query(StudyModel).filter_by(id=study_id).first()
study = Study.from_model(study_model)
last_time = sincetime("from model", last_time)
study.create_user_display = LdapService.user_info(study.user_uid).display_name
last_time = sincetime("user", last_time)
last_event: TaskEventModel = session.query(TaskEventModel) \
.filter_by(study_id=study_id, action='COMPLETE') \
.order_by(TaskEventModel.date.desc()).first()
@ -130,12 +125,10 @@ class StudyService(object):
else:
study.last_activity_user = LdapService.user_info(last_event.user_uid).display_name
study.last_activity_date = last_event.date
last_time = sincetime("task_events", last_time)
study.categories = categories
files = UserFileService.get_files_for_study(study.id)
files = (File.from_file_model(model, DocumentService.get_dictionary()) for model in files)
study.files = list(files)
last_time = sincetime("files", last_time)
if process_categories and master_workflow_results is not None:
if study.status != StudyStatus.abandoned:
workflow_metas = []
@ -149,8 +142,6 @@ class StudyService(object):
category.meta = category_meta
study.warnings = StudyService.get_study_warnings(workflow_metas, master_workflow_results)
last_time = sincetime("categories", last_time)
if study.primary_investigator is None:
associates = StudyService().get_study_associates(study.id)
for associate in associates:
@ -449,7 +440,6 @@ class StudyService(object):
return {}
@staticmethod
@timeit
def synch_with_protocol_builder_if_enabled(user, specs):
"""Assures that the studies we have locally for the given user are
in sync with the studies available in protocol builder. """

View File

@ -97,7 +97,8 @@ class TaskLoggingService(object):
sort_column = desc(task_log_query.sort_column)
else:
sort_column = task_log_query.sort_column
paginator = sql_query.order_by(sort_column).paginate(task_log_query.page + 1, task_log_query.per_page,
paginator = sql_query.order_by(sort_column).paginate(page=task_log_query.page + 1,
per_page=task_log_query.per_page,
error_out=False)
task_log_query.update_from_sqlalchemy_paginator(paginator)
return task_log_query

View File

@ -3,14 +3,16 @@ from typing import List
from SpiffWorkflow.bpmn.PythonScriptEngine import PythonScriptEngine
from SpiffWorkflow.bpmn.serializer import BpmnWorkflowSerializer
from SpiffWorkflow.bpmn.specs.events import EndEvent, CancelEventDefinition
from SpiffWorkflow.bpmn.specs.events import EndEvent, SignalEventDefinition
from SpiffWorkflow.camunda.serializer import UserTaskConverter
from SpiffWorkflow.dmn.serializer import BusinessRuleTaskConverter
from SpiffWorkflow.serializer.exceptions import MissingSpecError
from lxml import etree
from datetime import datetime
from SpiffWorkflow import Task as SpiffTask, WorkflowException, Task, TaskState
from SpiffWorkflow.task import Task as SpiffTask, Task, TaskState
from SpiffWorkflow.exceptions import WorkflowException
from SpiffWorkflow.bpmn.parser.ValidationException import ValidationException
from SpiffWorkflow.bpmn.serializer.BpmnSerializer import BpmnSerializer
from SpiffWorkflow.bpmn.workflow import BpmnWorkflow
@ -40,7 +42,7 @@ class CustomBpmnScriptEngine(PythonScriptEngine):
scripts directory available for execution. """
def evaluate(self, task, expression):
return self._evaluate(expression, task.data, task)
return self._evaluate(expression, task.data, task=task)
def __get_augment_methods(self, task):
methods = []
@ -67,16 +69,16 @@ class CustomBpmnScriptEngine(PythonScriptEngine):
if(external_methods):
methods.update(external_methods)
try:
return super()._evaluate(expression, context, task, methods)
return super()._evaluate(expression, context, methods)
except Exception as e:
raise WorkflowTaskExecException(task,
"Error evaluating expression "
"'%s', %s" % (expression, str(e)))
def execute(self, task: SpiffTask, script, data):
def execute(self, task: SpiffTask, script):
try:
augment_methods = self.__get_augment_methods(task)
super().execute(task, script, data, external_methods=augment_methods)
super().execute(task, script, external_methods=augment_methods)
except WorkflowException as e:
raise e
except Exception as e:
@ -326,8 +328,7 @@ class WorkflowProcessor(object):
def __cancel_notify(bpmn_workflow):
try:
# A little hackly, but make the bpmn_workflow catch a cancel event.
bpmn_workflow.signal('cancel') # generate a cancel signal.
bpmn_workflow.catch(CancelEventDefinition())
bpmn_workflow.catch(SignalEventDefinition('cancel'))
bpmn_workflow.do_engine_steps()
except WorkflowTaskExecException as we:
raise ApiError.from_workflow_exception("task_error", str(we), we)

View File

@ -8,7 +8,8 @@ from datetime import datetime
from typing import List
import jinja2
from SpiffWorkflow import Task as SpiffTask, WorkflowException, NavItem, TaskState
from SpiffWorkflow.task import Task as SpiffTask, TaskState, TaskStateNames
from SpiffWorkflow.exceptions import WorkflowException
from SpiffWorkflow.bpmn.PythonScriptEngine import Box
from SpiffWorkflow.bpmn.specs.ManualTask import ManualTask
from SpiffWorkflow.bpmn.specs.ScriptTask import ScriptTask
@ -26,6 +27,7 @@ from crc.api.common import ApiError
from crc.models.api_models import Task, MultiInstanceType, WorkflowApi
from crc.models.file import LookupDataModel, FileModel, File, FileSchema
from crc.models.ldap import LdapModel
from crc.models.nav_item import NavItem
from crc.models.study import StudyModel
from crc.models.task_event import TaskEventModel, TaskAction
from crc.models.user import UserModel
@ -674,7 +676,7 @@ class WorkflowService(object):
if any(nav.name == user_task.task_spec.name and user_task.state == TaskState.FUTURE for nav in navigation):
continue # Don't re-add the same spec for future items
nav_item = NavItem.from_spec(spec=user_task.task_spec)
nav_item.state = user_task.state.name
nav_item.state = TaskStateNames[user_task.state]
nav_item.task_id = user_task.id
nav_item.indent = 0 # we should remove indent, this is not nested now.
navigation.append(nav_item)
@ -685,8 +687,8 @@ class WorkflowService(object):
def processor_to_workflow_api(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."""
navigation = processor.bpmn_workflow.get_deep_nav_list()
WorkflowService.update_navigation(navigation, processor)
# navigation = processor.bpmn_workflow.get_deep_nav_list()
# WorkflowService.update_navigation(navigation, processor)
spec_service = WorkflowSpecService()
spec = spec_service.get_spec(processor.workflow_spec_id)
is_admin_workflow = WorkflowService.is_admin_workflow(processor.workflow_spec_id)
@ -772,16 +774,7 @@ class WorkflowService(object):
@staticmethod
def spiff_task_to_api_task(spiff_task, add_docs_and_forms=False):
task_type = spiff_task.task_spec.__class__.__name__
task_types = [UserTask, ManualTask, BusinessRuleTask, CancelTask, ScriptTask, StartTask, EndEvent, StartEvent]
for t in task_types:
if isinstance(spiff_task.task_spec, t):
task_type = t.__name__
break
else:
task_type = "NoneTask"
task_type = spiff_task.task_spec.spec_type
info = spiff_task.task_info()
if info["is_looping"]:
@ -802,7 +795,6 @@ class WorkflowService(object):
lane = spiff_task.task_spec.lane
else:
lane = None
task = Task(spiff_task.id,
spiff_task.task_spec.name,
spiff_task.task_spec.description,

View File

@ -16,11 +16,8 @@ class ExampleDataLoader:
@staticmethod
def clean_db():
session.flush() # Clear out any transactions before deleting it all to avoid spurious errors.
engine = session.bind.engine
connection = engine.connect()
for table in reversed(db.metadata.sorted_tables):
if engine.dialect.has_table(connection, table):
session.execute(table.delete())
session.execute(table.delete())
session.commit()
session.flush()

View File

@ -94,8 +94,9 @@ class BaseTest(unittest.TestCase):
@classmethod
def tearDownClass(cls):
cls.ctx.pop()
db.session.commit()
db.drop_all()
cls.ctx.pop()
def setUp(self):
pass
@ -212,14 +213,18 @@ class BaseTest(unittest.TestCase):
return data
def assert_success(self, rv, msg=""):
error_message = ""
try:
data = json.loads(rv.get_data(as_text=True))
self.assertTrue(200 <= rv.status_code < 300,
"BAD Response: %i. \n %s" %
(rv.status_code, data['message']) + ". " + msg + ". ")
except:
self.assertTrue(200 <= rv.status_code < 300,
"BAD Response: %i." % rv.status_code + ". " + msg)
if 'message' in data:
error_message = data['message']
except Exception as e:
# Can't get an error message from the body.
error_message = "unparsable response"
self.assertTrue(200 <= rv.status_code < 300,
"BAD Response: %i. \n %s" %
(rv.status_code, error_message + ". " + msg + ". "))
def assert_failure(self, rv, status_code=0, error_code=""):
self.assertFalse(200 <= rv.status_code < 300,

View File

@ -1,5 +1,5 @@
<?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:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_3fd9241" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.0.0-dev">
<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:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_3fd9241" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="5.0.0">
<bpmn:process id="Process_9d7b2c2" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>Flow_17nzcku</bpmn:outgoing>
@ -8,12 +8,12 @@
<bpmn:scriptTask id="Activity_GetCheckStudy" name="Get Check Study">
<bpmn:incoming>Flow_17nzcku</bpmn:incoming>
<bpmn:outgoing>Flow_0oozrfg</bpmn:outgoing>
<bpmn:script>check_study = check_study()</bpmn:script>
<bpmn:script>my_check_study = check_study()</bpmn:script>
</bpmn:scriptTask>
<bpmn:sequenceFlow id="Flow_0oozrfg" sourceRef="Activity_GetCheckStudy" targetRef="Activity_DisplayCheckStudy" />
<bpmn:manualTask id="Activity_DisplayCheckStudy" name="Display Check Study">
<bpmn:documentation># Check Study
&lt;div&gt;&lt;span&gt;{{check_study}}&lt;/span&gt;&lt;/div&gt;</bpmn:documentation>
&lt;div&gt;&lt;span&gt;{{my_check_study}}&lt;/span&gt;&lt;/div&gt;</bpmn:documentation>
<bpmn:incoming>Flow_0oozrfg</bpmn:incoming>
<bpmn:outgoing>Flow_10sc31i</bpmn:outgoing>
</bpmn:manualTask>
@ -25,28 +25,28 @@
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_9d7b2c2">
<bpmndi:BPMNEdge id="Flow_10sc31i_di" bpmnElement="Flow_10sc31i">
<di:waypoint x="530" y="177" />
<di:waypoint x="592" y="177" />
<di:waypoint x="530" y="117" />
<di:waypoint x="592" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0oozrfg_di" bpmnElement="Flow_0oozrfg">
<di:waypoint x="370" y="177" />
<di:waypoint x="430" y="177" />
<di:waypoint x="370" y="117" />
<di:waypoint x="430" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_17nzcku_di" bpmnElement="Flow_17nzcku">
<di:waypoint x="215" y="177" />
<di:waypoint x="270" y="177" />
<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="159" width="36" height="36" />
<dc:Bounds x="179" y="99" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1f9d5ew_di" bpmnElement="Activity_GetCheckStudy">
<dc:Bounds x="270" y="137" width="100" height="80" />
<dc:Bounds x="270" y="77" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_01vscea_di" bpmnElement="Activity_DisplayCheckStudy">
<dc:Bounds x="430" y="137" width="100" height="80" />
<dc:Bounds x="430" y="77" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_0embsc7_di" bpmnElement="Event_0embsc7">
<dc:Bounds x="592" y="159" width="36" height="36" />
<dc:Bounds x="592" y="99" width="36" height="36" />
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>

View File

@ -1,5 +1,5 @@
<?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:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_0a9entn" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="4.10.0">
<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:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_0a9entn" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="5.0.0">
<bpmn:process id="Process_1dagb7t" name="TestMessage" isExecutable="true">
<bpmn:startEvent id="StartEvent_1" name="Start">
<bpmn:outgoing>Flow_0xym55y</bpmn:outgoing>
@ -14,7 +14,7 @@ print('New Title')</bpmn:script>
<bpmn:userTask id="Activity_HowMany" name="HowMany" camunda:formKey="HowMany">
<bpmn:extensionElements>
<camunda:formData>
<camunda:formField id="how_many" label="'How many?'" type="long" defaultValue="1" />
<camunda:formField id="how_many" label="&#39;How many?&#39;" type="long" defaultValue="1" />
</camunda:formData>
</bpmn:extensionElements>
<bpmn:incoming>Flow_1e9j7mj</bpmn:incoming>
@ -51,7 +51,7 @@ print('Second Title')</bpmn:script>
<bpmn:userTask id="Activity_Modify" name="Modify" camunda:formKey="FormModify">
<bpmn:extensionElements>
<camunda:formData>
<camunda:formField id="modify" label="'Modify Data?'" type="boolean" defaultValue="True" />
<camunda:formField id="modify" label="&#39;Modify Data?&#39;" type="boolean" defaultValue="True" />
</camunda:formData>
</bpmn:extensionElements>
<bpmn:incoming>Flow_0tbg5qf</bpmn:incoming>
@ -63,24 +63,37 @@ print('Second Title')</bpmn:script>
<bpmn:sequenceFlow id="Flow_0tbg5qf" sourceRef="Event_0eajey9" targetRef="Activity_Modify" />
<bpmn:sequenceFlow id="Flow_1vk7gok" sourceRef="Activity_Modify" targetRef="Event_11vagro" />
</bpmn:transaction>
<bpmn:boundaryEvent id="Event_0dufc7h" attachedToRef="Activity_0kb7m1i">
<bpmn:outgoing>Flow_1qqhbqp</bpmn:outgoing>
<bpmn:cancelEventDefinition id="CancelEventDefinition_198zuch" />
</bpmn:boundaryEvent>
<bpmn:sequenceFlow id="Flow_1qqhbqp" sourceRef="Event_0dufc7h" targetRef="Activity_CancelMessage" />
<bpmn:boundaryEvent id="Event_TokenReset" name="TokenReset" attachedToRef="Activity_HowMany">
<bpmn:outgoing>Flow_16q1uec</bpmn:outgoing>
<bpmn:signalEventDefinition id="SignalEventDefinition_0axomz7" signalRef="Signal_0ymiy61" />
</bpmn:boundaryEvent>
<bpmn:sequenceFlow id="Flow_0zkgcak" sourceRef="Activity_0kb7m1i" targetRef="Activity_GoodBye" />
<bpmn:boundaryEvent id="Event_0dufc7h" name="cancel" attachedToRef="Activity_0kb7m1i">
<bpmn:outgoing>Flow_1qqhbqp</bpmn:outgoing>
<bpmn:signalEventDefinition id="SignalEventDefinition_10ag4b2" signalRef="Signal_0ymiy61" />
</bpmn:boundaryEvent>
</bpmn:process>
<bpmn:message id="Message_0iyvlbz" name="token_reset" />
<bpmn:message id="Message_1ow6ruy" name="Message_00ldv4i" />
<bpmn:signal id="Signal_1fbgshz" name="token_reset" />
<bpmn:message id="Message_1czi5ye" name="token_reset" />
<bpmn:signal id="Signal_0ymiy61" name="cancel" />
<bpmn:message id="Message_0guondt" name="cancel" />
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1dagb7t">
<bpmndi:BPMNEdge id="Flow_0zkgcak_di" bpmnElement="Flow_0zkgcak">
<di:waypoint x="960" y="178" />
<di:waypoint x="1090" y="178" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1qqhbqp_di" bpmnElement="Flow_1qqhbqp">
<di:waypoint x="800" y="296" />
<di:waypoint x="800" y="329" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1avyz25_di" bpmnElement="Flow_1avyz25">
<di:waypoint x="541" y="178" />
<di:waypoint x="610" y="178" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0rus4fi_di" bpmnElement="Flow_0rus4fi">
<di:waypoint x="1190" y="178" />
<di:waypoint x="1312" y="178" />
@ -100,24 +113,15 @@ print('Second Title')</bpmn:script>
<dc:Bounds x="496" y="256" width="89" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1avyz25_di" bpmnElement="Flow_1avyz25">
<di:waypoint x="541" y="178" />
<di:waypoint x="610" y="178" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1qqhbqp_di" bpmnElement="Flow_1qqhbqp">
<di:waypoint x="800" y="296" />
<di:waypoint x="800" y="329" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0zkgcak_di" bpmnElement="Flow_0zkgcak">
<di:waypoint x="960" y="178" />
<di:waypoint x="1090" y="178" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="153" y="160" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="159" y="203" width="24" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0bieozg_di" bpmnElement="Activity_TestMessage">
<dc:Bounds x="441" y="320" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0hkt70o_di" bpmnElement="Activity_HowMany">
<dc:Bounds x="441" y="138" width="100" height="80" />
</bpmndi:BPMNShape>
@ -133,20 +137,17 @@ print('Second Title')</bpmn:script>
<bpmndi:BPMNShape id="Activity_0a9mhpp_di" bpmnElement="Activity_CancelMessage">
<dc:Bounds x="750" y="329" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0bieozg_di" bpmnElement="Activity_TestMessage">
<dc:Bounds x="441" y="320" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0wggmha_di" bpmnElement="Activity_0kb7m1i" isExpanded="true">
<dc:Bounds x="610" y="78" width="350" height="200" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_0tbg5qf_di" bpmnElement="Flow_0tbg5qf">
<di:waypoint x="686" y="178" />
<di:waypoint x="730" y="178" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1vk7gok_di" bpmnElement="Flow_1vk7gok">
<di:waypoint x="830" y="178" />
<di:waypoint x="882" y="178" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0tbg5qf_di" bpmnElement="Flow_0tbg5qf">
<di:waypoint x="686" y="178" />
<di:waypoint x="730" y="178" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="Event_0eajey9_di" bpmnElement="Event_0eajey9">
<dc:Bounds x="650" y="160" width="36" height="36" />
</bpmndi:BPMNShape>
@ -156,15 +157,18 @@ print('Second Title')</bpmn:script>
<bpmndi:BPMNShape id="Event_11vagro_di" bpmnElement="Event_11vagro">
<dc:Bounds x="882" y="160" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_1qq0qh3_di" bpmnElement="Event_0dufc7h">
<dc:Bounds x="782" y="260" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_1uudkk1_di" bpmnElement="Event_TokenReset">
<dc:Bounds x="473" y="200" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="501" y="234" width="59" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_1rbvyil_di" bpmnElement="Event_0dufc7h">
<dc:Bounds x="782" y="260" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="814" y="293" width="32" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>
</bpmn:definitions>

View File

@ -1,5 +1,5 @@
<?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:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" id="Definitions_0kmksnn" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.7.3">
<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:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" id="Definitions_0kmksnn" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="5.0.0">
<bpmn:process id="Process_0exnnpv" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>SequenceFlow_1nfe5m9</bpmn:outgoing>
@ -32,7 +32,7 @@ out4 = get_study_associate('lb3dp')</bpmn:script>
<bpmn:outgoing>Flow_1vlh6s0</bpmn:outgoing>
<bpmn:script>uids = []
for assoc in out:
uids.append(assoc.uid)
uids.append(assoc['uid'])
update_study_associates([{'uid':'lb3dp','role':'SuperGal','send_email':False,'access':True}])</bpmn:script>
</bpmn:scriptTask>
<bpmn:sequenceFlow id="Flow_1vlh6s0" sourceRef="Activity_0run091" targetRef="Activity_0d8iftx" />

View File

@ -1,5 +1,5 @@
<?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:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_3435c8d" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.0.0-dev">
<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:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_3435c8d" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="5.0.0">
<bpmn:process id="Process_3435c8d" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>Flow_0g8e1jy</bpmn:outgoing>
@ -8,8 +8,8 @@
<bpmn:userTask id="Activity_GetData" name="Get Data" camunda:formKey="GetDataForm">
<bpmn:extensionElements>
<camunda:formData>
<camunda:formField id="short_name" label="'Short Name'" type="string" />
<camunda:formField id="proposal_name" label="'Proposal Name'" type="string" />
<camunda:formField id="short_name" label="&#39;Short Name&#39;" type="string" />
<camunda:formField id="proposal_name" label="&#39;Proposal Name&#39;" type="string" />
</camunda:formData>
</bpmn:extensionElements>
<bpmn:incoming>Flow_0g8e1jy</bpmn:incoming>
@ -25,12 +25,12 @@
<bpmn:scriptTask id="Activity_GetStudyInfo" name="Get Study Info">
<bpmn:incoming>Flow_0xf0u0k</bpmn:incoming>
<bpmn:outgoing>Flow_0tzamxo</bpmn:outgoing>
<bpmn:script>study_info = study_info("info")</bpmn:script>
<bpmn:script>my_study_info = study_info("info")</bpmn:script>
</bpmn:scriptTask>
<bpmn:sequenceFlow id="Flow_0tzamxo" sourceRef="Activity_GetStudyInfo" targetRef="Activity_DisplayInfo" />
<bpmn:manualTask id="Activity_DisplayInfo" name="Display Study Info">
<bpmn:documentation># Info
{{ study_info }}
{{ my_study_info }}
</bpmn:documentation>
<bpmn:incoming>Flow_0tzamxo</bpmn:incoming>
<bpmn:outgoing>Flow_0x5ex3d</bpmn:outgoing>
@ -43,42 +43,42 @@
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_3435c8d">
<bpmndi:BPMNEdge id="Flow_0x5ex3d_di" bpmnElement="Flow_0x5ex3d">
<di:waypoint x="850" y="177" />
<di:waypoint x="912" y="177" />
<di:waypoint x="850" y="117" />
<di:waypoint x="912" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0tzamxo_di" bpmnElement="Flow_0tzamxo">
<di:waypoint x="690" y="177" />
<di:waypoint x="750" y="177" />
<di:waypoint x="690" y="117" />
<di:waypoint x="750" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0xf0u0k_di" bpmnElement="Flow_0xf0u0k">
<di:waypoint x="530" y="177" />
<di:waypoint x="590" y="177" />
<di:waypoint x="530" y="117" />
<di:waypoint x="590" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_09flr91_di" bpmnElement="Flow_09flr91">
<di:waypoint x="370" y="177" />
<di:waypoint x="430" y="177" />
<di:waypoint x="370" y="117" />
<di:waypoint x="430" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0g8e1jy_di" bpmnElement="Flow_0g8e1jy">
<di:waypoint x="215" y="177" />
<di:waypoint x="270" y="177" />
<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="159" width="36" height="36" />
<dc:Bounds x="179" y="99" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_06lg3xc_di" bpmnElement="Activity_GetData">
<dc:Bounds x="270" y="137" width="100" height="80" />
<dc:Bounds x="270" y="77" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0ulzpsr_di" bpmnElement="Activity_UpdateStudy">
<dc:Bounds x="430" y="137" width="100" height="80" />
<dc:Bounds x="430" y="77" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_15xfbrf_di" bpmnElement="Activity_GetStudyInfo">
<dc:Bounds x="590" y="137" width="100" height="80" />
<dc:Bounds x="590" y="77" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1yteq9j_di" bpmnElement="Activity_DisplayInfo">
<dc:Bounds x="750" y="137" width="100" height="80" />
<dc:Bounds x="750" y="77" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_05xjsir_di" bpmnElement="Event_05xjsir">
<dc:Bounds x="912" y="159" width="36" height="36" />
<dc:Bounds x="912" y="99" width="36" height="36" />
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>

View File

@ -73,5 +73,5 @@ class TestLanePermissions(BaseTest):
# Complete the workflow as lje5u
workflow_api = self.complete_form(workflow, third_task, {},
user_uid='lje5u')
self.assertEqual('EndEvent', workflow_api.next_task.type)
self.assertEqual('End Event', workflow_api.next_task.type)
self.assertEqual('COMPLETED', workflow_api.next_task.state)

View File

@ -31,7 +31,7 @@ class TestMultiinstanceTasksApi(BaseTest):
workflow_api = self.get_workflow_api(workflow)
navigation = self.get_workflow_api(workflow_api).navigation
self.assertEqual(2, len(navigation)) # Start task, multi-instance/user task
self.assertEqual("UserTask", workflow_api.next_task.type)
self.assertEqual("User Task", workflow_api.next_task.type)
self.assertEqual(MultiInstanceType.sequential.value, workflow_api.next_task.multi_instance_type)
self.assertEqual(5, workflow_api.next_task.multi_instance_count)
@ -56,7 +56,7 @@ class TestMultiinstanceTasksApi(BaseTest):
ready_items = [nav for nav in workflow_api.navigation if nav.state == "READY"]
self.assertEqual(5, len(ready_items)) # Just the 5 investigators.
self.assertEqual("UserTask", workflow_api.next_task.type)
self.assertEqual("User Task", workflow_api.next_task.type)
self.assertEqual("MultiInstanceTask",workflow_api.next_task.name)
self.assertEqual("Primary Investigator", workflow_api.next_task.title)
@ -94,7 +94,7 @@ class TestMultiinstanceTasksApi(BaseTest):
ready_items = [nav for nav in workflow_api.navigation if nav.state == "READY"]
self.assertEqual(5, len(ready_items))
self.assertEqual("UserTask", workflow_api.next_task.type)
self.assertEqual("User Task", workflow_api.next_task.type)
self.assertEqual("MultiInstanceTask",workflow_api.next_task.name)
self.assertEqual("Primary Investigator", workflow_api.next_task.title)

View File

@ -46,10 +46,7 @@ class TestStudyInfoScript(BaseTest):
workflow_api = self.complete_form(workflow, task, {'short_name': short_name, 'proposal_name': proposal_name})
task = workflow_api.next_task
# The workflow calls study_info('info') and puts the result in Element Documentation
# I create a dictionary of that info with `eval` to make the asserts easier to read
study_info = eval(task.documentation)
study_info = task.data['my_study_info']
self.assertIn('short_name', study_info.keys())
self.assertEqual(short_name, study_info['short_name'])
self.assertIn('proposal_name', study_info.keys())

View File

@ -63,7 +63,7 @@ class TestTasksApi(BaseTest):
self.assertEqual('two_forms', workflow_api.workflow_spec_id)
self.assertEqual(3, len(workflow_api.navigation)) # start and 2 forms
self.assertIsNotNone(workflow_api.next_task.form)
self.assertEqual("UserTask", workflow_api.next_task.type)
self.assertEqual("User Task", workflow_api.next_task.type)
self.assertEqual("StepOne", workflow_api.next_task.name)
self.assertEqual(1, len(workflow_api.next_task.form['fields']))
@ -98,40 +98,6 @@ 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):
workflow = self.create_workflow('exclusive_gateway')
processor = WorkflowProcessor(workflow)
nav = processor.bpmn_workflow.get_deep_nav_list()
self.assertEqual(4, len(nav))
self.assertEqual("Enter Do You Have Bananas", nav[1].description)
self.assertEqual("Has Bananas?", nav[2].description)
self.assertEqual("yes", nav[2].children[0].description)
self.assertEqual("MAYBE", nav[2].children[0].state)
self.assertEqual("Number of Bananas", nav[2].children[0].children[0].description)
self.assertEqual("EndEvent", nav[2].children[0].children[1].spec_type)
self.assertEqual("no", nav[2].children[1].description)
self.assertEqual("MAYBE", nav[2].children[1].state)
self.assertEqual("Why no bananas", nav[2].children[1].children[0].description)
self.assertEqual("EndEvent", nav[2].children[1].children[1].spec_type)
def test_navigation_with_exclusive_gateway(self):
workflow = self.create_workflow('exclusive_gateway_2')
processor = WorkflowProcessor(workflow)
# get the first form in the two form workflow.
nav = processor.bpmn_workflow.get_deep_nav_list()
self.assertEqual(7, len(nav))
self.assertEqual("Enter Task 1", nav[1].description)
self.assertEqual("Decide Which Branch?", nav[2].description)
self.assertEqual("a", nav[2].children[0].description)
self.assertEqual("Enter Task 2a", nav[2].children[0].children[0].description)
self.assertEqual("b", nav[2].children[1].description)
self.assertEqual("Enter Task 2b", nav[2].children[1].children[0].description)
self.assertEqual(None, nav[3].description)
self.assertEqual("Enter Task 3", nav[4].description)
self.assertEqual("EndEvent", nav[5].spec_type)
def test_document_added_to_workflow_shows_up_in_file_list(self):
self.create_reference_document()
workflow = self.create_workflow('docx')
@ -226,7 +192,7 @@ class TestTasksApi(BaseTest):
workflow = self.get_workflow_api(workflow)
self.assertEqual('Task_Manual_One', workflow.next_task.name)
self.assertEqual('ManualTask', workflow_api.next_task.type)
self.assertEqual('Manual Task', workflow_api.next_task.type)
self.assertTrue('Markdown' in workflow_api.next_task.documentation)
self.assertTrue('Dan' in workflow_api.next_task.documentation)
@ -351,7 +317,7 @@ class TestTasksApi(BaseTest):
task = workflow_api.next_task
self.assertEqual(4, len(navigation)) # 2 start events + 2 user tasks
self.assertEqual("UserTask", task.type)
self.assertEqual("User Task", task.type)
self.assertEqual("Activity_A", task.name)
self.assertEqual("My Sub Process", task.process_name)
workflow_api = self.complete_form(workflow, task, {"FieldA": "Dan"})

View File

@ -189,7 +189,7 @@ class TestUserRoles(BaseTest):
workflow_api = self.get_workflow_api(workflow, user_uid=submitter.uid)
self.assertEqual('COMPLETED', workflow_api.next_task.state)
self.assertEqual('EndEvent', workflow_api.next_task.type) # Are are at the end.
self.assertEqual('End Event', workflow_api.next_task.type) # Are are at the end.
self.assertEqual(WorkflowStatus.complete, workflow_api.status)
def get_assignment_task_events(self, uid):
@ -245,7 +245,7 @@ class TestUserRoles(BaseTest):
data["approval"] = True
workflow_api = self.complete_form(workflow, workflow_api.next_task, data, user_uid=supervisor.uid)
self.assertEqual(WorkflowStatus.complete, workflow_api.status)
self.assertEqual('EndEvent', workflow_api.next_task.type) # Are are at the end.
self.assertEqual('End Event', workflow_api.next_task.type) # Are are at the end.
self.assertEqual(0, len(self.get_assignment_task_events(submitter.uid)))
self.assertEqual(0, len(self.get_assignment_task_events(supervisor.uid)))

View File

@ -11,4 +11,4 @@ class TestNameErrorHint(BaseTest):
spec_model = self.load_test_spec('script_with_name_error')
rv = self.app.get('/v1.0/workflow-specification/%s/validate' % spec_model.id, headers=self.logged_in_headers())
json_data = json.loads(rv.get_data(as_text=True))
self.assertIn('Did you mean \'[\'spam\'', json_data[0]['message'])
self.assertIn('Did you mean one of \'[\'spam\'', json_data[0]['message'])

View File

@ -1,7 +1,7 @@
import json
import os
from SpiffWorkflow import TaskState
from SpiffWorkflow.task import TaskState
from SpiffWorkflow.bpmn.serializer.BpmnSerializer import BpmnSerializer
from tests.base_test import BaseTest

View File

@ -44,7 +44,6 @@ class TestWorkflowProcessorMultiInstance(BaseTest):
# This depends on getting a list of investigators back from the protocol builder.
mock_study_service.return_value = self.mock_investigator_response
workflow_spec_model = self.create_workflow("irb_api_personnel")
study = session.query(StudyModel).first()
processor = WorkflowProcessor(workflow_spec_model)
@ -52,12 +51,13 @@ class TestWorkflowProcessorMultiInstance(BaseTest):
task_list = processor.get_ready_user_tasks()
processor.complete_task(task_list[0])
processor.do_engine_steps()
nav_list = processor.bpmn_workflow.get_flat_nav_list()
processor.save()
# reload after save
processor = WorkflowProcessor(workflow_spec_model)
nav_list2 = processor.bpmn_workflow.get_flat_nav_list()
self.assertEqual(nav_list,nav_list2)
task_list = processor.get_ready_user_tasks()
data = task_list[0].data
self.assertIn('investigators', data)
self.assertEqual(data['investigators'], self.mock_investigator_response)
@patch('crc.services.study_service.StudyService.get_investigators')
def test_create_and_complete_workflow(self, mock_study_service):
@ -156,9 +156,6 @@ class TestWorkflowProcessorMultiInstance(BaseTest):
# In the Parallel instance, there should be three tasks, all of them in the ready state.
next_user_tasks = processor.next_user_tasks()
self.assertEqual(3, len(next_user_tasks))
# There should be six tasks in the navigation: start event, the script task, end event, and three tasks
# for the three executions of hte multi-instance.
self.assertEqual(7, len(processor.bpmn_workflow.get_flat_nav_list()))
# We can complete the tasks out of order.
task = next_user_tasks[2]
@ -176,7 +173,6 @@ class TestWorkflowProcessorMultiInstance(BaseTest):
task.update_data({"investigator": {"email": "dhf8r@virginia.edu"}})
processor.complete_task(task)
processor.do_engine_steps()
self.assertEqual(7, len(processor.bpmn_workflow.get_flat_nav_list()))
task = next_user_tasks[0]
api_task = WorkflowService.spiff_task_to_api_task(task)
@ -184,7 +180,6 @@ class TestWorkflowProcessorMultiInstance(BaseTest):
task.update_data({"investigator":{"email":"asd3v@virginia.edu"}})
processor.complete_task(task)
processor.do_engine_steps()
self.assertEqual(7, len(processor.bpmn_workflow.get_flat_nav_list()))
task = next_user_tasks[1]
api_task = WorkflowService.spiff_task_to_api_task(task)
@ -192,7 +187,6 @@ class TestWorkflowProcessorMultiInstance(BaseTest):
task.update_data({"investigator":{"email":"asdf32@virginia.edu"}})
processor.complete_task(task)
processor.do_engine_steps()
self.assertEqual(7, len(processor.bpmn_workflow.get_flat_nav_list()))
# Completing the tasks out of order, still provides the correct information.
expected = self.mock_investigator_response
@ -203,4 +197,3 @@ class TestWorkflowProcessorMultiInstance(BaseTest):
task.data['StudyInfo']['investigators'])
self.assertEqual(WorkflowStatus.complete, processor.get_status())
self.assertEqual(7, len(processor.bpmn_workflow.get_flat_nav_list()))

View File

@ -1,7 +1,5 @@
import json
import time
from SpiffWorkflow.util.metrics import timeit
from unittest.mock import patch
from tests.base_test import BaseTest
@ -54,7 +52,7 @@ class TestWorkflowSpecValidation(BaseTest):
self.assertEqual("ExclusiveGateway_003amsm", errors[0]['task_id'])
self.assertEqual("Has Bananas Gateway", errors[0]['task_name'])
self.assertEqual("invalid_expression.bpmn", errors[0]['file_name'])
self.assertEqual('ExclusiveGateway_003amsm: Error evaluating expression \'this_value_does_not_exist==true\', '
self.assertEqual('Error evaluating expression \'this_value_does_not_exist==true\', '
'name \'this_value_does_not_exist\' is not defined', errors[0]["message"])
self.assertIsNotNone(errors[0]['task_data'])
self.assertIn("has_bananas", errors[0]['task_data'])