2020-05-04 14:57:09 +00:00
|
|
|
from datetime import datetime
|
2020-04-29 14:21:24 +00:00
|
|
|
import json
|
Created a "StudyService" and moved all complex logic around study manipulation out of the study api, and this service, as things were getting complicated. The Workflow Processor no longer creates the WorkflowModel, the study object handles that, and only passes the model into the workflow processor when it is ready to start the workflow.
Created a Study object (seperate from the StudyModel) that can cronstructed on request, and contains a different data structure than we store in the DB. This allows us to return underlying Categories and Workflows in a clean way.
Added a new status to workflows called "not_started", meaning we have not yet instantiated a processor or created a BPMN, they have no version yet and no stored data, just the possiblity of being started.
The Top Level Workflow or "Master" workflow is now a part of the sample data, and loaded at all times.
Removed the ability to "add a workflow to a study" and "remove a workflow from a study", a study contains all possible workflows by definition.
Example data no longer creates users or studies, it just creates the specs.
2020-03-30 12:00:16 +00:00
|
|
|
from typing import List
|
|
|
|
|
2020-05-11 21:04:05 +00:00
|
|
|
import requests
|
Created a "StudyService" and moved all complex logic around study manipulation out of the study api, and this service, as things were getting complicated. The Workflow Processor no longer creates the WorkflowModel, the study object handles that, and only passes the model into the workflow processor when it is ready to start the workflow.
Created a Study object (seperate from the StudyModel) that can cronstructed on request, and contains a different data structure than we store in the DB. This allows us to return underlying Categories and Workflows in a clean way.
Added a new status to workflows called "not_started", meaning we have not yet instantiated a processor or created a BPMN, they have no version yet and no stored data, just the possiblity of being started.
The Top Level Workflow or "Master" workflow is now a part of the sample data, and loaded at all times.
Removed the ability to "add a workflow to a study" and "remove a workflow from a study", a study contains all possible workflows by definition.
Example data no longer creates users or studies, it just creates the specs.
2020-03-30 12:00:16 +00:00
|
|
|
from SpiffWorkflow import WorkflowException
|
2020-05-07 17:57:24 +00:00
|
|
|
from ldap3.core.exceptions import LDAPSocketOpenError
|
Created a "StudyService" and moved all complex logic around study manipulation out of the study api, and this service, as things were getting complicated. The Workflow Processor no longer creates the WorkflowModel, the study object handles that, and only passes the model into the workflow processor when it is ready to start the workflow.
Created a Study object (seperate from the StudyModel) that can cronstructed on request, and contains a different data structure than we store in the DB. This allows us to return underlying Categories and Workflows in a clean way.
Added a new status to workflows called "not_started", meaning we have not yet instantiated a processor or created a BPMN, they have no version yet and no stored data, just the possiblity of being started.
The Top Level Workflow or "Master" workflow is now a part of the sample data, and loaded at all times.
Removed the ability to "add a workflow to a study" and "remove a workflow from a study", a study contains all possible workflows by definition.
Example data no longer creates users or studies, it just creates the specs.
2020-03-30 12:00:16 +00:00
|
|
|
|
2020-05-07 17:57:24 +00:00
|
|
|
from crc import db, session, app
|
Created a "StudyService" and moved all complex logic around study manipulation out of the study api, and this service, as things were getting complicated. The Workflow Processor no longer creates the WorkflowModel, the study object handles that, and only passes the model into the workflow processor when it is ready to start the workflow.
Created a Study object (seperate from the StudyModel) that can cronstructed on request, and contains a different data structure than we store in the DB. This allows us to return underlying Categories and Workflows in a clean way.
Added a new status to workflows called "not_started", meaning we have not yet instantiated a processor or created a BPMN, they have no version yet and no stored data, just the possiblity of being started.
The Top Level Workflow or "Master" workflow is now a part of the sample data, and loaded at all times.
Removed the ability to "add a workflow to a study" and "remove a workflow from a study", a study contains all possible workflows by definition.
Example data no longer creates users or studies, it just creates the specs.
2020-03-30 12:00:16 +00:00
|
|
|
from crc.api.common import ApiError
|
2020-04-29 14:21:24 +00:00
|
|
|
from crc.models.file import FileModel, FileModelSchema
|
Created a "StudyService" and moved all complex logic around study manipulation out of the study api, and this service, as things were getting complicated. The Workflow Processor no longer creates the WorkflowModel, the study object handles that, and only passes the model into the workflow processor when it is ready to start the workflow.
Created a Study object (seperate from the StudyModel) that can cronstructed on request, and contains a different data structure than we store in the DB. This allows us to return underlying Categories and Workflows in a clean way.
Added a new status to workflows called "not_started", meaning we have not yet instantiated a processor or created a BPMN, they have no version yet and no stored data, just the possiblity of being started.
The Top Level Workflow or "Master" workflow is now a part of the sample data, and loaded at all times.
Removed the ability to "add a workflow to a study" and "remove a workflow from a study", a study contains all possible workflows by definition.
Example data no longer creates users or studies, it just creates the specs.
2020-03-30 12:00:16 +00:00
|
|
|
from crc.models.protocol_builder import ProtocolBuilderStudy, ProtocolBuilderStatus
|
2020-05-04 14:57:09 +00:00
|
|
|
from crc.models.stats import TaskEventModel
|
Created a "StudyService" and moved all complex logic around study manipulation out of the study api, and this service, as things were getting complicated. The Workflow Processor no longer creates the WorkflowModel, the study object handles that, and only passes the model into the workflow processor when it is ready to start the workflow.
Created a Study object (seperate from the StudyModel) that can cronstructed on request, and contains a different data structure than we store in the DB. This allows us to return underlying Categories and Workflows in a clean way.
Added a new status to workflows called "not_started", meaning we have not yet instantiated a processor or created a BPMN, they have no version yet and no stored data, just the possiblity of being started.
The Top Level Workflow or "Master" workflow is now a part of the sample data, and loaded at all times.
Removed the ability to "add a workflow to a study" and "remove a workflow from a study", a study contains all possible workflows by definition.
Example data no longer creates users or studies, it just creates the specs.
2020-03-30 12:00:16 +00:00
|
|
|
from crc.models.study import StudyModel, Study, Category, WorkflowMetadata
|
|
|
|
from crc.models.workflow import WorkflowSpecCategoryModel, WorkflowModel, WorkflowSpecModel, WorkflowState, \
|
|
|
|
WorkflowStatus
|
2020-04-23 23:25:01 +00:00
|
|
|
from crc.services.file_service import FileService
|
2020-05-07 17:57:24 +00:00
|
|
|
from crc.services.ldap_service import LdapService
|
Created a "StudyService" and moved all complex logic around study manipulation out of the study api, and this service, as things were getting complicated. The Workflow Processor no longer creates the WorkflowModel, the study object handles that, and only passes the model into the workflow processor when it is ready to start the workflow.
Created a Study object (seperate from the StudyModel) that can cronstructed on request, and contains a different data structure than we store in the DB. This allows us to return underlying Categories and Workflows in a clean way.
Added a new status to workflows called "not_started", meaning we have not yet instantiated a processor or created a BPMN, they have no version yet and no stored data, just the possiblity of being started.
The Top Level Workflow or "Master" workflow is now a part of the sample data, and loaded at all times.
Removed the ability to "add a workflow to a study" and "remove a workflow from a study", a study contains all possible workflows by definition.
Example data no longer creates users or studies, it just creates the specs.
2020-03-30 12:00:16 +00:00
|
|
|
from crc.services.protocol_builder import ProtocolBuilderService
|
|
|
|
from crc.services.workflow_processor import WorkflowProcessor
|
|
|
|
|
|
|
|
|
|
|
|
class StudyService(object):
|
|
|
|
"""Provides common tools for working with a Study"""
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def get_studies_for_user(user):
|
|
|
|
"""Returns a list of all studies for the given user."""
|
|
|
|
db_studies = session.query(StudyModel).filter_by(user_uid=user.uid).all()
|
|
|
|
studies = []
|
|
|
|
for study_model in db_studies:
|
|
|
|
studies.append(StudyService.get_study(study_model.id, study_model))
|
|
|
|
return studies
|
|
|
|
|
2020-05-22 15:46:03 +00:00
|
|
|
@staticmethod
|
|
|
|
def get_studies_with_files():
|
|
|
|
"""Returns a list of all studies"""
|
|
|
|
db_studies = session.query(StudyModel).all()
|
|
|
|
return db_studies
|
|
|
|
|
Created a "StudyService" and moved all complex logic around study manipulation out of the study api, and this service, as things were getting complicated. The Workflow Processor no longer creates the WorkflowModel, the study object handles that, and only passes the model into the workflow processor when it is ready to start the workflow.
Created a Study object (seperate from the StudyModel) that can cronstructed on request, and contains a different data structure than we store in the DB. This allows us to return underlying Categories and Workflows in a clean way.
Added a new status to workflows called "not_started", meaning we have not yet instantiated a processor or created a BPMN, they have no version yet and no stored data, just the possiblity of being started.
The Top Level Workflow or "Master" workflow is now a part of the sample data, and loaded at all times.
Removed the ability to "add a workflow to a study" and "remove a workflow from a study", a study contains all possible workflows by definition.
Example data no longer creates users or studies, it just creates the specs.
2020-03-30 12:00:16 +00:00
|
|
|
@staticmethod
|
|
|
|
def get_study(study_id, study_model: StudyModel = 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."""
|
|
|
|
if not study_model:
|
|
|
|
study_model = session.query(StudyModel).filter_by(id=study_id).first()
|
|
|
|
study = Study.from_model(study_model)
|
|
|
|
study.categories = StudyService.get_categories()
|
|
|
|
workflow_metas = StudyService.__get_workflow_metas(study_id)
|
Refactor the document details scripts. Now there is one script, it returns data in a consistent format, and has all the details required. The script is located in StudyInfo, with the argument documents. Make note that it returns a dictionary of ALL the documents, with a field to mark which ones are required according to the protocol builder. Others may become required if a workflow determines such, in which case the workflow will enforce this, and the document will have a count > 0, and additional details in a list of files within the document. I modified the XLS file to use lower case variable names, because it disturbed me, and we have to reference them frequently. Removed devious "as_object" variable on get_required_docs, so it behaves like the other methods all the time, and returns a dictionary. All the core business logic for finding the documents list now resides in the StudyService.
Because this changes the endpoint for all existing document details, I've modified all the test and static bpmn files to use the new format.
Shorting up the SponsorsList.xls file makes for slightly faster tests. seems senseless to load 5000 everytime we reset the data.
Tried to test all of this carefully in the test_study_details_documents.py test.
2020-04-29 19:08:11 +00:00
|
|
|
|
|
|
|
# Calling this line repeatedly is very very slow. It creates the
|
|
|
|
# master spec and runs it.
|
Created a "StudyService" and moved all complex logic around study manipulation out of the study api, and this service, as things were getting complicated. The Workflow Processor no longer creates the WorkflowModel, the study object handles that, and only passes the model into the workflow processor when it is ready to start the workflow.
Created a Study object (seperate from the StudyModel) that can cronstructed on request, and contains a different data structure than we store in the DB. This allows us to return underlying Categories and Workflows in a clean way.
Added a new status to workflows called "not_started", meaning we have not yet instantiated a processor or created a BPMN, they have no version yet and no stored data, just the possiblity of being started.
The Top Level Workflow or "Master" workflow is now a part of the sample data, and loaded at all times.
Removed the ability to "add a workflow to a study" and "remove a workflow from a study", a study contains all possible workflows by definition.
Example data no longer creates users or studies, it just creates the specs.
2020-03-30 12:00:16 +00:00
|
|
|
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}
|
|
|
|
|
|
|
|
return study
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def delete_study(study_id):
|
2020-04-06 17:08:17 +00:00
|
|
|
session.query(TaskEventModel).filter_by(study_id=study_id).delete()
|
2020-04-29 20:07:39 +00:00
|
|
|
for workflow in session.query(WorkflowModel).filter_by(study_id=study_id):
|
2020-05-25 16:29:05 +00:00
|
|
|
StudyService.delete_workflow(workflow)
|
Created a "StudyService" and moved all complex logic around study manipulation out of the study api, and this service, as things were getting complicated. The Workflow Processor no longer creates the WorkflowModel, the study object handles that, and only passes the model into the workflow processor when it is ready to start the workflow.
Created a Study object (seperate from the StudyModel) that can cronstructed on request, and contains a different data structure than we store in the DB. This allows us to return underlying Categories and Workflows in a clean way.
Added a new status to workflows called "not_started", meaning we have not yet instantiated a processor or created a BPMN, they have no version yet and no stored data, just the possiblity of being started.
The Top Level Workflow or "Master" workflow is now a part of the sample data, and loaded at all times.
Removed the ability to "add a workflow to a study" and "remove a workflow from a study", a study contains all possible workflows by definition.
Example data no longer creates users or studies, it just creates the specs.
2020-03-30 12:00:16 +00:00
|
|
|
session.query(StudyModel).filter_by(id=study_id).delete()
|
2020-04-08 17:28:43 +00:00
|
|
|
session.commit()
|
Created a "StudyService" and moved all complex logic around study manipulation out of the study api, and this service, as things were getting complicated. The Workflow Processor no longer creates the WorkflowModel, the study object handles that, and only passes the model into the workflow processor when it is ready to start the workflow.
Created a Study object (seperate from the StudyModel) that can cronstructed on request, and contains a different data structure than we store in the DB. This allows us to return underlying Categories and Workflows in a clean way.
Added a new status to workflows called "not_started", meaning we have not yet instantiated a processor or created a BPMN, they have no version yet and no stored data, just the possiblity of being started.
The Top Level Workflow or "Master" workflow is now a part of the sample data, and loaded at all times.
Removed the ability to "add a workflow to a study" and "remove a workflow from a study", a study contains all possible workflows by definition.
Example data no longer creates users or studies, it just creates the specs.
2020-03-30 12:00:16 +00:00
|
|
|
|
2020-04-29 20:07:39 +00:00
|
|
|
@staticmethod
|
2020-05-25 16:29:05 +00:00
|
|
|
def delete_workflow(workflow):
|
|
|
|
for file in session.query(FileModel).filter_by(workflow_id=workflow.id).all():
|
2020-04-29 20:07:39 +00:00
|
|
|
FileService.delete_file(file.id)
|
2020-05-25 16:29:05 +00:00
|
|
|
session.query(TaskEventModel).filter_by(workflow_id=workflow.id).delete()
|
|
|
|
session.query(WorkflowModel).filter_by(id=workflow.id).delete()
|
2020-04-29 20:07:39 +00:00
|
|
|
|
Created a "StudyService" and moved all complex logic around study manipulation out of the study api, and this service, as things were getting complicated. The Workflow Processor no longer creates the WorkflowModel, the study object handles that, and only passes the model into the workflow processor when it is ready to start the workflow.
Created a Study object (seperate from the StudyModel) that can cronstructed on request, and contains a different data structure than we store in the DB. This allows us to return underlying Categories and Workflows in a clean way.
Added a new status to workflows called "not_started", meaning we have not yet instantiated a processor or created a BPMN, they have no version yet and no stored data, just the possiblity of being started.
The Top Level Workflow or "Master" workflow is now a part of the sample data, and loaded at all times.
Removed the ability to "add a workflow to a study" and "remove a workflow from a study", a study contains all possible workflows by definition.
Example data no longer creates users or studies, it just creates the specs.
2020-03-30 12:00:16 +00:00
|
|
|
@staticmethod
|
|
|
|
def get_categories():
|
|
|
|
"""Returns a list of category objects, in the correct order."""
|
|
|
|
cat_models = db.session.query(WorkflowSpecCategoryModel) \
|
|
|
|
.order_by(WorkflowSpecCategoryModel.display_order).all()
|
|
|
|
categories = []
|
|
|
|
for cat_model in cat_models:
|
|
|
|
categories.append(Category(cat_model))
|
|
|
|
return categories
|
|
|
|
|
2020-04-23 18:40:05 +00:00
|
|
|
@staticmethod
|
|
|
|
def get_approvals(study_id):
|
2020-04-24 13:45:55 +00:00
|
|
|
"""Returns a list of non-hidden approval workflows."""
|
|
|
|
study = StudyService.get_study(study_id)
|
|
|
|
cat = next(c for c in study.categories if c.name == 'approvals')
|
2020-04-23 18:40:05 +00:00
|
|
|
|
|
|
|
approvals = []
|
2020-04-24 13:45:55 +00:00
|
|
|
for wf in cat.workflows:
|
|
|
|
if wf.state is WorkflowState.hidden:
|
|
|
|
continue
|
|
|
|
|
|
|
|
workflow = db.session.query(WorkflowModel).filter_by(id=wf.id).first()
|
2020-04-23 18:40:05 +00:00
|
|
|
approvals.append({
|
2020-04-24 03:32:20 +00:00
|
|
|
'study_id': study_id,
|
2020-04-24 13:45:55 +00:00
|
|
|
'workflow_id': wf.id,
|
|
|
|
'display_name': wf.display_name,
|
|
|
|
'display_order': wf.display_order or 0,
|
|
|
|
'name': wf.name,
|
|
|
|
'state': wf.state.value,
|
|
|
|
'status': wf.status.value,
|
2020-04-23 18:40:05 +00:00
|
|
|
'workflow_spec_id': workflow.workflow_spec_id,
|
|
|
|
})
|
2020-04-24 12:54:14 +00:00
|
|
|
|
|
|
|
approvals.sort(key=lambda k: k['display_order'])
|
2020-04-23 18:40:05 +00:00
|
|
|
return approvals
|
|
|
|
|
2020-04-23 23:25:01 +00:00
|
|
|
@staticmethod
|
|
|
|
def get_documents_status(study_id):
|
2020-05-06 15:25:50 +00:00
|
|
|
"""Returns a list of documents related to the study, and any file information
|
Refactor the document details scripts. Now there is one script, it returns data in a consistent format, and has all the details required. The script is located in StudyInfo, with the argument documents. Make note that it returns a dictionary of ALL the documents, with a field to mark which ones are required according to the protocol builder. Others may become required if a workflow determines such, in which case the workflow will enforce this, and the document will have a count > 0, and additional details in a list of files within the document. I modified the XLS file to use lower case variable names, because it disturbed me, and we have to reference them frequently. Removed devious "as_object" variable on get_required_docs, so it behaves like the other methods all the time, and returns a dictionary. All the core business logic for finding the documents list now resides in the StudyService.
Because this changes the endpoint for all existing document details, I've modified all the test and static bpmn files to use the new format.
Shorting up the SponsorsList.xls file makes for slightly faster tests. seems senseless to load 5000 everytime we reset the data.
Tried to test all of this carefully in the test_study_details_documents.py test.
2020-04-29 19:08:11 +00:00
|
|
|
that is available.."""
|
2020-04-23 23:25:01 +00:00
|
|
|
|
2020-05-22 18:37:49 +00:00
|
|
|
# Get PB required docs, if Protocol Builder Service is enabled.
|
2020-05-27 02:42:49 +00:00
|
|
|
if ProtocolBuilderService.is_enabled():
|
2020-05-22 18:37:49 +00:00
|
|
|
try:
|
|
|
|
pb_docs = ProtocolBuilderService.get_required_docs(study_id=study_id)
|
|
|
|
except requests.exceptions.ConnectionError as ce:
|
|
|
|
app.logger.error("Failed to connect to the Protocol Builder - %s" % str(ce))
|
|
|
|
pb_docs = []
|
|
|
|
else:
|
2020-05-11 21:04:05 +00:00
|
|
|
pb_docs = []
|
2020-04-23 23:25:01 +00:00
|
|
|
|
2020-05-22 18:37:49 +00:00
|
|
|
# Loop through all known document types, get the counts for those files,
|
|
|
|
# and use pb_docs to mark those as required.
|
2020-05-07 17:57:24 +00:00
|
|
|
doc_dictionary = FileService.get_reference_data(FileService.DOCUMENT_LIST, 'code', ['id'])
|
|
|
|
|
Refactor the document details scripts. Now there is one script, it returns data in a consistent format, and has all the details required. The script is located in StudyInfo, with the argument documents. Make note that it returns a dictionary of ALL the documents, with a field to mark which ones are required according to the protocol builder. Others may become required if a workflow determines such, in which case the workflow will enforce this, and the document will have a count > 0, and additional details in a list of files within the document. I modified the XLS file to use lower case variable names, because it disturbed me, and we have to reference them frequently. Removed devious "as_object" variable on get_required_docs, so it behaves like the other methods all the time, and returns a dictionary. All the core business logic for finding the documents list now resides in the StudyService.
Because this changes the endpoint for all existing document details, I've modified all the test and static bpmn files to use the new format.
Shorting up the SponsorsList.xls file makes for slightly faster tests. seems senseless to load 5000 everytime we reset the data.
Tried to test all of this carefully in the test_study_details_documents.py test.
2020-04-29 19:08:11 +00:00
|
|
|
documents = {}
|
|
|
|
for code, doc in doc_dictionary.items():
|
2020-04-24 03:32:20 +00:00
|
|
|
|
2020-05-27 02:42:49 +00:00
|
|
|
if ProtocolBuilderService.is_enabled():
|
2020-05-22 18:37:49 +00:00
|
|
|
pb_data = next((item for item in pb_docs if int(item['AUXDOCID']) == int(doc['id'])), None)
|
|
|
|
doc['required'] = False
|
|
|
|
if pb_data:
|
|
|
|
doc['required'] = True
|
|
|
|
|
2020-04-23 23:25:01 +00:00
|
|
|
doc['study_id'] = study_id
|
|
|
|
doc['code'] = code
|
2020-04-24 03:32:20 +00:00
|
|
|
|
Refactor the document details scripts. Now there is one script, it returns data in a consistent format, and has all the details required. The script is located in StudyInfo, with the argument documents. Make note that it returns a dictionary of ALL the documents, with a field to mark which ones are required according to the protocol builder. Others may become required if a workflow determines such, in which case the workflow will enforce this, and the document will have a count > 0, and additional details in a list of files within the document. I modified the XLS file to use lower case variable names, because it disturbed me, and we have to reference them frequently. Removed devious "as_object" variable on get_required_docs, so it behaves like the other methods all the time, and returns a dictionary. All the core business logic for finding the documents list now resides in the StudyService.
Because this changes the endpoint for all existing document details, I've modified all the test and static bpmn files to use the new format.
Shorting up the SponsorsList.xls file makes for slightly faster tests. seems senseless to load 5000 everytime we reset the data.
Tried to test all of this carefully in the test_study_details_documents.py test.
2020-04-29 19:08:11 +00:00
|
|
|
# Make a display name out of categories
|
|
|
|
name_list = []
|
|
|
|
for cat_key in ['category1', 'category2', 'category3']:
|
|
|
|
if doc[cat_key] not in ['', 'NULL']:
|
|
|
|
name_list.append(doc[cat_key])
|
|
|
|
doc['display_name'] = ' / '.join(name_list)
|
2020-04-23 23:25:01 +00:00
|
|
|
|
|
|
|
# For each file, get associated workflow status
|
2020-04-24 03:32:20 +00:00
|
|
|
doc_files = FileService.get_files(study_id=study_id, irb_doc_code=code)
|
Refactor the document details scripts. Now there is one script, it returns data in a consistent format, and has all the details required. The script is located in StudyInfo, with the argument documents. Make note that it returns a dictionary of ALL the documents, with a field to mark which ones are required according to the protocol builder. Others may become required if a workflow determines such, in which case the workflow will enforce this, and the document will have a count > 0, and additional details in a list of files within the document. I modified the XLS file to use lower case variable names, because it disturbed me, and we have to reference them frequently. Removed devious "as_object" variable on get_required_docs, so it behaves like the other methods all the time, and returns a dictionary. All the core business logic for finding the documents list now resides in the StudyService.
Because this changes the endpoint for all existing document details, I've modified all the test and static bpmn files to use the new format.
Shorting up the SponsorsList.xls file makes for slightly faster tests. seems senseless to load 5000 everytime we reset the data.
Tried to test all of this carefully in the test_study_details_documents.py test.
2020-04-29 19:08:11 +00:00
|
|
|
doc['count'] = len(doc_files)
|
|
|
|
doc['files'] = []
|
2020-04-23 23:25:01 +00:00
|
|
|
for file in doc_files:
|
Refactor the document details scripts. Now there is one script, it returns data in a consistent format, and has all the details required. The script is located in StudyInfo, with the argument documents. Make note that it returns a dictionary of ALL the documents, with a field to mark which ones are required according to the protocol builder. Others may become required if a workflow determines such, in which case the workflow will enforce this, and the document will have a count > 0, and additional details in a list of files within the document. I modified the XLS file to use lower case variable names, because it disturbed me, and we have to reference them frequently. Removed devious "as_object" variable on get_required_docs, so it behaves like the other methods all the time, and returns a dictionary. All the core business logic for finding the documents list now resides in the StudyService.
Because this changes the endpoint for all existing document details, I've modified all the test and static bpmn files to use the new format.
Shorting up the SponsorsList.xls file makes for slightly faster tests. seems senseless to load 5000 everytime we reset the data.
Tried to test all of this carefully in the test_study_details_documents.py test.
2020-04-29 19:08:11 +00:00
|
|
|
doc['files'].append({'file_id': file.id,
|
|
|
|
'task_id': file.task_id,
|
|
|
|
'workflow_id': file.workflow_id,
|
|
|
|
'workflow_spec_id': file.workflow_spec_id})
|
2020-04-23 23:25:01 +00:00
|
|
|
|
Refactor the document details scripts. Now there is one script, it returns data in a consistent format, and has all the details required. The script is located in StudyInfo, with the argument documents. Make note that it returns a dictionary of ALL the documents, with a field to mark which ones are required according to the protocol builder. Others may become required if a workflow determines such, in which case the workflow will enforce this, and the document will have a count > 0, and additional details in a list of files within the document. I modified the XLS file to use lower case variable names, because it disturbed me, and we have to reference them frequently. Removed devious "as_object" variable on get_required_docs, so it behaves like the other methods all the time, and returns a dictionary. All the core business logic for finding the documents list now resides in the StudyService.
Because this changes the endpoint for all existing document details, I've modified all the test and static bpmn files to use the new format.
Shorting up the SponsorsList.xls file makes for slightly faster tests. seems senseless to load 5000 everytime we reset the data.
Tried to test all of this carefully in the test_study_details_documents.py test.
2020-04-29 19:08:11 +00:00
|
|
|
# update the document status to match the status of the workflow it is in.
|
|
|
|
if not 'status' in doc or doc['status'] is None:
|
2020-04-23 23:25:01 +00:00
|
|
|
workflow: WorkflowModel = session.query(WorkflowModel).filter_by(id=file.workflow_id).first()
|
|
|
|
doc['status'] = workflow.status.value
|
|
|
|
|
Refactor the document details scripts. Now there is one script, it returns data in a consistent format, and has all the details required. The script is located in StudyInfo, with the argument documents. Make note that it returns a dictionary of ALL the documents, with a field to mark which ones are required according to the protocol builder. Others may become required if a workflow determines such, in which case the workflow will enforce this, and the document will have a count > 0, and additional details in a list of files within the document. I modified the XLS file to use lower case variable names, because it disturbed me, and we have to reference them frequently. Removed devious "as_object" variable on get_required_docs, so it behaves like the other methods all the time, and returns a dictionary. All the core business logic for finding the documents list now resides in the StudyService.
Because this changes the endpoint for all existing document details, I've modified all the test and static bpmn files to use the new format.
Shorting up the SponsorsList.xls file makes for slightly faster tests. seems senseless to load 5000 everytime we reset the data.
Tried to test all of this carefully in the test_study_details_documents.py test.
2020-04-29 19:08:11 +00:00
|
|
|
documents[code] = doc
|
2020-04-23 23:25:01 +00:00
|
|
|
return documents
|
|
|
|
|
2020-04-29 14:21:24 +00:00
|
|
|
|
2020-05-07 17:57:24 +00:00
|
|
|
@staticmethod
|
|
|
|
def get_investigators(study_id):
|
|
|
|
|
|
|
|
# Loop through all known investigator types as set in the reference file
|
|
|
|
inv_dictionary = FileService.get_reference_data(FileService.INVESTIGATOR_LIST, 'code')
|
|
|
|
|
|
|
|
# Get PB required docs
|
|
|
|
pb_investigators = ProtocolBuilderService.get_investigators(study_id=study_id)
|
|
|
|
|
|
|
|
"""Convert array of investigators from protocol builder into a dictionary keyed on the type"""
|
|
|
|
for i_type in inv_dictionary:
|
|
|
|
pb_data = next((item for item in pb_investigators if item['INVESTIGATORTYPE'] == i_type), None)
|
|
|
|
if pb_data:
|
|
|
|
inv_dictionary[i_type]['user_id'] = pb_data["NETBADGEID"]
|
|
|
|
inv_dictionary[i_type].update(StudyService.get_ldap_dict_if_available(pb_data["NETBADGEID"]))
|
|
|
|
else:
|
|
|
|
inv_dictionary[i_type]['user_id'] = None
|
|
|
|
|
|
|
|
return inv_dictionary
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def get_ldap_dict_if_available(user_id):
|
|
|
|
try:
|
|
|
|
ldap_service = LdapService()
|
|
|
|
return ldap_service.user_info(user_id).__dict__
|
|
|
|
except ApiError as ae:
|
|
|
|
app.logger.info(str(ae))
|
|
|
|
return {"error": str(ae)}
|
|
|
|
except LDAPSocketOpenError:
|
|
|
|
app.logger.info("Failed to connect to LDAP Server.")
|
|
|
|
return {}
|
Refactor the document details scripts. Now there is one script, it returns data in a consistent format, and has all the details required. The script is located in StudyInfo, with the argument documents. Make note that it returns a dictionary of ALL the documents, with a field to mark which ones are required according to the protocol builder. Others may become required if a workflow determines such, in which case the workflow will enforce this, and the document will have a count > 0, and additional details in a list of files within the document. I modified the XLS file to use lower case variable names, because it disturbed me, and we have to reference them frequently. Removed devious "as_object" variable on get_required_docs, so it behaves like the other methods all the time, and returns a dictionary. All the core business logic for finding the documents list now resides in the StudyService.
Because this changes the endpoint for all existing document details, I've modified all the test and static bpmn files to use the new format.
Shorting up the SponsorsList.xls file makes for slightly faster tests. seems senseless to load 5000 everytime we reset the data.
Tried to test all of this carefully in the test_study_details_documents.py test.
2020-04-29 19:08:11 +00:00
|
|
|
|
2020-04-29 14:21:24 +00:00
|
|
|
@staticmethod
|
|
|
|
def get_protocol(study_id):
|
|
|
|
"""Returns the study protocol, if it has been uploaded."""
|
|
|
|
file = db.session.query(FileModel)\
|
|
|
|
.filter_by(study_id=study_id)\
|
|
|
|
.filter_by(form_field_key='Study_Protocol_Document')\
|
|
|
|
.first()
|
|
|
|
|
|
|
|
return FileModelSchema().dump(file)
|
|
|
|
|
|
|
|
|
Created a "StudyService" and moved all complex logic around study manipulation out of the study api, and this service, as things were getting complicated. The Workflow Processor no longer creates the WorkflowModel, the study object handles that, and only passes the model into the workflow processor when it is ready to start the workflow.
Created a Study object (seperate from the StudyModel) that can cronstructed on request, and contains a different data structure than we store in the DB. This allows us to return underlying Categories and Workflows in a clean way.
Added a new status to workflows called "not_started", meaning we have not yet instantiated a processor or created a BPMN, they have no version yet and no stored data, just the possiblity of being started.
The Top Level Workflow or "Master" workflow is now a part of the sample data, and loaded at all times.
Removed the ability to "add a workflow to a study" and "remove a workflow from a study", a study contains all possible workflows by definition.
Example data no longer creates users or studies, it just creates the specs.
2020-03-30 12:00:16 +00:00
|
|
|
@staticmethod
|
2020-05-22 18:37:49 +00:00
|
|
|
def synch_with_protocol_builder_if_enabled(user):
|
Created a "StudyService" and moved all complex logic around study manipulation out of the study api, and this service, as things were getting complicated. The Workflow Processor no longer creates the WorkflowModel, the study object handles that, and only passes the model into the workflow processor when it is ready to start the workflow.
Created a Study object (seperate from the StudyModel) that can cronstructed on request, and contains a different data structure than we store in the DB. This allows us to return underlying Categories and Workflows in a clean way.
Added a new status to workflows called "not_started", meaning we have not yet instantiated a processor or created a BPMN, they have no version yet and no stored data, just the possiblity of being started.
The Top Level Workflow or "Master" workflow is now a part of the sample data, and loaded at all times.
Removed the ability to "add a workflow to a study" and "remove a workflow from a study", a study contains all possible workflows by definition.
Example data no longer creates users or studies, it just creates the specs.
2020-03-30 12:00:16 +00:00
|
|
|
"""Assures that the studies we have locally for the given user are
|
|
|
|
in sync with the studies available in protocol builder. """
|
2020-05-22 18:37:49 +00:00
|
|
|
|
2020-05-27 03:18:14 +00:00
|
|
|
if ProtocolBuilderService.is_enabled():
|
Created a "StudyService" and moved all complex logic around study manipulation out of the study api, and this service, as things were getting complicated. The Workflow Processor no longer creates the WorkflowModel, the study object handles that, and only passes the model into the workflow processor when it is ready to start the workflow.
Created a Study object (seperate from the StudyModel) that can cronstructed on request, and contains a different data structure than we store in the DB. This allows us to return underlying Categories and Workflows in a clean way.
Added a new status to workflows called "not_started", meaning we have not yet instantiated a processor or created a BPMN, they have no version yet and no stored data, just the possiblity of being started.
The Top Level Workflow or "Master" workflow is now a part of the sample data, and loaded at all times.
Removed the ability to "add a workflow to a study" and "remove a workflow from a study", a study contains all possible workflows by definition.
Example data no longer creates users or studies, it just creates the specs.
2020-03-30 12:00:16 +00:00
|
|
|
|
2020-05-27 03:18:14 +00:00
|
|
|
app.logger.info("The Protocol Builder is enabled. app.config['PB_ENABLED'] = " +
|
|
|
|
str(app.config['PB_ENABLED']))
|
|
|
|
|
|
|
|
# Get studies matching this user from Protocol Builder
|
|
|
|
pb_studies: List[ProtocolBuilderStudy] = ProtocolBuilderService.get_studies(user.uid)
|
|
|
|
|
|
|
|
# Get studies from the database
|
|
|
|
db_studies = session.query(StudyModel).filter_by(user_uid=user.uid).all()
|
|
|
|
|
|
|
|
# Update all studies from the protocol builder, create new studies as needed.
|
|
|
|
# Futher assures that every active study (that does exist in the protocol builder)
|
|
|
|
# has a reference to every available workflow (though some may not have started yet)
|
|
|
|
for pb_study in pb_studies:
|
|
|
|
db_study = next((s for s in db_studies if s.id == pb_study.STUDYID), None)
|
|
|
|
if not db_study:
|
|
|
|
db_study = StudyModel(id=pb_study.STUDYID)
|
|
|
|
session.add(db_study)
|
|
|
|
db_studies.append(db_study)
|
|
|
|
db_study.update_from_protocol_builder(pb_study)
|
|
|
|
StudyService._add_all_workflow_specs_to_study(db_study)
|
|
|
|
|
|
|
|
# Mark studies as inactive that are no longer in Protocol Builder
|
|
|
|
for study in db_studies:
|
|
|
|
pb_study = next((pbs for pbs in pb_studies if pbs.STUDYID == study.id), None)
|
|
|
|
if not pb_study:
|
|
|
|
study.protocol_builder_status = ProtocolBuilderStatus.ABANDONED
|
|
|
|
|
|
|
|
db.session.commit()
|
Created a "StudyService" and moved all complex logic around study manipulation out of the study api, and this service, as things were getting complicated. The Workflow Processor no longer creates the WorkflowModel, the study object handles that, and only passes the model into the workflow processor when it is ready to start the workflow.
Created a Study object (seperate from the StudyModel) that can cronstructed on request, and contains a different data structure than we store in the DB. This allows us to return underlying Categories and Workflows in a clean way.
Added a new status to workflows called "not_started", meaning we have not yet instantiated a processor or created a BPMN, they have no version yet and no stored data, just the possiblity of being started.
The Top Level Workflow or "Master" workflow is now a part of the sample data, and loaded at all times.
Removed the ability to "add a workflow to a study" and "remove a workflow from a study", a study contains all possible workflows by definition.
Example data no longer creates users or studies, it just creates the specs.
2020-03-30 12:00:16 +00:00
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def __update_status_of_workflow_meta(workflow_metas, status):
|
|
|
|
# Update the status on each workflow
|
|
|
|
warnings = []
|
|
|
|
for wfm in workflow_metas:
|
|
|
|
if wfm.name in status.keys():
|
|
|
|
if not WorkflowState.has_value(status[wfm.name]):
|
|
|
|
warnings.append(ApiError("invalid_status",
|
|
|
|
"Workflow '%s' can not be set to '%s', should be one of %s" % (
|
|
|
|
wfm.name, status[wfm.name], ",".join(WorkflowState.list())
|
|
|
|
)))
|
|
|
|
else:
|
|
|
|
wfm.state = WorkflowState[status[wfm.name]]
|
|
|
|
else:
|
|
|
|
warnings.append(ApiError("missing_status", "No status specified for workflow %s" % wfm.name))
|
|
|
|
return warnings
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def __get_workflow_metas(study_id):
|
|
|
|
# Add in the Workflows for each category
|
2020-04-23 23:25:01 +00:00
|
|
|
workflow_models = db.session.query(WorkflowModel). \
|
|
|
|
join(WorkflowSpecModel). \
|
|
|
|
filter(WorkflowSpecModel.is_master_spec == False). \
|
|
|
|
filter(WorkflowModel.study_id == study_id). \
|
2020-03-30 18:01:57 +00:00
|
|
|
all()
|
Created a "StudyService" and moved all complex logic around study manipulation out of the study api, and this service, as things were getting complicated. The Workflow Processor no longer creates the WorkflowModel, the study object handles that, and only passes the model into the workflow processor when it is ready to start the workflow.
Created a Study object (seperate from the StudyModel) that can cronstructed on request, and contains a different data structure than we store in the DB. This allows us to return underlying Categories and Workflows in a clean way.
Added a new status to workflows called "not_started", meaning we have not yet instantiated a processor or created a BPMN, they have no version yet and no stored data, just the possiblity of being started.
The Top Level Workflow or "Master" workflow is now a part of the sample data, and loaded at all times.
Removed the ability to "add a workflow to a study" and "remove a workflow from a study", a study contains all possible workflows by definition.
Example data no longer creates users or studies, it just creates the specs.
2020-03-30 12:00:16 +00:00
|
|
|
workflow_metas = []
|
|
|
|
for workflow in workflow_models:
|
|
|
|
workflow_metas.append(WorkflowMetadata.from_workflow(workflow))
|
|
|
|
return workflow_metas
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def __get_study_status(study_model):
|
|
|
|
"""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.")
|
|
|
|
|
2020-03-30 18:01:57 +00:00
|
|
|
return WorkflowProcessor.run_master_spec(master_specs[0], study_model)
|
Created a "StudyService" and moved all complex logic around study manipulation out of the study api, and this service, as things were getting complicated. The Workflow Processor no longer creates the WorkflowModel, the study object handles that, and only passes the model into the workflow processor when it is ready to start the workflow.
Created a Study object (seperate from the StudyModel) that can cronstructed on request, and contains a different data structure than we store in the DB. This allows us to return underlying Categories and Workflows in a clean way.
Added a new status to workflows called "not_started", meaning we have not yet instantiated a processor or created a BPMN, they have no version yet and no stored data, just the possiblity of being started.
The Top Level Workflow or "Master" workflow is now a part of the sample data, and loaded at all times.
Removed the ability to "add a workflow to a study" and "remove a workflow from a study", a study contains all possible workflows by definition.
Example data no longer creates users or studies, it just creates the specs.
2020-03-30 12:00:16 +00:00
|
|
|
|
|
|
|
@staticmethod
|
2020-05-25 16:29:05 +00:00
|
|
|
def _add_all_workflow_specs_to_study(study_model:StudyModel):
|
|
|
|
existing_models = session.query(WorkflowModel).filter(WorkflowModel.study == study_model).all()
|
Created a "StudyService" and moved all complex logic around study manipulation out of the study api, and this service, as things were getting complicated. The Workflow Processor no longer creates the WorkflowModel, the study object handles that, and only passes the model into the workflow processor when it is ready to start the workflow.
Created a Study object (seperate from the StudyModel) that can cronstructed on request, and contains a different data structure than we store in the DB. This allows us to return underlying Categories and Workflows in a clean way.
Added a new status to workflows called "not_started", meaning we have not yet instantiated a processor or created a BPMN, they have no version yet and no stored data, just the possiblity of being started.
The Top Level Workflow or "Master" workflow is now a part of the sample data, and loaded at all times.
Removed the ability to "add a workflow to a study" and "remove a workflow from a study", a study contains all possible workflows by definition.
Example data no longer creates users or studies, it just creates the specs.
2020-03-30 12:00:16 +00:00
|
|
|
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()
|
|
|
|
errors = []
|
|
|
|
for workflow_spec in new_specs:
|
|
|
|
try:
|
2020-05-25 16:29:05 +00:00
|
|
|
StudyService._create_workflow_model(study_model, workflow_spec)
|
Created a "StudyService" and moved all complex logic around study manipulation out of the study api, and this service, as things were getting complicated. The Workflow Processor no longer creates the WorkflowModel, the study object handles that, and only passes the model into the workflow processor when it is ready to start the workflow.
Created a Study object (seperate from the StudyModel) that can cronstructed on request, and contains a different data structure than we store in the DB. This allows us to return underlying Categories and Workflows in a clean way.
Added a new status to workflows called "not_started", meaning we have not yet instantiated a processor or created a BPMN, they have no version yet and no stored data, just the possiblity of being started.
The Top Level Workflow or "Master" workflow is now a part of the sample data, and loaded at all times.
Removed the ability to "add a workflow to a study" and "remove a workflow from a study", a study contains all possible workflows by definition.
Example data no longer creates users or studies, it just creates the specs.
2020-03-30 12:00:16 +00:00
|
|
|
except WorkflowException as we:
|
|
|
|
errors.append(ApiError.from_task_spec("workflow_execution_exception", str(we), we.sender))
|
|
|
|
return errors
|
|
|
|
|
|
|
|
@staticmethod
|
2020-05-25 16:29:05 +00:00
|
|
|
def _create_workflow_model(study: StudyModel, spec):
|
Created a "StudyService" and moved all complex logic around study manipulation out of the study api, and this service, as things were getting complicated. The Workflow Processor no longer creates the WorkflowModel, the study object handles that, and only passes the model into the workflow processor when it is ready to start the workflow.
Created a Study object (seperate from the StudyModel) that can cronstructed on request, and contains a different data structure than we store in the DB. This allows us to return underlying Categories and Workflows in a clean way.
Added a new status to workflows called "not_started", meaning we have not yet instantiated a processor or created a BPMN, they have no version yet and no stored data, just the possiblity of being started.
The Top Level Workflow or "Master" workflow is now a part of the sample data, and loaded at all times.
Removed the ability to "add a workflow to a study" and "remove a workflow from a study", a study contains all possible workflows by definition.
Example data no longer creates users or studies, it just creates the specs.
2020-03-30 12:00:16 +00:00
|
|
|
workflow_model = WorkflowModel(status=WorkflowStatus.not_started,
|
2020-05-25 16:29:05 +00:00
|
|
|
study=study,
|
2020-05-04 14:57:09 +00:00
|
|
|
workflow_spec_id=spec.id,
|
|
|
|
last_updated=datetime.now())
|
Created a "StudyService" and moved all complex logic around study manipulation out of the study api, and this service, as things were getting complicated. The Workflow Processor no longer creates the WorkflowModel, the study object handles that, and only passes the model into the workflow processor when it is ready to start the workflow.
Created a Study object (seperate from the StudyModel) that can cronstructed on request, and contains a different data structure than we store in the DB. This allows us to return underlying Categories and Workflows in a clean way.
Added a new status to workflows called "not_started", meaning we have not yet instantiated a processor or created a BPMN, they have no version yet and no stored data, just the possiblity of being started.
The Top Level Workflow or "Master" workflow is now a part of the sample data, and loaded at all times.
Removed the ability to "add a workflow to a study" and "remove a workflow from a study", a study contains all possible workflows by definition.
Example data no longer creates users or studies, it just creates the specs.
2020-03-30 12:00:16 +00:00
|
|
|
session.add(workflow_model)
|
|
|
|
session.commit()
|
|
|
|
return workflow_model
|