"Reset" should not mean "re-start". Calling the reset_workflow script will now set the workflow to an "unstarted" state with no outstanding tasks, no json-state structure stored. The workflow is not yet running.

Also:
* Assured that arguments are consistent (we always seem to use workflow_spec_id, so I made sure we use that consistently.
* Don't require named parameters - so it's cool to call it like: reset_workflow('my_workflow_id')
* Task Actions (ie create, assign, etc...) are now an enumeration in the models, and not static variables on Workflow Service, so we can reference them consistently from anywhere.
* Removed some repetitive code
* Always try to validate as much as possible in the scripts to save folks time debugging.
*
This commit is contained in:
Dan 2022-04-15 15:36:23 -04:00
parent 0072f4ecea
commit 5e54c90b47
18 changed files with 142 additions and 135 deletions

View File

@ -6,7 +6,7 @@ from crc import session
from crc.api.common import ApiError, ApiErrorSchema
from crc.models.api_models import WorkflowApiSchema
from crc.models.study import StudyModel, WorkflowMetadata, StudyStatus
from crc.models.task_event import TaskEventModel, TaskEvent, TaskEventSchema
from crc.models.task_event import TaskEventModel, TaskEvent, TaskEventSchema, TaskAction
from crc.models.workflow import WorkflowModel, WorkflowSpecInfoSchema, WorkflowSpecCategorySchema
from crc.services.error_service import ValidationErrorService
from crc.services.lookup_service import LookupService
@ -205,8 +205,10 @@ def get_workflow(workflow_id, do_engine_steps=True):
def restart_workflow(workflow_id, clear_data=False, delete_files=False):
"""Restart a workflow with the latest spec.
Clear data allows user to restart the workflow without previous data."""
# fixme: remove delete_files arg, clear_data is the only one respected.
workflow_model: WorkflowModel = session.query(WorkflowModel).filter_by(id=workflow_id).first()
processor = WorkflowProcessor.reset(workflow_model, clear_data=clear_data, delete_files=delete_files)
WorkflowProcessor.reset(workflow_model, clear_data=clear_data)
processor = WorkflowProcessor(workflow_model)
processor.do_engine_steps()
processor.save()
WorkflowService.update_task_assignments(processor)
@ -269,7 +271,7 @@ def set_current_task(workflow_id, task_id):
spiff_task.reset_token({}, reset_data=True) # Don't try to copy the existing data back into this task.
processor.save()
WorkflowService.log_task_action(user_uid, processor, spiff_task, WorkflowService.TASK_ACTION_TOKEN_RESET)
WorkflowService.log_task_action(user_uid, processor, spiff_task, TaskAction.TOKEN_RESET.value)
WorkflowService.update_task_assignments(processor)
workflow_api_model = WorkflowService.processor_to_workflow_api(processor, spiff_task)
@ -327,7 +329,7 @@ def __update_task(processor, task, data, user):
# Log the action before doing the engine steps, as doing so could effect the state of the task
# the workflow could wrap around in the ngine steps, and the task could jump from being completed to
# another state. What we are logging here is the completion.
WorkflowService.log_task_action(user.uid, processor, task, WorkflowService.TASK_ACTION_COMPLETE)
WorkflowService.log_task_action(user.uid, processor, task, TaskAction.COMPLETE.value)
processor.do_engine_steps()
processor.save()

View File

@ -1,3 +1,5 @@
import enum
from marshmallow import INCLUDE, fields
from marshmallow_sqlalchemy import SQLAlchemyAutoSchema
@ -7,6 +9,13 @@ from crc.models.workflow import WorkflowModel
from crc.services.ldap_service import LdapService
from sqlalchemy import func
class TaskAction(enum.Enum):
COMPLETE = "COMPLETE"
TOKEN_RESET = "TOKEN_RESET"
HARD_RESET = "HARD_RESET"
SOFT_RESET = "SOFT_RESET"
ASSIGNMENT = "ASSIGNMENT" # Whenever the lane changes between tasks we assign the task to specific user.
class TaskEventModel(db.Model):
__tablename__ = 'task_event'

View File

@ -2,7 +2,7 @@ from crc import session
from crc.api.common import ApiError
from crc.models.data_store import DataStoreModel
from crc.models.file import FileModel
from crc.models.task_event import TaskEventModel
from crc.models.task_event import TaskEventModel, TaskAction
from crc.scripts.script import Script
from crc.services.document_service import DocumentService
from crc.services.user_file_service import UserFileService
@ -36,7 +36,7 @@ class DeleteTaskData(Script):
# delete task events
session.query(TaskEventModel).filter(TaskEventModel.workflow_id == workflow_id).filter(
TaskEventModel.study_id == study_id).filter(TaskEventModel.task_name == task_spec_name).filter_by(
action=WorkflowService.TASK_ACTION_COMPLETE).delete()
action=TaskAction.COMPLETE.value).delete()
files_to_delete = session.query(FileModel). \
filter(FileModel.workflow_id == workflow_id). \

