diff --git a/crc/models/study.py b/crc/models/study.py
index be6d20dd..5848dfc6 100644
--- a/crc/models/study.py
+++ b/crc/models/study.py
@@ -1,6 +1,4 @@
-import datetime
import enum
-import json
import marshmallow
from marshmallow import INCLUDE, fields
@@ -9,13 +7,11 @@ from sqlalchemy import func
from crc import db, ma
from crc.api.common import ApiErrorSchema, ApiError
-from crc.models.file import FileModel, SimpleFileSchema, FileSchema
+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 WorkflowSpecCategoryModel, WorkflowState, WorkflowStatus, WorkflowSpecModel, \
- WorkflowModel
+from crc.models.workflow import WorkflowSpecCategoryModel, WorkflowState, WorkflowStatus, WorkflowModel
from crc.services.file_service import FileService
-from crc.services.user_service import UserService
class StudyStatus(enum.Enum):
@@ -23,6 +19,10 @@ class StudyStatus(enum.Enum):
hold = 'hold'
open_for_enrollment = 'open_for_enrollment'
abandoned = 'abandoned'
+
+
+class ProgressStatus(enum.Enum):
+ in_progress = 'in_progress'
submitted_for_pre_review = 'submitted_for_pre_review'
in_pre_review = 'in_pre_review'
returned_from_pre_review = 'returned_from_pre_review'
@@ -44,7 +44,6 @@ class StudyEventType(enum.Enum):
automatic = 'automatic'
-
class StudyModel(db.Model):
__tablename__ = 'study'
id = db.Column(db.Integer, primary_key=True)
@@ -52,6 +51,7 @@ class StudyModel(db.Model):
short_title = db.Column(db.String, nullable=True)
last_updated = db.Column(db.DateTime(timezone=True), server_default=func.now())
status = db.Column(db.Enum(StudyStatus))
+ progress_status = db.Column(db.Enum(ProgressStatus))
irb_status = db.Column(db.Enum(IrbStatus))
primary_investigator_id = db.Column(db.String, nullable=True)
sponsor = db.Column(db.String, nullable=True)
@@ -184,7 +184,7 @@ class CategorySchema(ma.Schema):
class Study(object):
def __init__(self, title, short_title, last_updated, primary_investigator_id, user_uid,
- id=None, status=None, irb_status=None, short_name=None, proposal_name=None, comment="",
+ id=None, status=None, progress_status=None, irb_status=None, short_name=None, proposal_name=None, comment="",
sponsor="", ind_number="", categories=[],
files=[], approvals=[], enrollment_date=None, events_history=[],
last_activity_user="",last_activity_date =None,create_user_display="", **argsv):
@@ -197,6 +197,7 @@ class Study(object):
self.short_title = short_title
self.last_updated = last_updated
self.status = status
+ self.progress_status = progress_status
self.irb_status = irb_status
self.comment = comment
self.primary_investigator_id = primary_investigator_id
@@ -265,6 +266,7 @@ class StudySchema(ma.Schema):
warnings = fields.List(fields.Nested(ApiErrorSchema), dump_only=True)
protocol_builder_status = EnumField(StudyStatus, by_value=True)
status = EnumField(StudyStatus, by_value=True)
+ progress_status = EnumField(ProgressStatus, by_value=True, allow_none=True)
short_title = fields.String(allow_none=True)
sponsor = fields.String(allow_none=True)
ind_number = fields.String(allow_none=True)
diff --git a/crc/models/workflow.py b/crc/models/workflow.py
index ca848cf7..ced02aac 100644
--- a/crc/models/workflow.py
+++ b/crc/models/workflow.py
@@ -1,13 +1,13 @@
import enum
import marshmallow
-from marshmallow import EXCLUDE,fields
+from marshmallow import EXCLUDE
from marshmallow_sqlalchemy import SQLAlchemyAutoSchema
from sqlalchemy import func
from sqlalchemy.orm import backref
from crc import db
-from crc.models.file import FileModel, FileDataModel
+from crc.models.file import FileDataModel
class WorkflowSpecCategoryModel(db.Model):
diff --git a/crc/scripts/get_study_progress_status.py b/crc/scripts/get_study_progress_status.py
new file mode 100644
index 00000000..d2d6d2ee
--- /dev/null
+++ b/crc/scripts/get_study_progress_status.py
@@ -0,0 +1,21 @@
+from crc import session
+from crc.models.study import StudyModel
+from crc.scripts.script import Script
+
+
+class GetStudyProgressStatus(Script):
+
+ def get_description(self):
+ return """
+ Get the progress status of the current study.
+ Progress status is only set when `status` is `in_progress`.
+ Progress status can be one of `in_progress`, `submitted_for_pre_review`, `in_pre_review`, `returned_from_pre_review`, `pre_review_complete`, `agenda_date_set`, `approved`, `approved_with_conditions`, `deferred`, or `disapproved`.
+ """
+
+ def do_task_validate_only(self, task, study_id, workflow_id, *args, **kwargs):
+ return self.do_task(task, study_id, workflow_id, *args, **kwargs)
+
+ def do_task(self, task, study_id, workflow_id, *args, **kwargs):
+ progress_status = session.query(StudyModel.progress_status).filter(StudyModel.id == study_id).scalar()
+ if progress_status:
+ return progress_status.value
diff --git a/crc/scripts/set_study_status.py b/crc/scripts/set_study_progress_status.py
similarity index 66%
rename from crc/scripts/set_study_status.py
rename to crc/scripts/set_study_progress_status.py
index 32397d0c..fb706f89 100644
--- a/crc/scripts/set_study_status.py
+++ b/crc/scripts/set_study_progress_status.py
@@ -1,14 +1,14 @@
from crc import session
from crc.api.common import ApiError
-from crc.models.study import StudyModel, StudyStatus
+from crc.models.study import StudyModel, ProgressStatus
from crc.scripts.script import Script
-class SetStudyStatus(Script):
+class SetStudyProgressStatus(Script):
def get_description(self):
- return """Set the status of the current study.
- Status can be one of `in_progress`, `hold`, `open_for_enrollment`, or `abandoned`."""
+ return """Set the progress status of the current study.
+ Progress status can be one of `in_progress`, `submitted_for_pre_review`, `in_pre_review`, `returned_from_pre_review`, `pre_review_complete`, `agenda_date_set`, `approved`, `approved_with_conditions`, `deferred`, or `disapproved`."""
def do_task_validate_only(self, task, study_id, workflow_id, *args, **kwargs):
@@ -19,17 +19,17 @@ class SetStudyStatus(Script):
new_status = args[0]
try:
- study_status = getattr(StudyStatus, new_status)
+ progress_status = getattr(ProgressStatus, new_status)
except AttributeError as ae:
raise ApiError.from_task(code='invalid_argument',
message=f"We could not find a status matching `{new_status}`. Original message: {ae}",
task=task)
- return study_status.value
+ return progress_status.value
else:
raise ApiError.from_task(code='missing_argument',
- message='You must include the new status when calling `set_study_status` script. '
+ message='You must include the new status when calling `set_study_progress_status` script. '
'The new status must be one of `in_progress`, `hold`, `open_for_enrollment`, or `abandoned`.',
task=task)
@@ -42,28 +42,26 @@ class SetStudyStatus(Script):
else:
new_status = args[0]
- # Get StudyStatus object for new_status
+ # Get ProgressStatus object for new_status
try:
- study_status = getattr(StudyStatus, new_status)
+ progress_status = getattr(ProgressStatus, new_status)
# Invalid argument
except AttributeError as ae:
raise ApiError.from_task(code='invalid_argument',
- message=f"We could not find a status matching `{new_status}`. Original message: {ae}"
- 'The new status must be one of `in_progress`, `hold`, `open_for_enrollment`, or `abandoned`.',
+ message=f"We could not find a status matching `{new_status}`. Original message: {ae}.",
task=task)
# Set new status
study_model = session.query(StudyModel).filter(StudyModel.id == study_id).first()
- study_model.status = study_status
+ study_model.progress_status = progress_status
session.commit()
- return study_model.status.value
+ return study_model.progress_status.value
# Missing argument
else:
raise ApiError.from_task(code='missing_argument',
- message='You must include the new status when calling `set_study_status` script. '
- 'The new status must be one of `in_progress`, `hold`, `open_for_enrollment`, or `abandoned`.',
+ message='You must include the new progress status when calling `set_study_progress_status` script. ',
task=task)
diff --git a/crc/services/study_service.py b/crc/services/study_service.py
index a5bd2d9e..4f720548 100755
--- a/crc/services/study_service.py
+++ b/crc/services/study_service.py
@@ -16,7 +16,7 @@ from crc.models.ldap import LdapSchema
from crc.models.protocol_builder import ProtocolBuilderCreatorStudy
from crc.models.study import StudyModel, Study, StudyStatus, Category, WorkflowMetadata, StudyEventType, StudyEvent, \
- StudyAssociated
+ StudyAssociated, ProgressStatus
from crc.models.task_event import TaskEventModel
from crc.models.task_log import TaskLogModel
from crc.models.workflow import WorkflowSpecCategoryModel, WorkflowModel, WorkflowSpecModel, WorkflowState, \
@@ -382,11 +382,14 @@ class StudyService(object):
# has a reference to every available workflow (though some may not have started yet)
for pb_study in pb_studies:
new_status = None
+ new_progress_status = None
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)
db_study.status = None # Force a new sa
new_status = StudyStatus.in_progress
+ new_progress_status = ProgressStatus.in_progress
+
session.add(db_study)
db_studies.append(db_study)
@@ -396,6 +399,9 @@ class StudyService(object):
# If there is a new automatic status change and there isn't a manual change in place, record it.
if new_status and db_study.status != StudyStatus.hold:
db_study.status = new_status
+ # make sure status is `in_progress`, before processing new automatic progress_status.
+ if new_progress_status and db_study.status == StudyStatus.in_progress:
+ db_study.progress_status = new_progress_status
StudyService.add_study_update_event(db_study,
status=new_status,
event_type=StudyEventType.automatic)
diff --git a/crc/services/workflow_service.py b/crc/services/workflow_service.py
index 8cd56c37..838d10c2 100755
--- a/crc/services/workflow_service.py
+++ b/crc/services/workflow_service.py
@@ -1,9 +1,7 @@
import copy
import json
-import string
import sys
import traceback
-from datetime import datetime
import random
import string
from datetime import datetime
@@ -14,26 +12,22 @@ from SpiffWorkflow import Task as SpiffTask, WorkflowException, NavItem
from SpiffWorkflow.bpmn.PythonScriptEngine import Box
from SpiffWorkflow.bpmn.specs.EndEvent import EndEvent
from SpiffWorkflow.bpmn.specs.ManualTask import ManualTask
-from SpiffWorkflow.bpmn.specs.MultiInstanceTask import MultiInstanceTask
from SpiffWorkflow.bpmn.specs.ScriptTask import ScriptTask
from SpiffWorkflow.bpmn.specs.StartEvent import StartEvent
from SpiffWorkflow.bpmn.specs.UserTask import UserTask
from SpiffWorkflow.dmn.specs.BusinessRuleTask import BusinessRuleTask
-from SpiffWorkflow.specs import CancelTask, StartTask, MultiChoice
+from SpiffWorkflow.specs import CancelTask, StartTask
from SpiffWorkflow.util.deep_merge import DeepMerge
from SpiffWorkflow.util.metrics import timeit
-from jinja2 import Template
-
-from crc import db, app, session, connexion_app
+from crc import db, app, session
from crc.api.common import ApiError
from crc.models.api_models import Task, MultiInstanceType, WorkflowApi
-from crc.models.data_store import DataStoreModel
from crc.models.file import LookupDataModel, FileModel, File, FileSchema
from crc.models.ldap import LdapModel
from crc.models.study import StudyModel
from crc.models.task_event import TaskEventModel
-from crc.models.user import UserModel, UserModelSchema
+from crc.models.user import UserModel
from crc.models.workflow import WorkflowModel, WorkflowStatus, WorkflowSpecModel, WorkflowSpecCategoryModel
from crc.services.data_store_service import DataStoreBase
diff --git a/migrations/versions/d830959e96c0_new_study_progress_statuses.py b/migrations/versions/d830959e96c0_new_study_progress_statuses.py
new file mode 100644
index 00000000..244924b7
--- /dev/null
+++ b/migrations/versions/d830959e96c0_new_study_progress_statuses.py
@@ -0,0 +1,28 @@
+"""new study progress statuses
+
+Revision ID: d830959e96c0
+Revises: a4f87f90cc64
+Create Date: 2021-12-09 11:55:28.890437
+
+"""
+from alembic import op
+import sqlalchemy as sa
+from crc.models.study import StudyStatus
+
+
+# revision identifiers, used by Alembic.
+revision = 'd830959e96c0'
+down_revision = 'a4f87f90cc64'
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+ op.execute("CREATE TYPE progressstatus AS ENUM('in_progress', 'submitted_for_pre_review', 'in_pre_review', 'returned_from_pre_review', 'pre_review_complete', 'agenda_date_set', 'approved', 'approved_with_conditions', 'deferred', 'disapproved')")
+ op.add_column('study', sa.Column('progress_status', sa.Enum('in_progress', 'submitted_for_pre_review', 'in_pre_review', 'returned_from_pre_review', 'pre_review_complete', 'agenda_date_set', 'approved', 'approved_with_conditions', 'deferred', 'disapproved', name='progressstatus'), nullable=True))
+ op.execute("update study set progress_status = 'in_progress' where status='in_progress'")
+
+
+def downgrade():
+ op.drop_column('study', 'progress_status')
+ op.execute('DROP TYPE progressstatus')
diff --git a/tests/base_test.py b/tests/base_test.py
index b162ae15..fe227ec1 100644
--- a/tests/base_test.py
+++ b/tests/base_test.py
@@ -15,7 +15,7 @@ from crc import app, db, session
from crc.models.api_models import WorkflowApiSchema, MultiInstanceType
from crc.models.file import FileModel, FileDataModel, CONTENT_TYPES
from crc.models.task_event import TaskEventModel
-from crc.models.study import StudyModel, StudyStatus
+from crc.models.study import StudyModel, StudyStatus, ProgressStatus
from crc.models.ldap import LdapModel
from crc.models.user import UserModel
from crc.models.workflow import WorkflowSpecModel, WorkflowSpecCategoryModel
@@ -61,6 +61,7 @@ class BaseTest(unittest.TestCase):
'title': 'The impact of fried pickles on beer consumption in bipedal software developers.',
'last_updated': datetime.datetime.utcnow(),
'status': StudyStatus.in_progress,
+ 'progress_status': ProgressStatus.in_progress,
'primary_investigator_id': 'dhf8r',
'sponsor': 'Sartography Pharmaceuticals',
'ind_number': '1234',
@@ -71,6 +72,7 @@ class BaseTest(unittest.TestCase):
'title': 'Requirement of hippocampal neurogenesis for the behavioral effects of soft pretzels',
'last_updated': datetime.datetime.utcnow(),
'status': StudyStatus.in_progress,
+ 'progress_status': ProgressStatus.in_progress,
'primary_investigator_id': 'dhf8r',
'sponsor': 'Makerspace & Co.',
'ind_number': '5678',
diff --git a/tests/data/get_study_progress_status/get_study_progress_status.bpmn b/tests/data/get_study_progress_status/get_study_progress_status.bpmn
new file mode 100644
index 00000000..a3d67fb8
--- /dev/null
+++ b/tests/data/get_study_progress_status/get_study_progress_status.bpmn
@@ -0,0 +1,47 @@
+
+
+
+
+ Flow_1iqprcz
+
+
+
+ Flow_1iqprcz
+ Flow_0npc38l
+ study_progress_status = get_study_progress_status()
+
+
+ # Study Progress Status
+{{ study_progress_status }}
+ Flow_0npc38l
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/data/set_study_status/set_study_status.bpmn b/tests/data/set_study_progress_status/set_study_progress_status.bpmn
similarity index 93%
rename from tests/data/set_study_status/set_study_status.bpmn
rename to tests/data/set_study_progress_status/set_study_progress_status.bpmn
index af1027d3..65dd3c53 100644
--- a/tests/data/set_study_status/set_study_status.bpmn
+++ b/tests/data/set_study_progress_status/set_study_progress_status.bpmn
@@ -9,9 +9,8 @@
-
-
-
+
+
@@ -23,7 +22,7 @@
Flow_0q0rtvj
Flow_0ana8xt
- returned_status = set_study_status(selected_status)
+ returned_status = set_study_progress_status(selected_status)
@@ -53,13 +52,13 @@
Flow_0c77bdh
Flow_1e9oiuw
- original_status = get_study_status()
+ original_status = get_study_progress_status()
Flow_0ana8xt
Flow_0nckhhn
- new_status = get_study_status()
+ new_status = get_study_progress_status()
diff --git a/tests/scripts/test_get_study_progress_status.py b/tests/scripts/test_get_study_progress_status.py
new file mode 100644
index 00000000..4fb2e333
--- /dev/null
+++ b/tests/scripts/test_get_study_progress_status.py
@@ -0,0 +1,16 @@
+from tests.base_test import BaseTest
+
+from crc import session
+from crc.models.study import StudyModel, ProgressStatus
+
+
+class TestGetStudyProgressStatus(BaseTest):
+
+ def test_get_study_progress_status(self):
+ workflow = self.create_workflow('get_study_progress_status')
+ study_model = session.query(StudyModel).filter(StudyModel.id == workflow.study_id).first()
+ study_model.progress_status = ProgressStatus.approved
+ workflow_api = self.get_workflow_api(workflow)
+ task = workflow_api.next_task
+
+ self.assertEqual(task.data['study_progress_status'], workflow.study.progress_status.value)
diff --git a/tests/scripts/test_set_study_progress_status.py b/tests/scripts/test_set_study_progress_status.py
new file mode 100644
index 00000000..0b05b54e
--- /dev/null
+++ b/tests/scripts/test_set_study_progress_status.py
@@ -0,0 +1,46 @@
+from tests.base_test import BaseTest
+
+from crc.models.study import ProgressStatus
+
+
+class TestSetStudyProgressStatus(BaseTest):
+
+ def test_set_study_progress_status_validation(self):
+ self.load_example_data()
+ spec_model = self.load_test_spec('set_study_progress_status')
+ rv = self.app.get('/v1.0/workflow-specification/%s/validate' % spec_model.id, headers=self.logged_in_headers())
+ # The workflow has an enum option that causes an exception.
+ # We take advantage of this in test_set_study_progress_status_fail below.
+ # Sometimes, the validation process chooses the failing path,
+ # so we have to check for that here.
+ try:
+ self.assertEqual([], rv.json)
+ except AssertionError:
+ # 'asdf' is the failing enum option
+ self.assertEqual('asdf', rv.json[0]['task_data']['selected_status'])
+
+ def test_set_study_progress_status(self):
+ workflow = self.create_workflow('set_study_progress_status')
+ workflow.study.progress_status = ProgressStatus.in_progress
+ workflow_api = self.get_workflow_api(workflow)
+ task = workflow_api.next_task
+
+ original_status = task.data['original_status']
+ self.assertEqual('in_progress', original_status)
+
+ workflow_api = self.complete_form(workflow, task, {'selected_status': 'disapproved'})
+ task = workflow_api.next_task
+
+ self.assertEqual('Activity_DisplayStatus', task.name)
+ self.assertEqual('disapproved', task.data['selected_status'])
+ self.assertEqual('disapproved', task.data['new_status'])
+
+ def test_set_study_progress_status_fail(self):
+
+ self.load_example_data()
+ workflow = self.create_workflow('set_study_progress_status')
+ workflow_api = self.get_workflow_api(workflow)
+ task = workflow_api.next_task
+
+ with self.assertRaises(AssertionError):
+ self.complete_form(workflow, task, {'selected_status': 'asdf'})
diff --git a/tests/scripts/test_set_study_status.py b/tests/scripts/test_set_study_status.py
deleted file mode 100644
index ece8c30c..00000000
--- a/tests/scripts/test_set_study_status.py
+++ /dev/null
@@ -1,35 +0,0 @@
-from tests.base_test import BaseTest
-
-
-class TestSetStudyStatus(BaseTest):
-
- def test_set_study_status_validation(self):
- self.load_example_data()
- spec_model = self.load_test_spec('set_study_status')
- rv = self.app.get('/v1.0/workflow-specification/%s/validate' % spec_model.id, headers=self.logged_in_headers())
- self.assertEqual([], rv.json)
-
- def test_set_study_status(self):
- workflow = self.create_workflow('set_study_status')
- workflow_api = self.get_workflow_api(workflow)
- task = workflow_api.next_task
-
- original_status = task.data['original_status']
- self.assertEqual('in_progress', original_status)
-
- workflow_api = self.complete_form(workflow, task, {'selected_status': 'hold'})
- task = workflow_api.next_task
-
- self.assertEqual('Activity_DisplayStatus', task.name)
- self.assertEqual('hold', task.data['selected_status'])
- self.assertEqual('hold', task.data['new_status'])
-
- def test_set_study_status_fail(self):
-
- self.load_example_data()
- workflow = self.create_workflow('set_study_status')
- workflow_api = self.get_workflow_api(workflow)
- task = workflow_api.next_task
-
- with self.assertRaises(AssertionError):
- self.complete_form(workflow, task, {'selected_status': 'asdf'})