Merge branch 'main' into feature/waku-fault-message
This commit is contained in:
commit
223793e79d
|
@ -11,8 +11,7 @@ set -o errtrace -o errexit -o nounset -o pipefail
|
||||||
# so we can see what resources that user has access to
|
# so we can see what resources that user has access to
|
||||||
|
|
||||||
# originally from https://medium.com/keycloak/keycloak-jwt-token-using-curl-post-72c9e791ba8c
|
# originally from https://medium.com/keycloak/keycloak-jwt-token-using-curl-post-72c9e791ba8c
|
||||||
# btw, meta config endpoint: http://localhost:7002/realms/spiffworkflow/.well-known/openid-configuration
|
# btw, meta config endpoint: http://localhost:7002/realms/spiffworkflow/.well-known/openid-configuration token exchange described at https://github.com/keycloak/keycloak-documentation/blob/main/securing_apps/topics/token-exchange/token-exchange.adoc
|
||||||
# token exchange described at https://github.com/keycloak/keycloak-documentation/blob/main/securing_apps/topics/token-exchange/token-exchange.adoc
|
|
||||||
# some UMA stuff at https://github.com/keycloak/keycloak-documentation/blob/main/authorization_services/topics/service-authorization-obtaining-permission.adoc,
|
# some UMA stuff at https://github.com/keycloak/keycloak-documentation/blob/main/authorization_services/topics/service-authorization-obtaining-permission.adoc,
|
||||||
# though resource_set docs are elsewhere.
|
# though resource_set docs are elsewhere.
|
||||||
|
|
||||||
|
@ -21,11 +20,13 @@ set -o errtrace -o errexit -o nounset -o pipefail
|
||||||
# ./bin/get_token repeat_form_user_1 repeat_form_user_1 # actually has permissions to the resource in this script
|
# ./bin/get_token repeat_form_user_1 repeat_form_user_1 # actually has permissions to the resource in this script
|
||||||
# ./bin/get_token ciadmin1 ciadmin1 '%2Fprocess-models'
|
# ./bin/get_token ciadmin1 ciadmin1 '%2Fprocess-models'
|
||||||
|
|
||||||
HOSTNAME=localhost:7002
|
# KEYCLOAK_BASE_URL=http://localhost:7002
|
||||||
|
KEYCLOAK_BASE_URL=https://keycloak.dev.spiffworkflow.org
|
||||||
|
# BACKEND_BASE_URL=http://localhost:7000
|
||||||
|
BACKEND_BASE_URL=https://api.dev.spiffworkflow.org
|
||||||
REALM_NAME=spiffworkflow
|
REALM_NAME=spiffworkflow
|
||||||
USERNAME=${1-ciuser1}
|
USERNAME=${1-fin}
|
||||||
PASSWORD=${2-ciuser1}
|
PASSWORD=${2-fin}
|
||||||
URI_TO_TEST_AGAINST=${3-'%2Fprocess-models%2Fcategory_number_one%2Fprocess-model-with-repeating-form'}
|
|
||||||
|
|
||||||
FRONTEND_CLIENT_ID=spiffworkflow-frontend
|
FRONTEND_CLIENT_ID=spiffworkflow-frontend
|
||||||
BACKEND_CLIENT_ID=spiffworkflow-backend
|
BACKEND_CLIENT_ID=spiffworkflow-backend
|
||||||
|
@ -33,7 +34,7 @@ BACKEND_CLIENT_SECRET="JXeQExm0JhQPLumgHtIIqf52bDalHz0q" # noqa: S105
|
||||||
SECURE=false
|
SECURE=false
|
||||||
|
|
||||||
BACKEND_BASIC_AUTH=$(echo -n "${BACKEND_CLIENT_ID}:${BACKEND_CLIENT_SECRET}" | base64)
|
BACKEND_BASIC_AUTH=$(echo -n "${BACKEND_CLIENT_ID}:${BACKEND_CLIENT_SECRET}" | base64)
|
||||||
KEYCLOAK_URL=http://$HOSTNAME/realms/$REALM_NAME/protocol/openid-connect/token
|
KEYCLOAK_URL=$KEYCLOAK_BASE_URL/realms/$REALM_NAME/protocol/openid-connect/token
|
||||||
|
|
||||||
echo "Using Keycloak: $KEYCLOAK_URL"
|
echo "Using Keycloak: $KEYCLOAK_URL"
|
||||||
echo "realm: $REALM_NAME"
|
echo "realm: $REALM_NAME"
|
||||||
|
@ -49,55 +50,72 @@ else
|
||||||
INSECURE=--insecure
|
INSECURE=--insecure
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
### Basic auth test with backend
|
||||||
result=$(curl -s -X POST "$KEYCLOAK_URL" "$INSECURE" \
|
result=$(curl -s -X POST "$KEYCLOAK_URL" "$INSECURE" \
|
||||||
-H "Content-Type: application/x-www-form-urlencoded" \
|
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||||
|
-H "Authorization: Basic $BACKEND_BASIC_AUTH" \
|
||||||
-d "username=$USERNAME" \
|
-d "username=$USERNAME" \
|
||||||
-d "password=$PASSWORD" \
|
-d "password=$PASSWORD" \
|
||||||
-d 'grant_type=password' \
|
-d 'grant_type=password' \
|
||||||
-d "client_id=$FRONTEND_CLIENT_ID" \
|
|
||||||
)
|
|
||||||
frontend_token=$(jq -r '.access_token' <<< "$result")
|
|
||||||
|
|
||||||
result=$(curl -s -X POST "$KEYCLOAK_URL" "$INSECURE" \
|
|
||||||
-H "Content-Type: application/x-www-form-urlencoded" \
|
|
||||||
--data-urlencode 'grant_type=urn:ietf:params:oauth:grant-type:token-exchange' \
|
|
||||||
-d "client_id=$BACKEND_CLIENT_ID" \
|
-d "client_id=$BACKEND_CLIENT_ID" \
|
||||||
-d "subject_token=${frontend_token}" \
|
|
||||||
-H "Authorization: Basic $BACKEND_BASIC_AUTH" \
|
|
||||||
-d "audience=${BACKEND_CLIENT_ID}" \
|
|
||||||
)
|
)
|
||||||
backend_token=$(jq -r '.access_token' <<< "$result")
|
backend_token=$(jq -r '.access_token' <<< "$result")
|
||||||
|
curl --fail -v "${BACKEND_BASE_URL}/v1.0/process-groups?per_page=1" -H "Authorization: Bearer $backend_token"
|
||||||
|
|
||||||
if [[ "$backend_token" != 'null' ]]; then
|
|
||||||
echo "backend_token: $backend_token"
|
|
||||||
|
|
||||||
echo "Getting resource set"
|
### Get with frontend and exchange with backend - not configured to work in keycloak atm
|
||||||
# everything_resource_id='446bdcf4-a3bd-41c7-a0f8-67a225ba6b57'
|
# result=$(curl -s -X POST "$KEYCLOAK_URL" "$INSECURE" \
|
||||||
resource_result=$(curl -s "http://${HOSTNAME}/realms/spiffworkflow/authz/protection/resource_set?matchingUri=true&deep=true&max=-1&exactName=false&uri=${URI_TO_TEST_AGAINST}" -H "Authorization: Bearer $backend_token")
|
# -H "Content-Type: application/x-www-form-urlencoded" \
|
||||||
# resource_result=$(curl -s "http://${HOSTNAME}/realms/spiffworkflow/authz/protection/resource_set?matchingUri=false&deep=true&max=-1&exactName=false&type=admin" -H "Authorization: Bearer $backend_token")
|
# -d "username=$USERNAME" \
|
||||||
|
# -d "password=$PASSWORD" \
|
||||||
|
# -d 'grant_type=password' \
|
||||||
|
# -d "client_id=$FRONTEND_CLIENT_ID" \
|
||||||
|
# )
|
||||||
|
# frontend_token=$(jq -r '.access_token' <<< "$result")
|
||||||
|
#
|
||||||
|
# result=$(curl -s -X POST "$KEYCLOAK_URL" "$INSECURE" \
|
||||||
|
# -H "Content-Type: application/x-www-form-urlencoded" \
|
||||||
|
# --data-urlencode 'grant_type=urn:ietf:params:oauth:grant-type:token-exchange' \
|
||||||
|
# -d "client_id=$BACKEND_CLIENT_ID" \
|
||||||
|
# -d "subject_token=${frontend_token}" \
|
||||||
|
# -H "Authorization: Basic $BACKEND_BASIC_AUTH" \
|
||||||
|
# -d "audience=${BACKEND_CLIENT_ID}" \
|
||||||
|
# )
|
||||||
|
# backend_token=$(jq -r '.access_token' <<< "$result")
|
||||||
|
|
||||||
resource_id_name_pairs=$(jq -r '.[] | "\(._id):\(.name)"' <<<"$resource_result" || echo '')
|
### Check fine grain permissions - does not work currently
|
||||||
if [[ -z "$resource_id_name_pairs" || "$resource_id_name_pairs" == "null" ]]; then
|
# URI_TO_TEST_AGAINST=${3-'%2Fprocess-models%2Fcategory_number_one%2Fprocess-model-with-repeating-form'}
|
||||||
>&2 echo "ERROR: Could not find the resource id from the result: ${resource_result}"
|
# if [[ "$backend_token" != 'null' ]]; then
|
||||||
exit 1
|
# echo "backend_token: $backend_token"
|
||||||
fi
|
#
|
||||||
echo $resource_id_name_pairs
|
# echo "Getting resource set"
|
||||||
|
# # everything_resource_id='446bdcf4-a3bd-41c7-a0f8-67a225ba6b57'
|
||||||
echo "Getting permissions"
|
# resource_result=$(curl -s "${BASE_URL}/realms/spiffworkflow/authz/protection/resource_set?matchingUri=true&deep=true&max=-1&exactName=false&uri=${URI_TO_TEST_AGAINST}" -H "Authorization: Bearer $backend_token")
|
||||||
for resource_id_name_pair in $resource_id_name_pairs ; do
|
# # resource_result=$(curl -s "${BASE_URL}/realms/spiffworkflow/authz/protection/resource_set?matchingUri=false&deep=true&max=-1&exactName=false&type=admin" -H "Authorization: Bearer $backend_token")
|
||||||
resource_id=$(awk -F ':' '{print $1}' <<<"$resource_id_name_pair")
|
#
|
||||||
resource_name=$(awk -F ':' '{print $2}' <<<"$resource_id_name_pair")
|
# resource_id_name_pairs=$(jq -r '.[] | "\(._id):\(.name)"' <<<"$resource_result" || echo '')
|
||||||
|
# if [[ -z "$resource_id_name_pairs" || "$resource_id_name_pairs" == "null" ]]; then
|
||||||
echo "Checking $resource_name"
|
# >&2 echo "ERROR: Could not find the resource id from the result: ${resource_result}"
|
||||||
curl -s -X POST "$KEYCLOAK_URL" "$INSECURE" \
|
# exit 1
|
||||||
-H "Content-Type: application/x-www-form-urlencoded" \
|
# fi
|
||||||
-H "Authorization: Basic $BACKEND_BASIC_AUTH" \
|
# echo $resource_id_name_pairs
|
||||||
-d "audience=${BACKEND_CLIENT_ID}" \
|
#
|
||||||
--data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:uma-ticket" \
|
# echo "Getting permissions"
|
||||||
-d "permission=${resource_id}" \
|
# for resource_id_name_pair in $resource_id_name_pairs ; do
|
||||||
-d "subject_token=${backend_token}" \
|
# resource_id=$(awk -F ':' '{print $1}' <<<"$resource_id_name_pair")
|
||||||
| jq .
|
# resource_name=$(awk -F ':' '{print $2}' <<<"$resource_id_name_pair")
|
||||||
done
|
#
|
||||||
else
|
# echo "Checking $resource_name"
|
||||||
echo "Failed auth result: $result"
|
# curl -s -X POST "$KEYCLOAK_URL" "$INSECURE" \
|
||||||
fi
|
# -H "Content-Type: application/x-www-form-urlencoded" \
|
||||||
|
# -H "Authorization: Basic $BACKEND_BASIC_AUTH" \
|
||||||
|
# -d "audience=${BACKEND_CLIENT_ID}" \
|
||||||
|
# --data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:uma-ticket" \
|
||||||
|
# -d "permission=${resource_id}" \
|
||||||
|
# -d "subject_token=${backend_token}" \
|
||||||
|
# | jq .
|
||||||
|
# done
|
||||||
|
# else
|
||||||
|
# echo "Failed auth result: $result"
|
||||||
|
# fi
|
||||||
|
|
|
@ -397,6 +397,12 @@ paths:
|
||||||
description: the modified process model id
|
description: the modified process model id
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
|
- name: include_file_references
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
description: include all file references in the return
|
||||||
|
schema:
|
||||||
|
type: boolean
|
||||||
get:
|
get:
|
||||||
operationId: spiffworkflow_backend.routes.process_models_controller.process_model_show
|
operationId: spiffworkflow_backend.routes.process_models_controller.process_model_show
|
||||||
summary: Returns a single process model
|
summary: Returns a single process model
|
||||||
|
|
|
@ -15,6 +15,7 @@ from flask import jsonify
|
||||||
from flask import make_response
|
from flask import make_response
|
||||||
from flask.wrappers import Response
|
from flask.wrappers import Response
|
||||||
from flask_bpmn.api.api_error import ApiError
|
from flask_bpmn.api.api_error import ApiError
|
||||||
|
from werkzeug.datastructures import FileStorage
|
||||||
|
|
||||||
from spiffworkflow_backend.interfaces import IdToProcessGroupMapping
|
from spiffworkflow_backend.interfaces import IdToProcessGroupMapping
|
||||||
from spiffworkflow_backend.models.file import FileSchema
|
from spiffworkflow_backend.models.file import FileSchema
|
||||||
|
@ -38,6 +39,9 @@ from spiffworkflow_backend.services.process_instance_report_service import (
|
||||||
ProcessInstanceReportService,
|
ProcessInstanceReportService,
|
||||||
)
|
)
|
||||||
from spiffworkflow_backend.services.process_model_service import ProcessModelService
|
from spiffworkflow_backend.services.process_model_service import ProcessModelService
|
||||||
|
from spiffworkflow_backend.services.spec_file_service import (
|
||||||
|
ProcessModelFileInvalidError,
|
||||||
|
)
|
||||||
from spiffworkflow_backend.services.spec_file_service import SpecFileService
|
from spiffworkflow_backend.services.spec_file_service import SpecFileService
|
||||||
|
|
||||||
|
|
||||||
|
@ -123,7 +127,9 @@ def process_model_update(
|
||||||
return ProcessModelInfoSchema().dump(process_model)
|
return ProcessModelInfoSchema().dump(process_model)
|
||||||
|
|
||||||
|
|
||||||
def process_model_show(modified_process_model_identifier: str) -> Any:
|
def process_model_show(
|
||||||
|
modified_process_model_identifier: str, include_file_references: bool = False
|
||||||
|
) -> Any:
|
||||||
"""Process_model_show."""
|
"""Process_model_show."""
|
||||||
process_model_identifier = modified_process_model_identifier.replace(":", "/")
|
process_model_identifier = modified_process_model_identifier.replace(":", "/")
|
||||||
process_model = _get_process_model(process_model_identifier)
|
process_model = _get_process_model(process_model_identifier)
|
||||||
|
@ -132,8 +138,12 @@ def process_model_show(modified_process_model_identifier: str) -> Any:
|
||||||
key=lambda f: "" if f.name == process_model.primary_file_name else f.sort_index,
|
key=lambda f: "" if f.name == process_model.primary_file_name else f.sort_index,
|
||||||
)
|
)
|
||||||
process_model.files = files
|
process_model.files = files
|
||||||
for file in process_model.files:
|
|
||||||
file.references = SpecFileService.get_references_for_file(file, process_model)
|
if include_file_references:
|
||||||
|
for file in process_model.files:
|
||||||
|
file.references = SpecFileService.get_references_for_file(
|
||||||
|
file, process_model
|
||||||
|
)
|
||||||
|
|
||||||
process_model.parent_groups = ProcessModelService.get_parent_group_array(
|
process_model.parent_groups = ProcessModelService.get_parent_group_array(
|
||||||
process_model.id
|
process_model.id
|
||||||
|
@ -226,26 +236,11 @@ def process_model_file_update(
|
||||||
modified_process_model_identifier: str, file_name: str
|
modified_process_model_identifier: str, file_name: str
|
||||||
) -> flask.wrappers.Response:
|
) -> flask.wrappers.Response:
|
||||||
"""Process_model_file_update."""
|
"""Process_model_file_update."""
|
||||||
process_model_identifier = modified_process_model_identifier.replace(":", "/")
|
message = f"User: {g.user.username} clicked save for"
|
||||||
process_model = _get_process_model(process_model_identifier)
|
return _create_or_update_process_model_file(
|
||||||
|
modified_process_model_identifier, message, 200
|
||||||
request_file = _get_file_from_request()
|
|
||||||
request_file_contents = request_file.stream.read()
|
|
||||||
if not request_file_contents:
|
|
||||||
raise ApiError(
|
|
||||||
error_code="file_contents_empty",
|
|
||||||
message="Given request file does not have any content",
|
|
||||||
status_code=400,
|
|
||||||
)
|
|
||||||
|
|
||||||
SpecFileService.update_file(process_model, file_name, request_file_contents)
|
|
||||||
_commit_and_push_to_git(
|
|
||||||
f"User: {g.user.username} clicked save for"
|
|
||||||
f" {process_model_identifier}/{file_name}"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return Response(json.dumps({"ok": True}), status=200, mimetype="application/json")
|
|
||||||
|
|
||||||
|
|
||||||
def process_model_file_delete(
|
def process_model_file_delete(
|
||||||
modified_process_model_identifier: str, file_name: str
|
modified_process_model_identifier: str, file_name: str
|
||||||
|
@ -275,28 +270,9 @@ def process_model_file_create(
|
||||||
modified_process_model_identifier: str,
|
modified_process_model_identifier: str,
|
||||||
) -> flask.wrappers.Response:
|
) -> flask.wrappers.Response:
|
||||||
"""Process_model_file_create."""
|
"""Process_model_file_create."""
|
||||||
process_model_identifier = modified_process_model_identifier.replace(":", "/")
|
message = f"User: {g.user.username} added process model file"
|
||||||
process_model = _get_process_model(process_model_identifier)
|
return _create_or_update_process_model_file(
|
||||||
request_file = _get_file_from_request()
|
modified_process_model_identifier, message, 201
|
||||||
if not request_file.filename:
|
|
||||||
raise ApiError(
|
|
||||||
error_code="could_not_get_filename",
|
|
||||||
message="Could not get filename from request",
|
|
||||||
status_code=400,
|
|
||||||
)
|
|
||||||
|
|
||||||
file = SpecFileService.add_file(
|
|
||||||
process_model, request_file.filename, request_file.stream.read()
|
|
||||||
)
|
|
||||||
file_contents = SpecFileService.get_data(process_model, file.name)
|
|
||||||
file.file_contents = file_contents
|
|
||||||
file.process_model_id = process_model.id
|
|
||||||
_commit_and_push_to_git(
|
|
||||||
f"User: {g.user.username} added process model file"
|
|
||||||
f" {process_model_identifier}/{file.name}"
|
|
||||||
)
|
|
||||||
return Response(
|
|
||||||
json.dumps(FileSchema().dump(file)), status=201, mimetype="application/json"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -466,9 +442,9 @@ def process_model_create_with_natural_language(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _get_file_from_request() -> Any:
|
def _get_file_from_request() -> FileStorage:
|
||||||
"""Get_file_from_request."""
|
"""Get_file_from_request."""
|
||||||
request_file = connexion.request.files.get("file")
|
request_file: FileStorage = connexion.request.files.get("file")
|
||||||
if not request_file:
|
if not request_file:
|
||||||
raise ApiError(
|
raise ApiError(
|
||||||
error_code="no_file_given",
|
error_code="no_file_given",
|
||||||
|
@ -506,3 +482,58 @@ def _get_process_group_from_modified_identifier(
|
||||||
status_code=400,
|
status_code=400,
|
||||||
)
|
)
|
||||||
return process_group
|
return process_group
|
||||||
|
|
||||||
|
|
||||||
|
def _create_or_update_process_model_file(
|
||||||
|
modified_process_model_identifier: str,
|
||||||
|
message_for_git_commit: str,
|
||||||
|
http_status_to_return: int,
|
||||||
|
) -> flask.wrappers.Response:
|
||||||
|
"""_create_or_update_process_model_file."""
|
||||||
|
process_model_identifier = modified_process_model_identifier.replace(":", "/")
|
||||||
|
process_model = _get_process_model(process_model_identifier)
|
||||||
|
request_file = _get_file_from_request()
|
||||||
|
|
||||||
|
# for mypy
|
||||||
|
request_file_contents = request_file.stream.read()
|
||||||
|
if not request_file_contents:
|
||||||
|
raise ApiError(
|
||||||
|
error_code="file_contents_empty",
|
||||||
|
message="Given request file does not have any content",
|
||||||
|
status_code=400,
|
||||||
|
)
|
||||||
|
if not request_file.filename:
|
||||||
|
raise ApiError(
|
||||||
|
error_code="could_not_get_filename",
|
||||||
|
message="Could not get filename from request",
|
||||||
|
status_code=400,
|
||||||
|
)
|
||||||
|
|
||||||
|
file = None
|
||||||
|
try:
|
||||||
|
file = SpecFileService.update_file(
|
||||||
|
process_model, request_file.filename, request_file_contents
|
||||||
|
)
|
||||||
|
except ProcessModelFileInvalidError as exception:
|
||||||
|
raise (
|
||||||
|
ApiError(
|
||||||
|
error_code="process_model_file_invalid",
|
||||||
|
message=(
|
||||||
|
f"Invalid Process model file cannot be save: {request_file.name}."
|
||||||
|
f" Received error: {str(exception)}"
|
||||||
|
),
|
||||||
|
status_code=400,
|
||||||
|
)
|
||||||
|
) from exception
|
||||||
|
file_contents = SpecFileService.get_data(process_model, file.name)
|
||||||
|
file.file_contents = file_contents
|
||||||
|
file.process_model_id = process_model.id
|
||||||
|
_commit_and_push_to_git(
|
||||||
|
f"{message_for_git_commit} {process_model_identifier}/{file.name}"
|
||||||
|
)
|
||||||
|
|
||||||
|
return Response(
|
||||||
|
json.dumps(FileSchema().dump(file)),
|
||||||
|
status=http_status_to_return,
|
||||||
|
mimetype="application/json",
|
||||||
|
)
|
||||||
|
|
|
@ -34,6 +34,8 @@ from SpiffWorkflow.bpmn.serializer.workflow import BpmnWorkflowSerializer # typ
|
||||||
from SpiffWorkflow.bpmn.specs.BpmnProcessSpec import BpmnProcessSpec # type: ignore
|
from SpiffWorkflow.bpmn.specs.BpmnProcessSpec import BpmnProcessSpec # type: ignore
|
||||||
from SpiffWorkflow.bpmn.specs.events.EndEvent import EndEvent # type: ignore
|
from SpiffWorkflow.bpmn.specs.events.EndEvent import EndEvent # type: ignore
|
||||||
from SpiffWorkflow.bpmn.specs.events.event_definitions import CancelEventDefinition # type: ignore
|
from SpiffWorkflow.bpmn.specs.events.event_definitions import CancelEventDefinition # type: ignore
|
||||||
|
from SpiffWorkflow.bpmn.specs.events.StartEvent import StartEvent # type: ignore
|
||||||
|
from SpiffWorkflow.bpmn.specs.SubWorkflowTask import SubWorkflowTask # type: ignore
|
||||||
from SpiffWorkflow.bpmn.workflow import BpmnWorkflow # type: ignore
|
from SpiffWorkflow.bpmn.workflow import BpmnWorkflow # type: ignore
|
||||||
from SpiffWorkflow.dmn.parser.BpmnDmnParser import BpmnDmnParser # type: ignore
|
from SpiffWorkflow.dmn.parser.BpmnDmnParser import BpmnDmnParser # type: ignore
|
||||||
from SpiffWorkflow.dmn.serializer.task_spec_converters import BusinessRuleTaskConverter # type: ignore
|
from SpiffWorkflow.dmn.serializer.task_spec_converters import BusinessRuleTaskConverter # type: ignore
|
||||||
|
@ -787,7 +789,16 @@ class ProcessInstanceProcessor:
|
||||||
f"Manually executing Task {spiff_task.task_spec.name} of process"
|
f"Manually executing Task {spiff_task.task_spec.name} of process"
|
||||||
f" instance {self.process_instance_model.id}"
|
f" instance {self.process_instance_model.id}"
|
||||||
)
|
)
|
||||||
spiff_task.complete()
|
# Executing a subworkflow manually will restart its subprocess and allow stepping through it
|
||||||
|
if isinstance(spiff_task.task_spec, SubWorkflowTask):
|
||||||
|
subprocess = self.bpmn_process_instance.get_subprocess(spiff_task)
|
||||||
|
# We have to get to the actual start event
|
||||||
|
for task in self.bpmn_process_instance.get_tasks(workflow=subprocess):
|
||||||
|
task.complete()
|
||||||
|
if isinstance(task.task_spec, StartEvent):
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
spiff_task.complete()
|
||||||
else:
|
else:
|
||||||
spiff_logger = logging.getLogger("spiff")
|
spiff_logger = logging.getLogger("spiff")
|
||||||
spiff_logger.info(
|
spiff_logger.info(
|
||||||
|
@ -796,7 +807,20 @@ class ProcessInstanceProcessor:
|
||||||
spiff_task._set_state(TaskState.COMPLETED)
|
spiff_task._set_state(TaskState.COMPLETED)
|
||||||
for child in spiff_task.children:
|
for child in spiff_task.children:
|
||||||
child.task_spec._update(child)
|
child.task_spec._update(child)
|
||||||
self.bpmn_process_instance.last_task = spiff_task
|
spiff_task.workflow.last_task = spiff_task
|
||||||
|
|
||||||
|
if isinstance(spiff_task.task_spec, EndEvent):
|
||||||
|
for task in self.bpmn_process_instance.get_tasks(
|
||||||
|
TaskState.DEFINITE_MASK, workflow=spiff_task.workflow
|
||||||
|
):
|
||||||
|
task.complete()
|
||||||
|
|
||||||
|
# A subworkflow task will become ready when its workflow is complete. Engine steps would normally
|
||||||
|
# then complete it, but we have to do it ourselves here.
|
||||||
|
for task in self.bpmn_process_instance.get_tasks(TaskState.READY):
|
||||||
|
if isinstance(task.task_spec, SubWorkflowTask):
|
||||||
|
task.complete()
|
||||||
|
|
||||||
self.increment_spiff_step()
|
self.increment_spiff_step()
|
||||||
self.add_step()
|
self.add_step()
|
||||||
self.save()
|
self.save()
|
||||||
|
|
|
@ -6,7 +6,8 @@ from typing import List
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from flask_bpmn.models.db import db
|
from flask_bpmn.models.db import db
|
||||||
from SpiffWorkflow.bpmn.parser.ValidationException import ValidationException # type: ignore
|
from lxml import etree # type: ignore
|
||||||
|
from SpiffWorkflow.bpmn.parser.BpmnParser import BpmnValidator # type: ignore
|
||||||
|
|
||||||
from spiffworkflow_backend.models.file import File
|
from spiffworkflow_backend.models.file import File
|
||||||
from spiffworkflow_backend.models.file import FileType
|
from spiffworkflow_backend.models.file import FileType
|
||||||
|
@ -29,6 +30,10 @@ class ProcessModelFileNotFoundError(Exception):
|
||||||
"""ProcessModelFileNotFoundError."""
|
"""ProcessModelFileNotFoundError."""
|
||||||
|
|
||||||
|
|
||||||
|
class ProcessModelFileInvalidError(Exception):
|
||||||
|
"""ProcessModelFileInvalidError."""
|
||||||
|
|
||||||
|
|
||||||
class SpecFileService(FileSystemService):
|
class SpecFileService(FileSystemService):
|
||||||
"""SpecFileService."""
|
"""SpecFileService."""
|
||||||
|
|
||||||
|
@ -44,7 +49,6 @@ class SpecFileService(FileSystemService):
|
||||||
extension_filter: str = "",
|
extension_filter: str = "",
|
||||||
) -> List[File]:
|
) -> List[File]:
|
||||||
"""Return all files associated with a workflow specification."""
|
"""Return all files associated with a workflow specification."""
|
||||||
# path = SpecFileService.workflow_path(process_model_info)
|
|
||||||
path = os.path.join(
|
path = os.path.join(
|
||||||
FileSystemService.root_path(), process_model_info.id_for_file_path()
|
FileSystemService.root_path(), process_model_info.id_for_file_path()
|
||||||
)
|
)
|
||||||
|
@ -76,9 +80,22 @@ class SpecFileService(FileSystemService):
|
||||||
)
|
)
|
||||||
return references
|
return references
|
||||||
|
|
||||||
@staticmethod
|
@classmethod
|
||||||
def get_references_for_file(
|
def get_references_for_file(
|
||||||
file: File, process_model_info: ProcessModelInfo
|
cls, file: File, process_model_info: ProcessModelInfo
|
||||||
|
) -> list[SpecReference]:
|
||||||
|
"""Get_references_for_file."""
|
||||||
|
full_file_path = SpecFileService.full_file_path(process_model_info, file.name)
|
||||||
|
file_contents: bytes = b""
|
||||||
|
with open(full_file_path) as f:
|
||||||
|
file_contents = f.read().encode()
|
||||||
|
return cls.get_references_for_file_contents(
|
||||||
|
process_model_info, file.name, file_contents
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_references_for_file_contents(
|
||||||
|
cls, process_model_info: ProcessModelInfo, file_name: str, binary_data: bytes
|
||||||
) -> list[SpecReference]:
|
) -> list[SpecReference]:
|
||||||
"""Uses spiffworkflow to parse BPMN and DMN files to determine how they can be externally referenced.
|
"""Uses spiffworkflow to parse BPMN and DMN files to determine how they can be externally referenced.
|
||||||
|
|
||||||
|
@ -89,8 +106,8 @@ class SpecFileService(FileSystemService):
|
||||||
type = {str} 'process' / 'decision'
|
type = {str} 'process' / 'decision'
|
||||||
"""
|
"""
|
||||||
references: list[SpecReference] = []
|
references: list[SpecReference] = []
|
||||||
full_file_path = SpecFileService.full_file_path(process_model_info, file.name)
|
file_path = os.path.join(process_model_info.id_for_file_path(), file_name)
|
||||||
file_path = os.path.join(process_model_info.id_for_file_path(), file.name)
|
file_type = FileSystemService.file_type(file_name)
|
||||||
parser = MyCustomParser()
|
parser = MyCustomParser()
|
||||||
parser_type = None
|
parser_type = None
|
||||||
sub_parser = None
|
sub_parser = None
|
||||||
|
@ -100,14 +117,14 @@ class SpecFileService(FileSystemService):
|
||||||
messages = {}
|
messages = {}
|
||||||
correlations = {}
|
correlations = {}
|
||||||
start_messages = []
|
start_messages = []
|
||||||
if file.type == FileType.bpmn.value:
|
if file_type.value == FileType.bpmn.value:
|
||||||
parser.add_bpmn_file(full_file_path)
|
parser.add_bpmn_xml(etree.fromstring(binary_data))
|
||||||
parser_type = "process"
|
parser_type = "process"
|
||||||
sub_parsers = list(parser.process_parsers.values())
|
sub_parsers = list(parser.process_parsers.values())
|
||||||
messages = parser.messages
|
messages = parser.messages
|
||||||
correlations = parser.correlations
|
correlations = parser.correlations
|
||||||
elif file.type == FileType.dmn.value:
|
elif file_type.value == FileType.dmn.value:
|
||||||
parser.add_dmn_file(full_file_path)
|
parser.add_dmn_xml(etree.fromstring(binary_data))
|
||||||
sub_parsers = list(parser.dmn_parsers.values())
|
sub_parsers = list(parser.dmn_parsers.values())
|
||||||
parser_type = "decision"
|
parser_type = "decision"
|
||||||
else:
|
else:
|
||||||
|
@ -127,7 +144,7 @@ class SpecFileService(FileSystemService):
|
||||||
display_name=sub_parser.get_name(),
|
display_name=sub_parser.get_name(),
|
||||||
process_model_id=process_model_info.id,
|
process_model_id=process_model_info.id,
|
||||||
type=parser_type,
|
type=parser_type,
|
||||||
file_name=file.name,
|
file_name=file_name,
|
||||||
relative_path=file_path,
|
relative_path=file_path,
|
||||||
has_lanes=has_lanes,
|
has_lanes=has_lanes,
|
||||||
is_executable=is_executable,
|
is_executable=is_executable,
|
||||||
|
@ -147,23 +164,36 @@ class SpecFileService(FileSystemService):
|
||||||
# Same as update
|
# Same as update
|
||||||
return SpecFileService.update_file(process_model_info, file_name, binary_data)
|
return SpecFileService.update_file(process_model_info, file_name, binary_data)
|
||||||
|
|
||||||
@staticmethod
|
@classmethod
|
||||||
|
def validate_bpmn_xml(cls, file_name: str, binary_data: bytes) -> None:
|
||||||
|
"""Validate_bpmn_xml."""
|
||||||
|
file_type = FileSystemService.file_type(file_name)
|
||||||
|
if file_type.value == FileType.bpmn.value:
|
||||||
|
validator = BpmnValidator()
|
||||||
|
parser = MyCustomParser(validator=validator)
|
||||||
|
try:
|
||||||
|
parser.add_bpmn_xml(etree.fromstring(binary_data), filename=file_name)
|
||||||
|
except etree.XMLSyntaxError as exception:
|
||||||
|
raise ProcessModelFileInvalidError(
|
||||||
|
f"Received error trying to parse bpmn xml: {str(exception)}"
|
||||||
|
) from exception
|
||||||
|
|
||||||
|
@classmethod
|
||||||
def update_file(
|
def update_file(
|
||||||
process_model_info: ProcessModelInfo, file_name: str, binary_data: bytes
|
cls, process_model_info: ProcessModelInfo, file_name: str, binary_data: bytes
|
||||||
) -> File:
|
) -> File:
|
||||||
"""Update_file."""
|
"""Update_file."""
|
||||||
SpecFileService.assert_valid_file_name(file_name)
|
SpecFileService.assert_valid_file_name(file_name)
|
||||||
full_file_path = SpecFileService.full_file_path(process_model_info, file_name)
|
cls.validate_bpmn_xml(file_name, binary_data)
|
||||||
SpecFileService.write_file_data_to_system(full_file_path, binary_data)
|
|
||||||
file = SpecFileService.to_file_object(file_name, full_file_path)
|
|
||||||
|
|
||||||
references = SpecFileService.get_references_for_file(file, process_model_info)
|
references = cls.get_references_for_file_contents(
|
||||||
|
process_model_info, file_name, binary_data
|
||||||
|
)
|
||||||
primary_process_ref = next(
|
primary_process_ref = next(
|
||||||
(ref for ref in references if ref.is_primary and ref.is_executable), None
|
(ref for ref in references if ref.is_primary and ref.is_executable), None
|
||||||
)
|
)
|
||||||
|
|
||||||
SpecFileService.clear_caches_for_file(file_name, process_model_info)
|
SpecFileService.clear_caches_for_file(file_name, process_model_info)
|
||||||
|
|
||||||
for ref in references:
|
for ref in references:
|
||||||
# If no valid primary process is defined, default to the first process in the
|
# If no valid primary process is defined, default to the first process in the
|
||||||
# updated file.
|
# updated file.
|
||||||
|
@ -184,7 +214,11 @@ class SpecFileService(FileSystemService):
|
||||||
update_hash,
|
update_hash,
|
||||||
)
|
)
|
||||||
SpecFileService.update_caches(ref)
|
SpecFileService.update_caches(ref)
|
||||||
return file
|
|
||||||
|
# make sure we save the file as the last thing we do to ensure validations have run
|
||||||
|
full_file_path = SpecFileService.full_file_path(process_model_info, file_name)
|
||||||
|
SpecFileService.write_file_data_to_system(full_file_path, binary_data)
|
||||||
|
return SpecFileService.to_file_object(file_name, full_file_path)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_data(process_model_info: ProcessModelInfo, file_name: str) -> bytes:
|
def get_data(process_model_info: ProcessModelInfo, file_name: str) -> bytes:
|
||||||
|
@ -282,7 +316,7 @@ class SpecFileService(FileSystemService):
|
||||||
# if the old relative bpmn file no longer exists, then assume things were moved around
|
# if the old relative bpmn file no longer exists, then assume things were moved around
|
||||||
# on the file system. Otherwise, assume it is a duplicate process id and error.
|
# on the file system. Otherwise, assume it is a duplicate process id and error.
|
||||||
if os.path.isfile(full_bpmn_file_path):
|
if os.path.isfile(full_bpmn_file_path):
|
||||||
raise ValidationException(
|
raise ProcessModelFileInvalidError(
|
||||||
f"Process id ({ref.identifier}) has already been used for "
|
f"Process id ({ref.identifier}) has already been used for "
|
||||||
f"{process_id_lookup.relative_path}. It cannot be reused."
|
f"{process_id_lookup.relative_path}. It cannot be reused."
|
||||||
)
|
)
|
||||||
|
@ -314,7 +348,7 @@ class SpecFileService(FileSystemService):
|
||||||
identifier=message_model_identifier
|
identifier=message_model_identifier
|
||||||
).first()
|
).first()
|
||||||
if message_model is None:
|
if message_model is None:
|
||||||
raise ValidationException(
|
raise ProcessModelFileInvalidError(
|
||||||
"Could not find message model with identifier"
|
"Could not find message model with identifier"
|
||||||
f" '{message_model_identifier}'Required by a Start Event in :"
|
f" '{message_model_identifier}'Required by a Start Event in :"
|
||||||
f" {ref.file_name}"
|
f" {ref.file_name}"
|
||||||
|
@ -336,7 +370,7 @@ class SpecFileService(FileSystemService):
|
||||||
message_triggerable_process_model.process_model_identifier
|
message_triggerable_process_model.process_model_identifier
|
||||||
!= ref.process_model_id
|
!= ref.process_model_id
|
||||||
):
|
):
|
||||||
raise ValidationException(
|
raise ProcessModelFileInvalidError(
|
||||||
"Message model is already used to start process model"
|
"Message model is already used to start process model"
|
||||||
f" {ref.process_model_id}"
|
f" {ref.process_model_id}"
|
||||||
)
|
)
|
||||||
|
@ -355,7 +389,7 @@ class SpecFileService(FileSystemService):
|
||||||
identifier=message_model_identifier
|
identifier=message_model_identifier
|
||||||
).first()
|
).first()
|
||||||
if message_model is None:
|
if message_model is None:
|
||||||
raise ValidationException(
|
raise ProcessModelFileInvalidError(
|
||||||
"Could not find message model with identifier"
|
"Could not find message model with identifier"
|
||||||
f" '{message_model_identifier}'specified by correlation"
|
f" '{message_model_identifier}'specified by correlation"
|
||||||
f" property: {cpre}"
|
f" property: {cpre}"
|
||||||
|
|
|
@ -2,10 +2,10 @@
|
||||||
<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: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:correlationProperty id="message_correlation_property" name="Message Correlation Property">
|
<bpmn:correlationProperty id="message_correlation_property" name="Message Correlation Property">
|
||||||
<bpmn:correlationPropertyRetrievalExpression messageRef="message_send">
|
<bpmn:correlationPropertyRetrievalExpression messageRef="message_send">
|
||||||
<bpmn:formalExpression>to</bpmn:formalExpression>
|
<bpmn:messagePath>to</bpmn:messagePath>
|
||||||
</bpmn:correlationPropertyRetrievalExpression>
|
</bpmn:correlationPropertyRetrievalExpression>
|
||||||
<bpmn:correlationPropertyRetrievalExpression messageRef="message_response">
|
<bpmn:correlationPropertyRetrievalExpression messageRef="message_response">
|
||||||
<bpmn:formalExpression>from.name</bpmn:formalExpression>
|
<bpmn:messagePath>from.name</bpmn:messagePath>
|
||||||
</bpmn:correlationPropertyRetrievalExpression>
|
</bpmn:correlationPropertyRetrievalExpression>
|
||||||
</bpmn:correlationProperty>
|
</bpmn:correlationProperty>
|
||||||
<bpmn:message id="message_send" name="Message Send">
|
<bpmn:message id="message_send" name="Message Send">
|
||||||
|
@ -20,7 +20,7 @@
|
||||||
</bpmn:message>
|
</bpmn:message>
|
||||||
<bpmn:correlationProperty id="correlation_property_one" name="Correlation Property One">
|
<bpmn:correlationProperty id="correlation_property_one" name="Correlation Property One">
|
||||||
<bpmn:correlationPropertyRetrievalExpression messageRef="message_send">
|
<bpmn:correlationPropertyRetrievalExpression messageRef="message_send">
|
||||||
<bpmn:formalExpression>new</bpmn:formalExpression>
|
<bpmn:messagePath>new</bpmn:messagePath>
|
||||||
</bpmn:correlationPropertyRetrievalExpression>
|
</bpmn:correlationPropertyRetrievalExpression>
|
||||||
</bpmn:correlationProperty>
|
</bpmn:correlationProperty>
|
||||||
<bpmn:process id="test_dot_notation" name="Test Dot Notation" isExecutable="true">
|
<bpmn:process id="test_dot_notation" name="Test Dot Notation" isExecutable="true">
|
||||||
|
|
|
@ -12,18 +12,18 @@
|
||||||
</bpmn:collaboration>
|
</bpmn:collaboration>
|
||||||
<bpmn:correlationProperty id="message_correlation_property_topica" name="Message Correlation Property TopicA">
|
<bpmn:correlationProperty id="message_correlation_property_topica" name="Message Correlation Property TopicA">
|
||||||
<bpmn:correlationPropertyRetrievalExpression messageRef="message_send">
|
<bpmn:correlationPropertyRetrievalExpression messageRef="message_send">
|
||||||
<bpmn:formalExpression>topica</bpmn:formalExpression>
|
<bpmn:messagePath>topica</bpmn:messagePath>
|
||||||
</bpmn:correlationPropertyRetrievalExpression>
|
</bpmn:correlationPropertyRetrievalExpression>
|
||||||
<bpmn:correlationPropertyRetrievalExpression messageRef="message_response">
|
<bpmn:correlationPropertyRetrievalExpression messageRef="message_response">
|
||||||
<bpmn:formalExpression>the_payload.topica</bpmn:formalExpression>
|
<bpmn:messagePath>the_payload.topica</bpmn:messagePath>
|
||||||
</bpmn:correlationPropertyRetrievalExpression>
|
</bpmn:correlationPropertyRetrievalExpression>
|
||||||
</bpmn:correlationProperty>
|
</bpmn:correlationProperty>
|
||||||
<bpmn:correlationProperty id="message_correlation_property_topicb" name="Message Correlation Property TopicB">
|
<bpmn:correlationProperty id="message_correlation_property_topicb" name="Message Correlation Property TopicB">
|
||||||
<bpmn:correlationPropertyRetrievalExpression messageRef="message_send">
|
<bpmn:correlationPropertyRetrievalExpression messageRef="message_send">
|
||||||
<bpmn:formalExpression>topicb</bpmn:formalExpression>
|
<bpmn:messagePath>topicb</bpmn:messagePath>
|
||||||
</bpmn:correlationPropertyRetrievalExpression>
|
</bpmn:correlationPropertyRetrievalExpression>
|
||||||
<bpmn:correlationPropertyRetrievalExpression messageRef="message_response">
|
<bpmn:correlationPropertyRetrievalExpression messageRef="message_response">
|
||||||
<bpmn:formalExpression>the_payload.topicb</bpmn:formalExpression>
|
<bpmn:messagePath>the_payload.topicb</bpmn:messagePath>
|
||||||
</bpmn:correlationPropertyRetrievalExpression>
|
</bpmn:correlationPropertyRetrievalExpression>
|
||||||
</bpmn:correlationProperty>
|
</bpmn:correlationProperty>
|
||||||
<bpmn:message id="message_send" name="Message Send">
|
<bpmn:message id="message_send" name="Message Send">
|
||||||
|
|
|
@ -12,18 +12,18 @@
|
||||||
</bpmn:collaboration>
|
</bpmn:collaboration>
|
||||||
<bpmn:correlationProperty id="message_correlation_property_topica" name="Message Correlation Property TopicA">
|
<bpmn:correlationProperty id="message_correlation_property_topica" name="Message Correlation Property TopicA">
|
||||||
<bpmn:correlationPropertyRetrievalExpression messageRef="message_send">
|
<bpmn:correlationPropertyRetrievalExpression messageRef="message_send">
|
||||||
<bpmn:formalExpression>topica</bpmn:formalExpression>
|
<bpmn:messagePath>topica</bpmn:messagePath>
|
||||||
</bpmn:correlationPropertyRetrievalExpression>
|
</bpmn:correlationPropertyRetrievalExpression>
|
||||||
<bpmn:correlationPropertyRetrievalExpression messageRef="message_response">
|
<bpmn:correlationPropertyRetrievalExpression messageRef="message_response">
|
||||||
<bpmn:formalExpression>the_payload.topica</bpmn:formalExpression>
|
<bpmn:messagePath>the_payload.topica</bpmn:messagePath>
|
||||||
</bpmn:correlationPropertyRetrievalExpression>
|
</bpmn:correlationPropertyRetrievalExpression>
|
||||||
</bpmn:correlationProperty>
|
</bpmn:correlationProperty>
|
||||||
<bpmn:correlationProperty id="message_correlation_property_topicb" name="Message Correlation Property TopicB">
|
<bpmn:correlationProperty id="message_correlation_property_topicb" name="Message Correlation Property TopicB">
|
||||||
<bpmn:correlationPropertyRetrievalExpression messageRef="message_send">
|
<bpmn:correlationPropertyRetrievalExpression messageRef="message_send">
|
||||||
<bpmn:formalExpression>topicb</bpmn:formalExpression>
|
<bpmn:messagePath>topicb</bpmn:messagePath>
|
||||||
</bpmn:correlationPropertyRetrievalExpression>
|
</bpmn:correlationPropertyRetrievalExpression>
|
||||||
<bpmn:correlationPropertyRetrievalExpression messageRef="message_response">
|
<bpmn:correlationPropertyRetrievalExpression messageRef="message_response">
|
||||||
<bpmn:formalExpression>the_payload.topicb</bpmn:formalExpression>
|
<bpmn:messagePath>the_payload.topicb</bpmn:messagePath>
|
||||||
</bpmn:correlationPropertyRetrievalExpression>
|
</bpmn:correlationPropertyRetrievalExpression>
|
||||||
</bpmn:correlationProperty>
|
</bpmn:correlationProperty>
|
||||||
<bpmn:process id="message_send_process" name="Message Send Process" isExecutable="true">
|
<bpmn:process id="message_send_process" name="Message Send Process" isExecutable="true">
|
||||||
|
|
|
@ -12,18 +12,18 @@
|
||||||
</bpmn:collaboration>
|
</bpmn:collaboration>
|
||||||
<bpmn:correlationProperty id="mcp_topica_one" name="MCP TopicA One">
|
<bpmn:correlationProperty id="mcp_topica_one" name="MCP TopicA One">
|
||||||
<bpmn:correlationPropertyRetrievalExpression messageRef="message_send_one">
|
<bpmn:correlationPropertyRetrievalExpression messageRef="message_send_one">
|
||||||
<bpmn:formalExpression>topica_one</bpmn:formalExpression>
|
<bpmn:messagePath>topica_one</bpmn:messagePath>
|
||||||
</bpmn:correlationPropertyRetrievalExpression>
|
</bpmn:correlationPropertyRetrievalExpression>
|
||||||
<bpmn:correlationPropertyRetrievalExpression messageRef="message_response_one">
|
<bpmn:correlationPropertyRetrievalExpression messageRef="message_response_one">
|
||||||
<bpmn:formalExpression>topica_one</bpmn:formalExpression>
|
<bpmn:messagePath>topica_one</bpmn:messagePath>
|
||||||
</bpmn:correlationPropertyRetrievalExpression>
|
</bpmn:correlationPropertyRetrievalExpression>
|
||||||
</bpmn:correlationProperty>
|
</bpmn:correlationProperty>
|
||||||
<bpmn:correlationProperty id="mcp_topicb_one" name="MCP TopicB_one">
|
<bpmn:correlationProperty id="mcp_topicb_one" name="MCP TopicB_one">
|
||||||
<bpmn:correlationPropertyRetrievalExpression messageRef="message_send_one">
|
<bpmn:correlationPropertyRetrievalExpression messageRef="message_send_one">
|
||||||
<bpmn:formalExpression>topicb_one</bpmn:formalExpression>
|
<bpmn:messagePath>topicb_one</bpmn:messagePath>
|
||||||
</bpmn:correlationPropertyRetrievalExpression>
|
</bpmn:correlationPropertyRetrievalExpression>
|
||||||
<bpmn:correlationPropertyRetrievalExpression messageRef="message_response_one">
|
<bpmn:correlationPropertyRetrievalExpression messageRef="message_response_one">
|
||||||
<bpmn:formalExpression>topicb_one</bpmn:formalExpression>
|
<bpmn:messagePath>topicb_one</bpmn:messagePath>
|
||||||
</bpmn:correlationPropertyRetrievalExpression>
|
</bpmn:correlationPropertyRetrievalExpression>
|
||||||
</bpmn:correlationProperty>
|
</bpmn:correlationProperty>
|
||||||
<bpmn:message id="message_send_one" name="Message Send One">
|
<bpmn:message id="message_send_one" name="Message Send One">
|
||||||
|
|
|
@ -12,18 +12,18 @@
|
||||||
</bpmn:collaboration>
|
</bpmn:collaboration>
|
||||||
<bpmn:correlationProperty id="mcp_topica_two" name="MCP TopicA Two">
|
<bpmn:correlationProperty id="mcp_topica_two" name="MCP TopicA Two">
|
||||||
<bpmn:correlationPropertyRetrievalExpression messageRef="message_send_two">
|
<bpmn:correlationPropertyRetrievalExpression messageRef="message_send_two">
|
||||||
<bpmn:formalExpression>topica_two</bpmn:formalExpression>
|
<bpmn:messagePath>topica_two</bpmn:messagePath>
|
||||||
</bpmn:correlationPropertyRetrievalExpression>
|
</bpmn:correlationPropertyRetrievalExpression>
|
||||||
<bpmn:correlationPropertyRetrievalExpression messageRef="message_response_two">
|
<bpmn:correlationPropertyRetrievalExpression messageRef="message_response_two">
|
||||||
<bpmn:formalExpression>topica_two</bpmn:formalExpression>
|
<bpmn:messagePath>topica_two</bpmn:messagePath>
|
||||||
</bpmn:correlationPropertyRetrievalExpression>
|
</bpmn:correlationPropertyRetrievalExpression>
|
||||||
</bpmn:correlationProperty>
|
</bpmn:correlationProperty>
|
||||||
<bpmn:correlationProperty id="mcp_topicb_two" name="MCP TopicB_two">
|
<bpmn:correlationProperty id="mcp_topicb_two" name="MCP TopicB_two">
|
||||||
<bpmn:correlationPropertyRetrievalExpression messageRef="message_send_two">
|
<bpmn:correlationPropertyRetrievalExpression messageRef="message_send_two">
|
||||||
<bpmn:formalExpression>topicb_two</bpmn:formalExpression>
|
<bpmn:messagePath>topicb_two</bpmn:messagePath>
|
||||||
</bpmn:correlationPropertyRetrievalExpression>
|
</bpmn:correlationPropertyRetrievalExpression>
|
||||||
<bpmn:correlationPropertyRetrievalExpression messageRef="message_response_two">
|
<bpmn:correlationPropertyRetrievalExpression messageRef="message_response_two">
|
||||||
<bpmn:formalExpression>topicb_two</bpmn:formalExpression>
|
<bpmn:messagePath>topicb_two</bpmn:messagePath>
|
||||||
</bpmn:correlationPropertyRetrievalExpression>
|
</bpmn:correlationPropertyRetrievalExpression>
|
||||||
</bpmn:correlationProperty>
|
</bpmn:correlationProperty>
|
||||||
<bpmn:message id="message_send_two" name="Message Send Two">
|
<bpmn:message id="message_send_two" name="Message Send Two">
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?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: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:collaboration id="Collaboration_0oye1os" messages="[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object]">
|
<bpmn:collaboration id="Collaboration_0oye1os">
|
||||||
<bpmn:participant id="message_initiator" name="Message Initiator" processRef="message_send_process" />
|
<bpmn:participant id="message_initiator" name="Message Initiator" processRef="message_send_process" />
|
||||||
<bpmn:participant id="message-receiver-one" name="Message Receiver One" />
|
<bpmn:participant id="message-receiver-one" name="Message Receiver One" />
|
||||||
<bpmn:participant id="message-receiver-two" name="Message Receiver Two" />
|
<bpmn:participant id="message-receiver-two" name="Message Receiver Two" />
|
||||||
|
@ -19,18 +19,18 @@
|
||||||
</bpmn:collaboration>
|
</bpmn:collaboration>
|
||||||
<bpmn:correlationProperty id="mcp_topica_one" name="MCP TopicA One">
|
<bpmn:correlationProperty id="mcp_topica_one" name="MCP TopicA One">
|
||||||
<bpmn:correlationPropertyRetrievalExpression messageRef="message_send_one">
|
<bpmn:correlationPropertyRetrievalExpression messageRef="message_send_one">
|
||||||
<bpmn:formalExpression>topica_one</bpmn:formalExpression>
|
<bpmn:messagePath>topica_one</bpmn:messagePath>
|
||||||
</bpmn:correlationPropertyRetrievalExpression>
|
</bpmn:correlationPropertyRetrievalExpression>
|
||||||
<bpmn:correlationPropertyRetrievalExpression messageRef="message_response_one">
|
<bpmn:correlationPropertyRetrievalExpression messageRef="message_response_one">
|
||||||
<bpmn:formalExpression>payload_var_one.topica_one</bpmn:formalExpression>
|
<bpmn:messagePath>payload_var_one.topica_one</bpmn:messagePath>
|
||||||
</bpmn:correlationPropertyRetrievalExpression>
|
</bpmn:correlationPropertyRetrievalExpression>
|
||||||
</bpmn:correlationProperty>
|
</bpmn:correlationProperty>
|
||||||
<bpmn:correlationProperty id="mcp_topicb_one" name="MCP TopicB_one">
|
<bpmn:correlationProperty id="mcp_topicb_one" name="MCP TopicB_one">
|
||||||
<bpmn:correlationPropertyRetrievalExpression messageRef="message_send_one">
|
<bpmn:correlationPropertyRetrievalExpression messageRef="message_send_one">
|
||||||
<bpmn:formalExpression>topicb_one</bpmn:formalExpression>
|
<bpmn:messagePath>topicb_one</bpmn:messagePath>
|
||||||
</bpmn:correlationPropertyRetrievalExpression>
|
</bpmn:correlationPropertyRetrievalExpression>
|
||||||
<bpmn:correlationPropertyRetrievalExpression messageRef="message_response_one">
|
<bpmn:correlationPropertyRetrievalExpression messageRef="message_response_one">
|
||||||
<bpmn:formalExpression>payload_var_one.topicb</bpmn:formalExpression>
|
<bpmn:messagePath>payload_var_one.topicb</bpmn:messagePath>
|
||||||
</bpmn:correlationPropertyRetrievalExpression>
|
</bpmn:correlationPropertyRetrievalExpression>
|
||||||
</bpmn:correlationProperty>
|
</bpmn:correlationProperty>
|
||||||
<bpmn:process id="message_send_process" name="Message Send Process" isExecutable="true">
|
<bpmn:process id="message_send_process" name="Message Send Process" isExecutable="true">
|
||||||
|
@ -117,18 +117,18 @@ del time</bpmn:script>
|
||||||
</bpmn:message>
|
</bpmn:message>
|
||||||
<bpmn:correlationProperty id="mcp_topica_two" name="MCP Topica Two">
|
<bpmn:correlationProperty id="mcp_topica_two" name="MCP Topica Two">
|
||||||
<bpmn:correlationPropertyRetrievalExpression messageRef="message_send_two">
|
<bpmn:correlationPropertyRetrievalExpression messageRef="message_send_two">
|
||||||
<bpmn:formalExpression>topica_two</bpmn:formalExpression>
|
<bpmn:messagePath>topica_two</bpmn:messagePath>
|
||||||
</bpmn:correlationPropertyRetrievalExpression>
|
</bpmn:correlationPropertyRetrievalExpression>
|
||||||
<bpmn:correlationPropertyRetrievalExpression messageRef="message_response_two">
|
<bpmn:correlationPropertyRetrievalExpression messageRef="message_response_two">
|
||||||
<bpmn:formalExpression>topica_two</bpmn:formalExpression>
|
<bpmn:messagePath>topica_two</bpmn:messagePath>
|
||||||
</bpmn:correlationPropertyRetrievalExpression>
|
</bpmn:correlationPropertyRetrievalExpression>
|
||||||
</bpmn:correlationProperty>
|
</bpmn:correlationProperty>
|
||||||
<bpmn:correlationProperty id="mcp_topicb_two" name="MCP Topicb Two">
|
<bpmn:correlationProperty id="mcp_topicb_two" name="MCP Topicb Two">
|
||||||
<bpmn:correlationPropertyRetrievalExpression messageRef="message_send_two">
|
<bpmn:correlationPropertyRetrievalExpression messageRef="message_send_two">
|
||||||
<bpmn:formalExpression>topicb_two</bpmn:formalExpression>
|
<bpmn:messagePath>topicb_two</bpmn:messagePath>
|
||||||
</bpmn:correlationPropertyRetrievalExpression>
|
</bpmn:correlationPropertyRetrievalExpression>
|
||||||
<bpmn:correlationPropertyRetrievalExpression messageRef="message_response_two">
|
<bpmn:correlationPropertyRetrievalExpression messageRef="message_response_two">
|
||||||
<bpmn:formalExpression>topicb_two</bpmn:formalExpression>
|
<bpmn:messagePath>topicb_two</bpmn:messagePath>
|
||||||
</bpmn:correlationPropertyRetrievalExpression>
|
</bpmn:correlationPropertyRetrievalExpression>
|
||||||
</bpmn:correlationProperty>
|
</bpmn:correlationProperty>
|
||||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||||
|
|
|
@ -860,7 +860,7 @@ class TestProcessApi(BaseTest):
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.json is not None
|
assert response.json is not None
|
||||||
assert response.json["ok"]
|
assert response.json["file_contents"] is not None
|
||||||
|
|
||||||
response = client.get(
|
response = client.get(
|
||||||
f"/v1.0/process-models/{modified_process_model_id}/files/random_fact.svg",
|
f"/v1.0/process-models/{modified_process_model_id}/files/random_fact.svg",
|
||||||
|
|
|
@ -5,13 +5,15 @@ import pytest
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
from flask.testing import FlaskClient
|
from flask.testing import FlaskClient
|
||||||
from flask_bpmn.models.db import db
|
from flask_bpmn.models.db import db
|
||||||
from SpiffWorkflow.bpmn.parser.ValidationException import ValidationException # type: ignore
|
|
||||||
from tests.spiffworkflow_backend.helpers.base_test import BaseTest
|
from tests.spiffworkflow_backend.helpers.base_test import BaseTest
|
||||||
from tests.spiffworkflow_backend.helpers.test_data import load_test_spec
|
from tests.spiffworkflow_backend.helpers.test_data import load_test_spec
|
||||||
|
|
||||||
from spiffworkflow_backend.models.spec_reference import SpecReferenceCache
|
from spiffworkflow_backend.models.spec_reference import SpecReferenceCache
|
||||||
from spiffworkflow_backend.models.user import UserModel
|
from spiffworkflow_backend.models.user import UserModel
|
||||||
from spiffworkflow_backend.services.process_model_service import ProcessModelService
|
from spiffworkflow_backend.services.process_model_service import ProcessModelService
|
||||||
|
from spiffworkflow_backend.services.spec_file_service import (
|
||||||
|
ProcessModelFileInvalidError,
|
||||||
|
)
|
||||||
from spiffworkflow_backend.services.spec_file_service import SpecFileService
|
from spiffworkflow_backend.services.spec_file_service import SpecFileService
|
||||||
|
|
||||||
|
|
||||||
|
@ -74,7 +76,7 @@ class TestSpecFileService(BaseTest):
|
||||||
bpmn_process_id_lookups[0].relative_path
|
bpmn_process_id_lookups[0].relative_path
|
||||||
== self.call_activity_nested_relative_file_path
|
== self.call_activity_nested_relative_file_path
|
||||||
)
|
)
|
||||||
with pytest.raises(ValidationException) as exception:
|
with pytest.raises(ProcessModelFileInvalidError) as exception:
|
||||||
load_test_spec(
|
load_test_spec(
|
||||||
"call_activity_nested_duplicate",
|
"call_activity_nested_duplicate",
|
||||||
process_model_source_directory="call_activity_duplicate",
|
process_model_source_directory="call_activity_duplicate",
|
||||||
|
@ -85,6 +87,14 @@ class TestSpecFileService(BaseTest):
|
||||||
in str(exception.value)
|
in str(exception.value)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
process_model = ProcessModelService.get_process_model(
|
||||||
|
"call_activity_nested_duplicate"
|
||||||
|
)
|
||||||
|
full_file_path = SpecFileService.full_file_path(
|
||||||
|
process_model, "call_activity_nested_duplicate.bpmn"
|
||||||
|
)
|
||||||
|
assert not os.path.isfile(full_file_path)
|
||||||
|
|
||||||
def test_updates_relative_file_path_when_appropriate(
|
def test_updates_relative_file_path_when_appropriate(
|
||||||
self,
|
self,
|
||||||
app: Flask,
|
app: Flask,
|
||||||
|
@ -206,3 +216,23 @@ class TestSpecFileService(BaseTest):
|
||||||
assert dmn1[0].display_name == "Decision 1"
|
assert dmn1[0].display_name == "Decision 1"
|
||||||
assert dmn1[0].identifier == "Decision_0vrtcmk"
|
assert dmn1[0].identifier == "Decision_0vrtcmk"
|
||||||
assert dmn1[0].type == "decision"
|
assert dmn1[0].type == "decision"
|
||||||
|
|
||||||
|
def test_validate_bpmn_xml_with_invalid_xml(
|
||||||
|
self,
|
||||||
|
app: Flask,
|
||||||
|
client: FlaskClient,
|
||||||
|
with_db_and_bpmn_file_cleanup: None,
|
||||||
|
) -> None:
|
||||||
|
"""Test_validate_bpmn_xml_with_invalid_xml."""
|
||||||
|
process_model = load_test_spec(
|
||||||
|
process_model_id="group/invalid_xml",
|
||||||
|
bpmn_file_name="script_error_with_task_data.bpmn",
|
||||||
|
process_model_source_directory="error",
|
||||||
|
)
|
||||||
|
with pytest.raises(ProcessModelFileInvalidError):
|
||||||
|
SpecFileService.update_file(
|
||||||
|
process_model, "bad_xml.bpmn", b"THIS_IS_NOT_VALID_XML"
|
||||||
|
)
|
||||||
|
|
||||||
|
full_file_path = SpecFileService.full_file_path(process_model, "bad_xml.bpmn")
|
||||||
|
assert not os.path.isfile(full_file_path)
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
"@rjsf/core": "*",
|
"@rjsf/core": "*",
|
||||||
"@rjsf/mui": "^5.0.0-beta.13",
|
"@rjsf/mui": "^5.0.0-beta.13",
|
||||||
"@rjsf/utils": "^5.0.0-beta.13",
|
"@rjsf/utils": "^5.0.0-beta.13",
|
||||||
"@rjsf/validator-ajv6": "^5.0.0-beta.13",
|
"@rjsf/validator-ajv8": "^5.0.0-beta.16",
|
||||||
"@tanstack/react-table": "^8.2.2",
|
"@tanstack/react-table": "^8.2.2",
|
||||||
"@testing-library/jest-dom": "^5.16.4",
|
"@testing-library/jest-dom": "^5.16.4",
|
||||||
"@testing-library/react": "^13.3.0",
|
"@testing-library/react": "^13.3.0",
|
||||||
|
@ -4863,9 +4863,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rjsf/core": {
|
"node_modules/@rjsf/core": {
|
||||||
"version": "5.0.0-beta.13",
|
"version": "5.0.0-beta.16",
|
||||||
"resolved": "https://registry.npmjs.org/@rjsf/core/-/core-5.0.0-beta.13.tgz",
|
"resolved": "https://registry.npmjs.org/@rjsf/core/-/core-5.0.0-beta.16.tgz",
|
||||||
"integrity": "sha512-uQ3A9aJhMJsz9ct5tV3ogZkSFEkKUxrM9SJ9Hc8ijxmuaW7Jv8tNv5jiWZZsLvNXlIONX83s6JqkiOJf6IOAvg==",
|
"integrity": "sha512-TqOd3CKptWAswX9PU8pLSoAe5zI03J6Kk/aWAFbMj+xW/6hR5PXHbs5X5kxwpQx7IVXiJZZZpP5n1oDsu4GwNg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lodash": "^4.17.15",
|
"lodash": "^4.17.15",
|
||||||
"lodash-es": "^4.17.15",
|
"lodash-es": "^4.17.15",
|
||||||
|
@ -4881,9 +4881,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rjsf/mui": {
|
"node_modules/@rjsf/mui": {
|
||||||
"version": "5.0.0-beta.13",
|
"version": "5.0.0-beta.16",
|
||||||
"resolved": "https://registry.npmjs.org/@rjsf/mui/-/mui-5.0.0-beta.13.tgz",
|
"resolved": "https://registry.npmjs.org/@rjsf/mui/-/mui-5.0.0-beta.16.tgz",
|
||||||
"integrity": "sha512-hwCtADpjNssq/CsT3Wj1FDVJfdCN3gptKedGjbusLUEwQqXoVzkzl25e/IRfN8y/JxYu4lMXDU89bN9nJSKWLA==",
|
"integrity": "sha512-QskaSc2Zcwqz+nKoACstvn5LhrAx4EmicYc/6kNoj3jKH6MlfVCA7FYumu5g6TIqMrDEvZuZqBKtjL64Tv52PQ==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14"
|
"node": ">=14"
|
||||||
},
|
},
|
||||||
|
@ -4898,9 +4898,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rjsf/utils": {
|
"node_modules/@rjsf/utils": {
|
||||||
"version": "5.0.0-beta.13",
|
"version": "5.0.0-beta.16",
|
||||||
"resolved": "https://registry.npmjs.org/@rjsf/utils/-/utils-5.0.0-beta.13.tgz",
|
"resolved": "https://registry.npmjs.org/@rjsf/utils/-/utils-5.0.0-beta.16.tgz",
|
||||||
"integrity": "sha512-hWWWFD2ifjSOhqWueML4OHrZe2HW5pE2nfKGhCObFbwtggHoQlj64xDBsJ1qfUG8DGvCHztJQ/sKIaOvXnpt7w==",
|
"integrity": "sha512-dNQ620Q6a9cB28sjjRgJkxIuD9TFd03sNMlcZVdZOuZC6wjfGc4rKG0Lc7+xgLFvSPFKwXJprzfKSM3yuy9jXg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"json-schema-merge-allof": "^0.8.1",
|
"json-schema-merge-allof": "^0.8.1",
|
||||||
"jsonpointer": "^5.0.1",
|
"jsonpointer": "^5.0.1",
|
||||||
|
@ -4933,12 +4933,13 @@
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
|
||||||
"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
|
"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
|
||||||
},
|
},
|
||||||
"node_modules/@rjsf/validator-ajv6": {
|
"node_modules/@rjsf/validator-ajv8": {
|
||||||
"version": "5.0.0-beta.13",
|
"version": "5.0.0-beta.16",
|
||||||
"resolved": "https://registry.npmjs.org/@rjsf/validator-ajv6/-/validator-ajv6-5.0.0-beta.13.tgz",
|
"resolved": "https://registry.npmjs.org/@rjsf/validator-ajv8/-/validator-ajv8-5.0.0-beta.16.tgz",
|
||||||
"integrity": "sha512-X9N3/HJYV23MjUN/VJHIdBhUdBuMTUsh4HAZm50eUvUAhWK95wIqjjhAs24rzeLajrjFeH7kFr89zAqDgIFhVQ==",
|
"integrity": "sha512-VrQzR9HEH/1BF2TW/lRJuV+kILzR4geS+iW5Th1OlPeNp1NNWZuSO1kCU9O0JA17t2WHOEl/SFZXZBnN1/zwzQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ajv": "^6.7.0",
|
"ajv-formats": "^2.1.1",
|
||||||
|
"ajv8": "npm:ajv@^8.11.0",
|
||||||
"lodash": "^4.17.15",
|
"lodash": "^4.17.15",
|
||||||
"lodash-es": "^4.17.15"
|
"lodash-es": "^4.17.15"
|
||||||
},
|
},
|
||||||
|
@ -4946,7 +4947,7 @@
|
||||||
"node": ">=14"
|
"node": ">=14"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@rjsf/utils": "^5.0.0-beta.1"
|
"@rjsf/utils": "^5.0.0-beta.12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/plugin-babel": {
|
"node_modules/@rollup/plugin-babel": {
|
||||||
|
@ -6822,6 +6823,27 @@
|
||||||
"ajv": "^6.9.1"
|
"ajv": "^6.9.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ajv8": {
|
||||||
|
"name": "ajv",
|
||||||
|
"version": "8.12.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
|
||||||
|
"integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
|
||||||
|
"dependencies": {
|
||||||
|
"fast-deep-equal": "^3.1.1",
|
||||||
|
"json-schema-traverse": "^1.0.0",
|
||||||
|
"require-from-string": "^2.0.2",
|
||||||
|
"uri-js": "^4.2.2"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/epoberezkin"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ajv8/node_modules/json-schema-traverse": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
|
||||||
|
},
|
||||||
"node_modules/ansi-align": {
|
"node_modules/ansi-align": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz",
|
||||||
|
@ -34767,9 +34789,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@rjsf/core": {
|
"@rjsf/core": {
|
||||||
"version": "5.0.0-beta.13",
|
"version": "5.0.0-beta.16",
|
||||||
"resolved": "https://registry.npmjs.org/@rjsf/core/-/core-5.0.0-beta.13.tgz",
|
"resolved": "https://registry.npmjs.org/@rjsf/core/-/core-5.0.0-beta.16.tgz",
|
||||||
"integrity": "sha512-uQ3A9aJhMJsz9ct5tV3ogZkSFEkKUxrM9SJ9Hc8ijxmuaW7Jv8tNv5jiWZZsLvNXlIONX83s6JqkiOJf6IOAvg==",
|
"integrity": "sha512-TqOd3CKptWAswX9PU8pLSoAe5zI03J6Kk/aWAFbMj+xW/6hR5PXHbs5X5kxwpQx7IVXiJZZZpP5n1oDsu4GwNg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"lodash": "^4.17.15",
|
"lodash": "^4.17.15",
|
||||||
"lodash-es": "^4.17.15",
|
"lodash-es": "^4.17.15",
|
||||||
|
@ -34778,15 +34800,15 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@rjsf/mui": {
|
"@rjsf/mui": {
|
||||||
"version": "5.0.0-beta.13",
|
"version": "5.0.0-beta.16",
|
||||||
"resolved": "https://registry.npmjs.org/@rjsf/mui/-/mui-5.0.0-beta.13.tgz",
|
"resolved": "https://registry.npmjs.org/@rjsf/mui/-/mui-5.0.0-beta.16.tgz",
|
||||||
"integrity": "sha512-hwCtADpjNssq/CsT3Wj1FDVJfdCN3gptKedGjbusLUEwQqXoVzkzl25e/IRfN8y/JxYu4lMXDU89bN9nJSKWLA==",
|
"integrity": "sha512-QskaSc2Zcwqz+nKoACstvn5LhrAx4EmicYc/6kNoj3jKH6MlfVCA7FYumu5g6TIqMrDEvZuZqBKtjL64Tv52PQ==",
|
||||||
"requires": {}
|
"requires": {}
|
||||||
},
|
},
|
||||||
"@rjsf/utils": {
|
"@rjsf/utils": {
|
||||||
"version": "5.0.0-beta.13",
|
"version": "5.0.0-beta.16",
|
||||||
"resolved": "https://registry.npmjs.org/@rjsf/utils/-/utils-5.0.0-beta.13.tgz",
|
"resolved": "https://registry.npmjs.org/@rjsf/utils/-/utils-5.0.0-beta.16.tgz",
|
||||||
"integrity": "sha512-hWWWFD2ifjSOhqWueML4OHrZe2HW5pE2nfKGhCObFbwtggHoQlj64xDBsJ1qfUG8DGvCHztJQ/sKIaOvXnpt7w==",
|
"integrity": "sha512-dNQ620Q6a9cB28sjjRgJkxIuD9TFd03sNMlcZVdZOuZC6wjfGc4rKG0Lc7+xgLFvSPFKwXJprzfKSM3yuy9jXg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"json-schema-merge-allof": "^0.8.1",
|
"json-schema-merge-allof": "^0.8.1",
|
||||||
"jsonpointer": "^5.0.1",
|
"jsonpointer": "^5.0.1",
|
||||||
|
@ -34812,12 +34834,13 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@rjsf/validator-ajv6": {
|
"@rjsf/validator-ajv8": {
|
||||||
"version": "5.0.0-beta.13",
|
"version": "5.0.0-beta.16",
|
||||||
"resolved": "https://registry.npmjs.org/@rjsf/validator-ajv6/-/validator-ajv6-5.0.0-beta.13.tgz",
|
"resolved": "https://registry.npmjs.org/@rjsf/validator-ajv8/-/validator-ajv8-5.0.0-beta.16.tgz",
|
||||||
"integrity": "sha512-X9N3/HJYV23MjUN/VJHIdBhUdBuMTUsh4HAZm50eUvUAhWK95wIqjjhAs24rzeLajrjFeH7kFr89zAqDgIFhVQ==",
|
"integrity": "sha512-VrQzR9HEH/1BF2TW/lRJuV+kILzR4geS+iW5Th1OlPeNp1NNWZuSO1kCU9O0JA17t2WHOEl/SFZXZBnN1/zwzQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"ajv": "^6.7.0",
|
"ajv-formats": "^2.1.1",
|
||||||
|
"ajv8": "npm:ajv@^8.11.0",
|
||||||
"lodash": "^4.17.15",
|
"lodash": "^4.17.15",
|
||||||
"lodash-es": "^4.17.15"
|
"lodash-es": "^4.17.15"
|
||||||
}
|
}
|
||||||
|
@ -36295,6 +36318,24 @@
|
||||||
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
|
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
|
||||||
"requires": {}
|
"requires": {}
|
||||||
},
|
},
|
||||||
|
"ajv8": {
|
||||||
|
"version": "npm:ajv@8.12.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
|
||||||
|
"integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
|
||||||
|
"requires": {
|
||||||
|
"fast-deep-equal": "^3.1.1",
|
||||||
|
"json-schema-traverse": "^1.0.0",
|
||||||
|
"require-from-string": "^2.0.2",
|
||||||
|
"uri-js": "^4.2.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"json-schema-traverse": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"ansi-align": {
|
"ansi-align": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz",
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
"@rjsf/core": "*",
|
"@rjsf/core": "*",
|
||||||
"@rjsf/mui": "^5.0.0-beta.13",
|
"@rjsf/mui": "^5.0.0-beta.13",
|
||||||
"@rjsf/utils": "^5.0.0-beta.13",
|
"@rjsf/utils": "^5.0.0-beta.13",
|
||||||
"@rjsf/validator-ajv6": "^5.0.0-beta.13",
|
"@rjsf/validator-ajv8": "^5.0.0-beta.16",
|
||||||
"@tanstack/react-table": "^8.2.2",
|
"@tanstack/react-table": "^8.2.2",
|
||||||
"@testing-library/jest-dom": "^5.16.4",
|
"@testing-library/jest-dom": "^5.16.4",
|
||||||
"@testing-library/react": "^13.3.0",
|
"@testing-library/react": "^13.3.0",
|
||||||
|
|
|
@ -37,7 +37,9 @@ export default function ProcessModelSearch({
|
||||||
const shouldFilterProcessModel = (options: any) => {
|
const shouldFilterProcessModel = (options: any) => {
|
||||||
const processModel: ProcessModel = options.item;
|
const processModel: ProcessModel = options.item;
|
||||||
const { inputValue } = options;
|
const { inputValue } = options;
|
||||||
return getFullProcessModelLabel(processModel).includes(inputValue);
|
return getFullProcessModelLabel(processModel)
|
||||||
|
.toLowerCase()
|
||||||
|
.includes((inputValue || '').toLowerCase());
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<ComboBox
|
<ComboBox
|
||||||
|
|
|
@ -528,11 +528,24 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isCurrentTask = (task: any) => {
|
||||||
|
const subprocessTypes = [
|
||||||
|
'Subprocess',
|
||||||
|
'Call Activity',
|
||||||
|
'Transactional Subprocess',
|
||||||
|
];
|
||||||
|
return (
|
||||||
|
(task.state === 'WAITING' &&
|
||||||
|
subprocessTypes.filter((t) => t === task.type).length > 0) ||
|
||||||
|
task.state === 'READY'
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const canEditTaskData = (task: any) => {
|
const canEditTaskData = (task: any) => {
|
||||||
return (
|
return (
|
||||||
processInstance &&
|
processInstance &&
|
||||||
ability.can('PUT', targetUris.processInstanceTaskListDataPath) &&
|
ability.can('PUT', targetUris.processInstanceTaskListDataPath) &&
|
||||||
task.state === 'READY' &&
|
isCurrentTask(task) &&
|
||||||
processInstance.status === 'suspended' &&
|
processInstance.status === 'suspended' &&
|
||||||
showingLastSpiffStep()
|
showingLastSpiffStep()
|
||||||
);
|
);
|
||||||
|
@ -556,7 +569,7 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
|
||||||
processInstance &&
|
processInstance &&
|
||||||
processInstance.status === 'suspended' &&
|
processInstance.status === 'suspended' &&
|
||||||
ability.can('POST', targetUris.processInstanceCompleteTaskPath) &&
|
ability.can('POST', targetUris.processInstanceCompleteTaskPath) &&
|
||||||
task.state === 'READY' &&
|
isCurrentTask(task) &&
|
||||||
showingLastSpiffStep()
|
showingLastSpiffStep()
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -134,7 +134,7 @@ export default function ProcessModelEditDiagram() {
|
||||||
setProcessModel(result);
|
setProcessModel(result);
|
||||||
};
|
};
|
||||||
HttpService.makeCallToBackend({
|
HttpService.makeCallToBackend({
|
||||||
path: `/${processModelPath}`,
|
path: `/${processModelPath}?include_file_references=true`,
|
||||||
successCallback: processResult,
|
successCallback: processResult,
|
||||||
});
|
});
|
||||||
}, [processModelPath]);
|
}, [processModelPath]);
|
||||||
|
@ -966,7 +966,6 @@ export default function ProcessModelEditDiagram() {
|
||||||
{scriptEditorAndTests()}
|
{scriptEditorAndTests()}
|
||||||
{markdownEditor()}
|
{markdownEditor()}
|
||||||
{processModelSelector()}
|
{processModelSelector()}
|
||||||
{`Processes length: ${processes.length}`}
|
|
||||||
<div id="diagram-container" />
|
<div id="diagram-container" />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -374,6 +374,7 @@ export default function ProcessModelShow() {
|
||||||
|
|
||||||
const doFileUpload = (event: any) => {
|
const doFileUpload = (event: any) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
setErrorObject(null);
|
||||||
const url = `/process-models/${modifiedProcessModelId}/files`;
|
const url = `/process-models/${modifiedProcessModelId}/files`;
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('file', filesToUpload[0]);
|
formData.append('file', filesToUpload[0]);
|
||||||
|
@ -383,6 +384,7 @@ export default function ProcessModelShow() {
|
||||||
successCallback: onUploadedCallback,
|
successCallback: onUploadedCallback,
|
||||||
httpMethod: 'POST',
|
httpMethod: 'POST',
|
||||||
postBody: formData,
|
postBody: formData,
|
||||||
|
failureCallback: setErrorObject,
|
||||||
});
|
});
|
||||||
setFilesToUpload(null);
|
setFilesToUpload(null);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { useEffect, useState } from 'react';
|
import { useContext, useEffect, useState } from 'react';
|
||||||
import Editor from '@monaco-editor/react';
|
import Editor from '@monaco-editor/react';
|
||||||
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
|
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
@ -8,18 +8,25 @@ import HttpService from '../services/HttpService';
|
||||||
import ButtonWithConfirmation from '../components/ButtonWithConfirmation';
|
import ButtonWithConfirmation from '../components/ButtonWithConfirmation';
|
||||||
import { modifyProcessIdentifierForPathParam } from '../helpers';
|
import { modifyProcessIdentifierForPathParam } from '../helpers';
|
||||||
import { ProcessFile } from '../interfaces';
|
import { ProcessFile } from '../interfaces';
|
||||||
|
import ErrorContext from '../contexts/ErrorContext';
|
||||||
|
import { Notification } from '../components/Notification';
|
||||||
|
|
||||||
// NOTE: This is mostly the same as ProcessModelEditDiagram and if we go this route could
|
// NOTE: This is mostly the same as ProcessModelEditDiagram and if we go this route could
|
||||||
// possibly be merged into it. I'm leaving as a separate file now in case it does
|
// possibly be merged into it. I'm leaving as a separate file now in case it does
|
||||||
// end up diverging greatly
|
// end up diverging greatly
|
||||||
export default function ReactFormEditor() {
|
export default function ReactFormEditor() {
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
|
const setErrorObject = (useContext as any)(ErrorContext)[1];
|
||||||
|
|
||||||
const [showFileNameEditor, setShowFileNameEditor] = useState(false);
|
const [showFileNameEditor, setShowFileNameEditor] = useState(false);
|
||||||
const [newFileName, setNewFileName] = useState('');
|
const [newFileName, setNewFileName] = useState('');
|
||||||
const searchParams = useSearchParams()[0];
|
const searchParams = useSearchParams()[0];
|
||||||
const handleShowFileNameEditor = () => setShowFileNameEditor(true);
|
const handleShowFileNameEditor = () => setShowFileNameEditor(true);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const [displaySaveFileMessage, setDisplaySaveFileMessage] =
|
||||||
|
useState<boolean>(false);
|
||||||
|
|
||||||
const [processModelFile, setProcessModelFile] = useState<ProcessFile | null>(
|
const [processModelFile, setProcessModelFile] = useState<ProcessFile | null>(
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
|
@ -70,6 +77,7 @@ export default function ReactFormEditor() {
|
||||||
}, [params, modifiedProcessModelId]);
|
}, [params, modifiedProcessModelId]);
|
||||||
|
|
||||||
const navigateToProcessModelFile = (_result: any) => {
|
const navigateToProcessModelFile = (_result: any) => {
|
||||||
|
setDisplaySaveFileMessage(true);
|
||||||
if (!params.file_name) {
|
if (!params.file_name) {
|
||||||
const fileNameWithExtension = `${newFileName}.${fileExtension}`;
|
const fileNameWithExtension = `${newFileName}.${fileExtension}`;
|
||||||
navigate(
|
navigate(
|
||||||
|
@ -79,6 +87,9 @@ export default function ReactFormEditor() {
|
||||||
};
|
};
|
||||||
|
|
||||||
const saveFile = () => {
|
const saveFile = () => {
|
||||||
|
setErrorObject(null);
|
||||||
|
setDisplaySaveFileMessage(false);
|
||||||
|
|
||||||
let url = `/process-models/${modifiedProcessModelId}/files`;
|
let url = `/process-models/${modifiedProcessModelId}/files`;
|
||||||
let httpMethod = 'PUT';
|
let httpMethod = 'PUT';
|
||||||
let fileNameWithExtension = params.file_name;
|
let fileNameWithExtension = params.file_name;
|
||||||
|
@ -105,6 +116,7 @@ export default function ReactFormEditor() {
|
||||||
HttpService.makeCallToBackend({
|
HttpService.makeCallToBackend({
|
||||||
path: url,
|
path: url,
|
||||||
successCallback: navigateToProcessModelFile,
|
successCallback: navigateToProcessModelFile,
|
||||||
|
failureCallback: setErrorObject,
|
||||||
httpMethod,
|
httpMethod,
|
||||||
postBody: formData,
|
postBody: formData,
|
||||||
});
|
});
|
||||||
|
@ -162,6 +174,20 @@ export default function ReactFormEditor() {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const saveFileMessage = () => {
|
||||||
|
if (displaySaveFileMessage) {
|
||||||
|
return (
|
||||||
|
<Notification
|
||||||
|
title="File Saved: "
|
||||||
|
onClose={() => setDisplaySaveFileMessage(false)}
|
||||||
|
>
|
||||||
|
Changes to the file were saved.
|
||||||
|
</Notification>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
if (processModelFile || !params.file_name) {
|
if (processModelFile || !params.file_name) {
|
||||||
const processModelFileName = processModelFile ? processModelFile.name : '';
|
const processModelFileName = processModelFile ? processModelFile.name : '';
|
||||||
return (
|
return (
|
||||||
|
@ -182,6 +208,7 @@ export default function ReactFormEditor() {
|
||||||
{processModelFileName}
|
{processModelFileName}
|
||||||
</h1>
|
</h1>
|
||||||
{newFileNameBox()}
|
{newFileNameBox()}
|
||||||
|
{saveFileMessage()}
|
||||||
<Button onClick={saveFile} variant="danger" data-qa="file-save-button">
|
<Button onClick={saveFile} variant="danger" data-qa="file-save-button">
|
||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -1,13 +1,6 @@
|
||||||
import { useContext, useEffect, useState } from 'react';
|
import { useContext, useEffect, useState } from 'react';
|
||||||
import { useNavigate, useParams } from 'react-router-dom';
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
|
import validator from '@rjsf/validator-ajv8';
|
||||||
// FIXME: npm install @rjsf/validator-ajv8 and use it as soon as
|
|
||||||
// rawErrors is fixed.
|
|
||||||
// https://react-jsonschema-form.readthedocs.io/en/latest/usage/validation/
|
|
||||||
// https://github.com/rjsf-team/react-jsonschema-form/issues/2309 links to a codesandbox that might be useful to fork
|
|
||||||
// if we wanted to file a defect against rjsf to show the difference between validator-ajv6 and validator-ajv8.
|
|
||||||
// https://github.com/rjsf-team/react-jsonschema-form/blob/main/docs/api-reference/uiSchema.md talks about rawErrors
|
|
||||||
import validator from '@rjsf/validator-ajv6';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
TabList,
|
TabList,
|
||||||
|
|
|
@ -1,12 +1,23 @@
|
||||||
import React, { CSSProperties } from 'react';
|
import React, { CSSProperties } from 'react';
|
||||||
import Box from '@mui/material/Box';
|
import {
|
||||||
import Grid from '@mui/material/Grid';
|
ArrayFieldTemplateItemType,
|
||||||
import Paper from '@mui/material/Paper';
|
FormContextType,
|
||||||
import { ArrayFieldTemplateItemType } from '@rjsf/utils';
|
RJSFSchema,
|
||||||
|
StrictRJSFSchema,
|
||||||
|
} from '@rjsf/utils';
|
||||||
|
|
||||||
function ArrayFieldItemTemplate(props: ArrayFieldTemplateItemType) {
|
/** The `ArrayFieldItemTemplate` component is the template used to render an items of an array.
|
||||||
|
*
|
||||||
|
* @param props - The `ArrayFieldTemplateItemType` props for the component
|
||||||
|
*/
|
||||||
|
export default function ArrayFieldItemTemplate<
|
||||||
|
T = any,
|
||||||
|
S extends StrictRJSFSchema = RJSFSchema,
|
||||||
|
F extends FormContextType = any
|
||||||
|
>(props: ArrayFieldTemplateItemType<T, S, F>) {
|
||||||
const {
|
const {
|
||||||
children,
|
children,
|
||||||
|
className,
|
||||||
disabled,
|
disabled,
|
||||||
hasToolbar,
|
hasToolbar,
|
||||||
hasMoveDown,
|
hasMoveDown,
|
||||||
|
@ -16,8 +27,8 @@ function ArrayFieldItemTemplate(props: ArrayFieldTemplateItemType) {
|
||||||
onDropIndexClick,
|
onDropIndexClick,
|
||||||
onReorderClick,
|
onReorderClick,
|
||||||
readonly,
|
readonly,
|
||||||
uiSchema,
|
|
||||||
registry,
|
registry,
|
||||||
|
uiSchema,
|
||||||
} = props;
|
} = props;
|
||||||
const { MoveDownButton, MoveUpButton, RemoveButton } =
|
const { MoveDownButton, MoveUpButton, RemoveButton } =
|
||||||
registry.templates.ButtonTemplates;
|
registry.templates.ButtonTemplates;
|
||||||
|
@ -26,47 +37,49 @@ function ArrayFieldItemTemplate(props: ArrayFieldTemplateItemType) {
|
||||||
paddingLeft: 6,
|
paddingLeft: 6,
|
||||||
paddingRight: 6,
|
paddingRight: 6,
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
minWidth: 0,
|
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<Grid container alignItems="center">
|
<div className={className}>
|
||||||
<Grid item xs style={{ overflow: 'auto' }}>
|
<div className={hasToolbar ? 'col-xs-9' : 'col-xs-12'}>{children}</div>
|
||||||
<Box mb={2}>
|
|
||||||
<Paper elevation={2}>
|
|
||||||
<Box p={2}>{children}</Box>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
</Grid>
|
|
||||||
{hasToolbar && (
|
{hasToolbar && (
|
||||||
<Grid item>
|
<div className="col-xs-3 array-item-toolbox">
|
||||||
{(hasMoveUp || hasMoveDown) && (
|
<div
|
||||||
<MoveUpButton
|
className="btn-group"
|
||||||
style={btnStyle}
|
style={{
|
||||||
disabled={disabled || readonly || !hasMoveUp}
|
display: 'flex',
|
||||||
onClick={onReorderClick(index, index - 1)}
|
justifyContent: 'space-around',
|
||||||
uiSchema={uiSchema}
|
}}
|
||||||
/>
|
>
|
||||||
)}
|
{(hasMoveUp || hasMoveDown) && (
|
||||||
{(hasMoveUp || hasMoveDown) && (
|
<MoveUpButton
|
||||||
<MoveDownButton
|
style={btnStyle}
|
||||||
style={btnStyle}
|
disabled={disabled || readonly || !hasMoveUp}
|
||||||
disabled={disabled || readonly || !hasMoveDown}
|
onClick={onReorderClick(index, index - 1)}
|
||||||
onClick={onReorderClick(index, index + 1)}
|
uiSchema={uiSchema}
|
||||||
uiSchema={uiSchema}
|
registry={registry}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{hasRemove && (
|
{(hasMoveUp || hasMoveDown) && (
|
||||||
<RemoveButton
|
<MoveDownButton
|
||||||
style={btnStyle}
|
style={btnStyle}
|
||||||
disabled={disabled || readonly}
|
disabled={disabled || readonly || !hasMoveDown}
|
||||||
onClick={onDropIndexClick(index)}
|
onClick={onReorderClick(index, index + 1)}
|
||||||
uiSchema={uiSchema}
|
uiSchema={uiSchema}
|
||||||
/>
|
registry={registry}
|
||||||
)}
|
/>
|
||||||
</Grid>
|
)}
|
||||||
|
{hasRemove && (
|
||||||
|
<RemoveButton
|
||||||
|
style={btnStyle}
|
||||||
|
disabled={disabled || readonly}
|
||||||
|
onClick={onDropIndexClick(index)}
|
||||||
|
uiSchema={uiSchema}
|
||||||
|
registry={registry}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</Grid>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ArrayFieldItemTemplate;
|
|
||||||
|
|
|
@ -1,17 +1,26 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Box from '@mui/material/Box';
|
|
||||||
import Grid from '@mui/material/Grid';
|
|
||||||
import Paper from '@mui/material/Paper';
|
|
||||||
import {
|
import {
|
||||||
ArrayFieldTemplateItemType,
|
|
||||||
ArrayFieldTemplateProps,
|
|
||||||
getTemplate,
|
getTemplate,
|
||||||
getUiOptions,
|
getUiOptions,
|
||||||
|
ArrayFieldTemplateProps,
|
||||||
|
ArrayFieldTemplateItemType,
|
||||||
|
FormContextType,
|
||||||
|
RJSFSchema,
|
||||||
|
StrictRJSFSchema,
|
||||||
} from '@rjsf/utils';
|
} from '@rjsf/utils';
|
||||||
|
|
||||||
function ArrayFieldTemplate(props: ArrayFieldTemplateProps) {
|
/** The `ArrayFieldTemplate` component is the template used to render all items in an array.
|
||||||
|
*
|
||||||
|
* @param props - The `ArrayFieldTemplateItemType` props for the component
|
||||||
|
*/
|
||||||
|
export default function ArrayFieldTemplate<
|
||||||
|
T = any,
|
||||||
|
S extends StrictRJSFSchema = RJSFSchema,
|
||||||
|
F extends FormContextType = any
|
||||||
|
>(props: ArrayFieldTemplateProps<T, S, F>) {
|
||||||
const {
|
const {
|
||||||
canAdd,
|
canAdd,
|
||||||
|
className,
|
||||||
disabled,
|
disabled,
|
||||||
idSchema,
|
idSchema,
|
||||||
uiSchema,
|
uiSchema,
|
||||||
|
@ -23,68 +32,62 @@ function ArrayFieldTemplate(props: ArrayFieldTemplateProps) {
|
||||||
schema,
|
schema,
|
||||||
title,
|
title,
|
||||||
} = props;
|
} = props;
|
||||||
const uiOptions = getUiOptions(uiSchema);
|
const uiOptions = getUiOptions<T, S, F>(uiSchema);
|
||||||
const ArrayFieldDescriptionTemplate =
|
const ArrayFieldDescriptionTemplate = getTemplate<
|
||||||
getTemplate<'ArrayFieldDescriptionTemplate'>(
|
'ArrayFieldDescriptionTemplate',
|
||||||
'ArrayFieldDescriptionTemplate',
|
T,
|
||||||
registry,
|
S,
|
||||||
uiOptions
|
F
|
||||||
);
|
>('ArrayFieldDescriptionTemplate', registry, uiOptions);
|
||||||
const ArrayFieldItemTemplate = getTemplate<'ArrayFieldItemTemplate'>(
|
const ArrayFieldItemTemplate = getTemplate<'ArrayFieldItemTemplate', T, S, F>(
|
||||||
'ArrayFieldItemTemplate',
|
'ArrayFieldItemTemplate',
|
||||||
registry,
|
registry,
|
||||||
uiOptions
|
uiOptions
|
||||||
);
|
);
|
||||||
const ArrayFieldTitleTemplate = getTemplate<'ArrayFieldTitleTemplate'>(
|
const ArrayFieldTitleTemplate = getTemplate<
|
||||||
'ArrayFieldTitleTemplate',
|
'ArrayFieldTitleTemplate',
|
||||||
registry,
|
T,
|
||||||
uiOptions
|
S,
|
||||||
);
|
F
|
||||||
|
>('ArrayFieldTitleTemplate', registry, uiOptions);
|
||||||
// Button templates are not overridden in the uiSchema
|
// Button templates are not overridden in the uiSchema
|
||||||
const {
|
const {
|
||||||
ButtonTemplates: { AddButton },
|
ButtonTemplates: { AddButton },
|
||||||
} = registry.templates;
|
} = registry.templates;
|
||||||
return (
|
return (
|
||||||
<Paper elevation={2}>
|
<fieldset className={className} id={idSchema.$id}>
|
||||||
<Box p={2}>
|
<ArrayFieldTitleTemplate
|
||||||
<ArrayFieldTitleTemplate
|
idSchema={idSchema}
|
||||||
idSchema={idSchema}
|
title={uiOptions.title || title}
|
||||||
title={uiOptions.title || title}
|
required={required}
|
||||||
schema={schema}
|
schema={schema}
|
||||||
uiSchema={uiSchema}
|
uiSchema={uiSchema}
|
||||||
required={required}
|
registry={registry}
|
||||||
registry={registry}
|
/>
|
||||||
/>
|
<ArrayFieldDescriptionTemplate
|
||||||
<ArrayFieldDescriptionTemplate
|
idSchema={idSchema}
|
||||||
idSchema={idSchema}
|
description={uiOptions.description || schema.description}
|
||||||
description={uiOptions.description || schema.description}
|
schema={schema}
|
||||||
schema={schema}
|
uiSchema={uiSchema}
|
||||||
uiSchema={uiSchema}
|
registry={registry}
|
||||||
registry={registry}
|
/>
|
||||||
/>
|
<div className="row array-item-list">
|
||||||
<Grid container key={`array-item-list-${idSchema.$id}`}>
|
{items &&
|
||||||
{items &&
|
items.map(
|
||||||
items.map(({ key, ...itemProps }: ArrayFieldTemplateItemType) => (
|
({ key, ...itemProps }: ArrayFieldTemplateItemType<T, S, F>) => (
|
||||||
<ArrayFieldItemTemplate key={key} {...itemProps} />
|
<ArrayFieldItemTemplate key={key} {...itemProps} />
|
||||||
))}
|
)
|
||||||
{canAdd && (
|
|
||||||
<Grid container justifyContent="flex-end">
|
|
||||||
<Grid item>
|
|
||||||
<Box mt={2}>
|
|
||||||
<AddButton
|
|
||||||
className="array-item-add"
|
|
||||||
onClick={onAddClick}
|
|
||||||
disabled={disabled || readonly}
|
|
||||||
uiSchema={uiSchema}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
)}
|
)}
|
||||||
</Grid>
|
</div>
|
||||||
</Box>
|
{canAdd && (
|
||||||
</Paper>
|
<AddButton
|
||||||
|
className="array-item-add"
|
||||||
|
onClick={onAddClick}
|
||||||
|
disabled={disabled || readonly}
|
||||||
|
uiSchema={uiSchema}
|
||||||
|
registry={registry}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</fieldset>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ArrayFieldTemplate;
|
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const REQUIRED_FIELD_SYMBOL = "*";
|
||||||
|
|
||||||
|
export type LabelProps = {
|
||||||
|
/** The label for the field */
|
||||||
|
label?: string;
|
||||||
|
/** A boolean value stating if the field is required */
|
||||||
|
required?: boolean;
|
||||||
|
/** The id of the input field being labeled */
|
||||||
|
id?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Renders a label for a field
|
||||||
|
*
|
||||||
|
* @param props - The `LabelProps` for this component
|
||||||
|
*/
|
||||||
|
export default function Label(props: LabelProps) {
|
||||||
|
const { label, required, id } = props;
|
||||||
|
if (!label) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<label className="control-label" htmlFor={id}>
|
||||||
|
{label}
|
||||||
|
{required && <span className="required">{REQUIRED_FIELD_SYMBOL}</span>}
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,89 +1,87 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Grid from '@mui/material/Grid';
|
|
||||||
import {
|
import {
|
||||||
|
FormContextType,
|
||||||
|
ObjectFieldTemplatePropertyType,
|
||||||
ObjectFieldTemplateProps,
|
ObjectFieldTemplateProps,
|
||||||
|
RJSFSchema,
|
||||||
|
StrictRJSFSchema,
|
||||||
canExpand,
|
canExpand,
|
||||||
getTemplate,
|
getTemplate,
|
||||||
getUiOptions,
|
getUiOptions,
|
||||||
} from '@rjsf/utils';
|
} from '@rjsf/utils';
|
||||||
|
|
||||||
function ObjectFieldTemplate({
|
/** The `ObjectFieldTemplate` is the template to use to render all the inner properties of an object along with the
|
||||||
description,
|
* title and description if available. If the object is expandable, then an `AddButton` is also rendered after all
|
||||||
title,
|
* the properties.
|
||||||
properties,
|
*
|
||||||
required,
|
* @param props - The `ObjectFieldTemplateProps` for this component
|
||||||
disabled,
|
*/
|
||||||
readonly,
|
export default function ObjectFieldTemplate<
|
||||||
uiSchema,
|
T = any,
|
||||||
idSchema,
|
S extends StrictRJSFSchema = RJSFSchema,
|
||||||
schema,
|
F extends FormContextType = any
|
||||||
formData,
|
>(props: ObjectFieldTemplateProps<T, S, F>) {
|
||||||
onAddClick,
|
const {
|
||||||
registry,
|
description,
|
||||||
}: ObjectFieldTemplateProps) {
|
disabled,
|
||||||
const uiOptions = getUiOptions(uiSchema);
|
formData,
|
||||||
const TitleFieldTemplate = getTemplate<'TitleFieldTemplate'>(
|
idSchema,
|
||||||
|
onAddClick,
|
||||||
|
properties,
|
||||||
|
readonly,
|
||||||
|
registry,
|
||||||
|
required,
|
||||||
|
schema,
|
||||||
|
title,
|
||||||
|
uiSchema,
|
||||||
|
} = props;
|
||||||
|
const options = getUiOptions<T, S, F>(uiSchema);
|
||||||
|
const TitleFieldTemplate = getTemplate<'TitleFieldTemplate', T, S, F>(
|
||||||
'TitleFieldTemplate',
|
'TitleFieldTemplate',
|
||||||
registry,
|
registry,
|
||||||
uiOptions
|
options
|
||||||
);
|
);
|
||||||
const DescriptionFieldTemplate = getTemplate<'DescriptionFieldTemplate'>(
|
const DescriptionFieldTemplate = getTemplate<
|
||||||
'DescriptionFieldTemplate',
|
'DescriptionFieldTemplate',
|
||||||
registry,
|
T,
|
||||||
uiOptions
|
S,
|
||||||
);
|
F
|
||||||
|
>('DescriptionFieldTemplate', registry, options);
|
||||||
// Button templates are not overridden in the uiSchema
|
// Button templates are not overridden in the uiSchema
|
||||||
const {
|
const {
|
||||||
ButtonTemplates: { AddButton },
|
ButtonTemplates: { AddButton },
|
||||||
} = registry.templates;
|
} = registry.templates;
|
||||||
return (
|
return (
|
||||||
<>
|
<fieldset id={idSchema.$id}>
|
||||||
{(uiOptions.title || title) && (
|
{(options.title || title) && (
|
||||||
<TitleFieldTemplate
|
<TitleFieldTemplate
|
||||||
id={`${idSchema.$id}-title`}
|
id={`${idSchema.$id}__title`}
|
||||||
title={title}
|
title={options.title || title}
|
||||||
required={required}
|
required={required}
|
||||||
schema={schema}
|
schema={schema}
|
||||||
uiSchema={uiSchema}
|
uiSchema={uiSchema}
|
||||||
registry={registry}
|
registry={registry}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{(uiOptions.description || description) && (
|
{(options.description || description) && (
|
||||||
<DescriptionFieldTemplate
|
<DescriptionFieldTemplate
|
||||||
id={`${idSchema.$id}-description`}
|
id={`${idSchema.$id}__description`}
|
||||||
description={uiOptions.description || description!}
|
description={options.description || description!}
|
||||||
schema={schema}
|
schema={schema}
|
||||||
uiSchema={uiSchema}
|
uiSchema={uiSchema}
|
||||||
registry={registry}
|
registry={registry}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Grid container spacing={2} style={{ marginTop: '10px' }}>
|
{properties.map((prop: ObjectFieldTemplatePropertyType) => prop.content)}
|
||||||
{properties.map((element, index) =>
|
{canExpand<T, S, F>(schema, uiSchema, formData) && (
|
||||||
// Remove the <Grid> if the inner element is hidden as the <Grid>
|
<AddButton
|
||||||
// itself would otherwise still take up space.
|
className="object-property-expand"
|
||||||
element.hidden ? (
|
onClick={onAddClick(schema)}
|
||||||
element.content
|
disabled={disabled || readonly}
|
||||||
) : (
|
uiSchema={uiSchema}
|
||||||
<Grid item xs={12} key={index} style={{ marginBottom: '10px' }}>
|
registry={registry}
|
||||||
{element.content}
|
/>
|
||||||
</Grid>
|
)}
|
||||||
)
|
</fieldset>
|
||||||
)}
|
|
||||||
{canExpand(schema, uiSchema, formData) && (
|
|
||||||
<Grid container justifyContent="flex-end">
|
|
||||||
<Grid item>
|
|
||||||
<AddButton
|
|
||||||
className="object-property-expand"
|
|
||||||
onClick={onAddClick(schema)}
|
|
||||||
disabled={disabled || readonly}
|
|
||||||
uiSchema={uiSchema}
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
)}
|
|
||||||
</Grid>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ObjectFieldTemplate;
|
|
||||||
|
|
|
@ -1,80 +1,73 @@
|
||||||
import React, { CSSProperties } from 'react';
|
|
||||||
import FormControl from '@mui/material/FormControl';
|
|
||||||
import Grid from '@mui/material/Grid';
|
|
||||||
import InputLabel from '@mui/material/InputLabel';
|
|
||||||
import Input from '@mui/material/OutlinedInput';
|
|
||||||
import {
|
import {
|
||||||
ADDITIONAL_PROPERTY_FLAG,
|
ADDITIONAL_PROPERTY_FLAG,
|
||||||
|
FormContextType,
|
||||||
|
RJSFSchema,
|
||||||
|
StrictRJSFSchema,
|
||||||
WrapIfAdditionalTemplateProps,
|
WrapIfAdditionalTemplateProps,
|
||||||
} from '@rjsf/utils';
|
} from '@rjsf/utils';
|
||||||
|
|
||||||
function WrapIfAdditionalTemplate({
|
import Label from '../FieldTemplate/Label';
|
||||||
children,
|
|
||||||
classNames,
|
/** The `WrapIfAdditional` component is used by the `FieldTemplate` to rename, or remove properties that are
|
||||||
disabled,
|
* part of an `additionalProperties` part of a schema.
|
||||||
id,
|
*
|
||||||
label,
|
* @param props - The `WrapIfAdditionalProps` for this component
|
||||||
onDropPropertyClick,
|
*/
|
||||||
onKeyChange,
|
export default function WrapIfAdditionalTemplate<
|
||||||
readonly,
|
T = any,
|
||||||
required,
|
S extends StrictRJSFSchema = RJSFSchema,
|
||||||
schema,
|
F extends FormContextType = any
|
||||||
uiSchema,
|
>(props: WrapIfAdditionalTemplateProps<T, S, F>) {
|
||||||
registry,
|
const {
|
||||||
}: WrapIfAdditionalTemplateProps) {
|
id,
|
||||||
|
classNames,
|
||||||
|
disabled,
|
||||||
|
label,
|
||||||
|
onKeyChange,
|
||||||
|
onDropPropertyClick,
|
||||||
|
readonly,
|
||||||
|
required,
|
||||||
|
schema,
|
||||||
|
children,
|
||||||
|
uiSchema,
|
||||||
|
registry,
|
||||||
|
} = props;
|
||||||
// Button templates are not overridden in the uiSchema
|
// Button templates are not overridden in the uiSchema
|
||||||
const { RemoveButton } = registry.templates.ButtonTemplates;
|
const { RemoveButton } = registry.templates.ButtonTemplates;
|
||||||
const keyLabel = `${label} Key`; // i18n ?
|
const keyLabel = `${label} Key`; // i18n ?
|
||||||
const additional = ADDITIONAL_PROPERTY_FLAG in schema;
|
const additional = ADDITIONAL_PROPERTY_FLAG in schema;
|
||||||
const btnStyle: CSSProperties = {
|
|
||||||
flex: 1,
|
|
||||||
paddingLeft: 6,
|
|
||||||
paddingRight: 6,
|
|
||||||
fontWeight: 'bold',
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!additional) {
|
if (!additional) {
|
||||||
return <div className={classNames}>{children}</div>;
|
return <div className={classNames}>{children}</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleBlur = ({ target }: React.FocusEvent<HTMLInputElement>) =>
|
|
||||||
onKeyChange(target.value);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid
|
<div className={classNames}>
|
||||||
container
|
<div className="row">
|
||||||
key={`${id}-key`}
|
<div className="col-xs-5 form-additional">
|
||||||
alignItems="center"
|
<div className="form-group">
|
||||||
spacing={2}
|
<Label label={keyLabel} required={required} id={`${id}-key`} />
|
||||||
className={classNames}
|
<input
|
||||||
>
|
className="form-control"
|
||||||
<Grid item xs>
|
type="text"
|
||||||
<FormControl fullWidth required={required}>
|
id={`${id}-key`}
|
||||||
<InputLabel>{keyLabel}</InputLabel>
|
onBlur={(event) => onKeyChange(event.target.value)}
|
||||||
<Input
|
defaultValue={label}
|
||||||
defaultValue={label}
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="form-additional form-group col-xs-5">{children}</div>
|
||||||
|
<div className="col-xs-2">
|
||||||
|
<RemoveButton
|
||||||
|
className="array-item-remove btn-block"
|
||||||
|
style={{ border: '0' }}
|
||||||
disabled={disabled || readonly}
|
disabled={disabled || readonly}
|
||||||
id={`${id}-key`}
|
onClick={onDropPropertyClick(label)}
|
||||||
name={`${id}-key`}
|
uiSchema={uiSchema}
|
||||||
onBlur={!readonly ? handleBlur : undefined}
|
registry={registry}
|
||||||
type="text"
|
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</div>
|
||||||
</Grid>
|
</div>
|
||||||
<Grid item xs>
|
</div>
|
||||||
{children}
|
|
||||||
</Grid>
|
|
||||||
<Grid item>
|
|
||||||
<RemoveButton
|
|
||||||
iconType="default"
|
|
||||||
style={btnStyle}
|
|
||||||
disabled={disabled || readonly}
|
|
||||||
onClick={onDropPropertyClick(label)}
|
|
||||||
uiSchema={uiSchema}
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default WrapIfAdditionalTemplate;
|
|
||||||
|
|
Loading…
Reference in New Issue