View File

@ -9,34 +9,42 @@ from crc.services.workflow_spec_service import WorkflowSpecService
class ResetWorkflow(Script):
def get_description(self):
return """Reset a workflow. Run by master workflow.
return """Reset a workflow. Run by mas vftgv ter workflow.
Designed for completed workflows where we need to force rerunning the workflow.
I.e., a new PI"""
def get_spec(self, *args, **kwargs):
workflow_spec_id = None
if 'workflow_spec_id' in kwargs.keys():
workflow_spec_id = kwargs['workflow_spec_id']
elif len(args) > 0:
workflow_spec_id = args[0]
if not workflow_spec_id:
raise ApiError(code='missing_workflow_id',
message='Reset workflow requires a workflow_spec_id')
workflow_spec = WorkflowSpecService().get_spec(workflow_spec_id)
if not workflow_spec:
raise ApiError(code='missing_workflow_spec',
message=f'No workflow spec found with the \
id: {workflow_spec_id}')
return workflow_spec
def do_task_validate_only(self, task, study_id, workflow_id, *args, **kwargs):
return hasattr(kwargs, 'reset_id')
self.get_spec(*args, **kwargs) # Just assure we can find the workflow spec.
def do_task(self, task, study_id, workflow_id, *args, **kwargs):
if 'reset_id' in kwargs.keys():
reset_id = kwargs['reset_id']
workflow_spec = WorkflowSpecService().get_spec(reset_id)
if workflow_spec:
workflow_model: WorkflowModel = session.query(WorkflowModel).filter_by(
workflow_spec_id=workflow_spec.id,
study_id=study_id).first()
if workflow_model:
workflow_processor = WorkflowProcessor.reset(workflow_model, clear_data=False, delete_files=False)
return workflow_processor
else:
raise ApiError(code='missing_workflow_model',
message=f'No WorkflowModel returned. \
workflow_spec_id: {workflow_spec.id} \
study_id: {study_id}')
else:
raise ApiError(code='missing_workflow_spec',
message=f'No WorkflowSpecModel returned. \
id: {workflow_id}')
if 'clear_data' in kwargs.keys():
clear_data = bool(kwargs['clear_data'])
else:
raise ApiError(code='missing_workflow_id',
message='Reset workflow requires a workflow id')
clear_data = False
workflow_spec = self.get_spec(*args, **kwargs)
if workflow_spec:
workflow_model: WorkflowModel = session.query(WorkflowModel).filter_by(
workflow_spec_id=workflow_spec.id,
study_id=study_id).first()
if workflow_model:
WorkflowProcessor.reset(workflow_model, clear_data=clear_data)

View File

@ -9,67 +9,40 @@ from crc.services.workflow_service import WorkflowService
class StartWorkflow(Script):
@staticmethod
def get_workflow(workflow_id):
workflow_model: WorkflowModel = session.query(WorkflowModel).filter_by(id=workflow_id).first()
processor = WorkflowProcessor(workflow_model)
processor.do_engine_steps()
processor.save()
WorkflowService.update_task_assignments(processor)
workflow_api_model = WorkflowService.processor_to_workflow_api(processor)
return WorkflowApiSchema().dump(workflow_api_model)
def get_description(self):
return """Script to start a workflow programmatically.
It requires a workflow_spec_id.
It accepts the workflow_spec_id as a positional argument
or with the keyword 'workflow_spec_id'"""
def do_task_validate_only(self, task, study_id, workflow_id, *args, **kwargs):
def get_workflow(self, study_id, *args, **kwargs):
if len(args) == 1 or 'workflow_spec_id' in kwargs:
if 'workflow_spec_id' in kwargs:
workflow_spec_id = kwargs['workflow_spec_id']
else:
workflow_spec_id = args[0]
workflow_api = WorkflowApi(1234,
WorkflowStatus('user_input_required'),
'next_task',
'navigation',
workflow_spec_id,
'total_tasks',
'completed_tasks',
'last_updated',
'is_review',
'title',
study_id)
return WorkflowApiSchema().dump(workflow_api)
else:
raise ApiError(code='missing_parameter',
message=f'The start_workflow script requires a workflow id')
workflow = session.query(WorkflowModel).\
filter(WorkflowModel.study_id==study_id).\
filter(WorkflowModel.workflow_spec_id==workflow_spec_id).\
first()
if not(workflow):
raise ApiError(code='unknown_workflow',
message=f"We could not find a workflow with workflow_spec_id '{workflow_spec_id}'.")
return workflow
def do_task_validate_only(self, task, study_id, workflow_id, *args, **kwargs):
self.get_workflow(study_id, *args, **kwargs)
def do_task(self, task, study_id, workflow_id, *args, **kwargs):
if len(args) == 1 or 'workflow_spec_id' in kwargs:
if 'workflow_spec_id' in kwargs:
workflow_spec_id = kwargs['workflow_spec_id']
else:
workflow_spec_id = args[0]
workflow_model = self.get_workflow(study_id, *args, **kwargs)
processor = WorkflowProcessor(workflow_model)
processor.do_engine_steps()
processor.save()
WorkflowService.update_task_assignments(processor)
workflow = session.query(WorkflowModel).\
filter(WorkflowModel.study_id==study_id).\
filter(WorkflowModel.workflow_spec_id==workflow_spec_id).\
first()
if workflow:
workflow_api = self.get_workflow(workflow.id)
return workflow_api
else:
raise ApiError(code='unknown_workflow',
message=f"We could not find a workflow with workflow_spec_id '{workflow_spec_id}'.")
else:
raise ApiError(code='missing_parameter',
message=f'The start_workflow script requires a workflow id')

View File

@ -20,7 +20,7 @@ from SpiffWorkflow.specs import WorkflowSpec
from crc import session
from crc.api.common import ApiError
from crc.models.file import FileModel, FileType, File
from crc.models.task_event import TaskEventModel
from crc.models.task_event import TaskEventModel, TaskAction
from crc.models.user import UserModelSchema
from crc.models.workflow import WorkflowStatus, WorkflowModel, WorkflowSpecInfo
from crc.scripts.script import Script
@ -171,7 +171,13 @@ class WorkflowProcessor(object):
task.data['current_user'] = current_user_data
@staticmethod
def reset(workflow_model, clear_data=False, delete_files=False):
def reset(workflow_model, clear_data=False):
"""Resets the workflow back to an unstarted state - where nothing has
happened yet. If clear_data is set to false, then the information
previously used in forms will be re-populated when the form is re-
displayed, and any files that were updated will remain in place, otherwise
files will also be cleared out."""
# Try to execute a cancel notify
try:
bpmn_workflow = WorkflowProcessor.__get_bpmn_workflow(workflow_model)
@ -182,19 +188,27 @@ class WorkflowProcessor(object):
f" state. An %s error occured with the following information: %s" %
(workflow_model.id, e.__class__.__name__, str(e)))
workflow_model.bpmn_workflow_json = None
workflow_model.status = WorkflowStatus.not_started
# clear out any task assignments
session.query(TaskEventModel). \
filter(TaskEventModel.workflow_id == workflow_model.id). \
filter(TaskEventModel.action == TaskAction.ASSIGNMENT.value).delete()
if clear_data:
# Clear form_data from task_events
# Clear out data in previous task events
task_events = session.query(TaskEventModel). \
filter(TaskEventModel.workflow_id == workflow_model.id).all()
for task_event in task_events:
task_event.form_data = {}
session.add(task_event)
if delete_files:
# Remove any uploaded files.
files = FileModel.query.filter(FileModel.workflow_id == workflow_model.id).all()
for file in files:
UserFileService.delete_file(file.id)
session.commit()
return WorkflowProcessor(workflow_model)
@staticmethod
def __get_bpmn_workflow(workflow_model: WorkflowModel, spec: WorkflowSpec = None, validate_only=False):

View File

@ -27,7 +27,7 @@ from crc.models.api_models import Task, MultiInstanceType, WorkflowApi
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.task_event import TaskEventModel, TaskAction
from crc.models.user import UserModel
from crc.models.workflow import WorkflowModel, WorkflowStatus
from crc.services.data_store_service import DataStoreBase
@ -45,11 +45,6 @@ from flask import g
class WorkflowService(object):
TASK_ACTION_COMPLETE = "COMPLETE"
TASK_ACTION_TOKEN_RESET = "TOKEN_RESET"
TASK_ACTION_HARD_RESET = "HARD_RESET"
TASK_ACTION_SOFT_RESET = "SOFT_RESET"
TASK_ACTION_ASSIGNMENT = "ASSIGNMENT" # Whenever the lane changes between tasks we assign the task to specifc user.
TASK_STATE_LOCKED = "LOCKED" # When the task belongs to a different user.
@ -723,7 +718,7 @@ class WorkflowService(object):
query = db.session.query(TaskEventModel) \
.filter_by(workflow_id=workflow_id) \
.filter_by(task_name=spiff_task.task_spec.name) \
.filter_by(action=WorkflowService.TASK_ACTION_COMPLETE)
.filter_by(action=TaskAction.COMPLETE.value)
if hasattr(spiff_task, 'internal_data') and 'runtimes' in spiff_task.internal_data:
query = query.filter_by(mi_index=spiff_task.internal_data['runtimes'])
@ -976,14 +971,14 @@ class WorkflowService(object):
should be called whenever progress is made on a workflow."""
db.session.query(TaskEventModel). \
filter(TaskEventModel.workflow_id == processor.workflow_model.id). \
filter(TaskEventModel.action == WorkflowService.TASK_ACTION_ASSIGNMENT).delete()
filter(TaskEventModel.action == TaskAction.ASSIGNMENT.value).delete()
db.session.commit()
tasks = processor.get_current_user_tasks()
for task in tasks:
user_ids = WorkflowService.get_users_assigned_to_task(processor, task)
for user_id in user_ids:
WorkflowService.log_task_action(user_id, processor, task, WorkflowService.TASK_ACTION_ASSIGNMENT)
WorkflowService.log_task_action(user_id, processor, task, TaskAction.ASSIGNMENT.value)
@staticmethod
def get_users_assigned_to_task(processor, spiff_task) -> List[str]:
@ -1121,13 +1116,4 @@ class WorkflowService(object):
db.session.commit()
return workflow_model
@staticmethod
def delete_workflow_spec_task_events(spec_id):
session.query(TaskEventModel).filter(TaskEventModel.workflow_spec_id == spec_id).delete()
session.commit()
@staticmethod
def delete_workflow_spec_workflow_models(spec_id):
for workflow in session.query(WorkflowModel).filter_by(workflow_spec_id=spec_id):
StudyService.delete_workflow(workflow.id)

