mirror of
https://github.com/sartography/cr-connect-workflow.git
synced 2025-02-21 20:28:10 +00:00
Merge pull request #570 from sartography/feature/spiffworkflow_1_2
Feature/spiffworkflow 1 2
This commit is contained in:
commit
23bac81260
@ -1,2 +1,3 @@
|
||||
python 3.10.4
|
||||
python 3.9.12
|
||||
python 3.9.1
|
||||
|
1
Pipfile
1
Pipfile
@ -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
850
Pipfile.lock
generated
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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()
|
||||
)
|
||||
|
@ -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.
|
||||
)
|
||||
|
||||
|
||||
|
@ -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',
|
||||
|
@ -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
|
||||
)
|
||||
|
@ -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))
|
||||
|
@ -1,6 +1,6 @@
|
||||
import uuid
|
||||
|
||||
from SpiffWorkflow import TaskState
|
||||
from SpiffWorkflow.task import TaskState
|
||||
from flask import g
|
||||
|
||||
from crc import session
|
||||
|
@ -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
95
crc/models/nav_item.py
Normal 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
|
@ -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 = []
|
||||
|
@ -1,5 +1,3 @@
|
||||
from SpiffWorkflow.util.metrics import timeit
|
||||
|
||||
from crc.scripts.script import Script
|
||||
from crc.services.user_file_service import UserFileService
|
||||
|
||||
|
@ -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):
|
||||
"""
|
||||
|
@ -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
|
||||
|
@ -1,6 +1,6 @@
|
||||
import time
|
||||
|
||||
from SpiffWorkflow import Task
|
||||
from SpiffWorkflow.task import Task
|
||||
|
||||
cache_store = {}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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. """
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
<div><span>{{check_study}}</span></div></bpmn:documentation>
|
||||
<div><span>{{my_check_study}}</span></div></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>
|
||||
|
@ -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="'How many?'" 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="'Modify Data?'" 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>
|
||||
|
@ -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" />
|
||||
|
@ -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="'Short Name'" type="string" />
|
||||
<camunda:formField id="proposal_name" label="'Proposal Name'" 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>
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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())
|
||||
|
@ -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"})
|
||||
|
@ -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)))
|
||||
|
||||
|
@ -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'])
|
||||
|
@ -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
|
||||
|
@ -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()))
|
||||
|
@ -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'])
|
||||
|
Loading…
x
Reference in New Issue
Block a user