All enumerated lists used in web forms should contain a single value, not a dictionary of value/labels.

Removing the spreadsheet.value.column and data.value.column so we just have value.column for both.
Improving the __str__ function in the ApiError class, to make debugging a little easier.
Adding a "validate_all" flask command, to help us track down any issues with current workflows in production (use this in concert with sync_with_testing)
Fixed logs of tests.
removed fact_runner.py, a very early and crufty bit of code.
This commit is contained in:
Dan 2021-10-19 10:13:43 -04:00
parent c5d8e20e74
commit 5429e7da7d
24 changed files with 203 additions and 255 deletions

View File

@ -1,9 +1,12 @@
import json
import logging
import os
import click
import sentry_sdk
import connexion
from SpiffWorkflow import WorkflowException
from connexion import ProblemException
from flask import Response
from flask_cors import CORS
@ -15,6 +18,7 @@ from sentry_sdk.integrations.flask import FlaskIntegration
from apscheduler.schedulers.background import BackgroundScheduler
from werkzeug.middleware.proxy_fix import ProxyFix
logging.basicConfig(level=logging.INFO)
connexion_app = connexion.FlaskApp(__name__)
@ -130,3 +134,32 @@ def sync_with_testing():
"""Load all the workflows currently on testing into this system."""
from crc.api import workflow_sync
workflow_sync.sync_all_changed_workflows("https://testing.crconnect.uvadcos.io/api")
@app.cli.command()
@click.argument("study_id")
@click.argument("category")
@click.argument("spec_id")
def validate_all(study_id, category=None, spec_id=None):
"""Step through all the local workflows and validate them, returning any errors. This make take forever.
Please provide a real study id to use for validation, an optional category can be specified to only validate
that category, and you can further specify a specific spec, if needed."""
from crc.models.workflow import WorkflowSpecModel
from crc.services.workflow_service import WorkflowService
from crc.api.common import ApiError
specs = session.query(WorkflowSpecModel).all()
for spec in specs:
if spec_id and spec_id != spec.id:
continue
if category and (not spec.category or spec.category.display_name != category):
continue
try:
WorkflowService.test_spec(spec.id, validate_study_id=study_id)
except ApiError as e:
print("Failed to validate workflow " + spec.id)
print(e)
return
except WorkflowException as e:
print("Failed to validate workflow " + spec.id)
print(e)
return

View File

@ -35,6 +35,16 @@ class ApiError(Exception):
sentry_sdk.set_context("User", {'user': user})
Exception.__init__(self, self.message)
def __str__(self):
msg = "ApiError: % s. " % self.message
if self.task_name:
msg += "Error in task '%s' (%s). " % (self.task_name, self.task_id)
if self.line_number:
msg += "Error is on line %i. " % self.line_number
if self.file_name:
msg += "In file %s. " % self.file_name
return msg
@classmethod
def from_task(cls, code, message, task, status_code=400, line_number=0, offset=0, error_type="", error_line=""):
"""Constructs an API Error with details pulled from the current task."""

View File

@ -397,8 +397,8 @@ def lookup(workflow_id, task_spec_name, field_id, query=None, value=None, limit=
"""
workflow = session.query(WorkflowModel).filter(WorkflowModel.id == workflow_id).first()
lookup_data = LookupService.lookup(workflow, task_spec_name, field_id, query, value, limit)
return LookupDataSchema(many=True).dump(lookup_data)
# Just return the data
return lookup_data
def _verify_user_and_role(processor, spiff_task):
"""Assures the currently logged in user can access the given workflow and task, or

View File

@ -56,15 +56,16 @@ class Task(object):
FIELD_PROP_VALUE_EXPRESSION = "value_expression"
FIELD_PROP_REPEAT_HIDE_EXPRESSION = "repeat_hide_expression"
# Enum field options values pulled from a spreadsheet
# Enum field options
FIELD_PROP_SPREADSHEET_NAME = "spreadsheet.name"
FIELD_PROP_SPREADSHEET_VALUE_COLUMN = "spreadsheet.value.column"
FIELD_PROP_SPREADSHEET_LABEL_COLUMN = "spreadsheet.label.column"
FIELD_PROP_DATA_NAME = "data.name"
FIELD_PROP_VALUE_COLUMN = "value.column"
FIELD_PROP_LABEL_COLUMN = "label.column"
#FIELD_PROP_SPREADSHEET_VALUE_COLUMN = "spreadsheet.value.column"
#FIELD_PROP_SPREADSHEET_LABEL_COLUMN = "spreadsheet.label.column"
# Enum field options values pulled from task data
FIELD_PROP_DATA_NAME = "data.name"
FIELD_PROP_VALUE_COLUMN = "data.value.column"
FIELD_PROP_LABEL_COLUMN = "data.label.column"
# Group and Repeat functions
FIELD_PROP_GROUP = "group"

View File

@ -204,7 +204,7 @@ class LookupDataModel(db.Model):
)
class LookupDataSchema(SQLAlchemyAutoSchema):
class LookupDataSchema(ma.Schema):
class Meta:
model = LookupDataModel
load_instance = True

View File