5
package-lock.json generated
View File

@ -1,3 +1,6 @@
{
"lockfileVersion": 1
"name": "cr-connect-workflow",
"lockfileVersion": 2,
"requires": true,
"packages": {}
}

View File

@ -15,7 +15,7 @@ from flask import g
from crc import app, db, session
from crc.models.api_models import WorkflowApiSchema, MultiInstanceType
from crc.models.file import FileModel, CONTENT_TYPES
from crc.models.task_event import TaskEventModel
from crc.models.task_event import TaskEventModel, TaskAction
from crc.models.study import StudyModel, StudyStatus, ProgressStatus
from crc.models.user import UserModel
from crc.models.workflow import WorkflowSpecCategory
@ -368,7 +368,7 @@ class BaseTest(unittest.TestCase):
task_events = session.query(TaskEventModel) \
.filter_by(workflow_id=workflow.id) \
.filter_by(task_id=task_id) \
.filter_by(action=WorkflowService.TASK_ACTION_COMPLETE) \
.filter_by(action=TaskAction.COMPLETE.value) \
.order_by(TaskEventModel.date.desc()).all()
self.assertGreater(len(task_events), 0)
event = task_events[0]
@ -377,7 +377,7 @@ class BaseTest(unittest.TestCase):
self.assertEqual(user_uid, event.user_uid)
self.assertEqual(workflow.id, event.workflow_id)
self.assertEqual(workflow.workflow_spec_id, event.workflow_spec_id)
self.assertEqual(WorkflowService.TASK_ACTION_COMPLETE, event.action)
self.assertEqual(TaskAction.COMPLETE.value, event.action)
self.assertEqual(task_in.id, task_id)
self.assertEqual(task_in.name, event.task_name)
self.assertEqual(task_in.title, event.task_title)

