From 92b9ca3995ac622f63679c8c57cb9015a889d5d0 Mon Sep 17 00:00:00 2001 From: jasquat <2487833+jasquat@users.noreply.github.com> Date: Wed, 26 Jul 2023 11:08:17 -0400 Subject: [PATCH] attempt to save again if deadlock given for draft data save w/ burnettk (#410) Co-authored-by: jasquat --- .../routes/tasks_controller.py | 41 +++++++++++++++---- .../src/routes/TaskShow.tsx | 14 +------ 2 files changed, 35 insertions(+), 20 deletions(-) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/tasks_controller.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/tasks_controller.py index 936a031c..5af444de 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/tasks_controller.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/tasks_controller.py @@ -14,6 +14,7 @@ from flask import jsonify from flask import make_response from flask import stream_with_context from flask.wrappers import Response +from MySQLdb import OperationalError # type: ignore from SpiffWorkflow.bpmn.exceptions import WorkflowTaskException # type: ignore from SpiffWorkflow.bpmn.workflow import BpmnWorkflow # type: ignore from SpiffWorkflow.task import Task as SpiffTask # type: ignore @@ -586,13 +587,21 @@ def task_save_draft( principal = _find_principal_or_raise() process_instance = _find_process_instance_by_id_or_raise(process_instance_id) if not process_instance.can_submit_task(): - raise ApiError( - error_code="process_instance_not_runnable", - message=( - f"Process Instance ({process_instance.id}) has status " - f"{process_instance.status} which does not allow draft data to be saved." + return Response( + json.dumps( + { + "ok": True, + "saved": False, + "message": ( + f"Process Instance ({process_instance.id}) has status " + f"{process_instance.status} which does not allow draft data to be saved." + ), + "process_model_identifier": process_instance.process_model_identifier, + "process_instance_id": process_instance_id, + } ), - status_code=400, + status=200, + mimetype="application/json", ) try: @@ -610,12 +619,30 @@ def task_save_draft( if json_data_dict is not None: JsonDataModel.insert_or_update_json_data_dict(json_data_dict) db.session.add(task_draft_data) - db.session.commit() + try: + db.session.commit() + except OperationalError as exception: + db.session.rollback() + if "Deadlock" in str(exception): + task_draft_data = TaskService.task_draft_data_from_task_model(task_model) + # if we do not find a task_draft_data record, that means it was deleted when the form was submitted + # and we therefore have no need to save draft data + if task_draft_data is not None: + json_data_dict = TaskService.update_task_data_on_task_model_and_return_dict_if_updated( + task_draft_data, body, "saved_form_data_hash" + ) + if json_data_dict is not None: + JsonDataModel.insert_or_update_json_data_dict(json_data_dict) + db.session.add(task_draft_data) + db.session.commit() + else: + raise exception return Response( json.dumps( { "ok": True, + "saved": True, "process_model_identifier": process_instance.process_model_identifier, "process_instance_id": process_instance_id, } diff --git a/spiffworkflow-frontend/src/routes/TaskShow.tsx b/spiffworkflow-frontend/src/routes/TaskShow.tsx index 17725382..fa880149 100644 --- a/spiffworkflow-frontend/src/routes/TaskShow.tsx +++ b/spiffworkflow-frontend/src/routes/TaskShow.tsx @@ -21,7 +21,7 @@ import { modifyProcessIdentifierForPathParam, recursivelyChangeNullAndUndefined, } from '../helpers'; -import { ErrorForDisplay, EventDefinition, Task } from '../interfaces'; +import { EventDefinition, Task } from '../interfaces'; import ProcessBreadcrumb from '../components/ProcessBreadcrumb'; import InstructionsForEndUser from '../components/InstructionsForEndUser'; import TypeaheadWidget from '../rjsf/custom_widgets/TypeaheadWidget/TypeaheadWidget'; @@ -77,17 +77,6 @@ export default function TaskShow() { // eslint-disable-next-line react-hooks/exhaustive-deps }, [params]); - const handleAutoSaveError = (error: ErrorForDisplay) => { - if ( - error.error_code && - error.error_code === 'process_instance_not_runnable' - ) { - return undefined; - } - addError(error); - return undefined; - }; - // Before we auto-saved form data, we remembered what data was in the form, and then created a synthetic submit event // in order to implement a "Save and close" button. That button no longer saves (since we have auto-save), but the crazy // frontend code to support that Save and close button is here, in case we need to reference that someday: @@ -106,7 +95,6 @@ export default function TaskShow() { postBody: formData, httpMethod: 'POST', successCallback: successCallbackToUse, - failureCallback: handleAutoSaveError, }); return undefined; };