Merge branch 'dev' into dependabot/pip/lxml-4.6.3

This commit is contained in:
Dan 2021-04-02 14:55:59 -04:00
commit 33a87f6c35
7 changed files with 252 additions and 18 deletions

View File

@ -160,7 +160,6 @@ paths:
parameters:
- name: workflow_spec_id
in: path
required: false
description: The unique id of an existing workflow specification to modify.
schema:
type: string

View File

@ -96,7 +96,7 @@ class WorkflowMetadata(object):
def __init__(self, id, name = None, display_name = None, description = None, spec_version = None,
category_id = None, category_display_name = None, state: WorkflowState = None,
status: WorkflowStatus = None, total_tasks = None, completed_tasks = None,
is_review=None,display_order = None):
is_review=None,display_order = None, state_message = None):
self.id = id
self.name = name
self.display_name = display_name
@ -105,6 +105,7 @@ class WorkflowMetadata(object):
self.category_id = category_id
self.category_display_name = category_display_name
self.state = state
self.state_message = state_message
self.status = status
self.total_tasks = total_tasks
self.completed_tasks = completed_tasks
@ -140,7 +141,7 @@ class WorkflowMetadataSchema(ma.Schema):
model = WorkflowMetadata
additional = ["id", "name", "display_name", "description",
"total_tasks", "completed_tasks", "display_order",
"category_id", "is_review", "category_display_name"]
"category_id", "is_review", "category_display_name", "state_message"]
unknown = INCLUDE

View File

@ -71,7 +71,7 @@ class StudyService(object):
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)
workflow_metas = StudyService._get_workflow_metas(study_id)
files = FileService.get_files_for_study(study.id)
files = (File.from_models(model, FileService.get_file_data(model.id),
FileService.get_doc_dictionary()) for model in files)
@ -83,8 +83,9 @@ class StudyService(object):
# this line is taking 99% of the time that is used in get_study.
# see ticket #196
if do_status:
status = StudyService.__get_study_status(study_model)
study.warnings = StudyService.__update_status_of_workflow_meta(workflow_metas, 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:
@ -416,24 +417,36 @@ class StudyService(object):
db.session.commit()
@staticmethod
def __update_status_of_workflow_meta(workflow_metas, status):
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:
wfm.state_message = ''
# do we have a status for you
if wfm.name not in status.keys():
warnings.append(ApiError("missing_status", "No status specified for workflow %s" % wfm.name))
continue
if not isinstance(status[wfm.name], dict):
warnings.append(ApiError(code='invalid_status',
message=f'Status must be a dictionary with "status" and "message" keys. Name is {wfm.name}. Status is {status[wfm.name]}'))
continue
if 'status' not in status[wfm.name].keys():
warnings.append(ApiError("missing_status",
"Workflow '%s' does not have a status setting" % wfm.name))
continue
if not WorkflowState.has_value(status[wfm.name]['status']):
warnings.append(ApiError("invalid_state",
"Workflow '%s' can not be set to '%s', should be one of %s" % (
wfm.name, status[wfm.name]['status'], ",".join(WorkflowState.list())
)))
continue
wfm.state = WorkflowState[status[wfm.name]['status']]
if 'message' in status[wfm.name].keys():
wfm.state_message = status[wfm.name]['message']
return warnings
@staticmethod
def __get_workflow_metas(study_id):
def _get_workflow_metas(study_id):
# Add in the Workflows for each category
workflow_models = db.session.query(WorkflowModel). \
join(WorkflowSpecModel). \
@ -446,7 +459,7 @@ class StudyService(object):
return workflow_metas
@staticmethod
def __get_study_status(study_model):
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). \

View File

