Merge branch 'dev' into study-associated-email-207-223
This commit is contained in:
commit
e7c78c8d0e
|
@ -63,7 +63,7 @@ if app.config['SENTRY_ENVIRONMENT']:
|
|||
# Connexion Error handling
|
||||
def render_errors(exception):
|
||||
from crc.api.common import ApiError, ApiErrorSchema
|
||||
error = ApiError(code=exception.title, message=exception.details, status_code=exception.status)
|
||||
error = ApiError(code=exception.title, message=exception.detail, status_code=exception.status)
|
||||
return Response(ApiErrorSchema().dump(error), status=401, mimetype="application/json")
|
||||
|
||||
|
||||
|
|
|
@ -9,10 +9,8 @@ from jinja2 import Template, UndefinedError
|
|||
from crc.api.common import ApiError
|
||||
from crc.scripts.complete_template import CompleteTemplate
|
||||
from crc.scripts.script import Script
|
||||
import crc.scripts
|
||||
from crc.services.workflow_processor import WorkflowProcessor
|
||||
|
||||
from crc.services.email_service import EmailService
|
||||
from crc.services.workflow_processor import WorkflowProcessor, CustomBpmnScriptEngine
|
||||
from config.default import DEFAULT_SENDER
|
||||
|
||||
|
||||
|
@ -81,13 +79,13 @@ def evaluate_python_expression(body):
|
|||
front end application needs to do real-time processing on task data. If for instance
|
||||
there is a hide expression that is based on a previous value in the same form."""
|
||||
try:
|
||||
# fixme: The script engine should be pulled from Workflow Processor,
|
||||
# but the one it returns overwrites the evaluate expression making it uncallable.
|
||||
script_engine = PythonScriptEngine()
|
||||
result = script_engine.evaluate(body['expression'], **body['data'])
|
||||
script_engine = CustomBpmnScriptEngine()
|
||||
result = script_engine.eval(body['expression'], body['data'])
|
||||
return {"result": result}
|
||||
except Exception as e:
|
||||
raise ApiError("expression_error", str(e))
|
||||
raise ApiError("expression_error", f"Failed to evaluate the expression '%s'. %s" %
|
||||
(body['expression'], str(e)),
|
||||
task_data = body["data"])
|
||||
|
||||
|
||||
def send_test_email(subject, address, message, data=None):
|
||||
|
@ -98,4 +96,3 @@ def send_test_email(subject, address, message, data=None):
|
|||
recipients=[address],
|
||||
content=rendered,
|
||||
content_html=wrapped)
|
||||
|
||||
|
|
|
@ -120,7 +120,7 @@ def restart_workflow(workflow_id, clear_data=False):
|
|||
"""Restart a workflow with the latest spec.
|
||||
Clear data allows user to restart the workflow without previous data."""
|
||||
workflow_model: WorkflowModel = session.query(WorkflowModel).filter_by(id=workflow_id).first()
|
||||
WorkflowProcessor(workflow_model).reset(workflow_model, clear_data=clear_data)
|
||||
WorkflowProcessor.reset(workflow_model, clear_data=clear_data)
|
||||
return get_workflow(workflow_model.id)
|
||||
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ from ldap3.core.exceptions import LDAPSocketOpenError
|
|||
|
||||
from crc import db, session, app
|
||||
from crc.api.common import ApiError
|
||||
from crc.models.email import EmailModel
|
||||
from crc.models.file import FileDataModel, FileModel, FileModelSchema, File, LookupFileModel, LookupDataModel
|
||||
from crc.models.ldap import LdapSchema
|
||||
from crc.models.protocol_builder import ProtocolBuilderStudy, ProtocolBuilderStatus
|
||||
|
@ -210,6 +211,10 @@ class StudyService(object):
|
|||
@staticmethod
|
||||
def delete_study(study_id):
|
||||
session.query(TaskEventModel).filter_by(study_id=study_id).delete()
|
||||
session.query(StudyAssociated).filter_by(study_id=study_id).delete()
|
||||
session.query(EmailModel).filter_by(study_id=study_id).delete()
|
||||
session.query(StudyEvent).filter_by(study_id=study_id).delete()
|
||||
|
||||
for workflow in session.query(WorkflowModel).filter_by(study_id=study_id):
|
||||
StudyService.delete_workflow(workflow.id)
|
||||
study = session.query(StudyModel).filter_by(id=study_id).first()
|
||||
|
|
|
@ -62,7 +62,7 @@ class UserService(object):
|
|||
if uid is None:
|
||||
raise ApiError("invalid_uid", "Please provide a valid user uid.")
|
||||
|
||||
if not UserService.admin_is_impersonating() and UserService.is_different_user(uid):
|
||||
if UserService.is_different_user(uid):
|
||||
# Impersonate the user if the given uid is valid.
|
||||
impersonate_user = session.query(UserModel).filter(UserModel.uid == uid).first()
|
||||
|
||||
|
@ -70,6 +70,7 @@ class UserService(object):
|
|||
g.impersonate_user = impersonate_user
|
||||
|
||||
# Store the uid and user session token.
|
||||
session.query(AdminSessionModel).filter(AdminSessionModel.token==g.token).delete()
|
||||
session.add(AdminSessionModel(token=g.token, admin_impersonate_uid=uid))
|
||||
session.commit()
|
||||
else:
|
||||
|
|
|
@ -68,43 +68,6 @@ class CustomBpmnScriptEngine(BpmnScriptEngine):
|
|||
f'something you are referencing does not exist:'
|
||||
f' {script}, {e}')
|
||||
|
||||
# else:
|
||||
# self.run_predefined_script(task, script[2:], data) # strip off the first two characters.
|
||||
|
||||
# def run_predefined_script(self, task: SpiffTask, script, data):
|
||||
# commands = shlex.split(script)
|
||||
# path_and_command = commands[0].rsplit(".", 1)
|
||||
# if len(path_and_command) == 1:
|
||||
# module_name = "crc.scripts." + self.camel_to_snake(path_and_command[0])
|
||||
# class_name = path_and_command[0]
|
||||
# else:
|
||||
# module_name = "crc.scripts." + path_and_command[0] + "." + self.camel_to_snake(path_and_command[1])
|
||||
# class_name = path_and_command[1]
|
||||
# try:
|
||||
# mod = __import__(module_name, fromlist=[class_name])
|
||||
# klass = getattr(mod, class_name)
|
||||
# study_id = task.workflow.data[WorkflowProcessor.STUDY_ID_KEY]
|
||||
# if WorkflowProcessor.WORKFLOW_ID_KEY in task.workflow.data:
|
||||
# workflow_id = task.workflow.data[WorkflowProcessor.WORKFLOW_ID_KEY]
|
||||
# else:
|
||||
# workflow_id = None
|
||||
#
|
||||
# if not isinstance(klass(), Script):
|
||||
# raise ApiError.from_task("invalid_script",
|
||||
# "This is an internal error. The script '%s:%s' you called " %
|
||||
# (module_name, class_name) +
|
||||
# "does not properly implement the CRC Script class.",
|
||||
# task=task)
|
||||
# if task.workflow.data[WorkflowProcessor.VALIDATION_PROCESS_KEY]:
|
||||
# """If this is running a validation, and not a normal process, then we want to
|
||||
# mimic running the script, but not make any external calls or database changes."""
|
||||
# klass().do_task_validate_only(task, study_id, workflow_id, *commands[1:])
|
||||
# else:
|
||||
# klass().do_task(task, study_id, workflow_id, *commands[1:])
|
||||
# except ModuleNotFoundError:
|
||||
# raise ApiError.from_task("invalid_script",
|
||||
# "Unable to locate Script: '%s:%s'" % (module_name, class_name),
|
||||
# task=task)
|
||||
|
||||
def evaluate_expression(self, task, expression):
|
||||
"""
|
||||
|
@ -130,6 +93,10 @@ class CustomBpmnScriptEngine(BpmnScriptEngine):
|
|||
"Error evaluating expression "
|
||||
"'%s', %s" % (expression, str(e)))
|
||||
|
||||
def eval(self, exp, data):
|
||||
return super()._eval(exp, {}, **data)
|
||||
|
||||
|
||||
|
||||
@staticmethod
|
||||
def camel_to_snake(camel):
|
||||
|
@ -206,10 +173,19 @@ class WorkflowProcessor(object):
|
|||
else:
|
||||
self.is_latest_spec = False
|
||||
|
||||
def reset(self, workflow_model, clear_data=False):
|
||||
@staticmethod
|
||||
def reset(workflow_model, clear_data=False):
|
||||
print('WorkflowProcessor: reset: ')
|
||||
|
||||
self.cancel_notify()
|
||||
# Try to execute a cancel notify
|
||||
try:
|
||||
wp = WorkflowProcessor(workflow_model)
|
||||
wp.cancel_notify() # The executes a notification to all endpoints that
|
||||
except Exception as e:
|
||||
app.logger.error(f"Unable to send a cancel notify for workflow %s during a reset."
|
||||
f" Continuing with the reset anyway so we don't get in an unresolvable"
|
||||
f" state. An %s error occured with the following information: %s" %
|
||||
(workflow_model.id, e.__class__.__name__, str(e)))
|
||||
workflow_model.bpmn_workflow_json = None
|
||||
if clear_data:
|
||||
# Clear form_data from task_events
|
||||
|
@ -219,7 +195,7 @@ class WorkflowProcessor(object):
|
|||
task_event.form_data = {}
|
||||
session.add(task_event)
|
||||
session.commit()
|
||||
return self.__init__(workflow_model)
|
||||
return WorkflowProcessor(workflow_model)
|
||||
|
||||
def __get_bpmn_workflow(self, workflow_model: WorkflowModel, spec: WorkflowSpec, validate_only=False):
|
||||
if workflow_model.bpmn_workflow_json:
|
||||
|
|
|
@ -156,9 +156,10 @@ class WorkflowService(object):
|
|||
|
||||
# If a field is hidden and required, it must have a default value or value_expression
|
||||
if field.has_property(Task.FIELD_PROP_HIDE_EXPRESSION) and field.has_validation(Task.FIELD_CONSTRAINT_REQUIRED):
|
||||
if not field.has_property(Task.FIELD_PROP_VALUE_EXPRESSION) or not (hasattr(field, 'default_value')):
|
||||
if not field.has_property(Task.FIELD_PROP_VALUE_EXPRESSION) and \
|
||||
(not (hasattr(field, 'default_value')) or field.default_value is None):
|
||||
raise ApiError(code='hidden and required field missing default',
|
||||
message='Fields that are required but can be hidden must have either a default value or a value_expression',
|
||||
message=f'Field "{field.id}" is required but can be hidden. It must have either a default value or a value_expression',
|
||||
task_id='task.id',
|
||||
task_name=task.get_name())
|
||||
|
||||
|
@ -262,12 +263,13 @@ class WorkflowService(object):
|
|||
|
||||
# If no default exists, return None
|
||||
# Note: if default is False, we don't want to execute this code
|
||||
if default is None:
|
||||
return None
|
||||
if default is None or (isinstance(default, str) and default.strip() == ''):
|
||||
if field.type == "enum" or field.type == "autocomplete":
|
||||
return {'value': None, 'label': None}
|
||||
else:
|
||||
return None
|
||||
|
||||
if field.type == "enum" and not has_lookup:
|
||||
if isinstance(default, str) and default.strip() == '':
|
||||
return
|
||||
default_option = next((obj for obj in field.options if obj.id == default), None)
|
||||
if not default_option:
|
||||
raise ApiError.from_task("invalid_default", "You specified a default value that does not exist in "
|
||||
|
@ -729,5 +731,4 @@ class WorkflowService(object):
|
|||
workflows = db.session.query(WorkflowModel).filter_by(study_id=study_id).all()
|
||||
for workflow in workflows:
|
||||
if workflow.status == WorkflowStatus.user_input_required or workflow.status == WorkflowStatus.waiting:
|
||||
processor = WorkflowProcessor(workflow)
|
||||
processor.reset(workflow)
|
||||
WorkflowProcessor.reset(workflow, clear_data=False)
|
||||
|
|
|
@ -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_1mhc2v8" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="4.2.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_1mhc2v8" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.5.0">
|
||||
<bpmn:process id="Process_HiddenRequired" name="Hidden Reguired Field" isExecutable="true">
|
||||
<bpmn:startEvent id="StartEvent_1">
|
||||
<bpmn:outgoing>Flow_0zt7wv5</bpmn:outgoing>
|
||||
|
@ -32,7 +32,9 @@ if not 'hide_yes_no' in globals():
|
|||
</bpmn:scriptTask>
|
||||
<bpmn:sequenceFlow id="Flow_0fb4w15" sourceRef="Activity_PreData" targetRef="Activity_HiddenField" />
|
||||
<bpmn:manualTask id="Activity_GoodBye" name="Good Bye">
|
||||
<bpmn:documentation><H1>Good Bye{% if name %} {{ name }}{% endif %}</H1></bpmn:documentation>
|
||||
<bpmn:documentation><H1>Good Bye{% if name %} {{ name }}{% endif %}</H1>
|
||||
<div><span>Color is {{ color }}</span></div>
|
||||
</bpmn:documentation>
|
||||
<bpmn:incoming>Flow_1qkjkbh</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_1udbzd6</bpmn:outgoing>
|
||||
</bpmn:manualTask>
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
<?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_1mhc2v8" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.5.0">
|
||||
<bpmn:process id="Process_HiddenRequired" name="Hidden Reguired Field" isExecutable="true">
|
||||
<bpmn:startEvent id="StartEvent_1">
|
||||
<bpmn:outgoing>Flow_0zt7wv5</bpmn:outgoing>
|
||||
</bpmn:startEvent>
|
||||
<bpmn:sequenceFlow id="Flow_0zt7wv5" sourceRef="StartEvent_1" targetRef="Activity_Hello" />
|
||||
<bpmn:userTask id="Activity_HiddenField" name="Hidden Field" camunda:formKey="HiddenFieldForm">
|
||||
<bpmn:extensionElements>
|
||||
<camunda:formData>
|
||||
<camunda:formField id="name" label="Name" type="string" defaultValue="World">
|
||||
<camunda:properties>
|
||||
<camunda:property id="hide_expression" value="hide_yes_no" />
|
||||
</camunda:properties>
|
||||
<camunda:validation>
|
||||
<camunda:constraint name="required" config="require_yes_no" />
|
||||
</camunda:validation>
|
||||
</camunda:formField>
|
||||
</camunda:formData>
|
||||
</bpmn:extensionElements>
|
||||
<bpmn:incoming>Flow_0fb4w15</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_0c2rym0</bpmn:outgoing>
|
||||
</bpmn:userTask>
|
||||
<bpmn:sequenceFlow id="Flow_0cm6imh" sourceRef="Activity_Hello" targetRef="Activity_PreData" />
|
||||
<bpmn:scriptTask id="Activity_PreData" name="Pre Data">
|
||||
<bpmn:incoming>Flow_0cm6imh</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_0fb4w15</bpmn:outgoing>
|
||||
<bpmn:script>if not 'require_yes_no' in globals():
|
||||
require_yes_no = True
|
||||
if not 'hide_yes_no' in globals():
|
||||
hide_yes_no = True</bpmn:script>
|
||||
</bpmn:scriptTask>
|
||||
<bpmn:sequenceFlow id="Flow_0fb4w15" sourceRef="Activity_PreData" targetRef="Activity_HiddenField" />
|
||||
<bpmn:manualTask id="Activity_GoodBye" name="Good Bye">
|
||||
<bpmn:documentation><H1>Good Bye{% if name %} {{ name }}{% endif %}</H1>
|
||||
</bpmn:documentation>
|
||||
<bpmn:incoming>Flow_0c2rym0</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_1udbzd6</bpmn:outgoing>
|
||||
</bpmn:manualTask>
|
||||
<bpmn:endEvent id="Event_194gjyj">
|
||||
<bpmn:incoming>Flow_1udbzd6</bpmn:incoming>
|
||||
</bpmn:endEvent>
|
||||
<bpmn:sequenceFlow id="Flow_1udbzd6" sourceRef="Activity_GoodBye" targetRef="Event_194gjyj" />
|
||||
<bpmn:manualTask id="Activity_Hello" name="Hello">
|
||||
<bpmn:documentation><H1>Hello</H1></bpmn:documentation>
|
||||
<bpmn:incoming>Flow_0zt7wv5</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_0cm6imh</bpmn:outgoing>
|
||||
</bpmn:manualTask>
|
||||
<bpmn:sequenceFlow id="Flow_0c2rym0" sourceRef="Activity_HiddenField" targetRef="Activity_GoodBye" />
|
||||
</bpmn:process>
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_HiddenRequired">
|
||||
<bpmndi:BPMNEdge id="Flow_0fb4w15_di" bpmnElement="Flow_0fb4w15">
|
||||
<di:waypoint x="530" y="117" />
|
||||
<di:waypoint x="590" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_0cm6imh_di" bpmnElement="Flow_0cm6imh">
|
||||
<di:waypoint x="370" y="117" />
|
||||
<di:waypoint x="430" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_0zt7wv5_di" bpmnElement="Flow_0zt7wv5">
|
||||
<di:waypoint x="215" y="117" />
|
||||
<di:waypoint x="270" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_0c2rym0_di" bpmnElement="Flow_0c2rym0">
|
||||
<di:waypoint x="690" y="117" />
|
||||
<di:waypoint x="750" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_1udbzd6_di" bpmnElement="Flow_1udbzd6">
|
||||
<di:waypoint x="850" y="117" />
|
||||
<di:waypoint x="912" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
|
||||
<dc:Bounds x="179" y="99" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_0a4wzou_di" bpmnElement="Activity_HiddenField">
|
||||
<dc:Bounds x="590" y="77" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_0kjyqk8_di" bpmnElement="Activity_PreData">
|
||||
<dc:Bounds x="430" y="77" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_0v7ietz_di" bpmnElement="Activity_Hello">
|
||||
<dc:Bounds x="270" y="77" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_12r6tn2_di" bpmnElement="Activity_GoodBye">
|
||||
<dc:Bounds x="750" y="77" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Event_194gjyj_di" bpmnElement="Event_194gjyj">
|
||||
<dc:Bounds x="912" y="99" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
</bpmn:definitions>
|
|
@ -0,0 +1,96 @@
|
|||
<?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_1mhc2v8" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.5.0">
|
||||
<bpmn:process id="Process_HiddenRequired" name="Hidden Reguired Field" isExecutable="true">
|
||||
<bpmn:startEvent id="StartEvent_1">
|
||||
<bpmn:outgoing>Flow_0zt7wv5</bpmn:outgoing>
|
||||
</bpmn:startEvent>
|
||||
<bpmn:sequenceFlow id="Flow_0zt7wv5" sourceRef="StartEvent_1" targetRef="Activity_Hello" />
|
||||
<bpmn:userTask id="Activity_HiddenField" name="Hidden Field" camunda:formKey="HiddenFieldForm">
|
||||
<bpmn:extensionElements>
|
||||
<camunda:formData>
|
||||
<camunda:formField id="name" label="Name" type="string">
|
||||
<camunda:properties>
|
||||
<camunda:property id="hide_expression" value="True" />
|
||||
<camunda:property id="value_expression" value="value_expression_value" />
|
||||
</camunda:properties>
|
||||
<camunda:validation>
|
||||
<camunda:constraint name="required" config="True" />
|
||||
</camunda:validation>
|
||||
</camunda:formField>
|
||||
</camunda:formData>
|
||||
</bpmn:extensionElements>
|
||||
<bpmn:incoming>Flow_0fb4w15</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_0c2rym0</bpmn:outgoing>
|
||||
</bpmn:userTask>
|
||||
<bpmn:sequenceFlow id="Flow_0cm6imh" sourceRef="Activity_Hello" targetRef="Activity_PreData" />
|
||||
<bpmn:scriptTask id="Activity_PreData" name="Pre Data">
|
||||
<bpmn:incoming>Flow_0cm6imh</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_0fb4w15</bpmn:outgoing>
|
||||
<bpmn:script>if not 'require_yes_no' in globals():
|
||||
require_yes_no = True
|
||||
if not 'hide_yes_no' in globals():
|
||||
hide_yes_no = True
|
||||
if not 'value_expression_value' in globals():
|
||||
value_expression_value = 'World'</bpmn:script>
|
||||
</bpmn:scriptTask>
|
||||
<bpmn:sequenceFlow id="Flow_0fb4w15" sourceRef="Activity_PreData" targetRef="Activity_HiddenField" />
|
||||
<bpmn:manualTask id="Activity_GoodBye" name="Good Bye">
|
||||
<bpmn:documentation><H1>Good Bye{% if name %} {{ name }}{% endif %}</H1>
|
||||
</bpmn:documentation>
|
||||
<bpmn:incoming>Flow_0c2rym0</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_1udbzd6</bpmn:outgoing>
|
||||
</bpmn:manualTask>
|
||||
<bpmn:endEvent id="Event_194gjyj">
|
||||
<bpmn:incoming>Flow_1udbzd6</bpmn:incoming>
|
||||
</bpmn:endEvent>
|
||||
<bpmn:sequenceFlow id="Flow_1udbzd6" sourceRef="Activity_GoodBye" targetRef="Event_194gjyj" />
|
||||
<bpmn:manualTask id="Activity_Hello" name="Hello">
|
||||
<bpmn:documentation><H1>Hello</H1></bpmn:documentation>
|
||||
<bpmn:incoming>Flow_0zt7wv5</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_0cm6imh</bpmn:outgoing>
|
||||
</bpmn:manualTask>
|
||||
<bpmn:sequenceFlow id="Flow_0c2rym0" sourceRef="Activity_HiddenField" targetRef="Activity_GoodBye" />
|
||||
</bpmn:process>
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_HiddenRequired">
|
||||
<bpmndi:BPMNEdge id="Flow_0fb4w15_di" bpmnElement="Flow_0fb4w15">
|
||||
<di:waypoint x="530" y="117" />
|
||||
<di:waypoint x="590" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_0cm6imh_di" bpmnElement="Flow_0cm6imh">
|
||||
<di:waypoint x="370" y="117" />
|
||||
<di:waypoint x="430" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_0zt7wv5_di" bpmnElement="Flow_0zt7wv5">
|
||||
<di:waypoint x="215" y="117" />
|
||||
<di:waypoint x="270" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_0c2rym0_di" bpmnElement="Flow_0c2rym0">
|
||||
<di:waypoint x="690" y="117" />
|
||||
<di:waypoint x="750" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_1udbzd6_di" bpmnElement="Flow_1udbzd6">
|
||||
<di:waypoint x="850" y="117" />
|
||||
<di:waypoint x="912" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
|
||||
<dc:Bounds x="179" y="99" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_0a4wzou_di" bpmnElement="Activity_HiddenField">
|
||||
<dc:Bounds x="590" y="77" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_0kjyqk8_di" bpmnElement="Activity_PreData">
|
||||
<dc:Bounds x="430" y="77" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_0v7ietz_di" bpmnElement="Activity_Hello">
|
||||
<dc:Bounds x="270" y="77" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_12r6tn2_di" bpmnElement="Activity_GoodBye">
|
||||
<dc:Bounds x="750" y="77" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Event_194gjyj_di" bpmnElement="Event_194gjyj">
|
||||
<dc:Bounds x="912" y="99" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
</bpmn:definitions>
|
|
@ -1,17 +1,19 @@
|
|||
import json
|
||||
from profile import Profile
|
||||
|
||||
from crc.services.ldap_service import LdapService
|
||||
from tests.base_test import BaseTest
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from unittest.mock import patch
|
||||
|
||||
from crc.models.email import EmailModel
|
||||
from crc import session, app
|
||||
from crc.models.protocol_builder import ProtocolBuilderStatus, \
|
||||
ProtocolBuilderStudySchema
|
||||
from crc.models.file import FileModel
|
||||
from crc.models.task_event import TaskEventModel
|
||||
from crc.models.study import StudyEvent, StudyModel, StudySchema, StudyStatus, StudyEventType
|
||||
from crc.models.study import StudyEvent, StudyModel, StudySchema, StudyStatus, StudyEventType, StudyAssociated
|
||||
from crc.models.workflow import WorkflowSpecModel, WorkflowModel
|
||||
from crc.services.file_service import FileService
|
||||
from crc.services.workflow_processor import WorkflowProcessor
|
||||
|
@ -274,7 +276,7 @@ class TestStudyApi(BaseTest):
|
|||
self.assertTrue(file.archived)
|
||||
self.assertIsNone(file.workflow_id)
|
||||
|
||||
def test_delete_study_with_workflow_and_status(self):
|
||||
def test_delete_study_with_workflow_and_status_etc(self):
|
||||
self.load_example_data()
|
||||
workflow = session.query(WorkflowModel).first()
|
||||
stats1 = StudyEvent(
|
||||
|
@ -284,6 +286,14 @@ class TestStudyApi(BaseTest):
|
|||
event_type=StudyEventType.user,
|
||||
user_uid=self.users[0]['uid'],
|
||||
)
|
||||
LdapService.user_info('dhf8r') # Assure that there is a dhf8r in ldap for StudyAssociated.
|
||||
|
||||
email = EmailModel(subject="x", study_id=workflow.study_id)
|
||||
associate = StudyAssociated(study_id=workflow.study_id, uid=self.users[0]['uid'])
|
||||
event = StudyEvent(study_id=workflow.study_id)
|
||||
session.add_all([email, associate, event])
|
||||
|
||||
|
||||
stats2 = TaskEventModel(study_id=workflow.study_id, workflow_id=workflow.id, user_uid=self.users[0]['uid'])
|
||||
session.add_all([stats1, stats2])
|
||||
session.commit()
|
||||
|
@ -293,7 +303,6 @@ class TestStudyApi(BaseTest):
|
|||
self.assertIsNone(del_study)
|
||||
|
||||
|
||||
|
||||
# """
|
||||
# Workflow Specs that have been made available (or not) to a particular study via the status.bpmn should be flagged
|
||||
# as available (or not) when the list of a study's workflows is retrieved.
|
||||
|
|
|
@ -66,7 +66,7 @@ class DataStoreTest(BaseTest):
|
|||
|
||||
|
||||
|
||||
def test_update_study(self):
|
||||
def test_update_datastore(self):
|
||||
self.load_example_data()
|
||||
new_study = self.add_test_study_data()
|
||||
new_study = session.query(DataStoreModel).filter_by(id=new_study["id"]).first()
|
||||
|
@ -87,7 +87,7 @@ class DataStoreTest(BaseTest):
|
|||
|
||||
|
||||
|
||||
def test_delete_study(self):
|
||||
def test_delete_datastore(self):
|
||||
self.load_example_data()
|
||||
new_study = self.add_test_study_data()
|
||||
oldid = new_study['id']
|
||||
|
|
|
@ -54,8 +54,7 @@ class TestLookupService(BaseTest):
|
|||
|
||||
# restart the workflow, so it can pick up the changes.
|
||||
|
||||
processor = WorkflowProcessor(workflow)
|
||||
processor.reset(workflow)
|
||||
processor = WorkflowProcessor.reset(workflow)
|
||||
workflow = processor.workflow_model
|
||||
|
||||
LookupService.lookup(workflow, "Task_Enum_Lookup", "sponsor", "sam", limit=10)
|
||||
|
@ -92,8 +91,7 @@ class TestLookupService(BaseTest):
|
|||
results = LookupService.lookup(workflow, task.task_spec.name, "selectedItem", "", value="apples", limit=10)
|
||||
self.assertEqual(0, len(results), "We shouldn't find our fruits mixed in with our animals.")
|
||||
|
||||
|
||||
processor.reset(workflow, clear_data=True)
|
||||
processor = WorkflowProcessor.reset(workflow, clear_data=True)
|
||||
processor.do_engine_steps()
|
||||
task = processor.get_ready_user_tasks()[0]
|
||||
task.data = {"type": "fruit"}
|
||||
|
|
|
@ -213,6 +213,27 @@ class TestTasksApi(BaseTest):
|
|||
self.assertTrue(workflow_api.spec_version.startswith("v2 "))
|
||||
self.assertTrue(workflow_api.is_latest_spec)
|
||||
|
||||
def test_reset_workflow_from_broken_spec(self):
|
||||
# Start the basic two_forms workflow and complete a task.
|
||||
workflow = self.create_workflow('two_forms')
|
||||
workflow_api = self.get_workflow_api(workflow)
|
||||
self.complete_form(workflow, workflow_api.next_task, {"color": "blue"})
|
||||
self.assertTrue(workflow_api.is_latest_spec)
|
||||
|
||||
# Break the bpmn json
|
||||
workflow.bpmn_workflow_json = '{"something":"broken"}'
|
||||
session.add(workflow)
|
||||
session.commit()
|
||||
|
||||
# Try to load the workflow, we should get an error
|
||||
with self.assertRaises(Exception):
|
||||
workflow_api = self.complete_form(workflow, workflow_api.next_task, {"name": "Dan"})
|
||||
|
||||
# Now, Reset the workflow, and we should not get an error
|
||||
workflow_api = self.restart_workflow_api(workflow_api, clear_data=True)
|
||||
self.assertIsNotNone(workflow_api)
|
||||
|
||||
|
||||
|
||||
def test_manual_task_with_external_documentation(self):
|
||||
workflow = self.create_workflow('manual_task_with_external_documentation')
|
||||
|
|
|
@ -48,6 +48,7 @@ class TestStudyApi(BaseTest):
|
|||
response = json.loads(rv.get_data(as_text=True))
|
||||
self.assertEqual(True, response['result'])
|
||||
|
||||
|
||||
def test_eval_expression_with_strings(self):
|
||||
"""Assures we can use python to process a value expression from the front end"""
|
||||
rv = self.app.put('/v1.0/eval',
|
||||
|
@ -81,3 +82,14 @@ CR Connect
|
|||
self.assertEqual(subject, outbox[0].subject)
|
||||
self.assertEqual(['user@example.com'], outbox[0].recipients)
|
||||
self.assertIn('Person of Interest', outbox[0].body)
|
||||
|
||||
def test_eval_to_boolean_expression_with_dot_notation(self):
|
||||
"""Assures we can use python to process a value expression from the front end"""
|
||||
rv = self.app.put('/v1.0/eval',
|
||||
data='{"expression": "test.value", "data": {"test":{"value": true}}}',
|
||||
follow_redirects=True,
|
||||
content_type='application/json',
|
||||
headers=self.logged_in_headers())
|
||||
self.assert_success(rv)
|
||||
response = json.loads(rv.get_data(as_text=True))
|
||||
self.assertEqual(True, response['result'])
|
||||
|
|
|
@ -14,6 +14,17 @@ class TestWorkflowHiddenRequiredField(BaseTest):
|
|||
self.assertEqual(json_data[0]['code'], 'hidden and required field missing default')
|
||||
self.assertIn('task_id', json_data[0])
|
||||
self.assertIn('task_name', json_data[0])
|
||||
self.assertIn('Field "name" is required but can be hidden', json_data[0]['message'])
|
||||
|
||||
def test_require_default_pass(self):
|
||||
spec_model = self.load_test_spec('hidden_required_field_pass')
|
||||
rv = self.app.get('/v1.0/workflow-specification/%s/validate' % spec_model.id, headers=self.logged_in_headers())
|
||||
self.assertEqual(0, len(rv.json))
|
||||
|
||||
def test_require_default_pass_expression(self):
|
||||
spec_model = self.load_test_spec('hidden_required_field_pass_expression')
|
||||
rv = self.app.get('/v1.0/workflow-specification/%s/validate' % spec_model.id, headers=self.logged_in_headers())
|
||||
self.assertEqual(0, len(rv.json))
|
||||
|
||||
def test_default_used(self):
|
||||
# If a field is hidden and required, make sure we use the default value
|
||||
|
|
|
@ -279,7 +279,7 @@ class TestWorkflowProcessor(BaseTest):
|
|||
self.assertFalse(processor2.is_latest_spec) # Still at version 1.
|
||||
|
||||
# Do a hard reset, which should bring us back to the beginning, but retain the data.
|
||||
processor2.reset(processor2.workflow_model)
|
||||
processor2 = WorkflowProcessor.reset(processor2.workflow_model)
|
||||
processor3 = WorkflowProcessor(processor.workflow_model)
|
||||
processor3.do_engine_steps()
|
||||
self.assertEqual("Step 1", processor3.next_task().task_spec.description)
|
||||
|
|
|
@ -14,7 +14,11 @@ class TestValueExpression(BaseTest):
|
|||
workflow_api = self.get_workflow_api(workflow)
|
||||
second_task = workflow_api.next_task
|
||||
self.assertEqual('', second_task.data['value_expression_value'])
|
||||
self.assertNotIn('color', second_task.data)
|
||||
# self.assertNotIn('color', second_task.data)
|
||||
self.assertIn('color', second_task.data)
|
||||
self.assertIsNone(second_task.data['color']['value'])
|
||||
|
||||
|
||||
|
||||
def test_value_expression_with_default(self):
|
||||
|
||||
|
|
Loading…
Reference in New Issue