Merge pull request #25 from sartography/feature/nested-groups

Feature/nested groups
This commit is contained in:
jasquat 2022-11-08 17:49:43 -05:00 committed by GitHub
commit 04b2e7c8fc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
67 changed files with 1936 additions and 884 deletions

View File

@ -0,0 +1,22 @@
"""Updates all JSON files, based on the current state of BPMN_SPEC_ABSOLUTE_DIR"""
from spiffworkflow_backend import get_hacked_up_app_for_script
from spiffworkflow_backend.services.process_model_service import ProcessModelService
def main() -> None:
"""Main."""
app = get_hacked_up_app_for_script()
with app.app_context():
groups = ProcessModelService().get_process_groups()
for group in groups:
for process_model in group.process_models:
update_items = {
'process_group_id': '',
'id': f"{group.id}/{process_model.id}"
}
ProcessModelService().update_spec(process_model, update_items)
if __name__ == "__main__":
main()

View File

@ -4,6 +4,7 @@ import shutil
import pytest
from flask.app import Flask
from flask.testing import FlaskClient
from flask_bpmn.models.db import db
from flask_bpmn.models.db import SpiffworkflowBaseDBModel
from tests.spiffworkflow_backend.helpers.base_test import BaseTest
@ -66,17 +67,35 @@ def with_super_admin_user() -> UserModel:
@pytest.fixture()
def setup_process_instances_for_reports() -> list[ProcessInstanceModel]:
def setup_process_instances_for_reports(client: FlaskClient, with_super_admin_user: UserModel) -> list[ProcessInstanceModel]:
"""Setup_process_instances_for_reports."""
user = BaseTest.find_or_create_user()
user = with_super_admin_user
process_group_id = "runs_without_input"
process_model_id = "sample"
load_test_spec(process_group_id=process_group_id, process_model_id=process_model_id)
bpmn_file_name = "sample.bpmn"
bpmn_file_location = "sample"
process_model_identifier = BaseTest().basic_test_setup(
client,
with_super_admin_user,
process_group_id=process_group_id,
process_model_id=process_model_id,
# bpmn_file_name=bpmn_file_name,
bpmn_file_location=bpmn_file_location
)
# BaseTest().create_process_group(
# client=client, user=user, process_group_id=process_group_id, display_name=process_group_id
# )
# process_model_id = "runs_without_input/sample"
# load_test_spec(
# process_model_id=f"{process_group_id}/{process_model_id}",
# process_model_source_directory="sample"
# )
process_instances = []
for data in [kay(), ray(), jay()]:
process_instance = ProcessInstanceService.create_process_instance(
process_group_identifier=process_group_id,
process_model_identifier=process_model_id,
# process_group_identifier=process_group_id,
process_model_identifier=process_model_identifier,
user=user,
)
processor = ProcessInstanceProcessor(process_instance)

View File

@ -1,3 +1,5 @@
from __future__ import with_statement
import logging
from logging.config import fileConfig

View File

