diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/api.yml b/spiffworkflow-backend/src/spiffworkflow_backend/api.yml index d8209cf7..5484868d 100755 --- a/spiffworkflow-backend/src/spiffworkflow_backend/api.yml +++ b/spiffworkflow-backend/src/spiffworkflow_backend/api.yml @@ -2068,7 +2068,7 @@ paths: - name: process_identifier in: query required: false - description: The identifier of the process the data object is in. + description: "DEPRECATED - only the bpmn_process_guid is needed now: The identifier of the process the data object is in." schema: type: string - name: bpmn_process_guid 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 632c9e39..2b529c0a 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py @@ -3,7 +3,6 @@ import os import uuid from typing import Any from typing import TypedDict -from uuid import UUID import flask.wrappers import sentry_sdk @@ -30,9 +29,11 @@ from spiffworkflow_backend.exceptions.error import HumanTaskAlreadyCompletedErro 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.db import db from spiffworkflow_backend.models.human_task import HumanTaskModel from spiffworkflow_backend.models.human_task_user import HumanTaskUserModel +from spiffworkflow_backend.models.json_data import JsonDataModel from spiffworkflow_backend.models.principal import PrincipalModel from spiffworkflow_backend.models.process_instance import ProcessInstanceModel from spiffworkflow_backend.models.process_instance import ProcessInstanceStatus @@ -135,43 +136,35 @@ def _process_data_fetcher( bpmn_process_guid: str | None = None, process_identifier: str | None = None, ) -> flask.wrappers.Response: - if process_identifier and bpmn_process_guid is None: - raise ApiError( - error_code="missing_required_parameter", - message="process_identifier was given but bpmn_process_guid was not. Both must be provided if either is required.", - status_code=404, - ) - if process_identifier is None and bpmn_process_guid: - raise ApiError( - error_code="missing_required_parameter", - message="bpmn_process_guid was given but process_identifier was not. Both must be provided if either is required.", - status_code=404, - ) - process_instance = _find_process_instance_by_id_or_raise(process_instance_id) - processor = ProcessInstanceProcessor(process_instance) + if bpmn_process_guid is not None: + bpmn_process = BpmnProcessModel.query.filter_by(guid=bpmn_process_guid).first() + else: + bpmn_process = process_instance.bpmn_process + if bpmn_process is None: + raise ApiError( + error_code="bpmn_process_not_found", + message=f"Cannot find a bpmn process with guid '{bpmn_process_guid}' for process instance {process_instance.id}", + status_code=404, + ) - bpmn_process_instance = processor.bpmn_process_instance - bpmn_process_data = processor.get_data() - if process_identifier and bpmn_process_instance.spec.name != process_identifier: - bpmn_process_instance = processor.bpmn_process_instance.subprocesses.get(UUID(bpmn_process_guid)) - if bpmn_process_instance is None: - raise ApiError( - error_code="bpmn_process_not_found", - message=f"Cannot find a bpmn process with guid '{bpmn_process_guid}' for process instance {process_instance.id}", - status_code=404, - ) - bpmn_process_data = bpmn_process_instance.data + 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_instance.spec.data_objects + data_objects = bpmn_process_data["data_objects"] data_object = data_objects.get(process_data_identifier) if data_object is None: raise ApiError( error_code="data_object_not_found", message=( - f"Cannot find a data object with identifier '{process_data_identifier}' for bpmn process '{process_identifier}'" - f" in process instance {process_instance.id}" + f"Cannot find a data object with identifier '{process_data_identifier}' for bpmn process" + f" '{bpmn_process.bpmn_process_definition.bpmn_identifier}' in process instance {process_instance.id}" ), status_code=404, ) diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py b/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py index 0f0d6ca0..8315ba3d 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py @@ -5,6 +5,7 @@ import os import time from hashlib import sha256 from typing import Any +from unittest.mock import patch import flask import pytest @@ -12,6 +13,8 @@ from flask.app import Flask from flask.testing import FlaskClient from SpiffWorkflow.util.task import TaskState # type: ignore 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.process_instance import ProcessInstanceModel from spiffworkflow_backend.models.process_instance import ProcessInstanceStatus @@ -3323,6 +3326,55 @@ class TestProcessApi(BaseTest): assert response.json is not None assert response.json["process_data_value"] == "hey" + def test_process_data_show_with_sub_process( + 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/with-service-task-call-activity-sub-process", + process_model_source_directory="with-service-task-call-activity-sub-process", + ) + process_instance_one = self.create_process_instance_from_process_model(process_model) + processor = ProcessInstanceProcessor(process_instance_one) + connector_response = { + "body": '{"ok": true}', + "mimetype": "application/json", + "http_status": 200, + "operator_identifier": "http/GetRequestV2", + } + with patch("requests.post") as mock_post: + mock_post.return_value.status_code = 200 + mock_post.return_value.ok = True + mock_post.return_value.text = json.dumps(connector_response) + processor.do_engine_steps(save=True) + self.complete_next_manual_task(processor, execution_mode="synchronous") + self.complete_next_manual_task(processor, execution_mode="synchronous", data={"firstName": "Chuck"}) + assert process_instance_one.status == "complete" + + process_identifier = "call_activity_sub_process" + 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)}/sub_level_data_object_three/" + 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"] == "d" + def _setup_testing_instance( self, client: FlaskClient,