@ -0,0 +1,71 @@
<?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_09rv9vf" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.5.0">
<bpmn:process id="Process_StatusMessage" name="Status Message" isExecutable="true">
<bpmn:documentation>Testing Workflow Status Messages</bpmn:documentation>
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>SequenceFlow_0x4n744</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:sequenceFlow id="SequenceFlow_0x4n744" sourceRef="StartEvent_1" targetRef="Task_SetName" />
<bpmn:sequenceFlow id="SequenceFlow_1o630oy" sourceRef="Task_SetName" targetRef="Task_Decision" />
<bpmn:businessRuleTask id="Task_Decision" name="Make Decision" camunda:decisionRef="Decision_Dog">
<bpmn:incoming>SequenceFlow_1o630oy</bpmn:incoming>
<bpmn:outgoing>SequenceFlow_1foyag7</bpmn:outgoing>
</bpmn:businessRuleTask>
<bpmn:sequenceFlow id="SequenceFlow_1foyag7" sourceRef="Task_Decision" targetRef="Task_GoodBye" />
<bpmn:manualTask id="Task_GoodBye" name="Say Good Bye">
<bpmn:documentation>&lt;div&gt;&lt;span&gt;Good Bye {{ dog.name }}&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span&gt;You are such a good {{ dog.breed }}&lt;/span&gt;&lt;/div&gt;
</bpmn:documentation>
<bpmn:incoming>SequenceFlow_1foyag7</bpmn:incoming>
<bpmn:outgoing>SequenceFlow_1bc1ugw</bpmn:outgoing>
</bpmn:manualTask>
<bpmn:endEvent id="EndEvent_19dasnt">
<bpmn:incoming>SequenceFlow_1bc1ugw</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="SequenceFlow_1bc1ugw" sourceRef="Task_GoodBye" targetRef="EndEvent_19dasnt" />
<bpmn:userTask id="Task_SetName" name="Set Name" camunda:formKey="NameForm">
<bpmn:extensionElements>
<camunda:formData>
<camunda:formField id="name" label="Name" type="string" defaultValue="Layla" />
</camunda:formData>
</bpmn:extensionElements>
<bpmn:incoming>SequenceFlow_0x4n744</bpmn:incoming>
<bpmn:outgoing>SequenceFlow_1o630oy</bpmn:outgoing>
</bpmn:userTask>
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_StatusMessage">
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="165" y="99" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="SequenceFlow_0x4n744_di" bpmnElement="SequenceFlow_0x4n744">
<di:waypoint x="201" y="117" />
<di:waypoint x="260" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="SequenceFlow_1o630oy_di" bpmnElement="SequenceFlow_1o630oy">
<di:waypoint x="360" y="117" />
<di:waypoint x="420" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="BusinessRuleTask_0dwwkqn_di" bpmnElement="Task_Decision">
<dc:Bounds x="420" y="77" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="SequenceFlow_1foyag7_di" bpmnElement="SequenceFlow_1foyag7">
<di:waypoint x="520" y="117" />
<di:waypoint x="580" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="ManualTask_0nb6k7f_di" bpmnElement="Task_GoodBye">
<dc:Bounds x="580" y="77" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="EndEvent_19dasnt_di" bpmnElement="EndEvent_19dasnt">
<dc:Bounds x="742" y="99" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="SequenceFlow_1bc1ugw_di" bpmnElement="SequenceFlow_1bc1ugw">
<di:waypoint x="680" y="117" />
<di:waypoint x="742" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="UserTask_1h3sio1_di" bpmnElement="Task_SetName">
<dc:Bounds x="260" y="77" width="100" height="80" />
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

View File

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/DMN/20151101/dmn.xsd" id="Definitions_1eg3sxk" name="DRD" namespace="http://camunda.org/schema/1.0/dmn" exporter="Camunda Modeler" exporterVersion="3.5.0">
<decision id="Decision_Dog" name="Dogs">
<decisionTable id="decisionTable_1">
<input id="input_1" label="Name">
<inputExpression id="inputExpression_1" typeRef="string">
<text>name</text>
</inputExpression>
</input>
<output id="output_1" label="Name" name="dog.name" typeRef="string" />
<output id="OutputClause_1wqk5xi" label="Breed" name="dog.breed" typeRef="string" />
<rule id="DecisionRule_1h6w5qu">
<inputEntry id="UnaryTests_0m8eblt">
<text>'Layla'</text>
</inputEntry>
<outputEntry id="LiteralExpression_186aovp">
<text>'Layla'</text>
</outputEntry>
<outputEntry id="LiteralExpression_10jbx5v">
<text>'Aussie'</text>
</outputEntry>
</rule>
<rule id="DecisionRule_0ziemrx">
<inputEntry id="UnaryTests_0okkroj">
<text>'Mona'</text>
</inputEntry>
<outputEntry id="LiteralExpression_0r1apkh">
<text>'Mona'</text>
</outputEntry>
<outputEntry id="LiteralExpression_08vl869">
<text>'Aussie Mix'</text>
</outputEntry>
</rule>
<rule id="DecisionRule_0fykwob">
<inputEntry id="UnaryTests_044ophk">
<text>'Jerry'</text>
</inputEntry>
<outputEntry id="LiteralExpression_0508umo">
<text>'Jerry'</text>
</outputEntry>
<outputEntry id="LiteralExpression_0ysgqib">
<text>'Aussie Mix'</text>
</outputEntry>
</rule>
<rule id="DecisionRule_05jugdn">
<inputEntry id="UnaryTests_1jri40s">
<text>'Zoey'</text>
</inputEntry>
<outputEntry id="LiteralExpression_1r5jrzq">
<text>'Zoey'</text>
</outputEntry>
<outputEntry id="LiteralExpression_0aqjmjy">
<text>'Healer'</text>
</outputEntry>
</rule>
<rule id="DecisionRule_0gehtk4">
<inputEntry id="UnaryTests_09f1t9t">
<text>'Etta'</text>
</inputEntry>
<outputEntry id="LiteralExpression_0kp8mvr">
<text>'Etta'</text>
</outputEntry>
<outputEntry id="LiteralExpression_0wwry9c">
<text>'Healer Mix'</text>
</outputEntry>
</rule>
</decisionTable>
</decision>
</definitions>

