Merge branch 'feature/spec_files_wthout_db' of https://github.com/sartography/cr-connect-workflow into feature/spec_files_wthout_db
This commit is contained in:
commit
b2824286f0
|
@ -10,12 +10,11 @@ import connexion
|
|||
|
||||
from crc.services.workflow_spec_service import WorkflowSpecService
|
||||
|
||||
workflow_spec_service = WorkflowSpecService()
|
||||
|
||||
def get_files(spec_id, include_libraries=False):
|
||||
if spec_id is None:
|
||||
raise ApiError(code='missing_spec_id',
|
||||
message='Please specify the workflow_spec_id.')
|
||||
workflow_spec_service = WorkflowSpecService()
|
||||
workflow_spec = workflow_spec_service.get_spec(spec_id)
|
||||
if workflow_spec is None:
|
||||
raise ApiError(code='unknown_spec',
|
||||
|
@ -26,6 +25,7 @@ def get_files(spec_id, include_libraries=False):
|
|||
|
||||
|
||||
def get_file(spec_id, file_name):
|
||||
workflow_spec_service = WorkflowSpecService()
|
||||
workflow_spec = workflow_spec_service.get_spec(spec_id)
|
||||
files = SpecFileService.get_files(workflow_spec, file_name)
|
||||
if len(files) == 0:
|
||||
|
@ -36,15 +36,17 @@ def get_file(spec_id, file_name):
|
|||
|
||||
|
||||
def add_file(spec_id):
|
||||
workflow_spec_service = WorkflowSpecService()
|
||||
workflow_spec = workflow_spec_service.get_spec(spec_id)
|
||||
file = connexion.request.files['file']
|
||||
file = SpecFileService.add_file(workflow_spec, file.filename, file.stream.read())
|
||||
if not workflow_spec.primary_process_name and file.type == FileType.bpmn:
|
||||
if not workflow_spec.primary_process_id and file.type == FileType.bpmn:
|
||||
SpecFileService.set_primary_bpmn(workflow_spec, file.name)
|
||||
return FileSchema().dump(file)
|
||||
|
||||
|
||||
def update(spec_id, file_name, is_primary):
|
||||
workflow_spec_service = WorkflowSpecService()
|
||||
workflow_spec = workflow_spec_service.get_spec(spec_id)
|
||||
files = SpecFileService.get_files(workflow_spec, file_name)
|
||||
if len(files) < 1:
|
||||
|
@ -58,6 +60,7 @@ def update(spec_id, file_name, is_primary):
|
|||
|
||||
|
||||
def update_data(spec_id, file_name):
|
||||
workflow_spec_service = WorkflowSpecService()
|
||||
workflow_spec_model = workflow_spec_service.get_spec(spec_id)
|
||||
if workflow_spec_model is None:
|
||||
raise ApiError(code='missing_spec',
|
||||
|
@ -68,6 +71,7 @@ def update_data(spec_id, file_name):
|
|||
|
||||
|
||||
def get_data(spec_id, file_name):
|
||||
workflow_spec_service = WorkflowSpecService()
|
||||
workflow_spec = workflow_spec_service.get_spec(spec_id)
|
||||
file_data = SpecFileService.get_data(workflow_spec, file_name)
|
||||
if file_data is not None:
|
||||
|
@ -85,5 +89,6 @@ def get_data(spec_id, file_name):
|
|||
|
||||
|
||||
def delete(spec_id, file_name):
|
||||
workflow_spec_service = WorkflowSpecService()
|
||||
workflow_spec = workflow_spec_service.get_spec(spec_id)
|
||||
SpecFileService.delete_file(workflow_spec, file_name)
|
||||
|
|
|
@ -12,6 +12,7 @@ from crc.models.task_log import TaskLogModelSchema, TaskLogQuery, TaskLogQuerySc
|
|||
from crc.services.study_service import StudyService
|
||||
from crc.services.task_logging_service import TaskLoggingService
|
||||
from crc.services.user_service import UserService
|
||||
from crc.services.workflow_processor import WorkflowProcessor
|
||||
from crc.services.workflow_service import WorkflowService
|
||||
from crc.services.workflow_spec_service import WorkflowSpecService
|
||||
|
||||
|
@ -34,16 +35,33 @@ def add_study(body):
|
|||
event_type=StudyEventType.user,
|
||||
user_uid=g.user.uid)
|
||||
|
||||
specs = WorkflowSpecService.get_specs()
|
||||
errors = StudyService._add_all_workflow_specs_to_study(study_model, specs)
|
||||
spec_service = WorkflowSpecService()
|
||||
specs = spec_service.get_specs()
|
||||
categories = spec_service.get_categories()
|
||||
errors = StudyService.add_all_workflow_specs_to_study(study_model, specs)
|
||||
session.commit()
|
||||
study = StudyService().get_study(study_model.id, do_status=True)
|
||||
|
||||
master_workflow_results = __run_master_spec(study_model, spec_service.master_spec)
|
||||
study = StudyService().get_study(study_model.id, categories, master_workflow_results)
|
||||
study_data = StudySchema().dump(study)
|
||||
study_data["errors"] = ApiErrorSchema(many=True).dump(errors)
|
||||
return study_data
|
||||
|
||||
|
||||
def __run_master_spec(study_model, master_spec):
|
||||
"""Runs the master workflow spec to get details on the status of each workflow.
|
||||
This is a fairly expensive call."""
|
||||
"""Uses the Top Level Workflow to calculate the status of the study, and it's
|
||||
workflow models."""
|
||||
if not master_spec:
|
||||
raise ApiError("missing_master_spec", "No specifications are currently marked as the master spec.")
|
||||
return WorkflowProcessor.run_master_spec(master_spec, study_model)
|
||||
|
||||
|
||||
def update_study(study_id, body):
|
||||
spec_service = WorkflowSpecService()
|
||||
categories = spec_service.get_categories()
|
||||
|
||||
"""Pretty limited, but allows manual modifications to the study status """
|
||||
if study_id is None:
|
||||
raise ApiError('unknown_study', 'Please provide a valid Study ID.')
|
||||
|
@ -74,12 +92,14 @@ def update_study(study_id, body):
|
|||
WorkflowService.process_workflows_for_cancels(study_id)
|
||||
|
||||
# Need to reload the full study to return it to the frontend
|
||||
study = StudyService.get_study(study_id)
|
||||
study = StudyService.get_study(study_id, categories)
|
||||
return StudySchema().dump(study)
|
||||
|
||||
|
||||
def get_study(study_id, update_status=False):
|
||||
study = StudyService.get_study(study_id, do_status=update_status)
|
||||
spec_service = WorkflowSpecService()
|
||||
categories = spec_service.get_categories()
|
||||
study = StudyService.get_study(study_id, categories=categories, do_status=update_status)
|
||||
if (study is None):
|
||||
raise ApiError("unknown_study", 'The study "' + study_id + '" is not recognized.', status_code=404)
|
||||
return StudySchema().dump(study)
|
||||
|
@ -107,11 +127,13 @@ def delete_study(study_id):
|
|||
def user_studies():
|
||||
"""Returns all the studies associated with the current user. """
|
||||
user = UserService.current_user(allow_admin_impersonate=True)
|
||||
specs = WorkflowSpecService.get_specs()
|
||||
spec_service = WorkflowSpecService()
|
||||
specs = spec_service.get_specs()
|
||||
cats = spec_service.get_categories()
|
||||
StudyService.synch_with_protocol_builder_if_enabled(user, specs)
|
||||
studies = StudyService().get_studies_for_user(user)
|
||||
studies = StudyService().get_studies_for_user(user, categories=cats)
|
||||
if len(studies) == 0:
|
||||
studies = StudyService().get_studies_for_user(user, include_invalid=True)
|
||||
studies = StudyService().get_studies_for_user(user, categories=cats, include_invalid=True)
|
||||
if len(studies) > 0:
|
||||
message = f"All studies associated with User: {user.uid} failed study validation"
|
||||
raise ApiError(code="study_integrity_error", message=message)
|
||||
|
|
|
@ -10,7 +10,7 @@ from crc.api.common import ApiErrorSchema, ApiError
|
|||
from crc.models.file import FileSchema
|
||||
from crc.models.ldap import LdapModel, LdapSchema
|
||||
from crc.models.protocol_builder import ProtocolBuilderCreatorStudy
|
||||
from crc.models.workflow import WorkflowSpecCategory, WorkflowState, WorkflowStatus, WorkflowModel
|
||||
from crc.models.workflow import WorkflowSpecCategory, WorkflowState, WorkflowStatus, WorkflowModel, WorkflowSpecInfo
|
||||
|
||||
|
||||
class StudyStatus(enum.Enum):
|
||||
|
@ -133,19 +133,19 @@ class WorkflowMetadata(object):
|
|||
|
||||
|
||||
@classmethod
|
||||
def from_workflow(cls, workflow: WorkflowModel):
|
||||
def from_workflow(cls, workflow: WorkflowModel, spec: WorkflowSpecInfo):
|
||||
instance = cls(
|
||||
id=workflow.id,
|
||||
display_name=workflow.workflow_spec.display_name,
|
||||
description=workflow.workflow_spec.description,
|
||||
category_id=workflow.workflow_spec.category_id,
|
||||
category_display_name=workflow.workflow_spec.category.display_name,
|
||||
display_name=spec.display_name,
|
||||
description=spec.description,
|
||||
category_id=spec.category_id,
|
||||
category_display_name=spec.category.display_name,
|
||||
state=WorkflowState.optional,
|
||||
status=workflow.status,
|
||||
total_tasks=workflow.total_tasks,
|
||||
completed_tasks=workflow.completed_tasks,
|
||||
is_review=workflow.workflow_spec.is_review,
|
||||
display_order=workflow.workflow_spec.display_order,
|
||||
is_review=spec.is_review,
|
||||
display_order=spec.display_order,
|
||||
workflow_spec_id=workflow.workflow_spec_id
|
||||
)
|
||||
return instance
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import enum
|
||||
|
||||
import marshmallow
|
||||
from marshmallow import EXCLUDE, post_load, fields
|
||||
from sqlalchemy import func
|
||||
|
||||
|
@ -12,7 +13,8 @@ class WorkflowSpecCategory(object):
|
|||
self.display_name = display_name
|
||||
self.display_order = display_order
|
||||
self.admin = admin
|
||||
self.workflows = []
|
||||
self.workflows = [] # For storing Workflow Metadata
|
||||
self.specs = [] # For the list of specifications associated with a category
|
||||
|
||||
class WorkflowSpecCategorySchema(ma.Schema):
|
||||
class Meta:
|
||||
|
@ -48,6 +50,7 @@ class WorkflowSpecInfoSchema(ma.Schema):
|
|||
"standalone", "library", "primary_file_name", "primary_process_id", "is_review",
|
||||
"libraries", "display_order", "is_master_spec", "is_review", "category_id"]
|
||||
unknown = EXCLUDE
|
||||
category_id = marshmallow.fields.String(required=False, allow_none=True, missing="", default="")
|
||||
|
||||
@post_load
|
||||
def make_spec(self, data, **kwargs):
|
||||
|
|
|
@ -2,6 +2,7 @@ from crc.scripts.script import Script
|
|||
from crc.api.common import ApiError
|
||||
from crc.services.protocol_builder import ProtocolBuilderService
|
||||
from crc.services.study_service import StudyService
|
||||
from crc.services.workflow_spec_service import WorkflowSpecService
|
||||
|
||||
|
||||
class CheckStudy(Script):
|
||||
|
@ -12,7 +13,9 @@ class CheckStudy(Script):
|
|||
return """Returns the Check Study data for a Study"""
|
||||
|
||||
def do_task_validate_only(self, task, study_id, workflow_id, *args, **kwargs):
|
||||
study = StudyService.get_study(study_id)
|
||||
spec_service = WorkflowSpecService()
|
||||
categories = spec_service.get_categories()
|
||||
study = StudyService.get_study(study_id, categories)
|
||||
if study:
|
||||
return {"DETAIL": "Passed validation.", "STATUS": "No Error"}
|
||||
else:
|
||||
|
|
|
@ -32,9 +32,8 @@ class FileSystemService(object):
|
|||
def category_path(name: str):
|
||||
return os.path.join(FileSystemService.root_path(), name)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def workflow_path(spec: WorkflowSpecInfo):
|
||||
def category_path_for_spec(spec):
|
||||
if spec.is_master_spec:
|
||||
return os.path.join(FileSystemService.root_path(), FileSystemService.MASTER_SPECIFICATION)
|
||||
elif spec.library:
|
||||
|
@ -43,10 +42,14 @@ class FileSystemService(object):
|
|||
category_path = FileSystemService.category_path(FileSystemService.STAND_ALONE_SPECS)
|
||||
else:
|
||||
category_path = FileSystemService.category_path(spec.category_id)
|
||||
return category_path
|
||||
@staticmethod
|
||||
def workflow_path(spec: WorkflowSpecInfo):
|
||||
category_path = FileSystemService.category_path_for_spec(spec)
|
||||
return os.path.join(category_path, spec.display_name)
|
||||
|
||||
def next_display_order(self, spec):
|
||||
path = self.category_path(spec.category_id)
|
||||
path = self.category_path_for_spec(spec)
|
||||
if os.path.exists(path):
|
||||
return len(next(os.walk(path))[1])
|
||||
else:
|
||||
|
|
|
@ -13,14 +13,13 @@ from sqlalchemy.exc import IntegrityError
|
|||
|
||||
class ReferenceFileService(FileSystemService):
|
||||
|
||||
SUB_DIR = "Reference"
|
||||
|
||||
@staticmethod
|
||||
def root_path():
|
||||
# fixme: allow absolute directory names (but support relative)
|
||||
dir_name = app.config['SYNC_FILE_ROOT']
|
||||
app_root = app.root_path
|
||||
return os.path.join(app_root, '..', dir_name, ReferenceFileService.SUB_DIR)
|
||||
return os.path.join(app_root, '..', dir_name, ReferenceFileService.REFERENCE_FILES)
|
||||
|
||||
@staticmethod
|
||||
def file_path(file_name: str):
|
||||
|
|
|
@ -47,7 +47,7 @@ class StudyService(object):
|
|||
return True
|
||||
return False
|
||||
|
||||
def get_studies_for_user(self, user, include_invalid=False):
|
||||
def get_studies_for_user(self, user, categories, include_invalid=False):
|
||||
"""Returns a list of all studies for the given user."""
|
||||
associated = session.query(StudyAssociated).filter_by(uid=user.uid, access=True).all()
|
||||
associated_studies = [x.study_id for x in associated]
|
||||
|
@ -57,7 +57,7 @@ class StudyService(object):
|
|||
studies = []
|
||||
for study_model in db_studies:
|
||||
if include_invalid or self._is_valid_study(study_model.id):
|
||||
studies.append(StudyService.get_study(study_model.id, study_model, do_status=False))
|
||||
studies.append(StudyService.get_study(study_model.id, categories, study_model))
|
||||
return studies
|
||||
|
||||
@staticmethod
|
||||
|
@ -72,13 +72,13 @@ class StudyService(object):
|
|||
return studies
|
||||
|
||||
@staticmethod
|
||||
def get_study(study_id, study_model: StudyModel = None, do_status=False):
|
||||
def get_study(study_id, categories: List[WorkflowSpecCategory], study_model: StudyModel = None,
|
||||
master_workflow_results=None):
|
||||
"""Returns a study model that contains all the workflows organized by category.
|
||||
IMPORTANT: This is intended to be a lightweight call, it should never involve
|
||||
loading up and executing all the workflows in a study to calculate information."""
|
||||
Pass in the results of the master workflow spec, and the status of other workflows will be updated."""
|
||||
|
||||
if not study_model:
|
||||
study_model = session.query(StudyModel).filter_by(id=study_id).first()
|
||||
|
||||
study = Study.from_model(study_model)
|
||||
study.create_user_display = LdapService.user_info(study.user_uid).display_name
|
||||
last_event: TaskEventModel = session.query(TaskEventModel) \
|
||||
|
@ -90,29 +90,33 @@ class StudyService(object):
|
|||
else:
|
||||
study.last_activity_user = LdapService.user_info(last_event.user_uid).display_name
|
||||
study.last_activity_date = last_event.date
|
||||
study.categories = StudyService.get_categories()
|
||||
workflow_metas = StudyService._get_workflow_metas(study_id)
|
||||
study.categories = categories
|
||||
files = UserFileService.get_files_for_study(study.id)
|
||||
files = (File.from_models(model, UserFileService.get_file_data(model.id),
|
||||
DocumentService.get_dictionary()) for model in files)
|
||||
study.files = list(files)
|
||||
# Calling this line repeatedly is very very slow. It creates the
|
||||
# master spec and runs it. Don't execute this for Abandoned studies, as
|
||||
# we don't have the information to process them.
|
||||
if study.status != StudyStatus.abandoned:
|
||||
# this line is taking 99% of the time that is used in get_study.
|
||||
# see ticket #196
|
||||
if do_status:
|
||||
# __get_study_status() runs the master workflow to generate the status dictionary
|
||||
status = StudyService._get_study_status(study_model)
|
||||
study.warnings = StudyService._update_status_of_workflow_meta(workflow_metas, status)
|
||||
|
||||
# Group the workflows into their categories.
|
||||
for category in study.categories:
|
||||
category.workflows = {w for w in workflow_metas if w.category_id == category.id}
|
||||
|
||||
workflow_metas = StudyService._get_workflow_metas(study_id, category)
|
||||
if master_workflow_results:
|
||||
study.warnings = StudyService._update_status_of_workflow_meta(workflow_metas,
|
||||
master_workflow_results)
|
||||
category.workflows = workflow_metas
|
||||
return study
|
||||
|
||||
@staticmethod
|
||||
def _get_workflow_metas(study_id, category):
|
||||
# Add in the Workflows for each category
|
||||
workflow_metas = []
|
||||
for spec in category.specs:
|
||||
workflow_models = db.session.query(WorkflowModel).\
|
||||
filter(WorkflowModel.study_id == study_id).\
|
||||
filter(WorkflowModel.workflow_spec_id == spec.id).\
|
||||
all()
|
||||
for workflow in workflow_models:
|
||||
workflow_metas.append(WorkflowMetadata.from_workflow(workflow, spec))
|
||||
return workflow_metas
|
||||
|
||||
@staticmethod
|
||||
def get_study_associate(study_id=None, uid=None):
|
||||
"""
|
||||
|
@ -462,31 +466,13 @@ class StudyService(object):
|
|||
return warnings
|
||||
|
||||
@staticmethod
|
||||
def _get_study_status(study_model):
|
||||
# Fixme: This belongs elsewhere. Not in the Study Service.
|
||||
"""Uses the Top Level Workflow to calculate the status of the study, and it's
|
||||
workflow models."""
|
||||
# master_specs = db.session.query(WorkflowSpecModel). \
|
||||
# filter_by(is_master_spec=True).all()
|
||||
# if len(master_specs) < 1:
|
||||
# raise ApiError("missing_master_spec", "No specifications are currently marked as the master spec.")
|
||||
# if len(master_specs) > 1:
|
||||
# raise ApiError("multiple_master_specs",
|
||||
# "There is more than one master specification, and I don't know what to do.")
|
||||
#
|
||||
# return WorkflowProcessor.run_master_spec(master_specs[0], study_model)
|
||||
|
||||
@staticmethod
|
||||
def _add_all_workflow_specs_to_study(study_model: StudyModel, specs: List[WorkflowSpecInfo]):
|
||||
def add_all_workflow_specs_to_study(study_model: StudyModel, specs: List[WorkflowSpecInfo]):
|
||||
existing_models = session.query(WorkflowModel).filter(WorkflowModel.study == study_model).all()
|
||||
# existing_specs = list(m.workflow_spec_id for m in existing_models)
|
||||
# new_specs = session.query(WorkflowSpecModel). \
|
||||
# filter(WorkflowSpecModel.is_master_spec == False). \
|
||||
# filter(WorkflowSpecModel.id.notin_(existing_specs)). \
|
||||
# all()
|
||||
existing_spec_ids = map(lambda x: x.workflow_spec_id, existing_models)
|
||||
errors = []
|
||||
for workflow_spec in specs:
|
||||
# fixme: don't create a new workflow model for any existing models.
|
||||
if workflow_spec.id in existing_spec_ids:
|
||||
continue
|
||||
try:
|
||||
StudyService._create_workflow_model(study_model, workflow_spec)
|
||||
except WorkflowTaskExecException as wtee:
|
||||
|
|
|
@ -99,13 +99,12 @@ class WorkflowProcessor(object):
|
|||
WORKFLOW_ID_KEY = "workflow_id"
|
||||
STUDY_ID_KEY = "study_id"
|
||||
VALIDATION_PROCESS_KEY = "validate_only"
|
||||
workflow_spec_service = WorkflowSpecService()
|
||||
|
||||
def __init__(self, workflow_model: WorkflowModel, validate_only=False):
|
||||
"""Create a Workflow Processor based on the serialized information available in the workflow model."""
|
||||
|
||||
self.workflow_model = workflow_model
|
||||
|
||||
self.workflow_spec_service = WorkflowSpecService()
|
||||
spec = None
|
||||
if workflow_model.bpmn_workflow_json is None:
|
||||
self.workflow_spec_service.scan_file_system()
|
||||
|
|
|
@ -60,33 +60,33 @@ class WorkflowSpecService(FileSystemService):
|
|||
return list(self.specs.values())
|
||||
|
||||
def reorder_spec(self, spec:WorkflowSpecInfo, direction):
|
||||
workflows = spec.category.workflows
|
||||
workflows.sort(key=lambda w: w.display_order)
|
||||
index = workflows.index(spec)
|
||||
specs = spec.category.specs
|
||||
specs.sort(key=lambda w: w.display_order)
|
||||
index = specs.index(spec)
|
||||
if direction == 'up' and index > 0:
|
||||
workflows[index-1], workflows[index] = workflows[index], workflows[index-1]
|
||||
if direction == 'down' and index < len(workflows)-1:
|
||||
workflows[index+1], workflows[index] = workflows[index], workflows[index+1]
|
||||
return self.cleanup_workflow_spec_display_order(spec.category.id)
|
||||
specs[index-1], specs[index] = specs[index], specs[index-1]
|
||||
if direction == 'down' and index < len(specs)-1:
|
||||
specs[index+1], specs[index] = specs[index], specs[index+1]
|
||||
return self.cleanup_workflow_spec_display_order(spec.category_id)
|
||||
|
||||
def cleanup_workflow_spec_display_order(self, category_id):
|
||||
index = 0
|
||||
category = self.get_category(category_id)
|
||||
if not category:
|
||||
return []
|
||||
for workflow in category.workflows:
|
||||
for workflow in category.specs:
|
||||
workflow.display_order = index
|
||||
self.update_spec(workflow)
|
||||
index += 1
|
||||
return category.workflows
|
||||
return category.specs
|
||||
|
||||
def get_libraries(self) -> List[WorkflowSpecInfo]:
|
||||
spec_list = self.libraries.workflows
|
||||
spec_list = self.libraries.specs
|
||||
spec_list.sort(key=lambda w: w.display_order)
|
||||
return spec_list
|
||||
|
||||
def get_standalones(self) -> List[WorkflowSpecInfo]:
|
||||
spec_list = list(self.standalone.workflows)
|
||||
spec_list = list(self.standalone.specs)
|
||||
spec_list.sort(key=lambda w: w.display_order)
|
||||
return spec_list
|
||||
|
||||
|
@ -105,7 +105,7 @@ class WorkflowSpecService(FileSystemService):
|
|||
return self.update_category(category)
|
||||
|
||||
def update_category(self, category: WorkflowSpecCategory, rescan=True):
|
||||
cat_path = self.category_path(category.display_name)
|
||||
cat_path = self.category_path(category.id)
|
||||
os.makedirs(cat_path, exist_ok=True)
|
||||
json_path = os.path.join(cat_path, self.CAT_JSON_FILE)
|
||||
with open(json_path, "w") as cat_json:
|
||||
|
@ -161,7 +161,9 @@ class WorkflowSpecService(FileSystemService):
|
|||
directory_items = os.scandir(FileSystemService.root_path())
|
||||
for item in directory_items:
|
||||
if item.is_dir():
|
||||
if item.name == self.LIBRARY_SPECS:
|
||||
if item.name == self.REFERENCE_FILES:
|
||||
continue
|
||||
elif item.name == self.LIBRARY_SPECS:
|
||||
self.scan_category(item, is_library=True)
|
||||
elif item.name == self.STAND_ALONE_SPECS:
|
||||
self.scan_category(item, is_standalone=True)
|
||||
|
@ -191,7 +193,7 @@ class WorkflowSpecService(FileSystemService):
|
|||
for item in workflow_dirs:
|
||||
if item.is_dir():
|
||||
self.scan_spec(item, category=cat)
|
||||
cat.workflows.sort(key=lambda w: w.display_order)
|
||||
cat.specs.sort(key=lambda w: w.display_order)
|
||||
return cat
|
||||
|
||||
@staticmethod
|
||||
|
@ -222,7 +224,7 @@ class WorkflowSpecService(FileSystemService):
|
|||
self.master_spec = spec
|
||||
elif category:
|
||||
spec.category = category
|
||||
category.workflows.append(spec)
|
||||
category.specs.append(spec)
|
||||
self.specs[spec.id] = spec
|
||||
|
||||
|
||||
|
|
|
@ -13,8 +13,6 @@ from crc.services.workflow_spec_service import WorkflowSpecService
|
|||
|
||||
class ExampleDataLoader:
|
||||
|
||||
workflow_spec_service = WorkflowSpecService()
|
||||
|
||||
@staticmethod
|
||||
def clean_db():
|
||||
session.flush() # Clear out any transactions before deleting it all to avoid spurious errors.
|
||||
|
@ -27,7 +25,7 @@ class ExampleDataLoader:
|
|||
session.flush()
|
||||
|
||||
def create_spec(self, id, display_name="", description="", filepath=None, master_spec=False,
|
||||
category_id=None, display_order=0, from_tests=False, standalone=False, library=False):
|
||||
category_id='', display_order=0, from_tests=False, standalone=False, library=False):
|
||||
"""Assumes that a directory exists in static/bpmn with the same name as the given id.
|
||||
further assumes that the [id].bpmn is the primary file for the workflow.
|
||||
returns an array of data models to be added to the database."""
|
||||
|
@ -44,7 +42,8 @@ class ExampleDataLoader:
|
|||
primary_process_id="",
|
||||
is_review=False,
|
||||
libraries=[])
|
||||
self.workflow_spec_service.add_spec(spec)
|
||||
workflow_spec_service = WorkflowSpecService()
|
||||
workflow_spec_service.add_spec(spec)
|
||||
|
||||
if not filepath and not from_tests:
|
||||
filepath = os.path.join(app.root_path, 'static', 'bpmn', id, "*.*")
|
||||
|
@ -67,7 +66,8 @@ class ExampleDataLoader:
|
|||
SpecFileService.add_file(workflow_spec=spec, file_name=filename, binary_data=data)
|
||||
if is_primary:
|
||||
SpecFileService.set_primary_bpmn(spec, filename, data)
|
||||
self.workflow_spec_service.update_spec(spec)
|
||||
workflow_spec_service = WorkflowSpecService()
|
||||
workflow_spec_service.update_spec(spec)
|
||||
except IsADirectoryError as de:
|
||||
# Ignore sub directories
|
||||
pass
|
||||
|
|
|
@ -208,13 +208,15 @@ class BaseTest(unittest.TestCase):
|
|||
category = self.workflow_spec_service.get_category(category_id)
|
||||
if category is None:
|
||||
category = WorkflowSpecCategory(id="test_category", display_name="Test Workflows", admin=False, display_order=0)
|
||||
self.workflow_spec_service.add_category(category)
|
||||
return category
|
||||
|
||||
def load_test_spec(self, dir_name, display_name=None, master_spec=False, category_id=None, library=False):
|
||||
"""Loads a spec into the database based on a directory in /tests/data"""
|
||||
category = BaseTest.assure_category_exists(category_id)
|
||||
category_id = category.id
|
||||
|
||||
category = None
|
||||
if not master_spec and not library:
|
||||
category = self.assure_category_exists(category_id)
|
||||
category_id = category.id
|
||||
workflow_spec = self.workflow_spec_service.get_spec(dir_name)
|
||||
if workflow_spec:
|
||||
return workflow_spec
|
||||
|
|
|
@ -2,55 +2,38 @@ import json
|
|||
from datetime import datetime
|
||||
from unittest.mock import patch
|
||||
|
||||
from crc.services.user_file_service import UserFileService
|
||||
from tests.base_test import BaseTest
|
||||
|
||||
from crc import db, app
|
||||
from crc.models.study import StudyModel, StudyStatus, StudyAssociatedSchema
|
||||
from crc.models.user import UserModel
|
||||
from crc.models.workflow import WorkflowModel, WorkflowStatus
|
||||
from crc.models.workflow import WorkflowModel, WorkflowStatus, WorkflowSpecCategory
|
||||
from crc.services.ldap_service import LdapService
|
||||
from crc.services.study_service import StudyService
|
||||
from crc.services.workflow_processor import WorkflowProcessor
|
||||
from crc.services.user_file_service import UserFileService
|
||||
from crc.services.user_service import UserService
|
||||
from crc.services.workflow_spec_service import WorkflowSpecService
|
||||
|
||||
|
||||
class TestStudyService(BaseTest):
|
||||
"""Largely tested via the test_study_api, and time is tight, but adding new tests here."""
|
||||
|
||||
def create_user_with_study_and_workflow(self):
|
||||
|
||||
# clear it all out.
|
||||
from example_data import ExampleDataLoader
|
||||
ExampleDataLoader.clean_db()
|
||||
|
||||
# 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.
|
||||
cat = WorkflowSpecCategoryModel(id=None, display_name="Approvals", display_order=0)
|
||||
db.session.add(cat)
|
||||
db.session.commit()
|
||||
self.load_test_spec("top_level_workflow", master_spec=True, category_id=cat.id)
|
||||
user = db.session.query(UserModel).filter(UserModel.uid == "dhf8r").first()
|
||||
if not user:
|
||||
ldap = LdapService.user_info('dhf8r')
|
||||
user = UserModel(uid="dhf8r", ldap_info=ldap)
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
else:
|
||||
for study in db.session.query(StudyModel).all():
|
||||
StudyService().delete_study(study.id)
|
||||
|
||||
self.load_test_spec("top_level_workflow", master_spec=True)
|
||||
cat = WorkflowSpecCategory(id="approvals", display_name="Approvals", display_order=0, admin=False)
|
||||
self.workflow_spec_service.add_category(cat)
|
||||
self.load_test_spec("random_fact", category_id=cat.id)
|
||||
self.workflow_spec_service.scan_file_system()
|
||||
user = self.create_user()
|
||||
study = StudyModel(title="My title", status=StudyStatus.in_progress, user_uid=user.uid)
|
||||
db.session.add(study)
|
||||
|
||||
self.load_test_spec("random_fact", category_id=cat.id)
|
||||
|
||||
db.session.commit()
|
||||
self.assertIsNotNone(study.id)
|
||||
workflow = WorkflowModel(workflow_spec_id="random_fact", study_id=study.id,
|
||||
status=WorkflowStatus.not_started, last_updated=datetime.utcnow())
|
||||
db.session.add(workflow)
|
||||
db.session.commit()
|
||||
# Assure there is a master specification, one standard spec, and lookup tables.
|
||||
ExampleDataLoader().load_reference_documents()
|
||||
return user
|
||||
|
||||
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_study_details') # mock_details
|
||||
|
@ -66,10 +49,12 @@ class TestStudyService(BaseTest):
|
|||
user = self.create_user_with_study_and_workflow()
|
||||
|
||||
# The load example data script should set us up a user and at least one study, one category, and one workflow.
|
||||
studies = StudyService().get_studies_for_user(user)
|
||||
spec_service = WorkflowSpecService()
|
||||
categories = spec_service.get_categories()
|
||||
studies = StudyService().get_studies_for_user(user, categories)
|
||||
self.assertTrue(len(studies) == 1)
|
||||
self.assertTrue(len(studies[0].categories) == 1)
|
||||
self.assertTrue(len(studies[0].categories[0].workflows) == 1)
|
||||
self.assertEqual(1, len(studies[0].categories[0].workflows))
|
||||
|
||||
workflow = next(iter(studies[0].categories[0].workflows)) # Workflows is a set.
|
||||
|
||||
|
@ -84,7 +69,9 @@ class TestStudyService(BaseTest):
|
|||
processor.do_engine_steps()
|
||||
|
||||
# Assure the workflow is now started, and knows the total and completed tasks.
|
||||
studies = StudyService().get_studies_for_user(user)
|
||||
spec_service = WorkflowSpecService()
|
||||
categories = spec_service.get_categories()
|
||||
studies = StudyService().get_studies_for_user(user, categories)
|
||||
workflow = next(iter(studies[0].categories[0].workflows)) # Workflows is a set.
|
||||
# self.assertEqual(WorkflowStatus.user_input_required, workflow.status)
|
||||
self.assertTrue(workflow.total_tasks > 0)
|
||||
|
@ -96,7 +83,7 @@ class TestStudyService(BaseTest):
|
|||
processor.save()
|
||||
|
||||
# Assure the workflow has moved on to the next task.
|
||||
studies = StudyService().get_studies_for_user(user)
|
||||
studies = StudyService().get_studies_for_user(user, spec_service.get_categories())
|
||||
workflow = next(iter(studies[0].categories[0].workflows)) # Workflows is a set.
|
||||
self.assertEqual(1, workflow.completed_tasks)
|
||||
|
||||
|
@ -105,6 +92,7 @@ class TestStudyService(BaseTest):
|
|||
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_study_details') # mock_details
|
||||
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_required_docs') # mock_docs
|
||||
def test_get_required_docs(self, mock_docs, mock_details):
|
||||
self.load_example_data()
|
||||
app.config['PB_ENABLED'] = True
|
||||
# mock out the protocol builder
|
||||
docs_response = self.protocol_builder_response('required_docs.json')
|
||||
|
@ -113,7 +101,9 @@ class TestStudyService(BaseTest):
|
|||
mock_details.return_value = json.loads(details_response)
|
||||
|
||||
user = self.create_user_with_study_and_workflow()
|
||||
studies = StudyService().get_studies_for_user(user)
|
||||
spec_service = WorkflowSpecService()
|
||||
categories = spec_service.get_categories()
|
||||
studies = StudyService().get_studies_for_user(user, categories)
|
||||
study = studies[0]
|
||||
|
||||
|
||||
|
@ -134,7 +124,7 @@ class TestStudyService(BaseTest):
|
|||
|
||||
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_required_docs') # mock_docs
|
||||
def test_get_documents_has_file_details(self, mock_docs):
|
||||
|
||||
self.load_example_data()
|
||||
# mock out the protocol builder
|
||||
docs_response = self.protocol_builder_response('required_docs.json')
|
||||
mock_docs.return_value = json.loads(docs_response)
|
||||
|
@ -242,7 +232,9 @@ class TestStudyService(BaseTest):
|
|||
mock_details.return_value = json.loads(details_response)
|
||||
|
||||
user = self.create_user_with_study_and_workflow()
|
||||
studies = StudyService().get_studies_for_user(user)
|
||||
spec_service = WorkflowSpecService()
|
||||
categories = spec_service.get_categories()
|
||||
studies = StudyService().get_studies_for_user(user, categories)
|
||||
# study_details has a valid REVIEW_TYPE, so we should get 1 study back
|
||||
self.assertEqual(1, len(studies))
|
||||
|
||||
|
@ -251,8 +243,10 @@ class TestStudyService(BaseTest):
|
|||
details_response = self.protocol_builder_response('study_details_bad_review_type.json')
|
||||
mock_details.return_value = json.loads(details_response)
|
||||
|
||||
spec_service = WorkflowSpecService()
|
||||
categories = spec_service.get_categories()
|
||||
user = self.create_user_with_study_and_workflow()
|
||||
studies = StudyService().get_studies_for_user(user)
|
||||
studies = StudyService().get_studies_for_user(user, categories)
|
||||
# study_details has an invalid REVIEW_TYPE, so we should get 0 studies back
|
||||
self.assertEqual(0, len(studies))
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from crc.services.workflow_spec_service import WorkflowSpecService
|
||||
from tests.base_test import BaseTest
|
||||
from crc import db, session
|
||||
from crc.models.study import StudyModel
|
||||
|
@ -15,7 +16,8 @@ class TestStudyStatusMessage(BaseTest):
|
|||
self.load_example_data()
|
||||
study_model = session.query(StudyModel).first()
|
||||
self.create_workflow('random_fact', study=study_model)
|
||||
workflow_metas = StudyService._get_workflow_metas(study_model.id)
|
||||
spec_service = WorkflowSpecService()
|
||||
workflow_metas = StudyService._get_workflow_metas(study_model.id, spec_service.categories)
|
||||
warnings = StudyService._update_status_of_workflow_meta(workflow_metas, status)
|
||||
return workflow_metas, warnings
|
||||
|
||||
|
|
Loading…
Reference in New Issue