diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/api.yml b/spiffworkflow-backend/src/spiffworkflow_backend/api.yml
index 2a247863..304fa4e1 100755
--- a/spiffworkflow-backend/src/spiffworkflow_backend/api.yml
+++ b/spiffworkflow-backend/src/spiffworkflow_backend/api.yml
@@ -1565,7 +1565,7 @@ paths:
schema:
type: string
post:
- operationId: spiffworkflow_backend.routes.process_api_blueprint.mark_task_complete
+ operationId: spiffworkflow_backend.routes.process_api_blueprint.manual_complete_task
summary: Mark a task complete without executing it
tags:
- Process Instances
diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py
index b8db2dab..2b1f1306 100644
--- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py
+++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py
@@ -28,11 +28,6 @@ from lxml import etree # type: ignore
from lxml.builder import ElementMaker # type: ignore
from SpiffWorkflow.task import Task as SpiffTask # type: ignore
from SpiffWorkflow.task import TaskState
-from sqlalchemy import and_
-from sqlalchemy import asc
-from sqlalchemy import desc
-from sqlalchemy import or_
-
from spiffworkflow_backend.exceptions.process_entity_not_found_error import (
ProcessEntityNotFoundError,
)
@@ -99,6 +94,10 @@ from spiffworkflow_backend.services.secret_service import SecretService
from spiffworkflow_backend.services.service_task_service import ServiceTaskService
from spiffworkflow_backend.services.spec_file_service import SpecFileService
from spiffworkflow_backend.services.user_service import UserService
+from sqlalchemy import and_
+from sqlalchemy import asc
+from sqlalchemy import desc
+from sqlalchemy import or_
class TaskDataSelectOption(TypedDict):
@@ -2189,23 +2188,24 @@ def send_bpmn_event(
)
-def mark_task_complete(
+def manual_complete_task(
modified_process_model_identifier: str,
process_instance_id: str,
task_id: str,
body: Dict,
) -> Response:
"""Mark a task complete without executing it."""
+ execute = body.get("execute", True)
process_instance = ProcessInstanceModel.query.filter(
ProcessInstanceModel.id == int(process_instance_id)
).first()
if process_instance:
processor = ProcessInstanceProcessor(process_instance)
- processor.mark_task_complete(task_id)
+ processor.manual_complete_task(task_id, execute)
else:
raise ApiError(
- error_code="send_bpmn_event_error",
- message=f"Could not skip Task {task_id} in Instance {process_instance_id}",
+ error_code="complete_task",
+ message=f"Could not complete Task {task_id} in Instance {process_instance_id}",
)
return Response(
json.dumps(ProcessInstanceModelSchema().dump(process_instance)),
diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py
index 79de71f0..cd838d79 100644
--- a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py
+++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py
@@ -621,7 +621,7 @@ class ProcessInstanceProcessor:
db.session.add(pim)
db.session.commit()
- def save(self) -> None:
+ def _save(self) -> None:
"""Saves the current state of this processor to the database."""
self.process_instance_model.bpmn_json = self.serialize()
@@ -643,6 +643,9 @@ class ProcessInstanceProcessor:
db.session.add(self.process_instance_model)
db.session.commit()
+ def save(self) -> None:
+ """Saves the current state and moves on to the next state."""
+ self._save()
human_tasks = HumanTaskModel.query.filter_by(
process_instance_id=self.process_instance_model.id
).all()
@@ -729,17 +732,25 @@ class ProcessInstanceProcessor:
self.bpmn_process_instance.catch(event_definition)
self.do_engine_steps(save=True)
- def mark_task_complete(self, task_id: str) -> None:
- """Mark the task complete without executing it."""
+ def manual_complete_task(self, task_id: str, execute: bool) -> None:
+ """Mark the task complete optionally executing it."""
spiff_task = self.bpmn_process_instance.get_task(UUID(task_id))
- spiff_task._set_state(TaskState.COMPLETED)
+ if execute:
+ current_app.logger.info(
+ f"Manually executing Task {spiff_task.task_spec.name} of process instance {self.process_instance_model.id}"
+ )
+ spiff_task.complete()
+ else:
+ current_app.logger.info(
+ f"Skipping Task {spiff_task.task_spec.name} of process instance {self.process_instance_model.id}"
+ )
+ spiff_task._set_state(TaskState.COMPLETED)
+ for child in spiff_task.children:
+ child.task_spec._update(child)
self.bpmn_process_instance.last_task = spiff_task
- for child in spiff_task.children:
- child.task_spec._update(child)
- current_app.logger.info(
- f"Task {spiff_task.task_spec.name} of process instance {self.process_instance_model.id} skipped"
- )
- self.do_engine_steps(save=True)
+ self._save()
+ # Saving the workflow seems to reset the status
+ self.suspend()
@staticmethod
def get_parser() -> MyCustomParser:
diff --git a/spiffworkflow-frontend/src/routes/ProcessInstanceShow.tsx b/spiffworkflow-frontend/src/routes/ProcessInstanceShow.tsx
index d984b664..678ebdf2 100644
--- a/spiffworkflow-frontend/src/routes/ProcessInstanceShow.tsx
+++ b/spiffworkflow-frontend/src/routes/ProcessInstanceShow.tsx
@@ -529,6 +529,8 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
// We actually could allow this for any waiting events
const taskTypes = ['Event Based Gateway'];
return (
+ processInstance &&
+ processInstance.status === 'waiting' &&
ability.can('POST', targetUris.processInstanceSendEventPath) &&
taskTypes.filter((t) => t === task.type).length > 0 &&
task.state === 'WAITING' &&
@@ -536,8 +538,10 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
);
};
- const canMarkTaskComplete = (task: any) => {
+ const canCompleteTask = (task: any) => {
return (
+ processInstance &&
+ processInstance.status === 'suspended' &&
ability.can('POST', targetUris.processInstanceCompleteTaskPath) &&
task.state === 'READY' &&
showingLastSpiffStep()
@@ -546,6 +550,7 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
const canResetProcess = (task: any) => {
return (
+ ability.can('POST', targetUris.processInstanceResetPath) &&
processInstance &&
processInstance.status === 'suspended' &&
task.state === 'READY' &&
@@ -627,13 +632,14 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
});
};
- const markTaskComplete = () => {
+ const completeTask = (execute: boolean) => {
const taskToUse: any = taskToDisplay;
HttpService.makeCallToBackend({
path: `/task-complete/${modifiedProcessModelId}/${params.process_instance_id}/${taskToUse.id}`,
httpMethod: 'POST',
successCallback: saveTaskDataResult,
failureCallback: saveTaskDataFailure,
+ postBody: { execute },
});
};
@@ -705,15 +711,23 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
);
}
- if (canMarkTaskComplete(task)) {
+ if (canCompleteTask(task)) {
buttons.push(
);
+ buttons.push(
+
+ );
}
if (canSendEvent(task)) {
buttons.push(