View File

@ -0,0 +1,66 @@
from tests.base_test import BaseTest
from crc import db, session
from crc.models.study import StudyModel
from crc.models.workflow import WorkflowState
from crc.services.study_service import StudyService
class TestStudyStatusMessage(BaseTest):
"""The workflow runs with a workflow_meta.name of `random_fact`
Add an entry to `status` dictionary with a key of `random_fact`"""
def run_update_status(self, status):
# shared code
self.load_example_data()
study_model = session.query(StudyModel).first()
workflow_metas = StudyService._get_workflow_metas(study_model.id)
warnings = StudyService._update_status_of_workflow_meta(workflow_metas, status)
return workflow_metas, warnings
def test_study_status_message(self):
# these are the passing tests
# we loop through each Workflow state
# (hidden,disabled,required,optional)
for state in WorkflowState:
# use state.value to set status['status'],
status = {'random_fact':
{'status': state.value,
'message': 'This is my status message!'}}
# call run_update_status(),
workflow_metas, warnings = self.run_update_status(status)
# and assert the values of workflow_metas[0].state and workflow_metas[0].state_message
self.assertEqual(0, len(warnings))
self.assertEqual(state, workflow_metas[0].state)
self.assertEqual('This is my status message!', workflow_metas[0].state_message)
def test_study_status_message_bad_name(self):
# we don't have an entry for you in the status dictionary
status = {'bad_name': {'status': 'hidden', 'message': 'This is my status message!'}}
workflow_metas, warnings = self.run_update_status(status)
self.assertEqual(1, len(warnings))
self.assertEqual('missing_status', warnings[0].code)
self.assertEqual('No status specified for workflow random_fact', warnings[0].message)
def test_study_status_message_not_dict(self):
# your entry in the status dictionary is not a dictionary
status = {'random_fact': 'This is my status message!'}
workflow_metas, warnings = self.run_update_status(status)
self.assertEqual(1, len(warnings))
self.assertEqual('invalid_status', warnings[0].code)
self.assertEqual('Status must be a dictionary with "status" and "message" keys. Name is random_fact. Status is This is my status message!',
warnings[0].message)
def test_study_status_message_bad_state(self):
# you have an invalid state
# I.e., not in (hidden,disabled,required,optional)
status = {'random_fact': {'status': 'hide', 'message': 'This is my status message!'}}
workflow_metas, warnings = self.run_update_status(status)
self.assertEqual(1, len(warnings))
self.assertEqual('invalid_state', warnings[0].code)
self.assertEqual('Workflow \'random_fact\' can not be set to \'hide\', should be one of hidden,disabled,required,optional',
warnings[0].message)

View File

@ -0,0 +1,15 @@
from tests.base_test import BaseTest
class TestDecisionTableDictionaryOutput(BaseTest):
def test_decision_table_dictionary_output(self):
workflow = self.create_workflow('decision_table_dictionary_output')
workflow_api = self.get_workflow_api(workflow)
first_task = workflow_api.next_task
result = self.complete_form(workflow, first_task, {'name': 'Mona'})
self.assertIn('dog', result.next_task.data)
self.assertEqual('Mona', result.next_task.data['dog']['name'])
self.assertEqual('Aussie Mix', result.next_task.data['dog']['breed'])