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:
parent
c5d8e20e74
commit
5429e7da7d
|
@ -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
|
||||
|
|
|
@ -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."""
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -204,7 +204,7 @@ class LookupDataModel(db.Model):
|
|||
)
|
||||
|
||||
|
||||
class LookupDataSchema(SQLAlchemyAutoSchema):
|
||||
class LookupDataSchema(ma.Schema):
|
||||
class Meta:
|
||||
model = LookupDataModel
|
||||
load_instance = True
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 "
|
||||
|
|
|
@ -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()
|
|
@ -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>
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
Binary file not shown.
|
@ -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'])
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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'])
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue