Merge branch 'master' into feature_personnel_multi_instance
This commit is contained in:
commit
678a45fa76
|
@ -241,11 +241,11 @@
|
||||||
},
|
},
|
||||||
"docxtpl": {
|
"docxtpl": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:216af2580b9f697c2f748faf06c0bfbf47a782f2dd10ad87824a4c5ecbd37008",
|
"sha256:a46c9cd6ea6d7350a8f16b467c3b1cd09767c83e1da5753f306cc550a7b04959",
|
||||||
"sha256:f5fed6ff724d802f1b151c86ee6141b17cc6fc2fe1979b7840b11db4bd633e48"
|
"sha256:ab92c5710b6774eff52a90529fb96af29aacfc2d14c0986b6f58ac5bfe403bdf"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==0.8.0"
|
"version": "==0.9.0"
|
||||||
},
|
},
|
||||||
"et-xmlfile": {
|
"et-xmlfile": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -768,7 +768,7 @@
|
||||||
"spiffworkflow": {
|
"spiffworkflow": {
|
||||||
"editable": true,
|
"editable": true,
|
||||||
"git": "https://github.com/sartography/SpiffWorkflow.git",
|
"git": "https://github.com/sartography/SpiffWorkflow.git",
|
||||||
"ref": "618be41e7e6b20f87865cf9fdd96a79c3cbee065"
|
"ref": "d46213c9c20859b42ff26c12f852fd32a58d3280"
|
||||||
},
|
},
|
||||||
"sqlalchemy": {
|
"sqlalchemy": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
|
|
@ -1,15 +1,22 @@
|
||||||
import os
|
import os
|
||||||
|
from os import environ
|
||||||
|
|
||||||
basedir = os.path.abspath(os.path.dirname(__file__))
|
basedir = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
|
||||||
NAME = "CR Connect Workflow"
|
NAME = "CR Connect Workflow"
|
||||||
CORS_ENABLED = False
|
CORS_ENABLED = False
|
||||||
DEVELOPMENT = True
|
DEVELOPMENT = environ.get('DEVELOPMENT', default="True")
|
||||||
SQLALCHEMY_DATABASE_URI = "postgresql://crc_user:crc_pass@localhost:5432/crc_dev"
|
|
||||||
|
DB_HOST = environ.get('DB_HOST', default="localhost")
|
||||||
|
DB_PORT = environ.get('DB_PORT', default="5432")
|
||||||
|
DB_NAME = environ.get('DB_NAME', default="crc_dev")
|
||||||
|
DB_USER = environ.get('DB_USER', default="crc_user")
|
||||||
|
DB_PASSWORD = environ.get('DB_PASSWORD', default="crc_pass")
|
||||||
|
SQLALCHEMY_DATABASE_URI = environ.get('SQLALCHEMY_DATABASE_URI', default="postgresql://%s:%s@%s:%s/%s" % (DB_USER, DB_PASSWORD, DB_HOST, DB_PORT, DB_NAME))
|
||||||
TOKEN_AUTH_TTL_HOURS = 2
|
TOKEN_AUTH_TTL_HOURS = 2
|
||||||
TOKEN_AUTH_SECRET_KEY = "Shhhh!!! This is secret! And better darn well not show up in prod."
|
TOKEN_AUTH_SECRET_KEY = environ.get('TOKEN_AUTH_SECRET_KEY', default="Shhhh!!! This is secret! And better darn well not show up in prod.")
|
||||||
FRONTEND_AUTH_CALLBACK = "http://localhost:4200/session"
|
FRONTEND_AUTH_CALLBACK = environ.get('FRONTEND_AUTH_CALLBACK', default="http://localhost:4200/session")
|
||||||
SWAGGER_AUTH_KEY = "SWAGGER"
|
SWAGGER_AUTH_KEY = environ.get('SWAGGER_AUTH_KEY', default="SWAGGER")
|
||||||
|
|
||||||
#: Default attribute map for single signon.
|
#: Default attribute map for single signon.
|
||||||
SSO_ATTRIBUTE_MAP = {
|
SSO_ATTRIBUTE_MAP = {
|
||||||
|
@ -24,7 +31,8 @@ SSO_ATTRIBUTE_MAP = {
|
||||||
}
|
}
|
||||||
|
|
||||||
# %s/%i placeholders expected for uva_id and study_id in various calls.
|
# %s/%i placeholders expected for uva_id and study_id in various calls.
|
||||||
PB_USER_STUDIES_URL = "http://workflow.sartography.com:5001/pb/user_studies?uva_id=%s"
|
PB_BASE_URL = environ.get('PB_BASE_URL', default="http://localhost:5001/pb/")
|
||||||
PB_INVESTIGATORS_URL = "http://workflow.sartography.com:5001/pb/investigators?studyid=%i"
|
PB_USER_STUDIES_URL = environ.get('PB_USER_STUDIES_URL', default=PB_BASE_URL + "user_studies?uva_id=%s")
|
||||||
PB_REQUIRED_DOCS_URL = "http://workflow.sartography.com:5001/pb/required_docs?studyid=%i"
|
PB_INVESTIGATORS_URL = environ.get('PB_INVESTIGATORS_URL', default=PB_BASE_URL + "investigators?studyid=%i")
|
||||||
PB_STUDY_DETAILS_URL = "http://workflow.sartography.com:5001/pb/study?studyid=%i"
|
PB_REQUIRED_DOCS_URL = environ.get('PB_REQUIRED_DOCS_URL', default=PB_BASE_URL + "required_docs?studyid=%i")
|
||||||
|
PB_STUDY_DETAILS_URL = environ.get('PB_STUDY_DETAILS_URL', default=PB_BASE_URL + "study?studyid=%i")
|
||||||
|
|
|
@ -4,7 +4,7 @@ import connexion
|
||||||
from flask import send_file
|
from flask import send_file
|
||||||
|
|
||||||
from crc import session
|
from crc import session
|
||||||
from crc.api.common import ApiErrorSchema, ApiError
|
from crc.api.common import ApiError
|
||||||
from crc.models.file import FileModelSchema, FileModel, FileDataModel
|
from crc.models.file import FileModelSchema, FileModel, FileDataModel
|
||||||
from crc.models.workflow import WorkflowSpecModel
|
from crc.models.workflow import WorkflowSpecModel
|
||||||
from crc.services.file_service import FileService
|
from crc.services.file_service import FileService
|
||||||
|
|
|
@ -10,6 +10,7 @@ from crc.models.stats import WorkflowStatsModel, TaskEventModel
|
||||||
from crc.models.workflow import WorkflowModel, WorkflowSpecModelSchema, WorkflowSpecModel, WorkflowSpecCategoryModel, \
|
from crc.models.workflow import WorkflowModel, WorkflowSpecModelSchema, WorkflowSpecModel, WorkflowSpecCategoryModel, \
|
||||||
WorkflowSpecCategoryModelSchema
|
WorkflowSpecCategoryModelSchema
|
||||||
from crc.services.workflow_processor import WorkflowProcessor
|
from crc.services.workflow_processor import WorkflowProcessor
|
||||||
|
from crc.services.workflow_service import WorkflowService
|
||||||
|
|
||||||
|
|
||||||
def all_specifications():
|
def all_specifications():
|
||||||
|
@ -40,7 +41,7 @@ def validate_workflow_specification(spec_id):
|
||||||
|
|
||||||
errors = []
|
errors = []
|
||||||
try:
|
try:
|
||||||
WorkflowProcessor.test_spec(spec_id)
|
WorkflowService.test_spec(spec_id)
|
||||||
except ApiError as ae:
|
except ApiError as ae:
|
||||||
errors.append(ae)
|
errors.append(ae)
|
||||||
return ApiErrorSchema(many=True).dump(errors)
|
return ApiErrorSchema(many=True).dump(errors)
|
||||||
|
@ -85,7 +86,7 @@ def delete_workflow_specification(spec_id):
|
||||||
|
|
||||||
def __get_workflow_api_model(processor: WorkflowProcessor, status_data=None):
|
def __get_workflow_api_model(processor: WorkflowProcessor, status_data=None):
|
||||||
spiff_tasks = processor.get_all_user_tasks()
|
spiff_tasks = processor.get_all_user_tasks()
|
||||||
user_tasks = list(map(Task.from_spiff, spiff_tasks))
|
user_tasks = list(map(WorkflowService.spiff_task_to_api_task, spiff_tasks))
|
||||||
is_active = True
|
is_active = True
|
||||||
|
|
||||||
if status_data is not None and processor.workflow_spec_id in status_data:
|
if status_data is not None and processor.workflow_spec_id in status_data:
|
||||||
|
@ -94,7 +95,7 @@ def __get_workflow_api_model(processor: WorkflowProcessor, status_data=None):
|
||||||
workflow_api = WorkflowApi(
|
workflow_api = WorkflowApi(
|
||||||
id=processor.get_workflow_id(),
|
id=processor.get_workflow_id(),
|
||||||
status=processor.get_status(),
|
status=processor.get_status(),
|
||||||
last_task=Task.from_spiff(processor.bpmn_workflow.last_task),
|
last_task=WorkflowService.spiff_task_to_api_task(processor.bpmn_workflow.last_task),
|
||||||
next_task=None,
|
next_task=None,
|
||||||
user_tasks=user_tasks,
|
user_tasks=user_tasks,
|
||||||
workflow_spec_id=processor.workflow_spec_id,
|
workflow_spec_id=processor.workflow_spec_id,
|
||||||
|
@ -102,7 +103,7 @@ def __get_workflow_api_model(processor: WorkflowProcessor, status_data=None):
|
||||||
is_latest_spec=processor.get_spec_version() == processor.get_latest_version_string(processor.workflow_spec_id),
|
is_latest_spec=processor.get_spec_version() == processor.get_latest_version_string(processor.workflow_spec_id),
|
||||||
)
|
)
|
||||||
if processor.next_task():
|
if processor.next_task():
|
||||||
workflow_api.next_task = Task.from_spiff(processor.next_task())
|
workflow_api.next_task = WorkflowService.spiff_task_to_api_task(processor.next_task())
|
||||||
return workflow_api
|
return workflow_api
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
import jinja2
|
|
||||||
import marshmallow
|
import marshmallow
|
||||||
from jinja2 import Template
|
|
||||||
from marshmallow import INCLUDE
|
from marshmallow import INCLUDE
|
||||||
from marshmallow_enum import EnumField
|
from marshmallow_enum import EnumField
|
||||||
|
|
||||||
from crc import ma
|
from crc import ma
|
||||||
from crc.api.common import ApiError
|
|
||||||
from crc.models.workflow import WorkflowStatus
|
from crc.models.workflow import WorkflowStatus
|
||||||
|
|
||||||
|
|
||||||
class Task(object):
|
class Task(object):
|
||||||
|
|
||||||
|
ENUM_OPTIONS_FILE_PROP = "enum.options.file"
|
||||||
|
EMUM_OPTIONS_VALUE_COL_PROP = "enum.options.value.column"
|
||||||
|
EMUM_OPTIONS_LABEL_COL_PROP = "enum.options.label.column"
|
||||||
|
|
||||||
def __init__(self, id, name, title, type, state, form, documentation, data):
|
def __init__(self, id, name, title, type, state, form, documentation, data):
|
||||||
self.id = id
|
self.id = id
|
||||||
self.name = name
|
self.name = name
|
||||||
|
@ -20,35 +22,6 @@ class Task(object):
|
||||||
self.documentation = documentation
|
self.documentation = documentation
|
||||||
self.data = data
|
self.data = data
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_spiff(cls, spiff_task):
|
|
||||||
documentation = spiff_task.task_spec.documentation if hasattr(spiff_task.task_spec, "documentation") else ""
|
|
||||||
instance = cls(spiff_task.id,
|
|
||||||
spiff_task.task_spec.name,
|
|
||||||
spiff_task.task_spec.description,
|
|
||||||
spiff_task.task_spec.__class__.__name__,
|
|
||||||
spiff_task.get_state_name(),
|
|
||||||
None,
|
|
||||||
documentation,
|
|
||||||
spiff_task.data)
|
|
||||||
if hasattr(spiff_task.task_spec, "form"):
|
|
||||||
instance.form = spiff_task.task_spec.form
|
|
||||||
if documentation != "" and documentation is not None:
|
|
||||||
|
|
||||||
instance.process_documentation(documentation)
|
|
||||||
return instance
|
|
||||||
|
|
||||||
def process_documentation(self, documentation):
|
|
||||||
'''Runs markdown documentation through the Jinja2 processor to inject data
|
|
||||||
create loops, etc...'''
|
|
||||||
|
|
||||||
try:
|
|
||||||
template = Template(documentation)
|
|
||||||
self.documentation = template.render(**self.data)
|
|
||||||
except jinja2.exceptions.TemplateError as ue:
|
|
||||||
raise ApiError(code="template_error", message="Error processing template for task %s: %s" %
|
|
||||||
(self.name, str(ue)), status_code=500)
|
|
||||||
# TODO: Catch additional errors and report back.
|
|
||||||
|
|
||||||
class OptionSchema(ma.Schema):
|
class OptionSchema(ma.Schema):
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
|
@ -50,7 +50,6 @@ Takes two arguments:
|
||||||
"the name of the docx template to use. The second "
|
"the name of the docx template to use. The second "
|
||||||
"argument is a code for the document, as "
|
"argument is a code for the document, as "
|
||||||
"set in the reference document %s. " % FileService.IRB_PRO_CATEGORIES_FILE)
|
"set in the reference document %s. " % FileService.IRB_PRO_CATEGORIES_FILE)
|
||||||
workflow_spec_model = self.find_spec_model_in_db(task.workflow)
|
|
||||||
task_study_id = task.workflow.data[WorkflowProcessor.STUDY_ID_KEY]
|
task_study_id = task.workflow.data[WorkflowProcessor.STUDY_ID_KEY]
|
||||||
file_name = args[0]
|
file_name = args[0]
|
||||||
|
|
||||||
|
@ -58,21 +57,7 @@ Takes two arguments:
|
||||||
raise ApiError(code="invalid_argument",
|
raise ApiError(code="invalid_argument",
|
||||||
message="The given task does not match the given study.")
|
message="The given task does not match the given study.")
|
||||||
|
|
||||||
if workflow_spec_model is None:
|
file_data_model = FileService.get_workflow_file_data(task.workflow, file_name)
|
||||||
raise ApiError(code="workflow_model_error",
|
|
||||||
message="Something is wrong. I can't find the workflow you are using.")
|
|
||||||
|
|
||||||
file_data_model = session.query(FileDataModel) \
|
|
||||||
.join(FileModel) \
|
|
||||||
.filter(FileModel.name == file_name) \
|
|
||||||
.filter(FileModel.workflow_spec_id == workflow_spec_model.id).first()
|
|
||||||
|
|
||||||
if file_data_model is None:
|
|
||||||
raise ApiError(code="file_missing",
|
|
||||||
message="Can not find a file called '%s' within workflow specification '%s'"
|
|
||||||
% (args[0], workflow_spec_model.id))
|
|
||||||
|
|
||||||
|
|
||||||
return self.make_template(BytesIO(file_data_model.data), task.data)
|
return self.make_template(BytesIO(file_data_model.data), task.data)
|
||||||
|
|
||||||
|
|
||||||
|
@ -85,15 +70,4 @@ Takes two arguments:
|
||||||
target_stream.seek(0) # move to the beginning of the stream.
|
target_stream.seek(0) # move to the beginning of the stream.
|
||||||
return target_stream
|
return target_stream
|
||||||
|
|
||||||
def find_spec_model_in_db(self, workflow):
|
|
||||||
""" Search for the workflow """
|
|
||||||
# When the workflow spec model is created, we record the primary process id,
|
|
||||||
# then we can look it up. As there is the potential for sub-workflows, we
|
|
||||||
# may need to travel up to locate the primary process.
|
|
||||||
spec = workflow.spec
|
|
||||||
workflow_model = session.query(WorkflowSpecModel). \
|
|
||||||
filter(WorkflowSpecModel.primary_process_id == spec.name).first()
|
|
||||||
if workflow_model is None and workflow != workflow.outer_workflow:
|
|
||||||
return self.find_spec_model_in_db(workflow.outer_workflow)
|
|
||||||
|
|
||||||
return workflow_model
|
|
||||||
|
|
|
@ -192,3 +192,40 @@ class FileService(object):
|
||||||
if not file_model:
|
if not file_model:
|
||||||
raise ApiError("file_not_found", "There is no reference file with the name '%s'" % file_name)
|
raise ApiError("file_not_found", "There is no reference file with the name '%s'" % file_name)
|
||||||
return FileService.get_file_data(file_model.id, file_model)
|
return FileService.get_file_data(file_model.id, file_model)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_workflow_file_data(workflow, file_name):
|
||||||
|
"""Given a SPIFF Workflow Model, tracks down a file with the given name in the datbase and returns it's data"""
|
||||||
|
workflow_spec_model = FileService.__find_spec_model_in_db(workflow)
|
||||||
|
study_id = workflow.data[WorkflowProcessor.STUDY_ID_KEY]
|
||||||
|
|
||||||
|
if workflow_spec_model is None:
|
||||||
|
raise ApiError(code="workflow_model_error",
|
||||||
|
message="Something is wrong. I can't find the workflow you are using.")
|
||||||
|
|
||||||
|
file_data_model = session.query(FileDataModel) \
|
||||||
|
.join(FileModel) \
|
||||||
|
.filter(FileModel.name == file_name) \
|
||||||
|
.filter(FileModel.workflow_spec_id == workflow_spec_model.id).first()
|
||||||
|
|
||||||
|
if file_data_model is None:
|
||||||
|
raise ApiError(code="file_missing",
|
||||||
|
message="Can not find a file called '%s' within workflow specification '%s'"
|
||||||
|
% (file_name, workflow_spec_model.id))
|
||||||
|
|
||||||
|
return file_data_model
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def __find_spec_model_in_db(workflow):
|
||||||
|
""" Search for the workflow """
|
||||||
|
# When the workflow spec model is created, we record the primary process id,
|
||||||
|
# then we can look it up. As there is the potential for sub-workflows, we
|
||||||
|
# may need to travel up to locate the primary process.
|
||||||
|
spec = workflow.spec
|
||||||
|
workflow_model = session.query(WorkflowSpecModel). \
|
||||||
|
filter(WorkflowSpecModel.primary_process_id == spec.name).first()
|
||||||
|
if workflow_model is None and workflow != workflow.outer_workflow:
|
||||||
|
return FileService.__find_spec_model_in_db(workflow.outer_workflow)
|
||||||
|
|
||||||
|
return workflow_model
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,6 @@ from SpiffWorkflow.specs import WorkflowSpec
|
||||||
|
|
||||||
from crc import session
|
from crc import session
|
||||||
from crc.api.common import ApiError
|
from crc.api.common import ApiError
|
||||||
from crc.models.api_models import Task
|
|
||||||
from crc.models.file import FileDataModel, FileModel, FileType
|
from crc.models.file import FileDataModel, FileModel, FileType
|
||||||
from crc.models.workflow import WorkflowStatus, WorkflowModel
|
from crc.models.workflow import WorkflowStatus, WorkflowModel
|
||||||
from crc.scripts.script import Script
|
from crc.scripts.script import Script
|
||||||
|
@ -271,26 +270,7 @@ class WorkflowProcessor(object):
|
||||||
spec.description = version
|
spec.description = version
|
||||||
return spec
|
return spec
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def test_spec(cls, spec_id):
|
|
||||||
|
|
||||||
spec = WorkflowProcessor.get_spec(spec_id)
|
|
||||||
bpmn_workflow = BpmnWorkflow(spec, script_engine=cls._script_engine)
|
|
||||||
bpmn_workflow.data[WorkflowProcessor.STUDY_ID_KEY] = 1
|
|
||||||
bpmn_workflow.data[WorkflowProcessor.WORKFLOW_ID_KEY] = spec_id
|
|
||||||
bpmn_workflow.data[WorkflowProcessor.VALIDATION_PROCESS_KEY] = True
|
|
||||||
|
|
||||||
while not bpmn_workflow.is_completed():
|
|
||||||
try:
|
|
||||||
bpmn_workflow.do_engine_steps()
|
|
||||||
tasks = bpmn_workflow.get_tasks(SpiffTask.READY)
|
|
||||||
for task in tasks:
|
|
||||||
task_api = Task.from_spiff(task) # Assure we try to process the documenation, and raise those errors.
|
|
||||||
WorkflowProcessor.populate_form_with_random_data(task)
|
|
||||||
task.complete()
|
|
||||||
except WorkflowException as we:
|
|
||||||
raise ApiError.from_task_spec("workflow_execution_exception", str(we),
|
|
||||||
we.sender)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def populate_form_with_random_data(task):
|
def populate_form_with_random_data(task):
|
||||||
|
|
|
@ -0,0 +1,112 @@
|
||||||
|
from SpiffWorkflow.bpmn.workflow import BpmnWorkflow
|
||||||
|
from pandas import ExcelFile
|
||||||
|
|
||||||
|
from crc.api.common import ApiError
|
||||||
|
from crc.models.api_models import Task
|
||||||
|
import jinja2
|
||||||
|
from jinja2 import Template
|
||||||
|
|
||||||
|
from crc.services.file_service import FileService
|
||||||
|
from crc.services.workflow_processor import WorkflowProcessor, CustomBpmnScriptEngine
|
||||||
|
from SpiffWorkflow import Task as SpiffTask, WorkflowException
|
||||||
|
|
||||||
|
|
||||||
|
class WorkflowService(object):
|
||||||
|
"""Provides tools for processing workflows and tasks. This
|
||||||
|
should at some point, be the only way to work with Workflows, and
|
||||||
|
the workflow Processor should be hidden behind this service.
|
||||||
|
This will help maintain a structure that avoids circular dependencies.
|
||||||
|
But for now, this contains tools for converting spiff-workflow models into our
|
||||||
|
own API models with additional information and capabilities."""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def test_spec(cls, spec_id):
|
||||||
|
"""Runs a spec through it's paces to see if it results in any errors. Not full proof, but a good
|
||||||
|
sanity check."""
|
||||||
|
|
||||||
|
spec = WorkflowProcessor.get_spec(spec_id)
|
||||||
|
bpmn_workflow = BpmnWorkflow(spec, script_engine=CustomBpmnScriptEngine())
|
||||||
|
bpmn_workflow.data[WorkflowProcessor.STUDY_ID_KEY] = 1
|
||||||
|
bpmn_workflow.data[WorkflowProcessor.WORKFLOW_ID_KEY] = spec_id
|
||||||
|
bpmn_workflow.data[WorkflowProcessor.VALIDATION_PROCESS_KEY] = True
|
||||||
|
|
||||||
|
while not bpmn_workflow.is_completed():
|
||||||
|
try:
|
||||||
|
bpmn_workflow.do_engine_steps()
|
||||||
|
tasks = bpmn_workflow.get_tasks(SpiffTask.READY)
|
||||||
|
for task in tasks:
|
||||||
|
task_api = WorkflowService.spiff_task_to_api_task(
|
||||||
|
task) # Assure we try to process the documenation, and raise those errors.
|
||||||
|
WorkflowProcessor.populate_form_with_random_data(task)
|
||||||
|
task.complete()
|
||||||
|
except WorkflowException as we:
|
||||||
|
raise ApiError.from_task_spec("workflow_execution_exception", str(we),
|
||||||
|
we.sender)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def spiff_task_to_api_task(spiff_task):
|
||||||
|
documentation = spiff_task.task_spec.documentation if hasattr(spiff_task.task_spec, "documentation") else ""
|
||||||
|
task = Task(spiff_task.id,
|
||||||
|
spiff_task.task_spec.name,
|
||||||
|
spiff_task.task_spec.description,
|
||||||
|
spiff_task.task_spec.__class__.__name__,
|
||||||
|
spiff_task.get_state_name(),
|
||||||
|
None,
|
||||||
|
documentation,
|
||||||
|
spiff_task.data)
|
||||||
|
|
||||||
|
# Only process the form and documentation if this is something that is ready or completed.
|
||||||
|
if not (spiff_task._is_predicted()):
|
||||||
|
if hasattr(spiff_task.task_spec, "form"):
|
||||||
|
task.form = spiff_task.task_spec.form
|
||||||
|
for field in task.form.fields:
|
||||||
|
WorkflowService._process_options(spiff_task, field)
|
||||||
|
|
||||||
|
if documentation != "" and documentation is not None:
|
||||||
|
WorkflowService._process_documentation(task, documentation)
|
||||||
|
return task
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _process_documentation(task, documentation):
|
||||||
|
"""Runs the given documentation string through the Jinja2 processor to inject data
|
||||||
|
create loops, etc..."""
|
||||||
|
|
||||||
|
try:
|
||||||
|
template = Template(documentation)
|
||||||
|
task.documentation = template.render(**task.data)
|
||||||
|
except jinja2.exceptions.TemplateError as ue:
|
||||||
|
raise ApiError(code="template_error", message="Error processing template for task %s: %s" %
|
||||||
|
(task.name, str(ue)), status_code=500)
|
||||||
|
# TODO: Catch additional errors and report back.
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _process_options(spiff_task, field):
|
||||||
|
""" Checks to see if the options are provided in a separate lookup table associated with the
|
||||||
|
workflow, and populates these if possible. """
|
||||||
|
if field.has_property(Task.ENUM_OPTIONS_FILE_PROP):
|
||||||
|
if not field.has_property(Task.EMUM_OPTIONS_VALUE_COL_PROP) or \
|
||||||
|
not field.has_property(Task.EMUM_OPTIONS_LABEL_COL_PROP):
|
||||||
|
raise ApiError.from_task("invalid_emum",
|
||||||
|
"For emumerations based on an xls file, you must include 3 properties: %s, "
|
||||||
|
"%s, and %s, you supplied %s" % (Task.ENUM_OPTIONS_FILE_PROP,
|
||||||
|
Task.EMUM_OPTIONS_VALUE_COL_PROP,
|
||||||
|
Task.EMUM_OPTIONS_LABEL_COL_PROP),
|
||||||
|
task=spiff_task)
|
||||||
|
|
||||||
|
# Get the file data from the File Service
|
||||||
|
file_name = field.get_property(Task.ENUM_OPTIONS_FILE_PROP)
|
||||||
|
value_column = field.get_property(Task.EMUM_OPTIONS_VALUE_COL_PROP)
|
||||||
|
label_column = field.get_property(Task.EMUM_OPTIONS_LABEL_COL_PROP)
|
||||||
|
data_model = FileService.get_workflow_file_data(spiff_task.workflow, file_name)
|
||||||
|
xls = ExcelFile(data_model.data)
|
||||||
|
df = xls.parse(xls.sheet_names[0])
|
||||||
|
if value_column not in df:
|
||||||
|
raise ApiError("invalid_emum",
|
||||||
|
"The file %s does not contain a column named % s" % (file_name, value_column))
|
||||||
|
if label_column not in df:
|
||||||
|
raise ApiError("invalid_emum",
|
||||||
|
"The file %s does not contain a column named % s" % (file_name, label_column))
|
||||||
|
|
||||||
|
for index, row in df.iterrows():
|
||||||
|
field.options.append({"id": row[value_column],
|
||||||
|
"name": row[label_column]})
|
|
@ -14,13 +14,13 @@
|
||||||
</camunda:formField>
|
</camunda:formField>
|
||||||
<camunda:formField id="FormField_BudgetDraft" label="Draft Budget" type="file">
|
<camunda:formField id="FormField_BudgetDraft" label="Draft Budget" type="file">
|
||||||
<camunda:properties>
|
<camunda:properties>
|
||||||
<camunda:property id="hide_expression" value="!model.FormField_isBudget" />
|
<camunda:property id="hide_expression" value="!(model.FormField_isBudget) | FormField_isBudget == null" />
|
||||||
</camunda:properties>
|
</camunda:properties>
|
||||||
</camunda:formField>
|
</camunda:formField>
|
||||||
<camunda:formField id="FormField_Budget Final" label="Final Budget" type="file">
|
<camunda:formField id="FormField_Budget Final" label="Final Budget" type="file">
|
||||||
<camunda:properties>
|
<camunda:properties>
|
||||||
<camunda:property id="description" value="This is the budget that you will negotiate with your funding source." />
|
<camunda:property id="description" value="This is the budget that you will negotiate with your funding source." />
|
||||||
<camunda:property id="hide_expression" value="!model.FormField_isBudget" />
|
<camunda:property id="hide_expression" value="!(model.FormField_isBudget) | FormField_isBudget == null" />
|
||||||
</camunda:properties>
|
</camunda:properties>
|
||||||
</camunda:formField>
|
</camunda:formField>
|
||||||
</camunda:formData>
|
</camunda:formData>
|
||||||
|
@ -54,7 +54,7 @@
|
||||||
<bpmn:sequenceFlow id="SequenceFlow_157c6e9" sourceRef="ExclusiveGateway_0m1n8mu" targetRef="Task_0xn3d6z" />
|
<bpmn:sequenceFlow id="SequenceFlow_157c6e9" sourceRef="ExclusiveGateway_0m1n8mu" targetRef="Task_0xn3d6z" />
|
||||||
<bpmn:sequenceFlow id="SequenceFlow_1oh6eq7" sourceRef="ExclusiveGateway_0m1n8mu" targetRef="Task_0dj66yz" />
|
<bpmn:sequenceFlow id="SequenceFlow_1oh6eq7" sourceRef="ExclusiveGateway_0m1n8mu" targetRef="Task_0dj66yz" />
|
||||||
<bpmn:userTask id="Task_0dj66yz" name="Enter Contract Funded" camunda:formKey="FormKey_ContractFunded">
|
<bpmn:userTask id="Task_0dj66yz" name="Enter Contract Funded" camunda:formKey="FormKey_ContractFunded">
|
||||||
<bpmn:documentation>#### Process:
|
<bpmn:documentation>#### Process:
|
||||||
|
|
||||||
The study team uploads the executed copy of the contract(s) after they receive it from the Office of Grants and Contracts, after the following process components are completed outside of the Clinical Research Connect:
|
The study team uploads the executed copy of the contract(s) after they receive it from the Office of Grants and Contracts, after the following process components are completed outside of the Clinical Research Connect:
|
||||||
|
|
||||||
|
@ -111,7 +111,7 @@ If you have any questions about the process, contact contract negotiator or Offi
|
||||||
<bpmn:documentation>#### Non-Funded Executed Agreement
|
<bpmn:documentation>#### Non-Funded Executed Agreement
|
||||||
|
|
||||||
|
|
||||||
#### Process:
|
#### Process:
|
||||||
OGC will upload the Non-Funded Executed Agreement after it has been negotiated by OSP contract negotiator.</bpmn:documentation>
|
OGC will upload the Non-Funded Executed Agreement after it has been negotiated by OSP contract negotiator.</bpmn:documentation>
|
||||||
<bpmn:extensionElements>
|
<bpmn:extensionElements>
|
||||||
<camunda:formData>
|
<camunda:formData>
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
<decisionTable id="decisionTable_1">
|
<decisionTable id="decisionTable_1">
|
||||||
<input id="input_1" label="Investigator's Brochure Form Upload Count">
|
<input id="input_1" label="Investigator's Brochure Form Upload Count">
|
||||||
<inputExpression id="inputExpression_1" typeRef="integer">
|
<inputExpression id="inputExpression_1" typeRef="integer">
|
||||||
<text>Documents["DrugDevDoc_InvestBrochure"]["count"]</text>
|
<text>Documents.DrugDevDoc_InvestBrochure.count</text>
|
||||||
</inputExpression>
|
</inputExpression>
|
||||||
</input>
|
</input>
|
||||||
<output id="output_1" label="Investigator's Brochure(s) Uploaded?" name="isInvestigatorsBrochure" typeRef="boolean" />
|
<output id="output_1" label="Investigator's Brochure(s) Uploaded?" name="isInvestigatorsBrochure" typeRef="boolean" />
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
<decisionTable id="decisionTable_1">
|
<decisionTable id="decisionTable_1">
|
||||||
<input id="input_1" label="IVRS-IWRS-IXRS Manual Count">
|
<input id="input_1" label="IVRS-IWRS-IXRS Manual Count">
|
||||||
<inputExpression id="inputExpression_1" typeRef="integer">
|
<inputExpression id="inputExpression_1" typeRef="integer">
|
||||||
<text>Documents["DrugDevDoc_IVRSIWRSIXRSMan"]["count"]</text>
|
<text>Documents.DrugDevDoc_IVRSIWRSIXRSMan.count</text>
|
||||||
</inputExpression>
|
</inputExpression>
|
||||||
</input>
|
</input>
|
||||||
<output id="output_1" label="IVRS-IWRS-IXRS Manual Uploaded?" name="isIVRS-IWRS-IXRS" typeRef="boolean" />
|
<output id="output_1" label="IVRS-IWRS-IXRS Manual Uploaded?" name="isIVRS-IWRS-IXRS" typeRef="boolean" />
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<definitions xmlns="http://www.omg.org/spec/DMN/20151101/dmn.xsd" xmlns:biodi="http://bpmn.io/schema/dmn/biodi/1.0" id="Definitions_65a4c07" name="DRD" namespace="http://camunda.org/schema/1.0/dmn">
|
||||||
|
<decision id="Decision_Coordinator" name="Coordinator Status">
|
||||||
|
<extensionElements>
|
||||||
|
<biodi:bounds x="200" y="140" width="180" height="80" />
|
||||||
|
</extensionElements>
|
||||||
|
<decisionTable id="decisionTable_1">
|
||||||
|
<input id="input_1" label="Coordinator Status">
|
||||||
|
<inputExpression id="inputExpression_1" typeRef="boolean">
|
||||||
|
<text></text>
|
||||||
|
</inputExpression>
|
||||||
|
</input>
|
||||||
|
<output id="output_1" label="Coordinator Form Banner" name="ElementDoc_Coordinator" typeRef="string" />
|
||||||
|
<rule id="DecisionRule_0sfkgkh">
|
||||||
|
<inputEntry id="UnaryTests_160rjry">
|
||||||
|
<text></text>
|
||||||
|
</inputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_1lxmr3n">
|
||||||
|
<text>"Placeholder"</text>
|
||||||
|
</outputEntry>
|
||||||
|
</rule>
|
||||||
|
</decisionTable>
|
||||||
|
</decision>
|
||||||
|
</definitions>
|
|
@ -0,0 +1,24 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<definitions xmlns="http://www.omg.org/spec/DMN/20151101/dmn.xsd" xmlns:biodi="http://bpmn.io/schema/dmn/biodi/1.0" id="Definitions_305b1db" name="DRD" namespace="http://camunda.org/schema/1.0/dmn">
|
||||||
|
<decision id="Decision_DepartmentChair" name="Department Chair Status">
|
||||||
|
<extensionElements>
|
||||||
|
<biodi:bounds x="200" y="150" width="180" height="80" />
|
||||||
|
</extensionElements>
|
||||||
|
<decisionTable id="decisionTable_1">
|
||||||
|
<input id="input_1" label="Department Chair Status">
|
||||||
|
<inputExpression id="inputExpression_1" typeRef="boolean">
|
||||||
|
<text></text>
|
||||||
|
</inputExpression>
|
||||||
|
</input>
|
||||||
|
<output id="output_1" label="Department Chair Form Banner" name="ElementDoc_DepartmentChair" typeRef="string" />
|
||||||
|
<rule id="DecisionRule_1by29jj">
|
||||||
|
<inputEntry id="UnaryTests_1gr92h8">
|
||||||
|
<text></text>
|
||||||
|
</inputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_0xadafz">
|
||||||
|
<text>"DC Placeholder"</text>
|
||||||
|
</outputEntry>
|
||||||
|
</rule>
|
||||||
|
</decisionTable>
|
||||||
|
</decision>
|
||||||
|
</definitions>
|
|
@ -10,14 +10,7 @@
|
||||||
<bpmn:script>StudyInfo investigators</bpmn:script>
|
<bpmn:script>StudyInfo investigators</bpmn:script>
|
||||||
</bpmn:scriptTask>
|
</bpmn:scriptTask>
|
||||||
<bpmn:userTask id="UserTask_EditPrimaryInvestigator" name="Edit Primary Investigator" camunda:formKey="PrimaryInvestigator">
|
<bpmn:userTask id="UserTask_EditPrimaryInvestigator" name="Edit Primary Investigator" camunda:formKey="PrimaryInvestigator">
|
||||||
<bpmn:documentation>### From Protocol Builder
|
<bpmn:documentation>{{ElementDoc_PrimaryInvestigator}}</bpmn:documentation>
|
||||||
{% for personnel in study.investigators|selectattr("INVESTIGATORTYPE", "equalto", "PI") %}
|
|
||||||
#### {{ personnel.INVESTIGATORTYPEFULL }}
|
|
||||||
{{ personnel.NETBADGEID }}
|
|
||||||
{% else %}
|
|
||||||
#### No Primary Investigator Entered in Protocol Builder
|
|
||||||
The PI is needed for many required steps. Please enter this information in Protocol Builder as soon as possible.
|
|
||||||
{% endfor %}</bpmn:documentation>
|
|
||||||
<bpmn:extensionElements>
|
<bpmn:extensionElements>
|
||||||
<camunda:formData>
|
<camunda:formData>
|
||||||
<camunda:formField id="PI_Experience" label="Investigator's Experience" type="textarea">
|
<camunda:formField id="PI_Experience" label="Investigator's Experience" type="textarea">
|
||||||
|
@ -44,7 +37,7 @@ The PI is needed for many required steps. Please enter this information in Prot
|
||||||
<bpmn:sequenceFlow id="SequenceFlow_122pp0f" sourceRef="UserTask_EditPrimaryInvestigator" targetRef="Gateway_19lvya6" />
|
<bpmn:sequenceFlow id="SequenceFlow_122pp0f" sourceRef="UserTask_EditPrimaryInvestigator" targetRef="Gateway_19lvya6" />
|
||||||
<bpmn:sequenceFlow id="Flow_0g0o593" sourceRef="Gateway_13kno0x" targetRef="UserTask_EditPrimaryInvestigator" />
|
<bpmn:sequenceFlow id="Flow_0g0o593" sourceRef="Gateway_13kno0x" targetRef="UserTask_EditPrimaryInvestigator" />
|
||||||
<bpmn:parallelGateway id="Gateway_13kno0x">
|
<bpmn:parallelGateway id="Gateway_13kno0x">
|
||||||
<bpmn:incoming>Flow_05aywbq</bpmn:incoming>
|
<bpmn:incoming>Flow_19i1d30</bpmn:incoming>
|
||||||
<bpmn:outgoing>Flow_0g0o593</bpmn:outgoing>
|
<bpmn:outgoing>Flow_0g0o593</bpmn:outgoing>
|
||||||
<bpmn:outgoing>Flow_1nudg96</bpmn:outgoing>
|
<bpmn:outgoing>Flow_1nudg96</bpmn:outgoing>
|
||||||
<bpmn:outgoing>Flow_18ix81l</bpmn:outgoing>
|
<bpmn:outgoing>Flow_18ix81l</bpmn:outgoing>
|
||||||
|
@ -58,13 +51,6 @@ The PI is needed for many required steps. Please enter this information in Prot
|
||||||
</bpmn:parallelGateway>
|
</bpmn:parallelGateway>
|
||||||
<bpmn:sequenceFlow id="Flow_1nudg96" sourceRef="Gateway_13kno0x" targetRef="Activity_EditCoordinator" />
|
<bpmn:sequenceFlow id="Flow_1nudg96" sourceRef="Gateway_13kno0x" targetRef="Activity_EditCoordinator" />
|
||||||
<bpmn:userTask id="Activity_EditCoordinator" name="Edit Coordinator" camunda:formKey="Coordinator">
|
<bpmn:userTask id="Activity_EditCoordinator" name="Edit Coordinator" camunda:formKey="Coordinator">
|
||||||
<bpmn:documentation>### From Protocol Builder
|
|
||||||
{% for personnel in study.investigators|selectattr("INVESTIGATORTYPE", "equalto", "SC_I") %}
|
|
||||||
#### {{ personnel.INVESTIGATORTYPEFULL }}
|
|
||||||
{{ personnel.NETBADGEID }}
|
|
||||||
{% else %}
|
|
||||||
#### No Primary Coordinator Entered in Protocol Builder
|
|
||||||
{% endfor %}</bpmn:documentation>
|
|
||||||
<bpmn:extensionElements>
|
<bpmn:extensionElements>
|
||||||
<camunda:formData>
|
<camunda:formData>
|
||||||
<camunda:formField id="CoordinatorI_EditAccess" label="Should this Coordinator have Study Team editing access in the system?" type="boolean" defaultValue="true" />
|
<camunda:formField id="CoordinatorI_EditAccess" label="Should this Coordinator have Study Team editing access in the system?" type="boolean" defaultValue="true" />
|
||||||
|
@ -77,13 +63,6 @@ The PI is needed for many required steps. Please enter this information in Prot
|
||||||
<bpmn:sequenceFlow id="Flow_00lv37g" sourceRef="Activity_EditCoordinator" targetRef="Gateway_19lvya6" />
|
<bpmn:sequenceFlow id="Flow_00lv37g" sourceRef="Activity_EditCoordinator" targetRef="Gateway_19lvya6" />
|
||||||
<bpmn:sequenceFlow id="Flow_18ix81l" sourceRef="Gateway_13kno0x" targetRef="Activity_EditDepartmentChair" />
|
<bpmn:sequenceFlow id="Flow_18ix81l" sourceRef="Gateway_13kno0x" targetRef="Activity_EditDepartmentChair" />
|
||||||
<bpmn:userTask id="Activity_EditDepartmentChair" name="Edit Department Chair" camunda:formKey="DeptmentChair">
|
<bpmn:userTask id="Activity_EditDepartmentChair" name="Edit Department Chair" camunda:formKey="DeptmentChair">
|
||||||
<bpmn:documentation>### From Protocol Builder
|
|
||||||
{% for personnel in study.investigators|selectattr("INVESTIGATORTYPE", "equalto", "DEPT_CH") %}
|
|
||||||
#### {{ personnel.INVESTIGATORTYPEFULL }}
|
|
||||||
{{ personnel.NETBADGEID }}
|
|
||||||
{% else %}
|
|
||||||
#### No Department Chair Entered in Protocol Builder
|
|
||||||
{% endfor %}</bpmn:documentation>
|
|
||||||
<bpmn:extensionElements>
|
<bpmn:extensionElements>
|
||||||
<camunda:formData>
|
<camunda:formData>
|
||||||
<camunda:formField id="DepartmentChair_EditAccess" label="Should the Department Chair have Study Team editing access in the system?" type="boolean" defaultValue="false" />
|
<camunda:formField id="DepartmentChair_EditAccess" label="Should the Department Chair have Study Team editing access in the system?" type="boolean" defaultValue="false" />
|
||||||
|
@ -94,74 +73,110 @@ The PI is needed for many required steps. Please enter this information in Prot
|
||||||
<bpmn:outgoing>Flow_0y1jvdw</bpmn:outgoing>
|
<bpmn:outgoing>Flow_0y1jvdw</bpmn:outgoing>
|
||||||
</bpmn:userTask>
|
</bpmn:userTask>
|
||||||
<bpmn:sequenceFlow id="Flow_0y1jvdw" sourceRef="Activity_EditDepartmentChair" targetRef="Gateway_19lvya6" />
|
<bpmn:sequenceFlow id="Flow_0y1jvdw" sourceRef="Activity_EditDepartmentChair" targetRef="Gateway_19lvya6" />
|
||||||
<bpmn:sequenceFlow id="Flow_05aywbq" sourceRef="ScriptTask_LoadPersonnel" targetRef="Gateway_13kno0x" />
|
<bpmn:sequenceFlow id="Flow_05aywbq" sourceRef="ScriptTask_LoadPersonnel" targetRef="Activity_PI-Satus" />
|
||||||
<bpmn:sequenceFlow id="Flow_0kcrx5l" sourceRef="StartEvent_1" targetRef="ScriptTask_LoadPersonnel" />
|
<bpmn:sequenceFlow id="Flow_0kcrx5l" sourceRef="StartEvent_1" targetRef="ScriptTask_LoadPersonnel" />
|
||||||
|
<bpmn:sequenceFlow id="Flow_12rh5aj" sourceRef="Activity_PI-Satus" targetRef="Activity_CoordinatorStatus" />
|
||||||
|
<bpmn:businessRuleTask id="Activity_PI-Satus" name="Primary Investigator Status" camunda:decisionRef="Decision_PrimaryInvestigator">
|
||||||
|
<bpmn:incoming>Flow_05aywbq</bpmn:incoming>
|
||||||
|
<bpmn:outgoing>Flow_12rh5aj</bpmn:outgoing>
|
||||||
|
</bpmn:businessRuleTask>
|
||||||
|
<bpmn:sequenceFlow id="Flow_04nzqn8" sourceRef="Activity_CoordinatorStatus" targetRef="Activity_DepartmentChairStatus" />
|
||||||
|
<bpmn:businessRuleTask id="Activity_CoordinatorStatus" name="Coordinator Status" camunda:decisionRef="Decision_Coordinator">
|
||||||
|
<bpmn:incoming>Flow_12rh5aj</bpmn:incoming>
|
||||||
|
<bpmn:outgoing>Flow_04nzqn8</bpmn:outgoing>
|
||||||
|
</bpmn:businessRuleTask>
|
||||||
|
<bpmn:sequenceFlow id="Flow_19i1d30" sourceRef="Activity_DepartmentChairStatus" targetRef="Gateway_13kno0x" />
|
||||||
|
<bpmn:businessRuleTask id="Activity_DepartmentChairStatus" name="Department Chair Status" camunda:decisionRef="Decision_DepartmentChair">
|
||||||
|
<bpmn:incoming>Flow_04nzqn8</bpmn:incoming>
|
||||||
|
<bpmn:outgoing>Flow_19i1d30</bpmn:outgoing>
|
||||||
|
</bpmn:businessRuleTask>
|
||||||
</bpmn:process>
|
</bpmn:process>
|
||||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_01143nb">
|
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_01143nb">
|
||||||
|
<bpmndi:BPMNEdge id="Flow_19i1d30_di" bpmnElement="Flow_19i1d30">
|
||||||
|
<di:waypoint x="580" y="170" />
|
||||||
|
<di:waypoint x="645" y="170" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNEdge id="Flow_04nzqn8_di" bpmnElement="Flow_04nzqn8">
|
||||||
|
<di:waypoint x="440" y="170" />
|
||||||
|
<di:waypoint x="480" y="170" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNEdge id="Flow_12rh5aj_di" bpmnElement="Flow_12rh5aj">
|
||||||
|
<di:waypoint x="290" y="170" />
|
||||||
|
<di:waypoint x="340" y="170" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
<bpmndi:BPMNEdge id="Flow_0kcrx5l_di" bpmnElement="Flow_0kcrx5l">
|
<bpmndi:BPMNEdge id="Flow_0kcrx5l_di" bpmnElement="Flow_0kcrx5l">
|
||||||
<di:waypoint x="168" y="170" />
|
<di:waypoint x="-52" y="170" />
|
||||||
<di:waypoint x="270" y="170" />
|
<di:waypoint x="30" y="170" />
|
||||||
</bpmndi:BPMNEdge>
|
</bpmndi:BPMNEdge>
|
||||||
<bpmndi:BPMNEdge id="Flow_05aywbq_di" bpmnElement="Flow_05aywbq">
|
<bpmndi:BPMNEdge id="Flow_05aywbq_di" bpmnElement="Flow_05aywbq">
|
||||||
<di:waypoint x="370" y="170" />
|
<di:waypoint x="130" y="170" />
|
||||||
<di:waypoint x="475" y="170" />
|
<di:waypoint x="190" y="170" />
|
||||||
</bpmndi:BPMNEdge>
|
</bpmndi:BPMNEdge>
|
||||||
<bpmndi:BPMNEdge id="Flow_0y1jvdw_di" bpmnElement="Flow_0y1jvdw">
|
<bpmndi:BPMNEdge id="Flow_0y1jvdw_di" bpmnElement="Flow_0y1jvdw">
|
||||||
<di:waypoint x="710" y="300" />
|
<di:waypoint x="880" y="300" />
|
||||||
<di:waypoint x="810" y="300" />
|
<di:waypoint x="980" y="300" />
|
||||||
<di:waypoint x="810" y="195" />
|
<di:waypoint x="980" y="195" />
|
||||||
</bpmndi:BPMNEdge>
|
</bpmndi:BPMNEdge>
|
||||||
<bpmndi:BPMNEdge id="Flow_18ix81l_di" bpmnElement="Flow_18ix81l">
|
<bpmndi:BPMNEdge id="Flow_18ix81l_di" bpmnElement="Flow_18ix81l">
|
||||||
<di:waypoint x="500" y="195" />
|
<di:waypoint x="670" y="195" />
|
||||||
<di:waypoint x="500" y="300" />
|
<di:waypoint x="670" y="300" />
|
||||||
<di:waypoint x="610" y="300" />
|
<di:waypoint x="780" y="300" />
|
||||||
</bpmndi:BPMNEdge>
|
</bpmndi:BPMNEdge>
|
||||||
<bpmndi:BPMNEdge id="Flow_00lv37g_di" bpmnElement="Flow_00lv37g">
|
<bpmndi:BPMNEdge id="Flow_00lv37g_di" bpmnElement="Flow_00lv37g">
|
||||||
<di:waypoint x="710" y="170" />
|
<di:waypoint x="880" y="170" />
|
||||||
<di:waypoint x="785" y="170" />
|
<di:waypoint x="955" y="170" />
|
||||||
</bpmndi:BPMNEdge>
|
</bpmndi:BPMNEdge>
|
||||||
<bpmndi:BPMNEdge id="Flow_1nudg96_di" bpmnElement="Flow_1nudg96">
|
<bpmndi:BPMNEdge id="Flow_1nudg96_di" bpmnElement="Flow_1nudg96">
|
||||||
<di:waypoint x="525" y="170" />
|
<di:waypoint x="695" y="170" />
|
||||||
<di:waypoint x="610" y="170" />
|
<di:waypoint x="780" y="170" />
|
||||||
</bpmndi:BPMNEdge>
|
</bpmndi:BPMNEdge>
|
||||||
<bpmndi:BPMNEdge id="Flow_14469u8_di" bpmnElement="Flow_14469u8">
|
<bpmndi:BPMNEdge id="Flow_14469u8_di" bpmnElement="Flow_14469u8">
|
||||||
<di:waypoint x="835" y="170" />
|
<di:waypoint x="1005" y="170" />
|
||||||
<di:waypoint x="932" y="170" />
|
<di:waypoint x="1102" y="170" />
|
||||||
</bpmndi:BPMNEdge>
|
</bpmndi:BPMNEdge>
|
||||||
<bpmndi:BPMNEdge id="Flow_0g0o593_di" bpmnElement="Flow_0g0o593">
|
<bpmndi:BPMNEdge id="Flow_0g0o593_di" bpmnElement="Flow_0g0o593">
|
||||||
<di:waypoint x="500" y="145" />
|
<di:waypoint x="670" y="145" />
|
||||||
<di:waypoint x="500" y="30" />
|
<di:waypoint x="670" y="30" />
|
||||||
<di:waypoint x="610" y="30" />
|
<di:waypoint x="780" y="30" />
|
||||||
</bpmndi:BPMNEdge>
|
</bpmndi:BPMNEdge>
|
||||||
<bpmndi:BPMNEdge id="SequenceFlow_122pp0f_di" bpmnElement="SequenceFlow_122pp0f">
|
<bpmndi:BPMNEdge id="SequenceFlow_122pp0f_di" bpmnElement="SequenceFlow_122pp0f">
|
||||||
<di:waypoint x="710" y="30" />
|
<di:waypoint x="880" y="30" />
|
||||||
<di:waypoint x="810" y="30" />
|
<di:waypoint x="980" y="30" />
|
||||||
<di:waypoint x="810" y="145" />
|
<di:waypoint x="980" y="145" />
|
||||||
</bpmndi:BPMNEdge>
|
</bpmndi:BPMNEdge>
|
||||||
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
|
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
|
||||||
<dc:Bounds x="132" y="152" width="36" height="36" />
|
<dc:Bounds x="-88" y="152" width="36" height="36" />
|
||||||
</bpmndi:BPMNShape>
|
</bpmndi:BPMNShape>
|
||||||
<bpmndi:BPMNShape id="ScriptTask_0h49cmf_di" bpmnElement="ScriptTask_LoadPersonnel">
|
<bpmndi:BPMNShape id="ScriptTask_0h49cmf_di" bpmnElement="ScriptTask_LoadPersonnel">
|
||||||
<dc:Bounds x="270" y="130" width="100" height="80" />
|
<dc:Bounds x="30" y="130" width="100" height="80" />
|
||||||
</bpmndi:BPMNShape>
|
|
||||||
<bpmndi:BPMNShape id="EndEvent_1qor16n_di" bpmnElement="EndEvent_1qor16n">
|
|
||||||
<dc:Bounds x="932" y="152" width="36" height="36" />
|
|
||||||
</bpmndi:BPMNShape>
|
|
||||||
<bpmndi:BPMNShape id="Gateway_1lvmp6i_di" bpmnElement="Gateway_13kno0x">
|
|
||||||
<dc:Bounds x="475" y="145" width="50" height="50" />
|
|
||||||
</bpmndi:BPMNShape>
|
|
||||||
<bpmndi:BPMNShape id="Gateway_0k9iptd_di" bpmnElement="Gateway_19lvya6">
|
|
||||||
<dc:Bounds x="785" y="145" width="50" height="50" />
|
|
||||||
</bpmndi:BPMNShape>
|
|
||||||
<bpmndi:BPMNShape id="Activity_0d622qi_di" bpmnElement="Activity_EditDepartmentChair">
|
|
||||||
<dc:Bounds x="610" y="260" width="100" height="80" />
|
|
||||||
</bpmndi:BPMNShape>
|
|
||||||
<bpmndi:BPMNShape id="Activity_0fl86y3_di" bpmnElement="Activity_EditCoordinator">
|
|
||||||
<dc:Bounds x="610" y="130" width="100" height="80" />
|
|
||||||
</bpmndi:BPMNShape>
|
</bpmndi:BPMNShape>
|
||||||
<bpmndi:BPMNShape id="UserTask_0hpnrm9_di" bpmnElement="UserTask_EditPrimaryInvestigator">
|
<bpmndi:BPMNShape id="UserTask_0hpnrm9_di" bpmnElement="UserTask_EditPrimaryInvestigator">
|
||||||
<dc:Bounds x="610" y="-10" width="100" height="80" />
|
<dc:Bounds x="780" y="-10" width="100" height="80" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="EndEvent_1qor16n_di" bpmnElement="EndEvent_1qor16n">
|
||||||
|
<dc:Bounds x="1102" y="152" width="36" height="36" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="Gateway_1lvmp6i_di" bpmnElement="Gateway_13kno0x">
|
||||||
|
<dc:Bounds x="645" y="145" width="50" height="50" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="Gateway_0k9iptd_di" bpmnElement="Gateway_19lvya6">
|
||||||
|
<dc:Bounds x="955" y="145" width="50" height="50" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="Activity_0fl86y3_di" bpmnElement="Activity_EditCoordinator">
|
||||||
|
<dc:Bounds x="780" y="130" width="100" height="80" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="Activity_0d622qi_di" bpmnElement="Activity_EditDepartmentChair">
|
||||||
|
<dc:Bounds x="780" y="260" width="100" height="80" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="Activity_1yggezg_di" bpmnElement="Activity_PI-Satus">
|
||||||
|
<dc:Bounds x="190" y="130" width="100" height="80" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="Activity_10gqxcp_di" bpmnElement="Activity_CoordinatorStatus">
|
||||||
|
<dc:Bounds x="340" y="130" width="100" height="80" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="Activity_1g0yuo4_di" bpmnElement="Activity_DepartmentChairStatus">
|
||||||
|
<dc:Bounds x="480" y="130" width="100" height="80" />
|
||||||
</bpmndi:BPMNShape>
|
</bpmndi:BPMNShape>
|
||||||
</bpmndi:BPMNPlane>
|
</bpmndi:BPMNPlane>
|
||||||
</bpmndi:BPMNDiagram>
|
</bpmndi:BPMNDiagram>
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<definitions xmlns="http://www.omg.org/spec/DMN/20151101/dmn.xsd" xmlns:biodi="http://bpmn.io/schema/dmn/biodi/1.0" id="Definitions_cc48f13" name="DRD" namespace="http://camunda.org/schema/1.0/dmn">
|
||||||
|
<decision id="Decision_PrimaryInvestigator" name="Primary Investigator Status">
|
||||||
|
<extensionElements>
|
||||||
|
<biodi:bounds x="200" y="160" width="180" height="80" />
|
||||||
|
</extensionElements>
|
||||||
|
<decisionTable id="decisionTable_1">
|
||||||
|
<input id="input_1" label="Primary Investogator Status">
|
||||||
|
<inputExpression id="inputExpression_1" typeRef="boolean" expressionLanguage="feel">
|
||||||
|
<text>list contains( for i in [study.investigators[0].INVESTIGATORTYPE, study.investigators[1].INVESTIGATORTYPE, study.investigators[2].INVESTIGATORTYPE] return i, "PI")</text>
|
||||||
|
</inputExpression>
|
||||||
|
</input>
|
||||||
|
<output id="output_1" label="Primary Investigator Form Banner" name="ElementDoc_PrimaryInvestigator" typeRef="string" />
|
||||||
|
<rule id="DecisionRule_19gl4re">
|
||||||
|
<inputEntry id="UnaryTests_14311bk">
|
||||||
|
<text>true</text>
|
||||||
|
</inputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_1d3eboq">
|
||||||
|
<text>"Placeholder - True"</text>
|
||||||
|
</outputEntry>
|
||||||
|
</rule>
|
||||||
|
<rule id="DecisionRule_1crmfau">
|
||||||
|
<inputEntry id="UnaryTests_110jbd6">
|
||||||
|
<text>false</text>
|
||||||
|
</inputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_107i08h">
|
||||||
|
<text>"Placeholder - False"</text>
|
||||||
|
</outputEntry>
|
||||||
|
</rule>
|
||||||
|
</decisionTable>
|
||||||
|
</decision>
|
||||||
|
</definitions>
|
|
@ -2,12 +2,12 @@
|
||||||
<definitions xmlns="http://www.omg.org/spec/DMN/20151101/dmn.xsd" xmlns:biodi="http://bpmn.io/schema/dmn/biodi/1.0" id="Definitions_1p34ouw" name="DRD" namespace="http://camunda.org/schema/1.0/dmn" exporter="Camunda Modeler" exporterVersion="3.4.1">
|
<definitions xmlns="http://www.omg.org/spec/DMN/20151101/dmn.xsd" xmlns:biodi="http://bpmn.io/schema/dmn/biodi/1.0" id="Definitions_1p34ouw" name="DRD" namespace="http://camunda.org/schema/1.0/dmn" exporter="Camunda Modeler" exporterVersion="3.4.1">
|
||||||
<decision id="data_security_plan" name="Data Security Plan">
|
<decision id="data_security_plan" name="Data Security Plan">
|
||||||
<extensionElements>
|
<extensionElements>
|
||||||
<biodi:bounds x="190" y="80" width="180" height="80" />
|
<biodi:bounds x="180" y="80" width="180" height="80" />
|
||||||
</extensionElements>
|
</extensionElements>
|
||||||
<decisionTable id="DecisionTable_1mjqwlv">
|
<decisionTable id="DecisionTable_1mjqwlv">
|
||||||
<input id="InputClause_18pwfqu" label="Data Plan Required in PB?">
|
<input id="InputClause_18pwfqu" label="Required Doc Keys">
|
||||||
<inputExpression id="LiteralExpression_1y84stb" typeRef="boolean" expressionLanguage="feel">
|
<inputExpression id="LiteralExpression_1y84stb" typeRef="boolean" expressionLanguage="feel">
|
||||||
<text>Documents['Study_DataSecurityPlan']['required']</text>
|
<text>Documents['UVACompl_PRCAppr']['required']</text>
|
||||||
</inputExpression>
|
</inputExpression>
|
||||||
</input>
|
</input>
|
||||||
<output id="OutputClause_05y0j7c" label="data_security_plan" name="data_security_plan" typeRef="string" />
|
<output id="OutputClause_05y0j7c" label="data_security_plan" name="data_security_plan" typeRef="string" />
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<definitions xmlns="http://www.omg.org/spec/DMN/20151101/dmn.xsd" xmlns:biodi="http://bpmn.io/schema/dmn/biodi/1.0" id="Definitions_1p34ouw" name="DRD" namespace="http://camunda.org/schema/1.0/dmn" exporter="Camunda Modeler" exporterVersion="3.4.1">
|
<definitions xmlns="http://www.omg.org/spec/DMN/20151101/dmn.xsd" xmlns:biodi="http://bpmn.io/schema/dmn/biodi/1.0" id="Definitions_1p34ouw" name="DRD" namespace="http://camunda.org/schema/1.0/dmn" exporter="Camunda Modeler" exporterVersion="3.4.1">
|
||||||
<decision id="enter_core_info" name="Enter Core Info">
|
<decision id="enter_core_info" name="Enter Core Info">
|
||||||
<extensionElements>
|
<extensionElements>
|
||||||
<biodi:bounds x="170" y="60" width="180" height="80" />
|
<biodi:bounds x="160" y="60" width="180" height="80" />
|
||||||
</extensionElements>
|
</extensionElements>
|
||||||
<decisionTable id="decisionTable_1">
|
<decisionTable id="decisionTable_1">
|
||||||
<input id="InputClause_1ki80j6" label="required doc ids">
|
<input id="InputClause_1ki80j6" label="required doc ids">
|
||||||
|
|
|
@ -2,10 +2,10 @@
|
||||||
<definitions xmlns="http://www.omg.org/spec/DMN/20151101/dmn.xsd" xmlns:biodi="http://bpmn.io/schema/dmn/biodi/1.0" id="Definitions_1p34ouw" name="DRD" namespace="http://camunda.org/schema/1.0/dmn" exporter="Camunda Modeler" exporterVersion="3.4.1">
|
<definitions xmlns="http://www.omg.org/spec/DMN/20151101/dmn.xsd" xmlns:biodi="http://bpmn.io/schema/dmn/biodi/1.0" id="Definitions_1p34ouw" name="DRD" namespace="http://camunda.org/schema/1.0/dmn" exporter="Camunda Modeler" exporterVersion="3.4.1">
|
||||||
<decision id="sponsor_funding_source" name="Sponsor Funding Source">
|
<decision id="sponsor_funding_source" name="Sponsor Funding Source">
|
||||||
<extensionElements>
|
<extensionElements>
|
||||||
<biodi:bounds x="190" y="70" width="180" height="80" />
|
<biodi:bounds x="190" y="80" width="180" height="80" />
|
||||||
</extensionElements>
|
</extensionElements>
|
||||||
<decisionTable id="DecisionTable_00zdxg0">
|
<decisionTable id="DecisionTable_00zdxg0">
|
||||||
<input id="InputClause_02n3ccs" label="Sponsor Document Required in PB?">
|
<input id="InputClause_02n3ccs" label="CoCApplication Required?">
|
||||||
<inputExpression id="LiteralExpression_1ju4o1o" typeRef="boolean" expressionLanguage="feel">
|
<inputExpression id="LiteralExpression_1ju4o1o" typeRef="boolean" expressionLanguage="feel">
|
||||||
<text>Documents['AD_LabManual']['required']</text>
|
<text>Documents['AD_LabManual']['required']</text>
|
||||||
</inputExpression>
|
</inputExpression>
|
||||||
|
|
|
@ -60,13 +60,52 @@
|
||||||
</bpmn:process>
|
</bpmn:process>
|
||||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_0jhpidf">
|
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_0jhpidf">
|
||||||
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
|
<bpmndi:BPMNShape id="TextAnnotation_0ydnva4_di" bpmnElement="TextAnnotation_0ydnva4">
|
||||||
<dc:Bounds x="192" y="421" width="36" height="36" />
|
<dc:Bounds x="155" y="210" width="110" height="82" />
|
||||||
</bpmndi:BPMNShape>
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNEdge id="Flow_0x9580l_di" bpmnElement="Flow_0x9580l">
|
||||||
|
<di:waypoint x="740" y="550" />
|
||||||
|
<di:waypoint x="800" y="550" />
|
||||||
|
<di:waypoint x="800" y="464" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNEdge id="Flow_1txrak2_di" bpmnElement="Flow_1txrak2">
|
||||||
|
<di:waypoint x="740" y="439" />
|
||||||
|
<di:waypoint x="775" y="439" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNEdge id="Flow_1nimppb_di" bpmnElement="Flow_1nimppb">
|
||||||
|
<di:waypoint x="608" y="439" />
|
||||||
|
<di:waypoint x="640" y="439" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNEdge id="Flow_18pl92p_di" bpmnElement="Flow_18pl92p">
|
||||||
|
<di:waypoint x="583" y="464" />
|
||||||
|
<di:waypoint x="583" y="550" />
|
||||||
|
<di:waypoint x="640" y="550" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNEdge id="SequenceFlow_17ct47v_di" bpmnElement="SequenceFlow_17ct47v">
|
||||||
|
<di:waypoint x="400" y="439" />
|
||||||
|
<di:waypoint x="558" y="439" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNEdge id="Flow_1m8285h_di" bpmnElement="Flow_1m8285h">
|
||||||
|
<di:waypoint x="583" y="414" />
|
||||||
|
<di:waypoint x="583" y="330" />
|
||||||
|
<di:waypoint x="640" y="330" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNEdge id="Flow_0pwtiqm_di" bpmnElement="Flow_0pwtiqm">
|
||||||
|
<di:waypoint x="825" y="439" />
|
||||||
|
<di:waypoint x="862" y="439" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNEdge id="Flow_1sggkit_di" bpmnElement="Flow_1sggkit">
|
||||||
|
<di:waypoint x="740" y="330" />
|
||||||
|
<di:waypoint x="800" y="330" />
|
||||||
|
<di:waypoint x="800" y="414" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
<bpmndi:BPMNEdge id="SequenceFlow_1ees8ka_di" bpmnElement="SequenceFlow_1ees8ka">
|
<bpmndi:BPMNEdge id="SequenceFlow_1ees8ka_di" bpmnElement="SequenceFlow_1ees8ka">
|
||||||
<di:waypoint x="228" y="439" />
|
<di:waypoint x="228" y="439" />
|
||||||
<di:waypoint x="300" y="439" />
|
<di:waypoint x="300" y="439" />
|
||||||
</bpmndi:BPMNEdge>
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
|
||||||
|
<dc:Bounds x="192" y="421" width="36" height="36" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
<bpmndi:BPMNShape id="Event_135x8jg_di" bpmnElement="Event_135x8jg">
|
<bpmndi:BPMNShape id="Event_135x8jg_di" bpmnElement="Event_135x8jg">
|
||||||
<dc:Bounds x="862" y="421" width="36" height="36" />
|
<dc:Bounds x="862" y="421" width="36" height="36" />
|
||||||
</bpmndi:BPMNShape>
|
</bpmndi:BPMNShape>
|
||||||
|
@ -76,74 +115,35 @@
|
||||||
<bpmndi:BPMNShape id="Activity_1yqy50i_di" bpmnElement="Activity_1yqy50i">
|
<bpmndi:BPMNShape id="Activity_1yqy50i_di" bpmnElement="Activity_1yqy50i">
|
||||||
<dc:Bounds x="640" y="290" width="100" height="80" />
|
<dc:Bounds x="640" y="290" width="100" height="80" />
|
||||||
</bpmndi:BPMNShape>
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="Gateway_1kk6x70_di" bpmnElement="Gateway_12tpgcy">
|
||||||
|
<dc:Bounds x="775" y="414" width="50" height="50" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="Gateway_1m22g4p_di" bpmnElement="Gateway_1nta7st">
|
||||||
|
<dc:Bounds x="558" y="414" width="50" height="50" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="Activity_1k5eeun_di" bpmnElement="Activity_1k5eeun">
|
||||||
|
<dc:Bounds x="640" y="399" width="100" height="80" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="Activity_16cm213_di" bpmnElement="Activity_16cm213">
|
||||||
|
<dc:Bounds x="640" y="510" width="100" height="80" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
<bpmndi:BPMNShape id="TextAnnotation_1pv8ygy_di" bpmnElement="TextAnnotation_1pv8ygy">
|
<bpmndi:BPMNShape id="TextAnnotation_1pv8ygy_di" bpmnElement="TextAnnotation_1pv8ygy">
|
||||||
<dc:Bounds x="300" y="247" width="100" height="68" />
|
<dc:Bounds x="300" y="247" width="100" height="68" />
|
||||||
</bpmndi:BPMNShape>
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="TextAnnotation_1f52jro_di" bpmnElement="TextAnnotation_1f52jro">
|
||||||
|
<dc:Bounds x="461" y="80" width="243" height="124" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
<bpmndi:BPMNEdge id="Association_0w69z3w_di" bpmnElement="Association_0w69z3w">
|
<bpmndi:BPMNEdge id="Association_0w69z3w_di" bpmnElement="Association_0w69z3w">
|
||||||
<di:waypoint x="350" y="399" />
|
<di:waypoint x="350" y="399" />
|
||||||
<di:waypoint x="350" y="315" />
|
<di:waypoint x="350" y="315" />
|
||||||
</bpmndi:BPMNEdge>
|
</bpmndi:BPMNEdge>
|
||||||
<bpmndi:BPMNShape id="TextAnnotation_0ydnva4_di" bpmnElement="TextAnnotation_0ydnva4">
|
|
||||||
<dc:Bounds x="155" y="220" width="110" height="82" />
|
|
||||||
</bpmndi:BPMNShape>
|
|
||||||
<bpmndi:BPMNEdge id="Association_0a41ixa_di" bpmnElement="Association_0a41ixa">
|
|
||||||
<di:waypoint x="210" y="421" />
|
|
||||||
<di:waypoint x="210" y="302" />
|
|
||||||
</bpmndi:BPMNEdge>
|
|
||||||
<bpmndi:BPMNShape id="TextAnnotation_1f52jro_di" bpmnElement="TextAnnotation_1f52jro">
|
|
||||||
<dc:Bounds x="461" y="80" width="243" height="124" />
|
|
||||||
</bpmndi:BPMNShape>
|
|
||||||
<bpmndi:BPMNEdge id="Flow_1sggkit_di" bpmnElement="Flow_1sggkit">
|
|
||||||
<di:waypoint x="740" y="330" />
|
|
||||||
<di:waypoint x="800" y="330" />
|
|
||||||
<di:waypoint x="800" y="414" />
|
|
||||||
</bpmndi:BPMNEdge>
|
|
||||||
<bpmndi:BPMNShape id="Gateway_1kk6x70_di" bpmnElement="Gateway_12tpgcy">
|
|
||||||
<dc:Bounds x="775" y="414" width="50" height="50" />
|
|
||||||
</bpmndi:BPMNShape>
|
|
||||||
<bpmndi:BPMNEdge id="Flow_0pwtiqm_di" bpmnElement="Flow_0pwtiqm">
|
|
||||||
<di:waypoint x="825" y="439" />
|
|
||||||
<di:waypoint x="862" y="439" />
|
|
||||||
</bpmndi:BPMNEdge>
|
|
||||||
<bpmndi:BPMNShape id="Gateway_1m22g4p_di" bpmnElement="Gateway_1nta7st">
|
|
||||||
<dc:Bounds x="558" y="414" width="50" height="50" />
|
|
||||||
</bpmndi:BPMNShape>
|
|
||||||
<bpmndi:BPMNEdge id="Association_1mzqzwj_di" bpmnElement="Association_1mzqzwj">
|
<bpmndi:BPMNEdge id="Association_1mzqzwj_di" bpmnElement="Association_1mzqzwj">
|
||||||
<di:waypoint x="583" y="414" />
|
<di:waypoint x="583" y="414" />
|
||||||
<di:waypoint x="583" y="204" />
|
<di:waypoint x="583" y="204" />
|
||||||
</bpmndi:BPMNEdge>
|
</bpmndi:BPMNEdge>
|
||||||
<bpmndi:BPMNEdge id="Flow_1m8285h_di" bpmnElement="Flow_1m8285h">
|
<bpmndi:BPMNEdge id="Association_0a41ixa_di" bpmnElement="Association_0a41ixa">
|
||||||
<di:waypoint x="583" y="414" />
|
<di:waypoint x="210" y="421" />
|
||||||
<di:waypoint x="583" y="330" />
|
<di:waypoint x="210" y="292" />
|
||||||
<di:waypoint x="640" y="330" />
|
|
||||||
</bpmndi:BPMNEdge>
|
|
||||||
<bpmndi:BPMNEdge id="SequenceFlow_17ct47v_di" bpmnElement="SequenceFlow_17ct47v">
|
|
||||||
<di:waypoint x="400" y="439" />
|
|
||||||
<di:waypoint x="558" y="439" />
|
|
||||||
</bpmndi:BPMNEdge>
|
|
||||||
<bpmndi:BPMNEdge id="Flow_18pl92p_di" bpmnElement="Flow_18pl92p">
|
|
||||||
<di:waypoint x="583" y="464" />
|
|
||||||
<di:waypoint x="583" y="550" />
|
|
||||||
<di:waypoint x="640" y="550" />
|
|
||||||
</bpmndi:BPMNEdge>
|
|
||||||
<bpmndi:BPMNEdge id="Flow_1nimppb_di" bpmnElement="Flow_1nimppb">
|
|
||||||
<di:waypoint x="608" y="439" />
|
|
||||||
<di:waypoint x="640" y="439" />
|
|
||||||
</bpmndi:BPMNEdge>
|
|
||||||
<bpmndi:BPMNShape id="Activity_1k5eeun_di" bpmnElement="Activity_1k5eeun">
|
|
||||||
<dc:Bounds x="640" y="399" width="100" height="80" />
|
|
||||||
</bpmndi:BPMNShape>
|
|
||||||
<bpmndi:BPMNEdge id="Flow_1txrak2_di" bpmnElement="Flow_1txrak2">
|
|
||||||
<di:waypoint x="740" y="439" />
|
|
||||||
<di:waypoint x="775" y="439" />
|
|
||||||
</bpmndi:BPMNEdge>
|
|
||||||
<bpmndi:BPMNShape id="Activity_16cm213_di" bpmnElement="Activity_16cm213">
|
|
||||||
<dc:Bounds x="640" y="510" width="100" height="80" />
|
|
||||||
</bpmndi:BPMNShape>
|
|
||||||
<bpmndi:BPMNEdge id="Flow_0x9580l_di" bpmnElement="Flow_0x9580l">
|
|
||||||
<di:waypoint x="740" y="550" />
|
|
||||||
<di:waypoint x="800" y="550" />
|
|
||||||
<di:waypoint x="800" y="464" />
|
|
||||||
</bpmndi:BPMNEdge>
|
</bpmndi:BPMNEdge>
|
||||||
</bpmndi:BPMNPlane>
|
</bpmndi:BPMNPlane>
|
||||||
</bpmndi:BPMNDiagram>
|
</bpmndi:BPMNDiagram>
|
||||||
|
|
|
@ -65,13 +65,6 @@ class ExampleDataLoader:
|
||||||
]
|
]
|
||||||
db.session.add_all(categories)
|
db.session.add_all(categories)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
self.create_spec(id="top_level_workflow",
|
|
||||||
name="top_level_workflow",
|
|
||||||
display_name="Top Level Workflow",
|
|
||||||
description="Determines the status of other workflows in a study",
|
|
||||||
category_id=None,
|
|
||||||
master_spec=True
|
|
||||||
)
|
|
||||||
|
|
||||||
# Pass IRB Review
|
# Pass IRB Review
|
||||||
self.create_spec(id="irb_api_personnel",
|
self.create_spec(id="irb_api_personnel",
|
||||||
|
@ -163,6 +156,14 @@ class ExampleDataLoader:
|
||||||
category_id=5,
|
category_id=5,
|
||||||
display_order=0)
|
display_order=0)
|
||||||
|
|
||||||
|
# Top Level (Master Status) Workflow
|
||||||
|
self.create_spec(id="top_level_workflow",
|
||||||
|
name="top_level_workflow",
|
||||||
|
display_name="Top Level Workflow",
|
||||||
|
description="Determines the status of other workflows in a study",
|
||||||
|
category_id=None,
|
||||||
|
master_spec=True)
|
||||||
|
|
||||||
|
|
||||||
def create_spec(self, id, name, display_name="", description="", filepath=None, master_spec=False, category_id=None, display_order=None):
|
def create_spec(self, id, name, display_name="", description="", filepath=None, master_spec=False, category_id=None, display_order=None):
|
||||||
"""Assumes that a directory exists in static/bpmn with the same name as the given id.
|
"""Assumes that a directory exists in static/bpmn with the same name as the given id.
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
,dan,lilmaker,15.04.2020 11:05,file:///home/dan/.config/libreoffice/4;
|
Binary file not shown.
|
@ -0,0 +1,49 @@
|
||||||
|
<?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.4.1">
|
||||||
|
<bpmn:process id="Process_1vu5nxl" isExecutable="true">
|
||||||
|
<bpmn:startEvent id="StartEvent_1">
|
||||||
|
<bpmn:outgoing>SequenceFlow_0lvudp8</bpmn:outgoing>
|
||||||
|
</bpmn:startEvent>
|
||||||
|
<bpmn:sequenceFlow id="SequenceFlow_0lvudp8" sourceRef="StartEvent_1" targetRef="Task_14svgcu" />
|
||||||
|
<bpmn:endEvent id="EndEvent_0q4qzl9">
|
||||||
|
<bpmn:incoming>SequenceFlow_02vev7n</bpmn:incoming>
|
||||||
|
</bpmn:endEvent>
|
||||||
|
<bpmn:sequenceFlow id="SequenceFlow_02vev7n" sourceRef="Task_14svgcu" targetRef="EndEvent_0q4qzl9" />
|
||||||
|
<bpmn:userTask id="Task_14svgcu" name="Enum Lookup Form" camunda:formKey="EnumForm">
|
||||||
|
<bpmn:extensionElements>
|
||||||
|
<camunda:formData>
|
||||||
|
<camunda:formField id="AllTheNames" label="Select a value" type="enum">
|
||||||
|
<camunda:properties>
|
||||||
|
<camunda:property id="enum.options.file" value="customer_list.xls" />
|
||||||
|
<camunda:property id="enum.options.value.column" value="CUSTOMER_NUMBER" />
|
||||||
|
<camunda:property id="enum.options.label.column" value="CUSTOMER_NAME" />
|
||||||
|
</camunda:properties>
|
||||||
|
</camunda:formField>
|
||||||
|
</camunda:formData>
|
||||||
|
</bpmn:extensionElements>
|
||||||
|
<bpmn:incoming>SequenceFlow_0lvudp8</bpmn:incoming>
|
||||||
|
<bpmn:outgoing>SequenceFlow_02vev7n</bpmn:outgoing>
|
||||||
|
</bpmn:userTask>
|
||||||
|
</bpmn:process>
|
||||||
|
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||||
|
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1vu5nxl">
|
||||||
|
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
|
||||||
|
<dc:Bounds x="179" y="99" width="36" height="36" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNEdge id="SequenceFlow_0lvudp8_di" bpmnElement="SequenceFlow_0lvudp8">
|
||||||
|
<di:waypoint x="215" y="117" />
|
||||||
|
<di:waypoint x="270" y="117" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNShape id="EndEvent_0q4qzl9_di" bpmnElement="EndEvent_0q4qzl9">
|
||||||
|
<dc:Bounds x="432" y="99" width="36" height="36" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNEdge id="SequenceFlow_02vev7n_di" bpmnElement="SequenceFlow_02vev7n">
|
||||||
|
<di:waypoint x="370" y="117" />
|
||||||
|
<di:waypoint x="432" y="117" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNShape id="UserTask_18ly1yq_di" bpmnElement="Task_14svgcu">
|
||||||
|
<dc:Bounds x="270" y="77" width="100" height="80" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
</bpmndi:BPMNPlane>
|
||||||
|
</bpmndi:BPMNDiagram>
|
||||||
|
</bpmn:definitions>
|
|
@ -14,8 +14,9 @@ class TestFilesApi(BaseTest):
|
||||||
|
|
||||||
def test_list_files_for_workflow_spec(self):
|
def test_list_files_for_workflow_spec(self):
|
||||||
self.load_example_data()
|
self.load_example_data()
|
||||||
spec = session.query(WorkflowSpecModel).first()
|
spec_id = 'core_info'
|
||||||
rv = self.app.get('/v1.0/file?workflow_spec_id=%s' % spec.id,
|
spec = session.query(WorkflowSpecModel).filter_by(id=spec_id).first()
|
||||||
|
rv = self.app.get('/v1.0/file?workflow_spec_id=%s' % spec_id,
|
||||||
follow_redirects=True,
|
follow_redirects=True,
|
||||||
content_type="application/json", headers=self.logged_in_headers())
|
content_type="application/json", headers=self.logged_in_headers())
|
||||||
self.assert_success(rv)
|
self.assert_success(rv)
|
||||||
|
|
|
@ -41,9 +41,14 @@ class TestStudyApi(BaseTest):
|
||||||
study = session.query(StudyModel).first()
|
study = session.query(StudyModel).first()
|
||||||
self.assertIsNotNone(study)
|
self.assertIsNotNone(study)
|
||||||
|
|
||||||
def test_get_study(self):
|
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_required_docs') # mock_docs
|
||||||
|
def test_get_study(self, mock_docs):
|
||||||
"""Generic test, but pretty detailed, in that the study should return a categorized list of workflows
|
"""Generic test, but pretty detailed, in that the study should return a categorized list of workflows
|
||||||
This starts with out loading the example data, to show that all the bases are covered from ground 0."""
|
This starts with out loading the example data, to show that all the bases are covered from ground 0."""
|
||||||
|
|
||||||
|
docs_response = self.protocol_builder_response('required_docs.json')
|
||||||
|
mock_docs.return_value = json.loads(docs_response)
|
||||||
|
|
||||||
new_study = self.add_test_study()
|
new_study = self.add_test_study()
|
||||||
new_study = session.query(StudyModel).filter_by(id=new_study["id"]).first()
|
new_study = session.query(StudyModel).filter_by(id=new_study["id"]).first()
|
||||||
# Add a category
|
# Add a category
|
||||||
|
@ -109,9 +114,10 @@ class TestStudyApi(BaseTest):
|
||||||
self.assertEqual(study.title, json_data['title'])
|
self.assertEqual(study.title, json_data['title'])
|
||||||
self.assertEqual(study.protocol_builder_status.name, json_data['protocol_builder_status'])
|
self.assertEqual(study.protocol_builder_status.name, json_data['protocol_builder_status'])
|
||||||
|
|
||||||
|
@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_study_details') # mock_details
|
||||||
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_studies') # mock_studies
|
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_studies') # mock_studies
|
||||||
def test_get_all_studies(self, mock_studies, mock_details):
|
def test_get_all_studies(self, mock_studies, mock_details, mock_docs):
|
||||||
self.load_example_data()
|
self.load_example_data()
|
||||||
s = StudyModel(
|
s = StudyModel(
|
||||||
id=54321, # This matches one of the ids from the study_details_json data.
|
id=54321, # This matches one of the ids from the study_details_json data.
|
||||||
|
@ -128,6 +134,8 @@ class TestStudyApi(BaseTest):
|
||||||
mock_studies.return_value = ProtocolBuilderStudySchema(many=True).loads(studies_response)
|
mock_studies.return_value = ProtocolBuilderStudySchema(many=True).loads(studies_response)
|
||||||
details_response = self.protocol_builder_response('study_details.json')
|
details_response = self.protocol_builder_response('study_details.json')
|
||||||
mock_details.return_value = json.loads(details_response)
|
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)
|
||||||
|
|
||||||
# Make the api call to get all studies
|
# Make the api call to get all studies
|
||||||
api_response = self.app.get('/v1.0/study', headers=self.logged_in_headers(), content_type="application/json")
|
api_response = self.app.get('/v1.0/study', headers=self.logged_in_headers(), content_type="application/json")
|
||||||
|
@ -155,7 +163,12 @@ class TestStudyApi(BaseTest):
|
||||||
test_study = session.query(StudyModel).filter_by(id=54321).first()
|
test_study = session.query(StudyModel).filter_by(id=54321).first()
|
||||||
self.assertFalse(test_study.inactive)
|
self.assertFalse(test_study.inactive)
|
||||||
|
|
||||||
def test_get_single_study(self):
|
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_required_docs') # mock_docs
|
||||||
|
def test_get_single_study(self, mock_docs):
|
||||||
|
|
||||||
|
docs_response = self.protocol_builder_response('required_docs.json')
|
||||||
|
mock_docs.return_value = json.loads(docs_response)
|
||||||
|
|
||||||
self.load_example_data()
|
self.load_example_data()
|
||||||
study = session.query(StudyModel).first()
|
study = session.query(StudyModel).first()
|
||||||
rv = self.app.get('/v1.0/study/%i' % study.id,
|
rv = self.app.get('/v1.0/study/%i' % study.id,
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
import json
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
from crc import db
|
from crc import db
|
||||||
from crc.models.protocol_builder import ProtocolBuilderStatus
|
from crc.models.protocol_builder import ProtocolBuilderStatus
|
||||||
from crc.models.study import StudyModel
|
from crc.models.study import StudyModel
|
||||||
|
@ -13,10 +16,13 @@ from tests.base_test import BaseTest
|
||||||
class TestStudyService(BaseTest):
|
class TestStudyService(BaseTest):
|
||||||
"""Largely tested via the test_study_api, and time is tight, but adding new tests here."""
|
"""Largely tested via the test_study_api, and time is tight, but adding new tests here."""
|
||||||
|
|
||||||
|
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_required_docs') # mock_docs
|
||||||
def test_total_tasks_updated(self):
|
def test_total_tasks_updated(self, mock_docs):
|
||||||
"""Assure that as a users progress is available when getting a list of studies for that user."""
|
"""Assure that as a users progress is available when getting a list of studies for that user."""
|
||||||
|
|
||||||
|
docs_response = self.protocol_builder_response('required_docs.json')
|
||||||
|
mock_docs.return_value = json.loads(docs_response)
|
||||||
|
|
||||||
# Assure some basic models are in place, This is a damn mess. Our database models need an overhaul to make
|
# Assure some basic models are in place, This is a damn mess. Our database models need an overhaul to make
|
||||||
# this easier - better relationship modeling is now critical.
|
# this easier - better relationship modeling is now critical.
|
||||||
self.load_test_spec("top_level_workflow", master_spec=True)
|
self.load_test_spec("top_level_workflow", master_spec=True)
|
||||||
|
|
|
@ -2,18 +2,15 @@ import json
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from crc import session, app
|
from crc import session, app
|
||||||
from crc.models.api_models import WorkflowApiSchema, Task
|
from crc.models.api_models import WorkflowApiSchema
|
||||||
from crc.models.file import FileModelSchema
|
from crc.models.file import FileModelSchema
|
||||||
from crc.models.stats import WorkflowStatsModel, TaskEventModel
|
from crc.models.stats import WorkflowStatsModel, TaskEventModel
|
||||||
from crc.models.study import StudyModel
|
from crc.models.workflow import WorkflowStatus
|
||||||
from crc.models.workflow import WorkflowSpecModelSchema, WorkflowModel, WorkflowStatus
|
|
||||||
from crc.services.workflow_processor import WorkflowProcessor
|
|
||||||
from tests.base_test import BaseTest
|
from tests.base_test import BaseTest
|
||||||
|
|
||||||
|
|
||||||
class TestTasksApi(BaseTest):
|
class TestTasksApi(BaseTest):
|
||||||
|
|
||||||
|
|
||||||
def get_workflow_api(self, workflow, soft_reset=False, hard_reset=False):
|
def get_workflow_api(self, workflow, soft_reset=False, hard_reset=False):
|
||||||
rv = self.app.get('/v1.0/workflow/%i?soft_reset=%s&hard_reset=%s' %
|
rv = self.app.get('/v1.0/workflow/%i?soft_reset=%s&hard_reset=%s' %
|
||||||
(workflow.id, str(soft_reset), str(hard_reset)),
|
(workflow.id, str(soft_reset), str(hard_reset)),
|
||||||
|
@ -158,47 +155,6 @@ class TestTasksApi(BaseTest):
|
||||||
files = FileModelSchema(many=True).load(json_data, session=session)
|
files = FileModelSchema(many=True).load(json_data, session=session)
|
||||||
self.assertTrue(len(files) == 1)
|
self.assertTrue(len(files) == 1)
|
||||||
|
|
||||||
def test_documentation_processing_handles_replacements(self):
|
|
||||||
|
|
||||||
docs = "Some simple docs"
|
|
||||||
task = Task(1, "bill", "bill", "", "started", {}, docs, {})
|
|
||||||
task.process_documentation(docs)
|
|
||||||
self.assertEqual(docs, task.documentation)
|
|
||||||
|
|
||||||
task.data = {"replace_me": "new_thing"}
|
|
||||||
task.process_documentation("{{replace_me}}")
|
|
||||||
self.assertEqual("new_thing", task.documentation)
|
|
||||||
|
|
||||||
documentation = """
|
|
||||||
# Bigger Test
|
|
||||||
|
|
||||||
* bullet one
|
|
||||||
* bullet two has {{replace_me}}
|
|
||||||
|
|
||||||
# other stuff.
|
|
||||||
"""
|
|
||||||
expected = """
|
|
||||||
# Bigger Test
|
|
||||||
|
|
||||||
* bullet one
|
|
||||||
* bullet two has new_thing
|
|
||||||
|
|
||||||
# other stuff.
|
|
||||||
"""
|
|
||||||
task.process_documentation(documentation)
|
|
||||||
self.assertEqual(expected, task.documentation)
|
|
||||||
|
|
||||||
def test_documentation_processing_handles_conditionals(self):
|
|
||||||
|
|
||||||
docs = "This test {% if works == 'yes' %}works{% endif %}"
|
|
||||||
task = Task(1, "bill", "bill", "", "started", {}, docs, {})
|
|
||||||
task.process_documentation(docs)
|
|
||||||
self.assertEqual("This test ", task.documentation)
|
|
||||||
|
|
||||||
task.data = {"works": 'yes'}
|
|
||||||
task.process_documentation(docs)
|
|
||||||
self.assertEqual("This test works", task.documentation)
|
|
||||||
|
|
||||||
def test_get_documentation_populated_in_end(self):
|
def test_get_documentation_populated_in_end(self):
|
||||||
self.load_example_data()
|
self.load_example_data()
|
||||||
workflow = self.create_workflow('random_fact')
|
workflow = self.create_workflow('random_fact')
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
from crc import session, app
|
||||||
|
from crc.models.api_models import WorkflowApiSchema, Task
|
||||||
|
from crc.models.file import FileModelSchema
|
||||||
|
from crc.models.stats import WorkflowStatsModel, TaskEventModel
|
||||||
|
from crc.models.study import StudyModel
|
||||||
|
from crc.models.workflow import WorkflowSpecModelSchema, WorkflowModel, WorkflowStatus
|
||||||
|
from crc.services.workflow_processor import WorkflowProcessor
|
||||||
|
from crc.services.workflow_service import WorkflowService
|
||||||
|
from tests.base_test import BaseTest
|
||||||
|
|
||||||
|
|
||||||
|
class TestWorkflowService(BaseTest):
|
||||||
|
|
||||||
|
def test_documentation_processing_handles_replacements(self):
|
||||||
|
|
||||||
|
docs = "Some simple docs"
|
||||||
|
task = Task(1, "bill", "bill", "", "started", {}, docs, {})
|
||||||
|
WorkflowService._process_documentation(task, docs)
|
||||||
|
self.assertEqual(docs, task.documentation)
|
||||||
|
|
||||||
|
task.data = {"replace_me": "new_thing"}
|
||||||
|
WorkflowService._process_documentation(task, "{{replace_me}}")
|
||||||
|
self.assertEqual("new_thing", task.documentation)
|
||||||
|
|
||||||
|
documentation = """
|
||||||
|
# Bigger Test
|
||||||
|
|
||||||
|
* bullet one
|
||||||
|
* bullet two has {{replace_me}}
|
||||||
|
|
||||||
|
# other stuff.
|
||||||
|
"""
|
||||||
|
expected = """
|
||||||
|
# Bigger Test
|
||||||
|
|
||||||
|
* bullet one
|
||||||
|
* bullet two has new_thing
|
||||||
|
|
||||||
|
# other stuff.
|
||||||
|
"""
|
||||||
|
WorkflowService._process_documentation(task,(documentation))
|
||||||
|
self.assertEqual(expected, task.documentation)
|
||||||
|
|
||||||
|
def test_documentation_processing_handles_conditionals(self):
|
||||||
|
|
||||||
|
docs = "This test {% if works == 'yes' %}works{% endif %}"
|
||||||
|
task = Task(1, "bill", "bill", "", "started", {}, docs, {})
|
||||||
|
WorkflowService._process_documentation(task, docs)
|
||||||
|
self.assertEqual("This test ", task.documentation)
|
||||||
|
|
||||||
|
task.data = {"works": 'yes'}
|
||||||
|
WorkflowService._process_documentation(task, docs)
|
||||||
|
self.assertEqual("This test works", task.documentation)
|
||||||
|
|
||||||
|
def test_enum_options_from_file(self):
|
||||||
|
self.load_example_data()
|
||||||
|
workflow = self.create_workflow('enum_options_from_file')
|
||||||
|
processor = WorkflowProcessor(workflow)
|
||||||
|
processor.do_engine_steps()
|
||||||
|
task = processor.next_task()
|
||||||
|
WorkflowService._process_options(task, task.task_spec.form.fields[0])
|
||||||
|
options = task.task_spec.form.fields[0].options
|
||||||
|
self.assertEquals(19, len(options))
|
||||||
|
self.assertEquals(1000, options[0]['id'])
|
||||||
|
self.assertEquals("UVA - INTERNAL - GM USE ONLY", options[0]['name'])
|
Loading…
Reference in New Issue