Fix process data get subprocess 3 (#1515)

* Fix process data get subprocess (#1493)

* added test to make sure we can get the data object of a sub process

* avoid the processor altogether to get data objects but use the db directly

---------

Co-authored-by: jasquat <jasquat@users.noreply.github.com>

* get the data object value from the bpmn process that defines it w/ burnettk

---------

Co-authored-by: jasquat <jasquat@users.noreply.github.com>
This commit is contained in:
jasquat 2024-05-08 13:29:07 +00:00 committed by GitHub
parent adab730bd9
commit d5e0ab6989
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 265 additions and 16 deletions

View File

@ -30,6 +30,7 @@ from spiffworkflow_backend.exceptions.error import HumanTaskNotFoundError
from spiffworkflow_backend.exceptions.error import UserDoesNotHaveAccessToTaskError
from spiffworkflow_backend.exceptions.process_entity_not_found_error import ProcessEntityNotFoundError
from spiffworkflow_backend.models.bpmn_process import BpmnProcessModel
from spiffworkflow_backend.models.bpmn_process_definition import BpmnProcessDefinitionModel
from spiffworkflow_backend.models.db import db
from spiffworkflow_backend.models.human_task import HumanTaskModel
from spiffworkflow_backend.models.human_task_user import HumanTaskUserModel
@ -129,6 +130,44 @@ def process_caller_list(bpmn_process_identifiers: list[str]) -> Any:
return ReferenceSchema(many=True).dump(references)
def _get_bpmn_process_with_data_object(
process_data_identifier: str,
bpmn_processes_by_id: dict[int, BpmnProcessModel],
bpmn_process_definitions_by_id: dict[int, BpmnProcessDefinitionModel],
current_bp: BpmnProcessModel,
) -> Any:
current_bpd = bpmn_process_definitions_by_id[current_bp.bpmn_process_definition_id]
if "data_objects" in current_bpd.properties_json and process_data_identifier in current_bpd.properties_json["data_objects"]:
return current_bp
elif current_bp.direct_parent_process_id in bpmn_processes_by_id:
return _get_bpmn_process_with_data_object(
process_data_identifier,
bpmn_processes_by_id,
bpmn_process_definitions_by_id,
bpmn_processes_by_id[current_bp.direct_parent_process_id],
)
else:
return current_bp
def _get_data_object_from_bpmn_process(
process_data_identifier: str,
bpmn_process: BpmnProcessModel,
bpmn_process_guid: str | None,
process_instance: ProcessInstanceModel,
) -> Any:
bpmn_process_data = JsonDataModel.find_data_dict_by_hash(bpmn_process.json_data_hash)
if bpmn_process_data is None:
raise ApiError(
error_code="bpmn_process_data_not_found",
message=f"Cannot find a bpmn process data with guid '{bpmn_process_guid}' for process instance {process_instance.id}",
status_code=404,
)
data_objects = bpmn_process_data.get("data_objects", {})
return data_objects.get(process_data_identifier)
def _process_data_fetcher(
process_instance_id: int,
process_data_identifier: str,
@ -148,18 +187,40 @@ def _process_data_fetcher(
status_code=404,
)
bpmn_process_data = JsonDataModel.find_data_dict_by_hash(bpmn_process.json_data_hash)
if bpmn_process_data is None:
raise ApiError(
error_code="bpmn_process_data_not_found",
message=f"Cannot find a bpmn process data with guid '{bpmn_process_guid}' for process instance {process_instance.id}",
status_code=404,
)
data_object_value = _get_data_object_from_bpmn_process(
process_data_identifier=process_data_identifier,
bpmn_process=bpmn_process,
bpmn_process_guid=bpmn_process_guid,
process_instance=process_instance,
)
data_objects = bpmn_process_data["data_objects"]
data_object = data_objects.get(process_data_identifier)
# if the data object value cannot be found with the given bpmn process then attempt to get it from the parent that defines it
if data_object_value is None:
all_bpmn_processes = None
if bpmn_process.top_level_process_id is not None:
all_bpmn_processes = BpmnProcessModel.query.filter(
or_(
BpmnProcessModel.top_level_process_id == bpmn_process.top_level_process_id,
BpmnProcessModel.id == bpmn_process.top_level_process_id,
)
).all()
all_bpmn_def_ids = [bp.bpmn_process_definition_id for bp in all_bpmn_processes]
all_bpmn_process_definitions = BpmnProcessDefinitionModel.query.filter(
BpmnProcessDefinitionModel.id.in_(all_bpmn_def_ids) # type: ignore
).all()
bpmn_processes_by_id = {bp.id: bp for bp in all_bpmn_processes}
bpmn_process_definitions_by_id = {bpd.id: bpd for bpd in all_bpmn_process_definitions}
bp = _get_bpmn_process_with_data_object(
process_data_identifier, bpmn_processes_by_id, bpmn_process_definitions_by_id, bpmn_process
)
data_object_value = _get_data_object_from_bpmn_process(
process_data_identifier=process_data_identifier,
bpmn_process=bp,
bpmn_process_guid=bpmn_process_guid,
process_instance=process_instance,
)
if data_object is None:
if data_object_value is None:
raise ApiError(
error_code="data_object_not_found",
message=(
@ -169,21 +230,20 @@ def _process_data_fetcher(
status_code=404,
)
if hasattr(data_object, "category") and data_object.category is not None:
if data_object.category != category:
if hasattr(data_object_value, "category") and data_object_value.category is not None:
if data_object_value.category != category:
raise ApiError(
error_code="data_object_category_mismatch",
message=f"The desired data object has category '{data_object.category}' instead of the expected '{category}'",
message=f"The desired data object has category '{data_object_value.category}' "
"instead of the expected '{category}'",
status_code=400,
)
process_data_value = bpmn_process_data.get("data_objects", bpmn_process_data).get(process_data_identifier)
return make_response(
jsonify(
{
"process_data_identifier": process_data_identifier,
"process_data_value": process_data_value,
"process_data_value": data_object_value,
}
),
200,

View File

@ -0,0 +1,152 @@
<?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:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_96f6665" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.0.0-dev">
<bpmn:process id="Process_data_object_in_subprocess_naa791d" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>Flow_17db3yp</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:sequenceFlow id="Flow_17db3yp" sourceRef="StartEvent_1" targetRef="subprocess1" />
<bpmn:endEvent id="EndEvent_1">
<bpmn:incoming>Flow_0ogjg7w</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_1qd0afw" sourceRef="subprocess1" targetRef="subprocess2" />
<bpmn:subProcess id="subprocess1" name="Subprocess1">
<bpmn:incoming>Flow_17db3yp</bpmn:incoming>
<bpmn:outgoing>Flow_1qd0afw</bpmn:outgoing>
<bpmn:dataOutputAssociation id="DataOutputAssociation_0piaeh8">
<bpmn:targetRef>DataObjectReference_0vvdt0g</bpmn:targetRef>
</bpmn:dataOutputAssociation>
<bpmn:startEvent id="Event_1ln0thm">
<bpmn:outgoing>Flow_1j5xvbb</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:sequenceFlow id="Flow_1j5xvbb" sourceRef="Event_1ln0thm" targetRef="Activity_1rt0k9h" />
<bpmn:endEvent id="Event_0ocmrhe">
<bpmn:incoming>Flow_1g3kgd6</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_1g3kgd6" sourceRef="Activity_1rt0k9h" targetRef="Event_0ocmrhe" />
<bpmn:scriptTask id="Activity_1rt0k9h">
<bpmn:incoming>Flow_1j5xvbb</bpmn:incoming>
<bpmn:outgoing>Flow_1g3kgd6</bpmn:outgoing>
<bpmn:script>our_data_object = "HEY"</bpmn:script>
</bpmn:scriptTask>
</bpmn:subProcess>
<bpmn:sequenceFlow id="Flow_0ogjg7w" sourceRef="subprocess2" targetRef="EndEvent_1" />
<bpmn:subProcess id="subprocess2" name="Subprocess2">
<bpmn:incoming>Flow_1qd0afw</bpmn:incoming>
<bpmn:outgoing>Flow_0ogjg7w</bpmn:outgoing>
<bpmn:startEvent id="Event_0ohesmq">
<bpmn:outgoing>Flow_1cbazwx</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:sequenceFlow id="Flow_1cbazwx" sourceRef="Event_0ohesmq" targetRef="Subprocess2_ScriptTask" />
<bpmn:endEvent id="Event_0fgm95v">
<bpmn:incoming>Flow_1ovr7kv</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_1ovr7kv" sourceRef="Subprocess2_ScriptTask" targetRef="Event_0fgm95v" />
<bpmn:scriptTask id="Subprocess2_ScriptTask" name="Subprocess2_ScriptTask">
<bpmn:incoming>Flow_1cbazwx</bpmn:incoming>
<bpmn:outgoing>Flow_1ovr7kv</bpmn:outgoing>
<bpmn:property id="Property_1q20lug" name="__targetRef_placeholder" />
<bpmn:dataInputAssociation id="DataInputAssociation_0n80m5c">
<bpmn:sourceRef>DataObjectReference_1vhsytv</bpmn:sourceRef>
<bpmn:targetRef>Property_1q20lug</bpmn:targetRef>
</bpmn:dataInputAssociation>
<bpmn:script>hey = f"{our_data_object}_NO"</bpmn:script>
</bpmn:scriptTask>
<bpmn:dataObjectReference id="DataObjectReference_1vhsytv" name="our_data_object" dataObjectRef="our_data_object" />
</bpmn:subProcess>
<bpmn:dataObjectReference id="DataObjectReference_0vvdt0g" name="our_data_object" dataObjectRef="our_data_object" />
<bpmn:dataObject id="our_data_object" name="our_data_object" />
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_td_hey_naa791d">
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="112" y="159" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_14za570_di" bpmnElement="EndEvent_1">
<dc:Bounds x="432" y="159" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_03m2m68_di" bpmnElement="subprocess1">
<dc:Bounds x="170" y="137" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1ccsja3_di" bpmnElement="subprocess2">
<dc:Bounds x="300" y="137" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="DataObjectReference_0vvdt0g_di" bpmnElement="DataObjectReference_0vvdt0g">
<dc:Bounds x="172" y="295" width="36" height="50" />
<bpmndi:BPMNLabel>
<dc:Bounds x="151" y="352" width="79" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_17db3yp_di" bpmnElement="Flow_17db3yp">
<di:waypoint x="148" y="177" />
<di:waypoint x="170" y="177" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1qd0afw_di" bpmnElement="Flow_1qd0afw">
<di:waypoint x="270" y="177" />
<di:waypoint x="300" y="177" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="DataOutputAssociation_0piaeh8_di" bpmnElement="DataOutputAssociation_0piaeh8">
<di:waypoint x="211" y="217" />
<di:waypoint x="193" y="295" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0ogjg7w_di" bpmnElement="Flow_0ogjg7w">
<di:waypoint x="400" y="177" />
<di:waypoint x="432" y="177" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
<bpmndi:BPMNDiagram id="BPMNDiagram_14tf7jl">
<bpmndi:BPMNPlane id="BPMNPlane_0u3vw6z" bpmnElement="subprocess1">
<bpmndi:BPMNShape id="Event_1ln0thm_di" bpmnElement="Event_1ln0thm">
<dc:Bounds x="542" y="242" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_0ocmrhe_di" bpmnElement="Event_0ocmrhe">
<dc:Bounds x="782" y="242" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_04ig3ob_di" bpmnElement="Activity_1rt0k9h">
<dc:Bounds x="630" y="220" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_1j5xvbb_di" bpmnElement="Flow_1j5xvbb">
<di:waypoint x="578" y="260" />
<di:waypoint x="630" y="260" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1g3kgd6_di" bpmnElement="Flow_1g3kgd6">
<di:waypoint x="730" y="260" />
<di:waypoint x="782" y="260" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
<bpmndi:BPMNDiagram id="BPMNDiagram_01zi3um">
<bpmndi:BPMNPlane id="BPMNPlane_0yyv2r4" bpmnElement="subprocess2">
<bpmndi:BPMNShape id="Event_0ohesmq_di" bpmnElement="Event_0ohesmq">
<dc:Bounds x="532" y="212" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_06vgk4z_di" bpmnElement="Subprocess2_ScriptTask">
<dc:Bounds x="620" y="190" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="DataObjectReference_1vhsytv_di" bpmnElement="DataObjectReference_1vhsytv">
<dc:Bounds x="652" y="335" width="36" height="50" />
<bpmndi:BPMNLabel>
<dc:Bounds x="631" y="392" width="79" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_0fgm95v_di" bpmnElement="Event_0fgm95v">
<dc:Bounds x="782" y="212" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_1cbazwx_di" bpmnElement="Flow_1cbazwx">
<di:waypoint x="568" y="230" />
<di:waypoint x="620" y="230" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1ovr7kv_di" bpmnElement="Flow_1ovr7kv">
<di:waypoint x="720" y="230" />
<di:waypoint x="782" y="230" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="DataInputAssociation_0n80m5c_di" bpmnElement="DataInputAssociation_0n80m5c">
<di:waypoint x="670" y="335" />
<di:waypoint x="670" y="270" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

View File

@ -3375,6 +3375,43 @@ class TestProcessApi(BaseTest):
assert response.json is not None
assert response.json["process_data_value"] == "d"
def test_process_data_show_with_sub_process_from_top_level(
self,
app: Flask,
client: FlaskClient,
with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel,
) -> None:
process_model = load_test_spec(
"test_group/data-object-in-subprocess",
process_model_source_directory="data-object-in-subprocess",
)
process_instance_one = self.create_process_instance_from_process_model(process_model)
processor = ProcessInstanceProcessor(process_instance_one)
processor.do_engine_steps(save=True)
assert process_instance_one.status == "complete"
process_identifier = "subprocess2"
bpmn_processes = (
BpmnProcessModel.query.join(
BpmnProcessDefinitionModel, BpmnProcessDefinitionModel.id == BpmnProcessModel.bpmn_process_definition_id
)
.filter(BpmnProcessDefinitionModel.bpmn_identifier == process_identifier)
.all()
)
assert len(bpmn_processes) == 1
bpmn_process = bpmn_processes[0]
response = client.get(
f"/v1.0/process-data/default/{self.modify_process_identifier_for_path_param(process_model.id)}/our_data_object/"
f"{process_instance_one.id}?process_identifier={process_identifier}&bpmn_process_guid={bpmn_process.guid}",
headers=self.logged_in_headers(with_super_admin_user),
)
assert response.status_code == 200
assert response.json is not None
assert response.json["process_data_value"] == "HEY"
def _setup_testing_instance(
self,
client: FlaskClient,