View File

@ -1,5 +1,5 @@
<?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_0vny0hv" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.5.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_0vny0hv" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="4.10.0">
<bpmn:process id="Process_ResetWorkflow" name="Reset Workflow" isExecutable="true">
<bpmn:documentation>Use this process to reset a workflow for the current study. You must enter the name of the workflow. I.e., lower case with underscores.</bpmn:documentation>
<bpmn:startEvent id="StartEvent_1">
@ -15,7 +15,7 @@
<bpmn:userTask id="Task_GetWorkflow" name="Get Workflow" camunda:formKey="WorkflowForm">
<bpmn:extensionElements>
<camunda:formData>
<camunda:formField id="workflow_name" label="'Workflow Name'" type="string">
<camunda:formField id="workflow_name" label="&#39;Workflow Name&#39;" type="string" defaultValue="&#34;reset_workflow&#34;">
<camunda:validation>
<camunda:constraint name="required" config="True" />
</camunda:validation>
@ -28,7 +28,7 @@
<bpmn:scriptTask id="Task_ResetWorkflow" name="Reset Workflow">
<bpmn:incoming>SequenceFlow_1q2ton3</bpmn:incoming>
<bpmn:outgoing>SequenceFlow_0x127gc</bpmn:outgoing>
<bpmn:script>value = reset_workflow(workflow_name=workflow_name)</bpmn:script>
<bpmn:script>value = reset_workflow(workflow_name)</bpmn:script>
</bpmn:scriptTask>
<bpmn:manualTask id="Task_DisplayWorkflow" name="Display Workflow">
<bpmn:documentation># Reset Workflow
@ -46,28 +46,28 @@
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_ResetWorkflow">
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="179" y="99" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="SequenceFlow_0i872g2_di" bpmnElement="SequenceFlow_0i872g2">
<di:waypoint x="215" y="117" />
<di:waypoint x="270" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="SequenceFlow_1q2ton3_di" bpmnElement="SequenceFlow_1q2ton3">
<di:waypoint x="370" y="117" />
<di:waypoint x="430" y="117" />
<bpmndi:BPMNEdge id="SequenceFlow_0yy50p2_di" bpmnElement="SequenceFlow_0yy50p2">
<di:waypoint x="690" y="117" />
<di:waypoint x="752" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="SequenceFlow_0x127gc_di" bpmnElement="SequenceFlow_0x127gc">
<di:waypoint x="530" y="117" />
<di:waypoint x="590" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="SequenceFlow_1q2ton3_di" bpmnElement="SequenceFlow_1q2ton3">
<di:waypoint x="370" y="117" />
<di:waypoint x="430" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="SequenceFlow_0i872g2_di" bpmnElement="SequenceFlow_0i872g2">
<di:waypoint x="215" y="117" />
<di:waypoint x="270" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="179" y="99" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="EndEvent_0fdym05_di" bpmnElement="EndEvent_0fdym05">
<dc:Bounds x="752" y="99" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="SequenceFlow_0yy50p2_di" bpmnElement="SequenceFlow_0yy50p2">
<di:waypoint x="690" y="117" />
<di:waypoint x="752" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="UserTask_0li5ksb_di" bpmnElement="Task_GetWorkflow">
<dc:Bounds x="270" y="77" width="100" height="80" />
</bpmndi:BPMNShape>

