Merge pull request #169 from sartography/cr-connect-106-augment-eval-methods

Cr connect 106 augment eval methods
This commit is contained in:
Dan Funk 2020-07-30 13:51:57 -04:00 committed by GitHub
commit 5d8fee7bc7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 281 additions and 314 deletions

View File

@ -38,7 +38,7 @@ recommonmark = "*"
requests = "*"
sentry-sdk = {extras = ["flask"],version = "==0.14.4"}
sphinx = "*"
spiffworkflow = {git = "https://github.com/sartography/SpiffWorkflow.git",ref = "master"}
spiffworkflow = {git = "https://github.com/sartography/SpiffWorkflow.git",ref = "cr-connect-106-augment-eval"}
#spiffworkflow = {editable = true,path="/home/kelly/sartography/SpiffWorkflow/"}
swagger-ui-bundle = "*"
webtest = "*"

4
Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "381d29428eb328ad6167774b510b9d818bd1505b95f50454a19f1564782326cc"
"sha256": "afb6a541d1a9f33155f91529ad961492dceded89466aa1e02fed9901ac5eb146"
},
"pipfile-spec": 6,
"requires": {
@ -804,7 +804,7 @@
},
"spiffworkflow": {
"git": "https://github.com/sartography/SpiffWorkflow.git",
"ref": "11ad40bbcb0fbd3c5bc1078e4989dc38b749f7f3"
"ref": "7712830665b4419df019413ac095cb0749adb346"
},
"sqlalchemy": {
"hashes": [

View File

@ -40,7 +40,7 @@ class FactService(Script):
else:
details = "unknown fact type."
self.add_data_to_task(task, details)
#self.add_data_to_task(task, details)
print(details)
return details

50
crc/scripts/ldap.py Normal file
View File

@ -0,0 +1,50 @@
import copy
from crc import app
from crc.api.common import ApiError
from crc.scripts.script import Script
from crc.services.ldap_service import LdapService
class Ldap(Script):
"""This Script allows to be introduced as part of a workflow and called from there, taking
a UID (or several) as input and looking it up through LDAP to return the person's details """
def get_description(self):
return """
Attempts to create a dictionary with person details, using the
provided argument (a UID) and look it up through LDAP.
Examples:
supervisor_info = ldap(supervisor_uid) // Sets the supervisor information to ldap details for the given uid.
"""
def do_task_validate_only(self, task, *args, **kwargs):
return self.set_users_info_in_task(task, args)
def do_task(self, task, study_id, workflow_id, *args, **kwargs):
return self.set_users_info_in_task(task, args)
def set_users_info_in_task(self, task, args):
if len(args) != 1:
raise ApiError(code="missing_argument",
message="Ldap takes a single argument, the "
"UID for the person we want to look up")
uid = args[0]
user_info_dict = {}
user_info = LdapService.user_info(uid)
user_info_dict = {
"display_name": user_info.display_name,
"given_name": user_info.given_name,
"email_address": user_info.email_address,
"telephone_number": user_info.telephone_number,
"title": user_info.title,
"department": user_info.department,
"affiliation": user_info.affiliation,
"sponsor_type": user_info.sponsor_type,
"uid": user_info.uid,
"proper_name": user_info.proper_name()
}
return user_info_dict

View File

@ -1,78 +0,0 @@
import copy
from crc import app
from crc.api.common import ApiError
from crc.scripts.script import Script
from crc.services.ldap_service import LdapService
USER_DETAILS = {
"PIComputingID": {
"value": "",
"data": {
},
"label": "invalid uid"
}
}
class LdapLookup(Script):
"""This Script allows to be introduced as part of a workflow and called from there, taking
a UID as input and looking it up through LDAP to return the person's details """
def get_description(self):
return """
Attempts to create a dictionary with person details, using the
provided argument (a UID) and look it up through LDAP.
Example:
LdapLookup PIComputingID
"""
def do_task_validate_only(self, task, *args, **kwargs):
self.get_user_info(task, args)
def do_task(self, task, *args, **kwargs):
args = [arg for arg in args if type(arg) == str]
user_info = self.get_user_info(task, args)
user_details = copy.deepcopy(USER_DETAILS)
user_details['PIComputingID']['value'] = user_info['uid']
if len(user_info.keys()) > 1:
user_details['PIComputingID']['label'] = user_info.pop('label')
else:
user_info.pop('uid')
user_details['PIComputingID']['data'] = user_info
return user_details
def get_user_info(self, task, args):
if len(args) < 1:
raise ApiError(code="missing_argument",
message="Ldap lookup script requires one argument. The "
"UID for the person we want to look up")
arg = args.pop() # Extracting only one value for now
uid = task.workflow.script_engine.evaluate_expression(task, arg)
if not isinstance(uid, str):
raise ApiError(code="invalid_argument",
message="Ldap lookup script requires one 1 UID argument, of type string.")
user_info_dict = {}
try:
user_info = LdapService.user_info(uid)
user_info_dict = {
"display_name": user_info.display_name,
"given_name": user_info.given_name,
"email_address": user_info.email_address,
"telephone_number": user_info.telephone_number,
"title": user_info.title,
"department": user_info.department,
"affiliation": user_info.affiliation,
"sponsor_type": user_info.sponsor_type,
"uid": user_info.uid,
"label": user_info.proper_name()
}
except:
user_info_dict['uid'] = uid
app.logger.error(f'Ldap lookup failed for UID {uid}')
return user_info_dict

View File

@ -1,60 +0,0 @@
import copy
from crc import app
from crc.api.common import ApiError
from crc.scripts.script import Script
from crc.services.ldap_service import LdapService
class LdapReplace(Script):
"""This Script allows to be introduced as part of a workflow and called from there, taking
a UID (or several) as input and looking it up through LDAP to return the person's details """
def get_description(self):
return """
Attempts to create a dictionary with person details, using the
provided argument (a UID) and look it up through LDAP.
Examples:
#! LdapReplace supervisor
#! LdapReplace supervisor collaborator
#! LdapReplace supervisor cosupervisor collaborator
"""
def do_task_validate_only(self, task, *args, **kwargs):
self.set_users_info_in_task(task, args)
def do_task(self, task, *args, **kwargs):
args = [arg for arg in args if type(arg) == str]
self.set_users_info_in_task(task, args)
def set_users_info_in_task(self, task, args):
if len(args) < 1:
raise ApiError(code="missing_argument",
message="Ldap replace script requires at least one argument. The "
"UID for the person(s) we want to look up")
users_info = {}
for arg in args:
uid = task.workflow.script_engine.evaluate_expression(task, arg)
if not isinstance(uid, str):
raise ApiError(code="invalid_argument",
message="Ldap replace script found an invalid argument, type string is required")
user_info_dict = {}
try:
user_info = LdapService.user_info(uid)
user_info_dict = {
"display_name": user_info.display_name,
"given_name": user_info.given_name,
"email_address": user_info.email_address,
"telephone_number": user_info.telephone_number,
"title": user_info.title,
"department": user_info.department,
"affiliation": user_info.affiliation,
"sponsor_type": user_info.sponsor_type,
"uid": user_info.uid,
"proper_name": user_info.proper_name()
}
except:
app.logger.error(f'Ldap replace failed for UID {uid}')
task.data[arg] = user_info_dict

View File

@ -23,6 +23,53 @@ class Script(object):
"This is an internal error. The script you are trying to execute '%s' " % self.__class__.__name__ +
"does must provide a validate_only option that mimics the do_task, " +
"but does not make external calls or database updates." )
@staticmethod
def generate_augmented_list(task, study_id,workflow_id):
"""
this makes a dictionary of lambda functions that are closed over the class instance that
They represent. This is passed into PythonScriptParser as a list of helper functions that are
available for running. In general, they maintain the do_task call structure that they had, but
they always return a value rather than updating the task data.
We may be able to remove the task for each of these calls if we are not using it other than potentially
updating the task data.
"""
def make_closure(subclass,task,study_id,workflow_id):
instance = subclass()
return lambda *a : subclass.do_task(instance,task,study_id,workflow_id,*a)
execlist = {}
subclasses = Script.get_all_subclasses()
for x in range(len(subclasses)):
subclass = subclasses[x]
execlist[subclass.__module__.split('.')[-1]] = make_closure(subclass,task,study_id,
workflow_id)
return execlist
@staticmethod
def generate_augmented_validate_list(task, study_id, workflow_id):
"""
this makes a dictionary of lambda functions that are closed over the class instance that
They represent. This is passed into PythonScriptParser as a list of helper functions that are
available for running. In general, they maintain the do_task call structure that they had, but
they always return a value rather than updating the task data.
We may be able to remove the task for each of these calls if we are not using it other than potentially
updating the task data.
"""
def make_closure_validate(subclass,task,study_id,workflow_id):
instance = subclass()
return lambda *a : subclass.do_task_validate_only(instance,task,study_id,workflow_id,*a)
execlist = {}
subclasses = Script.get_all_subclasses()
for x in range(len(subclasses)):
subclass = subclasses[x]
execlist[subclass.__module__.split('.')[-1]] = make_closure_validate(subclass,task,study_id,
workflow_id)
return execlist
@staticmethod
def get_all_subclasses():

View File

@ -8,7 +8,7 @@ from crc.scripts.script import Script
from crc.services.file_service import FileService
from crc.services.protocol_builder import ProtocolBuilderService
from crc.services.study_service import StudyService
from box import Box
class StudyInfo(Script):
"""Please see the detailed description that is provided below. """
@ -149,11 +149,11 @@ Returns information specific to the protocol.
def do_task_validate_only(self, task, study_id, workflow_id, *args, **kwargs):
"""For validation only, pretend no results come back from pb"""
self.check_args(args)
self.check_args(args,2)
# Assure the reference file exists (a bit hacky, but we want to raise this error early, and cleanly.)
FileService.get_reference_file_data(FileService.DOCUMENT_LIST)
FileService.get_reference_file_data(FileService.INVESTIGATOR_LIST)
data = {
data = Box({
"study":{
"info": {
"id": 12,
@ -195,38 +195,50 @@ Returns information specific to the protocol.
'id': 0,
}
}
}
self.add_data_to_task(task=task, data=data["study"])
self.add_data_to_task(task, {"documents": StudyService().get_documents_status(study_id)})
})
if args[0]=='documents':
return StudyService().get_documents_status(study_id)
return data['study'][args[0]]
#self.add_data_to_task(task=task, data=data["study"])
#self.add_data_to_task(task, {"documents": StudyService().get_documents_status(study_id)})
def do_task(self, task, study_id, workflow_id, *args, **kwargs):
self.check_args(args)
self.check_args(args,2)
prefix = None
if len(args) > 1:
prefix = args[1]
cmd = args[0]
study_info = {}
if self.__class__.__name__ in task.data:
study_info = task.data[self.__class__.__name__]
# study_info = {}
# if self.__class__.__name__ in task.data:
# study_info = task.data[self.__class__.__name__]
retval = None
if cmd == 'info':
study = session.query(StudyModel).filter_by(id=study_id).first()
schema = StudySchema()
self.add_data_to_task(task, {cmd: schema.dump(study)})
retval = schema.dump(study)
if cmd == 'investigators':
self.add_data_to_task(task, {cmd: StudyService().get_investigators(study_id)})
retval = StudyService().get_investigators(study_id)
if cmd == 'roles':
self.add_data_to_task(task, {cmd: StudyService().get_investigators(study_id, all=True)})
retval = StudyService().get_investigators(study_id, all=True)
if cmd == 'details':
self.add_data_to_task(task, {cmd: self.pb.get_study_details(study_id)})
retval = self.pb.get_study_details(study_id)
if cmd == 'approvals':
self.add_data_to_task(task, {cmd: StudyService().get_approvals(study_id)})
retval = StudyService().get_approvals(study_id)
if cmd == 'documents':
self.add_data_to_task(task, {cmd: StudyService().get_documents_status(study_id)})
retval = StudyService().get_documents_status(study_id)
if cmd == 'protocol':
self.add_data_to_task(task, {cmd: StudyService().get_protocol(study_id)})
retval = StudyService().get_protocol(study_id)
if isinstance(retval,dict) and prefix is not None:
return Box({x:retval[x] for x in retval.keys() if x[:len(prefix)] == prefix})
elif isinstance(retval,dict):
return Box(retval)
else:
return retval
def check_args(self, args):
if len(args) != 1 or (args[0] not in StudyInfo.type_options):
def check_args(self, args, maxlen=1):
if len(args) < 1 or len(args) > maxlen or (args[0] not in StudyInfo.type_options):
raise ApiError(code="missing_argument",
message="The StudyInfo script requires a single argument which must be "
"one of %s" % ",".join(StudyInfo.type_options))

View File

@ -17,6 +17,7 @@ from SpiffWorkflow.dmn.parser.BpmnDmnParser import BpmnDmnParser
from SpiffWorkflow.exceptions import WorkflowTaskExecException
from SpiffWorkflow.specs import WorkflowSpec
import crc
from crc import session, app
from crc.api.common import ApiError
from crc.models.file import FileDataModel, FileModel, FileType
@ -28,64 +29,71 @@ from crc import app
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. """
It will execute python code read in from the bpmn. It will also make any scripts in the
scripts directory available for execution. """
def execute(self, task: SpiffTask, script, data):
"""
Functions in two modes.
1. If the command is proceeded by #! then this is assumed to be a python script, and will
attempt to load that python module and execute the do_task method on that script. Scripts
must be located in the scripts package and they must extend the script.py class.
2. If not proceeded by the #! this will attempt to execute the script directly and assumes it is
valid Python.
"""
# Shlex splits the whole string while respecting double quoted strings within
if not script.startswith('#!'):
try:
super().execute(task, script, data)
except SyntaxError as e:
raise ApiError.from_task('syntax_error',
f'If you are running a pre-defined script, please'
f' proceed the script with "#!", otherwise this is assumed to be'
f' pure python: {script}, {e.msg}', task=task)
else:
self.run_predefined_script(task, script[2:], data) # strip off the first two characters.
def run_predefined_script(self, task: SpiffTask, script, data):
commands = shlex.split(script)
path_and_command = commands[0].rsplit(".", 1)
if len(path_and_command) == 1:
module_name = "crc.scripts." + self.camel_to_snake(path_and_command[0])
class_name = path_and_command[0]
study_id = task.workflow.data[WorkflowProcessor.STUDY_ID_KEY]
if WorkflowProcessor.WORKFLOW_ID_KEY in task.workflow.data:
workflow_id = task.workflow.data[WorkflowProcessor.WORKFLOW_ID_KEY]
else:
module_name = "crc.scripts." + path_and_command[0] + "." + self.camel_to_snake(path_and_command[1])
class_name = path_and_command[1]
workflow_id = None
try:
mod = __import__(module_name, fromlist=[class_name])
klass = getattr(mod, class_name)
study_id = task.workflow.data[WorkflowProcessor.STUDY_ID_KEY]
if WorkflowProcessor.WORKFLOW_ID_KEY in task.workflow.data:
workflow_id = task.workflow.data[WorkflowProcessor.WORKFLOW_ID_KEY]
else:
workflow_id = None
if not isinstance(klass(), Script):
raise ApiError.from_task("invalid_script",
"This is an internal error. The script '%s:%s' you called " %
(module_name, class_name) +
"does not properly implement the CRC Script class.",
task=task)
if task.workflow.data[WorkflowProcessor.VALIDATION_PROCESS_KEY]:
"""If this is running a validation, and not a normal process, then we want to
mimic running the script, but not make any external calls or database changes."""
klass().do_task_validate_only(task, study_id, workflow_id, *commands[1:])
augmentMethods = Script.generate_augmented_validate_list(task, study_id, workflow_id)
else:
klass().do_task(task, study_id, workflow_id, *commands[1:])
except ModuleNotFoundError:
raise ApiError.from_task("invalid_script",
"Unable to locate Script: '%s:%s'" % (module_name, class_name),
task=task)
augmentMethods = Script.generate_augmented_list(task, study_id, workflow_id)
super().execute(task, script, data, externalMethods=augmentMethods)
except SyntaxError as e:
raise ApiError('syntax_error',
f'Something is wrong with your python script '
f'please correct the following:'
f' {script}, {e.msg}')
except NameError as e:
raise ApiError('name_error',
f'something you are referencing does not exist:'
f' {script}, {e.name}')
# else:
# self.run_predefined_script(task, script[2:], data) # strip off the first two characters.
# def run_predefined_script(self, task: SpiffTask, script, data):
# commands = shlex.split(script)
# path_and_command = commands[0].rsplit(".", 1)
# if len(path_and_command) == 1:
# module_name = "crc.scripts." + self.camel_to_snake(path_and_command[0])
# class_name = path_and_command[0]
# else:
# module_name = "crc.scripts." + path_and_command[0] + "." + self.camel_to_snake(path_and_command[1])
# class_name = path_and_command[1]
# try:
# mod = __import__(module_name, fromlist=[class_name])
# klass = getattr(mod, class_name)
# study_id = task.workflow.data[WorkflowProcessor.STUDY_ID_KEY]
# if WorkflowProcessor.WORKFLOW_ID_KEY in task.workflow.data:
# workflow_id = task.workflow.data[WorkflowProcessor.WORKFLOW_ID_KEY]
# else:
# workflow_id = None
#
# if not isinstance(klass(), Script):
# raise ApiError.from_task("invalid_script",
# "This is an internal error. The script '%s:%s' you called " %
# (module_name, class_name) +
# "does not properly implement the CRC Script class.",
# task=task)
# if task.workflow.data[WorkflowProcessor.VALIDATION_PROCESS_KEY]:
# """If this is running a validation, and not a normal process, then we want to
# mimic running the script, but not make any external calls or database changes."""
# klass().do_task_validate_only(task, study_id, workflow_id, *commands[1:])
# else:
# klass().do_task(task, study_id, workflow_id, *commands[1:])
# except ModuleNotFoundError:
# raise ApiError.from_task("invalid_script",
# "Unable to locate Script: '%s:%s'" % (module_name, class_name),
# task=task)
def evaluate_expression(self, task, expression):
"""
@ -180,10 +188,10 @@ class WorkflowProcessor(object):
bpmn_workflow = BpmnWorkflow(spec, script_engine=self._script_engine)
bpmn_workflow.data[WorkflowProcessor.STUDY_ID_KEY] = workflow_model.study_id
bpmn_workflow.data[WorkflowProcessor.VALIDATION_PROCESS_KEY] = validate_only
try:
bpmn_workflow.do_engine_steps()
except WorkflowException as we:
raise ApiError.from_task_spec("error_loading_workflow", str(we), we.sender)
#try:
bpmn_workflow.do_engine_steps()
# except WorkflowException as we:
# raise ApiError.from_task_spec("error_loading_workflow", str(we), we.sender)
return bpmn_workflow
def save(self):

View File

@ -212,7 +212,8 @@
<bpmn:scriptTask id="Activity_10nxpt2" name="Load Study Details">
<bpmn:incoming>SequenceFlow_1r3yrhy</bpmn:incoming>
<bpmn:outgoing>Flow_09h1imz</bpmn:outgoing>
<bpmn:script>#! StudyInfo details</bpmn:script>
<bpmn:script>StudyInfo = {}
StudyInfo['details'] = study_info('details')</bpmn:script>
</bpmn:scriptTask>
<bpmn:businessRuleTask id="Activity_PBMultiSiteCheckQ12" name="PB Multi-Site Check Q12" camunda:decisionRef="Decision_core_info_multi_site_q12">
<bpmn:incoming>Flow_09h1imz</bpmn:incoming>

View File

@ -453,7 +453,7 @@ Indicate all the possible formats in which you will transmit your data outside o
<bpmn:incoming>SequenceFlow_0k2r83n</bpmn:incoming>
<bpmn:incoming>SequenceFlow_0t6xl9i</bpmn:incoming>
<bpmn:outgoing>SequenceFlow_16kyite</bpmn:outgoing>
<bpmn:script>#! CompleteTemplate NEW_DSP_template.docx Study_DataSecurityPlan</bpmn:script>
<bpmn:script>complete_template('NEW_DSP_template.docx','Study_DataSecurityPlan')</bpmn:script>
</bpmn:scriptTask>
<bpmn:manualTask id="Task_0q6ir2l" name="View Instructions">
<bpmn:documentation>##### Instructions

View File

@ -53,12 +53,13 @@
<bpmn:scriptTask id="Activity_0a14x7j" name="Load Approvals">
<bpmn:incoming>Flow_0c7ryff</bpmn:incoming>
<bpmn:outgoing>Flow_142jtxs</bpmn:outgoing>
<bpmn:script>#! StudyInfo approvals</bpmn:script>
<bpmn:script>StudyInfo['approvals'] = study_info('approvals')</bpmn:script>
</bpmn:scriptTask>
<bpmn:scriptTask id="Activity_1aju60t" name="Load Documents">
<bpmn:incoming>Flow_1k3su2q</bpmn:incoming>
<bpmn:outgoing>Flow_0c7ryff</bpmn:outgoing>
<bpmn:script>#! StudyInfo documents</bpmn:script>
<bpmn:script>StudyInfo = {}
StudyInfo['documents'] = study_info('documents')</bpmn:script>
</bpmn:scriptTask>
<bpmn:sequenceFlow id="Flow_142jtxs" sourceRef="Activity_0a14x7j" targetRef="Activity_DisplayDocsAndApprovals" />
<bpmn:sequenceFlow id="Flow_0c7ryff" sourceRef="Activity_1aju60t" targetRef="Activity_0a14x7j" />

View File

@ -36,7 +36,8 @@
<bpmn:scriptTask id="ScriptTask_1fn00ox" name="Load IRB Details">
<bpmn:incoming>SequenceFlow_1dhb8f4</bpmn:incoming>
<bpmn:outgoing>SequenceFlow_1uzcl1f</bpmn:outgoing>
<bpmn:script>#! StudyInfo details</bpmn:script>
<bpmn:script>StudyInfo = {}
StudyInfo['details'] = study_info('details')</bpmn:script>
</bpmn:scriptTask>
<bpmn:sequenceFlow id="SequenceFlow_1uzcl1f" sourceRef="ScriptTask_1fn00ox" targetRef="Task_SupplementIDE" />
<bpmn:exclusiveGateway id="ExclusiveGateway_1fib89p" name="IS_IDE = True and Number Provided?&#10;&#10;">

View File

@ -217,7 +217,8 @@ Protocol Owner: **(need to insert value here)**</bpmn:documentation>
<bpmn:scriptTask id="Activity_LoadDocuments" name="Load Documents">
<bpmn:incoming>SequenceFlow_1dexemq</bpmn:incoming>
<bpmn:outgoing>Flow_1x9d2mo</bpmn:outgoing>
<bpmn:script>#! StudyInfo documents</bpmn:script>
<bpmn:script>StudyInfo = {}
StudyInfo['documents'] = study_info('documents')</bpmn:script>
</bpmn:scriptTask>
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">

View File

@ -12,7 +12,8 @@
<bpmn:scriptTask id="ScriptTask_LoadIRBDetails" name="Load IRB Details">
<bpmn:incoming>SequenceFlow_1dhb8f4</bpmn:incoming>
<bpmn:outgoing>SequenceFlow_1uzcl1f</bpmn:outgoing>
<bpmn:script>#! StudyInfo details</bpmn:script>
<bpmn:script>StudyInfo = {}
StudyInfo['details'] = study_info('details')</bpmn:script>
</bpmn:scriptTask>
<bpmn:sequenceFlow id="SequenceFlow_1uzcl1f" sourceRef="ScriptTask_LoadIRBDetails" targetRef="Task_SupplementIDE" />
<bpmn:businessRuleTask id="Task_SupplementIDE" name="Current IND Status" camunda:decisionRef="decision_ind_check">

View File

@ -8,7 +8,8 @@
<bpmn:scriptTask id="ScriptTask_02924vs" name="Load IRB Details">
<bpmn:incoming>SequenceFlow_1fmyo77</bpmn:incoming>
<bpmn:outgoing>SequenceFlow_18nr0gf</bpmn:outgoing>
<bpmn:script>#! StudyInfo details</bpmn:script>
<bpmn:script>StudyInfo = {}
StudyInfo['details'] = study_info('details')</bpmn:script>
</bpmn:scriptTask>
<bpmn:sequenceFlow id="SequenceFlow_1fmyo77" sourceRef="StartEvent_1" targetRef="ScriptTask_02924vs" />
<bpmn:sequenceFlow id="SequenceFlow_18nr0gf" sourceRef="ScriptTask_02924vs" targetRef="Activity_FromIRB-API" />

View File

@ -7,7 +7,8 @@
<bpmn:scriptTask id="ScriptTask_LoadPersonnel" name="Load IRB Personnel">
<bpmn:incoming>Flow_0kcrx5l</bpmn:incoming>
<bpmn:outgoing>Flow_1dcsioh</bpmn:outgoing>
<bpmn:script>#! StudyInfo investigators</bpmn:script>
<bpmn:script>StudyInfo = {}
StudyInfo['investigators'] = study_info('investigators')</bpmn:script>
</bpmn:scriptTask>
<bpmn:endEvent id="EndEvent_1qor16n">
<bpmn:documentation>## The following information was gathered:

View File

@ -598,7 +598,7 @@ Use the EHS [Lab Safety Plan During COVID 19 template](https://www.google.com/ur
This step is internal to the system and do not require and user interaction</bpmn:documentation>
<bpmn:incoming>Flow_11uqavk</bpmn:incoming>
<bpmn:outgoing>Flow_0aqgwvu</bpmn:outgoing>
<bpmn:script>#! CompleteTemplate ResearchRampUpPlan.docx RESEARCH_RAMPUP</bpmn:script>
<bpmn:script>complete_template('ResearchRampUpPlan.docx','RESEARCH_RAMPUP')</bpmn:script>
</bpmn:scriptTask>
<bpmn:sequenceFlow id="Flow_0aqgwvu" sourceRef="Activity_GenerateRRP" targetRef="Activity_AcknowledgePlanReview" />
<bpmn:sequenceFlow id="Flow_0j4rs82" sourceRef="Activity_SubmitPlan" targetRef="Activity_0absozl" />
@ -755,7 +755,7 @@ Notify the Area Monitor for
This step is internal to the system and do not require and user interaction</bpmn:documentation>
<bpmn:incoming>Flow_0j4rs82</bpmn:incoming>
<bpmn:outgoing>Flow_07ge8uf</bpmn:outgoing>
<bpmn:script>#!RequestApproval ApprvlApprvr1 ApprvlApprvr2</bpmn:script>
<bpmn:script>request_approval('ApprvlApprvr1','ApprvlApprvr2')</bpmn:script>
</bpmn:scriptTask>
<bpmn:scriptTask id="Activity_1u58hox" name="Update Request">
<bpmn:documentation>#### Script Task
@ -764,7 +764,7 @@ This step is internal to the system and do not require and user interaction</bpm
This step is internal to the system and do not require and user interaction</bpmn:documentation>
<bpmn:incoming>Flow_16y8glw</bpmn:incoming>
<bpmn:outgoing>Flow_0uc4o6c</bpmn:outgoing>
<bpmn:script>#! UpdateStudy title:PIComputingID.label pi:PIComputingID.value</bpmn:script>
<bpmn:script>update_study('title:PIComputingID.label','pi:PIComputingID.value')</bpmn:script>
</bpmn:scriptTask>
<bpmn:userTask id="PersonnelSchedule" name="Upload Weekly Personnel Schedule(s)" camunda:formKey="Personnel Weekly Schedule">
<bpmn:documentation>#### Weekly Personnel Schedule(s)

View File

@ -11,7 +11,8 @@
<bpmn:scriptTask id="Task_Load_Requirements" name="Load Documents From PB">
<bpmn:incoming>SequenceFlow_1ees8ka</bpmn:incoming>
<bpmn:outgoing>SequenceFlow_17ct47v</bpmn:outgoing>
<bpmn:script>#! StudyInfo documents</bpmn:script>
<bpmn:script>StudyInfo = {}
StudyInfo['documents'] = study_info('documents')</bpmn:script>
</bpmn:scriptTask>
<bpmn:businessRuleTask id="Activity_1yqy50i" name="Enter Core Info&#10;" camunda:decisionRef="enter_core_info">
<bpmn:incoming>Flow_1m8285h</bpmn:incoming>
@ -62,7 +63,8 @@
<bpmn:scriptTask id="Activity_0f295la" name="Load Details from PB">
<bpmn:incoming>Flow_0pwtiqm</bpmn:incoming>
<bpmn:outgoing>Flow_0eq6px2</bpmn:outgoing>
<bpmn:script>#! StudyInfo details</bpmn:script>
<bpmn:script>StudyInfo = {}
StudyInfo['details'] = study_info('details')</bpmn:script>
</bpmn:scriptTask>
<bpmn:businessRuleTask id="Activity_0ahlc3u" name="IDE Supplement" camunda:decisionRef="decision_ide_menu_check">
<bpmn:incoming>Flow_14ce1d7</bpmn:incoming>
@ -91,7 +93,8 @@
<bpmn:scriptTask id="Activity_0g3qa1c" name="Load Personnel from PB">
<bpmn:incoming>Flow_1qyrmzn</bpmn:incoming>
<bpmn:outgoing>Flow_0vo6ul1</bpmn:outgoing>
<bpmn:script>#! StudyInfo investigators</bpmn:script>
<bpmn:script>StudyInfo = {}
StudyInfo['investigators'] = study_info('investigators')</bpmn:script>
</bpmn:scriptTask>
<bpmn:sequenceFlow id="Flow_1ybicki" sourceRef="Activity_13ep6ar" targetRef="Event_135x8jg" />
<bpmn:businessRuleTask id="Activity_13ep6ar" name="Personnel" camunda:decisionRef="personnel">

View File

@ -27,7 +27,7 @@
</bpmn:extensionElements>
<bpmn:incoming>SequenceFlow_1i7hk1a</bpmn:incoming>
<bpmn:outgoing>SequenceFlow_11c35oq</bpmn:outgoing>
<bpmn:script>#! CompleteTemplate Letter.docx AD_CoCApp</bpmn:script>
<bpmn:script>complete_template('Letter.docx','AD_CoCApp')</bpmn:script>
</bpmn:scriptTask>
<bpmn:endEvent id="EndEvent_0evb22x">
<bpmn:incoming>SequenceFlow_11c35oq</bpmn:incoming>

View File

@ -20,7 +20,7 @@ Email content to be delivered to {{ ApprvlApprvr1 }}
---</bpmn:documentation>
<bpmn:incoming>Flow_08n2npe</bpmn:incoming>
<bpmn:outgoing>Flow_1xlrgne</bpmn:outgoing>
<bpmn:script>#! Email "Camunda Email Subject" ApprvlApprvr1 PIComputingID</bpmn:script>
<bpmn:script>email("Camunda Email Subject",'ApprvlApprvr1','PIComputingID')</bpmn:script>
</bpmn:scriptTask>
<bpmn:sequenceFlow id="Flow_1synsig" sourceRef="StartEvent_1" targetRef="Activity_1l9vih3" />
<bpmn:sequenceFlow id="Flow_1xlrgne" sourceRef="Activity_0s5v97n" targetRef="Event_0izrcj4" />

View File

@ -11,7 +11,7 @@
<bpmn:scriptTask id="Invalid_Script_Task" name="An Invalid Script Reference">
<bpmn:incoming>SequenceFlow_1pnq3kg</bpmn:incoming>
<bpmn:outgoing>SequenceFlow_12pf6um</bpmn:outgoing>
<bpmn:script>#! NoSuchScript withArg1</bpmn:script>
<bpmn:script>no_such_script('withArg1')</bpmn:script>
</bpmn:scriptTask>
<bpmn:sequenceFlow id="SequenceFlow_12pf6um" sourceRef="Invalid_Script_Task" targetRef="EndEvent_063bpg6" />
</bpmn:process>

View File

@ -10,7 +10,8 @@
<bpmn:scriptTask id="Activity_0s5v97n" name="Ldap Replace">
<bpmn:incoming>Flow_08n2npe</bpmn:incoming>
<bpmn:outgoing>Flow_1xlrgne</bpmn:outgoing>
<bpmn:script>#! LdapReplace Supervisor Investigator</bpmn:script>
<bpmn:script>Supervisor = ldap(Supervisor)
Investigator = ldap(Investigator)</bpmn:script>
</bpmn:scriptTask>
<bpmn:sequenceFlow id="Flow_1synsig" sourceRef="StartEvent_1" targetRef="Activity_1l9vih3" />
<bpmn:sequenceFlow id="Flow_1xlrgne" sourceRef="Activity_0s5v97n" targetRef="Activity_0f78ek5" />
@ -33,6 +34,10 @@
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_0tad5ma">
<bpmndi:BPMNEdge id="Flow_11e7jgz_di" bpmnElement="Flow_11e7jgz">
<di:waypoint x="720" y="117" />
<di:waypoint x="802" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_08n2npe_di" bpmnElement="Flow_08n2npe">
<di:waypoint x="370" y="117" />
<di:waypoint x="450" y="117" />
@ -45,22 +50,18 @@
<di:waypoint x="215" y="117" />
<di:waypoint x="270" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_11e7jgz_di" bpmnElement="Flow_11e7jgz">
<di:waypoint x="720" y="117" />
<di:waypoint x="802" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="179" y="99" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_0izrcj4_di" bpmnElement="Event_0izrcj4">
<dc:Bounds x="802" y="99" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_04imfm6_di" bpmnElement="Activity_0s5v97n">
<dc:Bounds x="450" y="77" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0xugr62_di" bpmnElement="Activity_1l9vih3">
<dc:Bounds x="270" y="77" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_0izrcj4_di" bpmnElement="Event_0izrcj4">
<dc:Bounds x="802" y="99" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_17h05g6_di" bpmnElement="Activity_0f78ek5">
<dc:Bounds x="620" y="77" width="100" height="80" />
</bpmndi:BPMNShape>

View File

@ -29,7 +29,8 @@
<bpmn:scriptTask id="Task_1v0e2zu" name="Load Personnel">
<bpmn:incoming>Flow_0t6p1sb</bpmn:incoming>
<bpmn:outgoing>SequenceFlow_1p568pp</bpmn:outgoing>
<bpmn:script>#! StudyInfo investigators</bpmn:script>
<bpmn:script>StudyInfo = {}
StudyInfo['investigators'] = study_info('investigators')</bpmn:script>
</bpmn:scriptTask>
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">

View File

@ -11,7 +11,7 @@
<bpmn:sequenceFlow id="Flow_0ugjw69" sourceRef="MultiInstanceTask" targetRef="Event_End" />
<bpmn:userTask id="MultiInstanceTask" name="Gather more information" camunda:formKey="GetEmail">
<bpmn:documentation># Please provide addtional information about:
## Investigator ID: {{investigator.user_id}}
## Investigator ID: {{investigator.user_id}}
## Role: {{investigator.type_full}}</bpmn:documentation>
<bpmn:extensionElements>
<camunda:formData>
@ -29,7 +29,8 @@
<bpmn:scriptTask id="Task_1v0e2zu" name="Load Personnel">
<bpmn:incoming>Flow_0t6p1sb</bpmn:incoming>
<bpmn:outgoing>SequenceFlow_1p568pp</bpmn:outgoing>
<bpmn:script>#! StudyInfo investigators</bpmn:script>
<bpmn:script>StudyInfo = {}
StudyInfo['investigators'] = study_info('investigators')</bpmn:script>
</bpmn:scriptTask>
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">

View File

@ -132,7 +132,7 @@ Autoconverted link https://github.com/nodeca/pica (enable linkify to see)
</bpmn:extensionElements>
<bpmn:incoming>SequenceFlow_0641sh6</bpmn:incoming>
<bpmn:outgoing>SequenceFlow_0t29gjo</bpmn:outgoing>
<bpmn:script>#! FactService</bpmn:script>
<bpmn:script>FactService = fact_service()</bpmn:script>
</bpmn:scriptTask>
<bpmn:endEvent id="EndEvent_0u1cgrf">
<bpmn:documentation># Great Job!

View File

@ -8,12 +8,19 @@
<bpmn:scriptTask id="Task_Script_Load_Study_Details" name="Load Study Info">
<bpmn:incoming>SequenceFlow_1nfe5m9</bpmn:incoming>
<bpmn:outgoing>SequenceFlow_1bqiin0</bpmn:outgoing>
<bpmn:script>#! StudyInfo info</bpmn:script>
<bpmn:script>StudyInfo = {}
StudyInfo['info'] = study_info('info')</bpmn:script>
</bpmn:scriptTask>
<bpmn:sequenceFlow id="SequenceFlow_1bqiin0" sourceRef="Task_Script_Load_Study_Details" targetRef="EndEvent_171dj09" />
<bpmn:sequenceFlow id="SequenceFlow_1bqiin0" sourceRef="Task_Script_Load_Study_Details" targetRef="Activity_0w91u9s" />
<bpmn:endEvent id="EndEvent_171dj09">
<bpmn:incoming>SequenceFlow_1bqiin0</bpmn:incoming>
<bpmn:incoming>Flow_0ochvmi</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_0ochvmi" sourceRef="Activity_0w91u9s" targetRef="EndEvent_171dj09" />
<bpmn:scriptTask id="Activity_0w91u9s" name="StudyInfo as Script">
<bpmn:incoming>SequenceFlow_1bqiin0</bpmn:incoming>
<bpmn:outgoing>Flow_0ochvmi</bpmn:outgoing>
<bpmn:script>study = study_info('info','p')</bpmn:script>
</bpmn:scriptTask>
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_0exnnpv">
@ -29,10 +36,17 @@
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="SequenceFlow_1bqiin0_di" bpmnElement="SequenceFlow_1bqiin0">
<di:waypoint x="370" y="117" />
<di:waypoint x="402" y="117" />
<di:waypoint x="430" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="EndEvent_171dj09_di" bpmnElement="EndEvent_171dj09">
<dc:Bounds x="402" y="99" width="36" height="36" />
<dc:Bounds x="622" y="99" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_0ochvmi_di" bpmnElement="Flow_0ochvmi">
<di:waypoint x="530" y="117" />
<di:waypoint x="622" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="Activity_1wtk4bb_di" bpmnElement="Activity_0w91u9s">
<dc:Bounds x="430" y="77" width="100" height="80" />
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>

View File

@ -11,7 +11,8 @@
<bpmn:scriptTask id="Task_Load_Requirements" name="Load Required Documents From PM">
<bpmn:incoming>SequenceFlow_1ees8ka</bpmn:incoming>
<bpmn:outgoing>SequenceFlow_17ct47v</bpmn:outgoing>
<bpmn:script>#! StudyInfo documents</bpmn:script>
<bpmn:script>StudyInfo = {}
StudyInfo['documents'] = study_info('documents')</bpmn:script>
</bpmn:scriptTask>
<bpmn:businessRuleTask id="Activity_1yqy50i" name="Enter Core Info&#10;" camunda:decisionRef="enter_core_info">
<bpmn:incoming>Flow_1m8285h</bpmn:incoming>

View File

@ -1,7 +1,8 @@
from tests.base_test import BaseTest
from crc.services.workflow_processor import WorkflowProcessor
from crc.scripts.ldap_replace import LdapReplace
from crc.scripts.ldap import Ldap
from crc.api.common import ApiError
from crc import db, mail
@ -14,60 +15,19 @@ class TestLdapLookupScript(BaseTest):
processor = WorkflowProcessor(workflow)
task = processor.next_task()
task.data = {
'PIComputingID': 'dhf8r'
}
script = Ldap()
user_details = script.do_task(task, workflow.study_id, workflow.id, "dhf8r")
script = LdapReplace()
user_details = script.do_task(task, workflow.study_id, workflow.id, "PIComputingID")
self.assertEqual(task.data['PIComputingID']['display_name'], 'Dan Funk')
self.assertEqual(task.data['PIComputingID']['given_name'], 'Dan')
self.assertEqual(task.data['PIComputingID']['email_address'], 'dhf8r@virginia.edu')
self.assertEqual(task.data['PIComputingID']['telephone_number'], '+1 (434) 924-1723')
self.assertEqual(task.data['PIComputingID']['title'], 'E42:He\'s a hoopy frood')
self.assertEqual(task.data['PIComputingID']['department'], 'E0:EN-Eng Study of Parallel Universes')
self.assertEqual(task.data['PIComputingID']['affiliation'], 'faculty')
self.assertEqual(task.data['PIComputingID']['sponsor_type'], 'Staff')
self.assertEqual(task.data['PIComputingID']['uid'], 'dhf8r')
self.assertEqual(task.data['PIComputingID']['proper_name'], 'Dan Funk - (dhf8r)')
def test_get_existing_users_details(self):
self.load_example_data()
self.create_reference_document()
workflow = self.create_workflow('empty_workflow')
processor = WorkflowProcessor(workflow)
task = processor.next_task()
task.data = {
'supervisor': 'dhf8r',
'investigator': 'lb3dp'
}
script = LdapReplace()
user_details = script.do_task(task, workflow.study_id, workflow.id, "supervisor", "investigator")
self.assertEqual(task.data['supervisor']['display_name'], 'Dan Funk')
self.assertEqual(task.data['supervisor']['given_name'], 'Dan')
self.assertEqual(task.data['supervisor']['email_address'], 'dhf8r@virginia.edu')
self.assertEqual(task.data['supervisor']['telephone_number'], '+1 (434) 924-1723')
self.assertEqual(task.data['supervisor']['title'], 'E42:He\'s a hoopy frood')
self.assertEqual(task.data['supervisor']['department'], 'E0:EN-Eng Study of Parallel Universes')
self.assertEqual(task.data['supervisor']['affiliation'], 'faculty')
self.assertEqual(task.data['supervisor']['sponsor_type'], 'Staff')
self.assertEqual(task.data['supervisor']['uid'], 'dhf8r')
self.assertEqual(task.data['supervisor']['proper_name'], 'Dan Funk - (dhf8r)')
self.assertEqual(task.data['investigator']['display_name'], 'Laura Barnes')
self.assertEqual(task.data['investigator']['given_name'], 'Laura')
self.assertEqual(task.data['investigator']['email_address'], 'lb3dp@virginia.edu')
self.assertEqual(task.data['investigator']['telephone_number'], '+1 (434) 924-1723')
self.assertEqual(task.data['investigator']['title'], 'E0:Associate Professor of Systems and Information Engineering')
self.assertEqual(task.data['investigator']['department'], 'E0:EN-Eng Sys and Environment')
self.assertEqual(task.data['investigator']['affiliation'], 'faculty')
self.assertEqual(task.data['investigator']['sponsor_type'], 'Staff')
self.assertEqual(task.data['investigator']['uid'], 'lb3dp')
self.assertEqual(task.data['investigator']['proper_name'], 'Laura Barnes - (lb3dp)')
self.assertEqual(user_details['display_name'], 'Dan Funk')
self.assertEqual(user_details['given_name'], 'Dan')
self.assertEqual(user_details['email_address'], 'dhf8r@virginia.edu')
self.assertEqual(user_details['telephone_number'], '+1 (434) 924-1723')
self.assertEqual(user_details['title'], 'E42:He\'s a hoopy frood')
self.assertEqual(user_details['department'], 'E0:EN-Eng Study of Parallel Universes')
self.assertEqual(user_details['affiliation'], 'faculty')
self.assertEqual(user_details['sponsor_type'], 'Staff')
self.assertEqual(user_details['uid'], 'dhf8r')
self.assertEqual(user_details['proper_name'], 'Dan Funk - (dhf8r)')
def test_get_invalid_user_details(self):
self.load_example_data()
@ -80,10 +40,10 @@ class TestLdapLookupScript(BaseTest):
'PIComputingID': 'rec3z'
}
script = LdapReplace()
user_details = script.do_task(task, workflow.study_id, workflow.id, "PIComputingID")
script = Ldap()
with(self.assertRaises(ApiError)):
user_details = script.do_task(task, workflow.study_id, workflow.id, "PIComputingID")
self.assertEqual(task.data['PIComputingID'], {})
def test_bpmn_task_receives_user_details(self):
workflow = self.create_workflow('ldap_replace')

View File

@ -89,8 +89,8 @@ class TestWorkflowSpecValidation(BaseTest):
self.load_example_data()
errors = self.validate_workflow("invalid_script")
self.assertEqual(2, len(errors))
self.assertEqual("error_loading_workflow", errors[0]['code'])
self.assertTrue("NoSuchScript" in errors[0]['message'])
self.assertEqual("workflow_validation_exception", errors[0]['code'])
#self.assertTrue("NoSuchScript" in errors[0]['message'])
self.assertEqual("Invalid_Script_Task", errors[0]['task_id'])
self.assertEqual("An Invalid Script Reference", errors[0]['task_name'])
self.assertEqual("invalid_script.bpmn", errors[0]['file_name'])
@ -99,7 +99,7 @@ class TestWorkflowSpecValidation(BaseTest):
self.load_example_data()
errors = self.validate_workflow("invalid_script2")
self.assertEqual(2, len(errors))
self.assertEqual("error_loading_workflow", errors[0]['code'])
self.assertEqual("workflow_validation_exception", errors[0]['code'])
self.assertEqual("Invalid_Script_Task", errors[0]['task_id'])
self.assertEqual("An Invalid Script Reference", errors[0]['task_name'])
self.assertEqual("invalid_script2.bpmn", errors[0]['file_name'])