Bpmn unit test from pi (#1262)

* added most of the code needed to generate a bpmn unit test from a process instance w/ burnettk

* pyl and tests are passing

* renamed some files and some clean up

* added api method to generate test case w/ burnettk

* debugging the generate test api w/ burnettk

* test case generator test case is now passing w/ burnettk

* added test for TaskMismatchError w/ burnettk

* added support for extension to create bpmn unit tests with the api w/ burnettk

* coderabbit and typeguard fixes w/ burnettk

---------

Co-authored-by: jasquat <jasquat@users.noreply.github.com>
This commit is contained in:
jasquat 2024-03-26 14:14:57 +00:00 committed by GitHub
parent 62ba2ed282
commit a12d2883c1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
31 changed files with 1996 additions and 329 deletions

View File

@ -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"},

View File

@ -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

View File

@ -55,3 +55,7 @@ class InvalidPermissionError(Exception):
class InvalidRedirectUrlError(Exception):
pass
class TaskMismatchError(Exception):
pass

View File

@ -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"

View File

@ -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"]},
]

View File

@ -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(

View File

@ -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()))

View File

@ -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]

View File

@ -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(

View File

@ -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)

View File

@ -0,0 +1,105 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:spiffworkflow="http://spiffworkflow.org/bpmn/schema/1.0/core" id="Definitions_96f6665" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.0.0-dev">
<bpmn:process id="Process_top_level" name="Top Level Process" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>Flow_0j3unyw</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:sequenceFlow id="Flow_0j3unyw" sourceRef="StartEvent_1" targetRef="top_script_task" />
<bpmn:endEvent id="Event_1swh8gs">
<bpmn:incoming>Flow_1dbi4yf</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_1dbi4yf" sourceRef="top_call_activity" targetRef="Event_1swh8gs" />
<bpmn:callActivity id="top_call_activity" name="Top Call Activity" calledElement="Process_sub_level">
<bpmn:incoming>Flow_0z2u0gc</bpmn:incoming>
<bpmn:outgoing>Flow_1dbi4yf</bpmn:outgoing>
</bpmn:callActivity>
<bpmn:sequenceFlow id="Flow_0uoa0st" sourceRef="top_script_task" targetRef="top_service_task" />
<bpmn:dataObjectReference id="DataObjectReference_0qfbjfj" name="top_level_data_object" dataObjectRef="top_level_data_object" />
<bpmn:dataObject id="top_level_data_object" name="top_level_data_object" />
<bpmn:scriptTask id="top_script_task" name="Top Script Task">
<bpmn:extensionElements>
<spiffworkflow:serviceTaskOperator id="http/GetRequestV2" resultVariable="the_response">
<spiffworkflow:parameters>
<spiffworkflow:parameter id="url" type="str" value="&#34;http://localhost:7000/v1.0/status&#34;" />
<spiffworkflow:parameter id="headers" type="any" />
<spiffworkflow:parameter id="params" type="any" />
<spiffworkflow:parameter id="basic_auth_username" type="str" />
<spiffworkflow:parameter id="basic_auth_password" type="str" />
<spiffworkflow:parameter id="attempts" type="int" />
</spiffworkflow:parameters>
</spiffworkflow:serviceTaskOperator>
</bpmn:extensionElements>
<bpmn:incoming>Flow_0j3unyw</bpmn:incoming>
<bpmn:outgoing>Flow_0uoa0st</bpmn:outgoing>
<bpmn:dataOutputAssociation id="DataOutputAssociation_1fxfphs">
<bpmn:targetRef>DataObjectReference_0qfbjfj</bpmn:targetRef>
</bpmn:dataOutputAssociation>
<bpmn:script>top_level_data_object = "a"</bpmn:script>
</bpmn:scriptTask>
<bpmn:sequenceFlow id="Flow_0z2u0gc" sourceRef="top_service_task" targetRef="top_call_activity" />
<bpmn:serviceTask id="top_service_task" name="Top Service Task">
<bpmn:extensionElements>
<spiffworkflow:serviceTaskOperator id="http/GetRequestV2" resultVariable="backend_status_response">
<spiffworkflow:parameters>
<spiffworkflow:parameter id="url" type="str" value="&#34;http://localhost:7000/v1.0/status&#34;" />
<spiffworkflow:parameter id="headers" type="any" />
<spiffworkflow:parameter id="params" type="any" />
<spiffworkflow:parameter id="basic_auth_username" type="str" />
<spiffworkflow:parameter id="basic_auth_password" type="str" />
<spiffworkflow:parameter id="attempts" type="int" />
</spiffworkflow:parameters>
</spiffworkflow:serviceTaskOperator>
</bpmn:extensionElements>
<bpmn:incoming>Flow_0uoa0st</bpmn:incoming>
<bpmn:outgoing>Flow_0z2u0gc</bpmn:outgoing>
</bpmn:serviceTask>
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_top_level">
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="32" y="159" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_1swh8gs_di" bpmnElement="Event_1swh8gs">
<dc:Bounds x="502" y="159" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_18m3q89_di" bpmnElement="top_call_activity">
<dc:Bounds x="370" y="137" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="DataObjectReference_0qfbjfj_di" bpmnElement="DataObjectReference_0qfbjfj">
<dc:Bounds x="132" y="55" width="36" height="50" />
<bpmndi:BPMNLabel>
<dc:Bounds x="105" y="31" width="90" height="27" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0dh9loj_di" bpmnElement="top_script_task">
<dc:Bounds x="100" y="137" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1ngnsv7_di" bpmnElement="top_service_task">
<dc:Bounds x="230" y="137" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_0j3unyw_di" bpmnElement="Flow_0j3unyw">
<di:waypoint x="68" y="177" />
<di:waypoint x="100" y="177" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1dbi4yf_di" bpmnElement="Flow_1dbi4yf">
<di:waypoint x="470" y="177" />
<di:waypoint x="502" y="177" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0uoa0st_di" bpmnElement="Flow_0uoa0st">
<di:waypoint x="200" y="177" />
<di:waypoint x="230" y="177" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="DataOutputAssociation_1fxfphs_di" bpmnElement="DataOutputAssociation_1fxfphs">
<di:waypoint x="152" y="137" />
<di:waypoint x="153" y="105" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0z2u0gc_di" bpmnElement="Flow_0z2u0gc">
<di:waypoint x="330" y="177" />
<di:waypoint x="370" y="177" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