View File

@ -1,5 +1,5 @@
<?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_0a7bvlf" 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_0a7bvlf" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="4.10.0">
<bpmn:process id="Process_0inkg2m" name="Start Workflow Programmatically" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>Flow_0ac3s7d</bpmn:outgoing>
@ -14,7 +14,7 @@
<bpmn:userTask id="Activity_0n0md5g" name="Get Required Data" camunda:formKey="DataForm">
<bpmn:extensionElements>
<camunda:formData>
<camunda:formField id="workflow_spec_to_start" label="&#39;Workflow Spec&#39;" type="string">
<camunda:formField id="workflow_spec_to_start" label="&#39;Workflow Spec&#39;" type="string" defaultValue="&#39;random_fact&#39;">
<camunda:validation>
<camunda:constraint name="required" config="True" />
</camunda:validation>

View File

@ -4,7 +4,7 @@ from crc import session
from crc.models.data_store import DataStoreModel
from crc.models.file import FileModel
from crc.models.task_event import TaskEventModel
from crc.models.task_event import TaskEventModel, TaskAction
from crc.services.workflow_service import WorkflowService
from io import BytesIO
@ -100,7 +100,7 @@ class TestDeleteTaskData(BaseTest):
# Make sure we have something in task_events
task_events = session.query(TaskEventModel).\
filter(TaskEventModel.workflow_id == workflow.id).\
filter(TaskEventModel.action == WorkflowService.TASK_ACTION_COMPLETE).all()
filter(TaskEventModel.action == TaskAction.COMPLETE.value).all()
for task_event in task_events:
self.assertNotEqual({}, task_event.form_data)
@ -117,7 +117,7 @@ class TestDeleteTaskData(BaseTest):
files = session.query(FileModel).filter(FileModel.workflow_id == workflow.id).all()
task_events = session.query(TaskEventModel).\
filter(TaskEventModel.workflow_id == workflow.id).\
filter(TaskEventModel.action == WorkflowService.TASK_ACTION_COMPLETE).all()
filter(TaskEventModel.action == TaskAction.COMPLETE.value).all()
self.assertEqual(0, len(data_stores))
self.assertEqual(0, len(data_stores_1))