@ -1,8 +1,8 @@
"""empty message
Revision ID: 7c12964efde1
Revision ID: 50dd2e016d94
Revises:
Create Date: 2022-11-08 07:48:44.265652
Create Date: 2022-11-08 16:28:18.991635
"""
from alembic import op
@ -10,7 +10,7 @@ import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '7c12964efde1'
revision = '50dd2e016d94'
down_revision = None
branch_labels = None
depends_on = None
@ -95,7 +95,7 @@ def upgrade():
)
op.create_table('process_instance',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('process_model_identifier', sa.String(length=50), nullable=False),
sa.Column('process_model_identifier', sa.String(length=255), nullable=False),
sa.Column('process_group_identifier', sa.String(length=50), nullable=False),
sa.Column('process_initiator_id', sa.Integer(), nullable=False),
sa.Column('bpmn_json', sa.JSON(), nullable=True),

View File

@ -286,21 +286,14 @@ paths:
schema:
$ref: "#/components/schemas/ProcessModel"
/process-models/{process_group_id}/{process_model_id}/files:
/process-models/{modified_process_model_id}/files:
parameters:
- name: process_group_id
- name: modified_process_model_id
in: path
required: true
description: The group containing the models we want to return
description: The process_model_id, modified to replace slashes (/)
schema:
type: string
- name: process_model_id
in: path
required: true
description: The unique id of an existing process model to validate.
schema:
type: string
# add_file
post:
operationId: spiffworkflow_backend.routes.process_api_blueprint.add_file
summary: Add a new workflow spec file
@ -322,36 +315,15 @@ paths:
application/json:
schema:
$ref: "#/components/schemas/File"
# get:
# operationId: spiffworkflow_backend.api.process_api_blueprint.get_files
# summary: Provide a list of workflow spec files for the given workflow_spec_id. IMPORTANT, only includes metadata, not the file content.
# tags:
# - Process Model Files
# responses:
# '200':
# description: An array of file descriptions (not the file content)
# content:
# application/json:
# schema:
# type: array
# items:
# $ref: "#/components/schemas/File"
/process-models/{process_group_id}/{process_model_id}:
/process-models/{modified_process_model_identifier}:
parameters:
- name: process_group_id
- name: modified_process_model_identifier
in: path
required: true
description: The unique id of an existing process group
description: the modified process model id
schema:
type: string
- name: process_model_id
in: path
required: true
description: The unique id of an existing process model.
schema:
type: string
# process_model_show
get:
operationId: spiffworkflow_backend.routes.process_api_blueprint.process_model_show
summary: Returns a single process model
@ -364,22 +336,9 @@ paths:
application/json:
schema:
$ref: "#/components/schemas/ProcessModel"
# process_model_delete
delete:
operationId: spiffworkflow_backend.routes.process_api_blueprint.process_model_delete
summary: Removes an existing process model
tags:
- Process Models
responses:
"200":
description: The process model has been removed.
content:
application/json:
schema:
$ref: "#/components/schemas/OkTrue"
put:
operationId: spiffworkflow_backend.routes.process_api_blueprint.process_model_update
summary: Modifies an existing process mosel with the given parameters.
summary: Modifies an existing process model with the given parameters.
tags:
- Process Models
requestBody:
@ -394,15 +353,21 @@ paths:
application/json:
schema:
$ref: "#/components/schemas/ProcessModel"
delete:
operationId: spiffworkflow_backend.routes.process_api_blueprint.process_model_delete
summary: Removes an existing process model
tags:
- Process Models
responses:
"200":
description: The process model has been removed.
content:
application/json:
schema:
$ref: "#/components/schemas/OkTrue"
/process-instances:
parameters:
- name: process_group_identifier
in: query
required: false
description: The unique id of an existing process group
schema:
type: string
- name: process_model_identifier
in: query
required: false
@ -548,15 +513,9 @@ paths:
schema:
$ref: "#/components/schemas/Workflow"
/process-models/{process_group_id}/{process_model_id}/process-instances:
/process-models/{modified_process_model_id}/process-instances:
parameters:
- name: process_group_id
in: path
required: true
description: The unique id of an existing process group
schema:
type: string
- name: process_model_id
- name: modified_process_model_id
in: path
required: true
description: The unique id of an existing process model.
@ -576,18 +535,33 @@ paths:
schema:
$ref: "#/components/schemas/Workflow"
/process-models/{process_group_id}/{process_model_id}/process-instances/{process_instance_id}:
/process-instances/{process_instance_id}:
parameters:
- name: process_group_id
- name: process_instance_id
in: path
required: true
description: The unique id of an existing process group
description: The unique id of an existing process instance.
schema:
type: string
- name: process_model_id
type: integer
delete:
operationId: spiffworkflow_backend.routes.process_api_blueprint.process_instance_delete
summary: Deletes a single process instance
tags:
- Process Instances
responses:
"200":
description: The process instance was deleted.
content:
application/json:
schema:
$ref: "#/components/schemas/OkTrue"
/process-models/{modified_process_model_identifier}/process-instances/{process_instance_id}:
parameters:
- name: modified_process_model_identifier
in: path
required: true
description: The unique id of an existing process model.
description: The unique id of an existing process model
schema:
type: string
- name: process_instance_id
@ -608,34 +582,9 @@ paths:
application/json:
schema:
$ref: "#/components/schemas/Workflow"
# process_instance_delete
delete:
operationId: spiffworkflow_backend.routes.process_api_blueprint.process_instance_delete
summary: Deletes a single process instance
tags:
- Process Instances
responses:
"200":
description: The process instance was deleted.
content:
application/json:
schema:
$ref: "#/components/schemas/OkTrue"
/process-models/{process_group_id}/{process_model_id}/process-instances/{process_instance_id}/run:
/process-instances/{process_instance_id}/run:
parameters:
- name: process_group_id
in: path
required: true
description: The unique id of an existing process group
schema:
type: string
- name: process_model_id
in: path
required: true
description: The unique id of an existing process model.
schema:
type: string
- name: process_instance_id
in: path
required: true
@ -662,20 +611,8 @@ paths:
schema:
$ref: "#/components/schemas/Workflow"
/process-models/{process_group_id}/{process_model_id}/process-instances/{process_instance_id}/terminate:
/process-instances/{process_instance_id}/terminate:
parameters:
- name: process_group_id
in: path
required: true
description: The unique id of an existing process group
schema:
type: string
- name: process_model_id
in: path
required: true
description: The unique id of an existing process model.
schema:
type: string
- name: process_instance_id
in: path
required: true
@ -695,20 +632,8 @@ paths:
schema:
$ref: "#/components/schemas/OkTrue"
/process-models/{process_group_id}/{process_model_id}/process-instances/{process_instance_id}/suspend:
/process-instances/{process_instance_id}/suspend:
parameters:
- name: process_group_id
in: path
required: true
description: The unique id of an existing process group
schema:
type: string
- name: process_model_id
in: path
required: true
description: The unique id of an existing process model.
schema:
type: string
- name: process_instance_id
in: path
required: true
@ -728,20 +653,8 @@ paths:
schema:
$ref: "#/components/schemas/OkTrue"
/process-models/{process_group_id}/{process_model_id}/process-instances/{process_instance_id}/resume:
/process-instances/{process_instance_id}/resume:
parameters:
- name: process_group_id
in: path
required: true
description: The unique id of an existing process group
schema:
type: string
- name: process_model_id
in: path
required: true
description: The unique id of an existing process model.
schema:
type: string
- name: process_instance_id
in: path
required: true
@ -789,6 +702,33 @@ paths:
type: array
items:
$ref: "#/components/schemas/Workflow"
/process-models/{process_group_id}/{process_model_id}/process-instances/reports:
parameters:
- name: process_group_id
in: path
required: true
description: The unique id of an existing process group
schema:
type: string
- name: process_model_id
in: path
required: true
description: The unique id of an existing process model.
schema:
type: string
- name: page
in: query
required: false
description: The page number to return. Defaults to page 1.
schema:
type: integer
- name: per_page
in: query
required: false
description: The page number to return. Defaults to page 1.
schema:
type: integer
post:
operationId: spiffworkflow_backend.routes.process_api_blueprint.process_instance_report_create
summary: Returns all process instance reports for process model
@ -836,6 +776,39 @@ paths:
type: array
items:
$ref: "#/components/schemas/Workflow"
/process-models/{process_group_id}/{process_model_id}/process-instances/reports/{report_identifier}:
parameters:
- name: process_group_id
in: path
required: true
description: The unique id of an existing process group
schema:
type: string
- name: process_model_id
in: path
required: true
description: The unique id of an existing process model.
schema:
type: string
- name: report_identifier
in: path
required: true
description: The unique id of an existing report
schema:
type: string
- name: page
in: query
required: false
description: The page number to return. Defaults to page 1.
schema:
type: integer
- name: per_page
in: query
required: false
description: The page number to return. Defaults to page 1.
schema:
type: integer
put:
operationId: spiffworkflow_backend.routes.process_api_blueprint.process_instance_report_update
summary: Updates a process instance report
@ -861,18 +834,12 @@ paths:
schema:
$ref: "#/components/schemas/OkTrue"
/process-models/{process_group_id}/{process_model_id}/files/{file_name}:
/process-models/{modified_process_model_id}/files/{file_name}:
parameters:
- name: process_group_id
- name: modified_process_model_id
in: path
required: true
description: The unique id of an existing process group
schema:
type: string
- name: process_model_id
in: path
required: true
description: The unique id of an existing process model to validate.
description: The modified process model id
schema:
type: string
- name: file_name
@ -881,7 +848,6 @@ paths:
description: The id of the spec file
schema:
type: string
# get_file
get:
operationId: spiffworkflow_backend.routes.process_api_blueprint.get_file
summary: Returns metadata about the file
@ -1179,20 +1145,8 @@ paths:
schema:
$ref: "#/components/schemas/Workflow"
/process-models/{process_group_id}/{process_model_id}/process-instances/{process_instance_id}/logs:
/process-instances/{process_instance_id}/logs:
parameters:
- name: process_group_id
in: path
required: true
description: The unique id of an existing process group
schema:
type: string
- name: process_model_id
in: path
required: true
description: The unique id of an existing process model.
schema:
type: string
- name: process_instance_id
in: path
required: true

View File

@ -41,3 +41,15 @@ permissions:
users: [testuser4]
allowed_permissions: [create, read, update, delete]
uri: /v1.0/process-models/finance/*
finance-admin-model-lanes:
groups: ["Finance Team"]
users: [testuser4]
allowed_permissions: [create, read, update, delete]
uri: /v1.0/process-models/finance:model_with_lanes/*
finance-admin-instance-run:
groups: ["Finance Team"]
users: [testuser4]
allowed_permissions: [create, read, update, delete]
uri: /v1.0/process-instances/*

View File

@ -20,6 +20,9 @@ SPIFFWORKFLOW_BACKEND_LOG_LEVEL = environ.get(
# different places and this allows us to know exactly where we are at the start
BPMN_SPEC_ABSOLUTE_DIR = os.path.join(
os.path.dirname(__file__),
"..",
"..",
"..",
"tests",
"spiffworkflow_backend",
"files",

View File

@ -72,7 +72,7 @@ class ProcessInstanceModel(SpiffworkflowBaseDBModel):
__tablename__ = "process_instance"
id: int = db.Column(db.Integer, primary_key=True)
process_model_identifier: str = db.Column(db.String(50), nullable=False, index=True)
process_model_identifier: str = db.Column(db.String(255), nullable=False, index=True)
process_group_identifier: str = db.Column(db.String(50), nullable=False, index=True)
process_initiator_id: int = db.Column(ForeignKey(UserModel.id), nullable=False)
process_initiator = relationship("UserModel")

View File

@ -109,6 +109,9 @@ class ProcessInstanceReportModel(SpiffworkflowBaseDBModel):
def add_fixtures(cls) -> None:
"""Add_fixtures."""
try:
# process_model = ProcessModelService().get_process_model(
# process_model_id="sartography-admin/ticket"
# )
user = UserModel.query.first()
columns = [
{"Header": "id", "accessor": "id"},
@ -235,8 +238,18 @@ class ProcessInstanceReportModel(SpiffworkflowBaseDBModel):
user: UserModel,
) -> ProcessInstanceReportModel:
"""Create_with_attributes."""
# <<<<<<< HEAD
# process_model = ProcessModelService().get_process_model(
# process_model_id=f"{process_model_identifier}"
# )
# process_instance_report = cls(
# identifier=identifier,
# process_group_identifier="process_model.process_group_id",
# process_model_identifier=process_model.id,
# =======
process_instance_report = cls(
identifier=identifier,
# >>>>>>> main
created_by_id=user.id,
report_metadata=report_metadata,
)

View File

@ -29,7 +29,7 @@ class ProcessModelInfo:
id: str
display_name: str
description: str
process_group_id: str = ""
process_group: Any | None = None
primary_file_name: str | None = None
primary_process_id: str | None = None
display_order: int | None = 0
@ -40,7 +40,7 @@ class ProcessModelInfo:
def __post_init__(self) -> None:
"""__post_init__."""
self.sort_index = f"{self.process_group_id}:{self.id}"
self.sort_index = self.id
def __eq__(self, other: Any) -> bool:
"""__eq__."""
@ -66,7 +66,6 @@ class ProcessModelInfoSchema(Schema):
primary_file_name = marshmallow.fields.String(allow_none=True)
primary_process_id = marshmallow.fields.String(allow_none=True)
is_review = marshmallow.fields.Boolean(allow_none=True)
process_group_id = marshmallow.fields.String(allow_none=True)
files = marshmallow.fields.List(marshmallow.fields.Nested("FileSchema"))
fault_or_suspend_on_exception = marshmallow.fields.String()
exception_notification_addresses = marshmallow.fields.List(

View File

@ -137,6 +137,14 @@ def permissions_check(body: Dict[str, Dict[str, list[str]]]) -> flask.wrappers.R
return make_response(jsonify({"results": response_dict}), 200)
def modify_process_model_id(process_model_id: str) -> str:
return process_model_id.replace('/', ':')
def un_modify_modified_process_model_id(modified_process_model_id: str) -> str:
return modified_process_model_id.replace(':', '/')
def process_group_add(body: dict) -> flask.wrappers.Response:
"""Add_process_group."""
process_model_service = ProcessModelService()
@ -216,9 +224,10 @@ def process_model_add(
status_code=400,
)
process_group_id, _ = os.path.split(process_model_info.id)
process_model_service = ProcessModelService()
process_group = process_model_service.get_process_group(
process_model_info.process_group_id
process_group_id
)
if process_group is None:
raise ApiError(
@ -236,17 +245,20 @@ def process_model_add(
def process_model_delete(
process_group_id: str, process_model_id: str
modified_process_model_identifier: str
) -> flask.wrappers.Response:
"""Process_model_delete."""
ProcessModelService().process_model_delete(process_model_id)
process_model_identifier = modified_process_model_identifier.replace(":", "/")
# process_model_identifier = f"{process_group_id}/{process_model_id}"
ProcessModelService().process_model_delete(process_model_identifier)
return Response(json.dumps({"ok": True}), status=200, mimetype="application/json")
def process_model_update(
process_group_id: str, process_model_id: str, body: Dict[str, Union[str, bool, int]]
modified_process_model_identifier: str, body: Dict[str, Union[str, bool, int]]
) -> Any:
"""Process_model_update."""
process_model_identifier = modified_process_model_identifier.replace(":", "/")
body_include_list = ["display_name", "primary_file_name", "primary_process_id", "description"]
body_filtered = {
include_item: body[include_item]
@ -254,14 +266,19 @@ def process_model_update(
if include_item in body
}
process_model = get_process_model(process_model_id, process_group_id)
# process_model_identifier = f"{process_group_id}/{process_model_id}"
process_model = get_process_model(process_model_identifier)
ProcessModelService().update_spec(process_model, body_filtered)
return ProcessModelInfoSchema().dump(process_model)
def process_model_show(process_group_id: str, process_model_id: str) -> Any:
def process_model_show(modified_process_model_identifier: str) -> Any:
"""Process_model_show."""
process_model = get_process_model(process_model_id, process_group_id)
process_model_identifier = modified_process_model_identifier.replace(":", "/")
# process_model_identifier = f"{process_group_id}/{process_model_id}"
process_model = get_process_model(process_model_identifier)
# TODO: Temporary. Should not need the next line once models have correct ids
# process_model.id = process_model_identifier
files = sorted(SpecFileService.get_files(process_model))
process_model.files = files
for file in process_model.files:
@ -298,15 +315,16 @@ def process_model_list(
return Response(json.dumps(response_json), status=200, mimetype="application/json")
def get_file(process_group_id: str, process_model_id: str, file_name: str) -> Any:
def get_file(modified_process_model_id: str, file_name: str) -> Any:
"""Get_file."""
process_model = get_process_model(process_model_id, process_group_id)
process_model_identifier = modified_process_model_id.replace(":", "/")
process_model = get_process_model(process_model_identifier)
files = SpecFileService.get_files(process_model, file_name)
if len(files) == 0:
raise ApiError(
error_code="unknown file",
message=f"No information exists for file {file_name}"
f" it does not exist in workflow {process_model_id}.",
f" it does not exist in workflow {process_model_identifier}.",
status_code=404,
)
@ -314,15 +332,17 @@ def get_file(process_group_id: str, process_model_id: str, file_name: str) -> An
file_contents = SpecFileService.get_data(process_model, file.name)
file.file_contents = file_contents
file.process_model_id = process_model.id
file.process_group_id = process_model.process_group_id
# file.process_group_id = process_model.process_group_id
return FileSchema().dump(file)
def process_model_file_update(
process_group_id: str, process_model_id: str, file_name: str
modified_process_model_id: str, file_name: str
) -> flask.wrappers.Response:
"""Process_model_file_update."""
process_model = get_process_model(process_model_id, process_group_id)
process_model_identifier = modified_process_model_id.replace(":", "/")
# process_model_identifier = f"{process_group_id}/{process_model_id}"
process_model = get_process_model(process_model_identifier)
request_file = get_file_from_request()
request_file_contents = request_file.stream.read()
@ -337,7 +357,7 @@ def process_model_file_update(
if current_app.config["GIT_COMMIT_ON_SAVE"]:
git_output = GitService.commit(
message=f"User: {g.user.username} clicked save for {process_group_id}/{process_model_id}/{file_name}"
message=f"User: {g.user.username} clicked save for {process_model_identifier}/{file_name}"
)
current_app.logger.info(f"git output: {git_output}")
else:
@ -347,10 +367,11 @@ def process_model_file_update(
def process_model_file_delete(
process_group_id: str, process_model_id: str, file_name: str
modified_process_model_id: str, file_name: str
) -> flask.wrappers.Response:
"""Process_model_file_delete."""
process_model = get_process_model(process_model_id, process_group_id)
process_model_identifier = modified_process_model_id.replace(":", "/")
process_model = get_process_model(process_model_identifier)
try:
SpecFileService.delete_file(process_model, file_name)
except FileNotFoundError as exception:
@ -365,9 +386,10 @@ def process_model_file_delete(
return Response(json.dumps({"ok": True}), status=200, mimetype="application/json")
def add_file(process_group_id: str, process_model_id: str) -> flask.wrappers.Response:
def add_file(modified_process_model_id: str) -> flask.wrappers.Response:
"""Add_file."""
process_model = get_process_model(process_model_id, process_group_id)
process_model_identifier = modified_process_model_id.replace(":", "/")
process_model = get_process_model(process_model_identifier)
request_file = get_file_from_request()
if not request_file.filename:
raise ApiError(
@ -382,18 +404,18 @@ def add_file(process_group_id: str, process_model_id: str) -> flask.wrappers.Res
file_contents = SpecFileService.get_data(process_model, file.name)
file.file_contents = file_contents
file.process_model_id = process_model.id
file.process_group_id = process_model.process_group_id
return Response(
json.dumps(FileSchema().dump(file)), status=201, mimetype="application/json"
)
def process_instance_create(
process_group_id: str, process_model_id: str
modified_process_model_id: str
) -> flask.wrappers.Response:
"""Create_process_instance."""
process_model_identifier = un_modify_modified_process_model_id(modified_process_model_id)
process_instance = ProcessInstanceService.create_process_instance(
process_model_id, g.user, process_group_identifier=process_group_id
process_model_identifier, g.user
)
return Response(
json.dumps(ProcessInstanceModelSchema().dump(process_instance)),
@ -403,8 +425,6 @@ def process_instance_create(
def process_instance_run(
process_group_id: str,
process_model_id: str,
process_instance_id: int,
do_engine_steps: bool = True,
) -> flask.wrappers.Response:
@ -446,10 +466,7 @@ def process_instance_run(
def process_instance_terminate(
process_group_id: str,
process_model_id: str,
process_instance_id: int,
do_engine_steps: bool = True,
) -> flask.wrappers.Response:
"""Process_instance_run."""
process_instance = ProcessInstanceService().get_process_instance(
@ -461,8 +478,6 @@ def process_instance_terminate(
def process_instance_suspend(
process_group_id: str,
process_model_id: str,
process_instance_id: int,
) -> flask.wrappers.Response:
"""Process_instance_suspend."""
@ -475,8 +490,6 @@ def process_instance_suspend(
def process_instance_resume(
process_group_id: str,
process_model_id: str,
process_instance_id: int,
) -> flask.wrappers.Response:
"""Process_instance_resume."""
@ -489,8 +502,6 @@ def process_instance_resume(
def process_instance_log_list(
process_group_id: str,
process_model_id: str,
process_instance_id: int,
page: int = 1,
per_page: int = 100,
@ -651,7 +662,6 @@ def message_start(
def process_instance_list(
process_group_identifier: Optional[str] = None,
process_model_identifier: Optional[str] = None,
page: int = 1,
per_page: int = 100,
@ -662,10 +672,11 @@ def process_instance_list(
process_status: Optional[str] = None,
) -> flask.wrappers.Response:
"""Process_instance_list."""
# process_model_identifier = un_modify_modified_process_model_id(modified_process_model_identifier)
process_instance_query = ProcessInstanceModel.query
if process_model_identifier is not None and process_group_identifier is not None:
if process_model_identifier is not None:
process_model = get_process_model(
process_model_identifier, process_group_identifier
f"{process_model_identifier}",
)
process_instance_query = process_instance_query.filter_by(
@ -743,12 +754,13 @@ def process_instance_list(
def process_instance_show(
process_group_id: str, process_model_id: str, process_instance_id: int
modified_process_model_identifier: str, process_instance_id: int
) -> flask.wrappers.Response:
"""Create_process_instance."""
process_model_identifier = modified_process_model_identifier.replace(":", "/")
process_instance = find_process_instance_by_id_or_raise(process_instance_id)
current_version_control_revision = GitService.get_current_revision()
process_model = get_process_model(process_model_id, process_group_id)
process_model = get_process_model(process_model_identifier)
if process_model.primary_file_name:
if (
@ -768,7 +780,7 @@ def process_instance_show(
def process_instance_delete(
process_group_id: str, process_model_id: str, process_instance_id: int
process_instance_id: int
) -> flask.wrappers.Response:
"""Create_process_instance."""
process_instance = find_process_instance_by_id_or_raise(process_instance_id)
@ -888,6 +900,7 @@ def process_instance_report_show(
per_page: int = 100,
) -> flask.wrappers.Response:
"""Process_instance_list."""
process_instances = ProcessInstanceModel.query.order_by( # .filter_by(process_model_identifier=process_model.id)
ProcessInstanceModel.start_in_seconds.desc(), ProcessInstanceModel.id.desc() # type: ignore
).paginate(
@ -1008,7 +1021,6 @@ def task_show(process_instance_id: int, task_id: str) -> flask.wrappers.Response
process_model = get_process_model(
process_instance.process_model_identifier,
process_instance.process_group_identifier,
)
form_schema_file_name = ""
@ -1160,7 +1172,7 @@ def task_submit(
def script_unit_test_create(
process_group_id: str, process_model_id: str, body: Dict[str, Union[str, bool, int]]
) -> flask.wrappers.Response:
"""Script_unit_test_run."""
"""Script_unit_test_create."""
bpmn_task_identifier = _get_required_parameter_or_raise(
"bpmn_task_identifier", body
)
@ -1169,7 +1181,8 @@ def script_unit_test_create(
"expected_output_json", body
)
process_model = get_process_model(process_model_id, process_group_id)
process_model_identifier = f"{process_group_id}/{process_model_id}"
process_model = get_process_model(process_model_identifier)
file = SpecFileService.get_files(process_model, process_model.primary_file_name)[0]
if file is None:
raise ApiError(
@ -1279,12 +1292,12 @@ def get_file_from_request() -> Any:
return request_file
def get_process_model(process_model_id: str, process_group_id: str) -> ProcessModelInfo:
def get_process_model(process_model_id: str) -> ProcessModelInfo:
"""Get_process_model."""
process_model = None
try:
process_model = ProcessModelService().get_process_model(
process_model_id, group_id=process_group_id
process_model_id
)
except ProcessEntityNotFoundError as exception:
raise (

View File

@ -46,6 +46,7 @@ def verify_token(
ApiError: If not on production and token is not valid, returns an 'invalid_token' 403 error.
If on production and user is not authenticated, returns a 'no_user' 403 error.
"""
user_info = None
if not force_run and AuthorizationService.should_disable_auth_for_request():
return None
@ -104,6 +105,7 @@ def verify_token(
raise ApiError(
error_code="fail_get_user_info",
message="Cannot get user info from token",
status_code=401
) from e
if (

View File

@ -35,7 +35,7 @@ class ErrorHandlingService:
) -> None:
"""On unhandled exceptions, set instance.status based on model.fault_or_suspend_on_exception."""
process_model = ProcessModelService().get_process_model(
_processor.process_model_identifier, _processor.process_group_identifier
_processor.process_model_identifier
)
if process_model.fault_or_suspend_on_exception == "suspend":
self.set_instance_status(

View File

@ -54,18 +54,20 @@ class FileSystemService:
@staticmethod
def process_group_path_for_spec(spec: ProcessModelInfo) -> str:
"""Category_path_for_spec."""
return FileSystemService.process_group_path(spec.process_group_id)
process_group_id, _ = os.path.split(spec.id)
return FileSystemService.process_group_path(process_group_id)
@staticmethod
def workflow_path(spec: ProcessModelInfo) -> str:
"""Workflow_path."""
process_group_path = FileSystemService.process_group_path_for_spec(spec)
return os.path.join(process_group_path, spec.id)
process_model_path = os.path.join(FileSystemService.root_path(), spec.id)
# process_group_path = FileSystemService.process_group_path_for_spec(spec)
return process_model_path
@staticmethod
def full_path_to_process_model_file(spec: ProcessModelInfo, file_name: str) -> str:
def full_path_to_process_model_file(spec: ProcessModelInfo) -> str:
"""Full_path_to_process_model_file."""
return os.path.join(FileSystemService.workflow_path(spec), file_name)
return os.path.join(FileSystemService.workflow_path(spec), spec.primary_file_name)
def next_display_order(self, spec: ProcessModelInfo) -> int:
"""Next_display_order."""

View File

@ -120,7 +120,6 @@ class MessageService:
process_instance_receive = ProcessInstanceService.create_process_instance(
message_triggerable_process_model.process_model_identifier,
user,
process_group_identifier=message_triggerable_process_model.process_group_identifier,
)
processor_receive = ProcessInstanceProcessor(process_instance_receive)
processor_receive.do_engine_steps(save=False)

View File

@ -307,8 +307,7 @@ class ProcessInstanceProcessor:
bpmn_process_spec,
subprocesses,
) = ProcessInstanceProcessor.get_process_model_and_subprocesses(
process_instance_model.process_model_identifier,
process_instance_model.process_group_identifier,
process_instance_model.process_model_identifier
)
else:
bpmn_json_length = len(process_instance_model.bpmn_json.encode("utf-8"))
@ -359,7 +358,7 @@ class ProcessInstanceProcessor:
check_sub_specs(test_spec, 5)
self.process_model_identifier = process_instance_model.process_model_identifier
self.process_group_identifier = process_instance_model.process_group_identifier
# self.process_group_identifier = process_instance_model.process_group_identifier
try:
self.bpmn_process_instance = self.__get_bpmn_process_instance(
@ -394,17 +393,17 @@ class ProcessInstanceProcessor:
@classmethod
def get_process_model_and_subprocesses(
cls, process_model_identifier: str, process_group_identifier: str
cls, process_model_identifier: str
) -> Tuple[BpmnProcessSpec, IdToBpmnProcessSpecMapping]:
"""Get_process_model_and_subprocesses."""
process_model_info = ProcessModelService().get_process_model(
process_model_identifier, process_group_identifier
process_model_identifier
)
if process_model_info is None:
raise (
ApiError(
"process_model_not_found",
f"The given process model was not found: {process_group_identifier}/{process_model_identifier}.",
f"The given process model was not found: {process_model_identifier}.",
)
)
spec_files = SpecFileService.get_files(process_model_info)
@ -412,12 +411,11 @@ class ProcessInstanceProcessor:
@classmethod
def get_bpmn_process_instance_from_process_model(
cls, process_model_identifier: str, process_group_identifier: str
cls, process_model_identifier: str
) -> BpmnWorkflow:
"""Get_all_bpmn_process_identifiers_for_process_model."""
(bpmn_process_spec, subprocesses) = cls.get_process_model_and_subprocesses(
process_model_identifier,
process_group_identifier,
)
return cls.get_bpmn_process_instance_from_workflow_spec(
bpmn_process_spec, subprocesses
@ -698,7 +696,7 @@ class ProcessInstanceProcessor:
etree_element,
)
return FileSystemService.full_path_to_process_model_file(
process_model, process_model.primary_file_name
process_model
)
return None

View File

@ -33,7 +33,6 @@ class ProcessInstanceService:
def create_process_instance(
process_model_identifier: str,
user: UserModel,
process_group_identifier: Optional[str] = None,
) -> ProcessInstanceModel:
"""Get_process_instance_from_spec."""
current_git_revision = GitService.get_current_revision()
@ -41,7 +40,7 @@ class ProcessInstanceService:
status=ProcessInstanceStatus.not_started.value,
process_initiator=user,
process_model_identifier=process_model_identifier,
process_group_identifier=process_group_identifier,
process_group_identifier="",
start_in_seconds=round(time.time()),
bpmn_version_control_type="git",
bpmn_version_control_identifier=current_git_revision,
@ -98,7 +97,7 @@ class ProcessInstanceService:
next_task=None,
# navigation=navigation,
process_model_identifier=processor.process_model_identifier,
process_group_identifier=processor.process_group_identifier,
process_group_identifier="",
# total_tasks=len(navigation),
completed_tasks=processor.process_instance_model.completed_tasks,
updated_at_in_seconds=processor.process_instance_model.updated_at_in_seconds,

View File

@ -34,6 +34,18 @@ class ProcessModelService(FileSystemService):
GROUP_SCHEMA = ProcessGroupSchema()
WF_SCHEMA = ProcessModelInfoSchema()
def is_group(self, path):
group_json_path = os.path.join(path, self.CAT_JSON_FILE)
if os.path.exists(group_json_path):
return True
return False
def is_model(self, path):
model_json_path = os.path.join(path, self.WF_JSON_FILE)
if os.path.exists(model_json_path):
return True
return False
@staticmethod
def get_batch(
items: list[T],
@ -62,7 +74,7 @@ class ProcessModelService(FileSystemService):
def save_process_model(self, process_model: ProcessModelInfo) -> None:
"""Save_process_model."""
spec_path = self.workflow_path(process_model)
spec_path = os.path.join(FileSystemService.root_path(), process_model.id)
os.makedirs(spec_path, exist_ok=True)
json_path = os.path.join(spec_path, self.WF_JSON_FILE)
with open(json_path, "w") as wf_json:
@ -81,7 +93,8 @@ class ProcessModelService(FileSystemService):
message=f"We cannot delete the model `{process_model_id}`, there are existing instances that depend on it.",
)
process_model = self.get_process_model(process_model_id)
path = self.workflow_path(process_model)
# path = self.workflow_path(process_model)
path = f"{FileSystemService.root_path()}/{process_model_id}"
shutil.rmtree(path)
@classmethod
@ -89,36 +102,43 @@ class ProcessModelService(FileSystemService):
cls, relative_path: str
) -> ProcessModelInfo:
"""Get_process_model_from_relative_path."""
process_group_identifier = os.path.dirname(relative_path)
process_group_identifier, _ = os.path.split(relative_path)
process_group = cls().get_process_group(process_group_identifier)
path = os.path.join(FileSystemService.root_path(), relative_path)
return cls().__scan_spec(path, process_group=process_group)
def get_process_model(
self, process_model_id: str, group_id: Optional[str] = None
self, process_model_id: str
) -> ProcessModelInfo:
"""Get a process model from a model and group id."""
"""Get a process model from a model and group id.
process_model_id is the full path to the model--including groups"""
if not os.path.exists(FileSystemService.root_path()):
raise ProcessEntityNotFoundError("process_model_not_found")
raise ProcessEntityNotFoundError("process_model_root_not_found")
if group_id is not None:
process_group = self.get_process_group(group_id)
if process_group is not None:
for process_model in process_group.process_models:
if process_model_id == process_model.id:
return process_model
with os.scandir(FileSystemService.root_path()) as process_group_dirs:
for item in process_group_dirs:
process_group_dir = item
if item.is_dir():
with os.scandir(item.path) as spec_dirs:
for sd in spec_dirs:
if sd.name == process_model_id:
# Now we have the process_group direcotry, and spec directory
process_group = self.__scan_process_group(
process_group_dir
)
return self.__scan_spec(sd.path, sd.name, process_group)
model_path = os.path.join(FileSystemService.root_path(), process_model_id)
if self.is_model(model_path):
process_model = self.get_process_model_from_relative_path(process_model_id)
return process_model
# group_path, model_id = os.path.split(process_model_id)
# if group_path is not None:
# process_group = self.get_process_group(group_path)
# if process_group is not None:
# for process_model in process_group.process_models:
# if process_model_id == process_model.id:
# return process_model
# with os.scandir(FileSystemService.root_path()) as process_group_dirs:
# for item in process_group_dirs:
# process_group_dir = item
# if item.is_dir():
# with os.scandir(item.path) as spec_dirs:
# for sd in spec_dirs:
# if sd.name == process_model_id:
# # Now we have the process_group directory, and spec directory
# process_group = self.__scan_process_group(
# process_group_dir
# )
# return self.__scan_spec(sd.path, sd.name, process_group)
raise ProcessEntityNotFoundError("process_model_not_found")
def get_process_models(
@ -148,10 +168,22 @@ class ProcessModelService(FileSystemService):
def get_process_group(self, process_group_id: str) -> ProcessGroup:
"""Look for a given process_group, and return it."""
if os.path.exists(FileSystemService.root_path()):
with os.scandir(FileSystemService.root_path()) as directory_items:
for item in directory_items:
if item.is_dir() and item.name == process_group_id:
return self.__scan_process_group(item)
process_group_path = os.path.join(FileSystemService.root_path(), process_group_id)
if self.is_group(process_group_path):
return self.__scan_process_group(process_group_path)
# nested_groups = []
# process_group_dir = os.scandir(process_group_path)
# for item in process_group_dir:
# if self.is_group(item.path):
# nested_group = self.get_process_group(os.path.join(process_group_path, item.path))
# nested_groups.append(nested_group)
# elif self.is_model(item.path):
# print("get_process_group: ")
# return self.__scan_process_group(process_group_path)
# with os.scandir(FileSystemService.root_path()) as directory_items:
# for item in directory_items:
# if item.is_dir() and item.name == process_group_id:
# return self.__scan_process_group(item)
raise ProcessEntityNotFoundError(
"process_group_not_found", f"Process Group Id: {process_group_id}"
@ -202,13 +234,15 @@ class ProcessModelService(FileSystemService):
with os.scandir(FileSystemService.root_path()) as directory_items:
process_groups = []
for item in directory_items:
if item.is_dir() and not item.name[0] == ".":
process_groups.append(self.__scan_process_group(item))
# if item.is_dir() and not item.name[0] == ".":
if item.is_dir() and self.is_group(item):
scanned_process_group = self.__scan_process_group(item.path)
process_groups.append(scanned_process_group)
return process_groups
def __scan_process_group(self, dir_item: os.DirEntry) -> ProcessGroup:
"""Reads the process_group.json file, and any workflow directories."""
cat_path = os.path.join(dir_item.path, self.CAT_JSON_FILE)
def __scan_process_group(self, dir_path: str) -> ProcessGroup:
"""Reads the process_group.json file, and any nested directories."""
cat_path = os.path.join(dir_path, self.CAT_JSON_FILE)
if os.path.exists(cat_path):
with open(cat_path) as cat_json:
data = json.load(cat_json)
@ -216,26 +250,32 @@ class ProcessModelService(FileSystemService):
if process_group is None:
raise ApiError(
error_code="process_group_could_not_be_loaded_from_disk",
message=f"We could not load the process_group from disk from: {dir_item}",
message=f"We could not load the process_group from disk from: {dir_path}",
)
else:
process_group_id = dir_path.replace(FileSystemService.root_path(), '')
process_group = ProcessGroup(
id=dir_item.name,
display_name=dir_item.name,
id=process_group_id,
display_name=process_group_id,
display_order=10000,
admin=False,
)
with open(cat_path, "w") as wf_json:
json.dump(self.GROUP_SCHEMA.dump(process_group), wf_json, indent=4)
with os.scandir(dir_item.path) as workflow_dirs:
with os.scandir(dir_path) as nested_items:
process_group.process_models = []
for item in workflow_dirs:
if item.is_dir():
process_group.process_models.append(
self.__scan_spec(
item.path, item.name, process_group=process_group
for nested_item in nested_items:
if nested_item.is_dir():
# TODO: check whether this is a group or model
if self.is_group(nested_item.path):
# This is a nested group
...
elif self.is_model(nested_item.path):
process_group.process_models.append(
self.__scan_spec(
nested_item.path, nested_item.name, process_group=process_group
)
)
)
process_group.process_models.sort()
return process_group
@ -251,6 +291,8 @@ class ProcessModelService(FileSystemService):
if os.path.exists(spec_path):
with open(spec_path) as wf_json:
data = json.load(wf_json)
if "process_group_id" in data:
data.pop("process_group_id")
spec = ProcessModelInfo(**data)
if spec is None:
raise ApiError(

View File

@ -48,7 +48,8 @@ class SpecFileService(FileSystemService):
extension_filter: str = "",
) -> List[File]:
"""Return all files associated with a workflow specification."""
path = SpecFileService.workflow_path(process_model_info)
# path = SpecFileService.workflow_path(process_model_info)
path = os.path.join(FileSystemService.root_path(), process_model_info.id)
files = SpecFileService._get_files(path, file_name)
if extension_filter != "":
files = list(
@ -105,7 +106,8 @@ class SpecFileService(FileSystemService):
) -> File:
"""Update_file."""
SpecFileService.assert_valid_file_name(file_name)
file_path = SpecFileService.file_path(process_model_info, file_name)
# file_path = SpecFileService.file_path(process_model_info, file_name)
file_path = os.path.join(FileSystemService.root_path(), process_model_info.id, file_name)
SpecFileService.write_file_data_to_system(file_path, binary_data)
file = SpecFileService.to_file_object(file_name, file_path)
@ -129,7 +131,8 @@ class SpecFileService(FileSystemService):
@staticmethod
def get_data(process_model_info: ProcessModelInfo, file_name: str) -> bytes:
"""Get_data."""
file_path = SpecFileService.file_path(process_model_info, file_name)
# file_path = SpecFileService.file_path(process_model_info, file_name)
file_path = os.path.join(FileSystemService.root_path(), process_model_info.id, file_name)
if not os.path.exists(file_path):
raise ProcessModelFileNotFoundError(
f"No file found with name {file_name} in {process_model_info.display_name}"
@ -163,7 +166,8 @@ class SpecFileService(FileSystemService):
# for lf in lookup_files:
# session.query(LookupDataModel).filter_by(lookup_file_model_id=lf.id).delete()
# session.query(LookupFileModel).filter_by(id=lf.id).delete()
file_path = SpecFileService.file_path(spec, file_name)
# file_path = SpecFileService.file_path(spec, file_name)
file_path = os.path.join(FileSystemService.root_path(), spec.id, file_name)
os.remove(file_path)
@staticmethod
@ -367,9 +371,8 @@ class SpecFileService(FileSystemService):
process_model_info: ProcessModelInfo, bpmn_file_name: str, et_root: _Element
) -> None:
"""Store_bpmn_process_identifiers."""
relative_process_model_path = SpecFileService.process_model_relative_path(
process_model_info
)
relative_process_model_path = process_model_info.id
relative_bpmn_file_path = os.path.join(
relative_process_model_path, bpmn_file_name
)
@ -465,7 +468,7 @@ class SpecFileService(FileSystemService):
message_triggerable_process_model = MessageTriggerableProcessModel(
message_model_id=message_model.id,
process_model_identifier=process_model_info.id,
process_group_identifier=process_model_info.process_group_id,
process_group_identifier="process_group_identifier",
)
db.session.add(message_triggerable_process_model)
db.session.commit()
@ -473,8 +476,8 @@ class SpecFileService(FileSystemService):
if (
message_triggerable_process_model.process_model_identifier
!= process_model_info.id
or message_triggerable_process_model.process_group_identifier
!= process_model_info.process_group_id
# or message_triggerable_process_model.process_group_identifier
# != process_model_info.process_group_id
):
raise ValidationException(
"Message model is already used to start process model"

View File

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:spiffworkflow="http://spiffworkflow.org/bpmn/schema/1.0/core" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_96f6665" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.0.0-dev">
<bpmn:process id="Proccess_ManualTask" name="Manual Task" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>Flow_1xlck7g</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:sequenceFlow id="Flow_1xlck7g" sourceRef="StartEvent_1" targetRef="Activity_Hello" />
<bpmn:endEvent id="Event_0ia26nb">
<bpmn:incoming>Flow_0nnh2x9</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_0nnh2x9" sourceRef="Activity_Hello" targetRef="Event_0ia26nb" />
<bpmn:manualTask id="Activity_Hello" name="Hello">
<bpmn:extensionElements>
<spiffworkflow:instructionsForEndUser>## Hello</spiffworkflow:instructionsForEndUser>
</bpmn:extensionElements>
<bpmn:incoming>Flow_1xlck7g</bpmn:incoming>
<bpmn:outgoing>Flow_0nnh2x9</bpmn:outgoing>
</bpmn:manualTask>
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Proccess_ManualTask">
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="179" y="159" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_0ia26nb_di" bpmnElement="Event_0ia26nb">
<dc:Bounds x="432" y="159" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1rcj16n_di" bpmnElement="Activity_Hello">
<dc:Bounds x="270" y="137" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_1xlck7g_di" bpmnElement="Flow_1xlck7g">
<di:waypoint x="215" y="177" />
<di:waypoint x="270" y="177" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0nnh2x9_di" bpmnElement="Flow_0nnh2x9">
<di:waypoint x="370" y="177" />
<di:waypoint x="432" y="177" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

View File

@ -1,67 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_1kbzkan" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="5.0.0">
<bpmn:process id="Process_SimpleScript" name="Simple Script" isExecutable="true">
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:spiffworkflow="http://spiffworkflow.org/bpmn/schema/1.0/core" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_96f6665" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.0.0-dev">
<bpmn:process id="Proccess_SimpleScript" name="Simple Script" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>Flow_1k9q28c</bpmn:outgoing>
<bpmn:outgoing>Flow_0r3ua0i</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:sequenceFlow id="Flow_1k9q28c" sourceRef="StartEvent_1" targetRef="Activity_RunScript" />
<bpmn:sequenceFlow id="Flow_1fviiob" sourceRef="Activity_RunScript" targetRef="Activity_DisplayData" />
<bpmn:endEvent id="Event_1fep863">
<bpmn:incoming>Flow_10610n2</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_10610n2" sourceRef="Activity_DisplayData" targetRef="Event_1fep863" />
<bpmn:scriptTask id="Activity_RunScript" name="Run Script">
<bpmn:incoming>Flow_1k9q28c</bpmn:incoming>
<bpmn:outgoing>Flow_1fviiob</bpmn:outgoing>
<bpmn:sequenceFlow id="Flow_0r3ua0i" sourceRef="StartEvent_1" targetRef="Activity_SetInitialData" />
<bpmn:scriptTask id="Activity_SetInitialData" name="Set Initial Data">
<bpmn:incoming>Flow_0r3ua0i</bpmn:incoming>
<bpmn:outgoing>Flow_19g4f88</bpmn:outgoing>
<bpmn:script>a = 1
b = 2
c = a + b
norris=fact_service(type='norris')</bpmn:script>
b = 2</bpmn:script>
</bpmn:scriptTask>
<bpmn:sequenceFlow id="Flow_19g4f88" sourceRef="Activity_SetInitialData" targetRef="Activity_CalculateNewData" />
<bpmn:scriptTask id="Activity_CalculateNewData" name="Calculate New Data">
<bpmn:extensionElements>
<spiffworkflow:unitTests>
<spiffworkflow:unitTest id="ScriptUnitTest_SimpleScript">
<spiffworkflow:inputJson>{'a': 1, 'b': 2}</spiffworkflow:inputJson>
<spiffworkflow:expectedOutputJson>{'a': 1, 'b': 2, 'c': 3}</spiffworkflow:expectedOutputJson>
</spiffworkflow:unitTest>
</spiffworkflow:unitTests>
</bpmn:extensionElements>
<bpmn:incoming>Flow_19g4f88</bpmn:incoming>
<bpmn:outgoing>Flow_152cqfw</bpmn:outgoing>
<bpmn:script>c = a + b</bpmn:script>
</bpmn:scriptTask>
<bpmn:sequenceFlow id="Flow_152cqfw" sourceRef="Activity_CalculateNewData" targetRef="Activity_DisplayData" />
<bpmn:manualTask id="Activity_DisplayData" name="Display Data">
<bpmn:documentation>## Display Data
<bpmn:extensionElements>
<spiffworkflow:instructionsForEndUser>## Data
### a
### A
{{ a }}
### b
### B
{{ b }}
### c
{{ c }}</bpmn:documentation>
<bpmn:incoming>Flow_1fviiob</bpmn:incoming>
<bpmn:outgoing>Flow_10610n2</bpmn:outgoing>
### C
{{ c }}</spiffworkflow:instructionsForEndUser>
</bpmn:extensionElements>
<bpmn:incoming>Flow_152cqfw</bpmn:incoming>
<bpmn:outgoing>Flow_1vqk60p</bpmn:outgoing>
</bpmn:manualTask>
<bpmn:endEvent id="Event_19fiqu4">
<bpmn:incoming>Flow_1vqk60p</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_1vqk60p" sourceRef="Activity_DisplayData" targetRef="Event_19fiqu4" />
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_SimpleScript">
<bpmndi:BPMNEdge id="Flow_10610n2_di" bpmnElement="Flow_10610n2">
<di:waypoint x="530" y="117" />
<di:waypoint x="592" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1fviiob_di" bpmnElement="Flow_1fviiob">
<di:waypoint x="370" y="117" />
<di:waypoint x="430" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1k9q28c_di" bpmnElement="Flow_1k9q28c">
<di:waypoint x="215" y="117" />
<di:waypoint x="270" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Proccess_SimpleScript">
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="179" y="99" width="36" height="36" />
<dc:Bounds x="179" y="159" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_1fep863_di" bpmnElement="Event_1fep863">
<dc:Bounds x="592" y="99" width="36" height="36" />
<bpmndi:BPMNShape id="Activity_0l45w13_di" bpmnElement="Activity_SetInitialData">
<dc:Bounds x="270" y="137" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_12kpu89_di" bpmnElement="Activity_RunScript">
<dc:Bounds x="270" y="77" width="100" height="80" />
<bpmndi:BPMNShape id="Activity_00n1s76_di" bpmnElement="Activity_CalculateNewData">
<dc:Bounds x="430" y="137" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_17mrit2_di" bpmnElement="Activity_DisplayData">
<dc:Bounds x="430" y="77" width="100" height="80" />
<bpmndi:BPMNShape id="Activity_1nhghi0_di" bpmnElement="Activity_DisplayData">
<dc:Bounds x="590" y="137" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_19fiqu4_di" bpmnElement="Event_19fiqu4">
<dc:Bounds x="752" y="159" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_0r3ua0i_di" bpmnElement="Flow_0r3ua0i">
<di:waypoint x="215" y="177" />
<di:waypoint x="270" y="177" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_19g4f88_di" bpmnElement="Flow_19g4f88">
<di:waypoint x="370" y="177" />
<di:waypoint x="430" y="177" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_152cqfw_di" bpmnElement="Flow_152cqfw">
<di:waypoint x="530" y="177" />
<di:waypoint x="590" y="177" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1vqk60p_di" bpmnElement="Flow_1vqk60p">
<di:waypoint x="690" y="177" />
<di:waypoint x="752" y="177" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

View File

@ -12,6 +12,10 @@ from flask.app import Flask
from flask.testing import FlaskClient
from flask_bpmn.api.api_error import ApiError
from flask_bpmn.models.db import db
from spiffworkflow_backend.services.file_system_service import FileSystemService
from spiffworkflow_backend.services.process_instance_processor import ProcessInstanceProcessor
from spiffworkflow_backend.services.process_instance_service import ProcessInstanceService
from tests.spiffworkflow_backend.helpers.test_data import load_test_spec
from werkzeug.test import TestResponse # type: ignore
@ -34,6 +38,43 @@ from spiffworkflow_backend.services.user_service import UserService
class BaseTest:
"""BaseTest."""
def basic_test_setup(
self,
client: FlaskClient,
user: UserModel,
process_group_id: Optional[str] = "test_group",
process_model_id: Optional[str] = "random_fact",
bpmn_file_name: Optional[str] = None,
bpmn_file_location: Optional[str] = None
) -> str:
"""Creates a process group
Creates a process model
Adds a bpmn file to the model"""
process_group_display_name = process_group_id
process_group_description = process_group_id
process_model_identifier = f"{process_group_id}/{process_model_id}"
if bpmn_file_location is None:
bpmn_file_location = process_model_id
self.create_process_group(client, user, process_group_id, process_group_display_name)
self.create_process_model_with_api(
client,
process_model_id=process_model_identifier,
process_model_display_name=process_group_display_name,
process_model_description=process_group_description,
user=user,
)
load_test_spec(
process_model_id=process_model_identifier,
bpmn_file_name=bpmn_file_name,
process_model_source_directory=bpmn_file_location
)
return process_model_identifier
@staticmethod
def find_or_create_user(username: str = "test_user_1") -> UserModel:
"""Find_or_create_user."""
@ -67,17 +108,18 @@ class BaseTest:
open_id_client_secret_key,
)
@staticmethod
def create_process_instance(
self,
client: FlaskClient,
test_process_group_id: str,
test_process_model_id: str,
headers: Dict[str, str],
) -> TestResponse:
"""Create_process_instance."""
load_test_spec(test_process_model_id, process_group_id=test_process_group_id)
"""Create_process_instance.
There must be an existing process model to instantiate."""
modified_process_model_id = test_process_model_id.replace("/", ":")
response = client.post(
f"/v1.0/process-models/{test_process_group_id}/{test_process_model_id}/process-instances",
f"/v1.0/process-models/{modified_process_model_id}/process-instances",
headers=headers,
)
assert response.status_code == 201
@ -86,8 +128,7 @@ class BaseTest:
def create_process_model_with_api(
self,
client: FlaskClient,
process_group_id: Optional[str] = None,
process_model_id: str = "make_cookies",
process_model_id: Optional[str] = None,
process_model_display_name: str = "Cooooookies",
process_model_description: str = "Om nom nom delicious cookies",
fault_or_suspend_on_exception: str = NotificationType.suspend.value,
@ -97,65 +138,74 @@ class BaseTest:
user: Optional[UserModel] = None,
) -> TestResponse:
"""Create_process_model."""
process_model_service = ProcessModelService()
# make sure we have a group
if process_group_id is None:
process_group_tmp = ProcessGroup(
id="test_cat",
display_name="Test Category",
display_order=0,
admin=False,
)
process_group = process_model_service.add_process_group(process_group_tmp)
if process_model_id is not None:
# make sure we have a group
process_group_id, _ = os.path.split(process_model_id)
process_group_path = f"{FileSystemService.root_path()}/{process_group_id}"
if ProcessModelService().is_group(process_group_path):
if exception_notification_addresses is None:
exception_notification_addresses = []
model = ProcessModelInfo(
id=process_model_id,
display_name=process_model_display_name,
description=process_model_description,
is_review=False,
primary_process_id=primary_process_id,
primary_file_name=primary_file_name,
fault_or_suspend_on_exception=fault_or_suspend_on_exception,
exception_notification_addresses=exception_notification_addresses,
)
if user is None:
user = self.find_or_create_user()
response = client.post(
"/v1.0/process-models",
content_type="application/json",
data=json.dumps(ProcessModelInfoSchema().dump(model)),
headers=self.logged_in_headers(user),
)
assert response.status_code == 201
return response
else:
raise Exception("You must create the group first")
else:
process_group = ProcessModelService().get_process_group(process_group_id)
if exception_notification_addresses is None:
exception_notification_addresses = []
model = ProcessModelInfo(
id=process_model_id,
display_name=process_model_display_name,
description=process_model_description,
process_group_id=process_group.id,
is_review=False,
primary_process_id=primary_process_id,
primary_file_name=primary_file_name,
fault_or_suspend_on_exception=fault_or_suspend_on_exception,
exception_notification_addresses=exception_notification_addresses,
)
if user is None:
user = self.find_or_create_user()
response = client.post(
"/v1.0/process-models",
content_type="application/json",
data=json.dumps(ProcessModelInfoSchema().dump(model)),
headers=self.logged_in_headers(user),
)
assert response.status_code == 201
return response
raise Exception("You must include the process_model_id, which must be a path to the model")
def create_spec_file(
self,
client: FlaskClient,
process_group_id: str = "random_fact",
process_model_id: str = "random_fact",
process_model_id: str,
process_model_location: Optional[str] = None,
process_model: Optional[ProcessModelInfo] = None,
file_name: str = "random_fact.svg",
file_data: bytes = b"abcdef",
user: Optional[UserModel] = None,
) -> Any:
"""Test_create_spec_file."""
"""Test_create_spec_file.
Adds a bpmn file to the model.
process_model_id is the destination path
process_model_location is the source path
because of permissions, user might be required now..., not sure yet"""
if process_model_location is None:
process_model_location = file_name.split(".")[0]
if process_model is None:
process_model = load_test_spec(
process_model_id, process_group_id=process_group_id
process_model_id=process_model_id,
bpmn_file_name=file_name,
process_model_source_directory=process_model_location
)
data = {"file": (io.BytesIO(file_data), file_name)}
if user is None:
user = self.find_or_create_user()
modified_process_model_id = process_model.id.replace("/", ":")
response = client.post(
f"/v1.0/process-models/{process_model.process_group_id}/{process_model.id}/files",
f"/v1.0/process-models/{modified_process_model_id}/files",
data=data,
follow_redirects=True,
content_type="multipart/form-data",
@ -168,7 +218,7 @@ class BaseTest:
# assert "image/svg+xml" == file["content_type"]
response = client.get(
f"/v1.0/process-models/{process_model.process_group_id}/{process_model.id}/files/{file_name}",
f"/v1.0/process-models/{modified_process_model_id}/files/{file_name}",
headers=self.logged_in_headers(user),
)
assert response.status_code == 200
@ -221,7 +271,7 @@ class BaseTest:
status=status,
process_initiator=user,
process_model_identifier=process_model.id,
process_group_identifier=process_model.process_group_id,
process_group_identifier="",
updated_at_in_seconds=round(time.time()),
start_in_seconds=current_time - (3600 * 1),
end_in_seconds=current_time - (3600 * 1 - 20),

View File

@ -13,27 +13,30 @@ from spiffworkflow_backend.services.spec_file_service import SpecFileService
class ExampleDataLoader:
"""ExampleDataLoader."""
@staticmethod
def create_spec(
self,
process_model_id: str,
display_name: str = "",
description: str = "",
process_group_id: str = "",
display_order: int = 0,
from_tests: bool = False,
# from_tests: bool = False,
bpmn_file_name: Optional[str] = None,
process_model_source_directory: Optional[str] = None,
process_model_source_directory: str = None,
) -> ProcessModelInfo:
"""Assumes that a directory exists in static/bpmn with the same name as the given process_model_id.
"""Assumes that process_model_source_directory exists in static/bpmn and contains bpmn_file_name.
further assumes that the [process_model_id].bpmn is the primary file for the process model.
returns an array of data models to be added to the database.
further assumes that bpmn_file_name is the primary file for the process model.
if bpmn_file_name is None we load all files in process_model_source_directory,
otherwise, we only load bpmn_file_name
"""
if process_model_source_directory is None:
raise Exception("You must include `process_model_source_directory`.")
spec = ProcessModelInfo(
id=process_model_id,
display_name=display_name,
description=description,
process_group_id=process_group_id,
display_order=display_order,
is_review=False,
)
@ -55,25 +58,16 @@ class ExampleDataLoader:
if bpmn_file_name:
file_name_matcher = bpmn_file_name_with_extension
file_glob = ""
if from_tests:
file_glob = os.path.join(
current_app.instance_path,
"..",
"..",
"tests",
"data",
process_model_source_directory_to_use,
file_name_matcher,
)
else:
file_glob = os.path.join(
current_app.root_path,
"static",
"bpmn",
process_model_source_directory_to_use,
file_name_matcher,
)
# file_glob = ""
file_glob = os.path.join(
current_app.root_path,
"..",
"..",
"tests",
"data",
process_model_source_directory_to_use,
file_name_matcher,
)
files = glob.glob(file_glob)
for file_path in files:

View File

@ -37,40 +37,18 @@ def assure_process_group_exists(process_group_id: Optional[str] = None) -> Proce
def load_test_spec(
process_model_id: str,
process_group_id: Optional[str] = None,
bpmn_file_name: Optional[str] = None,
process_model_source_directory: Optional[str] = None,
process_model_source_directory: str = None,
) -> ProcessModelInfo:
"""Loads a process model into the bpmn dir based on a directory in tests/data."""
process_group = None
process_model_service = ProcessModelService()
if process_group_id is None:
process_group_id = "test_process_group_id"
process_group = assure_process_group_exists(process_group_id)
process_group_id = process_group.id
"""Loads a bpmn file into the process model dir based on a directory in tests/data."""
try:
return process_model_service.get_process_model(
process_model_id, group_id=process_group_id
)
except ProcessEntityNotFoundError:
spec = ExampleDataLoader().create_spec(
process_model_id=process_model_id,
from_tests=True,
display_name=process_model_id,
process_group_id=process_group_id,
bpmn_file_name=bpmn_file_name,
process_model_source_directory=process_model_source_directory,
)
return spec
if process_model_source_directory is None:
raise Exception("You must inclode a `process_model_source_directory`.")
# def user_info_to_query_string(user_info, redirect_url):
# query_string_list = []
# items = user_info.items()
# for key, value in items:
# query_string_list.append('%s=%s' % (key, urllib.parse.quote(value)))
#
# query_string_list.append('redirect_url=%s' % redirect_url)
#
# return '?%s' % '&'.join(query_string_list)
spec = ExampleDataLoader.create_spec(
process_model_id=process_model_id,
display_name=process_model_id,
bpmn_file_name=bpmn_file_name,
process_model_source_directory=process_model_source_directory,
)
return spec

View File

@ -19,20 +19,43 @@ class TestLoggingService(BaseTest):
"""Test_process_instance_run."""
process_group_id = "test_logging_spiff_logger"
process_model_id = "simple_script"
self.create_process_group(client=client, user=with_super_admin_user, process_group_id=process_group_id)
process_model_identifier = f"{process_group_id}/{process_model_id}"
# create the model
process_model_info = self.create_process_model_with_api(
client=client,
process_model_id=process_model_identifier,
process_model_display_name="Simple Script",
process_model_description="Simple Script",
user=with_super_admin_user
)
bpmn_file_name = "simple_script.bpmn"
bpmn_file_data_bytes = self.get_test_data_file_contents(
bpmn_file_name, "simple_script"
)
# add bpmn to the model
self.create_spec_file(
client=client,
process_model_id=process_model_identifier,
file_name=bpmn_file_name,
file_data=bpmn_file_data_bytes,
user=with_super_admin_user
)
headers = self.logged_in_headers(with_super_admin_user)
response = self.create_process_instance(
client, process_group_id, process_model_id, headers
client, process_model_identifier, headers
)
assert response.json is not None
process_instance_id = response.json["id"]
response = client.post(
f"/v1.0/process-models/{process_group_id}/{process_model_id}/process-instances/{process_instance_id}/run",
f"/v1.0/process-instances/{process_instance_id}/run",
headers=headers,
)
assert response.status_code == 200
log_response = client.get(
f"/v1.0/process-models/{process_group_id}/{process_model_id}/process-instances/{process_instance_id}/logs",
f"/v1.0/process-instances/{process_instance_id}/logs",
headers=headers,
)
assert log_response.status_code == 200

View File

@ -0,0 +1,172 @@
import json
from spiffworkflow_backend.models.process_group import ProcessGroup, ProcessGroupSchema
from spiffworkflow_backend.models.process_model import ProcessModelInfo, ProcessModelInfoSchema
from spiffworkflow_backend.models.user import UserModel
from tests.spiffworkflow_backend.helpers.base_test import BaseTest
from flask.app import Flask
from flask.testing import FlaskClient
class TestNestedGroups(BaseTest):
def test_nested_groups(
self,
app: Flask,
client: FlaskClient,
with_db_and_bpmn_file_cleanup: None,
) -> None:
# /process-groups/{process_group_path}/show
target_uri = "/v1.0/process-groups/group_a,group_b"
user = self.find_or_create_user()
self.add_permissions_to_user(
user, target_uri=target_uri, permission_names=["read"]
)
response = client.get(
target_uri,
headers=self.logged_in_headers(user)
)
print("test_nested_groups")
def test_add_nested_group(
self,
app: Flask,
client: FlaskClient,
with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel,
) -> None:
target_uri = "/process-groups"
# user = self.find_or_create_user()
# self.add_permissions_to_user(
# user, target_uri=target_uri, permission_names=["read", "create"]
# )
process_group_a = ProcessGroup(
id="group_a",
display_name="Group A",
display_order=0,
admin=False,
)
response_a = client.post(
"/v1.0/process-groups",
headers=self.logged_in_headers(with_super_admin_user),
content_type="application/json",
data=json.dumps(ProcessGroupSchema().dump(process_group_a)),
)
process_group_b = ProcessGroup(
id="group_a/group_b",
display_name="Group B",
display_order=0,
admin=False,
)
response_b = client.post(
"/v1.0/process-groups",
headers=self.logged_in_headers(with_super_admin_user),
content_type="application/json",
data=json.dumps(ProcessGroupSchema().dump(process_group_b)),
)
process_group_c = ProcessGroup(
id="group_a/group_b/group_c",
display_name="Group C",
display_order=0,
admin=False,
)
response_c = client.post(
"/v1.0/process-groups",
headers=self.logged_in_headers(with_super_admin_user),
content_type="application/json",
data=json.dumps(ProcessGroupSchema().dump(process_group_c)),
)
print("test_add_nested_group")
def test_process_model_add(
self,
app: Flask,
client: FlaskClient,
with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel,
):
process_group_a = ProcessGroup(
id="group_a",
display_name="Group A",
display_order=0,
admin=False,
)
response_a = client.post(
"/v1.0/process-groups",
headers=self.logged_in_headers(with_super_admin_user),
content_type="application/json",
data=json.dumps(ProcessGroupSchema().dump(process_group_a)),
)
process_group_b = ProcessGroup(
id="group_a/group_b",
display_name="Group B",
display_order=0,
admin=False,
)
response_b = client.post(
"/v1.0/process-groups",
headers=self.logged_in_headers(with_super_admin_user),
content_type="application/json",
data=json.dumps(ProcessGroupSchema().dump(process_group_b)),
)
process_model = ProcessModelInfo(
id="process_model",
display_name="Process Model",
description="Process Model",
primary_file_name="primary_file.bpmn",
primary_process_id="primary_process_id",
display_order=0
)
model_response = client.post(
"v1.0/process-models",
headers=self.logged_in_headers(with_super_admin_user),
content_type="application/json",
data=json.dumps(ProcessModelInfoSchema().dump(process_model))
)
print("test_process_model_add")
def test_process_group_show(
self,
app: Flask,
client: FlaskClient,
with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel,
) -> None:
# target_uri = "/process-groups/{process_group_id}"
# user = self.find_or_create_user("testadmin1")
# self.add_permissions_to_user(
# user, target_uri="v1.0/process-groups", permission_names=["read", "create"]
# )
# self.add_permissions_to_user(
# user, target_uri="/process-groups/{process_group_id}", permission_names=["read", "create"]
# )
process_group_a = ProcessGroup(
id="group_a",
display_name="Group A",
display_order=0,
admin=False,
)
response_create_a = client.post(
"/v1.0/process-groups",
headers=self.logged_in_headers(with_super_admin_user),
content_type="application/json",
data=json.dumps(ProcessGroupSchema().dump(process_group_a)),
)
target_uri = "/v1.0/process-groups/group_a"
user = self.find_or_create_user()
self.add_permissions_to_user(
user, target_uri=target_uri, permission_names=["read"]
)
response = client.get(
target_uri,
headers=self.logged_in_headers(user)
)
print("test_process_group_show: ")

View File

@ -42,16 +42,16 @@ class SecretServiceTestHelpers(BaseTest):
self.test_process_group_id,
display_name=self.test_process_group_display_name,
)
process_model_identifier = f"{self.test_process_group_id}/{self.test_process_model_id}"
self.create_process_model_with_api(
client,
process_group_id=self.test_process_group_id,
process_model_id=self.test_process_model_id,
process_model_id=process_model_identifier,
process_model_display_name=self.test_process_model_display_name,
process_model_description=self.test_process_model_description,
user=user,
)
process_model_info = ProcessModelService().get_process_model(
self.test_process_model_id, self.test_process_group_id
process_model_identifier
)
return process_model_info

View File

@ -1,10 +1,12 @@
"""Test_get_localtime."""
from flask.app import Flask
from flask.testing import FlaskClient
from flask_bpmn.models.db import db
from tests.spiffworkflow_backend.helpers.base_test import BaseTest
from tests.spiffworkflow_backend.helpers.test_data import load_test_spec
from spiffworkflow_backend.models.group import GroupModel
from spiffworkflow_backend.models.user import UserModel
from spiffworkflow_backend.services.process_instance_processor import (
ProcessInstanceProcessor,
)
@ -17,7 +19,9 @@ class TestGetGroupMembers(BaseTest):
def test_can_get_members_of_a_group(
self,
app: Flask,
client: FlaskClient,
with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel
) -> None:
"""Test_can_get_members_of_a_group."""
initiator_user = self.find_or_create_user("initiator_user")
@ -34,9 +38,11 @@ class TestGetGroupMembers(BaseTest):
UserService.add_user_to_group(testuser2, group_a)
UserService.add_user_to_group(testuser3, group_b)
self.create_process_group(client, with_super_admin_user, "test_group", "test_group")
process_model = load_test_spec(
process_model_id="get_group_members",
process_model_id="test_group/get_group_members",
bpmn_file_name="get_group_members.bpmn",
process_model_source_directory="get_group_members"
)
process_instance = self.create_process_instance_from_process_model(
process_model=process_model, user=initiator_user

View File

@ -49,8 +49,14 @@ class TestGetLocaltime(BaseTest):
) -> None:
"""Test_process_instance_run."""
initiator_user = self.find_or_create_user("initiator_user")
self.add_permissions_to_user(
initiator_user, target_uri="/v1.0/process-groups", permission_names=["read", "create"]
)
self.create_process_group(client=client, user=initiator_user, process_group_id="test_group")
process_model = load_test_spec(
process_model_id="get_localtime", bpmn_file_name="get_localtime.bpmn"
process_model_id="test_group/get_localtime",
bpmn_file_name="get_localtime.bpmn",
process_model_source_directory="get_localtime"
)
process_instance = self.create_process_instance_from_process_model(
process_model=process_model, user=initiator_user

View File

@ -1,10 +1,13 @@
"""Test_message_service."""
import pytest
from flask import Flask
from flask.testing import FlaskClient
from spiffworkflow_backend.services.process_model_service import ProcessModelService
from tests.spiffworkflow_backend.helpers.base_test import BaseTest
from tests.spiffworkflow_backend.helpers.test_data import load_test_spec
from spiffworkflow_backend.models.user import UserNotFoundError
from spiffworkflow_backend.models.user import UserModel, UserNotFoundError
from spiffworkflow_backend.services.authorization_service import AuthorizationService
from spiffworkflow_backend.services.process_instance_processor import (
ProcessInstanceProcessor,
@ -89,7 +92,11 @@ class TestAuthorizationService(BaseTest):
)
def test_user_can_be_added_to_active_task_on_first_login(
self, app: Flask, with_db_and_bpmn_file_cleanup: None
self,
app: Flask,
client: FlaskClient,
with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel
) -> None:
"""Test_user_can_be_added_to_active_task_on_first_login."""
initiator_user = self.find_or_create_user("initiator_user")
@ -98,8 +105,17 @@ class TestAuthorizationService(BaseTest):
self.find_or_create_user("testuser1")
AuthorizationService.import_permissions_from_yaml_file()
process_model = load_test_spec(
process_model_id="model_with_lanes", bpmn_file_name="lanes.bpmn"
process_model_identifier = self.basic_test_setup(
client=client,
user=with_super_admin_user,
process_group_id="test_group",
process_model_id="model_with_lanes",
bpmn_file_name="lanes.bpmn",
bpmn_file_location="model_with_lanes"
)
process_model = ProcessModelService().get_process_model(
process_model_id=process_model_identifier
)
process_instance = self.create_process_instance_from_process_model(
process_model=process_model, user=initiator_user

View File

@ -1,8 +1,12 @@
"""Test_various_bpmn_constructs."""
from flask.app import Flask
from flask.testing import FlaskClient
from spiffworkflow_backend.services.authorization_service import AuthorizationService
from tests.spiffworkflow_backend.helpers.base_test import BaseTest
from tests.spiffworkflow_backend.helpers.test_data import load_test_spec
from spiffworkflow_backend.models.user import UserModel
from spiffworkflow_backend.services.process_instance_processor import (
ProcessInstanceProcessor,
)
@ -15,21 +19,30 @@ class TestDotNotation(BaseTest):
"""TestVariousBpmnConstructs."""
def test_dot_notation(
self, app: Flask, with_db_and_bpmn_file_cleanup: None
self, app: Flask, client: FlaskClient, with_db_and_bpmn_file_cleanup: None, with_super_admin_user: UserModel
) -> None:
"""Test_form_data_conversion_to_dot_dict."""
process_model = load_test_spec(
"test_dot_notation",
bpmn_file_name="diagram.bpmn",
process_model_source_directory="dot_notation",
process_group_id = "dot_notation_group"
process_model_id = "test_dot_notation"
bpmn_file_name = "diagram.bpmn"
bpmn_file_location = "dot_notation"
process_model_identifier = self.basic_test_setup(
client,
with_super_admin_user,
process_group_id=process_group_id,
process_model_id=process_model_id,
bpmn_file_name=bpmn_file_name,
bpmn_file_location=bpmn_file_location
)
current_user = self.find_or_create_user()
process_instance = self.create_process_instance_from_process_model(
process_model
headers = self.logged_in_headers(with_super_admin_user)
response = self.create_process_instance(
client, process_model_identifier, headers
)
process_instance_id = response.json["id"]
process_instance = ProcessInstanceService().get_process_instance(process_instance_id)
processor = ProcessInstanceProcessor(process_instance)
processor.do_engine_steps(save=True)
user_task = processor.get_ready_user_tasks()[0]
@ -41,7 +54,7 @@ class TestDotNotation(BaseTest):
"invoice.dueDate": "09/30/2022",
}
ProcessInstanceService.complete_form_task(
processor, user_task, form_data, current_user
processor, user_task, form_data, with_super_admin_user
)
expected = {

View File

@ -1,24 +1,46 @@
"""Test_message_instance."""
import pytest
from flask import Flask
from flask.testing import FlaskClient
from flask_bpmn.models.db import db
from spiffworkflow_backend.services.process_model_service import ProcessModelService
from tests.spiffworkflow_backend.helpers.base_test import BaseTest
from tests.spiffworkflow_backend.helpers.test_data import load_test_spec
from spiffworkflow_backend.models.message_instance import MessageInstanceModel
from spiffworkflow_backend.models.message_model import MessageModel
from spiffworkflow_backend.models.user import UserModel
class TestMessageInstance(BaseTest):
"""TestMessageInstance."""
def setup_message_tests(self, client: FlaskClient, user: UserModel) -> str:
process_group_id = "test_group"
process_model_id = "hello_world"
bpmn_file_name = "hello_world.bpmn"
bpmn_file_location = "hello_world"
process_model_identifier = self.basic_test_setup(
client,
user,
process_group_id=process_group_id,
process_model_id=process_model_id,
bpmn_file_name=bpmn_file_name,
bpmn_file_location=bpmn_file_location
)
return process_model_identifier
def test_can_create_message_instance(
self, app: Flask, with_db_and_bpmn_file_cleanup: None
self, app: Flask, client: FlaskClient, with_db_and_bpmn_file_cleanup: None, with_super_admin_user: UserModel
) -> None:
"""Test_can_create_message_instance."""
message_model_identifier = "message_model_one"
message_model = self.create_message_model(message_model_identifier)
process_model = load_test_spec("hello_world")
process_model_identifier = self.setup_message_tests(client, with_super_admin_user)
process_model = ProcessModelService().get_process_model(
process_model_id=process_model_identifier
)
process_instance = self.create_process_instance_from_process_model(
process_model, "waiting"
)
@ -40,12 +62,16 @@ class TestMessageInstance(BaseTest):
assert queued_message_from_query is not None
def test_cannot_set_invalid_status(
self, app: Flask, with_db_and_bpmn_file_cleanup: None
self, app: Flask, client: FlaskClient, with_db_and_bpmn_file_cleanup: None, with_super_admin_user: UserModel
) -> None:
"""Test_cannot_set_invalid_status."""
message_model_identifier = "message_model_one"
message_model = self.create_message_model(message_model_identifier)
process_model = load_test_spec("hello_world")
process_model_identifier = self.setup_message_tests(client, with_super_admin_user)
process_model = ProcessModelService().get_process_model(
process_model_id=process_model_identifier
)
process_instance = self.create_process_instance_from_process_model(
process_model, "waiting"
)
@ -76,12 +102,16 @@ class TestMessageInstance(BaseTest):
)
def test_cannot_set_invalid_message_type(
self, app: Flask, with_db_and_bpmn_file_cleanup: None
self, app: Flask, client: FlaskClient, with_db_and_bpmn_file_cleanup: None, with_super_admin_user: UserModel
) -> None:
"""Test_cannot_set_invalid_message_type."""
message_model_identifier = "message_model_one"
message_model = self.create_message_model(message_model_identifier)
process_model = load_test_spec("hello_world")
process_model_identifier = self.setup_message_tests(client, with_super_admin_user)
process_model = ProcessModelService().get_process_model(
process_model_id=process_model_identifier
)
process_instance = self.create_process_instance_from_process_model(
process_model, "waiting"
)
@ -113,12 +143,16 @@ class TestMessageInstance(BaseTest):
)
def test_force_failure_cause_if_status_is_failure(
self, app: Flask, with_db_and_bpmn_file_cleanup: None
self, app: Flask, client: FlaskClient, with_db_and_bpmn_file_cleanup: None, with_super_admin_user: UserModel
) -> None:
"""Test_force_failure_cause_if_status_is_failure."""
message_model_identifier = "message_model_one"
message_model = self.create_message_model(message_model_identifier)
process_model = load_test_spec("hello_world")
process_model_identifier = self.setup_message_tests(client, with_super_admin_user)
process_model = ProcessModelService().get_process_model(
process_model_id=process_model_identifier
)
process_instance = self.create_process_instance_from_process_model(
process_model, "waiting"
)
@ -154,7 +188,8 @@ class TestMessageInstance(BaseTest):
assert queued_message.id is not None
assert queued_message.failure_cause == "THIS TEST FAILURE"
def create_message_model(self, message_model_identifier: str) -> MessageModel:
@staticmethod
def create_message_model(message_model_identifier: str) -> MessageModel:
"""Create_message_model."""
message_model = MessageModel(identifier=message_model_identifier)
db.session.add(message_model)

View File

@ -1,5 +1,6 @@
"""Test_message_service."""
from flask import Flask
from flask.testing import FlaskClient
from tests.spiffworkflow_backend.helpers.base_test import BaseTest
from tests.spiffworkflow_backend.helpers.test_data import load_test_spec
@ -9,6 +10,7 @@ from spiffworkflow_backend.models.message_correlation_message_instance import (
)
from spiffworkflow_backend.models.message_instance import MessageInstanceModel
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
from spiffworkflow_backend.models.user import UserModel
from spiffworkflow_backend.services.message_service import MessageService
from spiffworkflow_backend.services.process_instance_processor import (
ProcessInstanceProcessor,
@ -22,25 +24,26 @@ class TestMessageService(BaseTest):
"""TestMessageService."""
def test_can_send_message_to_waiting_message(
self, app: Flask, with_db_and_bpmn_file_cleanup: None
self, app: Flask, client: FlaskClient, with_db_and_bpmn_file_cleanup: None, with_super_admin_user: UserModel
) -> None:
"""Test_can_send_message_to_waiting_message."""
process_model_sender = load_test_spec(
"message_sender",
process_model_source_directory="message_send_one_conversation",
bpmn_file_name="message_sender",
)
process_group_id = "test_group"
self.create_process_group(client, with_super_admin_user, process_group_id, process_group_id)
load_test_spec(
"message_receiver",
"test_group/message_receiver",
process_model_source_directory="message_send_one_conversation",
bpmn_file_name="message_receiver",
bpmn_file_name="message_receiver.bpmn"
)
process_model_sender = load_test_spec(
"test_group/message_sender",
process_model_source_directory="message_send_one_conversation",
bpmn_file_name="message_sender.bpmn",
)
user = self.find_or_create_user()
process_instance_sender = ProcessInstanceService.create_process_instance(
process_model_sender.id,
user,
process_group_identifier=process_model_sender.process_group_id,
with_super_admin_user,
)
processor_sender = ProcessInstanceProcessor(process_instance_sender)
@ -115,21 +118,25 @@ class TestMessageService(BaseTest):
assert process_instance.status == "complete"
def test_can_send_message_to_multiple_process_models(
self, app: Flask, with_db_and_bpmn_file_cleanup: None
self, app: Flask, client: FlaskClient, with_db_and_bpmn_file_cleanup: None, with_super_admin_user: UserModel
) -> None:
"""Test_can_send_message_to_multiple_process_models."""
process_group_id = "test_group"
self.create_process_group(client, with_super_admin_user, process_group_id, process_group_id)
process_model_sender = load_test_spec(
"message_sender",
"test_group/message_sender",
process_model_source_directory="message_send_two_conversations",
bpmn_file_name="message_sender",
)
load_test_spec(
"message_receiver_one",
"test_group/message_receiver_one",
process_model_source_directory="message_send_two_conversations",
bpmn_file_name="message_receiver_one",
)
load_test_spec(
"message_receiver_two",
"test_group/message_receiver_two",
process_model_source_directory="message_send_two_conversations",
bpmn_file_name="message_receiver_two",
)
@ -139,7 +146,7 @@ class TestMessageService(BaseTest):
process_instance_sender = ProcessInstanceService.create_process_instance(
process_model_sender.id,
user,
process_group_identifier=process_model_sender.process_group_id,
# process_group_identifier=process_model_sender.process_group_id,
)
processor_sender = ProcessInstanceProcessor(process_instance_sender)
@ -189,24 +196,24 @@ class TestMessageService(BaseTest):
assert len(process_instance_result) == 3
process_instance_receiver_one = ProcessInstanceModel.query.filter_by(
process_model_identifier="message_receiver_one"
process_model_identifier="test_group/message_receiver_one"
).first()
assert process_instance_receiver_one is not None
process_instance_receiver_two = ProcessInstanceModel.query.filter_by(
process_model_identifier="message_receiver_two"
process_model_identifier="test_group/message_receiver_two"
).first()
assert process_instance_receiver_two is not None
# just make sure it's a different process instance
assert (
process_instance_receiver_one.process_model_identifier
== "message_receiver_one"
== "test_group/message_receiver_one"
)
assert process_instance_receiver_one.id != process_instance_sender.id
assert process_instance_receiver_one.status == "complete"
assert (
process_instance_receiver_two.process_model_identifier
== "message_receiver_two"
== "test_group/message_receiver_two"
)
assert process_instance_receiver_two.id != process_instance_sender.id
assert process_instance_receiver_two.status == "complete"

View File

@ -1,5 +1,6 @@
"""Test Permissions."""
from flask.app import Flask
from flask.testing import FlaskClient
from flask_bpmn.models.db import db
from tests.spiffworkflow_backend.helpers.base_test import BaseTest
from tests.spiffworkflow_backend.helpers.test_data import load_test_spec
@ -8,6 +9,7 @@ from spiffworkflow_backend.models.group import GroupModel
from spiffworkflow_backend.models.permission_assignment import PermissionAssignmentModel
from spiffworkflow_backend.models.permission_target import PermissionTargetModel
from spiffworkflow_backend.models.principal import PrincipalModel
from spiffworkflow_backend.models.user import UserModel
from spiffworkflow_backend.services.user_service import UserService
@ -22,13 +24,15 @@ class TestPermissions(BaseTest):
"""TestPermissions."""
def test_user_can_be_given_permission_to_administer_process_group(
self, app: Flask, with_db_and_bpmn_file_cleanup: None
self, app: Flask, client: FlaskClient, with_db_and_bpmn_file_cleanup: None, with_super_admin_user: UserModel
) -> None:
"""Test_user_can_be_given_permission_to_administer_process_group."""
process_group_id = "group-a"
self.create_process_group(client, with_super_admin_user, process_group_id, process_group_id)
load_test_spec(
"timers_intermediate_catch_event",
process_group_id=process_group_id,
"group-a/timers_intermediate_catch_event",
bpmn_file_name="timers_intermediate_catch_event.bpmn",
process_model_source_directory="timers_intermediate_catch_event"
)
dan = self.find_or_create_user()
principal = dan.principal
@ -55,8 +59,9 @@ class TestPermissions(BaseTest):
process_group_b_id = process_group_ids[1]
for process_group_id in process_group_ids:
load_test_spec(
"timers_intermediate_catch_event",
process_group_id=process_group_id,
f"{process_group_id}/timers_intermediate_catch_event",
bpmn_file_name="timers_intermediate_catch_event",
process_model_source_directory="timers_intermediate_catch_event"
)
group_a_admin = self.find_or_create_user()
@ -86,11 +91,11 @@ class TestPermissions(BaseTest):
"""Test_user_can_be_granted_access_through_a_group."""
process_group_ids = ["group-a", "group-b"]
process_group_a_id = process_group_ids[0]
process_group_ids[1]
for process_group_id in process_group_ids:
load_test_spec(
"timers_intermediate_catch_event",
process_group_id=process_group_id,
f"{process_group_id}/timers_intermediate_catch_event",
bpmn_file_name="timers_intermediate_catch_event.bpmn",
process_model_source_directory="timers_intermediate_catch_event"
)
user = self.find_or_create_user()
group = GroupModel(identifier="groupA")
@ -127,8 +132,9 @@ class TestPermissions(BaseTest):
process_group_b_id = process_group_ids[1]
for process_group_id in process_group_ids:
load_test_spec(
"timers_intermediate_catch_event",
process_group_id=process_group_id,
f"{process_group_id}/timers_intermediate_catch_event",
bpmn_file_name="timers_intermediate_catch_event.bpmn",
process_model_source_directory="timers_intermediate_catch_event"
)
group_a_admin = self.find_or_create_user()

View File

@ -2,11 +2,13 @@
import pytest
from flask import g
from flask.app import Flask
from flask.testing import FlaskClient
from tests.spiffworkflow_backend.helpers.base_test import BaseTest
from tests.spiffworkflow_backend.helpers.test_data import load_test_spec
from spiffworkflow_backend.models.group import GroupModel
from spiffworkflow_backend.models.process_instance import ProcessInstanceStatus
from spiffworkflow_backend.models.user import UserModel
from spiffworkflow_backend.services.authorization_service import AuthorizationService
from spiffworkflow_backend.services.authorization_service import (
UserDoesNotHaveAccessToTaskError,
@ -50,9 +52,12 @@ class TestProcessInstanceProcessor(BaseTest):
def test_sets_permission_correctly_on_active_task(
self,
app: Flask,
client: FlaskClient,
with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel
) -> None:
"""Test_sets_permission_correctly_on_active_task."""
self.create_process_group(client, with_super_admin_user, "test_group", "test_group")
initiator_user = self.find_or_create_user("initiator_user")
finance_user = self.find_or_create_user("testuser2")
assert initiator_user.principal is not None
@ -63,7 +68,9 @@ class TestProcessInstanceProcessor(BaseTest):
assert finance_group is not None
process_model = load_test_spec(
process_model_id="model_with_lanes", bpmn_file_name="lanes.bpmn"
process_model_id="test_group/model_with_lanes",
bpmn_file_name="lanes.bpmn",
process_model_source_directory="model_with_lanes"
)
process_instance = self.create_process_instance_from_process_model(
process_model=process_model, user=initiator_user
@ -123,9 +130,12 @@ class TestProcessInstanceProcessor(BaseTest):
def test_sets_permission_correctly_on_active_task_when_using_dict(
self,
app: Flask,
client: FlaskClient,
with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel
) -> None:
"""Test_sets_permission_correctly_on_active_task_when_using_dict."""
self.create_process_group(client, with_super_admin_user, "test_group", "test_group")
initiator_user = self.find_or_create_user("initiator_user")
finance_user_three = self.find_or_create_user("testuser3")
finance_user_four = self.find_or_create_user("testuser4")
@ -138,8 +148,9 @@ class TestProcessInstanceProcessor(BaseTest):
assert finance_group is not None
process_model = load_test_spec(
process_model_id="model_with_lanes",
process_model_id="test_group/model_with_lanes",
bpmn_file_name="lanes_with_owner_dict.bpmn",
process_model_source_directory="model_with_lanes"
)
process_instance = self.create_process_instance_from_process_model(
process_model=process_model, user=initiator_user

View File

@ -1,11 +1,13 @@
"""Process Model."""
from flask.app import Flask
from flask.testing import FlaskClient
from flask_bpmn.models.db import db
from tests.spiffworkflow_backend.helpers.base_test import BaseTest
from tests.spiffworkflow_backend.helpers.test_data import load_test_spec
from spiffworkflow_backend.models.bpmn_process_id_lookup import BpmnProcessIdLookup
from spiffworkflow_backend.models.process_model import ProcessModelInfo
from spiffworkflow_backend.models.user import UserModel
from spiffworkflow_backend.services.process_instance_processor import (
ProcessInstanceProcessor,
)
@ -22,11 +24,13 @@ class TestProcessModel(BaseTest):
assert process_model_one.files == []
def test_can_run_process_model_with_call_activities_when_in_same_process_model_directory(
self, app: Flask, with_db_and_bpmn_file_cleanup: None
self, app: Flask, client: FlaskClient, with_db_and_bpmn_file_cleanup: None, with_super_admin_user: UserModel
) -> None:
"""Test_can_run_process_model_with_call_activities."""
self.create_process_group(client, with_super_admin_user, "test_group", "test_group")
process_model = load_test_spec(
"call_activity_test",
"test_group/call_activity_test",
# bpmn_file_name="call_activity_test.bpmn",
process_model_source_directory="call_activity_same_directory",
)
@ -38,11 +42,12 @@ class TestProcessModel(BaseTest):
assert process_instance.status == "complete"
def test_can_run_process_model_with_call_activities_when_not_in_same_directory(
self, app: Flask, with_db_and_bpmn_file_cleanup: None
self, app: Flask, client: FlaskClient, with_db_and_bpmn_file_cleanup: None, with_super_admin_user: UserModel
) -> None:
"""Test_can_run_process_model_with_call_activities."""
self.create_process_group(client, with_super_admin_user, "test_group", "test_group")
process_model = load_test_spec(
"call_activity_nested",
"test_group/call_activity_nested",
process_model_source_directory="call_activity_nested",
bpmn_file_name="call_activity_nested",
)
@ -54,7 +59,7 @@ class TestProcessModel(BaseTest):
]
for bpmn_file_name in bpmn_file_names:
load_test_spec(
bpmn_file_name,
f"test_group/{bpmn_file_name}",
process_model_source_directory="call_activity_nested",
bpmn_file_name=bpmn_file_name,
)
@ -66,11 +71,12 @@ class TestProcessModel(BaseTest):
assert process_instance.status == "complete"
def test_can_run_process_model_with_call_activities_when_process_identifier_is_not_in_database(
self, app: Flask, with_db_and_bpmn_file_cleanup: None
self, app: Flask, client: FlaskClient, with_db_and_bpmn_file_cleanup: None, with_super_admin_user: UserModel
) -> None:
"""Test_can_run_process_model_with_call_activities."""
self.create_process_group(client, with_super_admin_user, "test_group", "test_group")
process_model = load_test_spec(
"call_activity_nested",
"test_group/call_activity_nested",
process_model_source_directory="call_activity_nested",
bpmn_file_name="call_activity_nested",
)
@ -82,7 +88,7 @@ class TestProcessModel(BaseTest):
]
for bpmn_file_name in bpmn_file_names:
load_test_spec(
bpmn_file_name,
f"test_group/{bpmn_file_name}",
process_model_source_directory="call_activity_nested",
bpmn_file_name=bpmn_file_name,
)
@ -93,6 +99,7 @@ class TestProcessModel(BaseTest):
# delete all of the id lookup items to force to processor to find the correct
# process model when running the process
db.session.query(BpmnProcessIdLookup).delete()
db.session.commit()
processor = ProcessInstanceProcessor(process_instance)
processor.do_engine_steps(save=True)
assert process_instance.status == "complete"

View File

@ -1,8 +1,10 @@
"""Test_process_model_service."""
from flask import Flask
from flask.testing import FlaskClient
from tests.spiffworkflow_backend.helpers.base_test import BaseTest
from tests.spiffworkflow_backend.helpers.test_data import load_test_spec
from spiffworkflow_backend.models.user import UserModel
from spiffworkflow_backend.services.process_model_service import ProcessModelService
@ -10,11 +12,16 @@ class TestProcessModelService(BaseTest):
"""TestProcessModelService."""
def test_can_update_specified_attributes(
self, app: Flask, with_db_and_bpmn_file_cleanup: None
self, app: Flask, client: FlaskClient, with_db_and_bpmn_file_cleanup: None, with_super_admin_user: UserModel
) -> None:
"""Test_can_update_specified_attributes."""
process_model = load_test_spec("hello_world")
assert process_model.display_name == "hello_world"
self.create_process_group(client, with_super_admin_user, "test_group", "test_group")
process_model = load_test_spec(
"test_group/hello_world",
bpmn_file_name="hello_world.bpmn",
process_model_source_directory="hello_world"
)
assert process_model.display_name == "test_group/hello_world"
primary_process_id = process_model.primary_process_id
assert primary_process_id == "Process_HelloWorld"

View File

@ -1,10 +1,12 @@
"""Test_various_bpmn_constructs."""
import pytest
from flask.app import Flask
from flask.testing import FlaskClient
from flask_bpmn.api.api_error import ApiError
from tests.spiffworkflow_backend.helpers.base_test import BaseTest
from tests.spiffworkflow_backend.helpers.test_data import load_test_spec
from spiffworkflow_backend.models.user import UserModel
from spiffworkflow_backend.services.process_instance_processor import (
ProcessInstanceProcessor,
)
@ -14,11 +16,12 @@ class TestOpenFile(BaseTest):
"""TestVariousBpmnConstructs."""
def test_dot_notation(
self, app: Flask, with_db_and_bpmn_file_cleanup: None
self, app: Flask, client: FlaskClient, with_db_and_bpmn_file_cleanup: None, with_super_admin_user: UserModel
) -> None:
"""Test_form_data_conversion_to_dot_dict."""
self.create_process_group(client, with_super_admin_user, "test_group", "test_group")
process_model = load_test_spec(
"dangerous",
"test_group/dangerous",
bpmn_file_name="read_etc_passwd.bpmn",
process_model_source_directory="dangerous-scripts",
)
@ -38,11 +41,12 @@ class TestImportModule(BaseTest):
"""TestVariousBpmnConstructs."""
def test_dot_notation(
self, app: Flask, with_db_and_bpmn_file_cleanup: None
self, app: Flask, client: FlaskClient, with_db_and_bpmn_file_cleanup: None, with_super_admin_user: UserModel
) -> None:
"""Test_form_data_conversion_to_dot_dict."""
self.create_process_group(client, with_super_admin_user, "test_group", "test_group")
process_model = load_test_spec(
"dangerous",
"test_group/dangerous",
bpmn_file_name="read_env.bpmn",
process_model_source_directory="dangerous-scripts",
)

View File

@ -1,8 +1,10 @@
"""Test Permissions."""
from flask.app import Flask
from flask.testing import FlaskClient
from tests.spiffworkflow_backend.helpers.base_test import BaseTest
from tests.spiffworkflow_backend.helpers.test_data import load_test_spec
from spiffworkflow_backend.models.user import UserModel
from spiffworkflow_backend.services.process_instance_processor import (
ProcessInstanceProcessor,
)
@ -16,21 +18,29 @@ class TestScriptUnitTestRunner(BaseTest):
def test_takes_data_and_returns_expected_result(
self,
app: Flask,
client: FlaskClient,
with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel
) -> None:
"""Test_takes_data_and_returns_expected_result."""
app.config["THREAD_LOCAL_DATA"].process_instance_id = None
process_group_id = "test_logging_spiff_logger"
self.create_process_group(client, with_super_admin_user, process_group_id, process_group_id)
process_model_id = "simple_script"
load_test_spec(process_model_id, process_group_id=process_group_id)
process_model_identifier = f"{process_group_id}/{process_model_id}"
load_test_spec(
process_model_identifier,
bpmn_file_name=process_model_id,
process_model_source_directory=process_model_id
)
bpmn_process_instance = (
ProcessInstanceProcessor.get_bpmn_process_instance_from_process_model(
process_model_id, process_group_id
process_model_identifier
)
)
task = ProcessInstanceProcessor.get_task_by_bpmn_identifier(
"Activity_RunScript", bpmn_process_instance
"Activity_CalculateNewData", bpmn_process_instance
)
assert task is not None
@ -48,21 +58,30 @@ class TestScriptUnitTestRunner(BaseTest):
def test_fails_when_expected_output_does_not_match_actual_output(
self,
app: Flask,
client: FlaskClient,
with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel
) -> None:
"""Test_fails_when_expected_output_does_not_match_actual_output."""
app.config["THREAD_LOCAL_DATA"].process_instance_id = None
process_group_id = "test_logging_spiff_logger"
self.create_process_group(client, with_super_admin_user, process_group_id, process_group_id)
process_model_id = "simple_script"
load_test_spec(process_model_id, process_group_id=process_group_id)
process_model_identifier = f"{process_group_id}/{process_model_id}"
load_test_spec(
process_model_identifier,
bpmn_file_name=process_model_id,
process_model_source_directory=process_model_id
)
bpmn_process_instance = (
ProcessInstanceProcessor.get_bpmn_process_instance_from_process_model(
process_model_id, process_group_id
process_model_identifier
)
)
task = ProcessInstanceProcessor.get_task_by_bpmn_identifier(
"Activity_RunScript", bpmn_process_instance
"Activity_CalculateNewData", bpmn_process_instance
)
assert task is not None
@ -80,17 +99,26 @@ class TestScriptUnitTestRunner(BaseTest):
def test_script_with_unit_tests_when_hey_is_passed_in(
self,
app: Flask,
client: FlaskClient,
with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel
) -> None:
"""Test_script_with_unit_tests_when_hey_is_passed_in."""
app.config["THREAD_LOCAL_DATA"].process_instance_id = None
process_group_id = "script_with_unit_tests"
self.create_process_group(client, with_super_admin_user, process_group_id, process_group_id)
process_model_id = "script_with_unit_tests"
load_test_spec(process_model_id, process_group_id=process_group_id)
process_model_identifier = f"{process_group_id}/{process_model_id}"
load_test_spec(
process_model_identifier,
bpmn_file_name=process_model_id,
process_model_source_directory=process_model_id
)
bpmn_process_instance = (
ProcessInstanceProcessor.get_bpmn_process_instance_from_process_model(
process_model_id, process_group_id
process_model_identifier
)
)
task = ProcessInstanceProcessor.get_task_by_bpmn_identifier(
@ -110,17 +138,27 @@ class TestScriptUnitTestRunner(BaseTest):
def test_script_with_unit_tests_when_hey_is_not_passed_in(
self,
app: Flask,
client: FlaskClient,
with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel
) -> None:
"""Test_script_with_unit_tests_when_hey_is_not_passed_in."""
app.config["THREAD_LOCAL_DATA"].process_instance_id = None
process_group_id = "script_with_unit_tests"
self.create_process_group(client, with_super_admin_user, process_group_id, process_group_id)
process_model_id = "script_with_unit_tests"
load_test_spec(process_model_id, process_group_id=process_group_id)
process_model_identifier = f"{process_group_id}/{process_model_id}"
load_test_spec(
process_model_identifier,
bpmn_file_name=process_model_id,
process_model_source_directory=process_model_id
)
bpmn_process_instance = (
ProcessInstanceProcessor.get_bpmn_process_instance_from_process_model(
process_model_id, process_group_id
process_model_identifier
)
)
task = ProcessInstanceProcessor.get_task_by_bpmn_identifier(

View File

@ -3,6 +3,7 @@ import os
import pytest
from flask import Flask
from flask.testing import FlaskClient
from flask_bpmn.api.api_error import ApiError
from flask_bpmn.models.db import db
from SpiffWorkflow.dmn.parser.BpmnDmnParser import BpmnDmnParser # type: ignore
@ -10,6 +11,7 @@ from tests.spiffworkflow_backend.helpers.base_test import BaseTest
from tests.spiffworkflow_backend.helpers.test_data import load_test_spec
from spiffworkflow_backend.models.bpmn_process_id_lookup import BpmnProcessIdLookup
from spiffworkflow_backend.models.user import UserModel
from spiffworkflow_backend.services.process_model_service import ProcessModelService
from spiffworkflow_backend.services.spec_file_service import SpecFileService
@ -17,18 +19,25 @@ from spiffworkflow_backend.services.spec_file_service import SpecFileService
class TestSpecFileService(BaseTest):
"""TestSpecFileService."""
process_group_id = "test_process_group_id"
process_model_id = "call_activity_nested"
bpmn_file_name = "call_activity_nested.bpmn"
call_activity_nested_relative_file_path = os.path.join(
"test_process_group_id", "call_activity_nested", "call_activity_nested.bpmn"
process_group_id, process_model_id, bpmn_file_name
)
def test_can_store_process_ids_for_lookup(
self, app: Flask, with_db_and_bpmn_file_cleanup: None
self, app: Flask, client: FlaskClient, with_db_and_bpmn_file_cleanup: None, with_super_admin_user: UserModel
) -> None:
"""Test_can_store_process_ids_for_lookup."""
load_test_spec(
"call_activity_nested",
process_model_source_directory="call_activity_nested",
bpmn_file_name="call_activity_nested",
self.basic_test_setup(
client=client,
user=with_super_admin_user,
process_group_id=self.process_group_id,
process_model_id=self.process_model_id,
bpmn_file_name=self.bpmn_file_name,
bpmn_file_location="call_activity_nested"
)
bpmn_process_id_lookups = BpmnProcessIdLookup.query.all()
assert len(bpmn_process_id_lookups) == 1
@ -39,14 +48,17 @@ class TestSpecFileService(BaseTest):
)
def test_fails_to_save_duplicate_process_id(
self, app: Flask, with_db_and_bpmn_file_cleanup: None
self, app: Flask, client: FlaskClient, with_db_and_bpmn_file_cleanup: None, with_super_admin_user: UserModel
) -> None:
"""Test_fails_to_save_duplicate_process_id."""
bpmn_process_identifier = "Level1"
load_test_spec(
"call_activity_nested",
process_model_source_directory="call_activity_nested",
bpmn_file_name="call_activity_nested",
self.basic_test_setup(
client=client,
user=with_super_admin_user,
process_group_id=self.process_group_id,
process_model_id=self.process_model_id,
bpmn_file_name=self.bpmn_file_name,
bpmn_file_location=self.process_model_id
)
bpmn_process_id_lookups = BpmnProcessIdLookup.query.all()
assert len(bpmn_process_id_lookups) == 1
@ -69,25 +81,26 @@ class TestSpecFileService(BaseTest):
)
def test_updates_relative_file_path_when_appropriate(
self, app: Flask, with_db_and_bpmn_file_cleanup: None
self, app: Flask, client: FlaskClient, with_db_and_bpmn_file_cleanup: None, with_super_admin_user: UserModel
) -> None:
"""Test_updates_relative_file_path_when_appropriate."""
bpmn_process_identifier = "Level1"
bpmn_file_relative_path = os.path.join(
"test_process_group_id", "call_activity_nested", "new_bpmn_file.bpmn"
)
process_id_lookup = BpmnProcessIdLookup(
bpmn_process_identifier=bpmn_process_identifier,
bpmn_file_relative_path=bpmn_file_relative_path,
bpmn_file_relative_path=self.call_activity_nested_relative_file_path,
)
db.session.add(process_id_lookup)
db.session.commit()
load_test_spec(
"call_activity_nested",
process_model_source_directory="call_activity_nested",
bpmn_file_name="call_activity_nested",
self.basic_test_setup(
client=client,
user=with_super_admin_user,
process_group_id=self.process_group_id,
process_model_id=self.process_model_id,
bpmn_file_name=self.bpmn_file_name,
bpmn_file_location=self.process_model_id
)
bpmn_process_id_lookups = BpmnProcessIdLookup.query.all()
assert len(bpmn_process_id_lookups) == 1
assert (
@ -100,7 +113,7 @@ class TestSpecFileService(BaseTest):
)
def test_load_reference_information(
self, app: Flask, with_db_and_bpmn_file_cleanup: None
self, app: Flask, client: FlaskClient, with_db_and_bpmn_file_cleanup: None, with_super_admin_user: UserModel
) -> None:
"""Test_load_reference_information.
@ -113,12 +126,23 @@ class TestSpecFileService(BaseTest):
a DMN file can (theoretically) contain many decisions. So this
is an array.
"""
load_test_spec(
"call_activity_nested",
process_model_source_directory="call_activity_nested",
process_group_id = "test_group"
process_model_id = "call_activity_nested"
bpmn_file_name = "call_activity_nested.bpmn"
process_model_identifier = self.basic_test_setup(
client=client,
user=with_super_admin_user,
process_group_id=process_group_id,
process_model_id=process_model_id,
# bpmn_file_name=bpmn_file_name,
bpmn_file_location=process_model_id
)
# load_test_spec(
# ,
# process_model_source_directory="call_activity_nested",
# )
process_model_info = ProcessModelService().get_process_model(
"call_activity_nested"
process_model_identifier
)
files = SpecFileService.get_files(process_model_info)

View File

@ -1,23 +1,32 @@
"""Test_various_bpmn_constructs."""
from flask.app import Flask
from flask.testing import FlaskClient
from spiffworkflow_backend.models.user import UserModel
from spiffworkflow_backend.services.process_model_service import ProcessModelService
from spiffworkflow_backend.services.process_instance_processor import ProcessInstanceProcessor
from tests.spiffworkflow_backend.helpers.base_test import BaseTest
from tests.spiffworkflow_backend.helpers.test_data import load_test_spec
from spiffworkflow_backend.services.process_instance_processor import (
ProcessInstanceProcessor,
)
class TestVariousBpmnConstructs(BaseTest):
"""TestVariousBpmnConstructs."""
def test_running_process_with_timer_intermediate_catch_event(
self, app: Flask, with_db_and_bpmn_file_cleanup: None
self,
app: Flask,
client: FlaskClient,
with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel
) -> None:
"""Test_running_process_with_timer_intermediate_catch_event."""
process_model = load_test_spec(
"timers_intermediate_catch_event",
process_model_source_directory="timer_intermediate_catch_event",
process_model_identifier = self.basic_test_setup(
client,
with_super_admin_user,
"test_group",
"timer_intermediate_catch_event"
)
process_model = ProcessModelService().get_process_model(
process_model_id=process_model_identifier
)
process_instance = self.create_process_instance_from_process_model(

View File

@ -68,6 +68,7 @@ describe('process-instances', () => {
cy.login();
cy.navigateToProcessModel(
'Acceptance Tests Group One',
'Acceptance Tests Model 1',
'acceptance-tests-model-1'
);
});
@ -90,28 +91,29 @@ describe('process-instances', () => {
cy.runPrimaryBpmnFile();
// Change dmn
cy.contains(dmnFile).click();
cy.contains(`Process Model File: ${dmnFile}`);
cy.getBySel('files-accordion').click();
cy.getBySel(`edit-file-${dmnFile.replace('.', '-')}`).click();
updateDmnText(originalDmnOutputForKevin, newDmnOutputForKevin);
cy.contains('acceptance-tests-model-1').click();
cy.runPrimaryBpmnFile();
cy.contains(dmnFile).click();
cy.contains(`Process Model File: ${dmnFile}`);
cy.getBySel('files-accordion').click();
cy.getBySel(`edit-file-${dmnFile.replace('.', '-')}`).click();
updateDmnText(newDmnOutputForKevin, originalDmnOutputForKevin);
cy.contains('acceptance-tests-model-1').click();
cy.runPrimaryBpmnFile();
// Change bpmn
cy.contains(bpmnFile).click();
cy.getBySel('files-accordion').click();
cy.getBySel(`edit-file-${bpmnFile.replace('.', '-')}`).click();
cy.contains(`Process Model File: ${bpmnFile}`);
updateBpmnPythonScript(newPythonScript);
cy.contains('acceptance-tests-model-1').click();
cy.runPrimaryBpmnFile();
cy.contains(bpmnFile).click();
cy.contains(`Process Model File: ${bpmnFile}`);
cy.getBySel('files-accordion').click();
cy.getBySel(`edit-file-${bpmnFile.replace('.', '-')}`).click();
updateBpmnPythonScript(originalPythonScript);
cy.contains('acceptance-tests-model-1').click();
cy.runPrimaryBpmnFile();
@ -125,13 +127,15 @@ describe('process-instances', () => {
const bpmnFile = 'process_model_one.bpmn';
// Change bpmn
cy.contains(bpmnFile).click();
cy.getBySel('files-accordion').click();
cy.getBySel(`edit-file-${bpmnFile.replace('.', '-')}`).click();
cy.contains(`Process Model File: ${bpmnFile}`);
updateBpmnPythonScriptWithMonaco(newPythonScript);
cy.contains('acceptance-tests-model-1').click();
cy.runPrimaryBpmnFile();
cy.contains(bpmnFile).click();
cy.getBySel('files-accordion').click();
cy.getBySel(`edit-file-${bpmnFile.replace('.', '-')}`).click();
cy.contains(`Process Model File: ${bpmnFile}`);
updateBpmnPythonScriptWithMonaco(originalPythonScript);
cy.contains('acceptance-tests-model-1').click();

View File

@ -25,10 +25,11 @@ describe('tasks', () => {
it('can complete and navigate a form', () => {
const groupDisplayName = 'Acceptance Tests Group One';
const modelId = `acceptance-tests-model-2`;
const modelDisplayName = `Acceptance Tests Model 2`;
const completedTaskClassName = 'completed-task-highlight';
const activeTaskClassName = 'active-task-highlight';
cy.navigateToProcessModel(groupDisplayName, modelId);
cy.navigateToProcessModel(groupDisplayName, modelDisplayName, modelId);
// avoid reloading so we can click on the task link that appears on running the process instance
cy.runPrimaryBpmnFile(false);
@ -67,7 +68,7 @@ describe('tasks', () => {
);
cy.contains('Task: get_user_generated_number_four');
cy.navigateToProcessModel(groupDisplayName, modelId);
cy.navigateToProcessModel(groupDisplayName, modelDisplayName, modelId);
cy.getBySel('process-instance-list-link').click();
cy.assertAtLeastOneItemInPaginatedResults();
@ -84,7 +85,7 @@ describe('tasks', () => {
checkTaskHasClass('form2', completedTaskClassName);
checkTaskHasClass('form3', completedTaskClassName);
checkTaskHasClass('form4', activeTaskClassName);
cy.get('.modal .btn-close').click();
cy.get('.is-visible .cds--modal-close').click();
cy.navigateToHome();
cy.contains('Tasks').should('exist');
@ -99,7 +100,7 @@ describe('tasks', () => {
);
cy.url().should('include', '/tasks');
cy.navigateToProcessModel(groupDisplayName, modelId);
cy.navigateToProcessModel(groupDisplayName, modelDisplayName, modelId);
cy.getBySel('process-instance-list-link').click();
cy.assertAtLeastOneItemInPaginatedResults();
@ -112,6 +113,7 @@ describe('tasks', () => {
it('can paginate items', () => {
cy.navigateToProcessModel(
'Acceptance Tests Group One',
'Acceptance Tests Model 2',
'acceptance-tests-model-2'
);

View File

@ -91,13 +91,12 @@ Cypress.Commands.add('runPrimaryBpmnFile', (reload = true) => {
Cypress.Commands.add(
'navigateToProcessModel',
(groupDisplayName, modelDisplayName) => {
(groupDisplayName, modelDisplayName, modelIdentifier) => {
cy.navigateToAdmin();
cy.contains(groupDisplayName).click();
cy.contains(`Process Group: ${groupDisplayName}`);
// https://stackoverflow.com/q/51254946/6090676
cy.getBySel('process-model-show-link').contains(modelDisplayName).click();
// cy.url().should('include', `process-models/${groupDisplayName}/${modelDisplayName}`);
cy.getBySel('process-model-show-link').contains(modelIdentifier).click();
cy.contains(`Process Model: ${modelDisplayName}`);
}
);
@ -115,10 +114,7 @@ Cypress.Commands.add('basicPaginationTest', () => {
});
Cypress.Commands.add('assertAtLeastOneItemInPaginatedResults', () => {
cy.getBySel('total-paginated-items')
.invoke('text')
.then(parseFloat)
.should('be.gt', 0);
cy.contains(/\b[1-9]\d*[1-9]\d* of [1-9]\d*/);
});
Cypress.Commands.add('assertNoItemInPaginatedResults', () => {

View File

@ -1,5 +1,6 @@
import React from 'react';
import HttpService from '../services/HttpService';
import { modifyProcessModelPath } from '../helpers';
type Props = {
processGroupId: string;
@ -27,7 +28,10 @@ export default class FileInput extends React.Component<Props> {
handleSubmit(event: any) {
event.preventDefault();
const url = `/process-models/${this.processGroupId}/${this.processModelId}/files`;
const modifiedProcessModelId = modifyProcessModelPath(
`${this.processGroupId}/${this.processModelId}`
);
const url = `/process-models/${modifiedProcessModelId}/files`;
const formData = new FormData();
formData.append('file', this.fileInput.current.files[0]);
formData.append('fileName', this.fileInput.current.files[0].name);

View File

@ -1,5 +1,6 @@
// @ts-ignore
import { Breadcrumb, BreadcrumbItem } from '@carbon/react';
import { splitProcessModelId } from '../helpers';
import { HotCrumbItem } from '../interfaces';
type OwnProps = {
@ -9,6 +10,39 @@ type OwnProps = {
hotCrumbs?: HotCrumbItem[];
};
const explodeCrumb = (crumb: HotCrumbItem) => {
const url: string = crumb[1] || '';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [_unused, processModelId, link] = url.split(':');
const processModelIdSegments = splitProcessModelId(processModelId);
const paths: string[] = [];
const lastPathItem = processModelIdSegments.pop();
const breadcrumbItems = processModelIdSegments.map(
(processModelIdSegment: string) => {
paths.push(processModelIdSegment);
const fullUrl = `/admin/process-groups/${paths.join(':')}`;
return (
<BreadcrumbItem key={processModelIdSegment} href={fullUrl}>
{processModelIdSegment}
</BreadcrumbItem>
);
}
);
if (link === 'link') {
const lastUrl = `/admin/process-models/${paths.join(':')}:${lastPathItem}`;
breadcrumbItems.push(
<BreadcrumbItem key={lastPathItem} href={lastUrl}>
{lastPathItem}
</BreadcrumbItem>
);
} else {
breadcrumbItems.push(
<BreadcrumbItem isCurrentPage>{lastPathItem}</BreadcrumbItem>
);
}
return breadcrumbItems;
};
export default function ProcessBreadcrumb({
processModelId,
processGroupId,
@ -18,28 +52,22 @@ export default function ProcessBreadcrumb({
let processGroupBreadcrumb = null;
let processModelBreadcrumb = null;
if (hotCrumbs) {
const lastItem = hotCrumbs.pop();
if (lastItem === undefined) {
return null;
}
const lastCrumb = (
<BreadcrumbItem isCurrentPage>{lastItem[0]}</BreadcrumbItem>
);
const leadingCrumbLinks = hotCrumbs.map((crumb: any) => {
const valueLabel = crumb[0];
const url = crumb[1];
if (!url) {
return <BreadcrumbItem isCurrentPage>{valueLabel}</BreadcrumbItem>;
}
if (url && url.startsWith('process_model:')) {
return explodeCrumb(crumb);
}
return (
<BreadcrumbItem key={valueLabel} href={url}>
{valueLabel}
</BreadcrumbItem>
);
});
return (
<Breadcrumb noTrailingSlash>
{leadingCrumbLinks}
{lastCrumb}
</Breadcrumb>
);
return <Breadcrumb noTrailingSlash>{leadingCrumbLinks}</Breadcrumb>;
}
if (processModelId) {
if (linkProcessModel) {

View File

@ -2,7 +2,11 @@ import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
// @ts-ignore
import { Button, ButtonSet, Form, Stack, TextInput } from '@carbon/react';
import { slugifyString } from '../helpers';
import {
getGroupFromModifiedModelId,
modifyProcessModelPath,
slugifyString,
} from '../helpers';
import HttpService from '../services/HttpService';
import { ProcessModel } from '../interfaces';
import ButtonWithConfirmation from './ButtonWithConfirmation';
@ -23,17 +27,20 @@ export default function ProcessModelForm({
useState<boolean>(false);
const [displayNameInvalid, setDisplayNameInvalid] = useState<boolean>(false);
const navigate = useNavigate();
const modifiedProcessModelPath = modifyProcessModelPath(processModel.id);
const navigateToProcessModel = (_result: any) => {
if (processModel) {
navigate(
`/admin/process-models/${processModel.process_group_id}/${processModel.id}`
);
navigate(`/admin/process-models/${modifiedProcessModelPath}`);
}
};
const navigateToProcessModels = (_result: any) => {
navigate(`/admin/process-models/${processModel.process_group_id}`);
navigate(
`/admin/process-groups/${getGroupFromModifiedModelId(
modifiedProcessModelPath
)}`
);
};
const hasValidIdentifier = (identifierToCheck: string) => {
@ -42,7 +49,7 @@ export default function ProcessModelForm({
const deleteProcessModel = () => {
HttpService.makeCallToBackend({
path: `/process-models/${processModel.process_group_id}/${processModel.id}`,
path: `/process-models/${modifiedProcessModelPath}`,
successCallback: navigateToProcessModels,
httpMethod: 'DELETE',
});
@ -64,7 +71,7 @@ export default function ProcessModelForm({
}
let path = `/process-models`;
if (mode === 'edit') {
path = `/process-models/${processModel.process_group_id}/${processModel.id}`;
path = `/process-models/${modifiedProcessModelPath}`;
}
let httpMethod = 'POST';
if (mode === 'edit') {
@ -77,10 +84,9 @@ export default function ProcessModelForm({
if (mode === 'new') {
Object.assign(postBody, {
id: processModel.id,
process_group_id: processModel.process_group_id,
});
}
console.log('postBody', postBody);
HttpService.makeCallToBackend({
path,
successCallback: navigateToProcessModel,
@ -176,7 +182,6 @@ export default function ProcessModelForm({
}
return <ButtonSet>{buttons}</ButtonSet>;
};
console.log('processModel.process_group_id', processModel.process_group_id);
return (
<Form onSubmit={handleFormSubmission}>
<Stack gap={5}>

View File

@ -21,7 +21,7 @@ export default function ProcessModelSearch({
const shouldFilterProcessModel = (options: any) => {
const processModel: ProcessModel = options.item;
const { inputValue } = options;
return `${processModel.process_group_id}/${processModel.id} (${processModel.display_name})`.includes(
return `${processModel.id} (${processModel.display_name})`.includes(
inputValue
);
};
@ -33,9 +33,10 @@ export default function ProcessModelSearch({
items={processModels}
itemToString={(processModel: ProcessModel) => {
if (processModel) {
return `${processModel.process_group_id}/${
processModel.id
} (${truncateString(processModel.display_name, 20)})`;
return `${processModel.id} (${truncateString(
processModel.display_name,
20
)})`;
}
return null;
}}

View File

@ -59,7 +59,6 @@ import { makeid } from '../helpers';
type OwnProps = {
processModelId: string;
processGroupId: string;
diagramType: string;
readyOrWaitingBpmnTaskIds?: string[] | null;
completedTasksBpmnIds?: string[] | null;
@ -83,7 +82,6 @@ type OwnProps = {
// https://codesandbox.io/s/quizzical-lake-szfyo?file=/src/App.js was a handy reference
export default function ReactDiagramEditor({
processModelId,
processGroupId,
diagramType,
readyOrWaitingBpmnTaskIds,
completedTasksBpmnIds,
@ -408,6 +406,7 @@ export default function ReactDiagramEditor({
}
function fetchDiagramFromURL(urlToUse: any) {
console.log(`urlToUse: ${urlToUse}`);
fetch(urlToUse)
.then((response) => response.text())
.then((text) => {
@ -424,7 +423,7 @@ export default function ReactDiagramEditor({
function fetchDiagramFromJsonAPI() {
HttpService.makeCallToBackend({
path: `/process-models/${processGroupId}/${processModelId}/files/${fileName}`,
path: `/process-models/${processModelId}/files/${fileName}`,
successCallback: setDiagramXMLStringFromResponseJson,
});
}
@ -470,7 +469,6 @@ export default function ReactDiagramEditor({
completedTasksBpmnIds,
fileName,
performingXmlUpdates,
processGroupId,
processModelId,
url,
]);

View File

@ -113,3 +113,22 @@ export const truncateString = (text: string, len: number) => {
}
return text;
};
// Because of limitations in the way openapi defines parameters, we have to modify process models ids
// which are basically paths to the models
export const modifyProcessModelPath = (path: String) => {
return path.replace('/', ':') || '';
};
export const unModifyProcessModelPath = (path: String) => {
return path.replace(':', '/') || '';
};
export const getGroupFromModifiedModelId = (modifiedId: String) => {
const finalSplitIndex = modifiedId.lastIndexOf(':');
return modifiedId.slice(0, finalSplitIndex);
};
export const splitProcessModelId = (processModelId: String) => {
return processModelId.split('/');
};

View File

@ -44,6 +44,14 @@ span.bjs-crumb {
vertical-align: middle;
}
.cds--breadcrumb {
margin-bottom: 2em;
}
.process-description {
margin-bottom: 2em;
}
.diagram-editor-canvas {
border:1px solid #000000;
height:70vh;

View File

@ -25,6 +25,21 @@
// background-color: colors.$gray-100;
color: white;
}
h1{
height: 36px;
font-family: 'IBM Plex Sans';
font-style: normal;
font-weight: 400;
font-size: 28px;
line-height: 36px;
color: #161616;
flex: none;
order: 0;
align-self: stretch;
flex-grow: 0;
margin-bottom: 1em
}
.cds--breadcrumb-item a.cds--link:hover {
color: #525252;

View File

@ -27,7 +27,6 @@ export interface ProcessFile {
content_type: string;
last_modified: string;
name: string;
process_group_id: string;
process_model_id: string;
references: ProcessFileReference[];
size: number;
@ -38,7 +37,6 @@ export interface ProcessFile {
export interface ProcessModel {
id: string;
description: string;
process_group_id: string;
display_name: string;
primary_file_name: string;
files: ProcessFile[];

View File

@ -53,35 +53,35 @@ export default function AdminRoutes() {
element={<ProcessModelNew />}
/>
<Route
path="process-models/:process_group_id/:process_model_id"
path="process-models/:process_model_id"
element={<ProcessModelShow />}
/>
<Route
path="process-models/:process_group_id/:process_model_id/files"
path="process-models/:process_model_id/files"
element={<ProcessModelEditDiagram />}
/>
<Route
path="process-models/:process_group_id/:process_model_id/files/:file_name"
path="process-models/:process_model_id/files/:file_name"
element={<ProcessModelEditDiagram />}
/>
<Route
path="process-models/:process_group_id/:process_model_id/process-instances"
path="process-models/:process_model_id/process-instances"
element={<ProcessInstanceList />}
/>
<Route
path="process-models/:process_group_id/:process_model_id/edit"
path="process-models/:process_model_id/edit"
element={<ProcessModelEdit />}
/>
<Route
path="process-models/:process_group_id/:process_model_id/process-instances/:process_instance_id"
path="process-models/:process_model_id/process-instances/:process_instance_id"
element={<ProcessInstanceShow />}
/>
<Route
path="process-models/:process_group_id/:process_model_id/process-instances/:process_instance_id/:spiff_step"
path="process-models/:process_model_id/process-instances/:process_instance_id/:spiff_step"
element={<ProcessInstanceShow />}
/>
<Route
path="process-models/:process_group_id/:process_model_id/process-instances/reports"
path="process-models/:process_model_id/process-instances/reports"
element={<ProcessInstanceReportList />}
/>
<Route
@ -97,15 +97,15 @@ export default function AdminRoutes() {
element={<ProcessInstanceReportEdit />}
/>
<Route
path="process-models/:process_group_id/:process_model_id/form"
path="process-models/:process_model_id/form"
element={<ReactFormEditor />}
/>
<Route
path="process-models/:process_group_id/:process_model_id/form/:file_name"
path="process-models/:process_model_id/form/:file_name"
element={<ReactFormEditor />}
/>
<Route
path="process-models/:process_group_id/:process_model_id/process-instances/:process_instance_id/logs"
path="process-models/:process_model_id/process-instances/:process_instance_id/logs"
element={<ProcessInstanceLogList />}
/>
<Route path="process-instances" element={<ProcessInstanceList />} />

View File

@ -3,7 +3,10 @@ import { useEffect, useState } from 'react';
import { Button, Table } from '@carbon/react';
import { Link, useSearchParams } from 'react-router-dom';
import PaginationForTable from '../components/PaginationForTable';
import { getPageInfoFromSearchParams } from '../helpers';
import {
getPageInfoFromSearchParams,
modifyProcessModelPath,
} from '../helpers';
import HttpService from '../services/HttpService';
import { PaginationObject, RecentProcessModel } from '../interfaces';
@ -39,12 +42,15 @@ export default function HomePage() {
const rows = tasks.map((row) => {
const rowToUse = row as any;
const taskUrl = `/tasks/${rowToUse.process_instance_id}/${rowToUse.id}`;
const modifiedProcessModelIdentifier = modifyProcessModelPath(
rowToUse.process_model_identifier
);
return (
<tr key={rowToUse.id}>
<td>
<Link
data-qa="process-model-show-link"
to={`/admin/process-models/${rowToUse.process_group_identifier}/${rowToUse.process_model_identifier}`}
to={`/admin/process-models/${modifiedProcessModelIdentifier}`}
>
{rowToUse.process_model_display_name}
</Link>
@ -52,7 +58,7 @@ export default function HomePage() {
<td>
<Link
data-qa="process-instance-show-link"
to={`/admin/process-models/${rowToUse.process_group_identifier}/${rowToUse.process_model_identifier}/process-instances/${rowToUse.process_instance_id}`}
to={`/admin/process-models/${modifiedProcessModelIdentifier}/process-instances/${rowToUse.process_instance_id}`}
>
View {rowToUse.process_instance_id}
</Link>
@ -96,6 +102,9 @@ export default function HomePage() {
const buildRecentProcessModelSection = () => {
const rows = recentProcessModels.map((row) => {
const rowToUse = row as any;
const modifiedProcessModelId = modifyProcessModelPath(
rowToUse.processModelIdentifier
);
return (
<tr
key={`${rowToUse.processGroupIdentifier}/${rowToUse.processModelIdentifier}`}
@ -103,7 +112,7 @@ export default function HomePage() {
<td>
<Link
data-qa="process-model-show-link"
to={`/admin/process-models/${rowToUse.processGroupIdentifier}/${rowToUse.processModelIdentifier}`}
to={`/admin/process-models/${modifiedProcessModelId}`}
>
{rowToUse.processModelDisplayName}
</Link>

View File

@ -13,7 +13,10 @@ import {
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
import PaginationForTable from '../components/PaginationForTable';
import HttpService from '../services/HttpService';
import { getPageInfoFromSearchParams } from '../helpers';
import {
getPageInfoFromSearchParams,
modifyProcessModelPath,
} from '../helpers';
import { CarbonComboBoxSelection, ProcessGroup } from '../interfaces';
import ProcessModelSearch from '../components/ProcessModelSearch';
@ -36,7 +39,7 @@ export default function ProcessGroupList() {
};
const processResultForProcessModels = (result: any) => {
const selectionArray = result.results.map((item: any) => {
const label = `${item.process_group_id}/${item.id}`;
const label = `${item.id}`;
Object.assign(item, { label });
return item;
});
@ -120,7 +123,7 @@ export default function ProcessGroupList() {
const processModelSearchOnChange = (selection: CarbonComboBoxSelection) => {
const processModel = selection.selectedItem;
navigate(
`/admin/process-models/${processModel.process_group_id}/${processModel.id}`
`/admin/process-models/${modifyProcessModelPath(processModel.id)}`
);
};
return (

View File

@ -24,6 +24,7 @@ export default function ProcessGroupShow() {
setPagination(result.pagination);
};
const processResult = (result: any) => {
console.log(result);
setProcessGroup(result);
HttpService.makeCallToBackend({
path: `/process-models?process_group_identifier=${params.process_group_id}&per_page=${perPage}&page=${page}`,
@ -41,11 +42,12 @@ export default function ProcessGroupShow() {
return null;
}
const rows = processModels.map((row) => {
const modifiedProcessModelId: String = (row as any).id.replace('/', ':');
return (
<tr key={(row as any).id}>
<td>
<Link
to={`/admin/process-models/${processGroup.id}/${(row as any).id}`}
to={`/admin/process-models/${modifiedProcessModelId}`}
data-qa="process-model-show-link"
>
{(row as any).id}

View File

@ -30,6 +30,7 @@ import {
convertSecondsToFormattedDate,
getPageInfoFromSearchParams,
getProcessModelFullIdentifierFromSearchParams,
modifyProcessModelPath,
} from '../helpers';
import PaginationForTable from '../components/PaginationForTable';
@ -85,7 +86,6 @@ export default function ProcessInstanceList() {
const parametersToGetFromSearchParams = useMemo(() => {
return {
process_group_identifier: null,
process_model_identifier: null,
process_status: null,
};
@ -137,7 +137,7 @@ export default function ProcessInstanceList() {
const processModelFullIdentifier =
getProcessModelFullIdentifierFromSearchParams(searchParams);
const selectionArray = result.results.map((item: any) => {
const label = `${item.process_group_id}/${item.id}`;
const label = `${item.id}`;
Object.assign(item, { label });
if (label === processModelFullIdentifier) {
setProcessModelSelection(item);
@ -243,7 +243,7 @@ export default function ProcessInstanceList() {
}
if (processModelSelection) {
queryParamString += `&process_group_identifier=${processModelSelection.process_group_id}&process_model_identifier=${processModelSelection.id}`;
queryParamString += `&process_model_identifier=${processModelSelection.id}`;
}
setErrorMessage(null);
@ -382,7 +382,6 @@ export default function ProcessInstanceList() {
const buildTable = () => {
const headerLabels: Record<string, string> = {
id: 'Process Instance Id',
process_group_identifier: 'Process Group',
process_model_identifier: 'Process Model',
start_in_seconds: 'Start Time',
end_in_seconds: 'End Time',
@ -397,10 +396,13 @@ export default function ProcessInstanceList() {
});
const formatProcessInstanceId = (row: any, id: any) => {
const modifiedProcessModelId: String = modifyProcessModelPath(
(row as any).process_model_identifier
);
return (
<Link
data-qa="process-instance-show-link"
to={`/admin/process-models/${row.process_group_identifier}/${row.process_model_identifier}/process-instances/${row.id}`}
to={`/admin/process-models/${modifiedProcessModelId}/process-instances/${row.id}`}
>
{id}
</Link>
@ -414,7 +416,7 @@ export default function ProcessInstanceList() {
const formatProcessModelIdentifier = (row: any, identifier: any) => {
return (
<Link
to={`/admin/process-models/${row.process_group_identifier}/${identifier}`}
to={`/admin/process-models/${modifyProcessModelPath(identifier)}`}
>
{identifier}
</Link>
@ -429,7 +431,6 @@ export default function ProcessInstanceList() {
const columnFormatters: Record<string, any> = {
id: formatProcessInstanceId,
process_group_identifier: formatProcessGroupIdentifier,
process_model_identifier: formatProcessModelIdentifier,
start_in_seconds: formatSecondsForDisplay,
end_in_seconds: formatSecondsForDisplay,

View File

@ -7,6 +7,7 @@ import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
import {
getPageInfoFromSearchParams,
convertSecondsToFormattedDate,
modifyProcessModelPath,
} from '../helpers';
import HttpService from '../services/HttpService';
@ -15,6 +16,9 @@ export default function ProcessInstanceLogList() {
const [searchParams] = useSearchParams();
const [processInstanceLogs, setProcessInstanceLogs] = useState([]);
const [pagination, setPagination] = useState(null);
const modifiedProcessModelId = modifyProcessModelPath(
`${params.process_model_id}`
);
useEffect(() => {
const setProcessInstanceLogListFromResult = (result: any) => {
@ -23,7 +27,7 @@ export default function ProcessInstanceLogList() {
};
const { page, perPage } = getPageInfoFromSearchParams(searchParams);
HttpService.makeCallToBackend({
path: `/process-models/${params.process_group_id}/${params.process_model_id}/process-instances/${params.process_instance_id}/logs?per_page=${perPage}&page=${page}`,
path: `/process-instances/${params.process_instance_id}/logs?per_page=${perPage}&page=${page}`,
successCallback: setProcessInstanceLogListFromResult,
});
}, [searchParams, params]);
@ -32,6 +36,7 @@ export default function ProcessInstanceLogList() {
// return null;
const rows = processInstanceLogs.map((row) => {
const rowToUse = row as any;
console.log(`rowToUse: ${rowToUse}`);
return (
<tr key={rowToUse.id}>
<td>{rowToUse.bpmn_process_identifier}</td>
@ -43,7 +48,7 @@ export default function ProcessInstanceLogList() {
<td>
<Link
data-qa="process-instance-show-link"
to={`/admin/process-models/${params.process_group_id}/${params.process_model_id}/process-instances/${rowToUse.process_instance_id}/${rowToUse.spiff_step}`}
to={`/admin/process-models/${modifiedProcessModelId}/process-instances/${rowToUse.process_instance_id}/${rowToUse.spiff_step}`}
>
{convertSecondsToFormattedDate(rowToUse.timestamp)}
</Link>
@ -83,7 +88,7 @@ export default function ProcessInstanceLogList() {
perPage={perPage}
pagination={pagination}
tableToDisplay={buildTable()}
path={`/admin/process-models/${params.process_group_id}/${params.process_model_id}/process-instances/${params.process_instance_id}/logs`}
path={`/admin/process-instances/${params.process_instance_id}/logs`}
/>
</main>
);

View File

@ -4,10 +4,14 @@ import { Button, Table } from '@carbon/react';
import { useParams, Link } from 'react-router-dom';
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
import HttpService from '../services/HttpService';
import { modifyProcessModelPath } from '../helpers';
export default function ProcessInstanceReportList() {
const params = useParams();
const [processInstanceReports, setProcessInstanceReports] = useState([]);
const modifiedProcessModelId = modifyProcessModelPath(
params.process_model_id || ''
);
useEffect(() => {
HttpService.makeCallToBackend({
@ -23,7 +27,7 @@ export default function ProcessInstanceReportList() {
<tr key={(row as any).id}>
<td>
<Link
to={`/admin/process-models/${params.process_group_id}/${params.process_model_id}/process-instances/reports/${rowToUse.identifier}`}
to={`/admin/process-models/${modifiedProcessModelId}/process-instances/reports/${rowToUse.identifier}`}
>
{rowToUse.identifier}
</Link>
@ -52,7 +56,7 @@ export default function ProcessInstanceReportList() {
/>
<h2>Reports for Process Model: {params.process_model_id}</h2>
<Button
href={`/admin/process-models/${params.process_group_id}/${params.process_model_id}/process-instances/reports/new`}
href={`/admin/process-models/${modifiedProcessModelId}/process-instances/reports/new`}
>
Add a process instance report
</Button>

View File

@ -6,7 +6,10 @@ import { Button, Modal, Stack } from '@carbon/react';
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
import HttpService from '../services/HttpService';
import ReactDiagramEditor from '../components/ReactDiagramEditor';
import { convertSecondsToFormattedDate } from '../helpers';
import {
convertSecondsToFormattedDate,
unModifyProcessModelPath,
} from '../helpers';
import ButtonWithConfirmation from '../components/ButtonWithConfirmation';
import ErrorContext from '../contexts/ErrorContext';
@ -22,15 +25,20 @@ export default function ProcessInstanceShow() {
const setErrorMessage = (useContext as any)(ErrorContext)[1];
const unModifiedProcessModelId = unModifyProcessModelPath(
`${params.process_model_id}`
);
const modifiedProcessModelId = params.process_model_id;
const navigateToProcessInstances = (_result: any) => {
navigate(
`/admin/process-instances?process_group_identifier=${params.process_group_id}&process_model_identifier=${params.process_model_id}`
`/admin/process-instances?process_model_identifier=${unModifiedProcessModelId}`
);
};
useEffect(() => {
HttpService.makeCallToBackend({
path: `/process-models/${params.process_group_id}/${params.process_model_id}/process-instances/${params.process_instance_id}`,
path: `/process-models/${modifiedProcessModelId}/process-instances/${params.process_instance_id}`,
successCallback: setProcessInstance,
});
if (typeof params.spiff_step === 'undefined')
@ -43,11 +51,11 @@ export default function ProcessInstanceShow() {
path: `/process-instance/${params.process_instance_id}/tasks?all_tasks=true&spiff_step=${params.spiff_step}`,
successCallback: setTasks,
});
}, [params]);
}, [params, modifiedProcessModelId]);
const deleteProcessInstance = () => {
HttpService.makeCallToBackend({
path: `/process-models/${params.process_group_id}/${params.process_model_id}/process-instances/${params.process_instance_id}`,
path: `/process-instances/${params.process_instance_id}`,
successCallback: navigateToProcessInstances,
httpMethod: 'DELETE',
});
@ -60,7 +68,7 @@ export default function ProcessInstanceShow() {
const terminateProcessInstance = () => {
HttpService.makeCallToBackend({
path: `/process-models/${params.process_group_id}/${params.process_model_id}/process-instances/${params.process_instance_id}/terminate`,
path: `/process-instances/${params.process_instance_id}/terminate`,
successCallback: refreshPage,
httpMethod: 'POST',
});
@ -68,7 +76,7 @@ export default function ProcessInstanceShow() {
const suspendProcessInstance = () => {
HttpService.makeCallToBackend({
path: `/process-models/${params.process_group_id}/${params.process_model_id}/process-instances/${params.process_instance_id}/suspend`,
path: `/process-instances/${params.process_instance_id}/suspend`,
successCallback: refreshPage,
httpMethod: 'POST',
});
@ -76,7 +84,7 @@ export default function ProcessInstanceShow() {
const resumeProcessInstance = () => {
HttpService.makeCallToBackend({
path: `/process-models/${params.process_group_id}/${params.process_model_id}/process-instances/${params.process_instance_id}/resume`,
path: `/process-instances/${params.process_instance_id}/resume`,
successCallback: refreshPage,
httpMethod: 'POST',
});
@ -125,7 +133,7 @@ export default function ProcessInstanceShow() {
<Link
reloadDocument
data-qa="process-instance-step-link"
to={`/admin/process-models/${params.process_group_id}/${
to={`/admin/process-models/${
params.process_model_id
}/process-instances/${params.process_instance_id}/${
currentSpiffStep(processInstanceToUse) + distance
@ -179,7 +187,7 @@ export default function ProcessInstanceShow() {
<li>
<Link
data-qa="process-instance-log-list-link"
to={`/admin/process-models/${params.process_group_id}/${params.process_model_id}/process-instances/${params.process_instance_id}/logs`}
to={`/admin/process-models/${modifiedProcessModelId}/process-instances/${params.process_instance_id}/logs`}
>
Logs
</Link>
@ -187,7 +195,7 @@ export default function ProcessInstanceShow() {
<li>
<Link
data-qa="process-instance-message-instance-list-link"
to={`/admin/messages?process_group_id=${params.process_group_id}&process_model_id=${params.process_model_id}&process_instance_id=${params.process_instance_id}`}
to={`/admin/messages?process_model_id=${params.process_model_id}&process_instance_id=${params.process_instance_id}`}
>
Messages
</Link>
@ -284,7 +292,7 @@ export default function ProcessInstanceShow() {
const taskToUse: any = taskToDisplay;
const previousTask: any = getTaskById(taskToUse.parent);
HttpService.makeCallToBackend({
path: `/process-models/${params.process_group_id}/${params.process_model_id}/script-unit-tests`,
path: `/process-models/${modifiedProcessModelId}/script-unit-tests`,
httpMethod: 'POST',
successCallback: processScriptUnitTestCreateResult,
postBody: {
@ -428,11 +436,14 @@ export default function ProcessInstanceShow() {
if (processInstance && tasks) {
const processInstanceToUse = processInstance as any;
const taskIds = getTaskIds();
const processModelId = unModifyProcessModelPath(
params.process_model_id ? params.process_model_id : ''
);
return (
<>
<ProcessBreadcrumb
processModelId={params.process_model_id}
processModelId={processModelId}
processGroupId={params.process_group_id}
linkProcessModel
/>
@ -450,8 +461,7 @@ export default function ProcessInstanceShow() {
{getInfoTag(processInstanceToUse)}
{taskDataDisplayArea()}
<ReactDiagramEditor
processModelId={params.process_model_id || ''}
processGroupId={params.process_group_id || ''}
processModelId={processModelId || ''}
diagramXML={processInstanceToUse.bpmn_xml_file_contents || ''}
fileName={processInstanceToUse.bpmn_xml_file_contents || ''}
readyOrWaitingBpmnTaskIds={taskIds.readyOrWaiting}

View File

@ -8,7 +8,7 @@ import ProcessModelForm from '../components/ProcessModelForm';
export default function ProcessModelEdit() {
const params = useParams();
const [processModel, setProcessModel] = useState(null);
const processModelPath = `process-models/${params.process_group_id}/${params.process_model_id}`;
const processModelPath = `process-models/${params.process_model_id}`;
useEffect(() => {
HttpService.makeCallToBackend({

View File

@ -17,7 +17,7 @@ import ReactDiagramEditor from '../components/ReactDiagramEditor';
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
import HttpService from '../services/HttpService';
import ErrorContext from '../contexts/ErrorContext';
import { makeid } from '../helpers';
import { makeid, modifyProcessModelPath } from '../helpers';
import { ProcessFile, ProcessModel } from '../interfaces';
export default function ProcessModelEditDiagram() {
@ -77,12 +77,18 @@ export default function ProcessModelEditDiagram() {
const [searchParams] = useSearchParams();
const setErrorMessage = (useContext as any)(ErrorContext)[1];
const [processModelFile, setProcessModelFile] = useState(null);
const [processModelFile, setProcessModelFile] = useState<ProcessFile | null>(
null
);
const [newFileName, setNewFileName] = useState('');
const [bpmnXmlForDiagramRendering, setBpmnXmlForDiagramRendering] =
useState(null);
const processModelPath = `process-models/${params.process_group_id}/${params.process_model_id}`;
const modifiedProcessModelId = modifyProcessModelPath(
(params as any).process_model_id
);
const processModelPath = `process-models/${modifiedProcessModelId}`;
useEffect(() => {
const processResult = (result: ProcessModel) => {
@ -101,6 +107,7 @@ export default function ProcessModelEditDiagram() {
};
if (params.file_name) {
console.log(`processModelPath: ${processModelPath}`);
HttpService.makeCallToBackend({
path: `/${processModelPath}/files/${params.file_name}`,
successCallback: fileResult,
@ -119,7 +126,7 @@ export default function ProcessModelEditDiagram() {
'file_type'
)}`;
navigate(
`/admin/process-models/${params.process_group_id}/${params.process_model_id}/files/${fileNameWithExtension}`
`/admin/process-models/${modifiedProcessModelId}/files/${fileNameWithExtension}`
);
}
};
@ -128,7 +135,7 @@ export default function ProcessModelEditDiagram() {
setErrorMessage(null);
setBpmnXmlForDiagramRendering(bpmnXML);
let url = `/process-models/${params.process_group_id}/${params.process_model_id}/files`;
let url = `/process-models/${modifiedProcessModelId}/files`;
let httpMethod = 'PUT';
let fileNameWithExtension = fileName;
@ -162,13 +169,11 @@ export default function ProcessModelEditDiagram() {
};
const onDeleteFile = (fileName = params.file_name) => {
const url = `/process-models/${params.process_group_id}/${params.process_model_id}/files/${fileName}`;
const url = `/process-models/${modifiedProcessModelId}/files/${fileName}`;
const httpMethod = 'DELETE';
const navigateToProcessModelShow = (_httpResult: any) => {
navigate(
`/admin/process-models/${params.process_group_id}/${params.process_model_id}`
);
navigate(`/admin/process-models/${modifiedProcessModelId}`);
};
HttpService.makeCallToBackend({
path: url,
@ -178,7 +183,7 @@ export default function ProcessModelEditDiagram() {
};
const onSetPrimaryFile = (fileName = params.file_name) => {
const url = `/process-models/${params.process_group_id}/${params.process_model_id}`;
const url = `/process-models/${modifiedProcessModelId}`;
const httpMethod = 'PUT';
const navigateToProcessModelShow = (_httpResult: any) => {
@ -428,7 +433,7 @@ export default function ProcessModelEditDiagram() {
if (currentScriptUnitTest && scriptElement) {
resetUnitTextResult();
HttpService.makeCallToBackend({
path: `/process-models/${params.process_group_id}/${params.process_model_id}/script-unit-tests/run`,
path: `/process-models/${modifiedProcessModelId}/script-unit-tests/run`,
httpMethod: 'POST',
successCallback: processScriptUnitTestRunResult,
postBody: {
@ -730,7 +735,6 @@ export default function ProcessModelEditDiagram() {
return (
<ReactDiagramEditor
processModelId={params.process_model_id || ''}
processGroupId={params.process_group_id || ''}
saveDiagram={saveDiagram}
onDeleteFile={onDeleteFile}
diagramXML={bpmnXmlForDiagramRendering}
@ -752,7 +756,6 @@ export default function ProcessModelEditDiagram() {
return (
<ReactDiagramEditor
processModelId={params.process_model_id || ''}
processGroupId={params.process_group_id || ''}
saveDiagram={saveDiagram}
onDeleteFile={onDeleteFile}
onSetPrimaryFile={onSetPrimaryFileCallback}
@ -773,16 +776,24 @@ export default function ProcessModelEditDiagram() {
// if a file name is not given then this is a new model and the ReactDiagramEditor component will handle it
if ((bpmnXmlForDiagramRendering || !params.file_name) && processModel) {
const processModelFileName = processModelFile
? `: ${processModelFile.name}`
: '';
return (
<>
<ProcessBreadcrumb
processGroupId={params.process_group_id}
processModelId={params.process_model_id}
linkProcessModel
hotCrumbs={[
['Process Groups', '/admin'],
[
`Process Model: ${processModel.id}`,
`process_model:${processModel.id}:link`,
],
[processModelFileName],
]}
/>
<h2>
Process Model File
{processModelFile ? `: ${(processModelFile as any).name}` : ''}
{processModelFileName}
</h2>
{appropriateEditor()}
{newFileNameBox()}

View File

@ -1,17 +1,13 @@
import { useState } from 'react';
import { useParams } from 'react-router-dom';
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
import { ProcessModel } from '../interfaces';
import ProcessModelForm from '../components/ProcessModelForm';
export default function ProcessModelNew() {
const params = useParams();
const [processModel, setProcessModel] = useState<ProcessModel>({
id: '',
display_name: '',
description: '',
process_group_id: params.process_group_id || '',
primary_file_name: '',
files: [],
});

View File

@ -28,6 +28,7 @@ import {
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
import HttpService from '../services/HttpService';
import ErrorContext from '../contexts/ErrorContext';
import { modifyProcessModelPath, unModifyProcessModelPath } from '../helpers';
import { ProcessFile, ProcessModel, RecentProcessModel } from '../interfaces';
import ButtonWithConfirmation from '../components/ButtonWithConfirmation';
@ -97,6 +98,10 @@ export default function ProcessModelShow() {
useState<boolean>(false);
const navigate = useNavigate();
const modifiedProcessModelId = modifyProcessModelPath(
`${params.process_model_id}`
);
useEffect(() => {
const processResult = (result: ProcessModel) => {
setProcessModel(result);
@ -104,15 +109,15 @@ export default function ProcessModelShow() {
storeRecentProcessModelInLocalStorage(result, params);
};
HttpService.makeCallToBackend({
path: `/process-models/${params.process_group_id}/${params.process_model_id}`,
path: `/process-models/${modifiedProcessModelId}`,
successCallback: processResult,
});
}, [params, reloadModel]);
}, [params, reloadModel, modifiedProcessModelId]);
const processModelRun = (processInstance: any) => {
setErrorMessage(null);
HttpService.makeCallToBackend({
path: `/process-models/${params.process_group_id}/${params.process_model_id}/process-instances/${processInstance.id}/run`,
path: `/process-instances/${processInstance.id}/run`,
successCallback: setProcessInstanceResult,
failureCallback: setErrorMessage,
httpMethod: 'POST',
@ -121,7 +126,7 @@ export default function ProcessModelShow() {
const processInstanceCreateAndRun = () => {
HttpService.makeCallToBackend({
path: `/process-models/${params.process_group_id}/${params.process_model_id}/process-instances`,
path: `/process-models/${modifiedProcessModelId}/process-instances`,
successCallback: processModelRun,
httpMethod: 'POST',
});
@ -149,7 +154,7 @@ export default function ProcessModelShow() {
<p>
Process Instance {processInstanceId} kicked off (
<Link
to={`/admin/process-models/${processModel.process_group_id}/${processModel.id}/process-instances/${processInstanceId}`}
to={`/admin/process-models/${modifiedProcessModelId}/process-instances/${processInstanceId}`}
data-qa="process-instance-show-link"
>
view
@ -171,7 +176,7 @@ export default function ProcessModelShow() {
// Remove this code from
const onDeleteFile = (fileName: string) => {
const url = `/process-models/${params.process_group_id}/${params.process_model_id}/files/${fileName}`;
const url = `/process-models/${modifiedProcessModelId}/files/${fileName}`;
const httpMethod = 'DELETE';
HttpService.makeCallToBackend({
path: url,
@ -181,7 +186,7 @@ export default function ProcessModelShow() {
};
const onSetPrimaryFile = (fileName: string) => {
const url = `/process-models/${params.process_group_id}/${params.process_model_id}`;
const url = `/process-models/${modifiedProcessModelId}`;
const httpMethod = 'PUT';
const processModelToPass = {
@ -221,7 +226,7 @@ export default function ProcessModelShow() {
const downloadFile = (fileName: string) => {
setErrorMessage(null);
const processModelPath = `process-models/${params.process_group_id}/${params.process_model_id}`;
const processModelPath = `process-models/${modifiedProcessModelId}`;
HttpService.makeCallToBackend({
path: `/${processModelPath}/files/${fileName}`,
successCallback: handleProcessModelFileResult,
@ -232,11 +237,11 @@ export default function ProcessModelShow() {
if (processModel) {
if (processModelFile.name.match(/\.(dmn|bpmn)$/)) {
navigate(
`/admin/process-models/${processModel.process_group_id}/${processModel.id}/files/${processModelFile.name}`
`/admin/process-models/${modifiedProcessModelId}/files/${processModelFile.name}`
);
} else if (processModelFile.name.match(/\.(json|md)$/)) {
navigate(
`/admin/process-models/${processModel.process_group_id}/${processModel.id}/form/${processModelFile.name}`
`/admin/process-models/${modifiedProcessModelId}/form/${processModelFile.name}`
);
}
}
@ -254,6 +259,7 @@ export default function ProcessModelShow() {
iconDescription="Edit File"
hasIconOnly
size="lg"
data-qa={`edit-file-${processModelFile.name.replace('.', '-')}`}
onClick={() => navigateToFileEdit(processModelFile)}
/>
);
@ -349,6 +355,9 @@ export default function ProcessModelShow() {
};
const processInstancesUl = () => {
const unmodifiedProcessModelId: String = unModifyProcessModelPath(
`${params.process_model_id}`
);
if (!processModel) {
return null;
}
@ -356,7 +365,7 @@ export default function ProcessModelShow() {
<ul>
<li>
<Link
to={`/admin/process-instances?process_group_identifier=${processModel.process_group_id}&process_model_identifier=${processModel.id}`}
to={`/admin/process-instances?process_model_identifier=${unmodifiedProcessModelId}`}
data-qa="process-instance-list-link"
>
List
@ -364,7 +373,7 @@ export default function ProcessModelShow() {
</li>
<li>
<Link
to={`/admin/process-models/${processModel.process_group_id}/${processModel.id}/process-instances/reports`}
to={`/admin/process-models/${modifiedProcessModelId}/process-instances/reports`}
data-qa="process-instance-reports-link"
>
Reports
@ -381,7 +390,7 @@ export default function ProcessModelShow() {
const handleFileUpload = (event: any) => {
if (processModel) {
event.preventDefault();
const url = `/process-models/${processModel.process_group_id}/${processModel.id}/files`;
const url = `/process-models/${modifiedProcessModelId}/files`;
const formData = new FormData();
formData.append('file', filesToUpload[0]);
formData.append('fileName', filesToUpload[0].name);
@ -432,6 +441,7 @@ export default function ProcessModelShow() {
return (
<Accordion>
<AccordionItem
data-qa="files-accordion"
title={
<Stack orientation="horizontal">
<span>
@ -454,28 +464,28 @@ export default function ProcessModelShow() {
</Button>
<Button
renderIcon={Add}
href={`/admin/process-models/${processModel.process_group_id}/${processModel.id}/files?file_type=bpmn`}
href={`/admin/process-models/${modifiedProcessModelId}/files?file_type=bpmn`}
size="sm"
>
New BPMN File
</Button>
<Button
renderIcon={Add}
href={`/admin/process-models/${processModel.process_group_id}/${processModel.id}/files?file_type=dmn`}
href={`/admin/process-models/${modifiedProcessModelId}/files?file_type=dmn`}
size="sm"
>
New DMN File
</Button>
<Button
renderIcon={Add}
href={`/admin/process-models/${processModel.process_group_id}/${processModel.id}/form?file_ext=json`}
href={`/admin/process-models/${modifiedProcessModelId}/form?file_ext=json`}
size="sm"
>
New JSON File
</Button>
<Button
renderIcon={Add}
href={`/admin/process-models/${processModel.process_group_id}/${processModel.id}/form?file_ext=md`}
href={`/admin/process-models/${modifiedProcessModelId}/form?file_ext=md`}
size="sm"
>
New Markdown File
@ -493,17 +503,22 @@ export default function ProcessModelShow() {
<>
{fileUploadModal()}
<ProcessBreadcrumb
processGroupId={processModel.process_group_id}
processModelId={processModel.id}
hotCrumbs={[
['Process Groups', '/admin'],
[
`Process Model: ${processModel.id}`,
`process_model:${processModel.id}`,
],
]}
/>
<h1>{processModel.display_name}</h1>
<p>{processModel.description}</p>
<h1>Process Model: {processModel.display_name}</h1>
<p className="process-description">{processModel.description}</p>
<Stack orientation="horizontal" gap={3}>
<Button onClick={processInstanceCreateAndRun} variant="primary">
Run
</Button>
<Button
href={`/admin/process-models/${processModel.process_group_id}/${processModel.id}/edit`}
href={`/admin/process-models/${modifiedProcessModelId}/edit`}
variant="secondary"
>
Edit process model

View File

@ -6,6 +6,7 @@ import { Button, Modal } from '@carbon/react';
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
import HttpService from '../services/HttpService';
import ButtonWithConfirmation from '../components/ButtonWithConfirmation';
import { modifyProcessModelPath } from '../helpers';
// 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
@ -34,6 +35,10 @@ export default function ReactFormEditor() {
const editorDefaultLanguage = fileExtension === 'md' ? 'markdown' : 'json';
const modifiedProcessModelId = modifyProcessModelPath(
`${params.process_model_id}`
);
useEffect(() => {
const processResult = (result: any) => {
setProcessModelFile(result);
@ -42,23 +47,23 @@ export default function ReactFormEditor() {
if (params.file_name) {
HttpService.makeCallToBackend({
path: `/process-models/${params.process_group_id}/${params.process_model_id}/files/${params.file_name}`,
path: `/process-models/${modifiedProcessModelId}/files/${params.file_name}`,
successCallback: processResult,
});
}
}, [params]);
}, [params, modifiedProcessModelId]);
const navigateToProcessModelFile = (_result: any) => {
if (!params.file_name) {
const fileNameWithExtension = `${newFileName}.${fileExtension}`;
navigate(
`/admin/process-models/${params.process_group_id}/${params.process_model_id}/form/${fileNameWithExtension}`
`/admin/process-models/${modifiedProcessModelId}/form/${fileNameWithExtension}`
);
}
};
const saveFile = () => {
let url = `/process-models/${params.process_group_id}/${params.process_model_id}/files`;
let url = `/process-models/${modifiedProcessModelId}/files`;
let httpMethod = 'PUT';
let fileNameWithExtension = params.file_name;
@ -90,13 +95,11 @@ export default function ReactFormEditor() {
};
const deleteFile = () => {
const url = `/process-models/${params.process_group_id}/${params.process_model_id}/files/${params.file_name}`;
const url = `/process-models/${modifiedProcessModelId}/files/${params.file_name}`;
const httpMethod = 'DELETE';
const navigateToProcessModelShow = (_httpResult: any) => {
navigate(
`/admin/process-models/${params.process_group_id}/${params.process_model_id}`
);
navigate(`/admin/process-models/${modifiedProcessModelId}`);
};
HttpService.makeCallToBackend({
@ -149,6 +152,14 @@ export default function ReactFormEditor() {
processGroupId={params.process_group_id}
processModelId={params.process_model_id}
linkProcessModel
hotCrumbs={[
['Process Groups', '/admin'],
[
`Process Model: ${params.process_model_id}`,
`process_model:${params.process_model_id}:link`,
],
[(processModelFile as any).name || ''],
]}
/>
<h2>
Process Model File