Merge pull request #253 from sartography/220-short-study-name

Add short_title to study, change update_study task to use kw argument…
This commit is contained in:
Dan Funk 2021-03-02 13:37:24 -05:00 committed by GitHub
commit 9c31d69c12
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 74 additions and 38 deletions

View File

@ -40,6 +40,7 @@ class StudyModel(db.Model):
__tablename__ = 'study' __tablename__ = 'study'
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String) title = db.Column(db.String)
short_title = db.Column(db.String, nullable=True)
last_updated = db.Column(db.DateTime(timezone=True), default=func.now()) last_updated = db.Column(db.DateTime(timezone=True), default=func.now())
status = db.Column(db.Enum(StudyStatus)) status = db.Column(db.Enum(StudyStatus))
irb_status = db.Column(db.Enum(IrbStatus)) irb_status = db.Column(db.Enum(IrbStatus))
@ -161,7 +162,7 @@ class CategorySchema(ma.Schema):
class Study(object): class Study(object):
def __init__(self, title, last_updated, primary_investigator_id, user_uid, def __init__(self, title, short_title, last_updated, primary_investigator_id, user_uid,
id=None, status=None, irb_status=None, comment="", id=None, status=None, irb_status=None, comment="",
sponsor="", hsr_number="", ind_number="", categories=[], sponsor="", hsr_number="", ind_number="", categories=[],
files=[], approvals=[], enrollment_date=None, events_history=[], files=[], approvals=[], enrollment_date=None, events_history=[],
@ -172,6 +173,7 @@ class Study(object):
self.last_activity_date = last_activity_date self.last_activity_date = last_activity_date
self.last_activity_user = last_activity_user self.last_activity_user = last_activity_user
self.title = title self.title = title
self.short_title = short_title
self.last_updated = last_updated self.last_updated = last_updated
self.status = status self.status = status
self.irb_status = irb_status self.irb_status = irb_status
@ -243,6 +245,7 @@ class StudySchema(ma.Schema):
protocol_builder_status = EnumField(StudyStatus, by_value=True) protocol_builder_status = EnumField(StudyStatus, by_value=True)
status = EnumField(StudyStatus, by_value=True) status = EnumField(StudyStatus, by_value=True)
hsr_number = fields.String(allow_none=True) hsr_number = fields.String(allow_none=True)
short_title = fields.String(allow_none=True)
sponsor = fields.String(allow_none=True) sponsor = fields.String(allow_none=True)
ind_number = fields.String(allow_none=True) ind_number = fields.String(allow_none=True)
files = fields.List(fields.Nested(FileSchema), dump_only=True) files = fields.List(fields.Nested(FileSchema), dump_only=True)
@ -251,7 +254,7 @@ class StudySchema(ma.Schema):
class Meta: class Meta:
model = Study model = Study
additional = ["id", "title", "last_updated", "primary_investigator_id", "user_uid", additional = ["id", "title", "short_title", "last_updated", "primary_investigator_id", "user_uid",
"sponsor", "ind_number", "files", "enrollment_date", "sponsor", "ind_number", "files", "enrollment_date",
"create_user_display", "last_activity_date","last_activity_user", "create_user_display", "last_activity_date","last_activity_user",
"events_history"] "events_history"]

View File

@ -186,6 +186,7 @@ Returns information specific to the protocol.
"info": { "info": {
"id": 12, "id": 12,
"title": "test", "title": "test",
"short_title": "tst",
"primary_investigator_id":21, "primary_investigator_id":21,
"user_uid": "dif84", "user_uid": "dif84",
"sponsor": "sponsor", "sponsor": "sponsor",

View File

@ -15,44 +15,37 @@ class mock_study:
class UpdateStudy(Script): class UpdateStudy(Script):
argument_error_message = "You must supply at least one argument to the " \ argument_error_message = "You must supply at least one argument to the " \
"update_study task, in the form [study_field]:[value]", "update_study task, in the form [study_field]=[value]",
def get_description(self): def get_description(self):
return """ return """
Allows you to set specific attributes on the Study model by mapping them to Allows you to set specific attributes on the Study model by mapping them to
values in the task data. Should be called with the value to set (either title, or pi) values in the task data. Should be called with the value to set (either title, short_title, or pi)
followed by a ":" and then the value to use in dot notation.
Example: Example:
UpdateStudy title:PIComputingID.label pi:PIComputingID.value update_study(title=PIComputingID.label, short_title="Really Short Name")
""" """
def do_task_validate_only(self, task, study_id, workflow_id, *args, **kwargs): def do_task_validate_only(self, task, study_id, workflow_id, *args, **kwargs):
study = mock_study study = mock_study
self.__update_study(task, study, *args) self.__update_study(task, study, *args, **kwargs)
def do_task(self, task, study_id, workflow_id, *args, **kwargs): def do_task(self, task, study_id, workflow_id, *args, **kwargs):
study = db.session.query(StudyModel).filter(StudyModel.id == study_id).first() study = db.session.query(StudyModel).filter(StudyModel.id == study_id).first()
self.__update_study(task, study, *args) self.__update_study(task, study, *args, **kwargs)
db.session.add(study) db.session.add(study)
def __update_study(self, task, study, *args): def __update_study(self, task, study, *args, **kwargs):
if len(args) < 1: if len(kwargs.keys()) < 1:
raise ApiError.from_task("missing_argument", self.argument_error_message, raise ApiError.from_task("missing_argument", self.argument_error_message,
task=task) task=task)
for arg in args: for arg in kwargs.keys():
try: if arg.lower() == "title":
field, value_lookup = arg.split(':') study.title = kwargs[arg]
except: elif arg.lower() == "short_title":
raise ApiError.from_task("invalid_argument", self.argument_error_message, study.short_title = kwargs[arg]
task=task) elif arg.lower() == "pi":
study.primary_investigator_id = kwargs[arg]
value = task.workflow.script_engine.evaluate_expression(task, value_lookup)
if field.lower() == "title":
study.title = value
elif field.lower() == "pi":
study.primary_investigator_id = value
else: else:
raise ApiError.from_task("invalid_argument", self.argument_error_message, raise ApiError.from_task("invalid_argument", self.argument_error_message,
task=task) task=task)

View File

@ -36,7 +36,7 @@ class StudyService(object):
studies = [] studies = []
for study_model in db_studies: for study_model in db_studies:
studies.append(StudyService.get_study(study_model.id, study_model)) studies.append(StudyService.get_study(study_model.id, study_model,do_status=False))
return studies return studies
@staticmethod @staticmethod
@ -51,7 +51,7 @@ class StudyService(object):
return studies return studies
@staticmethod @staticmethod
def get_study(study_id, study_model: StudyModel = None): def get_study(study_id, study_model: StudyModel = None, do_status=True):
"""Returns a study model that contains all the workflows organized by category. """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 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.""" loading up and executing all the workflows in a study to calculate information."""
@ -81,8 +81,9 @@ class StudyService(object):
if study.status != StudyStatus.abandoned: if study.status != StudyStatus.abandoned:
# this line is taking 99% of the time that is used in get_study. # this line is taking 99% of the time that is used in get_study.
# see ticket #196 # see ticket #196
status = StudyService.__get_study_status(study_model) if do_status:
study.warnings = StudyService.__update_status_of_workflow_meta(workflow_metas, status) status = StudyService.__get_study_status(study_model)
study.warnings = StudyService.__update_status_of_workflow_meta(workflow_metas, status)
# Group the workflows into their categories. # Group the workflows into their categories.
for category in study.categories: for category in study.categories:

View File

@ -0,0 +1,28 @@
"""empty message
Revision ID: f28ee3722c49
Revises: cb892916166a
Create Date: 2021-03-02 08:30:22.879266
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'f28ee3722c49'
down_revision = 'cb892916166a'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('study', sa.Column('short_title', sa.String(), nullable=True))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('study', 'short_title')
# ### end Alembic commands ###

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_0a9entn" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.7.3"> <bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_0a9entn" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.7.0">
<bpmn:process id="Process_1dagb7t" name="TestMessage" isExecutable="true"> <bpmn:process id="Process_1dagb7t" name="TestMessage" isExecutable="true">
<bpmn:startEvent id="StartEvent_1" name="Start"> <bpmn:startEvent id="StartEvent_1" name="Start">
<bpmn:outgoing>Flow_0xym55y</bpmn:outgoing> <bpmn:outgoing>Flow_0xym55y</bpmn:outgoing>
@ -7,7 +7,7 @@
<bpmn:sequenceFlow id="Flow_16q1uec" name="TestMessageFlow" sourceRef="Event_TokenReset" targetRef="Activity_TestMessage" /> <bpmn:sequenceFlow id="Flow_16q1uec" name="TestMessageFlow" sourceRef="Event_TokenReset" targetRef="Activity_TestMessage" />
<bpmn:scriptTask id="Activity_TestMessage" name="Test Message" camunda:resultVariable="test_message"> <bpmn:scriptTask id="Activity_TestMessage" name="Test Message" camunda:resultVariable="test_message">
<bpmn:incoming>Flow_16q1uec</bpmn:incoming> <bpmn:incoming>Flow_16q1uec</bpmn:incoming>
<bpmn:script>update_study("title:'New Title'") <bpmn:script>update_study(title='New Title')
print('New Title')</bpmn:script> print('New Title')</bpmn:script>
</bpmn:scriptTask> </bpmn:scriptTask>
<bpmn:userTask id="Activity_GetData" name="GetData" camunda:formKey="FirstTaskForm"> <bpmn:userTask id="Activity_GetData" name="GetData" camunda:formKey="FirstTaskForm">

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_0a9entn" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="4.2.0"> <bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_0a9entn" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.7.0">
<bpmn:process id="Process_1dagb7t" name="TestMessage" isExecutable="true"> <bpmn:process id="Process_1dagb7t" name="TestMessage" isExecutable="true">
<bpmn:startEvent id="StartEvent_1" name="Start"> <bpmn:startEvent id="StartEvent_1" name="Start">
<bpmn:outgoing>Flow_0xym55y</bpmn:outgoing> <bpmn:outgoing>Flow_0xym55y</bpmn:outgoing>
@ -7,7 +7,7 @@
<bpmn:sequenceFlow id="Flow_16q1uec" name="TestMessageFlow" sourceRef="Event_TokenReset" targetRef="Activity_TestMessage" /> <bpmn:sequenceFlow id="Flow_16q1uec" name="TestMessageFlow" sourceRef="Event_TokenReset" targetRef="Activity_TestMessage" />
<bpmn:scriptTask id="Activity_TestMessage" name="Test Message" camunda:resultVariable="test_message"> <bpmn:scriptTask id="Activity_TestMessage" name="Test Message" camunda:resultVariable="test_message">
<bpmn:incoming>Flow_16q1uec</bpmn:incoming> <bpmn:incoming>Flow_16q1uec</bpmn:incoming>
<bpmn:script>update_study("title:'New Title'") <bpmn:script>update_study(title='New Title')
print('New Title')</bpmn:script> print('New Title')</bpmn:script>
</bpmn:scriptTask> </bpmn:scriptTask>
<bpmn:sequenceFlow id="Flow_0xym55y" sourceRef="StartEvent_1" targetRef="Activity_Hello" /> <bpmn:sequenceFlow id="Flow_0xym55y" sourceRef="StartEvent_1" targetRef="Activity_Hello" />
@ -58,7 +58,7 @@ print('New Title')</bpmn:script>
<bpmn:scriptTask id="Activity_CancelMessage" name="Cancel Message" camunda:resultVariable="cancel_message"> <bpmn:scriptTask id="Activity_CancelMessage" name="Cancel Message" camunda:resultVariable="cancel_message">
<bpmn:documentation>&lt;H1&gt;Cancel Message&lt;/H1&gt;</bpmn:documentation> <bpmn:documentation>&lt;H1&gt;Cancel Message&lt;/H1&gt;</bpmn:documentation>
<bpmn:incoming>Flow_13xidv2</bpmn:incoming> <bpmn:incoming>Flow_13xidv2</bpmn:incoming>
<bpmn:script>update_study("title:'Second Title'") <bpmn:script>update_study(title='Second Title')
print('Second Title')</bpmn:script> print('Second Title')</bpmn:script>
</bpmn:scriptTask> </bpmn:scriptTask>
</bpmn:process> </bpmn:process>

View File

@ -2,7 +2,7 @@ from tests.base_test import BaseTest
from crc.scripts.update_study import UpdateStudy from crc.scripts.update_study import UpdateStudy
from crc.services.workflow_processor import WorkflowProcessor from crc.services.workflow_processor import WorkflowProcessor
from box import Box
class TestUpdateStudyScript(BaseTest): class TestUpdateStudyScript(BaseTest):
@ -12,12 +12,22 @@ class TestUpdateStudyScript(BaseTest):
workflow = self.create_workflow('empty_workflow') workflow = self.create_workflow('empty_workflow')
processor = WorkflowProcessor(workflow) processor = WorkflowProcessor(workflow)
task = processor.next_task() task = processor.next_task()
task.data = {"details": { details = Box({
"label": "My New Title", "label": "My New Title",
"value": "dhf8r"} "short": "My New Short Title",
} "value": "dhf8r"})
script = UpdateStudy() script = UpdateStudy()
script.do_task(task, workflow.study_id, workflow.id, "title:details.label", "pi:details.value") # note that we changed where the argument gets evaluated
# previsously, it took the arguments and then evaluated them within the script
# now, it evaluates the arugments in the context of the main script so they get
# evaluated before they are passed to the script -
# this allows us to do a lot more things like strings, functions, etc.
# and it makes the arguments less confusing to use.
script.do_task(task, workflow.study_id, workflow.id, title = details.label,
short_title = details.short,
pi = details.value)
self.assertEqual("My New Title", workflow.study.title) self.assertEqual("My New Title", workflow.study.title)
self.assertEqual("My New Short Title", workflow.study.short_title)
self.assertEqual("dhf8r", workflow.study.primary_investigator_id) self.assertEqual("dhf8r", workflow.study.primary_investigator_id)

View File

@ -26,7 +26,7 @@ class TestMessageEvent(BaseTest):
headers=self.logged_in_headers(), headers=self.logged_in_headers(),
content_type="application/json") content_type="application/json")
# set_current_task should call the interupt (signal) task # set_current_task should call the interrupt (signal) task
# which should run the script in our task # which should run the script in our task
# #
# test to see if our changes made it to the DB # test to see if our changes made it to the DB