View File

@ -22,6 +22,7 @@ class TestStartWorkflow(BaseTest):
return workflow
def test_start_workflow_validation(self):
random_wf = self.create_workflow('random_fact') # Assure we have a workflow to start.
spec_model = self.load_test_spec('start_workflow')
rv = self.app.get('/v1.0/workflow-specification/%s/validate' % spec_model.id, headers=self.logged_in_headers())
self.assertEqual([], rv.json)

View File

@ -63,7 +63,8 @@ class TestLookupService(BaseTest):
file.close()
# restart the workflow, so it can pick up the changes.
processor = WorkflowProcessor.reset(workflow)
WorkflowProcessor.reset(workflow)
processor = WorkflowProcessor(workflow)
workflow = processor.workflow_model
LookupService.lookup(workflow, "Task_Enum_Lookup", "sponsor", "sam", limit=10)
@ -100,7 +101,8 @@ class TestLookupService(BaseTest):
results = LookupService.lookup(workflow, task.task_spec.name, "selectedItem", "", value="apples", limit=10)
self.assertEqual(0, len(results), "We shouldn't find our fruits mixed in with our animals.")
processor = WorkflowProcessor.reset(workflow, clear_data=True)
WorkflowProcessor.reset(workflow, clear_data=True)
processor = WorkflowProcessor(workflow)
processor.do_engine_steps()
task = processor.get_ready_user_tasks()[0]
task.data = {"type": "fruits"}

