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:
mike cullerton 2022-02-09 08:52:46 -05:00
commit b2824286f0
14 changed files with 151 additions and 131 deletions

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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):

View File

@ -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:

View File

@ -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:

View File

@ -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):

View File

@ -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:

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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))

View File

@ -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