Add the ability to forcibly restart a workflow, while retaining that workflows data.
A workflow specification knows it's version number, which is generated by the version of the files that make it up. A workflow specification version number is the primary file (the lead BPMN) followed by a consistency ordered version each extra file associated with the workflow. A change in any file modifies the specifications version.
This commit is contained in:
parent
d0f0acc8cf
commit
78b6f040eb
|
@ -103,11 +103,17 @@ class WorkflowProcessor(object):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_spec(workflow_spec_id):
|
def get_spec(workflow_spec_id):
|
||||||
|
"""Returns the last version of the specification."""
|
||||||
parser = WorkflowProcessor.get_parser()
|
parser = WorkflowProcessor.get_parser()
|
||||||
|
major_version = 0 # The version of the pirmary file.
|
||||||
|
minor_version = [] # The versions of the minor files if any.
|
||||||
process_id = None
|
process_id = None
|
||||||
file_data_models = session.query(FileDataModel) \
|
file_data_models = session.query(FileDataModel) \
|
||||||
.join(FileModel) \
|
.join(FileModel) \
|
||||||
.filter(FileModel.workflow_spec_id == workflow_spec_id).all()
|
.filter(FileModel.workflow_spec_id == workflow_spec_id)\
|
||||||
|
.filter(FileDataModel.version == FileModel.latest_version)\
|
||||||
|
.order_by(FileModel.id)\
|
||||||
|
.all()
|
||||||
for file_data in file_data_models:
|
for file_data in file_data_models:
|
||||||
if file_data.file_model.type == FileType.bpmn:
|
if file_data.file_model.type == FileType.bpmn:
|
||||||
bpmn: ElementTree.Element = ElementTree.fromstring(file_data.data)
|
bpmn: ElementTree.Element = ElementTree.fromstring(file_data.data)
|
||||||
|
@ -117,9 +123,16 @@ class WorkflowProcessor(object):
|
||||||
elif file_data.file_model.type == FileType.dmn:
|
elif file_data.file_model.type == FileType.dmn:
|
||||||
dmn: ElementTree.Element = ElementTree.fromstring(file_data.data)
|
dmn: ElementTree.Element = ElementTree.fromstring(file_data.data)
|
||||||
parser.add_dmn_xml(dmn, filename=file_data.file_model.name)
|
parser.add_dmn_xml(dmn, filename=file_data.file_model.name)
|
||||||
|
if file_data.file_model.primary:
|
||||||
|
major_version = file_data.version
|
||||||
|
else:
|
||||||
|
minor_version.append(file_data.version)
|
||||||
if process_id is None:
|
if process_id is None:
|
||||||
raise(Exception("There is no primary BPMN model defined for workflow %s" % workflow_spec_id))
|
raise(Exception("There is no primary BPMN model defined for workflow %s" % workflow_spec_id))
|
||||||
return parser.get_spec(process_id)
|
minor_version.insert(0, major_version) # Add major version to begining.
|
||||||
|
spec = parser.get_spec(process_id)
|
||||||
|
spec.description = ".".join(str(x) for x in minor_version)
|
||||||
|
return spec
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -144,6 +157,19 @@ class WorkflowProcessor(object):
|
||||||
session.commit()
|
session.commit()
|
||||||
return processor
|
return processor
|
||||||
|
|
||||||
|
def restart_with_current_task_data(self):
|
||||||
|
"""Recreate this workflow, but keep the data from the last completed task and add it back into the first task.
|
||||||
|
This may be useful when a workflow specification changes, and users need to review all the
|
||||||
|
prior steps, but don't need to reenter all the previous data. """
|
||||||
|
spec = WorkflowProcessor.get_spec(self.workflow_spec_id)
|
||||||
|
bpmn_workflow = BpmnWorkflow(spec, script_engine=self._script_engine)
|
||||||
|
bpmn_workflow.data = self.bpmn_workflow.data
|
||||||
|
for task in bpmn_workflow.get_tasks(SpiffTask.READY):
|
||||||
|
task.data = self.bpmn_workflow.last_task.data
|
||||||
|
bpmn_workflow.do_engine_steps()
|
||||||
|
self.bpmn_workflow = bpmn_workflow
|
||||||
|
|
||||||
|
|
||||||
def get_status(self):
|
def get_status(self):
|
||||||
if self.bpmn_workflow.is_completed():
|
if self.bpmn_workflow.is_completed():
|
||||||
return WorkflowStatus.complete
|
return WorkflowStatus.complete
|
||||||
|
@ -153,6 +179,10 @@ class WorkflowProcessor(object):
|
||||||
else:
|
else:
|
||||||
return WorkflowStatus.waiting
|
return WorkflowStatus.waiting
|
||||||
|
|
||||||
|
def get_spec_version(self):
|
||||||
|
"""We use the spec's descrption field to store the version information"""
|
||||||
|
return self.bpmn_workflow.spec.description
|
||||||
|
|
||||||
def do_engine_steps(self):
|
def do_engine_steps(self):
|
||||||
self.bpmn_workflow.do_engine_steps()
|
self.bpmn_workflow.do_engine_steps()
|
||||||
|
|
||||||
|
|
|
@ -203,6 +203,3 @@ class TestTasksApi(BaseTest):
|
||||||
self.assertIsNotNone(workflow_api.next_task['documentation'])
|
self.assertIsNotNone(workflow_api.next_task['documentation'])
|
||||||
self.assertTrue("norris" in workflow_api.next_task['documentation'])
|
self.assertTrue("norris" in workflow_api.next_task['documentation'])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# response = ProtocolBuilderService.get_study_details(self.test_study_id)
|
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
|
import os
|
||||||
import string
|
import string
|
||||||
import random
|
import random
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from SpiffWorkflow.bpmn.specs.EndEvent import EndEvent
|
from SpiffWorkflow.bpmn.specs.EndEvent import EndEvent
|
||||||
|
|
||||||
from crc import session
|
from crc import session, db, app
|
||||||
from crc.api.common import ApiError
|
from crc.api.common import ApiError
|
||||||
from crc.models.file import FileModel, FileDataModel
|
from crc.models.file import FileModel, FileDataModel
|
||||||
from crc.models.study import StudyModel
|
from crc.models.study import StudyModel
|
||||||
from crc.models.workflow import WorkflowSpecModel, WorkflowStatus
|
from crc.models.workflow import WorkflowSpecModel, WorkflowStatus, WorkflowModel
|
||||||
|
from crc.services.file_service import FileService
|
||||||
from tests.base_test import BaseTest
|
from tests.base_test import BaseTest
|
||||||
from crc.services.workflow_processor import WorkflowProcessor
|
from crc.services.workflow_processor import WorkflowProcessor
|
||||||
|
|
||||||
|
@ -214,3 +216,41 @@ class TestWorkflowProcessor(BaseTest):
|
||||||
self.assertIn("last_updated", task.data["study"]["info"])
|
self.assertIn("last_updated", task.data["study"]["info"])
|
||||||
self.assertIn("sponsor", task.data["study"]["info"])
|
self.assertIn("sponsor", task.data["study"]["info"])
|
||||||
|
|
||||||
|
def test_spec_versioning(self):
|
||||||
|
self.load_example_data()
|
||||||
|
study = session.query(StudyModel).first()
|
||||||
|
workflow_spec_model = self.load_test_spec("decision_table")
|
||||||
|
processor = WorkflowProcessor.create(study.id, workflow_spec_model.id)
|
||||||
|
self.assertEquals("1.1", processor.get_spec_version())
|
||||||
|
file_service = FileService()
|
||||||
|
|
||||||
|
file_service.add_workflow_spec_file(workflow_spec_model, "new_file.txt", "txt", b'blahblah')
|
||||||
|
processor = WorkflowProcessor.create(study.id, workflow_spec_model.id)
|
||||||
|
self.assertEquals("1.1.1", processor.get_spec_version())
|
||||||
|
|
||||||
|
file_path = os.path.join(app.root_path, '..', 'tests', 'data', 'docx', 'docx.bpmn')
|
||||||
|
file = open(file_path, "rb")
|
||||||
|
data = file.read()
|
||||||
|
|
||||||
|
file_model = db.session.query(FileModel).filter(FileModel.name == "decision_table.bpmn").first()
|
||||||
|
file_service.update_file(file_model, data, "txt")
|
||||||
|
processor = WorkflowProcessor.create(study.id, workflow_spec_model.id)
|
||||||
|
self.assertEquals("2.1.1", processor.get_spec_version())
|
||||||
|
|
||||||
|
def test_restart_workflow(self):
|
||||||
|
self.load_example_data()
|
||||||
|
study = session.query(StudyModel).first()
|
||||||
|
workflow_spec_model = self.load_test_spec("two_forms")
|
||||||
|
processor = WorkflowProcessor.create(study.id, workflow_spec_model.id)
|
||||||
|
workflow_model = db.session.query(WorkflowModel).filter(WorkflowModel.study_id == study.id).first()
|
||||||
|
self.assertEqual(workflow_model.workflow_spec_id, workflow_spec_model.id)
|
||||||
|
task = processor.next_task()
|
||||||
|
task.data = {"key": "Value"}
|
||||||
|
processor.complete_task(task)
|
||||||
|
task_before_restart = processor.next_task()
|
||||||
|
processor.restart_with_current_task_data()
|
||||||
|
task_after_restart = processor.next_task()
|
||||||
|
|
||||||
|
self.assertNotEqual(task.get_name(), task_before_restart.get_name())
|
||||||
|
self.assertEqual(task.get_name(), task_after_restart.get_name())
|
||||||
|
self.assertEqual(task.data, task_after_restart.data)
|
Loading…
Reference in New Issue