diff --git a/spiffworkflow-backend/poetry.lock b/spiffworkflow-backend/poetry.lock index 469dfdce..c578ff05 100644 --- a/spiffworkflow-backend/poetry.lock +++ b/spiffworkflow-backend/poetry.lock @@ -1945,8 +1945,6 @@ files = [ {file = "psycopg2-2.9.9-cp310-cp310-win_amd64.whl", hash = "sha256:426f9f29bde126913a20a96ff8ce7d73fd8a216cfb323b1f04da402d452853c3"}, {file = "psycopg2-2.9.9-cp311-cp311-win32.whl", hash = "sha256:ade01303ccf7ae12c356a5e10911c9e1c51136003a9a1d92f7aa9d010fb98372"}, {file = "psycopg2-2.9.9-cp311-cp311-win_amd64.whl", hash = "sha256:121081ea2e76729acfb0673ff33755e8703d45e926e416cb59bae3a86c6a4981"}, - {file = "psycopg2-2.9.9-cp312-cp312-win32.whl", hash = "sha256:d735786acc7dd25815e89cc4ad529a43af779db2e25aa7c626de864127e5a024"}, - {file = "psycopg2-2.9.9-cp312-cp312-win_amd64.whl", hash = "sha256:a7653d00b732afb6fc597e29c50ad28087dcb4fbfb28e86092277a559ae4e693"}, {file = "psycopg2-2.9.9-cp37-cp37m-win32.whl", hash = "sha256:5e0d98cade4f0e0304d7d6f25bbfbc5bd186e07b38eac65379309c4ca3193efa"}, {file = "psycopg2-2.9.9-cp37-cp37m-win_amd64.whl", hash = "sha256:7e2dacf8b009a1c1e843b5213a87f7c544b2b042476ed7755be813eaf4e8347a"}, {file = "psycopg2-2.9.9-cp38-cp38-win32.whl", hash = "sha256:ff432630e510709564c01dafdbe996cb552e0b9f3f065eb89bdce5bd31fabf4c"}, @@ -2286,7 +2284,6 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, @@ -2663,51 +2660,37 @@ python-versions = ">=3.6" files = [ {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b42169467c42b692c19cf539c38d4602069d8c1505e97b86387fcf7afb766e1d"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:07238db9cbdf8fc1e9de2489a4f68474e70dffcb32232db7c08fa61ca0c7c462"}, + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:d92f81886165cb14d7b067ef37e142256f1c6a90a65cd156b063a43da1708cfd"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:fff3573c2db359f091e1589c3d7c5fc2f86f5bdb6f24252c2d8e539d4e45f412"}, - {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux_2_24_aarch64.whl", hash = "sha256:aa2267c6a303eb483de8d02db2871afb5c5fc15618d894300b88958f729ad74f"}, - {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:840f0c7f194986a63d2c2465ca63af8ccbbc90ab1c6001b1978f05119b5e7334"}, - {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:024cfe1fc7c7f4e1aff4a81e718109e13409767e4f871443cbff3dba3578203d"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-win32.whl", hash = "sha256:c69212f63169ec1cfc9bb44723bf2917cbbd8f6191a00ef3410f5a7fe300722d"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-win_amd64.whl", hash = "sha256:cabddb8d8ead485e255fe80429f833172b4cadf99274db39abc080e068cbcc31"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bef08cd86169d9eafb3ccb0a39edb11d8e25f3dae2b28f5c52fd997521133069"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:b16420e621d26fdfa949a8b4b47ade8810c56002f5389970db4ddda51dbff248"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:b5edda50e5e9e15e54a6a8a0070302b00c518a9d32accc2346ad6c984aacd279"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:25c515e350e5b739842fc3228d662413ef28f295791af5e5110b543cf0b57d9b"}, - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_24_aarch64.whl", hash = "sha256:1707814f0d9791df063f8c19bb51b0d1278b8e9a2353abbb676c2f685dee6afe"}, - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:46d378daaac94f454b3a0e3d8d78cafd78a026b1d71443f4966c696b48a6d899"}, - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:09b055c05697b38ecacb7ac50bdab2240bfca1a0c4872b0fd309bb07dc9aa3a9"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-win32.whl", hash = "sha256:53a300ed9cea38cf5a2a9b069058137c2ca1ce658a874b79baceb8f892f915a7"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-win_amd64.whl", hash = "sha256:c2a72e9109ea74e511e29032f3b670835f8a59bbdc9ce692c5b4ed91ccf1eedb"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:ebc06178e8821efc9692ea7544aa5644217358490145629914d8020042c24aa1"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:edaef1c1200c4b4cb914583150dcaa3bc30e592e907c01117c08b13a07255ec2"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:7048c338b6c86627afb27faecf418768acb6331fc24cfa56c93e8c9780f815fa"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d176b57452ab5b7028ac47e7b3cf644bcfdc8cacfecf7e71759f7f51a59e5c92"}, - {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux_2_24_aarch64.whl", hash = "sha256:1dc67314e7e1086c9fdf2680b7b6c2be1c0d8e3a8279f2e993ca2a7545fecf62"}, - {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3213ece08ea033eb159ac52ae052a4899b56ecc124bb80020d9bbceeb50258e9"}, - {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aab7fd643f71d7946f2ee58cc88c9b7bfc97debd71dcc93e03e2d174628e7e2d"}, - {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-win32.whl", hash = "sha256:5c365d91c88390c8d0a8545df0b5857172824b1c604e867161e6b3d59a827eaa"}, - {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-win_amd64.whl", hash = "sha256:1758ce7d8e1a29d23de54a16ae867abd370f01b5a69e1a3ba75223eaa3ca1a1b"}, {file = "ruamel.yaml.clib-0.2.8-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a5aa27bad2bb83670b71683aae140a1f52b0857a2deff56ad3f6c13a017a26ed"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c58ecd827313af6864893e7af0a3bb85fd529f862b6adbefe14643947cfe2942"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-macosx_12_0_arm64.whl", hash = "sha256:f481f16baec5290e45aebdc2a5168ebc6d35189ae6fea7a58787613a25f6e875"}, - {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux_2_24_aarch64.whl", hash = "sha256:77159f5d5b5c14f7c34073862a6b7d34944075d9f93e681638f6d753606c6ce6"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:3fcc54cb0c8b811ff66082de1680b4b14cf8a81dce0d4fbf665c2265a81e07a1"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7f67a1ee819dc4562d444bbafb135832b0b909f81cc90f7aa00260968c9ca1b3"}, - {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4ecbf9c3e19f9562c7fdd462e8d18dd902a47ca046a2e64dba80699f0b6c09b7"}, - {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:87ea5ff66d8064301a154b3933ae406b0863402a799b16e4a1d24d9fbbcbe0d3"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-win32.whl", hash = "sha256:75e1ed13e1f9de23c5607fe6bd1aeaae21e523b32d83bb33918245361e9cc51b"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-win_amd64.whl", hash = "sha256:3f215c5daf6a9d7bbed4a0a4f760f3113b10e82ff4c5c44bec20a68c8014f675"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1b617618914cb00bf5c34d4357c37aa15183fa229b24767259657746c9077615"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:a6a9ffd280b71ad062eae53ac1659ad86a17f59a0fdc7699fd9be40525153337"}, - {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux_2_24_aarch64.whl", hash = "sha256:305889baa4043a09e5b76f8e2a51d4ffba44259f6b4c72dec8ca56207d9c6fe1"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:665f58bfd29b167039f714c6998178d27ccd83984084c286110ef26b230f259f"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:700e4ebb569e59e16a976857c8798aee258dceac7c7d6b50cab63e080058df91"}, - {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:e2b4c44b60eadec492926a7270abb100ef9f72798e18743939bdbf037aab8c28"}, - {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e79e5db08739731b0ce4850bed599235d601701d5694c36570a99a0c5ca41a9d"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-win32.whl", hash = "sha256:955eae71ac26c1ab35924203fda6220f84dce57d6d7884f189743e2abe3a9fbe"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-win_amd64.whl", hash = "sha256:56f4252222c067b4ce51ae12cbac231bce32aee1d33fbfc9d17e5b8d6966c312"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:03d1162b6d1df1caa3a4bd27aa51ce17c9afc2046c31b0ad60a0a96ec22f8001"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:bba64af9fa9cebe325a62fa398760f5c7206b215201b0ec825005f1b18b9bccf"}, - {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux_2_24_aarch64.whl", hash = "sha256:a1a45e0bb052edf6a1d3a93baef85319733a888363938e1fc9924cb00c8df24c"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:9eb5dee2772b0f704ca2e45b1713e4e5198c18f515b52743576d196348f374d3"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:da09ad1c359a728e112d60116f626cc9f29730ff3e0e7db72b9a2dbc2e4beed5"}, - {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:184565012b60405d93838167f425713180b949e9d8dd0bbc7b49f074407c5a8b"}, - {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a75879bacf2c987c003368cf14bed0ffe99e8e85acfa6c0bfffc21a090f16880"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-win32.whl", hash = "sha256:84b554931e932c46f94ab306913ad7e11bba988104c5cff26d90d03f68258cd5"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-win_amd64.whl", hash = "sha256:25ac8c08322002b06fa1d49d1646181f0b2c72f5cbc15a85e80b4c30a544bb15"}, {file = "ruamel.yaml.clib-0.2.8.tar.gz", hash = "sha256:beb2e0404003de9a4cab9753a8805a8fe9320ee6673136ed7f04255fe60bb512"}, diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/api.yml b/spiffworkflow-backend/src/spiffworkflow_backend/api.yml index 9c322083..4668787f 100755 --- a/spiffworkflow-backend/src/spiffworkflow_backend/api.yml +++ b/spiffworkflow-backend/src/spiffworkflow_backend/api.yml @@ -562,7 +562,7 @@ paths: schema: $ref: "#/components/schemas/ProcessModel" - /process-model-tests/{modified_process_model_identifier}: + /process-model-tests/run/{modified_process_model_identifier}: parameters: - name: modified_process_model_identifier in: path @@ -589,13 +589,9 @@ paths: - Process Model Tests requestBody: content: - multipart/form-data: + application/json: schema: - type: object - properties: - file: - type: string - format: binary + $ref: "#/components/schemas/AwesomeUnspecifiedPayload" responses: "201": description: Metadata about the uploaded file, but not the file content. @@ -604,6 +600,47 @@ paths: schema: $ref: "#/components/schemas/File" + /process-model-tests/create/{modified_process_model_identifier}: + parameters: + - name: modified_process_model_identifier + in: path + required: true + description: The process_model_id, modified to replace slashes (/) + schema: + type: string + - name: test_case_identifier + in: query + required: false + description: The name of the test case file to run + schema: + type: string + post: + operationId: spiffworkflow_backend.routes.process_models_controller.process_model_test_generate + summary: Create a bpmn unit test from a process instance + tags: + - Process Model Tests + requestBody: + content: + application/json: + schema: + properties: + process_instance_id: + description: The id of the associated process instance + type: number + example: 2 + nullable: false + test_case_identifier: + description: The name for the new test case + type: string + nullable: true + responses: + "201": + description: The generated bpmn unit test + content: + application/json: + schema: + $ref: "#/components/schemas/AwesomeUnspecifiedPayload" + /process-models/{modified_process_model_identifier}/files: parameters: - name: modified_process_model_identifier diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/exceptions/error.py b/spiffworkflow-backend/src/spiffworkflow_backend/exceptions/error.py index 7a7fdaad..61a39143 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/exceptions/error.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/exceptions/error.py @@ -55,3 +55,7 @@ class InvalidPermissionError(Exception): class InvalidRedirectUrlError(Exception): pass + + +class TaskMismatchError(Exception): + pass diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_models_controller.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_models_controller.py index a3bf51db..67cfed72 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_models_controller.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_models_controller.py @@ -25,16 +25,19 @@ from spiffworkflow_backend.models.process_model import ProcessModelInfo from spiffworkflow_backend.models.process_model import ProcessModelInfoSchema from spiffworkflow_backend.models.reference_cache import ReferenceCacheModel from spiffworkflow_backend.routes.process_api_blueprint import _commit_and_push_to_git +from spiffworkflow_backend.routes.process_api_blueprint import _find_process_instance_by_id_or_raise from spiffworkflow_backend.routes.process_api_blueprint import _get_process_model from spiffworkflow_backend.routes.process_api_blueprint import _un_modify_modified_process_model_id from spiffworkflow_backend.services.file_system_service import FileSystemService from spiffworkflow_backend.services.git_service import GitCommandError from spiffworkflow_backend.services.git_service import GitService from spiffworkflow_backend.services.git_service import MissingGitConfigsError +from spiffworkflow_backend.services.process_instance_processor import ProcessInstanceProcessor from spiffworkflow_backend.services.process_instance_report_service import ProcessInstanceReportNotFoundError from spiffworkflow_backend.services.process_instance_report_service import ProcessInstanceReportService from spiffworkflow_backend.services.process_model_service import ProcessModelService from spiffworkflow_backend.services.process_model_service import ProcessModelWithInstancesNotDeletableError +from spiffworkflow_backend.services.process_model_test_generator_service import ProcessModelTestGeneratorService from spiffworkflow_backend.services.process_model_test_runner_service import ProcessModelTestRunner from spiffworkflow_backend.services.spec_file_service import ProcessModelFileInvalidError from spiffworkflow_backend.services.spec_file_service import SpecFileService @@ -352,6 +355,41 @@ def process_model_test_run( return make_response(jsonify(response_json), 200) +def process_model_test_generate(modified_process_model_identifier: str, body: dict[str, str | int]) -> flask.wrappers.Response: + process_instance_id = body["process_instance_id"] + + if process_instance_id is None: + raise ApiError( + error_code="missing_process_instance_id", + message="Process instance id is required to be in the body of request.", + status_code=400, + ) + + test_case_identifier = body.get("test_case_identifier", f"test_case_for_process_instance_{process_instance_id}") + process_instance = _find_process_instance_by_id_or_raise(int(process_instance_id)) + processor = ProcessInstanceProcessor(process_instance, include_task_data_for_completed_tasks=True) + process_instance_dict = processor.serialize() + test_case_dict = ProcessModelTestGeneratorService.generate_test_from_process_instance_dict( + process_instance_dict, test_case_identifier=str(test_case_identifier) + ) + + process_model_identifier = modified_process_model_identifier.replace(":", "/") + process_model = _get_process_model(process_model_identifier) + + if process_model.primary_file_name is None: + raise ApiError( + error_code="process_model_primary_file_not_set", + message="The primary file is not set for the given process model.", + status_code=400, + ) + + primary_file_name_without_extension = ".".join(process_model.primary_file_name.split(".")[0:-1]) + test_case_file_name = f"test_{primary_file_name_without_extension}.json" + ProcessModelService.add_json_data_to_json_file(process_model, test_case_file_name, test_case_dict) + + return make_response(jsonify(test_case_dict), 200) + + # { # "natural_language_text": "Create a bug tracker process model \ # with a bug-details form that collects summary, description, and priority" diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py index c30081cd..b4095d2b 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py @@ -72,7 +72,8 @@ PATH_SEGMENTS_FOR_PERMISSION_ALL = [ {"path": "/process-instance-terminate", "relevant_permissions": ["create"]}, {"path": "/process-model-natural-language", "relevant_permissions": ["create"]}, {"path": "/process-model-publish", "relevant_permissions": ["create"]}, - {"path": "/process-model-tests", "relevant_permissions": ["create"]}, + {"path": "/process-model-tests/create", "relevant_permissions": ["create"]}, + {"path": "/process-model-tests/run", "relevant_permissions": ["create"]}, {"path": "/task-assign", "relevant_permissions": ["create"]}, {"path": "/task-data", "relevant_permissions": ["read", "update"]}, ] 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 51062bb3..97ecdd3c 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py @@ -63,6 +63,7 @@ from spiffworkflow_backend.data_stores.kkv import KKVDataStoreConverter from spiffworkflow_backend.data_stores.typeahead import TypeaheadDataStore from spiffworkflow_backend.data_stores.typeahead import TypeaheadDataStoreConverter from spiffworkflow_backend.exceptions.api_error import ApiError +from spiffworkflow_backend.exceptions.error import TaskMismatchError from spiffworkflow_backend.models.bpmn_process import BpmnProcessModel from spiffworkflow_backend.models.bpmn_process_definition import BpmnProcessDefinitionModel from spiffworkflow_backend.models.bpmn_process_definition_relationship import BpmnProcessDefinitionRelationshipModel @@ -408,7 +409,6 @@ class ProcessInstanceProcessor: _serializer = BpmnWorkflowSerializer(wf_spec_converter, version=SPIFFWORKFLOW_BACKEND_SERIALIZER_VERSION) PROCESS_INSTANCE_ID_KEY = "process_instance_id" - VALIDATION_PROCESS_KEY = "validate_only" # __init__ calls these helpers: # * get_spec, which returns a spec and any subprocesses (as IdToBpmnProcessSpecMapping dict) @@ -416,7 +416,6 @@ class ProcessInstanceProcessor: def __init__( self, process_instance_model: ProcessInstanceModel, - validate_only: bool = False, script_engine: PythonScriptEngine | None = None, workflow_completed_handler: WorkflowCompletedHandler | None = None, process_id_to_run: str | None = None, @@ -429,7 +428,6 @@ class ProcessInstanceProcessor: self.additional_processing_identifier = additional_processing_identifier self.setup_processor_with_process_instance( process_instance_model=process_instance_model, - validate_only=validate_only, process_id_to_run=process_id_to_run, include_task_data_for_completed_tasks=include_task_data_for_completed_tasks, ) @@ -437,7 +435,6 @@ class ProcessInstanceProcessor: def setup_processor_with_process_instance( self, process_instance_model: ProcessInstanceModel, - validate_only: bool = False, process_id_to_run: str | None = None, include_task_data_for_completed_tasks: bool = False, ) -> None: @@ -482,7 +479,6 @@ class ProcessInstanceProcessor: ) = self.__get_bpmn_process_instance( process_instance_model, bpmn_process_spec, - validate_only, subprocesses=subprocesses, ) self.set_script_engine(self.bpmn_process_instance, self._script_engine) @@ -819,7 +815,6 @@ class ProcessInstanceProcessor: def __get_bpmn_process_instance( process_instance_model: ProcessInstanceModel, spec: BpmnProcessSpec | None = None, - validate_only: bool = False, subprocesses: IdToBpmnProcessSpecMapping | None = None, include_task_data_for_completed_tasks: bool = False, ) -> tuple[BpmnWorkflow, dict, dict]: @@ -840,13 +835,13 @@ class ProcessInstanceProcessor: # FIXME: the from_dict entrypoint in spiff will one day do this copy instead process_copy = copy.deepcopy(full_bpmn_process_dict) bpmn_process_instance = ProcessInstanceProcessor._serializer.from_dict(process_copy) + bpmn_process_instance.get_tasks() except Exception as err: raise err finally: spiff_logger.setLevel(original_spiff_logger_log_level) else: bpmn_process_instance = ProcessInstanceProcessor.get_bpmn_process_instance_from_workflow_spec(spec, subprocesses) - bpmn_process_instance.data[ProcessInstanceProcessor.VALIDATION_PROCESS_KEY] = validate_only return ( bpmn_process_instance, @@ -1663,6 +1658,11 @@ class ProcessInstanceProcessor: return task_json def complete_task(self, spiff_task: SpiffTask, human_task: HumanTaskModel, user: UserModel) -> None: + if str(spiff_task.id) != human_task.task_guid: + raise TaskMismatchError( + f"Given spiff task ({spiff_task.task_spec.bpmn_id} - {spiff_task.id}) and human task ({human_task.task_name} -" + f" {human_task.task_guid}) must match" + ) task_model = TaskModel.query.filter_by(guid=human_task.task_id).first() if task_model is None: raise TaskNotFoundError( diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_model_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_model_service.py index ee27c779..15d0f76b 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_model_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_model_service.py @@ -114,6 +114,17 @@ class ProcessModelService(FileSystemService): configured_prefix = current_app.config["SPIFFWORKFLOW_BACKEND_EXTENSIONS_PROCESS_MODEL_PREFIX"] return process_model.id.startswith(f"{configured_prefix}/") + @classmethod + def add_json_data_to_json_file(cls, process_model: ProcessModelInfo, file_name: str, json_data: dict) -> None: + full_json_data = json_data + process_model_path = os.path.abspath(os.path.join(FileSystemService.root_path(), process_model.id_for_file_path())) + json_path = os.path.abspath(os.path.join(process_model_path, file_name)) + if os.path.exists(json_path): + with open(json_path) as f: + existing_json = json.loads(f.read()) + full_json_data = {**existing_json, **json_data} + cls.write_json_file(json_path, full_json_data) + @classmethod def save_process_model(cls, process_model: ProcessModelInfo) -> None: process_model_path = os.path.abspath(os.path.join(FileSystemService.root_path(), process_model.id_for_file_path())) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_model_test_generator_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_model_test_generator_service.py new file mode 100644 index 00000000..538c1c11 --- /dev/null +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_model_test_generator_service.py @@ -0,0 +1,46 @@ +import copy + +from SpiffWorkflow.bpmn.serializer.workflow import BpmnWorkflowSerializer # type: ignore +from SpiffWorkflow.bpmn.specs.mixins import ServiceTaskMixin # type: ignore +from SpiffWorkflow.spiff.serializer.config import SPIFF_CONFIG # type: ignore + + +class ProcessModelTestGeneratorService: + @classmethod + def generate_test_from_process_instance_dict( + cls, process_instance_dict: dict, test_case_identifier: str = "auto_generated_test_case" + ) -> dict: + wf_spec_converter = BpmnWorkflowSerializer.configure(SPIFF_CONFIG) + serializer = BpmnWorkflowSerializer(wf_spec_converter) + process_instance_dict_copy = copy.deepcopy(process_instance_dict) + bpmn_process_instance = serializer.from_dict(process_instance_dict_copy) + + human_tasks = bpmn_process_instance.get_tasks(manual=True) + service_tasks = bpmn_process_instance.get_tasks(spec_class=ServiceTaskMixin) + all_spiff_tasks = human_tasks + service_tasks + bpmn_unit_test_specification = {"tasks": {}, "expected_output_json": bpmn_process_instance.data} + for spiff_task in all_spiff_tasks: + process_id = spiff_task.workflow.spec.name + bpmn_task_identifier = f"{process_id}:{spiff_task.task_spec.bpmn_id}" + if bpmn_task_identifier not in bpmn_unit_test_specification["tasks"]: + bpmn_unit_test_specification["tasks"][bpmn_task_identifier] = {} + if "data" not in bpmn_unit_test_specification["tasks"][bpmn_task_identifier]: + bpmn_unit_test_specification["tasks"][bpmn_task_identifier]["data"] = [] + + previous_task_data = spiff_task.parent.data + current_task_data = spiff_task.data + cls.remove_duplicates(previous_task_data, current_task_data) + bpmn_unit_test_specification["tasks"][bpmn_task_identifier]["data"].append(current_task_data) + + return {test_case_identifier: bpmn_unit_test_specification} + + @classmethod + def remove_duplicates(cls, dict_one: dict, dict_two: dict) -> None: + for key in list(dict_two.keys()): + if key in dict_one: + if isinstance(dict_one[key], dict) and isinstance(dict_two[key], dict): + cls.remove_duplicates(dict_one[key], dict_two[key]) + if not dict_two[key]: + del dict_two[key] + else: + del dict_two[key] diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_model_test_runner_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_model_test_runner_service.py index cb0fb374..97f8ff05 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_model_test_runner_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_model_test_runner_service.py @@ -30,6 +30,44 @@ from spiffworkflow_backend.services.custom_parser import MyCustomParser from spiffworkflow_backend.services.jinja_service import JinjaHelpers from spiffworkflow_backend.services.process_instance_processor import CustomScriptEngineEnvironment +DEFAULT_NSMAP = { + "bpmn": "http://www.omg.org/spec/BPMN/20100524/MODEL", + "bpmndi": "http://www.omg.org/spec/BPMN/20100524/DI", + "dc": "http://www.omg.org/spec/DD/20100524/DC", +} + + +""" +JSON file name: + The name should be in format "test_BPMN_FILE_NAME.json". + +BPMN_TASK_IDENTIIFER: + can be either task bpmn identifier or in format: + [BPMN_PROCESS_ID]:[TASK_BPMN_IDENTIFIER] + example: 'BasicServiceTaskProcess:service_task_one' + this allows for tasks to share bpmn identifiers across models + which is useful for call activities + +DATA for tasks: + This is an array of task data. This allows for the task to + be called multiple times and given different data each time. + This is useful for testing loops where each iteration needs + different input. The test will fail if the task is called + multiple times without task data input for each call. + +JSON file format: +{ + TEST_CASE_NAME: { + "tasks": { + BPMN_TASK_IDENTIIFER: { + "data": [DATA] + } + }, + "expected_output_json": DATA + } +} +""" + class UnrunnableTestCaseError(Exception): pass @@ -55,6 +93,29 @@ class BpmnFileMissingExecutableProcessError(Exception): pass +@dataclass +class TestCaseErrorDetails: + error_messages: list[str] + task_error_line: str | None = None + task_trace: list[str] | None = None + task_bpmn_identifier: str | None = None + task_bpmn_type: str | None = None + task_bpmn_name: str | None = None + task_line_number: int | None = None + stacktrace: list[str] | None = None + + output_data: dict | None = None + expected_data: dict | None = None + + +@dataclass +class TestCaseResult: + passed: bool + bpmn_file: str + test_case_identifier: str + test_case_error_details: TestCaseErrorDetails | None = None + + def _import(name: str, glbls: dict[str, Any], *args: Any) -> None: if name not in glbls: raise ImportError(f"Import not allowed: {name}", name=name) @@ -137,25 +198,6 @@ class ProcessModelTestRunnerScriptEngine(PythonScriptEngine): # type: ignore raise Exception("please override this service task in your bpmn unit test json") -@dataclass -class TestCaseErrorDetails: - error_messages: list[str] - task_error_line: str | None = None - task_trace: list[str] | None = None - task_bpmn_identifier: str | None = None - task_bpmn_name: str | None = None - task_line_number: int | None = None - stacktrace: list[str] | None = None - - -@dataclass -class TestCaseResult: - passed: bool - bpmn_file: str - test_case_identifier: str - test_case_error_details: TestCaseErrorDetails | None = None - - class ProcessModelTestRunnerDelegate: """Abstract class for the process model test runner delegate. @@ -288,45 +330,6 @@ class ProcessModelTestRunnerMostlyPureSpiffDelegate(ProcessModelTestRunnerDelega self.bpmn_processes_to_file_mappings[bpmn_process_identifier] = file_norm -DEFAULT_NSMAP = { - "bpmn": "http://www.omg.org/spec/BPMN/20100524/MODEL", - "bpmndi": "http://www.omg.org/spec/BPMN/20100524/DI", - "dc": "http://www.omg.org/spec/DD/20100524/DC", -} - - -""" -JSON file name: - The name should be in format "test_BPMN_FILE_NAME_IT_TESTS.json". - -BPMN_TASK_IDENTIIFER: - can be either task bpmn identifier or in format: - [BPMN_PROCESS_ID]:[TASK_BPMN_IDENTIFIER] - example: 'BasicServiceTaskProcess:service_task_one' - this allows for tasks to share bpmn identifiers across models - which is useful for call activities - -DATA for tasks: - This is an array of task data. This allows for the task to - be called multiple times and given different data each time. - This is useful for testing loops where each iteration needs - different input. The test will fail if the task is called - multiple times without task data input for each call. - -JSON file format: -{ - TEST_CASE_NAME: { - "tasks": { - BPMN_TASK_IDENTIIFER: { - "data": [DATA] - } - }, - "expected_output_json": DATA - } -} -""" - - class ProcessModelTestRunner: """Runs the test case json files for a given process model directory. @@ -401,6 +404,7 @@ class ProcessModelTestRunner: def run_test_case(self, bpmn_file: str, test_case_identifier: str, test_case_contents: dict) -> None: bpmn_process_instance = self._instantiate_executer(bpmn_file) method_overrides = {} + # mocking python functions within script tasks if "mocks" in test_case_contents: for method_name, mock_return_value in test_case_contents["mocks"].items(): method_overrides[method_name] = lambda value=mock_return_value: value @@ -430,23 +434,23 @@ class ProcessModelTestRunner: error_message = None if bpmn_process_instance.is_completed() is False: - error_message = [ - "Expected process instance to complete but it did not.", - f"Final data was: {bpmn_process_instance.last_task.data}", - f"Last task bpmn id: {bpmn_process_instance.last_task.task_spec.bpmn_id}", - f"Last task type: {bpmn_process_instance.last_task.task_spec.__class__.__name__}", - ] + error_message = { + "error_messages": ["Expected process instance to complete but it did not."], + "output_data": bpmn_process_instance.last_task.data, + "task_bpmn_identifier": bpmn_process_instance.last_task.task_spec.bpmn_id, + "task_bpmn_type": bpmn_process_instance.last_task.task_spec.__class__.__name__, + } elif bpmn_process_instance.success is False: - error_message = [ - "Expected process instance to succeed but it did not.", - f"Final data was: {bpmn_process_instance.data}", - ] + error_message = { + "error_messages": ["Expected process instance to succeed but it did not."], + "output_data": bpmn_process_instance.data, + } elif test_case_contents["expected_output_json"] != bpmn_process_instance.data: - error_message = [ - "Expected output did not match actual output:", - f"expected: {test_case_contents['expected_output_json']}", - f"actual: {bpmn_process_instance.data}", - ] + error_message = { + "error_messages": ["Expected output did not match actual output."], + "expected_data": test_case_contents["expected_output_json"], + "output_data": bpmn_process_instance.data, + } self._add_test_result(error_message is None, bpmn_file, test_case_identifier, error_message) def _execute_task( @@ -510,14 +514,14 @@ class ProcessModelTestRunner: passed: bool, bpmn_file: str, test_case_identifier: str, - error_messages: list[str] | None = None, + error_messages: dict | None = None, exception: Exception | None = None, ) -> None: test_case_error_details = None if exception is not None: test_case_error_details = self._exception_to_test_case_error_details(exception) elif error_messages: - test_case_error_details = TestCaseErrorDetails(error_messages=error_messages) + test_case_error_details = TestCaseErrorDetails(**error_messages) bpmn_file_relative = self._get_relative_path_of_bpmn_file(bpmn_file) test_result = TestCaseResult( diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/task_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/task_service.py index c0905807..b5ce5c54 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/task_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/task_service.py @@ -12,6 +12,7 @@ from SpiffWorkflow.task import Task as SpiffTask # type: ignore from SpiffWorkflow.util.task import TaskState # type: ignore from sqlalchemy import asc +from spiffworkflow_backend.exceptions.error import TaskMismatchError from spiffworkflow_backend.models.bpmn_process import BpmnProcessModel from spiffworkflow_backend.models.bpmn_process import BpmnProcessNotFoundError from spiffworkflow_backend.models.bpmn_process_definition import BpmnProcessDefinitionModel @@ -280,6 +281,10 @@ class TaskService: This will NOT update start_in_seconds or end_in_seconds. It also returns the relating json_data object so they can be imported later. """ + if str(spiff_task.id) != task_model.guid: + raise TaskMismatchError( + f"Given spiff task ({spiff_task.task_spec.bpmn_id} - {spiff_task.id}) and task ({task_model.guid}) must match" + ) new_properties_json = self.serializer.to_dict(spiff_task) diff --git a/spiffworkflow-backend/tests/data/with-service-task-call-activity-sub-process/main.bpmn b/spiffworkflow-backend/tests/data/with-service-task-call-activity-sub-process/main.bpmn new file mode 100644 index 00000000..077aa269 --- /dev/null +++ b/spiffworkflow-backend/tests/data/with-service-task-call-activity-sub-process/main.bpmn @@ -0,0 +1,105 @@ + + + + + Flow_0j3unyw + + + + Flow_1dbi4yf + + + + Flow_0z2u0gc + Flow_1dbi4yf + + + + + + + + + + + + + + + + + + Flow_0j3unyw + Flow_0uoa0st + + DataObjectReference_0qfbjfj + + top_level_data_object = "a" + + + + + + + + + + + + + + + + Flow_0uoa0st + Flow_0z2u0gc + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spiffworkflow-backend/tests/data/with-service-task-call-activity-sub-process/sub-level-sub-process-user-form-exampledata.json b/spiffworkflow-backend/tests/data/with-service-task-call-activity-sub-process/sub-level-sub-process-user-form-exampledata.json new file mode 100644 index 00000000..cad75d00 --- /dev/null +++ b/spiffworkflow-backend/tests/data/with-service-task-call-activity-sub-process/sub-level-sub-process-user-form-exampledata.json @@ -0,0 +1,3 @@ +{ + "firstName": "Chuck" +} diff --git a/spiffworkflow-backend/tests/data/with-service-task-call-activity-sub-process/sub-level-sub-process-user-form-schema.json b/spiffworkflow-backend/tests/data/with-service-task-call-activity-sub-process/sub-level-sub-process-user-form-schema.json new file mode 100644 index 00000000..d5598803 --- /dev/null +++ b/spiffworkflow-backend/tests/data/with-service-task-call-activity-sub-process/sub-level-sub-process-user-form-schema.json @@ -0,0 +1,15 @@ +{ + "title": "Text Field", + "description": "A simple text field that is required, has a default value, sets a placeholder, includes a description. (field name will be 'firstname')", + "type": "object", + "required": [ + "firstName" + ], + "properties": { + "firstName": { + "type": "string", + "title": "First name", + "default": "Chuck" + } + } +} diff --git a/spiffworkflow-backend/tests/data/with-service-task-call-activity-sub-process/sub-level-sub-process-user-form-uischema.json b/spiffworkflow-backend/tests/data/with-service-task-call-activity-sub-process/sub-level-sub-process-user-form-uischema.json new file mode 100644 index 00000000..9d334096 --- /dev/null +++ b/spiffworkflow-backend/tests/data/with-service-task-call-activity-sub-process/sub-level-sub-process-user-form-uischema.json @@ -0,0 +1,10 @@ +{ + "firstName": { + "ui:autofocus": true, + "ui:emptyValue": "", + "ui:placeholder": "ui:emptyValue causes this field to always be valid despite being required", + "ui:autocomplete": "family-name", + "ui:enableMarkdownInDescription": true, + "ui:description": "Make text **bold** or *italic*. Take a look at other options [here](https://probablyup.com/markdown-to-jsx/)." + } +} diff --git a/spiffworkflow-backend/tests/data/with-service-task-call-activity-sub-process/without-service-task.bpmn b/spiffworkflow-backend/tests/data/with-service-task-call-activity-sub-process/without-service-task.bpmn new file mode 100644 index 00000000..5538eeae --- /dev/null +++ b/spiffworkflow-backend/tests/data/with-service-task-call-activity-sub-process/without-service-task.bpmn @@ -0,0 +1,182 @@ + + + + + Flow_18zaszw + + + + The process instance completed successfully. + + Flow_02mnp5n + + + + Flow_18zaszw + Flow_1dbpkjb + + DataObjectReference_11gskr0 + + sub_level_data_object = "b" + + + + + + + Flow_1dbpkjb + Flow_15y3o98 + + + + Flow_0zr28rt + Flow_02mnp5n + + DataObjectReference_06uyy2z + + sub_level_data_object_two = 'c' + + + + + + Flow_15y3o98 + Flow_0zr28rt + + Flow_1ekcyuv + + + + Flow_1p3g9vw + + + + + + Flow_157qtq3 + Flow_1p3g9vw + + DataObjectReference_1v83y6h + + sub_level_data_object_three = 'd' + + + + + + + + + + Flow_1ekcyuv + Flow_157qtq3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spiffworkflow-backend/tests/files/bpmn_process_instance_data_objects_version_3.json b/spiffworkflow-backend/tests/files/bpmn_process_instance_data_objects_version_3.json index ee23bf44..b40308d7 100644 --- a/spiffworkflow-backend/tests/files/bpmn_process_instance_data_objects_version_3.json +++ b/spiffworkflow-backend/tests/files/bpmn_process_instance_data_objects_version_3.json @@ -1,6 +1,5 @@ { "data": { - "validate_only": false, "top_level_data_object": "a", "sub_level_data_object_two": "c", "sub_level_data_object_three": "d" @@ -12,9 +11,7 @@ "098e4fc2-a399-4325-b0a9-76d6c330fbf4": { "id": "098e4fc2-a399-4325-b0a9-76d6c330fbf4", "parent": "142bb43d-7d87-4341-acb5-f7762e48d8d3", - "children": [ - "aa3991dd-2e91-4210-89e1-594245a0cf15" - ], + "children": ["aa3991dd-2e91-4210-89e1-594245a0cf15"], "last_state_change": 1710947480.2315426, "state": 64, "task_spec": "StartEvent_1", @@ -28,9 +25,7 @@ "142bb43d-7d87-4341-acb5-f7762e48d8d3": { "id": "142bb43d-7d87-4341-acb5-f7762e48d8d3", "parent": null, - "children": [ - "098e4fc2-a399-4325-b0a9-76d6c330fbf4" - ], + "children": ["098e4fc2-a399-4325-b0a9-76d6c330fbf4"], "last_state_change": 1710947480.210416, "state": 64, "task_spec": "Start", @@ -42,9 +37,7 @@ "270d76e0-c1fe-4add-b58e-d5a51214a37b": { "id": "270d76e0-c1fe-4add-b58e-d5a51214a37b", "parent": "aa3991dd-2e91-4210-89e1-594245a0cf15", - "children": [ - "e1188a09-95be-4b79-9a10-f7c376fa04a0" - ], + "children": ["e1188a09-95be-4b79-9a10-f7c376fa04a0"], "last_state_change": 1710950132.28626, "state": 64, "task_spec": "top_call_activity", @@ -56,9 +49,7 @@ "973db925-12b3-4f45-95fe-53215db8929d": { "id": "973db925-12b3-4f45-95fe-53215db8929d", "parent": "e1188a09-95be-4b79-9a10-f7c376fa04a0", - "children": [ - "a8052b4d-65ed-4e55-8233-062113ebe18f" - ], + "children": ["a8052b4d-65ed-4e55-8233-062113ebe18f"], "last_state_change": 1710950132.2983754, "state": 64, "task_spec": "Process_top_level.EndJoin", @@ -82,9 +73,7 @@ "aa3991dd-2e91-4210-89e1-594245a0cf15": { "id": "aa3991dd-2e91-4210-89e1-594245a0cf15", "parent": "098e4fc2-a399-4325-b0a9-76d6c330fbf4", - "children": [ - "270d76e0-c1fe-4add-b58e-d5a51214a37b" - ], + "children": ["270d76e0-c1fe-4add-b58e-d5a51214a37b"], "last_state_change": 1710947480.247028, "state": 64, "task_spec": "top_script_task", @@ -96,9 +85,7 @@ "e1188a09-95be-4b79-9a10-f7c376fa04a0": { "id": "e1188a09-95be-4b79-9a10-f7c376fa04a0", "parent": "270d76e0-c1fe-4add-b58e-d5a51214a37b", - "children": [ - "973db925-12b3-4f45-95fe-53215db8929d" - ], + "children": ["973db925-12b3-4f45-95fe-53215db8929d"], "last_state_change": 1710950132.2913136, "state": 64, "task_spec": "Event_1swh8gs", @@ -119,9 +106,7 @@ "description": "BPMN Task", "manual": false, "lookahead": 2, - "inputs": [ - "Process_top_level.EndJoin" - ], + "inputs": ["Process_top_level.EndJoin"], "outputs": [], "bpmn_id": null, "bpmn_name": null, @@ -137,12 +122,8 @@ "description": "Default End Event", "manual": false, "lookahead": 2, - "inputs": [ - "top_call_activity" - ], - "outputs": [ - "Process_top_level.EndJoin" - ], + "inputs": ["top_call_activity"], + "outputs": ["Process_top_level.EndJoin"], "bpmn_id": "Event_1swh8gs", "bpmn_name": null, "lane": null, @@ -163,12 +144,8 @@ "description": "BPMN Task", "manual": false, "lookahead": 2, - "inputs": [ - "Event_1swh8gs" - ], - "outputs": [ - "End" - ], + "inputs": ["Event_1swh8gs"], + "outputs": ["End"], "bpmn_id": null, "bpmn_name": null, "lane": null, @@ -184,9 +161,7 @@ "manual": false, "lookahead": 2, "inputs": [], - "outputs": [ - "StartEvent_1" - ], + "outputs": ["StartEvent_1"], "bpmn_id": null, "bpmn_name": null, "lane": null, @@ -201,12 +176,8 @@ "description": "Default Start Event", "manual": false, "lookahead": 2, - "inputs": [ - "Start" - ], - "outputs": [ - "top_script_task" - ], + "inputs": ["Start"], + "outputs": ["top_script_task"], "bpmn_id": "StartEvent_1", "bpmn_name": null, "lane": null, @@ -227,12 +198,8 @@ "description": "Call Activity", "manual": false, "lookahead": 2, - "inputs": [ - "top_script_task" - ], - "outputs": [ - "Event_1swh8gs" - ], + "inputs": ["top_script_task"], + "outputs": ["Event_1swh8gs"], "bpmn_id": "top_call_activity", "bpmn_name": "Top Call Activity", "lane": null, @@ -251,12 +218,8 @@ "description": "Script Task", "manual": false, "lookahead": 2, - "inputs": [ - "StartEvent_1" - ], - "outputs": [ - "top_call_activity" - ], + "inputs": ["StartEvent_1"], + "outputs": ["top_call_activity"], "bpmn_id": "top_script_task", "bpmn_name": "Top Script Task", "lane": null, @@ -312,12 +275,8 @@ "description": "Subprocess", "manual": false, "lookahead": 2, - "inputs": [ - "sub_manual_task" - ], - "outputs": [ - "sub_script_task_two" - ], + "inputs": ["sub_manual_task"], + "outputs": ["sub_script_task_two"], "bpmn_id": "call_activity_sub_process", "bpmn_name": "Call Activity Sub Process", "lane": null, @@ -336,9 +295,7 @@ "description": "BPMN Task", "manual": false, "lookahead": 2, - "inputs": [ - "Process_sub_level.EndJoin" - ], + "inputs": ["Process_sub_level.EndJoin"], "outputs": [], "bpmn_id": null, "bpmn_name": null, @@ -354,12 +311,8 @@ "description": "Default End Event", "manual": false, "lookahead": 2, - "inputs": [ - "sub_script_task_two" - ], - "outputs": [ - "Process_sub_level.EndJoin" - ], + "inputs": ["sub_script_task_two"], + "outputs": ["Process_sub_level.EndJoin"], "bpmn_id": "EndEvent_1", "bpmn_name": null, "lane": null, @@ -382,12 +335,8 @@ "description": "BPMN Task", "manual": false, "lookahead": 2, - "inputs": [ - "EndEvent_1" - ], - "outputs": [ - "End" - ], + "inputs": ["EndEvent_1"], + "outputs": ["End"], "bpmn_id": null, "bpmn_name": null, "lane": null, @@ -403,9 +352,7 @@ "manual": false, "lookahead": 2, "inputs": [], - "outputs": [ - "StartEvent_1" - ], + "outputs": ["StartEvent_1"], "bpmn_id": null, "bpmn_name": null, "lane": null, @@ -420,12 +367,8 @@ "description": "Default Start Event", "manual": false, "lookahead": 2, - "inputs": [ - "Start" - ], - "outputs": [ - "sub_script_task" - ], + "inputs": ["Start"], + "outputs": ["sub_script_task"], "bpmn_id": "StartEvent_1", "bpmn_name": null, "lane": null, @@ -446,12 +389,8 @@ "description": "Manual Task", "manual": true, "lookahead": 2, - "inputs": [ - "sub_script_task" - ], - "outputs": [ - "call_activity_sub_process" - ], + "inputs": ["sub_script_task"], + "outputs": ["call_activity_sub_process"], "bpmn_id": "sub_manual_task", "bpmn_name": "Sub Manual Task", "lane": null, @@ -469,12 +408,8 @@ "description": "Script Task", "manual": false, "lookahead": 2, - "inputs": [ - "StartEvent_1" - ], - "outputs": [ - "sub_manual_task" - ], + "inputs": ["StartEvent_1"], + "outputs": ["sub_manual_task"], "bpmn_id": "sub_script_task", "bpmn_name": "Sub Script Task", "lane": null, @@ -500,12 +435,8 @@ "description": "Script Task", "manual": false, "lookahead": 2, - "inputs": [ - "call_activity_sub_process" - ], - "outputs": [ - "EndEvent_1" - ], + "inputs": ["call_activity_sub_process"], + "outputs": ["EndEvent_1"], "bpmn_id": "sub_script_task_two", "bpmn_name": "Sub Script Task Two", "lane": null, @@ -555,12 +486,8 @@ "description": "BPMN Task", "manual": false, "lookahead": 2, - "inputs": [ - "Event_00vjfmy" - ], - "outputs": [ - "End" - ], + "inputs": ["Event_00vjfmy"], + "outputs": ["End"], "bpmn_id": null, "bpmn_name": null, "lane": null, @@ -575,9 +502,7 @@ "description": "BPMN Task", "manual": false, "lookahead": 2, - "inputs": [ - "call_activity_sub_process.EndJoin" - ], + "inputs": ["call_activity_sub_process.EndJoin"], "outputs": [], "bpmn_id": null, "bpmn_name": null, @@ -593,12 +518,8 @@ "description": "Default End Event", "manual": false, "lookahead": 2, - "inputs": [ - "sub_level_sub_process_script_task" - ], - "outputs": [ - "call_activity_sub_process.EndJoin" - ], + "inputs": ["sub_level_sub_process_script_task"], + "outputs": ["call_activity_sub_process.EndJoin"], "bpmn_id": "Event_00vjfmy", "bpmn_name": null, "lane": null, @@ -619,12 +540,8 @@ "description": "Default Start Event", "manual": false, "lookahead": 2, - "inputs": [ - "Start" - ], - "outputs": [ - "sub_level_sub_process_script_task" - ], + "inputs": ["Start"], + "outputs": ["sub_level_sub_process_script_task"], "bpmn_id": "Event_0yfq3gm", "bpmn_name": null, "lane": null, @@ -646,9 +563,7 @@ "manual": false, "lookahead": 2, "inputs": [], - "outputs": [ - "Event_0yfq3gm" - ], + "outputs": ["Event_0yfq3gm"], "bpmn_id": null, "bpmn_name": null, "lane": null, @@ -663,12 +578,8 @@ "description": "Script Task", "manual": false, "lookahead": 2, - "inputs": [ - "Event_0yfq3gm" - ], - "outputs": [ - "Event_00vjfmy" - ], + "inputs": ["Event_0yfq3gm"], + "outputs": ["Event_00vjfmy"], "bpmn_id": "sub_level_sub_process_script_task", "bpmn_name": "Sub Level Sub Process Script Task", "lane": null, @@ -718,7 +629,6 @@ "subprocesses": { "270d76e0-c1fe-4add-b58e-d5a51214a37b": { "data": { - "validate_only": false, "top_level_data_object": "a", "sub_level_data_object_two": "c", "sub_level_data_object_three": "d" @@ -730,9 +640,7 @@ "0315382d-fdf6-4c27-8d7d-63dddf0b05fb": { "id": "0315382d-fdf6-4c27-8d7d-63dddf0b05fb", "parent": "8efb7c04-82d1-459f-b0f8-778782dd7f0e", - "children": [ - "5eb9e777-cfbf-4ef9-8ba8-79fa5d172b7e" - ], + "children": ["5eb9e777-cfbf-4ef9-8ba8-79fa5d172b7e"], "last_state_change": 1710950132.2467537, "state": 64, "task_spec": "Process_sub_level.EndJoin", @@ -744,9 +652,7 @@ "186869f9-6992-4a79-86dc-365c3906dd64": { "id": "186869f9-6992-4a79-86dc-365c3906dd64", "parent": null, - "children": [ - "b9762626-24e2-48d8-939a-ce1b17757781" - ], + "children": ["b9762626-24e2-48d8-939a-ce1b17757781"], "last_state_change": 1710947480.247221, "state": 64, "task_spec": "Start", @@ -770,9 +676,7 @@ "688506fc-ab27-4eb2-a1fa-b435dd958561": { "id": "688506fc-ab27-4eb2-a1fa-b435dd958561", "parent": "8b51d215-15ab-4e0a-8dfc-e335e685fb52", - "children": [ - "d0c6a2d9-9a43-4ccd-b4e3-ea62872f15ed" - ], + "children": ["d0c6a2d9-9a43-4ccd-b4e3-ea62872f15ed"], "last_state_change": 1710950131.6774, "state": 64, "task_spec": "sub_manual_task", @@ -784,9 +688,7 @@ "8b51d215-15ab-4e0a-8dfc-e335e685fb52": { "id": "8b51d215-15ab-4e0a-8dfc-e335e685fb52", "parent": "b9762626-24e2-48d8-939a-ce1b17757781", - "children": [ - "688506fc-ab27-4eb2-a1fa-b435dd958561" - ], + "children": ["688506fc-ab27-4eb2-a1fa-b435dd958561"], "last_state_change": 1710947480.280872, "state": 64, "task_spec": "sub_script_task", @@ -798,9 +700,7 @@ "8efb7c04-82d1-459f-b0f8-778782dd7f0e": { "id": "8efb7c04-82d1-459f-b0f8-778782dd7f0e", "parent": "b482d5b3-a8e0-4903-9d48-0dbce70bd682", - "children": [ - "0315382d-fdf6-4c27-8d7d-63dddf0b05fb" - ], + "children": ["0315382d-fdf6-4c27-8d7d-63dddf0b05fb"], "last_state_change": 1710950132.1689236, "state": 64, "task_spec": "EndEvent_1", @@ -812,9 +712,7 @@ "b482d5b3-a8e0-4903-9d48-0dbce70bd682": { "id": "b482d5b3-a8e0-4903-9d48-0dbce70bd682", "parent": "d0c6a2d9-9a43-4ccd-b4e3-ea62872f15ed", - "children": [ - "8efb7c04-82d1-459f-b0f8-778782dd7f0e" - ], + "children": ["8efb7c04-82d1-459f-b0f8-778782dd7f0e"], "last_state_change": 1710950131.849423, "state": 64, "task_spec": "sub_script_task_two", @@ -826,9 +724,7 @@ "b9762626-24e2-48d8-939a-ce1b17757781": { "id": "b9762626-24e2-48d8-939a-ce1b17757781", "parent": "186869f9-6992-4a79-86dc-365c3906dd64", - "children": [ - "8b51d215-15ab-4e0a-8dfc-e335e685fb52" - ], + "children": ["8b51d215-15ab-4e0a-8dfc-e335e685fb52"], "last_state_change": 1710947480.255076, "state": 64, "task_spec": "StartEvent_1", @@ -842,9 +738,7 @@ "d0c6a2d9-9a43-4ccd-b4e3-ea62872f15ed": { "id": "d0c6a2d9-9a43-4ccd-b4e3-ea62872f15ed", "parent": "688506fc-ab27-4eb2-a1fa-b435dd958561", - "children": [ - "b482d5b3-a8e0-4903-9d48-0dbce70bd682" - ], + "children": ["b482d5b3-a8e0-4903-9d48-0dbce70bd682"], "last_state_change": 1710950131.8426125, "state": 64, "task_spec": "call_activity_sub_process", @@ -861,7 +755,6 @@ }, "d0c6a2d9-9a43-4ccd-b4e3-ea62872f15ed": { "data": { - "validate_only": false, "top_level_data_object": "a", "sub_level_data_object_two": "c", "sub_level_data_object_three": "d" @@ -873,9 +766,7 @@ "6e6ad5c3-e701-4b59-8a81-4ed2c63bd0e1": { "id": "6e6ad5c3-e701-4b59-8a81-4ed2c63bd0e1", "parent": "b346574d-c50c-4b4b-864c-685803ebf14e", - "children": [ - "af12522c-811b-4258-a569-65890838677f" - ], + "children": ["af12522c-811b-4258-a569-65890838677f"], "last_state_change": 1710950131.829411, "state": 64, "task_spec": "call_activity_sub_process.EndJoin", @@ -887,9 +778,7 @@ "95974b26-58b8-4fc3-a6d1-2158c1ab6de8": { "id": "95974b26-58b8-4fc3-a6d1-2158c1ab6de8", "parent": "b22dae80-ce20-4565-983e-e86b98625554", - "children": [ - "b346574d-c50c-4b4b-864c-685803ebf14e" - ], + "children": ["b346574d-c50c-4b4b-864c-685803ebf14e"], "last_state_change": 1710950131.8059175, "state": 64, "task_spec": "sub_level_sub_process_script_task", @@ -913,9 +802,7 @@ "b22dae80-ce20-4565-983e-e86b98625554": { "id": "b22dae80-ce20-4565-983e-e86b98625554", "parent": "ca86a501-34f8-48fc-b284-cce8e8af058d", - "children": [ - "95974b26-58b8-4fc3-a6d1-2158c1ab6de8" - ], + "children": ["95974b26-58b8-4fc3-a6d1-2158c1ab6de8"], "last_state_change": 1710950131.7874112, "state": 64, "task_spec": "Event_0yfq3gm", @@ -929,9 +816,7 @@ "b346574d-c50c-4b4b-864c-685803ebf14e": { "id": "b346574d-c50c-4b4b-864c-685803ebf14e", "parent": "95974b26-58b8-4fc3-a6d1-2158c1ab6de8", - "children": [ - "6e6ad5c3-e701-4b59-8a81-4ed2c63bd0e1" - ], + "children": ["6e6ad5c3-e701-4b59-8a81-4ed2c63bd0e1"], "last_state_change": 1710950131.817766, "state": 64, "task_spec": "Event_00vjfmy", @@ -943,9 +828,7 @@ "ca86a501-34f8-48fc-b284-cce8e8af058d": { "id": "ca86a501-34f8-48fc-b284-cce8e8af058d", "parent": null, - "children": [ - "b22dae80-ce20-4565-983e-e86b98625554" - ], + "children": ["b22dae80-ce20-4565-983e-e86b98625554"], "last_state_change": 1710950131.677685, "state": 64, "task_spec": "Start", diff --git a/spiffworkflow-backend/tests/files/process_model_test_importer.json b/spiffworkflow-backend/tests/files/process_model_test_importer.json new file mode 100644 index 00000000..5680c972 --- /dev/null +++ b/spiffworkflow-backend/tests/files/process_model_test_importer.json @@ -0,0 +1,1106 @@ +{ + "data": { + "firstName": "Chuck", + "data_objects": { + "top_level_data_object": "a" + }, + "backend_status_response": { + "body": "{\"ok\": true}", + "mimetype": "application/json", + "http_status": 200, + "operator_identifier": "http/GetRequestV2" + } + }, + "correlations": {}, + "last_task": "911a8306-343c-4a83-953c-fc114b606b59", + "success": true, + "tasks": { + "1b83893b-8c5e-4cbf-94cc-09fc0200eca2": { + "id": "1b83893b-8c5e-4cbf-94cc-09fc0200eca2", + "parent": "88c85404-2bf6-4235-a27a-c06bfe49c5fe", + "children": ["db5ebc6c-7696-4705-9cf4-fb0441ab6f62"], + "last_state_change": 1711048490.8429425, + "state": 64, + "task_spec": "top_service_task", + "triggered": false, + "internal_data": {}, + "data": { + "backend_status_response": { + "body": "{\"ok\": true}", + "mimetype": "application/json", + "http_status": 200, + "operator_identifier": "http/GetRequestV2" + } + }, + "typename": "Task" + }, + "7044e74a-3b02-43c6-90ff-5c69e23662e2": { + "id": "7044e74a-3b02-43c6-90ff-5c69e23662e2", + "parent": null, + "children": ["babfccea-16b1-438e-83ee-33ffeade1c4e"], + "last_state_change": 1711048490.7585742, + "state": 64, + "task_spec": "Start", + "triggered": false, + "internal_data": {}, + "data": {}, + "typename": "Task" + }, + "88c85404-2bf6-4235-a27a-c06bfe49c5fe": { + "id": "88c85404-2bf6-4235-a27a-c06bfe49c5fe", + "parent": "babfccea-16b1-438e-83ee-33ffeade1c4e", + "children": ["1b83893b-8c5e-4cbf-94cc-09fc0200eca2"], + "last_state_change": 1711048490.806986, + "state": 64, + "task_spec": "top_script_task", + "triggered": false, + "internal_data": {}, + "data": {}, + "typename": "Task" + }, + "911a8306-343c-4a83-953c-fc114b606b59": { + "id": "911a8306-343c-4a83-953c-fc114b606b59", + "parent": "e95c0ea2-43b0-4bed-89a1-cfc28c8287c3", + "children": [], + "last_state_change": 1711048497.0260544, + "state": 64, + "task_spec": "End", + "triggered": false, + "internal_data": {}, + "data": { + "firstName": "Chuck", + "backend_status_response": { + "body": "{\"ok\": true}", + "mimetype": "application/json", + "http_status": 200, + "operator_identifier": "http/GetRequestV2" + } + }, + "typename": "Task" + }, + "b18bd98e-224f-462c-980e-7cfa71394e73": { + "id": "b18bd98e-224f-462c-980e-7cfa71394e73", + "parent": "db5ebc6c-7696-4705-9cf4-fb0441ab6f62", + "children": ["e95c0ea2-43b0-4bed-89a1-cfc28c8287c3"], + "last_state_change": 1711048497.008846, + "state": 64, + "task_spec": "Event_1swh8gs", + "triggered": false, + "internal_data": {}, + "data": { + "firstName": "Chuck", + "backend_status_response": { + "body": "{\"ok\": true}", + "mimetype": "application/json", + "http_status": 200, + "operator_identifier": "http/GetRequestV2" + } + }, + "typename": "Task" + }, + "babfccea-16b1-438e-83ee-33ffeade1c4e": { + "id": "babfccea-16b1-438e-83ee-33ffeade1c4e", + "parent": "7044e74a-3b02-43c6-90ff-5c69e23662e2", + "children": ["88c85404-2bf6-4235-a27a-c06bfe49c5fe"], + "last_state_change": 1711048490.7823734, + "state": 64, + "task_spec": "StartEvent_1", + "triggered": false, + "internal_data": { + "event_fired": true + }, + "data": {}, + "typename": "Task" + }, + "db5ebc6c-7696-4705-9cf4-fb0441ab6f62": { + "id": "db5ebc6c-7696-4705-9cf4-fb0441ab6f62", + "parent": "1b83893b-8c5e-4cbf-94cc-09fc0200eca2", + "children": ["b18bd98e-224f-462c-980e-7cfa71394e73"], + "last_state_change": 1711048497.003491, + "state": 64, + "task_spec": "top_call_activity", + "triggered": false, + "internal_data": {}, + "data": { + "firstName": "Chuck", + "backend_status_response": { + "body": "{\"ok\": true}", + "mimetype": "application/json", + "http_status": 200, + "operator_identifier": "http/GetRequestV2" + } + }, + "typename": "Task" + }, + "e95c0ea2-43b0-4bed-89a1-cfc28c8287c3": { + "id": "e95c0ea2-43b0-4bed-89a1-cfc28c8287c3", + "parent": "b18bd98e-224f-462c-980e-7cfa71394e73", + "children": ["911a8306-343c-4a83-953c-fc114b606b59"], + "last_state_change": 1711048497.0182238, + "state": 64, + "task_spec": "Process_top_level.EndJoin", + "triggered": false, + "internal_data": {}, + "data": { + "firstName": "Chuck", + "backend_status_response": { + "body": "{\"ok\": true}", + "mimetype": "application/json", + "http_status": 200, + "operator_identifier": "http/GetRequestV2" + } + }, + "typename": "Task" + } + }, + "root": "7044e74a-3b02-43c6-90ff-5c69e23662e2", + "spec": { + "name": "Process_top_level", + "description": "Top Level Process", + "file": "main.bpmn", + "task_specs": { + "End": { + "name": "End", + "description": "BPMN Task", + "manual": false, + "lookahead": 2, + "inputs": ["Process_top_level.EndJoin"], + "outputs": [], + "bpmn_id": null, + "bpmn_name": null, + "lane": null, + "documentation": null, + "data_input_associations": [], + "data_output_associations": [], + "io_specification": null, + "typename": "SimpleBpmnTask" + }, + "Event_1swh8gs": { + "name": "Event_1swh8gs", + "description": "Default End Event", + "manual": false, + "lookahead": 2, + "inputs": ["top_call_activity"], + "outputs": ["Process_top_level.EndJoin"], + "bpmn_id": "Event_1swh8gs", + "bpmn_name": null, + "lane": null, + "documentation": null, + "data_input_associations": [], + "data_output_associations": [], + "io_specification": null, + "event_definition": { + "description": "Default", + "name": null, + "typename": "NoneEventDefinition" + }, + "typename": "EndEvent", + "extensions": {} + }, + "Process_top_level.EndJoin": { + "name": "Process_top_level.EndJoin", + "description": "BPMN Task", + "manual": false, + "lookahead": 2, + "inputs": ["Event_1swh8gs"], + "outputs": ["End"], + "bpmn_id": null, + "bpmn_name": null, + "lane": null, + "documentation": null, + "data_input_associations": [], + "data_output_associations": [], + "io_specification": null, + "typename": "_EndJoin" + }, + "Start": { + "name": "Start", + "description": "BPMN Task", + "manual": false, + "lookahead": 2, + "inputs": [], + "outputs": ["StartEvent_1"], + "bpmn_id": null, + "bpmn_name": null, + "lane": null, + "documentation": null, + "data_input_associations": [], + "data_output_associations": [], + "io_specification": null, + "typename": "BpmnStartTask" + }, + "StartEvent_1": { + "name": "StartEvent_1", + "description": "Default Start Event", + "manual": false, + "lookahead": 2, + "inputs": ["Start"], + "outputs": ["top_script_task"], + "bpmn_id": "StartEvent_1", + "bpmn_name": null, + "lane": null, + "documentation": null, + "data_input_associations": [], + "data_output_associations": [], + "io_specification": null, + "event_definition": { + "description": "Default", + "name": null, + "typename": "NoneEventDefinition" + }, + "typename": "StartEvent", + "extensions": {} + }, + "top_call_activity": { + "name": "top_call_activity", + "description": "Call Activity", + "manual": false, + "lookahead": 2, + "inputs": ["top_service_task"], + "outputs": ["Event_1swh8gs"], + "bpmn_id": "top_call_activity", + "bpmn_name": "Top Call Activity", + "lane": null, + "documentation": null, + "data_input_associations": [], + "data_output_associations": [], + "io_specification": null, + "prescript": null, + "postscript": null, + "spec": "Process_sub_level", + "typename": "CallActivity", + "extensions": {} + }, + "top_script_task": { + "name": "top_script_task", + "description": "Script Task", + "manual": false, + "lookahead": 2, + "inputs": ["StartEvent_1"], + "outputs": ["top_service_task"], + "bpmn_id": "top_script_task", + "bpmn_name": "Top Script Task", + "lane": null, + "documentation": null, + "data_input_associations": [], + "data_output_associations": [ + { + "bpmn_id": "top_level_data_object", + "bpmn_name": "top_level_data_object", + "category": null, + "typename": "DataObject" + } + ], + "io_specification": null, + "prescript": null, + "postscript": null, + "script": "top_level_data_object = \"a\"", + "typename": "ScriptTask", + "extensions": { + "serviceTaskOperator": { + "name": "http/GetRequestV2", + "parameters": { + "url": { + "type": "str", + "value": "\"http://localhost:7000/v1.0/status\"" + } + }, + "resultVariable": "the_response" + } + } + }, + "top_service_task": { + "name": "top_service_task", + "description": "Service Task", + "manual": false, + "lookahead": 2, + "inputs": ["top_script_task"], + "outputs": ["top_call_activity"], + "bpmn_id": "top_service_task", + "bpmn_name": "Top Service Task", + "lane": null, + "documentation": null, + "data_input_associations": [], + "data_output_associations": [], + "io_specification": null, + "prescript": null, + "postscript": null, + "operation_name": "http/GetRequestV2", + "operation_params": { + "url": { + "type": "str", + "value": "\"http://localhost:7000/v1.0/status\"" + } + }, + "result_variable": "backend_status_response", + "typename": "ServiceTask", + "extensions": { + "serviceTaskOperator": { + "name": "http/GetRequestV2", + "parameters": { + "url": { + "type": "str", + "value": "\"http://localhost:7000/v1.0/status\"" + } + }, + "resultVariable": "backend_status_response" + } + } + } + }, + "io_specification": null, + "data_objects": { + "top_level_data_object": { + "bpmn_id": "top_level_data_object", + "bpmn_name": "top_level_data_object", + "category": null, + "typename": "DataObject" + } + }, + "correlation_keys": {}, + "typename": "BpmnProcessSpec" + }, + "subprocess_specs": { + "Process_sub_level": { + "name": "Process_sub_level", + "description": "Process Sub Level", + "file": "without-service-task.bpmn", + "task_specs": { + "call_activity_sub_process": { + "name": "call_activity_sub_process", + "description": "Subprocess", + "manual": false, + "lookahead": 2, + "inputs": ["sub_manual_task"], + "outputs": ["sub_script_task_two"], + "bpmn_id": "call_activity_sub_process", + "bpmn_name": "Call Activity Sub Process", + "lane": null, + "documentation": null, + "data_input_associations": [], + "data_output_associations": [], + "io_specification": null, + "prescript": null, + "postscript": null, + "spec": "call_activity_sub_process", + "typename": "SubWorkflowTask", + "extensions": {} + }, + "End": { + "name": "End", + "description": "BPMN Task", + "manual": false, + "lookahead": 2, + "inputs": ["Process_sub_level.EndJoin"], + "outputs": [], + "bpmn_id": null, + "bpmn_name": null, + "lane": null, + "documentation": null, + "data_input_associations": [], + "data_output_associations": [], + "io_specification": null, + "typename": "SimpleBpmnTask" + }, + "EndEvent_1": { + "name": "EndEvent_1", + "description": "Default End Event", + "manual": false, + "lookahead": 2, + "inputs": ["sub_script_task_two"], + "outputs": ["Process_sub_level.EndJoin"], + "bpmn_id": "EndEvent_1", + "bpmn_name": null, + "lane": null, + "documentation": null, + "data_input_associations": [], + "data_output_associations": [], + "io_specification": null, + "event_definition": { + "description": "Default", + "name": null, + "typename": "NoneEventDefinition" + }, + "typename": "EndEvent", + "extensions": { + "instructionsForEndUser": "The process instance completed successfully." + } + }, + "Process_sub_level.EndJoin": { + "name": "Process_sub_level.EndJoin", + "description": "BPMN Task", + "manual": false, + "lookahead": 2, + "inputs": ["EndEvent_1"], + "outputs": ["End"], + "bpmn_id": null, + "bpmn_name": null, + "lane": null, + "documentation": null, + "data_input_associations": [], + "data_output_associations": [], + "io_specification": null, + "typename": "_EndJoin" + }, + "Start": { + "name": "Start", + "description": "BPMN Task", + "manual": false, + "lookahead": 2, + "inputs": [], + "outputs": ["StartEvent_1"], + "bpmn_id": null, + "bpmn_name": null, + "lane": null, + "documentation": null, + "data_input_associations": [], + "data_output_associations": [], + "io_specification": null, + "typename": "BpmnStartTask" + }, + "StartEvent_1": { + "name": "StartEvent_1", + "description": "Default Start Event", + "manual": false, + "lookahead": 2, + "inputs": ["Start"], + "outputs": ["sub_script_task"], + "bpmn_id": "StartEvent_1", + "bpmn_name": null, + "lane": null, + "documentation": null, + "data_input_associations": [], + "data_output_associations": [], + "io_specification": null, + "event_definition": { + "description": "Default", + "name": null, + "typename": "NoneEventDefinition" + }, + "typename": "StartEvent", + "extensions": {} + }, + "sub_manual_task": { + "name": "sub_manual_task", + "description": "Manual Task", + "manual": true, + "lookahead": 2, + "inputs": ["sub_script_task"], + "outputs": ["call_activity_sub_process"], + "bpmn_id": "sub_manual_task", + "bpmn_name": "Sub Manual Task", + "lane": null, + "documentation": null, + "data_input_associations": [], + "data_output_associations": [], + "io_specification": null, + "prescript": null, + "postscript": null, + "typename": "ManualTask", + "extensions": {} + }, + "sub_script_task": { + "name": "sub_script_task", + "description": "Script Task", + "manual": false, + "lookahead": 2, + "inputs": ["StartEvent_1"], + "outputs": ["sub_manual_task"], + "bpmn_id": "sub_script_task", + "bpmn_name": "Sub Script Task", + "lane": null, + "documentation": null, + "data_input_associations": [], + "data_output_associations": [ + { + "bpmn_id": "sub_level_data_object", + "bpmn_name": "sub_level_data_object", + "category": null, + "typename": "DataObject" + } + ], + "io_specification": null, + "prescript": null, + "postscript": null, + "script": "sub_level_data_object = \"b\"", + "typename": "ScriptTask", + "extensions": {} + }, + "sub_script_task_two": { + "name": "sub_script_task_two", + "description": "Script Task", + "manual": false, + "lookahead": 2, + "inputs": ["call_activity_sub_process"], + "outputs": ["EndEvent_1"], + "bpmn_id": "sub_script_task_two", + "bpmn_name": "Sub Script Task Two", + "lane": null, + "documentation": null, + "data_input_associations": [], + "data_output_associations": [ + { + "bpmn_id": "sub_level_data_object_two", + "bpmn_name": "sub_level_data_object_two", + "category": null, + "typename": "DataObject" + } + ], + "io_specification": null, + "prescript": null, + "postscript": null, + "script": "sub_level_data_object_two = 'c'", + "typename": "ScriptTask", + "extensions": {} + } + }, + "io_specification": null, + "data_objects": { + "sub_level_data_object": { + "bpmn_id": "sub_level_data_object", + "bpmn_name": "sub_level_data_object", + "category": null, + "typename": "DataObject" + }, + "sub_level_data_object_two": { + "bpmn_id": "sub_level_data_object_two", + "bpmn_name": "sub_level_data_object_two", + "category": null, + "typename": "DataObject" + } + }, + "correlation_keys": {}, + "typename": "BpmnProcessSpec" + }, + "call_activity_sub_process": { + "name": "call_activity_sub_process", + "description": "Call Activity Sub Process", + "file": "without-service-task.bpmn", + "task_specs": { + "call_activity_sub_process.EndJoin": { + "name": "call_activity_sub_process.EndJoin", + "description": "BPMN Task", + "manual": false, + "lookahead": 2, + "inputs": ["Event_00vjfmy"], + "outputs": ["End"], + "bpmn_id": null, + "bpmn_name": null, + "lane": null, + "documentation": null, + "data_input_associations": [], + "data_output_associations": [], + "io_specification": null, + "typename": "_EndJoin" + }, + "End": { + "name": "End", + "description": "BPMN Task", + "manual": false, + "lookahead": 2, + "inputs": ["call_activity_sub_process.EndJoin"], + "outputs": [], + "bpmn_id": null, + "bpmn_name": null, + "lane": null, + "documentation": null, + "data_input_associations": [], + "data_output_associations": [], + "io_specification": null, + "typename": "SimpleBpmnTask" + }, + "Event_00vjfmy": { + "name": "Event_00vjfmy", + "description": "Default End Event", + "manual": false, + "lookahead": 2, + "inputs": ["sub_level_sub_process_script_task"], + "outputs": ["call_activity_sub_process.EndJoin"], + "bpmn_id": "Event_00vjfmy", + "bpmn_name": null, + "lane": null, + "documentation": null, + "data_input_associations": [], + "data_output_associations": [], + "io_specification": null, + "event_definition": { + "description": "Default", + "name": null, + "typename": "NoneEventDefinition" + }, + "typename": "EndEvent", + "extensions": {} + }, + "Event_0yfq3gm": { + "name": "Event_0yfq3gm", + "description": "Default Start Event", + "manual": false, + "lookahead": 2, + "inputs": ["Start"], + "outputs": ["sub_level_sub_process_user_task"], + "bpmn_id": "Event_0yfq3gm", + "bpmn_name": null, + "lane": null, + "documentation": null, + "data_input_associations": [], + "data_output_associations": [], + "io_specification": null, + "event_definition": { + "description": "Default", + "name": null, + "typename": "NoneEventDefinition" + }, + "typename": "StartEvent", + "extensions": {} + }, + "Start": { + "name": "Start", + "description": "BPMN Task", + "manual": false, + "lookahead": 2, + "inputs": [], + "outputs": ["Event_0yfq3gm"], + "bpmn_id": null, + "bpmn_name": null, + "lane": null, + "documentation": null, + "data_input_associations": [], + "data_output_associations": [], + "io_specification": null, + "typename": "BpmnStartTask" + }, + "sub_level_sub_process_script_task": { + "name": "sub_level_sub_process_script_task", + "description": "Script Task", + "manual": false, + "lookahead": 2, + "inputs": ["sub_level_sub_process_user_task"], + "outputs": ["Event_00vjfmy"], + "bpmn_id": "sub_level_sub_process_script_task", + "bpmn_name": "Sub Level Sub Process Script Task", + "lane": null, + "documentation": null, + "data_input_associations": [], + "data_output_associations": [ + { + "bpmn_id": "sub_level_data_object_three", + "bpmn_name": "sub_level_data_object_three", + "category": null, + "typename": "DataObject" + } + ], + "io_specification": null, + "prescript": null, + "postscript": null, + "script": "sub_level_data_object_three = 'd'", + "typename": "ScriptTask", + "extensions": {} + }, + "sub_level_sub_process_user_task": { + "name": "sub_level_sub_process_user_task", + "description": "User Task", + "manual": true, + "lookahead": 2, + "inputs": ["Event_0yfq3gm"], + "outputs": ["sub_level_sub_process_script_task"], + "bpmn_id": "sub_level_sub_process_user_task", + "bpmn_name": "Sub Level Sub Process User Task", + "lane": null, + "documentation": null, + "data_input_associations": [], + "data_output_associations": [], + "io_specification": null, + "prescript": null, + "postscript": null, + "typename": "UserTask", + "extensions": { + "properties": { + "formUiSchemaFilename": "sub-level-sub-process-user-form-uischema.json", + "formJsonSchemaFilename": "sub-level-sub-process-user-form-schema.json" + } + } + } + }, + "io_specification": null, + "data_objects": { + "sub_level_data_object_three": { + "bpmn_id": "sub_level_data_object_three", + "bpmn_name": "sub_level_data_object_three", + "category": null, + "typename": "DataObject" + } + }, + "correlation_keys": {}, + "typename": "BpmnProcessSpec" + } + }, + "subprocesses": { + "db5ebc6c-7696-4705-9cf4-fb0441ab6f62": { + "data": { + "firstName": "Chuck", + "data_objects": { + "sub_level_data_object": "b", + "sub_level_data_object_two": "c" + }, + "backend_status_response": { + "body": "{\"ok\": true}", + "mimetype": "application/json", + "http_status": 200, + "operator_identifier": "http/GetRequestV2" + } + }, + "correlations": {}, + "last_task": "4b903b29-1c46-4b2c-bf22-eabac3fb235f", + "success": true, + "tasks": { + "09eb3d41-eca6-425b-bacf-38e79790cf28": { + "id": "09eb3d41-eca6-425b-bacf-38e79790cf28", + "parent": "f3c8cc7f-302b-4699-b6ff-4196dcfed42c", + "children": ["4b903b29-1c46-4b2c-bf22-eabac3fb235f"], + "last_state_change": 1711048496.9747708, + "state": 64, + "task_spec": "Process_sub_level.EndJoin", + "triggered": false, + "internal_data": {}, + "data": { + "firstName": "Chuck", + "backend_status_response": { + "body": "{\"ok\": true}", + "mimetype": "application/json", + "http_status": 200, + "operator_identifier": "http/GetRequestV2" + } + }, + "typename": "Task" + }, + "10f09dfb-9afa-4b0e-9cd1-3e65da8ab380": { + "id": "10f09dfb-9afa-4b0e-9cd1-3e65da8ab380", + "parent": "2976389b-3748-4561-a6ff-daa445c8bb53", + "children": ["86ce80d7-e8fa-4184-8871-25c3711f64c9"], + "last_state_change": 1711048494.2738447, + "state": 64, + "task_spec": "sub_manual_task", + "triggered": false, + "internal_data": {}, + "data": { + "backend_status_response": { + "body": "{\"ok\": true}", + "mimetype": "application/json", + "http_status": 200, + "operator_identifier": "http/GetRequestV2" + } + }, + "typename": "Task" + }, + "2976389b-3748-4561-a6ff-daa445c8bb53": { + "id": "2976389b-3748-4561-a6ff-daa445c8bb53", + "parent": "81b8c283-696f-44df-b27d-4d5e0fd101ba", + "children": ["10f09dfb-9afa-4b0e-9cd1-3e65da8ab380"], + "last_state_change": 1711048490.8815038, + "state": 64, + "task_spec": "sub_script_task", + "triggered": false, + "internal_data": {}, + "data": { + "backend_status_response": { + "body": "{\"ok\": true}", + "mimetype": "application/json", + "http_status": 200, + "operator_identifier": "http/GetRequestV2" + } + }, + "typename": "Task" + }, + "4b903b29-1c46-4b2c-bf22-eabac3fb235f": { + "id": "4b903b29-1c46-4b2c-bf22-eabac3fb235f", + "parent": "09eb3d41-eca6-425b-bacf-38e79790cf28", + "children": [], + "last_state_change": 1711048496.9838288, + "state": 64, + "task_spec": "End", + "triggered": false, + "internal_data": {}, + "data": { + "firstName": "Chuck", + "backend_status_response": { + "body": "{\"ok\": true}", + "mimetype": "application/json", + "http_status": 200, + "operator_identifier": "http/GetRequestV2" + } + }, + "typename": "Task" + }, + "6279f995-89a1-42b6-8cee-6521a7bbb525": { + "id": "6279f995-89a1-42b6-8cee-6521a7bbb525", + "parent": "86ce80d7-e8fa-4184-8871-25c3711f64c9", + "children": ["f3c8cc7f-302b-4699-b6ff-4196dcfed42c"], + "last_state_change": 1711048496.624915, + "state": 64, + "task_spec": "sub_script_task_two", + "triggered": false, + "internal_data": {}, + "data": { + "firstName": "Chuck", + "backend_status_response": { + "body": "{\"ok\": true}", + "mimetype": "application/json", + "http_status": 200, + "operator_identifier": "http/GetRequestV2" + } + }, + "typename": "Task" + }, + "81b8c283-696f-44df-b27d-4d5e0fd101ba": { + "id": "81b8c283-696f-44df-b27d-4d5e0fd101ba", + "parent": "b6df484c-5d4b-4967-a169-50ac5b68ce7c", + "children": ["2976389b-3748-4561-a6ff-daa445c8bb53"], + "last_state_change": 1711048490.8500838, + "state": 64, + "task_spec": "StartEvent_1", + "triggered": false, + "internal_data": { + "event_fired": true + }, + "data": { + "backend_status_response": { + "body": "{\"ok\": true}", + "mimetype": "application/json", + "http_status": 200, + "operator_identifier": "http/GetRequestV2" + } + }, + "typename": "Task" + }, + "86ce80d7-e8fa-4184-8871-25c3711f64c9": { + "id": "86ce80d7-e8fa-4184-8871-25c3711f64c9", + "parent": "10f09dfb-9afa-4b0e-9cd1-3e65da8ab380", + "children": ["6279f995-89a1-42b6-8cee-6521a7bbb525"], + "last_state_change": 1711048496.6147397, + "state": 64, + "task_spec": "call_activity_sub_process", + "triggered": false, + "internal_data": {}, + "data": { + "firstName": "Chuck", + "backend_status_response": { + "body": "{\"ok\": true}", + "mimetype": "application/json", + "http_status": 200, + "operator_identifier": "http/GetRequestV2" + } + }, + "typename": "Task" + }, + "b6df484c-5d4b-4967-a169-50ac5b68ce7c": { + "id": "b6df484c-5d4b-4967-a169-50ac5b68ce7c", + "parent": null, + "children": ["81b8c283-696f-44df-b27d-4d5e0fd101ba"], + "last_state_change": 1711048490.8432417, + "state": 64, + "task_spec": "Start", + "triggered": false, + "internal_data": {}, + "data": { + "backend_status_response": { + "body": "{\"ok\": true}", + "mimetype": "application/json", + "http_status": 200, + "operator_identifier": "http/GetRequestV2" + } + }, + "typename": "Task" + }, + "f3c8cc7f-302b-4699-b6ff-4196dcfed42c": { + "id": "f3c8cc7f-302b-4699-b6ff-4196dcfed42c", + "parent": "6279f995-89a1-42b6-8cee-6521a7bbb525", + "children": ["09eb3d41-eca6-425b-bacf-38e79790cf28"], + "last_state_change": 1711048496.905861, + "state": 64, + "task_spec": "EndEvent_1", + "triggered": false, + "internal_data": {}, + "data": { + "firstName": "Chuck", + "backend_status_response": { + "body": "{\"ok\": true}", + "mimetype": "application/json", + "http_status": 200, + "operator_identifier": "http/GetRequestV2" + } + }, + "typename": "Task" + } + }, + "root": "b6df484c-5d4b-4967-a169-50ac5b68ce7c", + "parent_task_id": "db5ebc6c-7696-4705-9cf4-fb0441ab6f62", + "spec": "Process_sub_level", + "typename": "BpmnSubWorkflow" + }, + "86ce80d7-e8fa-4184-8871-25c3711f64c9": { + "data": { + "firstName": "Chuck", + "data_objects": { + "sub_level_data_object_three": "d" + }, + "backend_status_response": { + "body": "{\"ok\": true}", + "mimetype": "application/json", + "http_status": 200, + "operator_identifier": "http/GetRequestV2" + } + }, + "correlations": {}, + "last_task": "f040826c-a6fd-4df3-94a3-cb52ee69363f", + "success": true, + "tasks": { + "1b5305a7-19f5-4ddc-8d9b-4264677b1e1d": { + "id": "1b5305a7-19f5-4ddc-8d9b-4264677b1e1d", + "parent": "30f001e6-bde6-4b14-9b42-ebaba70c01df", + "children": ["5039396b-ceae-4037-8eba-afa657de7610"], + "last_state_change": 1711048496.5584755, + "state": 64, + "task_spec": "Event_00vjfmy", + "triggered": false, + "internal_data": {}, + "data": { + "firstName": "Chuck", + "backend_status_response": { + "body": "{\"ok\": true}", + "mimetype": "application/json", + "http_status": 200, + "operator_identifier": "http/GetRequestV2" + } + }, + "typename": "Task" + }, + "30f001e6-bde6-4b14-9b42-ebaba70c01df": { + "id": "30f001e6-bde6-4b14-9b42-ebaba70c01df", + "parent": "aa169404-fecc-4b39-b3cc-6f715f715eb1", + "children": ["1b5305a7-19f5-4ddc-8d9b-4264677b1e1d"], + "last_state_change": 1711048496.541719, + "state": 64, + "task_spec": "sub_level_sub_process_script_task", + "triggered": false, + "internal_data": {}, + "data": { + "firstName": "Chuck", + "backend_status_response": { + "body": "{\"ok\": true}", + "mimetype": "application/json", + "http_status": 200, + "operator_identifier": "http/GetRequestV2" + } + }, + "typename": "Task" + }, + "5039396b-ceae-4037-8eba-afa657de7610": { + "id": "5039396b-ceae-4037-8eba-afa657de7610", + "parent": "1b5305a7-19f5-4ddc-8d9b-4264677b1e1d", + "children": ["f040826c-a6fd-4df3-94a3-cb52ee69363f"], + "last_state_change": 1711048496.5812914, + "state": 64, + "task_spec": "call_activity_sub_process.EndJoin", + "triggered": false, + "internal_data": {}, + "data": { + "firstName": "Chuck", + "backend_status_response": { + "body": "{\"ok\": true}", + "mimetype": "application/json", + "http_status": 200, + "operator_identifier": "http/GetRequestV2" + } + }, + "typename": "Task" + }, + "8232668f-39c6-4932-a236-711cd455ef3a": { + "id": "8232668f-39c6-4932-a236-711cd455ef3a", + "parent": "d2b07903-d018-43a6-9927-a9d0c99ce1c9", + "children": ["aa169404-fecc-4b39-b3cc-6f715f715eb1"], + "last_state_change": 1711048494.403072, + "state": 64, + "task_spec": "Event_0yfq3gm", + "triggered": false, + "internal_data": { + "event_fired": true + }, + "data": { + "backend_status_response": { + "body": "{\"ok\": true}", + "mimetype": "application/json", + "http_status": 200, + "operator_identifier": "http/GetRequestV2" + } + }, + "typename": "Task" + }, + "aa169404-fecc-4b39-b3cc-6f715f715eb1": { + "id": "aa169404-fecc-4b39-b3cc-6f715f715eb1", + "parent": "8232668f-39c6-4932-a236-711cd455ef3a", + "children": ["30f001e6-bde6-4b14-9b42-ebaba70c01df"], + "last_state_change": 1711048496.483813, + "state": 64, + "task_spec": "sub_level_sub_process_user_task", + "triggered": false, + "internal_data": {}, + "data": { + "firstName": "Chuck", + "backend_status_response": { + "body": "{\"ok\": true}", + "mimetype": "application/json", + "http_status": 200, + "operator_identifier": "http/GetRequestV2" + } + }, + "typename": "Task" + }, + "d2b07903-d018-43a6-9927-a9d0c99ce1c9": { + "id": "d2b07903-d018-43a6-9927-a9d0c99ce1c9", + "parent": null, + "children": ["8232668f-39c6-4932-a236-711cd455ef3a"], + "last_state_change": 1711048494.2741008, + "state": 64, + "task_spec": "Start", + "triggered": false, + "internal_data": {}, + "data": { + "backend_status_response": { + "body": "{\"ok\": true}", + "mimetype": "application/json", + "http_status": 200, + "operator_identifier": "http/GetRequestV2" + } + }, + "typename": "Task" + }, + "f040826c-a6fd-4df3-94a3-cb52ee69363f": { + "id": "f040826c-a6fd-4df3-94a3-cb52ee69363f", + "parent": "5039396b-ceae-4037-8eba-afa657de7610", + "children": [], + "last_state_change": 1711048496.5936687, + "state": 64, + "task_spec": "End", + "triggered": false, + "internal_data": {}, + "data": { + "firstName": "Chuck", + "backend_status_response": { + "body": "{\"ok\": true}", + "mimetype": "application/json", + "http_status": 200, + "operator_identifier": "http/GetRequestV2" + } + }, + "typename": "Task" + } + }, + "root": "d2b07903-d018-43a6-9927-a9d0c99ce1c9", + "parent_task_id": "86ce80d7-e8fa-4184-8871-25c3711f64c9", + "spec": "call_activity_sub_process", + "typename": "BpmnSubWorkflow" + } + }, + "bpmn_events": [], + "typename": "BpmnWorkflow" +} diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/helpers/base_test.py b/spiffworkflow-backend/tests/spiffworkflow_backend/helpers/base_test.py index 6d20643c..1fc160e4 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/helpers/base_test.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/helpers/base_test.py @@ -11,6 +11,7 @@ from flask.app import Flask from flask.testing import FlaskClient from spiffworkflow_backend.exceptions.api_error import ApiError from spiffworkflow_backend.models.db import db +from spiffworkflow_backend.models.human_task import HumanTaskModel from spiffworkflow_backend.models.message_instance import MessageInstanceModel from spiffworkflow_backend.models.permission_assignment import Permission from spiffworkflow_backend.models.permission_target import PermissionTargetModel @@ -551,11 +552,21 @@ class BaseTest: db.session.delete(process_instance_report) db.session.commit() - def complete_next_manual_task(self, processor: ProcessInstanceProcessor) -> None: + def complete_next_manual_task( + self, + processor: ProcessInstanceProcessor, + execution_mode: str | None = None, + data: dict | None = None, + ) -> None: user_task = processor.get_ready_user_tasks()[0] - human_task = processor.process_instance_model.human_tasks[0] + human_task = HumanTaskModel.query.filter_by(task_guid=str(user_task.id)).first() ProcessInstanceService.complete_form_task( - processor, user_task, {}, processor.process_instance_model.process_initiator, human_task + processor=processor, + spiff_task=user_task, + data=data or {}, + user=processor.process_instance_model.process_initiator, + human_task=human_task, + execution_mode=execution_mode, ) @contextmanager @@ -583,3 +594,6 @@ class BaseTest: elif isinstance(bpmn_process_dict, list): for item in bpmn_process_dict: self.round_last_state_change(item) + + def get_test_file(self, *args: str) -> str: + return os.path.join(current_app.instance_path, "..", "..", "tests", "files", *args) diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_extensions_controller.py b/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_extensions_controller.py index 293483b0..f3af446e 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_extensions_controller.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_extensions_controller.py @@ -37,7 +37,6 @@ class TestExtensionsController(BaseTest): "Mike": "Awesome", "my_var": "Hello World", "person": "Kevin", - "validate_only": False, "wonderfulness": "Very wonderful", "OUR_AWESOME_INPUT": "the awesome value", } diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_models_controller.py b/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_models_controller.py index e6973278..5391457a 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_models_controller.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_models_controller.py @@ -1,9 +1,14 @@ import io +import json from hashlib import sha256 +from unittest.mock import patch from flask.app import Flask from flask.testing import FlaskClient +from spiffworkflow_backend.models.process_instance import ProcessInstanceModel +from spiffworkflow_backend.models.process_instance import ProcessInstanceStatus from spiffworkflow_backend.models.user import UserModel +from spiffworkflow_backend.services.process_instance_processor import ProcessInstanceProcessor from spiffworkflow_backend.services.spec_file_service import SpecFileService from tests.spiffworkflow_backend.helpers.base_test import BaseTest @@ -103,6 +108,78 @@ class TestProcessModelsController(BaseTest): ) assert json["error_code"] == "process_model_cannot_be_found" + def test_process_model_test_generate( + self, + app: Flask, + client: FlaskClient, + with_db_and_bpmn_file_cleanup: None, + with_super_admin_user: UserModel, + ) -> None: + process_model = load_test_spec( + process_model_id="test_group/with-service-task-call-activity-sub-process", + process_model_source_directory="with-service-task-call-activity-sub-process", + ) + process_instance = self.create_process_instance_from_process_model(process_model, user=with_super_admin_user) + process_instance_id = process_instance.id + processor = ProcessInstanceProcessor(process_instance) + 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"}) + process_instance = ProcessInstanceModel.query.filter_by(id=process_instance_id).first() + assert process_instance.status == ProcessInstanceStatus.complete.value + + url = f"/v1.0/process-model-tests/create/{process_model.modified_process_model_identifier()}" + response = client.post( + url, + headers=self.logged_in_headers(with_super_admin_user), + content_type="application/json", + data=json.dumps({"process_instance_id": process_instance_id}), + ) + assert response.status_code == 200 + + test_case_identifier = f"test_case_for_process_instance_{process_instance_id}" + expected_specification = { + test_case_identifier: { + "tasks": { + "Process_sub_level:sub_manual_task": {"data": [{}]}, + "call_activity_sub_process:sub_level_sub_process_user_task": {"data": [{"firstName": "Chuck"}]}, + "Process_top_level:top_service_task": { + "data": [ + { + "backend_status_response": { + "body": '{"ok": true}', + "mimetype": "application/json", + "http_status": 200, + "operator_identifier": "http/GetRequestV2", + } + } + ] + }, + }, + "expected_output_json": { + "firstName": "Chuck", + "data_objects": {"top_level_data_object": "a"}, + "backend_status_response": { + "body": '{"ok": true}', + "mimetype": "application/json", + "http_status": 200, + "operator_identifier": "http/GetRequestV2", + }, + }, + } + } + assert expected_specification == response.json + def _get_process_show_show_response( self, client: FlaskClient, user: UserModel, process_model_id: str, expected_response: int = 200 ) -> dict: diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py index b5e3d083..38b850f2 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py @@ -127,7 +127,8 @@ class TestAuthorizationService(BaseTest): ("/process-instances/some-process-group:some-process-model:*", "read"), ("/process-model-natural-language/some-process-group:some-process-model:*", "create"), ("/process-model-publish/some-process-group:some-process-model:*", "create"), - ("/process-model-tests/some-process-group:some-process-model:*", "create"), + ("/process-model-tests/create/some-process-group:some-process-model:*", "create"), + ("/process-model-tests/run/some-process-group:some-process-model:*", "create"), ("/process-models/some-process-group:some-process-model:*", "create"), ("/process-models/some-process-group:some-process-model:*", "delete"), ("/process-models/some-process-group:some-process-model:*", "read"), @@ -209,7 +210,8 @@ class TestAuthorizationService(BaseTest): ("/process-instances/some-process-group:some-process-model/*", "read"), ("/process-model-natural-language/some-process-group:some-process-model/*", "create"), ("/process-model-publish/some-process-group:some-process-model/*", "create"), - ("/process-model-tests/some-process-group:some-process-model/*", "create"), + ("/process-model-tests/create/some-process-group:some-process-model/*", "create"), + ("/process-model-tests/run/some-process-group:some-process-model/*", "create"), ("/process-models/some-process-group:some-process-model/*", "create"), ("/process-models/some-process-group:some-process-model/*", "delete"), ("/process-models/some-process-group:some-process-model/*", "read"), diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_dot_notation.py b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_dot_notation.py index 0a3962ef..f58228b7 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_dot_notation.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_dot_notation.py @@ -52,5 +52,4 @@ class TestDotNotation(BaseTest): processor.do_engine_steps(save=True) actual_data = processor.get_data() - del actual_data["validate_only"] assert actual_data == expected diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_instance_processor.py b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_instance_processor.py index 7ec15a29..5eb8fd8c 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_instance_processor.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_instance_processor.py @@ -6,6 +6,7 @@ from flask.app import Flask from flask.testing import FlaskClient from SpiffWorkflow.task import Task as SpiffTask # type: ignore from SpiffWorkflow.util.task import TaskState # type: ignore +from spiffworkflow_backend.exceptions.error import TaskMismatchError from spiffworkflow_backend.exceptions.error import UserDoesNotHaveAccessToTaskError from spiffworkflow_backend.models.bpmn_process import BpmnProcessModel from spiffworkflow_backend.models.db import db @@ -551,7 +552,6 @@ class TestProcessInstanceProcessor(BaseTest): } data_set_5 = {**data_set_4, **{"a": 1, "we_move_on": True}} data_set_6 = {**data_set_5, **{"set_top_level_process_script_after_gate": 1}} - data_set_7 = {**data_set_6, **{"validate_only": False, "set_top_level_process_script_after_gate": 1}} expected_task_data = { "top_level_script": {"data": data_set_1, "bpmn_process_identifier": "top_level_process"}, "top_level_manual_task_one": {"data": data_set_1, "bpmn_process_identifier": "top_level_process"}, @@ -694,7 +694,7 @@ class TestProcessInstanceProcessor(BaseTest): .count() ) assert task_models_that_are_predicted_count == 4 - assert processor_final.get_data() == data_set_7 + assert processor_final.get_data() == data_set_6 def test_does_not_recreate_human_tasks_on_multiple_saves( self, @@ -971,3 +971,25 @@ class TestProcessInstanceProcessor(BaseTest): self.round_last_state_change(bpmn_process_dict_initial) assert bpmn_process_dict_after == bpmn_process_dict_initial + + def test_returns_error_if_spiff_task_and_human_task_are_different( + self, + app: Flask, + client: FlaskClient, + with_db_and_bpmn_file_cleanup: None, + ) -> None: + process_model = load_test_spec( + process_model_id="group/call_activity_with_manual_task", + process_model_source_directory="call_activity_with_manual_task", + ) + process_instance = self.create_process_instance_from_process_model(process_model=process_model) + processor = ProcessInstanceProcessor(process_instance) + processor.do_engine_steps(save=True) + + process_instance = ProcessInstanceModel.query.filter_by(id=process_instance.id).first() + processor = ProcessInstanceProcessor(process_instance) + human_task_one = process_instance.active_human_tasks[0] + non_manual_spiff_task = processor.bpmn_process_instance.get_tasks(manual=False)[0] + assert human_task_one.task_guid != str(non_manual_spiff_task.id) + with pytest.raises(TaskMismatchError): + processor.complete_task(non_manual_spiff_task, human_task_one, user=process_instance.process_initiator) diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_model_test_generator_service.py b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_model_test_generator_service.py new file mode 100644 index 00000000..c21c5a68 --- /dev/null +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_model_test_generator_service.py @@ -0,0 +1,74 @@ +import os + +from flask import json +from flask.app import Flask +from spiffworkflow_backend.services.file_system_service import FileSystemService +from spiffworkflow_backend.services.process_model_test_generator_service import ProcessModelTestGeneratorService +from spiffworkflow_backend.services.process_model_test_runner_service import ProcessModelTestRunner + +from tests.spiffworkflow_backend.helpers.base_test import BaseTest +from tests.spiffworkflow_backend.helpers.test_data import load_test_spec + + +class TestProcessModelTestGeneratorService(BaseTest): + def test_can_generate_bpmn_unit_test_from_process_instance_json_and_run_it( + self, app: Flask, with_db_and_bpmn_file_cleanup: None + ) -> None: + process_instance_json_file = self.get_test_file("process_model_test_importer.json") + with open(process_instance_json_file) as f: + process_instance_json = f.read() + process_instance_dict = json.loads(process_instance_json) + test_case_identifier = "our_test_case" + + bpmn_unit_test_specification = ProcessModelTestGeneratorService.generate_test_from_process_instance_dict( + process_instance_dict, test_case_identifier=test_case_identifier + ) + expected_specification = { + test_case_identifier: { + "tasks": { + "Process_sub_level:sub_manual_task": {"data": [{}]}, + "call_activity_sub_process:sub_level_sub_process_user_task": {"data": [{"firstName": "Chuck"}]}, + "Process_top_level:top_service_task": { + "data": [ + { + "backend_status_response": { + "body": '{"ok": true}', + "mimetype": "application/json", + "http_status": 200, + "operator_identifier": "http/GetRequestV2", + } + } + ] + }, + }, + "expected_output_json": { + "firstName": "Chuck", + "data_objects": {"top_level_data_object": "a"}, + "backend_status_response": { + "body": '{"ok": true}', + "mimetype": "application/json", + "http_status": 200, + "operator_identifier": "http/GetRequestV2", + }, + }, + } + } + assert expected_specification == bpmn_unit_test_specification + + process_model = load_test_spec( + process_model_id="test_group/with-service-task-call-activity-sub-process", + process_model_source_directory="with-service-task-call-activity-sub-process", + ) + process_model_path = os.path.abspath(os.path.join(FileSystemService.root_path(), process_model.id_for_file_path())) + with open(os.path.join(process_model_path, "test_main.json"), "w") as f: + f.write(json.dumps(bpmn_unit_test_specification, indent=2)) + process_model_test_runner = ProcessModelTestRunner( + process_model_directory_path=process_model_path, + process_model_directory_for_test_discovery=process_model_path, + test_case_file="test_main.json", + test_case_identifier=test_case_identifier, + ) + process_model_test_runner.run() + + results = process_model_test_runner.test_case_results[0] + assert results.passed is True diff --git a/spiffworkflow-frontend/package-lock.json b/spiffworkflow-frontend/package-lock.json index fcf05931..5c376493 100644 --- a/spiffworkflow-frontend/package-lock.json +++ b/spiffworkflow-frontend/package-lock.json @@ -5535,9 +5535,9 @@ "dev": true }, "node_modules/@tanstack/query-core": { - "version": "5.28.6", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.28.6.tgz", - "integrity": "sha512-hnhotV+DnQtvtR3jPvbQMPNMW4KEK0J4k7c609zJ8muiNknm+yoDyMHmxTWM5ZnlZpsz0zOxYFr+mzRJNHWJsA==", + "version": "5.28.8", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.28.8.tgz", + "integrity": "sha512-cx64XHeB0kvKxFt22ibvegPeOxnaWVFUbAuhXoIrb7+XePEexHWoB9Kq5n9qroNPkRwQZwgFAP9HNbQz5ohoIg==", "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" @@ -5553,11 +5553,11 @@ } }, "node_modules/@tanstack/react-query": { - "version": "5.28.6", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.28.6.tgz", - "integrity": "sha512-/DdYuDBSsA21Qbcder1R8Cr/3Nx0ZnA2lgtqKsLMvov8wL4+g0HBz/gWYZPlIsof7iyfQafyhg4wUVUsS3vWZw==", + "version": "5.28.8", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.28.8.tgz", + "integrity": "sha512-4XYhoRmcThqziB32HsyiBLNXJcukaeGfYwAQ+fZqUUE3ZP4oB/Zy41UJdql+TUg98+vsezfbixxAwAbGHfc5Hg==", "dependencies": { - "@tanstack/query-core": "5.28.6" + "@tanstack/query-core": "5.28.8" }, "funding": { "type": "github", @@ -5568,9 +5568,9 @@ } }, "node_modules/@tanstack/react-query-devtools": { - "version": "5.28.6", - "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.28.6.tgz", - "integrity": "sha512-xSfskHlM2JkP7WpN89UqhJV2RbFxg8YnOMzQz+EEzWSsgxMI5Crce8HO9pcUAcJce8gSmw93RQwuKNdG3FbT6w==", + "version": "5.28.8", + "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.28.8.tgz", + "integrity": "sha512-NorR2ueGlGdB5PTvt1WynzjfNI/OJwiisB1r0UAwgi0Em2UalZpMltwvoIrGhJ0T2V+8b0MV5wD+cmf0PPdHGA==", "dependencies": { "@tanstack/query-devtools": "5.28.6" }, @@ -5579,7 +5579,7 @@ "url": "https://github.com/sponsors/tannerlinsley" }, "peerDependencies": { - "@tanstack/react-query": "^5.28.6", + "@tanstack/react-query": "^5.28.8", "react": "^18.0.0" } }, @@ -38168,9 +38168,9 @@ } }, "@tanstack/query-core": { - "version": "5.28.6", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.28.6.tgz", - "integrity": "sha512-hnhotV+DnQtvtR3jPvbQMPNMW4KEK0J4k7c609zJ8muiNknm+yoDyMHmxTWM5ZnlZpsz0zOxYFr+mzRJNHWJsA==" + "version": "5.28.8", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.28.8.tgz", + "integrity": "sha512-cx64XHeB0kvKxFt22ibvegPeOxnaWVFUbAuhXoIrb7+XePEexHWoB9Kq5n9qroNPkRwQZwgFAP9HNbQz5ohoIg==" }, "@tanstack/query-devtools": { "version": "5.28.6", @@ -38178,17 +38178,17 @@ "integrity": "sha512-DXJGqbrsteWU9XehDf6s3k3QxwQqGUlNXpitsF1xbwkYBcDaAakiC6hjJSMfPBHOrbZCnWfAGCVf4vh2D75/xw==" }, "@tanstack/react-query": { - "version": "5.28.6", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.28.6.tgz", - "integrity": "sha512-/DdYuDBSsA21Qbcder1R8Cr/3Nx0ZnA2lgtqKsLMvov8wL4+g0HBz/gWYZPlIsof7iyfQafyhg4wUVUsS3vWZw==", + "version": "5.28.8", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.28.8.tgz", + "integrity": "sha512-4XYhoRmcThqziB32HsyiBLNXJcukaeGfYwAQ+fZqUUE3ZP4oB/Zy41UJdql+TUg98+vsezfbixxAwAbGHfc5Hg==", "requires": { - "@tanstack/query-core": "5.28.6" + "@tanstack/query-core": "5.28.8" } }, "@tanstack/react-query-devtools": { - "version": "5.28.6", - "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.28.6.tgz", - "integrity": "sha512-xSfskHlM2JkP7WpN89UqhJV2RbFxg8YnOMzQz+EEzWSsgxMI5Crce8HO9pcUAcJce8gSmw93RQwuKNdG3FbT6w==", + "version": "5.28.8", + "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.28.8.tgz", + "integrity": "sha512-NorR2ueGlGdB5PTvt1WynzjfNI/OJwiisB1r0UAwgi0Em2UalZpMltwvoIrGhJ0T2V+8b0MV5wD+cmf0PPdHGA==", "requires": { "@tanstack/query-devtools": "5.28.6" } diff --git a/spiffworkflow-frontend/src/components/ErrorDisplay.tsx b/spiffworkflow-frontend/src/components/ErrorDisplay.tsx index 05fbdb48..441aa377 100644 --- a/spiffworkflow-frontend/src/components/ErrorDisplay.tsx +++ b/spiffworkflow-frontend/src/components/ErrorDisplay.tsx @@ -15,11 +15,15 @@ function errorDetailDisplay( title: string ) { // Creates a bit of html for displaying a single error property if it exists. - if (propertyName in errorObject && errorObject[propertyName]) { + let value = errorObject[propertyName]; + if (propertyName in errorObject && value) { + if (typeof value === 'object') { + value = JSON.stringify(value); + } return (
{title}: - {errorObject[propertyName]} + {value}
); } @@ -63,6 +67,10 @@ export const errorForDisplayFromTestCaseErrorDetails = ( error_line: testCaseErrorDetails.task_line_contents, task_trace: testCaseErrorDetails.task_trace, stacktrace: testCaseErrorDetails.stacktrace, + + task_type: testCaseErrorDetails.task_bpmn_type, + output_data: testCaseErrorDetails.output_data, + expected_data: testCaseErrorDetails.expected_data, }; return errorForDisplay; }; @@ -94,6 +102,17 @@ export const childrenForErrorObject = (errorObject: ErrorForDisplay) => { 'Line Number' ); const errorLine = errorDetailDisplay(errorObject, 'error_line', 'Context'); + const taskType = errorDetailDisplay(errorObject, 'task_type', 'Task Type'); + const outputData = errorDetailDisplay( + errorObject, + 'output_data', + 'Output Data' + ); + const expectedData = errorDetailDisplay( + errorObject, + 'expected_data', + 'Expected Data' + ); let codeTrace = null; if (errorObject.task_trace && errorObject.task_trace.length > 0) { codeTrace = ( @@ -126,6 +145,9 @@ export const childrenForErrorObject = (errorObject: ErrorForDisplay) => { lineNumber, errorLine, codeTrace, + taskType, + outputData, + expectedData, ]; }; diff --git a/spiffworkflow-frontend/src/components/NavigationBar.tsx b/spiffworkflow-frontend/src/components/NavigationBar.tsx index 52e238b6..14bbd459 100644 --- a/spiffworkflow-frontend/src/components/NavigationBar.tsx +++ b/spiffworkflow-frontend/src/components/NavigationBar.tsx @@ -226,9 +226,11 @@ export default function NavigationBar({ extensionUxElements }: OwnProps) { element={Link} to={navItemPage} isCurrentPage={isActivePage(navItemPage)} - data-qa={`extension-${slugifyString(uxElement.label)}`} + data-qa={`extension-${slugifyString( + uxElement.label || uxElement.page + )}`} > - {uxElement.label} + {uxElement.label || uxElement.page} ); diff --git a/spiffworkflow-frontend/src/extension_ui_schema_interfaces.ts b/spiffworkflow-frontend/src/extension_ui_schema_interfaces.ts index bbb4e166..628f83bb 100644 --- a/spiffworkflow-frontend/src/extension_ui_schema_interfaces.ts +++ b/spiffworkflow-frontend/src/extension_ui_schema_interfaces.ts @@ -81,6 +81,7 @@ export interface UiSchemaAction { * The api_path to call. * This will normally just be the colon delimited process model identifier for the extension minus the extension process group. * For example: extensions/path/to/extension -> path:to:extension + * This will interpolate patterns like "{task_data_var}" if found in the task data. */ api_path: string; @@ -105,6 +106,13 @@ export interface UiSchemaAction { */ results_markdown_filename?: string; + /** + * By default the extension data comes a key called "task_data" in the api result. + * This will instead allow the extension data to be set by the full result. + * this is useful when making api calls to non-extension apis. + */ + set_extension_data_from_full_api_result?: string; + /** * Parameters to grab from the search params of the url. * This is useful when linking from one extension to another so params can be grabbed and given to the process model when running. diff --git a/spiffworkflow-frontend/src/hooks/UriListForPermissions.tsx b/spiffworkflow-frontend/src/hooks/UriListForPermissions.tsx index 881a0b66..45780f91 100644 --- a/spiffworkflow-frontend/src/hooks/UriListForPermissions.tsx +++ b/spiffworkflow-frontend/src/hooks/UriListForPermissions.tsx @@ -35,7 +35,7 @@ export const useUriListForPermissions = () => { processModelFileShowPath: `/v1.0/process-models/${params.process_model_id}/files/${params.file_name}`, processModelPublishPath: `/v1.0/process-model-publish/${params.process_model_id}`, processModelShowPath: `/v1.0/process-models/${params.process_model_id}`, - processModelTestsPath: `/v1.0/process-model-tests/${params.process_model_id}`, + processModelTestsPath: `/v1.0/process-model-tests/run/${params.process_model_id}`, secretListPath: `/v1.0/secrets`, userSearch: `/v1.0/users/search`, userExists: `/v1.0/users/exists/by-username`, diff --git a/spiffworkflow-frontend/src/interfaces.ts b/spiffworkflow-frontend/src/interfaces.ts index 3bd181b8..289b9b97 100644 --- a/spiffworkflow-frontend/src/interfaces.ts +++ b/spiffworkflow-frontend/src/interfaces.ts @@ -326,6 +326,10 @@ export interface ErrorForDisplay { task_id?: string; task_name?: string; task_trace?: string[]; + + task_type?: string; + output_data?: any; + expected_data?: any; } export interface AuthenticationParam { @@ -420,6 +424,10 @@ export interface TestCaseErrorDetails { task_line_contents?: string; task_line_number?: number; task_trace?: string[]; + + task_bpmn_type?: string; + output_data?: any; + expected_data?: any; } export interface TestCaseResult { diff --git a/spiffworkflow-frontend/src/routes/Extension.tsx b/spiffworkflow-frontend/src/routes/Extension.tsx index 8f40fb21..75e8fe3a 100644 --- a/spiffworkflow-frontend/src/routes/Extension.tsx +++ b/spiffworkflow-frontend/src/routes/Extension.tsx @@ -73,6 +73,7 @@ export default function Extension({ const interpolateNavigationString = useCallback( (navigationString: string, baseData: any) => { + // This will interpolate patterns like "{task_data_var}" if found in the task data. let isValid = true; const data = { backend_base_url: BACKEND_BASE_URL, ...baseData }; const optionString = navigationString.replace(/{(\w+)}/g, (_, k) => { @@ -224,16 +225,20 @@ export default function Extension({ pageComponent: UiSchemaPageComponent, result: ExtensionApiResponse ) => { + let taskData = result.task_data; + if (pageComponent.on_form_submit?.set_extension_data_from_full_api_result) { + taskData = result; + } if (pageComponent && pageComponent.navigate_to_on_form_submit) { const optionString = interpolateNavigationString( pageComponent.navigate_to_on_form_submit, - result.task_data + taskData ); if (optionString !== null) { window.location.href = optionString; } } else { - setProcessedTaskData(result.task_data); + setProcessedTaskData(taskData); if (result.rendered_results_markdown) { const newMarkdown = FormattingService.checkForSpiffFormats( result.rendered_results_markdown @@ -277,16 +282,18 @@ export default function Extension({ } } else { let postBody: ExtensionPostBody = { extension_input: dataToSubmit }; - let apiPath = targetUris.extensionPath; + let apiPathRaw = targetUris.extensionPath; if (pageComponent && pageComponent.on_form_submit) { - if (pageComponent.on_form_submit.is_full_api_path) { - apiPath = `/${pageComponent.on_form_submit.api_path}`; - postBody = dataToSubmit; + apiPathRaw = pageComponent.on_form_submit.api_path.replace(/^\/?/, '/'); + if (!pageComponent.on_form_submit.is_full_api_path) { + apiPathRaw = `${targetUris.extensionListPath}/${apiPathRaw}`; } else { - apiPath = `${targetUris.extensionListPath}/${pageComponent.on_form_submit.api_path}`; + postBody = dataToSubmit; } postBody.ui_schema_action = pageComponent.on_form_submit; } + const apiPath = + interpolateNavigationString(apiPathRaw, dataToSubmit) || apiPathRaw; // NOTE: rjsf sets blanks values to undefined and JSON.stringify removes keys with undefined values // so we convert undefined values to null recursively so that we can unset values in form fields