@ -13,6 +13,7 @@ from crc import db
from crc.api.common import ApiError
from crc.models.api_models import Task
from crc.models.file import FileModel, FileDataModel, LookupFileModel, LookupDataModel
from crc.models.ldap import LdapSchema
from crc.models.workflow import WorkflowModel, WorkflowSpecDependencyFile
from crc.services.file_service import FileService
from crc.services.ldap_service import LdapService
@ -88,10 +89,11 @@ class LookupService(object):
lookup_model = LookupService.__get_lookup_model(workflow, task_spec_id, field_id)
if lookup_model.is_ldap:
return LookupService._run_ldap_query(query, limit)
return LookupService._run_ldap_query(query, value, limit)
else:
return LookupService._run_lookup_query(lookup_model, query, value, limit)
@staticmethod
def create_lookup_model(workflow_model, task_spec_id, field_id):
"""
@ -115,19 +117,19 @@ class LookupService(object):
# Use the contents of a file to populate enum field options
if field.has_property(Task.FIELD_PROP_SPREADSHEET_NAME):
if not (field.has_property(Task.FIELD_PROP_SPREADSHEET_VALUE_COLUMN) or
field.has_property(Task.FIELD_PROP_SPREADSHEET_LABEL_COLUMN)):
if not (field.has_property(Task.FIELD_PROP_VALUE_COLUMN) or
field.has_property(Task.FIELD_PROP_LABEL_COLUMN)):
raise ApiError.from_task_spec("invalid_enum",
"For enumerations based on an xls file, you must include 3 properties: %s, "
"%s, and %s" % (Task.FIELD_PROP_SPREADSHEET_NAME,
Task.FIELD_PROP_SPREADSHEET_VALUE_COLUMN,
Task.FIELD_PROP_SPREADSHEET_LABEL_COLUMN),
Task.FIELD_PROP_VALUE_COLUMN,
Task.FIELD_PROP_LABEL_COLUMN),
task_spec=spec)
# Get the file data from the File Service
file_name = field.get_property(Task.FIELD_PROP_SPREADSHEET_NAME)
value_column = field.get_property(Task.FIELD_PROP_SPREADSHEET_VALUE_COLUMN)
label_column = field.get_property(Task.FIELD_PROP_SPREADSHEET_LABEL_COLUMN)
value_column = field.get_property(Task.FIELD_PROP_VALUE_COLUMN)
label_column = field.get_property(Task.FIELD_PROP_LABEL_COLUMN)
latest_files = FileService.get_spec_data_files(workflow_spec_id=workflow_model.workflow_spec_id,
workflow_id=workflow_model.id,
name=file_name)
@ -227,20 +229,13 @@ class LookupService(object):
logging.info(db_query)
result = db_query.limit(limit).all()
logging.getLogger('sqlalchemy.engine').setLevel(logging.ERROR)
return result
result_data = list(map(lambda lookup_item: lookup_item.data, result))
return result_data
@staticmethod
def _run_ldap_query(query, limit):
users = LdapService.search_users(query, limit)
"""Converts the user models into something akin to the
LookupModel in models/file.py, so this can be returned in the same way
we return a lookup data model."""
user_list = []
for user in users:
user_list.append({"value": user['uid'],
"label": user['display_name'] + " (" + user['uid'] + ")",
"data": user
})
return user_list
def _run_ldap_query(query, value, limit):
if value:
return [LdapSchema().dump(LdapService.user_info(value))]
else:
users = LdapService.search_users(query, limit)
return users

View File

@ -349,10 +349,7 @@ class WorkflowService(object):
field.get_property(Task.FIELD_PROP_FILE_DATA) in data and \
field.id in data and data[field.id]:
file_id = data[field.get_property(Task.FIELD_PROP_FILE_DATA)]["id"]
if field.type == 'enum':
data_args = (field.id, data[field.id]['label'])
else:
data_args = (field.id, data[field.id])
data_args = (field.id, data[field.id])
DataStoreBase().set_data_common(task.id, None, None, None, None, None, file_id, *data_args)
@staticmethod
@ -403,24 +400,21 @@ class WorkflowService(object):
# Note: if default is False, we don't want to execute this code
if default is None or (isinstance(default, str) and default.strip() == ''):
if field.type == "enum" or field.type == "autocomplete":
# Return empty arrays for multi-select enums, otherwise do a value of None.
if field.has_property(Task.FIELD_PROP_ENUM_TYPE) and field.get_property(Task.FIELD_PROP_ENUM_TYPE) == "checkbox":
# Return empty arrays for multi-select
if field.has_property(Task.FIELD_PROP_ENUM_TYPE) and \
field.get_property(Task.FIELD_PROP_ENUM_TYPE) == "checkbox":
return []
else:
return {'value': None, 'label': None}
return None
else:
return None
if field.type == "enum" and not has_lookup:
if hasattr(default, "value"):
default_option = next((obj for obj in field.options if obj.id == default.value), None)
else:
# Fixme: We should likely error out on this in validation, or remove this value/label alltogether.
default_option = next((obj for obj in field.options if obj.id == default), None)
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 "
"the enum options ", task)
return {'value': default_option.id, 'label': default_option.name}
return default
elif field.type == "autocomplete" or field.type == "enum":
lookup_model = LookupService.get_lookup_model(task, field)
if field.has_property(Task.FIELD_PROP_LDAP_LOOKUP): # All ldap records get the same person.
@ -433,7 +427,7 @@ class WorkflowService(object):
if not data:
raise ApiError.from_task("invalid_default", "You specified a default value that does not exist in "
"the enum options ", task)
return {"value": data.value, "label": data.label, "data": data.data}
return default
else:
raise ApiError.from_task("unknown_lookup_option", "The settings for this auto complete field "
"are incorrect: %s " % field.id, task)
@ -461,10 +455,10 @@ class WorkflowService(object):
if len(field.options) > 0:
random_choice = random.choice(field.options)
if isinstance(random_choice, dict):
random_value = {'value': random_choice['id'], 'label': random_choice['name'], 'data': random_choice['data']}
random_value = random_choice['id']
else:
# fixme: why it is sometimes an EnumFormFieldOption, and other times not?
random_value = {'value': random_choice.id, 'label': random_choice.name}
random_value = random_choice.id
if field.has_property(Task.FIELD_PROP_ENUM_TYPE) and field.get_property(Task.FIELD_PROP_ENUM_TYPE) == 'checkbox':
return [random_value]
else:
@ -484,7 +478,8 @@ class WorkflowService(object):
LookupDataModel.lookup_file_model == lookup_model).limit(10).all()
options = [{"value": d.value, "label": d.label, "data": d.data} for d in data]
if len(options) > 0:
random_value = random.choice(options)
option = random.choice(options)
random_value = option['value']
else:
raise ApiError.from_task("invalid enum", "You specified an enumeration field (%s),"
" with no options" % field.id, task)
@ -755,11 +750,24 @@ class WorkflowService(object):
@staticmethod
def process_options(spiff_task, field):
if field.type != Task.FIELD_TYPE_ENUM:
return field
# If this is an auto-complete field, do not populate options, a lookup will happen later.
if field.type == Task.FIELD_TYPE_AUTO_COMPLETE:
pass
elif field.has_property(Task.FIELD_PROP_SPREADSHEET_NAME):
if hasattr(field, 'options') and len(field.options) > 1:
return field
elif not (field.has_property(Task.FIELD_PROP_VALUE_COLUMN) or
field.has_property(Task.FIELD_PROP_LABEL_COLUMN)):
raise ApiError.from_task("invalid_enum",
f"For enumerations, you must include options, or a way to generate options from"
f" a spreadsheet or data set. Please set either a spreadsheet name or data name,"
f" along with the value and label columns to use from these sources. Valid params"
f" include: "
f"{Task.FIELD_PROP_SPREADSHEET_NAME}, "
f"{Task.FIELD_PROP_DATA_NAME}, "
f"{Task.FIELD_PROP_VALUE_COLUMN}, "
f"{Task.FIELD_PROP_LABEL_COLUMN}", task=spiff_task)
if field.has_property(Task.FIELD_PROP_SPREADSHEET_NAME):
lookup_model = LookupService.get_lookup_model(spiff_task, field)
data = db.session.query(LookupDataModel).filter(LookupDataModel.lookup_file_model == lookup_model).all()
if not hasattr(field, 'options'):
@ -768,16 +776,12 @@ class WorkflowService(object):
field.options.append({"id": d.value, "name": d.label, "data": d.data})
elif field.has_property(Task.FIELD_PROP_DATA_NAME):
field.options = WorkflowService.get_options_from_task_data(spiff_task, field)
return field
@staticmethod
def get_options_from_task_data(spiff_task, field):
if not (field.has_property(Task.FIELD_PROP_VALUE_COLUMN) or
field.has_property(Task.FIELD_PROP_LABEL_COLUMN)):
raise ApiError.from_task("invalid_enum",
f"For enumerations based on task data, you must include 3 properties: "
f"{Task.FIELD_PROP_DATA_NAME}, {Task.FIELD_PROP_VALUE_COLUMN}, "
f"{Task.FIELD_PROP_LABEL_COLUMN}", task=spiff_task)
prop = field.get_property(Task.FIELD_PROP_DATA_NAME)
if prop not in spiff_task.data:
raise ApiError.from_task("invalid_enum", f"For enumerations based on task data, task data must have "

View File

@ -1,79 +0,0 @@
import pprint
from SpiffWorkflow.bpmn.BpmnScriptEngine import BpmnScriptEngine
from SpiffWorkflow.bpmn.workflow import BpmnWorkflow
from SpiffWorkflow.camunda.serializer.CamundaSerializer import CamundaSerializer
from SpiffWorkflow.camunda.specs.UserTask import EnumFormField, UserTask
class CustomBpmnScriptEngine(BpmnScriptEngine):
"""This is a custom script processor that can be easily injected into Spiff Workflow.
Rather than execute arbitrary code, this assumes the script references a fully qualified python class
such as myapp.RandomFact. """
def execute(self, task, script, **kwargs):
"""
Assume that the script read in from the BPMN file is a fully qualified python class. Instantiate
that class, pass in any data available to the current task so that it might act on it.
Assume that the class implements the "do_task" method.
This allows us to reference custom code from the BPMN diagram.
"""
module_name = "app." + script
class_name = module_name.split(".")[-1]
mod = __import__(module_name, fromlist=[class_name])
klass = getattr(mod, class_name)
klass().do_task(task.data)
def main():
print("Loading BPMN Specification.")
spec = bpmn_diagram_to_spec('app/static/bpmn/random_fact')
print ("Creating a new workflow based on the specification.")
script_engine = CustomBpmnScriptEngine()
workflow = BpmnWorkflow(spec, script_engine=script_engine)
workflow.debug = False
print ("Running automated tasks.")
workflow.do_engine_steps()
while not workflow.is_completed():
workflow.do_engine_steps()
ready_tasks = workflow.get_ready_user_tasks()
while len(ready_tasks) > 0:
for task in ready_tasks:
if isinstance(task.task_spec, UserTask):
show_form(task)
workflow.complete_next()
else:
raise("Unown Ready Task.")
workflow.do_engine_steps()
ready_tasks = workflow.get_ready_user_tasks()
print("All tasks in the workflow are now complete.")
print("The following data was collected:")
pprint.pprint(workflow.last_task.data)
def show_form(task):
model = {}
form = task.task_spec.form
for field in form.fields:
print("Please complete the following questions:")
prompt = field.label
if isinstance(field, EnumFormField):
prompt += "? (Options: " + ', '.join([str(option.id) for option in field.options]) + ")"
prompt += "? "
model[form.key + "." + field.id] = input(prompt)
if task.data is None:
task.data = {}
task.data.update(model)
def bpmn_diagram_to_spec(file_path):
"""This loads up all BPMN diagrams in the BPMN folder."""
workflowSpec = CamundaSerializer().deserialize_workflow_spec(file_path)
return workflowSpec
if __name__ == "__main__":
main()

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_0b469f0" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.5.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_0b469f0" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="4.10.0">
<bpmn:process id="Process_4b7fa29" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>Flow_1kvuzs1</bpmn:outgoing>
@ -12,8 +12,8 @@
<camunda:properties>
<camunda:property id="file_data" value="Study_App_Doc" />
<camunda:property id="spreadsheet.name" value="IRB_HSR_Application_Type.xlsx" />
<camunda:property id="spreadsheet.label.column" value="Label" />
<camunda:property id="spreadsheet.value.column" value="Value" />
<camunda:property id="label.column" value="Label" />
<camunda:property id="value.column" value="Value" />
<camunda:property id="group" value="Application" />
</camunda:properties>
<camunda:validation>

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:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_13oadue" 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:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_13oadue" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="4.10.0">
<bpmn:process id="Process_1e56be7" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>Flow_07vc55t</bpmn:outgoing>
@ -33,8 +33,8 @@
<camunda:formField id="selectedItem" label="Select An Item" type="enum">
<camunda:properties>
<camunda:property id="spreadsheet.name" value="animals.xlsx" />
<camunda:property id="spreadsheet.value.column" value="Value" />
<camunda:property id="spreadsheet.label.column" value="Label" />
<camunda:property id="value.column" value="Value" />
<camunda:property id="label.column" value="Label" />
</camunda:properties>
</camunda:formField>
</camunda:formData>
@ -48,8 +48,8 @@
<camunda:formField id="selectedItem" type="enum">
<camunda:properties>
<camunda:property id="spreadsheet.name" value="fruits.xlsx" />
<camunda:property id="spreadsheet.value.column" value="Value" />
<camunda:property id="spreadsheet.label.column" value="Label" />
<camunda:property id="value.column" value="Value" />
<camunda:property id="label.column" value="Label" />
</camunda:properties>
</camunda:formField>
</camunda:formData>
@ -80,41 +80,41 @@
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1e56be7">
<bpmndi:BPMNShape id="TextAnnotation_1vfpzfh_di" bpmnElement="TextAnnotation_1vfpzfh">
<dc:Bounds x="640" y="80" width="200" height="60" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_07vc55t_di" bpmnElement="Flow_07vc55t">
<di:waypoint x="188" y="307" />
<di:waypoint x="250" y="307" />
<bpmndi:BPMNEdge id="Flow_1m73p95_di" bpmnElement="Flow_1m73p95">
<di:waypoint x="350" y="307" />
<di:waypoint x="385" y="307" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0gb1k4g_di" bpmnElement="Flow_0gb1k4g">
<di:waypoint x="410" y="282" />
<di:waypoint x="410" y="240" />
<di:waypoint x="490" y="240" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_08kr305_di" bpmnElement="Flow_08kr305">
<di:waypoint x="410" y="332" />
<di:waypoint x="410" y="370" />
<di:waypoint x="490" y="370" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_09ik0zr_di" bpmnElement="Flow_09ik0zr">
<di:waypoint x="590" y="240" />
<di:waypoint x="670" y="240" />
<di:waypoint x="670" y="282" />
<bpmndi:BPMNEdge id="Flow_1ym0gex_di" bpmnElement="Flow_1ym0gex">
<di:waypoint x="695" y="307" />
<di:waypoint x="752" y="307" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1tzwe06_di" bpmnElement="Flow_1tzwe06">
<di:waypoint x="590" y="370" />
<di:waypoint x="670" y="370" />
<di:waypoint x="670" y="332" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1ym0gex_di" bpmnElement="Flow_1ym0gex">
<di:waypoint x="695" y="307" />
<di:waypoint x="752" y="307" />
<bpmndi:BPMNEdge id="Flow_09ik0zr_di" bpmnElement="Flow_09ik0zr">
<di:waypoint x="590" y="240" />
<di:waypoint x="670" y="240" />
<di:waypoint x="670" y="282" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1m73p95_di" bpmnElement="Flow_1m73p95">
<di:waypoint x="350" y="307" />
<di:waypoint x="385" y="307" />
<bpmndi:BPMNEdge id="Flow_08kr305_di" bpmnElement="Flow_08kr305">
<di:waypoint x="410" y="332" />
<di:waypoint x="410" y="370" />
<di:waypoint x="490" y="370" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0gb1k4g_di" bpmnElement="Flow_0gb1k4g">
<di:waypoint x="410" y="282" />
<di:waypoint x="410" y="240" />
<di:waypoint x="490" y="240" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_07vc55t_di" bpmnElement="Flow_07vc55t">
<di:waypoint x="188" y="307" />
<di:waypoint x="250" y="307" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="152" y="289" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Gateway_1j2ytgn_di" bpmnElement="Gateway_1j2ytgn" isMarkerVisible="true">
<dc:Bounds x="385" y="282" width="50" height="50" />
</bpmndi:BPMNShape>
@ -130,12 +130,12 @@
<bpmndi:BPMNShape id="Activity_0kcbe51_di" bpmnElement="fruits">
<dc:Bounds x="490" y="330" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="152" y="289" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1fixobo_di" bpmnElement="Activity_0s5qx04">
<dc:Bounds x="250" y="267" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="TextAnnotation_1vfpzfh_di" bpmnElement="TextAnnotation_1vfpzfh">
<dc:Bounds x="640" y="80" width="200" height="60" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Association_0w3ioqq_di" bpmnElement="Association_0w3ioqq">
<di:waypoint x="589" y="208" />
<di:waypoint x="694" y="140" />

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_1v1rp1q" 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:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_1v1rp1q" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="4.10.0">
<bpmn:process id="Process_1vu5nxl" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>SequenceFlow_0lvudp8</bpmn:outgoing>
@ -15,8 +15,8 @@
<camunda:formField id="AllTheNames" label="Select a value" type="enum">
<camunda:properties>
<camunda:property id="spreadsheet.name" value="customer_list.xlsx" />
<camunda:property id="spreadsheet.value.column" value="CUSTOMER_NUMBER" />
<camunda:property id="spreadsheet.label.column" value="CUSTOMER_NAME" />
<camunda:property id="value.column" value="CUSTOMER_NUMBER" />
<camunda:property id="label.column" value="CUSTOMER_NAME" />
</camunda:properties>
</camunda:formField>
</camunda:formData>

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_1v1rp1q" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="4.0.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_1v1rp1q" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="4.10.0">
<bpmn:process id="Process_1vu5nxl" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>SequenceFlow_0lvudp8</bpmn:outgoing>
@ -15,8 +15,8 @@
<camunda:formField id="guest_of_honor" label="Who is the guest of honor?" type="enum">
<camunda:properties>
<camunda:property id="data.name" value="invitees" />
<camunda:property id="data.value.column" value="secret_id" />
<camunda:property id="data.label.column" value="display_name" />
<camunda:property id="value.column" value="secret_id" />
<camunda:property id="label.column" value="display_name" />
</camunda:properties>
</camunda:formField>
</camunda:formData>
@ -71,29 +71,33 @@
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1vu5nxl">
<bpmndi:BPMNEdge id="Flow_1yet4a9_di" bpmnElement="Flow_1yet4a9">
<di:waypoint x="350" y="121" />
<di:waypoint x="375" y="121" />
<di:waypoint x="375" y="117" />
<di:waypoint x="400" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="SequenceFlow_02vev7n_di" bpmnElement="SequenceFlow_02vev7n">
<di:waypoint x="500" y="117" />
<di:waypoint x="542" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="SequenceFlow_0lvudp8_di" bpmnElement="SequenceFlow_0lvudp8">
<di:waypoint x="215" y="117" />
<di:waypoint x="250" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1yet4a9_di" bpmnElement="Flow_1yet4a9">
<di:waypoint x="350" y="117" />
<di:waypoint x="400" y="117" />
<di:waypoint x="233" y="117" />
<di:waypoint x="233" y="121" />
<di:waypoint x="250" y="121" />
</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_0k8rwp9_di" bpmnElement="Activity_0oa736e">
<dc:Bounds x="250" y="77" width="100" height="80" />
<bpmndi:BPMNShape id="EndEvent_0q4qzl9_di" bpmnElement="EndEvent_0q4qzl9">
<dc:Bounds x="542" y="99" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="UserTask_18ly1yq_di" bpmnElement="Task_14svgcu">
<dc:Bounds x="400" y="77" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="EndEvent_0q4qzl9_di" bpmnElement="EndEvent_0q4qzl9">
<dc:Bounds x="542" y="99" width="36" height="36" />
<bpmndi:BPMNShape id="Activity_0k8rwp9_di" bpmnElement="Activity_0oa736e">
<dc:Bounds x="250" y="81" width="100" height="80" />
</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_1v1rp1q" 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:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_1v1rp1q" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="4.10.0">
<bpmn:process id="Process_1vu5nxl" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>SequenceFlow_0lvudp8</bpmn:outgoing>
@ -15,8 +15,8 @@
<camunda:formField id="sponsor" label="Select a value" type="autocomplete">
<camunda:properties>
<camunda:property id="spreadsheet.name" value="sponsors.xlsx" />
<camunda:property id="spreadsheet.value.column" value="CUSTOMER_NUMBER" />
<camunda:property id="spreadsheet.label.column" value="CUSTOMER_NAME" />
<camunda:property id="value.column" value="CUSTOMER_NUMBER" />
<camunda:property id="label.column" value="CUSTOMER_NAME" />
</camunda:properties>
</camunda:formField>
</camunda:formData>

View File

@ -210,7 +210,7 @@ class TestStudyService(BaseTest):
# asd3v is not in ldap, so an error should be returned.
self.assertEqual("asd3v", investigators['DC']['user_id'])
self.assertEqual("Unable to locate a user with id asd3v in LDAP", investigators['DC']['error']) # Data from ldap
self.assertEqual("ApiError: Unable to locate a user with id asd3v in LDAP. ", investigators['DC']['error']) # Data from ldap
# No value is provided for Department Chair
self.assertIsNone(investigators['DEPT_CH']['user_id'])

View File

@ -49,7 +49,7 @@ class TestFileDatastore(BaseTest):
# process the form that sets the datastore values
self.complete_form(workflow, task, {'Study_App_Doc': {'id': file_id},
'IRB_HSR_Application_Type': {'label': 'Expedited Application'},
'IRB_HSR_Application_Type': 'Expedited Application',
'my_test_field': 'some string',
'the_number': 8,
'a_boolean': True,

View File

@ -70,8 +70,8 @@ class TestLookupService(BaseTest):
processor.do_engine_steps()
results = LookupService.lookup(workflow, "TaskEnumLookup", "AllTheNames", "", value="1000", limit=10)
self.assertEqual(1, len(results), "It is possible to find an item based on the id, rather than as a search")
self.assertIsNotNone(results[0].data)
self.assertIsInstance(results[0].data, dict)
self.assertIsNotNone(results[0])
self.assertIsInstance(results[0], dict)
def test_lookup_with_two_spreadsheets_with_the_same_field_name_in_different_forms(self):
@ -87,7 +87,7 @@ class TestLookupService(BaseTest):
task = processor.get_ready_user_tasks()[0]
results = LookupService.lookup(workflow, task.task_spec.name, "selectedItem", "", value="pigs", limit=10)
self.assertEqual(1, len(results), "It is possible to find an item based on the id, rather than as a search")
self.assertIsNotNone(results[0].data)
self.assertIsNotNone(results[0])
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.")
@ -100,7 +100,7 @@ class TestLookupService(BaseTest):
task = processor.get_ready_user_tasks()[0]
results = LookupService.lookup(workflow, task.task_spec.name, "selectedItem", "", value="apples", limit=10)
self.assertEqual(1, len(results), "It is possible to find an item based on the id, rather than as a search")
self.assertIsNotNone(results[0].data)
self.assertIsNotNone(results[0])
results = LookupService.lookup(workflow, task.task_spec.name, "selectedItem", "", value="pigs", limit=10)
self.assertEqual(0, len(results), "We shouldn't find our animals mixed in with our fruits.")
@ -116,55 +116,63 @@ class TestLookupService(BaseTest):
results = LookupService.lookup(workflow, "TaskEnumLookup", "AllTheNames", "medicines", limit=10)
self.assertEqual(1, len(results), "words in the middle of label are detected.")
self.assertEqual("The Medicines Company", results[0].label)
self.assertEqual("The Medicines Company", results[0]['CUSTOMER_NAME'])
results = LookupService.lookup(workflow,"TaskEnumLookup", "AllTheNames", "UVA", limit=10)
self.assertEqual(1, len(results), "Beginning of label is found.")
self.assertEqual("UVA - INTERNAL - GM USE ONLY", results[0].label)
self.assertEqual("UVA - INTERNAL - GM USE ONLY", results[0]['CUSTOMER_NAME'])
results = LookupService.lookup(workflow, "TaskEnumLookup","AllTheNames", "uva", limit=10)
self.assertEqual(1, len(results), "case does not matter.")
self.assertEqual("UVA - INTERNAL - GM USE ONLY", results[0].label)
self.assertEqual("UVA - INTERNAL - GM USE ONLY", results[0]['CUSTOMER_NAME'])
results = LookupService.lookup(workflow, "TaskEnumLookup", "AllTheNames", "medici", limit=10)
self.assertEqual(1, len(results), "partial words are picked up.")
self.assertEqual("The Medicines Company", results[0].label)
self.assertEqual("The Medicines Company", results[0]['CUSTOMER_NAME'])
results = LookupService.lookup(workflow, "TaskEnumLookup", "AllTheNames", "Genetics Savings", limit=10)
self.assertEqual(1, len(results), "multiple terms are picked up..")
self.assertEqual("Genetics Savings & Clone, Inc.", results[0].label)
self.assertEqual("Genetics Savings & Clone, Inc.", results[0]['CUSTOMER_NAME'])
results = LookupService.lookup(workflow, "TaskEnumLookup", "AllTheNames", "Genetics Sav", limit=10)
self.assertEqual(1, len(results), "prefix queries still work with partial terms")
self.assertEqual("Genetics Savings & Clone, Inc.", results[0].label)
self.assertEqual("Genetics Savings & Clone, Inc.", results[0]['CUSTOMER_NAME'])
results = LookupService.lookup(workflow, "TaskEnumLookup", "AllTheNames", "Gen Sav", limit=10)
self.assertEqual(1, len(results), "prefix queries still work with ALL the partial terms")
self.assertEqual("Genetics Savings & Clone, Inc.", results[0].label)
self.assertEqual("Genetics Savings & Clone, Inc.", results[0]['CUSTOMER_NAME'])
results = LookupService.lookup(workflow, "TaskEnumLookup", "AllTheNames", "Inc", limit=10)
self.assertEqual(7, len(results), "short terms get multiple correct results.")
self.assertEqual("Genetics Savings & Clone, Inc.", results[0].label)
self.assertEqual("Genetics Savings & Clone, Inc.", results[0]['CUSTOMER_NAME'])
results = LookupService.lookup(workflow, "TaskEnumLookup", "AllTheNames", "reaction design", limit=10)
self.assertEqual(3, len(results), "all results come back for two terms.")
self.assertEqual("Reaction Design", results[0].label, "Exact matches come first.")
self.assertEqual("Reaction Design", results[0]['CUSTOMER_NAME'], "Exact matches come first.")
results = LookupService.lookup(workflow, "TaskEnumLookup", "AllTheNames", "1 Something", limit=10)
self.assertEqual("1 Something", results[0].label, "Exact matches are preferred")
self.assertEqual("1 Something", results[0]['CUSTOMER_NAME'], "Exact matches are preferred")
results = LookupService.lookup(workflow, "TaskEnumLookup", "AllTheNames", "1 (!-Something", limit=10)
self.assertEqual("1 Something", results[0].label, "special characters don't flake out")
self.assertEqual("1 Something", results[0]['CUSTOMER_NAME'], "special characters don't flake out")
results = LookupService.lookup(workflow, "TaskEnumLookup", "AllTheNames", "1 Something", limit=10)
self.assertEqual("1 Something", results[0].label, "double spaces should not be an issue.")
self.assertEqual("1 Something", results[0]['CUSTOMER_NAME'], "double spaces should not be an issue.")
results = LookupService.lookup(workflow, "TaskEnumLookup", "AllTheNames", "in", limit=10)
self.assertEqual(10, len(results), "stop words are not removed.")
self.assertEqual("Genetics Savings & Clone, Inc.", results[0].label)
self.assertEqual("Genetics Savings & Clone, Inc.", results[0]['CUSTOMER_NAME'])
results = LookupService.lookup(workflow, "TaskEnumLookup", "AllTheNames", "other", limit=10)
self.assertEqual("Other", results[0].label, "Can't find the word 'other', which is an english stop word")
self.assertEqual("Other", results[0]['CUSTOMER_NAME'], "Can't find the word 'other', which is an english stop word")
def test_find_by_id(self):
spec = BaseTest.load_test_spec('enum_options_from_file')
workflow = self.create_workflow('enum_options_from_file')
processor = WorkflowProcessor(workflow)
processor.do_engine_steps()
result = LookupService.lookup(workflow, "TaskEnumLookup", "AllTheNames", None, value="1000")
first_result = result[0]
self.assertEquals(1000, first_result['CUSTOMER_NUMBER'])
self.assertEquals('UVA - INTERNAL - GM USE ONLY', first_result['CUSTOMER_NAME'])

View File

@ -15,16 +15,10 @@ from SpiffWorkflow.bpmn.PythonScriptEngine import Box
class TestTasksApi(BaseTest):
def assert_options_populated(self, results, lookup_data_keys):
option_keys = ['value', 'label', 'data']
self.assertIsInstance(results, list)
for result in results:
for option_key in option_keys:
self.assertTrue(option_key in result, 'should have value, label, and data properties populated')
self.assertIsNotNone(result[option_key], '%s should not be None' % option_key)
self.assertIsInstance(result['data'], dict)
self.assertIsInstance(result, dict)
for lookup_data_key in lookup_data_keys:
self.assertTrue(lookup_data_key in result['data'], 'should have all lookup data columns populated')
self.assertTrue(lookup_data_key in result, 'should have all lookup data columns populated')
def test_get_current_user_tasks(self):
self.load_example_data()
@ -287,8 +281,9 @@ class TestTasksApi(BaseTest):
self.assertEqual(5, len(results))
self.assert_options_populated(results, ['CUSTOMER_NUMBER', 'CUSTOMER_NAME', 'CUSTOMER_CLASS_MEANING'])
# Use the lookup to find a specific record, rather than running a search.
rv = self.app.get('/v1.0/workflow/%i/lookup/%s/%s?value=%s' %
(workflow.id, task.name, field_id, results[0]['value']), # All records with a word that starts with 'c'
(workflow.id, task.name, field_id, results[0]['CUSTOMER_NUMBER']),
headers=self.logged_in_headers(),
content_type="application/json")
results = json.loads(rv.get_data(as_text=True))
@ -316,7 +311,7 @@ class TestTasksApi(BaseTest):
results = json.loads(rv.get_data(as_text=True))
self.assertEqual(1, len(results))
self.assert_options_populated(results, ['CUSTOMER_NUMBER', 'CUSTOMER_NAME', 'CUSTOMER_CLASS_MEANING'])
self.assertIsInstance(results[0]['data'], dict)
self.assertIsInstance(results[0], dict)
def test_enum_from_task_data(self):
workflow = self.create_workflow('enum_options_from_task_data')

View File

@ -13,7 +13,7 @@ class TestValidateEndEvent(BaseTest):
mock_get.return_value.ok = True
mock_get.return_value.text = self.protocol_builder_response('study_details.json')
error_string = """Error processing template for task EndEvent_1qvyxg7: expected token 'end of statement block', got '='"""
error_string = """ApiError: Error processing template for task EndEvent_1qvyxg7: expected token 'end of statement block', got '='. In file verify_end_event.bpmn. """
self.load_example_data()
spec_model = self.load_test_spec('verify_end_event')

View File

@ -19,7 +19,7 @@ class TestWorkflowEnumDefault(BaseTest):
workflow_api = self.get_workflow_api(workflow)
self.assertEqual('Activity_PickColor', workflow_api.next_task.name)
self.assertEqual({'value': 'black', 'label': 'Black'}, workflow_api.next_task.data['color_select'])
self.assertEqual('black', workflow_api.next_task.data['color_select'])
#
workflow = self.create_workflow('enum_value_expression')
@ -36,7 +36,7 @@ class TestWorkflowEnumDefault(BaseTest):
workflow_api = self.get_workflow_api(workflow)
self.assertEqual('Activity_PickColor', workflow_api.next_task.name)
self.assertEqual({'value': 'white', 'label': 'White'}, workflow_api.next_task.data['color_select'])
self.assertEqual('white', workflow_api.next_task.data['color_select'])
def test_enum_value_expression_and_default(self):
spec_model = self.load_test_spec('enum_value_expression_fail')

View File

@ -13,7 +13,7 @@ class TestEmptyEnumList(BaseTest):
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.assertEqual(json_data[0]['code'], 'invalid enum')
self.assertEqual(json_data[0]['code'], 'invalid_enum')
def test_default_values_for_enum_as_checkbox(self):
self.load_test_spec('enum_results')
@ -25,4 +25,4 @@ class TestEmptyEnumList(BaseTest):
checkbox_enum_field = task.task_spec.form.fields[0]
radio_enum_field = task.task_spec.form.fields[1]
self.assertEqual([], service.get_default_value(checkbox_enum_field, task))
self.assertEqual({'label': None, 'value': None}, service.get_default_value(radio_enum_field, task))
self.assertEqual(None, service.get_default_value(radio_enum_field, task))

View File

@ -88,7 +88,7 @@ class TestWorkflowService(BaseTest):
task = processor.next_task()
task_api = WorkflowService.spiff_task_to_api_task(task, add_docs_and_forms=True)
WorkflowService.populate_form_with_random_data(task, task_api, required_only=False)
self.assertTrue(isinstance(task.data["sponsor"], dict))
self.assertTrue(isinstance(task.data["sponsor"], str))
def test_dmn_evaluation_errors_in_oncomplete_raise_api_errors_during_validation(self):
workflow_spec_model = self.load_test_spec("decision_table_invalid")

View File

@ -39,27 +39,6 @@ class TestWorkflowSpecValidation(BaseTest):
self.assertEqual(0, len(self.validate_workflow("two_forms")))
self.assertEqual(0, len(self.validate_workflow("ldap_lookup")))
@unittest.skip("Major changes to operators, pushing up with broken crc workflows so we can progress together")
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_investigators') # mock_studies
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_required_docs') # mock_docs
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_study_details') # mock_details
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_studies') # mock_studies
def test_successful_validation_of_crc_workflows(self, mock_studies, mock_details, mock_docs, mock_investigators):
# Mock Protocol Builder responses
studies_response = self.protocol_builder_response('user_studies.json')
mock_studies.return_value = ProtocolBuilderStudySchema(many=True).loads(studies_response)
details_response = self.protocol_builder_response('study_details.json')
mock_details.return_value = json.loads(details_response)
docs_response = self.protocol_builder_response('required_docs.json')
mock_docs.return_value = json.loads(docs_response)
investigators_response = self.protocol_builder_response('investigators.json')
mock_investigators.return_value = json.loads(investigators_response)
self.load_example_data(use_crc_data=True)
app.config['PB_ENABLED'] = True
self.validate_all_loaded_workflows()
def validate_all_loaded_workflows(self):
workflows = session.query(WorkflowSpecModel).all()
errors = []
@ -146,7 +125,7 @@ class TestWorkflowSpecValidation(BaseTest):
final_data = WorkflowService.test_spec(spec_model.id, required_only=True)
self.assertIsNotNone(final_data)
self.assertIn('enum_with_default', final_data)
self.assertEqual('maybe', final_data['enum_with_default']['value'])
self.assertEqual('maybe', final_data['enum_with_default'])
def test_invalid_custom_field(self):
self.load_example_data()

View File

@ -14,9 +14,7 @@ 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.assertIn('color', second_task.data)
self.assertIsNone(second_task.data['color']['value'])
self.assertNotIn('color', second_task.data)
@ -32,7 +30,7 @@ class TestValueExpression(BaseTest):
second_task = workflow_api.next_task
self.assertEqual('black', second_task.data['value_expression_value'])
self.assertIn('color', second_task.data)
self.assertEqual('black', second_task.data['color']['value'])
self.assertEqual('black', second_task.data['color'])
def test_validate_task_with_both_default_and_expression(self):
# This actually fails validation.