View File

@ -6,7 +6,7 @@ from crc.models.api_models import NavigationItemSchema
from crc.models.workflow import WorkflowStatus
from crc import db
from crc.api.common import ApiError
from crc.models.task_event import TaskEventModel, TaskEventSchema
from crc.models.task_event import TaskEventModel, TaskEventSchema, TaskAction
from crc.services.workflow_service import WorkflowService
@ -82,7 +82,7 @@ class TestUserRoles(BaseTest):
# the supervisor.
task_logs = db.session.query(TaskEventModel). \
filter(TaskEventModel.user_uid == supervisor.uid). \
filter(TaskEventModel.action == WorkflowService.TASK_ACTION_ASSIGNMENT).all()
filter(TaskEventModel.action == TaskAction.ASSIGNMENT.value).all()
self.assertEqual(1, len(task_logs))
# A call to the /task endpoint as the supervisor user should return a list of
@ -213,7 +213,7 @@ class TestUserRoles(BaseTest):
def get_assignment_task_events(self, uid):
return db.session.query(TaskEventModel). \
filter(TaskEventModel.user_uid == uid). \
filter(TaskEventModel.action == WorkflowService.TASK_ACTION_ASSIGNMENT).all()
filter(TaskEventModel.action == TaskAction.ASSIGNMENT.value).all()
def test_workflow_reset_correctly_resets_the_task_events(self):

View File

@ -270,7 +270,8 @@ class TestWorkflowProcessor(BaseTest):
# self.assertFalse(processor2.is_latest_spec) # Still at version 1.
# Do a hard reset, which should bring us back to the beginning, but retain the data.
processor2 = WorkflowProcessor.reset(processor2.workflow_model)
WorkflowProcessor.reset(processor2.workflow_model)
processor2 = WorkflowProcessor(processor2.workflow_model)
processor3 = WorkflowProcessor(processor.workflow_model)
processor3.do_engine_steps()
self.assertEqual("Step 1", processor3.next_task().task_spec.description)

View File

@ -23,7 +23,7 @@ class TestWorkflowReset(BaseTest):
second_task = workflow_api.next_task
self.assertEqual('Task_GetAge', second_task.name)
ResetWorkflow().do_task(second_task, workflow.study_id, workflow.id, reset_id='two_user_tasks')
ResetWorkflow().do_task(second_task, workflow.study_id, workflow.id, workflow_spec_id='two_user_tasks')
workflow_api = self.get_workflow_api(workflow)
task = workflow_api.next_task
@ -43,4 +43,12 @@ class TestWorkflowReset(BaseTest):
first_task = workflow_api.next_task
with self.assertRaises(ApiError):
ResetWorkflow().do_task(first_task, workflow.study_id, workflow.id, reset_id='bad_workflow_name')
ResetWorkflow().do_task(first_task, workflow.study_id, workflow.id, workflow_spec_id='bad_workflow_name')
def test_workflow_reset_no_start(self):
"""Sometimes we want to reset the workflow, but not start it up (don't do the engine steps etc...)"""
workflow = self.create_workflow('two_user_tasks')
workflow_api = self.get_workflow_api(workflow)
task = workflow_api.next_task
ResetWorkflow().do_task(task, workflow.study_id, workflow.id, workflow_spec_id='two_user_tasks')

View File

@ -68,7 +68,7 @@ class TestWorkflowRestart(BaseTest):
self.assertEqual(True, IsFileUploaded.do_task(
IsFileUploaded, first_task, study_id, workflow.id, irb_code))
workflow_api = self.restart_workflow_api(workflow_api, delete_files=True)
workflow_api = self.restart_workflow_api(workflow_api, clear_data=True)
first_task = workflow_api.next_task
# Assert we do not have the file