handle dup key error when saving draft data by updating the record on conflict w/ burnettk (#518)
Co-authored-by: jasquat <jasquat@users.noreply.github.com>
This commit is contained in:
parent
a211f3fd49
commit
10fa556525
|
@ -81,6 +81,13 @@ class JsonDataModel(SpiffworkflowBaseDBModel):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create_and_insert_json_data_from_dict(cls, data: dict) -> str:
|
def create_and_insert_json_data_from_dict(cls, data: dict) -> str:
|
||||||
json_data_hash = sha256(json.dumps(data, sort_keys=True).encode("utf8")).hexdigest()
|
json_data_dict = cls.json_data_dict_from_dict(data)
|
||||||
cls.insert_or_update_json_data_dict({"hash": json_data_hash, "data": data})
|
cls.insert_or_update_json_data_dict(json_data_dict)
|
||||||
return json_data_hash
|
return json_data_dict["hash"]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def json_data_dict_from_dict(cls, data: dict) -> JsonDataDict:
|
||||||
|
task_data_json = json.dumps(data, sort_keys=True)
|
||||||
|
task_data_hash: str = sha256(task_data_json.encode("utf8")).hexdigest()
|
||||||
|
json_data_dict: JsonDataDict = {"hash": task_data_hash, "data": data}
|
||||||
|
return json_data_dict
|
||||||
|
|
|
@ -1,9 +1,14 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
from typing import TypedDict
|
||||||
|
|
||||||
|
from flask import current_app
|
||||||
from sqlalchemy import ForeignKey
|
from sqlalchemy import ForeignKey
|
||||||
from sqlalchemy import UniqueConstraint
|
from sqlalchemy import UniqueConstraint
|
||||||
|
from sqlalchemy.dialects.mysql import insert as mysql_insert
|
||||||
|
from sqlalchemy.dialects.postgresql import insert as postgres_insert
|
||||||
|
from sqlalchemy.dialects.sqlite import insert as sqlite_insert
|
||||||
|
|
||||||
from spiffworkflow_backend.models.db import SpiffworkflowBaseDBModel
|
from spiffworkflow_backend.models.db import SpiffworkflowBaseDBModel
|
||||||
from spiffworkflow_backend.models.db import db
|
from spiffworkflow_backend.models.db import db
|
||||||
|
@ -11,6 +16,12 @@ from spiffworkflow_backend.models.json_data import JsonDataModel # noqa: F401
|
||||||
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
|
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
|
||||||
|
|
||||||
|
|
||||||
|
class TaskDraftDataDict(TypedDict):
|
||||||
|
process_instance_id: int
|
||||||
|
task_definition_id_path: str
|
||||||
|
saved_form_data_hash: str | None
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class TaskDraftDataModel(SpiffworkflowBaseDBModel):
|
class TaskDraftDataModel(SpiffworkflowBaseDBModel):
|
||||||
__tablename__ = "task_draft_data"
|
__tablename__ = "task_draft_data"
|
||||||
|
@ -36,3 +47,23 @@ class TaskDraftDataModel(SpiffworkflowBaseDBModel):
|
||||||
if self.saved_form_data_hash is not None:
|
if self.saved_form_data_hash is not None:
|
||||||
return JsonDataModel.find_data_dict_by_hash(self.saved_form_data_hash)
|
return JsonDataModel.find_data_dict_by_hash(self.saved_form_data_hash)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def insert_or_update_task_draft_data_dict(cls, task_draft_data_dict: TaskDraftDataDict) -> None:
|
||||||
|
on_duplicate_key_stmt = None
|
||||||
|
if current_app.config["SPIFFWORKFLOW_BACKEND_DATABASE_TYPE"] == "mysql":
|
||||||
|
insert_stmt = mysql_insert(TaskDraftDataModel).values([task_draft_data_dict])
|
||||||
|
on_duplicate_key_stmt = insert_stmt.on_duplicate_key_update(
|
||||||
|
saved_form_data_hash=insert_stmt.inserted.saved_form_data_hash
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
insert_stmt = None
|
||||||
|
if current_app.config["SPIFFWORKFLOW_BACKEND_DATABASE_TYPE"] == "sqlite":
|
||||||
|
insert_stmt = sqlite_insert(TaskDraftDataModel).values([task_draft_data_dict])
|
||||||
|
else:
|
||||||
|
insert_stmt = postgres_insert(TaskDraftDataModel).values([task_draft_data_dict])
|
||||||
|
on_duplicate_key_stmt = insert_stmt.on_conflict_do_update(
|
||||||
|
index_elements=["process_instance_id", "task_definition_id_path"],
|
||||||
|
set_={"saved_form_data_hash": task_draft_data_dict["saved_form_data_hash"]},
|
||||||
|
)
|
||||||
|
db.session.execute(on_duplicate_key_stmt)
|
||||||
|
|
|
@ -32,6 +32,7 @@ from spiffworkflow_backend.models.db import db
|
||||||
from spiffworkflow_backend.models.group import GroupModel
|
from spiffworkflow_backend.models.group import GroupModel
|
||||||
from spiffworkflow_backend.models.human_task import HumanTaskModel
|
from spiffworkflow_backend.models.human_task import HumanTaskModel
|
||||||
from spiffworkflow_backend.models.human_task_user import HumanTaskUserModel
|
from spiffworkflow_backend.models.human_task_user import HumanTaskUserModel
|
||||||
|
from spiffworkflow_backend.models.json_data import JsonDataDict # noqa: F401
|
||||||
from spiffworkflow_backend.models.json_data import JsonDataModel # noqa: F401
|
from spiffworkflow_backend.models.json_data import JsonDataModel # noqa: F401
|
||||||
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
|
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
|
||||||
from spiffworkflow_backend.models.process_instance import ProcessInstanceModelSchema
|
from spiffworkflow_backend.models.process_instance import ProcessInstanceModelSchema
|
||||||
|
@ -41,6 +42,8 @@ from spiffworkflow_backend.models.process_instance_event import ProcessInstanceE
|
||||||
from spiffworkflow_backend.models.process_model import ProcessModelInfo
|
from spiffworkflow_backend.models.process_model import ProcessModelInfo
|
||||||
from spiffworkflow_backend.models.task import Task
|
from spiffworkflow_backend.models.task import Task
|
||||||
from spiffworkflow_backend.models.task import TaskModel
|
from spiffworkflow_backend.models.task import TaskModel
|
||||||
|
from spiffworkflow_backend.models.task_draft_data import TaskDraftDataDict
|
||||||
|
from spiffworkflow_backend.models.task_draft_data import TaskDraftDataModel
|
||||||
from spiffworkflow_backend.models.user import UserModel
|
from spiffworkflow_backend.models.user import UserModel
|
||||||
from spiffworkflow_backend.routes.process_api_blueprint import _find_principal_or_raise
|
from spiffworkflow_backend.routes.process_api_blueprint import _find_principal_or_raise
|
||||||
from spiffworkflow_backend.routes.process_api_blueprint import _find_process_instance_by_id_or_raise
|
from spiffworkflow_backend.routes.process_api_blueprint import _find_process_instance_by_id_or_raise
|
||||||
|
@ -700,15 +703,18 @@ def task_save_draft(
|
||||||
return make_response(jsonify({"ok": True}), 200)
|
return make_response(jsonify({"ok": True}), 200)
|
||||||
|
|
||||||
task_model = _get_task_model_from_guid_or_raise(task_guid, process_instance_id)
|
task_model = _get_task_model_from_guid_or_raise(task_guid, process_instance_id)
|
||||||
task_draft_data = TaskService.task_draft_data_from_task_model(task_model, create_if_not_exists=True)
|
full_bpmn_process_id_path = TaskService.full_bpmn_process_path(task_model.bpmn_process, "id")
|
||||||
|
task_definition_id_path = f"{':'.join(map(str,full_bpmn_process_id_path))}:{task_model.task_definition_id}"
|
||||||
|
task_draft_data_dict: TaskDraftDataDict = {
|
||||||
|
"process_instance_id": process_instance.id,
|
||||||
|
"task_definition_id_path": task_definition_id_path,
|
||||||
|
"saved_form_data_hash": None,
|
||||||
|
}
|
||||||
|
|
||||||
if task_draft_data is not None:
|
json_data_dict = JsonDataModel.json_data_dict_from_dict(body)
|
||||||
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)
|
JsonDataModel.insert_or_update_json_data_dict(json_data_dict)
|
||||||
db.session.add(task_draft_data)
|
task_draft_data_dict["saved_form_data_hash"] = json_data_dict["hash"]
|
||||||
|
TaskDraftDataModel.insert_or_update_task_draft_data_dict(task_draft_data_dict)
|
||||||
try:
|
try:
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
except OperationalError as exception:
|
except OperationalError as exception:
|
||||||
|
@ -718,11 +724,13 @@ def task_save_draft(
|
||||||
# if we do not find a task_draft_data record, that means it was deleted when the form was submitted
|
# 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
|
# and we therefore have no need to save draft data
|
||||||
if task_draft_data is not None:
|
if task_draft_data is not None:
|
||||||
json_data_dict = TaskService.update_task_data_on_task_model_and_return_dict_if_updated(
|
# using this method here since it will check the db if the json_data_hash
|
||||||
|
# has changed and then we can update the task_data_draft record if it has
|
||||||
|
new_json_data_dict = TaskService.update_task_data_on_task_model_and_return_dict_if_updated(
|
||||||
task_draft_data, body, "saved_form_data_hash"
|
task_draft_data, body, "saved_form_data_hash"
|
||||||
)
|
)
|
||||||
if json_data_dict is not None:
|
if new_json_data_dict is not None:
|
||||||
JsonDataModel.insert_or_update_json_data_dict(json_data_dict)
|
JsonDataModel.insert_or_update_json_data_dict(new_json_data_dict)
|
||||||
db.session.add(task_draft_data)
|
db.session.add(task_draft_data)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -512,13 +512,11 @@ class TaskService:
|
||||||
def update_task_data_on_task_model_and_return_dict_if_updated(
|
def update_task_data_on_task_model_and_return_dict_if_updated(
|
||||||
cls, task_model: TaskModel, task_data_dict: dict, task_model_data_column: str
|
cls, task_model: TaskModel, task_data_dict: dict, task_model_data_column: str
|
||||||
) -> JsonDataDict | None:
|
) -> JsonDataDict | None:
|
||||||
task_data_json = json.dumps(task_data_dict, sort_keys=True)
|
json_data_dict = JsonDataModel.json_data_dict_from_dict(task_data_dict)
|
||||||
task_data_hash: str = sha256(task_data_json.encode("utf8")).hexdigest()
|
if getattr(task_model, task_model_data_column) != json_data_dict["hash"]:
|
||||||
json_data_dict: JsonDataDict | None = None
|
setattr(task_model, task_model_data_column, json_data_dict["hash"])
|
||||||
if getattr(task_model, task_model_data_column) != task_data_hash:
|
|
||||||
json_data_dict = {"hash": task_data_hash, "data": task_data_dict}
|
|
||||||
setattr(task_model, task_model_data_column, task_data_hash)
|
|
||||||
return json_data_dict
|
return json_data_dict
|
||||||
|
return None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def bpmn_process_and_descendants(cls, bpmn_processes: list[BpmnProcessModel]) -> list[BpmnProcessModel]:
|
def bpmn_process_and_descendants(cls, bpmn_processes: list[BpmnProcessModel]) -> list[BpmnProcessModel]:
|
||||||
|
|
Loading…
Reference in New Issue