View File

@ -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"
}
}
}

View File

@ -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/)."
}
}

View File

@ -0,0 +1,182 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:spiffworkflow="http://spiffworkflow.org/bpmn/schema/1.0/core" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_96f6665" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.0.0-dev">
<bpmn:process id="Process_sub_level" name="Process Sub Level" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>Flow_18zaszw</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:endEvent id="EndEvent_1">
<bpmn:extensionElements>
<spiffworkflow:instructionsForEndUser>The process instance completed successfully.</spiffworkflow:instructionsForEndUser>
</bpmn:extensionElements>
<bpmn:incoming>Flow_02mnp5n</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_1dbpkjb" sourceRef="sub_script_task" targetRef="sub_manual_task" />
<bpmn:scriptTask id="sub_script_task" name="Sub Script Task">
<bpmn:incoming>Flow_18zaszw</bpmn:incoming>
<bpmn:outgoing>Flow_1dbpkjb</bpmn:outgoing>
<bpmn:dataOutputAssociation id="DataOutputAssociation_0nq3jkj">
<bpmn:targetRef>DataObjectReference_11gskr0</bpmn:targetRef>
</bpmn:dataOutputAssociation>
<bpmn:script>sub_level_data_object = "b"</bpmn:script>
</bpmn:scriptTask>
<bpmn:dataObject id="sub_level_data_object" name="sub_level_data_object" />
<bpmn:dataObjectReference id="DataObjectReference_11gskr0" name="sub_level_data_object" dataObjectRef="sub_level_data_object" />
<bpmn:sequenceFlow id="Flow_18zaszw" sourceRef="StartEvent_1" targetRef="sub_script_task" />
<bpmn:sequenceFlow id="Flow_15y3o98" sourceRef="sub_manual_task" targetRef="call_activity_sub_process" />
<bpmn:manualTask id="sub_manual_task" name="Sub Manual Task">
<bpmn:incoming>Flow_1dbpkjb</bpmn:incoming>
<bpmn:outgoing>Flow_15y3o98</bpmn:outgoing>
</bpmn:manualTask>
<bpmn:sequenceFlow id="Flow_02mnp5n" sourceRef="sub_script_task_two" targetRef="EndEvent_1" />
<bpmn:scriptTask id="sub_script_task_two" name="Sub Script Task Two">
<bpmn:incoming>Flow_0zr28rt</bpmn:incoming>
<bpmn:outgoing>Flow_02mnp5n</bpmn:outgoing>
<bpmn:dataOutputAssociation id="DataOutputAssociation_0quuwm4">
<bpmn:targetRef>DataObjectReference_06uyy2z</bpmn:targetRef>
</bpmn:dataOutputAssociation>
<bpmn:script>sub_level_data_object_two = 'c'</bpmn:script>
</bpmn:scriptTask>
<bpmn:dataObject id="sub_level_data_object_two" name="sub_level_data_object_two" />
<bpmn:dataObjectReference id="DataObjectReference_06uyy2z" name="sub_level_data_object_two" dataObjectRef="sub_level_data_object_two" />
<bpmn:sequenceFlow id="Flow_0zr28rt" sourceRef="call_activity_sub_process" targetRef="sub_script_task_two" />
<bpmn:subProcess id="call_activity_sub_process" name="Call Activity Sub Process">
<bpmn:incoming>Flow_15y3o98</bpmn:incoming>
<bpmn:outgoing>Flow_0zr28rt</bpmn:outgoing>
<bpmn:startEvent id="Event_0yfq3gm">
<bpmn:outgoing>Flow_1ekcyuv</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:sequenceFlow id="Flow_1ekcyuv" sourceRef="Event_0yfq3gm" targetRef="sub_level_sub_process_user_task" />
<bpmn:endEvent id="Event_00vjfmy">
<bpmn:incoming>Flow_1p3g9vw</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_1p3g9vw" sourceRef="sub_level_sub_process_script_task" targetRef="Event_00vjfmy" />
<bpmn:dataObjectReference id="DataObjectReference_1v83y6h" name="sub_level_data_object_three" dataObjectRef="sub_level_data_object_three" />
<bpmn:dataObject id="sub_level_data_object_three" name="sub_level_data_object_three" />
<bpmn:scriptTask id="sub_level_sub_process_script_task" name="Sub Level Sub Process Script Task">
<bpmn:incoming>Flow_157qtq3</bpmn:incoming>
<bpmn:outgoing>Flow_1p3g9vw</bpmn:outgoing>
<bpmn:dataOutputAssociation id="DataOutputAssociation_1xpueyl">
<bpmn:targetRef>DataObjectReference_1v83y6h</bpmn:targetRef>
</bpmn:dataOutputAssociation>
<bpmn:script>sub_level_data_object_three = 'd'</bpmn:script>
</bpmn:scriptTask>
<bpmn:sequenceFlow id="Flow_157qtq3" sourceRef="sub_level_sub_process_user_task" targetRef="sub_level_sub_process_script_task" />
<bpmn:userTask id="sub_level_sub_process_user_task" name="Sub Level Sub Process User Task">
<bpmn:extensionElements>
<spiffworkflow:properties>
<spiffworkflow:property name="formJsonSchemaFilename" value="sub-level-sub-process-user-form-schema.json" />
<spiffworkflow:property name="formUiSchemaFilename" value="sub-level-sub-process-user-form-uischema.json" />
</spiffworkflow:properties>
</bpmn:extensionElements>
<bpmn:incoming>Flow_1ekcyuv</bpmn:incoming>
<bpmn:outgoing>Flow_157qtq3</bpmn:outgoing>
</bpmn:userTask>
</bpmn:subProcess>
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_sub_level">
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="179" y="159" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_14za570_di" bpmnElement="EndEvent_1">
<dc:Bounds x="762" y="159" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0stvzig_di" bpmnElement="sub_script_task">
<dc:Bounds x="260" y="137" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="DataObjectReference_11gskr0_di" bpmnElement="DataObjectReference_11gskr0">
<dc:Bounds x="292" y="45" width="36" height="50" />
<bpmndi:BPMNLabel>
<dc:Bounds x="266" y="21" width="87" height="27" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1roka0p_di" bpmnElement="sub_manual_task">
<dc:Bounds x="380" y="137" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0ao5f38_di" bpmnElement="sub_script_task_two">
<dc:Bounds x="630" y="137" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="DataObjectReference_06uyy2z_di" bpmnElement="DataObjectReference_06uyy2z">
<dc:Bounds x="662" y="25" width="36" height="50" />
<bpmndi:BPMNLabel>
<dc:Bounds x="636" y="-12.5" width="87" height="27" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0bc52l5_di" bpmnElement="call_activity_sub_process">
<dc:Bounds x="510" y="137" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_1dbpkjb_di" bpmnElement="Flow_1dbpkjb">
<di:waypoint x="360" y="177" />
<di:waypoint x="380" y="177" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="DataOutputAssociation_0nq3jkj_di" bpmnElement="DataOutputAssociation_0nq3jkj">
<di:waypoint x="311" y="137" />
<di:waypoint x="314" y="95" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_18zaszw_di" bpmnElement="Flow_18zaszw">
<di:waypoint x="215" y="177" />
<di:waypoint x="260" y="177" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_15y3o98_di" bpmnElement="Flow_15y3o98">
<di:waypoint x="480" y="177" />
<di:waypoint x="510" y="177" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_02mnp5n_di" bpmnElement="Flow_02mnp5n">
<di:waypoint x="730" y="177" />
<di:waypoint x="762" y="177" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="DataOutputAssociation_0quuwm4_di" bpmnElement="DataOutputAssociation_0quuwm4">
<di:waypoint x="680" y="137" />
<di:waypoint x="679" y="75" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0zr28rt_di" bpmnElement="Flow_0zr28rt">
<di:waypoint x="610" y="177" />
<di:waypoint x="630" y="177" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
<bpmndi:BPMNDiagram id="BPMNDiagram_0714uj7">
<bpmndi:BPMNPlane id="BPMNPlane_1xy36cs" bpmnElement="call_activity_sub_process">
<bpmndi:BPMNShape id="Event_00vjfmy_di" bpmnElement="Event_00vjfmy">
<dc:Bounds x="812" y="292" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="DataObjectReference_1v83y6h_di" bpmnElement="DataObjectReference_1v83y6h">
<dc:Bounds x="692" y="135" width="36" height="50" />
<bpmndi:BPMNLabel>
<dc:Bounds x="666" y="98" width="87" height="27" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_165y1p1_di" bpmnElement="sub_level_sub_process_script_task">
<dc:Bounds x="660" y="270" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_0yfq3gm_di" bpmnElement="Event_0yfq3gm">
<dc:Bounds x="402" y="292" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1xq5smk_di" bpmnElement="sub_level_sub_process_user_task">
<dc:Bounds x="490" y="270" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_1ekcyuv_di" bpmnElement="Flow_1ekcyuv">
<di:waypoint x="438" y="310" />
<di:waypoint x="490" y="310" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1p3g9vw_di" bpmnElement="Flow_1p3g9vw">
<di:waypoint x="760" y="310" />
<di:waypoint x="812" y="310" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="DataOutputAssociation_1xpueyl_di" bpmnElement="DataOutputAssociation_1xpueyl">
<di:waypoint x="709" y="270" />
<di:waypoint x="708" y="185" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_157qtq3_di" bpmnElement="Flow_157qtq3">
<di:waypoint x="590" y="310" />
<di:waypoint x="660" y="310" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

View File

@ -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",

File diff suppressed because it is too large Load Diff

View File

@ -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)

View File

@ -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",
}

View File

@ -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:

View File

@ -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"),

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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"
}

View File

@ -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 (
<div className="error_info">
<span className="error_title">{title}:</span>
{errorObject[propertyName]}
{value}
</div>
);
}
@ -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,
];
};

View File

@ -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}
</HeaderMenuItem>
</SpiffTooltip>
);

View File

@ -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.

View File

@ -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`,

View File

@ -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 {

View File

@ -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