Merge remote-tracking branch 'origin/main' into feature/create_containers
This commit is contained in:
commit
e26eeb3262
|
@ -1,2 +1,3 @@
|
|||
pyrightconfig.json
|
||||
.idea/
|
||||
.idea/
|
||||
t
|
||||
|
|
|
@ -20,6 +20,12 @@ function get_python_dirs() {
|
|||
(git ls-tree -r HEAD --name-only | grep -E '\.py$' | awk -F '/' '{print $1}' | sort | uniq | grep -v '\.' | grep -Ev '^(bin|migrations)$') || echo ''
|
||||
}
|
||||
|
||||
function run_fix_docstrings() {
|
||||
if command -v fix_python_docstrings >/dev/null ; then
|
||||
fix_python_docstrings $(get_top_level_directories_containing_python_files)
|
||||
fi
|
||||
}
|
||||
|
||||
function run_autoflake() {
|
||||
if ! command -v autoflake8 >/dev/null ; then
|
||||
pip install autoflake8
|
||||
|
@ -51,6 +57,7 @@ done
|
|||
|
||||
for python_project in "${python_projects[@]}" ; do
|
||||
pushd "$python_project"
|
||||
run_fix_docstrings || run_fix_docstrings
|
||||
run_autoflake || run_autoflake
|
||||
popd
|
||||
done
|
||||
|
|
|
@ -74,7 +74,7 @@ def main():
|
|||
print(f"ticket_identifier: {ticket_identifier}")
|
||||
print(f"priority: {priority}")
|
||||
|
||||
process_instance = ProcessInstanceService.create_process_instance(
|
||||
process_instance = ProcessInstanceService.create_process_instance_from_process_model_identifier(
|
||||
process_model_identifier_ticket,
|
||||
user,
|
||||
process_group_identifier="sartography-admin",
|
||||
|
|
|
@ -68,7 +68,7 @@ def main():
|
|||
print(f"priority: {priority}")
|
||||
# if there is no month, who cares about it.
|
||||
if month:
|
||||
process_instance = ProcessInstanceService.create_process_instance(
|
||||
process_instance = ProcessInstanceService.create_process_instance_from_process_model_identifier(
|
||||
process_model_identifier=process_model_identifier_ticket,
|
||||
user=user,
|
||||
process_group_identifier="sartography-admin",
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -27,7 +27,7 @@ docker run \
|
|||
-e KEYCLOAK_LOGLEVEL=ALL \
|
||||
-e ROOT_LOGLEVEL=ALL \
|
||||
-e KEYCLOAK_ADMIN=admin \
|
||||
-e KEYCLOAK_ADMIN_PASSWORD=admin quay.io/keycloak/keycloak:19.0.3 start-dev \
|
||||
-e KEYCLOAK_ADMIN_PASSWORD=admin quay.io/keycloak/keycloak:20.0.1 start-dev \
|
||||
-Dkeycloak.profile.feature.token_exchange=enabled \
|
||||
-Dkeycloak.profile.feature.admin_fine_grained_authz=enabled
|
||||
|
||||
|
|
|
@ -96,7 +96,7 @@ def setup_process_instances_for_reports(
|
|||
# )
|
||||
process_instances = []
|
||||
for data in [kay(), ray(), jay()]:
|
||||
process_instance = ProcessInstanceService.create_process_instance(
|
||||
process_instance = ProcessInstanceService.create_process_instance_from_process_model_identifier(
|
||||
# process_group_identifier=process_group_id,
|
||||
process_model_identifier=process_model_identifier,
|
||||
user=user,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
FROM quay.io/keycloak/keycloak:19.0.3 as builder
|
||||
FROM quay.io/keycloak/keycloak:20.0.1 as builder
|
||||
|
||||
ENV KEYCLOAK_LOGLEVEL="ALL"
|
||||
ENV ROOT_LOGLEVEL="ALL"
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
"""empty message
|
||||
|
||||
Revision ID: 70223f5c7b98
|
||||
Revision ID: ff1c1628337c
|
||||
Revises:
|
||||
Create Date: 2022-11-20 19:54:45.061376
|
||||
Create Date: 2022-11-28 15:08:52.014254
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
|
@ -10,7 +10,7 @@ import sqlalchemy as sa
|
|||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '70223f5c7b98'
|
||||
revision = 'ff1c1628337c'
|
||||
down_revision = None
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
@ -97,14 +97,12 @@ def upgrade():
|
|||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('message_model_id', sa.Integer(), nullable=False),
|
||||
sa.Column('process_model_identifier', sa.String(length=50), nullable=False),
|
||||
sa.Column('process_group_identifier', sa.String(length=50), nullable=False),
|
||||
sa.Column('updated_at_in_seconds', sa.Integer(), nullable=True),
|
||||
sa.Column('created_at_in_seconds', sa.Integer(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['message_model_id'], ['message_model.id'], ),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('message_model_id')
|
||||
)
|
||||
op.create_index(op.f('ix_message_triggerable_process_model_process_group_identifier'), 'message_triggerable_process_model', ['process_group_identifier'], unique=False)
|
||||
op.create_index(op.f('ix_message_triggerable_process_model_process_model_identifier'), 'message_triggerable_process_model', ['process_model_identifier'], unique=False)
|
||||
op.create_table('principal',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
|
@ -120,7 +118,7 @@ def upgrade():
|
|||
op.create_table('process_instance',
|
||||
sa.Column('id', sa.Integer(), 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_model_display_name', sa.String(length=255), nullable=False),
|
||||
sa.Column('process_initiator_id', sa.Integer(), nullable=False),
|
||||
sa.Column('bpmn_json', sa.JSON(), nullable=True),
|
||||
sa.Column('start_in_seconds', sa.Integer(), nullable=True),
|
||||
|
@ -134,7 +132,7 @@ def upgrade():
|
|||
sa.ForeignKeyConstraint(['process_initiator_id'], ['user.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index(op.f('ix_process_instance_process_group_identifier'), 'process_instance', ['process_group_identifier'], unique=False)
|
||||
op.create_index(op.f('ix_process_instance_process_model_display_name'), 'process_instance', ['process_model_display_name'], unique=False)
|
||||
op.create_index(op.f('ix_process_instance_process_model_identifier'), 'process_instance', ['process_model_identifier'], unique=False)
|
||||
op.create_table('process_instance_report',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
|
@ -168,17 +166,6 @@ def upgrade():
|
|||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('key')
|
||||
)
|
||||
op.create_table('spiff_step_details',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('process_instance_id', sa.Integer(), nullable=False),
|
||||
sa.Column('spiff_step', sa.Integer(), nullable=False),
|
||||
sa.Column('task_json', sa.JSON(), nullable=False),
|
||||
sa.Column('timestamp', sa.DECIMAL(precision=17, scale=6), nullable=False),
|
||||
sa.Column('completed_by_user_id', sa.Integer(), nullable=True),
|
||||
sa.Column('lane_assignment_id', sa.Integer(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['lane_assignment_id'], ['group.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_table('user_group_assignment',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('user_id', sa.Integer(), nullable=False),
|
||||
|
@ -251,6 +238,29 @@ def upgrade():
|
|||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('principal_id', 'permission_target_id', 'permission', name='permission_assignment_uniq')
|
||||
)
|
||||
op.create_table('process_instance_metadata',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('process_instance_id', sa.Integer(), nullable=False),
|
||||
sa.Column('key', sa.String(length=255), nullable=False),
|
||||
sa.Column('value', sa.String(length=255), nullable=False),
|
||||
sa.Column('updated_at_in_seconds', sa.Integer(), nullable=False),
|
||||
sa.Column('created_at_in_seconds', sa.Integer(), nullable=False),
|
||||
sa.ForeignKeyConstraint(['process_instance_id'], ['process_instance.id'], ),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('process_instance_id', 'key', name='process_instance_metadata_unique')
|
||||
)
|
||||
op.create_table('spiff_step_details',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('process_instance_id', sa.Integer(), nullable=False),
|
||||
sa.Column('spiff_step', sa.Integer(), nullable=False),
|
||||
sa.Column('task_json', sa.JSON(), nullable=False),
|
||||
sa.Column('timestamp', sa.DECIMAL(precision=17, scale=6), nullable=False),
|
||||
sa.Column('completed_by_user_id', sa.Integer(), nullable=True),
|
||||
sa.Column('lane_assignment_id', sa.Integer(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['lane_assignment_id'], ['group.id'], ),
|
||||
sa.ForeignKeyConstraint(['process_instance_id'], ['process_instance.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_table('active_task_user',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('active_task_id', sa.Integer(), nullable=False),
|
||||
|
@ -284,6 +294,8 @@ def downgrade():
|
|||
op.drop_index(op.f('ix_active_task_user_user_id'), table_name='active_task_user')
|
||||
op.drop_index(op.f('ix_active_task_user_active_task_id'), table_name='active_task_user')
|
||||
op.drop_table('active_task_user')
|
||||
op.drop_table('spiff_step_details')
|
||||
op.drop_table('process_instance_metadata')
|
||||
op.drop_table('permission_assignment')
|
||||
op.drop_table('message_instance')
|
||||
op.drop_index(op.f('ix_message_correlation_value'), table_name='message_correlation')
|
||||
|
@ -293,18 +305,16 @@ def downgrade():
|
|||
op.drop_table('message_correlation')
|
||||
op.drop_table('active_task')
|
||||
op.drop_table('user_group_assignment')
|
||||
op.drop_table('spiff_step_details')
|
||||
op.drop_table('secret')
|
||||
op.drop_table('refresh_token')
|
||||
op.drop_index(op.f('ix_process_instance_report_identifier'), table_name='process_instance_report')
|
||||
op.drop_index(op.f('ix_process_instance_report_created_by_id'), table_name='process_instance_report')
|
||||
op.drop_table('process_instance_report')
|
||||
op.drop_index(op.f('ix_process_instance_process_model_identifier'), table_name='process_instance')
|
||||
op.drop_index(op.f('ix_process_instance_process_group_identifier'), table_name='process_instance')
|
||||
op.drop_index(op.f('ix_process_instance_process_model_display_name'), table_name='process_instance')
|
||||
op.drop_table('process_instance')
|
||||
op.drop_table('principal')
|
||||
op.drop_index(op.f('ix_message_triggerable_process_model_process_model_identifier'), table_name='message_triggerable_process_model')
|
||||
op.drop_index(op.f('ix_message_triggerable_process_model_process_group_identifier'), table_name='message_triggerable_process_model')
|
||||
op.drop_table('message_triggerable_process_model')
|
||||
op.drop_index(op.f('ix_message_correlation_property_identifier'), table_name='message_correlation_property')
|
||||
op.drop_table('message_correlation_property')
|
|
@ -160,7 +160,7 @@ paths:
|
|||
schema:
|
||||
type: integer
|
||||
get:
|
||||
operationId: spiffworkflow_backend.routes.process_api_blueprint.process_groups_list
|
||||
operationId: spiffworkflow_backend.routes.process_api_blueprint.process_group_list
|
||||
summary: get list
|
||||
tags:
|
||||
- Process Groups
|
||||
|
@ -278,7 +278,13 @@ paths:
|
|||
required: false
|
||||
description: Get all sub process models recursively if true
|
||||
schema:
|
||||
type: string
|
||||
type: boolean
|
||||
- name: filter_runnable_by_user
|
||||
in: query
|
||||
required: false
|
||||
description: Get only the process models that the user can run
|
||||
schema:
|
||||
type: boolean
|
||||
- name: page
|
||||
in: query
|
||||
required: false
|
||||
|
@ -508,6 +514,24 @@ paths:
|
|||
description: For filtering - not_started, user_input_required, waiting, complete, error, or suspended
|
||||
schema:
|
||||
type: string
|
||||
- name: initiated_by_me
|
||||
in: query
|
||||
required: false
|
||||
description: For filtering - show instances initiated by me
|
||||
schema:
|
||||
type: boolean
|
||||
- name: with_tasks_completed_by_me
|
||||
in: query
|
||||
required: false
|
||||
description: For filtering - show instances with tasks completed by me
|
||||
schema:
|
||||
type: boolean
|
||||
- name: with_tasks_completed_by_my_group
|
||||
in: query
|
||||
required: false
|
||||
description: For filtering - show instances with tasks completed by my group
|
||||
schema:
|
||||
type: boolean
|
||||
- name: user_filter
|
||||
in: query
|
||||
required: false
|
||||
|
@ -686,7 +710,7 @@ paths:
|
|||
schema:
|
||||
$ref: "#/components/schemas/Workflow"
|
||||
|
||||
/process-instances/{process_instance_id}/run:
|
||||
/process-instances/{modified_process_model_identifier}/{process_instance_id}/run:
|
||||
parameters:
|
||||
- name: process_instance_id
|
||||
in: path
|
||||
|
@ -700,7 +724,6 @@ paths:
|
|||
description: Defaults to true, can be set to false if you are just looking at the workflow not completeing it.
|
||||
schema:
|
||||
type: boolean
|
||||
# process_instance_run
|
||||
post:
|
||||
operationId: spiffworkflow_backend.routes.process_api_blueprint.process_instance_run
|
||||
summary: Run a process instance
|
||||
|
@ -1642,10 +1665,6 @@ components:
|
|||
type: integer
|
||||
x-nullable: true
|
||||
example: 12
|
||||
study_id:
|
||||
type: integer
|
||||
x-nullable: true
|
||||
example: 42
|
||||
user_id:
|
||||
type: string
|
||||
x-nullable: true
|
||||
|
@ -1770,8 +1789,6 @@ components:
|
|||
type: integer
|
||||
num_tasks_incomplete:
|
||||
type: integer
|
||||
study_id:
|
||||
type: integer
|
||||
|
||||
example:
|
||||
id: 291234
|
||||
|
@ -1906,9 +1923,6 @@ components:
|
|||
workflow_id:
|
||||
example: 42
|
||||
type: integer
|
||||
study_id:
|
||||
example: 187
|
||||
type: integer
|
||||
user_uid:
|
||||
example: "dhf8r"
|
||||
type: string
|
||||
|
|
|
@ -4,26 +4,22 @@ groups:
|
|||
admin:
|
||||
users:
|
||||
[
|
||||
admin,
|
||||
jakub,
|
||||
kb,
|
||||
alex,
|
||||
dan,
|
||||
mike,
|
||||
jason,
|
||||
j,
|
||||
amir,
|
||||
jarrad,
|
||||
elizabeth,
|
||||
jon,
|
||||
harmeet,
|
||||
sasha,
|
||||
manuchehr,
|
||||
natalia,
|
||||
]
|
||||
|
||||
Finance Team:
|
||||
users: [finance_user1, jason]
|
||||
|
||||
Project Lead:
|
||||
users:
|
||||
[
|
||||
jakub,
|
||||
|
@ -31,18 +27,42 @@ groups:
|
|||
dan,
|
||||
mike,
|
||||
jason,
|
||||
j,
|
||||
amir,
|
||||
jarrad,
|
||||
elizabeth,
|
||||
jon,
|
||||
natalia,
|
||||
sasha,
|
||||
fin,
|
||||
fin1,
|
||||
]
|
||||
|
||||
demo:
|
||||
users:
|
||||
[
|
||||
core,
|
||||
fin,
|
||||
fin1,
|
||||
harmeet,
|
||||
sasha,
|
||||
manuchehr,
|
||||
lead,
|
||||
lead1
|
||||
]
|
||||
|
||||
core-contributor:
|
||||
users:
|
||||
[
|
||||
core,
|
||||
harmeet,
|
||||
]
|
||||
|
||||
permissions:
|
||||
admin:
|
||||
groups: [admin]
|
||||
users: []
|
||||
allowed_permissions: [create, read, update, delete, list, instantiate]
|
||||
allowed_permissions: [create, read, update, delete]
|
||||
uri: /*
|
||||
|
||||
tasks-crud:
|
||||
|
@ -51,45 +71,116 @@ permissions:
|
|||
allowed_permissions: [create, read, update, delete]
|
||||
uri: /v1.0/tasks/*
|
||||
|
||||
process-model-read-all:
|
||||
groups: [everybody]
|
||||
users: []
|
||||
allowed_permissions: [read]
|
||||
uri: /v1.0/process-models/*
|
||||
|
||||
process-group-read-all:
|
||||
# read all for everybody
|
||||
read-all-process-groups:
|
||||
groups: [everybody]
|
||||
users: []
|
||||
allowed_permissions: [read]
|
||||
uri: /v1.0/process-groups/*
|
||||
|
||||
process-instance-list:
|
||||
read-all-process-models:
|
||||
groups: [everybody]
|
||||
users: []
|
||||
allowed_permissions: [read]
|
||||
uri: /v1.0/process-instances
|
||||
uri: /v1.0/process-models/*
|
||||
read-all-process-instance:
|
||||
groups: [everybody]
|
||||
users: []
|
||||
allowed_permissions: [read]
|
||||
uri: /v1.0/process-instances/*
|
||||
read-process-instance-reports:
|
||||
groups: [everybody]
|
||||
users: []
|
||||
allowed_permissions: [read]
|
||||
uri: /v1.0/process-instances/reports/*
|
||||
processes-read:
|
||||
groups: [everybody]
|
||||
users: []
|
||||
allowed_permissions: [read]
|
||||
uri: /v1.0/processes
|
||||
|
||||
# TODO: all uris should really have the same structure
|
||||
finance-admin-group:
|
||||
|
||||
manage-procurement-admin:
|
||||
groups: ["Project Lead"]
|
||||
users: []
|
||||
allowed_permissions: [create, read, update, delete]
|
||||
uri: /v1.0/process-groups/manage-procurement:*
|
||||
manage-procurement-admin-slash:
|
||||
groups: ["Project Lead"]
|
||||
users: []
|
||||
allowed_permissions: [create, read, update, delete]
|
||||
uri: /v1.0/process-groups/manage-procurement/*
|
||||
manage-procurement-admin-models:
|
||||
groups: ["Project Lead"]
|
||||
users: []
|
||||
allowed_permissions: [create, read, update, delete]
|
||||
uri: /v1.0/process-models/manage-procurement:*
|
||||
manage-procurement-admin-models-slash:
|
||||
groups: ["Project Lead"]
|
||||
users: []
|
||||
allowed_permissions: [create, read, update, delete]
|
||||
uri: /v1.0/process-models/manage-procurement/*
|
||||
manage-procurement-admin-instances:
|
||||
groups: ["Project Lead"]
|
||||
users: []
|
||||
allowed_permissions: [create, read, update, delete]
|
||||
uri: /v1.0/process-instances/manage-procurement:*
|
||||
manage-procurement-admin-instances-slash:
|
||||
groups: ["Project Lead"]
|
||||
users: []
|
||||
allowed_permissions: [create, read, update, delete]
|
||||
uri: /v1.0/process-instances/manage-procurement/*
|
||||
|
||||
finance-admin:
|
||||
groups: ["Finance Team"]
|
||||
users: []
|
||||
allowed_permissions: [create, read, update, delete]
|
||||
uri: /v1.0/process-groups/finance/*
|
||||
uri: /v1.0/process-groups/manage-procurement:procurement:*
|
||||
|
||||
finance-admin-model:
|
||||
groups: ["Finance Team"]
|
||||
manage-revenue-streams-instantiate:
|
||||
groups: ["core-contributor", "demo"]
|
||||
users: []
|
||||
allowed_permissions: [create, read, update, delete]
|
||||
uri: /v1.0/process-models/finance/*
|
||||
allowed_permissions: [create]
|
||||
uri: /v1.0/process-models/manage-revenue-streams:product-revenue-streams:customer-contracts-trade-terms/*
|
||||
manage-revenue-streams-instances:
|
||||
groups: ["core-contributor", "demo"]
|
||||
users: []
|
||||
allowed_permissions: [create, read]
|
||||
uri: /v1.0/process-instances/manage-revenue-streams:product-revenue-streams:customer-contracts-trade-terms/*
|
||||
|
||||
read-all:
|
||||
groups: [admin, "Project Lead"]
|
||||
manage-procurement-invoice-instantiate:
|
||||
groups: ["core-contributor", "demo"]
|
||||
users: []
|
||||
allowed_permissions: [read]
|
||||
uri: /*
|
||||
allowed_permissions: [create]
|
||||
uri: /v1.0/process-models/manage-procurement:procurement:core-contributor-invoice-management:*
|
||||
manage-procurement-invoice-instances:
|
||||
groups: ["core-contributor", "demo"]
|
||||
users: []
|
||||
allowed_permissions: [create, read]
|
||||
uri: /v1.0/process-instances/manage-procurement:procurement:core-contributor-invoice-management:*
|
||||
|
||||
invoice-approval-tasks-read:
|
||||
groups: ["Finance Team"]
|
||||
manage-procurement-instantiate:
|
||||
groups: ["core-contributor", "demo"]
|
||||
users: []
|
||||
allowed_permissions: [read]
|
||||
uri: /v1.0/process-instances/category_number_one:lanes/*
|
||||
allowed_permissions: [create]
|
||||
uri: /v1.0/process-models/manage-procurement:vendor-lifecycle-management:*
|
||||
manage-procurement-instances:
|
||||
groups: ["core-contributor", "demo"]
|
||||
users: []
|
||||
allowed_permissions: [create, read]
|
||||
uri: /v1.0/process-instances/manage-procurement:vendor-lifecycle-management:*
|
||||
|
||||
core1-admin-models-instantiate:
|
||||
groups: ["core-contributor"]
|
||||
users: []
|
||||
allowed_permissions: [create]
|
||||
uri: /v1.0/process-models/misc:category_number_one:process-model-with-form/process-instances
|
||||
core1-admin-instances:
|
||||
groups: ["core-contributor"]
|
||||
users: []
|
||||
allowed_permissions: [create, read]
|
||||
uri: /v1.0/process-instances/misc:category_number_one:process-model-with-form:*
|
||||
core1-admin-instances-slash:
|
||||
groups: ["core-contributor"]
|
||||
users: []
|
||||
allowed_permissions: [create, read]
|
||||
uri: /v1.0/process-instances/misc:category_number_one:process-model-with-form/*
|
||||
|
|
|
@ -4,20 +4,19 @@ groups:
|
|||
admin:
|
||||
users:
|
||||
[
|
||||
admin,
|
||||
jakub,
|
||||
kb,
|
||||
alex,
|
||||
dan,
|
||||
mike,
|
||||
jason,
|
||||
j,
|
||||
amir,
|
||||
jarrad,
|
||||
elizabeth,
|
||||
jon,
|
||||
natalia,
|
||||
harmeet,
|
||||
sasha,
|
||||
manuchehr,
|
||||
]
|
||||
|
||||
Finance Team:
|
||||
|
@ -28,60 +27,144 @@ groups:
|
|||
dan,
|
||||
mike,
|
||||
jason,
|
||||
j,
|
||||
amir,
|
||||
jarrad,
|
||||
elizabeth,
|
||||
jon,
|
||||
natalia,
|
||||
sasha,
|
||||
fin,
|
||||
fin1,
|
||||
]
|
||||
|
||||
Project Lead:
|
||||
demo:
|
||||
users:
|
||||
[
|
||||
jakub,
|
||||
alex,
|
||||
dan,
|
||||
mike,
|
||||
jason,
|
||||
jarrad,
|
||||
elizabeth,
|
||||
jon,
|
||||
natalia,
|
||||
core,
|
||||
fin,
|
||||
fin1,
|
||||
harmeet,
|
||||
sasha,
|
||||
manuchehr,
|
||||
lead,
|
||||
lead1
|
||||
]
|
||||
|
||||
hr:
|
||||
users: [manuchehr]
|
||||
core-contributor:
|
||||
users:
|
||||
[
|
||||
core,
|
||||
harmeet,
|
||||
]
|
||||
|
||||
permissions:
|
||||
admin:
|
||||
groups: [admin]
|
||||
users: []
|
||||
allowed_permissions: [create, read, update, delete]
|
||||
uri: /*
|
||||
|
||||
tasks-crud:
|
||||
groups: [everybody]
|
||||
users: []
|
||||
allowed_permissions: [create, read, update, delete]
|
||||
uri: /v1.0/tasks/*
|
||||
|
||||
admin:
|
||||
groups: [admin]
|
||||
# read all for everybody
|
||||
read-all-process-groups:
|
||||
groups: [everybody]
|
||||
users: []
|
||||
allowed_permissions: [create, read, update, delete, list, instantiate]
|
||||
uri: /*
|
||||
allowed_permissions: [read]
|
||||
uri: /v1.0/process-groups/*
|
||||
read-all-process-models:
|
||||
groups: [everybody]
|
||||
users: []
|
||||
allowed_permissions: [read]
|
||||
uri: /v1.0/process-models/*
|
||||
read-all-process-instance:
|
||||
groups: [everybody]
|
||||
users: []
|
||||
allowed_permissions: [read]
|
||||
uri: /v1.0/process-instances/*
|
||||
read-process-instance-reports:
|
||||
groups: [everybody]
|
||||
users: []
|
||||
allowed_permissions: [read]
|
||||
uri: /v1.0/process-instances/reports/*
|
||||
processes-read:
|
||||
groups: [everybody]
|
||||
users: []
|
||||
allowed_permissions: [read]
|
||||
uri: /v1.0/processes
|
||||
|
||||
# TODO: all uris should really have the same structure
|
||||
finance-admin-group:
|
||||
groups: ["Finance Team"]
|
||||
|
||||
manage-procurement-admin:
|
||||
groups: ["Project Lead"]
|
||||
users: []
|
||||
allowed_permissions: [create, read, update, delete]
|
||||
uri: /v1.0/process-groups/finance/*
|
||||
uri: /v1.0/process-groups/manage-procurement:*
|
||||
manage-procurement-admin-slash:
|
||||
groups: ["Project Lead"]
|
||||
users: []
|
||||
allowed_permissions: [create, read, update, delete]
|
||||
uri: /v1.0/process-groups/manage-procurement/*
|
||||
manage-procurement-admin-models:
|
||||
groups: ["Project Lead"]
|
||||
users: []
|
||||
allowed_permissions: [create, read, update, delete]
|
||||
uri: /v1.0/process-models/manage-procurement:*
|
||||
manage-procurement-admin-models-slash:
|
||||
groups: ["Project Lead"]
|
||||
users: []
|
||||
allowed_permissions: [create, read, update, delete]
|
||||
uri: /v1.0/process-models/manage-procurement/*
|
||||
manage-procurement-admin-instances:
|
||||
groups: ["Project Lead"]
|
||||
users: []
|
||||
allowed_permissions: [create, read, update, delete]
|
||||
uri: /v1.0/process-instances/manage-procurement:*
|
||||
manage-procurement-admin-instances-slash:
|
||||
groups: ["Project Lead"]
|
||||
users: []
|
||||
allowed_permissions: [create, read, update, delete]
|
||||
uri: /v1.0/process-instances/manage-procurement/*
|
||||
|
||||
finance-admin:
|
||||
groups: ["Finance Team"]
|
||||
users: []
|
||||
allowed_permissions: [create, read, update, delete]
|
||||
uri: /v1.0/process-groups/finance/*
|
||||
uri: /v1.0/process-groups/manage-procurement:procurement:*
|
||||
|
||||
read-all:
|
||||
groups: ["Finance Team", "Project Lead", hr, admin]
|
||||
manage-revenue-streams-instantiate:
|
||||
groups: ["core-contributor", "demo"]
|
||||
users: []
|
||||
allowed_permissions: [read]
|
||||
uri: /*
|
||||
allowed_permissions: [create]
|
||||
uri: /v1.0/process-models/manage-revenue-streams:product-revenue-streams:customer-contracts-trade-terms/*
|
||||
manage-revenue-streams-instances:
|
||||
groups: ["core-contributor", "demo"]
|
||||
users: []
|
||||
allowed_permissions: [create, read]
|
||||
uri: /v1.0/process-instances/manage-revenue-streams:product-revenue-streams:customer-contracts-trade-terms/*
|
||||
|
||||
manage-procurement-invoice-instantiate:
|
||||
groups: ["core-contributor", "demo"]
|
||||
users: []
|
||||
allowed_permissions: [create]
|
||||
uri: /v1.0/process-models/manage-procurement:procurement:core-contributor-invoice-management:*
|
||||
manage-procurement-invoice-instances:
|
||||
groups: ["core-contributor", "demo"]
|
||||
users: []
|
||||
allowed_permissions: [create, read]
|
||||
uri: /v1.0/process-instances/manage-procurement:procurement:core-contributor-invoice-management:*
|
||||
|
||||
manage-procurement-instantiate:
|
||||
groups: ["core-contributor", "demo"]
|
||||
users: []
|
||||
allowed_permissions: [create]
|
||||
uri: /v1.0/process-models/manage-procurement:vendor-lifecycle-management:*
|
||||
manage-procurement-instances:
|
||||
groups: ["core-contributor", "demo"]
|
||||
users: []
|
||||
allowed_permissions: [create, read]
|
||||
uri: /v1.0/process-instances/manage-procurement:vendor-lifecycle-management:*
|
||||
|
|
|
@ -51,5 +51,8 @@ from spiffworkflow_backend.models.spiff_step_details import (
|
|||
) # noqa: F401
|
||||
from spiffworkflow_backend.models.user import UserModel # noqa: F401
|
||||
from spiffworkflow_backend.models.group import GroupModel # noqa: F401
|
||||
from spiffworkflow_backend.models.process_instance_metadata import (
|
||||
ProcessInstanceMetadataModel,
|
||||
) # noqa: F401
|
||||
|
||||
add_listeners()
|
||||
|
|
|
@ -16,8 +16,6 @@ class MessageTriggerableProcessModel(SpiffworkflowBaseDBModel):
|
|||
ForeignKey(MessageModel.id), nullable=False, unique=True
|
||||
)
|
||||
process_model_identifier: str = db.Column(db.String(50), nullable=False, index=True)
|
||||
# fixme: Maybe we don't need this anymore?
|
||||
process_group_identifier: str = db.Column(db.String(50), nullable=False, index=True)
|
||||
|
||||
updated_at_in_seconds: int = db.Column(db.Integer)
|
||||
created_at_in_seconds: int = db.Column(db.Integer)
|
||||
|
|
|
@ -29,6 +29,7 @@ class ProcessGroup:
|
|||
default_factory=list[ProcessModelInfo]
|
||||
)
|
||||
process_groups: list[ProcessGroup] = field(default_factory=list["ProcessGroup"])
|
||||
parent_groups: list[dict] | None = None
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
"""__post_init__."""
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
"""Process_instance."""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
from typing import cast
|
||||
|
||||
|
@ -18,12 +17,15 @@ from sqlalchemy.orm import relationship
|
|||
from sqlalchemy.orm import validates
|
||||
|
||||
from spiffworkflow_backend.helpers.spiff_enum import SpiffEnum
|
||||
from spiffworkflow_backend.models.process_model import ProcessModelInfo
|
||||
from spiffworkflow_backend.models.task import Task
|
||||
from spiffworkflow_backend.models.task import TaskSchema
|
||||
from spiffworkflow_backend.models.user import UserModel
|
||||
|
||||
|
||||
class ProcessInstanceNotFoundError(Exception):
|
||||
"""ProcessInstanceNotFoundError."""
|
||||
|
||||
|
||||
class NavigationItemSchema(Schema):
|
||||
"""NavigationItemSchema."""
|
||||
|
||||
|
@ -74,7 +76,9 @@ class ProcessInstanceModel(SpiffworkflowBaseDBModel):
|
|||
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_model_display_name: str = db.Column(
|
||||
db.String(255), nullable=False, index=True
|
||||
)
|
||||
process_initiator_id: int = db.Column(ForeignKey(UserModel.id), nullable=False)
|
||||
process_initiator = relationship("UserModel")
|
||||
|
||||
|
@ -103,7 +107,7 @@ class ProcessInstanceModel(SpiffworkflowBaseDBModel):
|
|||
return {
|
||||
"id": self.id,
|
||||
"process_model_identifier": self.process_model_identifier,
|
||||
"process_group_identifier": self.process_group_identifier,
|
||||
"process_model_display_name": self.process_model_display_name,
|
||||
"status": self.status,
|
||||
"start_in_seconds": self.start_in_seconds,
|
||||
"end_in_seconds": self.end_in_seconds,
|
||||
|
@ -112,6 +116,7 @@ class ProcessInstanceModel(SpiffworkflowBaseDBModel):
|
|||
"bpmn_version_control_identifier": self.bpmn_version_control_identifier,
|
||||
"bpmn_version_control_type": self.bpmn_version_control_type,
|
||||
"spiff_step": self.spiff_step,
|
||||
"username": self.process_initiator.username,
|
||||
}
|
||||
|
||||
@property
|
||||
|
@ -140,7 +145,7 @@ class ProcessInstanceModelSchema(Schema):
|
|||
fields = [
|
||||
"id",
|
||||
"process_model_identifier",
|
||||
"process_group_identifier",
|
||||
"process_model_display_name",
|
||||
"process_initiator_id",
|
||||
"start_in_seconds",
|
||||
"end_in_seconds",
|
||||
|
@ -166,23 +171,18 @@ class ProcessInstanceApi:
|
|||
status: ProcessInstanceStatus,
|
||||
next_task: Task | None,
|
||||
process_model_identifier: str,
|
||||
process_group_identifier: str,
|
||||
process_model_display_name: str,
|
||||
completed_tasks: int,
|
||||
updated_at_in_seconds: int,
|
||||
is_review: bool,
|
||||
title: str,
|
||||
) -> None:
|
||||
"""__init__."""
|
||||
self.id = id
|
||||
self.status = status
|
||||
self.next_task = next_task # The next task that requires user input.
|
||||
# self.navigation = navigation fixme: would be a hotness.
|
||||
self.process_model_identifier = process_model_identifier
|
||||
self.process_group_identifier = process_group_identifier
|
||||
self.process_model_display_name = process_model_display_name
|
||||
self.completed_tasks = completed_tasks
|
||||
self.updated_at_in_seconds = updated_at_in_seconds
|
||||
self.title = title
|
||||
self.is_review = is_review
|
||||
|
||||
|
||||
class ProcessInstanceApiSchema(Schema):
|
||||
|
@ -196,24 +196,15 @@ class ProcessInstanceApiSchema(Schema):
|
|||
"id",
|
||||
"status",
|
||||
"next_task",
|
||||
"navigation",
|
||||
"process_model_identifier",
|
||||
"process_group_identifier",
|
||||
"process_model_display_name",
|
||||
"completed_tasks",
|
||||
"updated_at_in_seconds",
|
||||
"is_review",
|
||||
"title",
|
||||
"study_id",
|
||||
"state",
|
||||
]
|
||||
unknown = INCLUDE
|
||||
|
||||
status = EnumField(ProcessInstanceStatus)
|
||||
next_task = marshmallow.fields.Nested(TaskSchema, dump_only=True, required=False)
|
||||
navigation = marshmallow.fields.List(
|
||||
marshmallow.fields.Nested(NavigationItemSchema, dump_only=True)
|
||||
)
|
||||
state = marshmallow.fields.String(allow_none=True)
|
||||
|
||||
@marshmallow.post_load
|
||||
def make_process_instance(
|
||||
|
@ -224,73 +215,11 @@ class ProcessInstanceApiSchema(Schema):
|
|||
"id",
|
||||
"status",
|
||||
"next_task",
|
||||
"navigation",
|
||||
"process_model_identifier",
|
||||
"process_group_identifier",
|
||||
"process_model_display_name",
|
||||
"completed_tasks",
|
||||
"updated_at_in_seconds",
|
||||
"is_review",
|
||||
"title",
|
||||
"study_id",
|
||||
"state",
|
||||
]
|
||||
filtered_fields = {key: data[key] for key in keys}
|
||||
filtered_fields["next_task"] = TaskSchema().make_task(data["next_task"])
|
||||
return ProcessInstanceApi(**filtered_fields)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ProcessInstanceMetadata:
|
||||
"""ProcessInstanceMetadata."""
|
||||
|
||||
id: int
|
||||
display_name: str | None = None
|
||||
description: str | None = None
|
||||
spec_version: str | None = None
|
||||
state: str | None = None
|
||||
status: str | None = None
|
||||
completed_tasks: int | None = None
|
||||
is_review: bool | None = None
|
||||
state_message: str | None = None
|
||||
process_model_identifier: str | None = None
|
||||
process_group_id: str | None = None
|
||||
|
||||
@classmethod
|
||||
def from_process_instance(
|
||||
cls, process_instance: ProcessInstanceModel, process_model: ProcessModelInfo
|
||||
) -> ProcessInstanceMetadata:
|
||||
"""From_process_instance."""
|
||||
instance = cls(
|
||||
id=process_instance.id,
|
||||
display_name=process_model.display_name,
|
||||
description=process_model.description,
|
||||
process_group_id=process_model.process_group,
|
||||
state_message=process_instance.state_message,
|
||||
status=process_instance.status,
|
||||
completed_tasks=process_instance.completed_tasks,
|
||||
is_review=process_model.is_review,
|
||||
process_model_identifier=process_instance.process_model_identifier,
|
||||
)
|
||||
return instance
|
||||
|
||||
|
||||
class ProcessInstanceMetadataSchema(Schema):
|
||||
"""ProcessInstanceMetadataSchema."""
|
||||
|
||||
status = EnumField(ProcessInstanceStatus)
|
||||
|
||||
class Meta:
|
||||
"""Meta."""
|
||||
|
||||
model = ProcessInstanceMetadata
|
||||
additional = [
|
||||
"id",
|
||||
"display_name",
|
||||
"description",
|
||||
"state",
|
||||
"completed_tasks",
|
||||
"process_group_id",
|
||||
"is_review",
|
||||
"state_message",
|
||||
]
|
||||
unknown = INCLUDE
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
"""Spiff_step_details."""
|
||||
from dataclasses import dataclass
|
||||
|
||||
from flask_bpmn.models.db import db
|
||||
from flask_bpmn.models.db import SpiffworkflowBaseDBModel
|
||||
from sqlalchemy import ForeignKey
|
||||
|
||||
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
|
||||
|
||||
|
||||
@dataclass
|
||||
class ProcessInstanceMetadataModel(SpiffworkflowBaseDBModel):
|
||||
"""ProcessInstanceMetadataModel."""
|
||||
|
||||
__tablename__ = "process_instance_metadata"
|
||||
__table_args__ = (
|
||||
db.UniqueConstraint(
|
||||
"process_instance_id", "key", name="process_instance_metadata_unique"
|
||||
),
|
||||
)
|
||||
|
||||
id: int = db.Column(db.Integer, primary_key=True)
|
||||
process_instance_id: int = db.Column(
|
||||
ForeignKey(ProcessInstanceModel.id), nullable=False # type: ignore
|
||||
)
|
||||
key: str = db.Column(db.String(255), nullable=False)
|
||||
value: str = db.Column(db.String(255), nullable=False)
|
||||
|
||||
updated_at_in_seconds: int = db.Column(db.Integer, nullable=False)
|
||||
created_at_in_seconds: int = db.Column(db.Integer, nullable=False)
|
|
@ -75,7 +75,7 @@ class ProcessInstanceReportModel(SpiffworkflowBaseDBModel):
|
|||
def add_fixtures(cls) -> None:
|
||||
"""Add_fixtures."""
|
||||
try:
|
||||
# process_model = ProcessModelService().get_process_model(
|
||||
# process_model = ProcessModelService.get_process_model(
|
||||
# process_model_id="sartography-admin/ticket"
|
||||
# )
|
||||
user = UserModel.query.first()
|
||||
|
@ -205,7 +205,7 @@ class ProcessInstanceReportModel(SpiffworkflowBaseDBModel):
|
|||
) -> ProcessInstanceReportModel:
|
||||
"""Create_with_attributes."""
|
||||
# <<<<<<< HEAD
|
||||
# process_model = ProcessModelService().get_process_model(
|
||||
# process_model = ProcessModelService.get_process_model(
|
||||
# process_model_id=f"{process_model_identifier}"
|
||||
# )
|
||||
# process_instance_report = cls(
|
||||
|
|
|
@ -34,10 +34,10 @@ class ProcessModelInfo:
|
|||
primary_file_name: str | None = None
|
||||
primary_process_id: str | None = None
|
||||
display_order: int | None = 0
|
||||
is_review: bool = False
|
||||
files: list[File] | None = field(default_factory=list[File])
|
||||
fault_or_suspend_on_exception: str = NotificationType.fault.value
|
||||
exception_notification_addresses: list[str] = field(default_factory=list)
|
||||
parent_groups: list[dict] | None = None
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
"""__post_init__."""
|
||||
|
@ -71,7 +71,6 @@ class ProcessModelInfoSchema(Schema):
|
|||
display_order = marshmallow.fields.Integer(allow_none=True)
|
||||
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)
|
||||
files = marshmallow.fields.List(marshmallow.fields.Nested("FileSchema"))
|
||||
fault_or_suspend_on_exception = marshmallow.fields.String()
|
||||
exception_notification_addresses = marshmallow.fields.List(
|
||||
|
|
|
@ -8,6 +8,7 @@ from sqlalchemy import ForeignKey
|
|||
from sqlalchemy.orm import deferred
|
||||
|
||||
from spiffworkflow_backend.models.group import GroupModel
|
||||
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
|
||||
|
||||
|
||||
@dataclass
|
||||
|
@ -16,7 +17,9 @@ class SpiffStepDetailsModel(SpiffworkflowBaseDBModel):
|
|||
|
||||
__tablename__ = "spiff_step_details"
|
||||
id: int = db.Column(db.Integer, primary_key=True)
|
||||
process_instance_id: int = db.Column(db.Integer, nullable=False)
|
||||
process_instance_id: int = db.Column(
|
||||
ForeignKey(ProcessInstanceModel.id), nullable=False # type: ignore
|
||||
)
|
||||
spiff_step: int = db.Column(db.Integer, nullable=False)
|
||||
task_json: str = deferred(db.Column(db.JSON, nullable=False)) # type: ignore
|
||||
timestamp: float = db.Column(db.DECIMAL(17, 6), nullable=False)
|
||||
|
|
|
@ -27,28 +27,28 @@ ALLOWED_BPMN_EXTENSIONS = {"bpmn", "dmn"}
|
|||
|
||||
|
||||
@admin_blueprint.route("/process-groups", methods=["GET"])
|
||||
def process_groups_list() -> str:
|
||||
"""Process_groups_list."""
|
||||
process_groups = ProcessModelService().get_process_groups()
|
||||
return render_template("process_groups_list.html", process_groups=process_groups)
|
||||
def process_group_list() -> str:
|
||||
"""Process_group_list."""
|
||||
process_groups = ProcessModelService.get_process_groups()
|
||||
return render_template("process_group_list.html", process_groups=process_groups)
|
||||
|
||||
|
||||
@admin_blueprint.route("/process-groups/<process_group_id>", methods=["GET"])
|
||||
def process_group_show(process_group_id: str) -> str:
|
||||
"""Show_process_group."""
|
||||
process_group = ProcessModelService().get_process_group(process_group_id)
|
||||
process_group = ProcessModelService.get_process_group(process_group_id)
|
||||
return render_template("process_group_show.html", process_group=process_group)
|
||||
|
||||
|
||||
@admin_blueprint.route("/process-models/<process_model_id>", methods=["GET"])
|
||||
def process_model_show(process_model_id: str) -> Union[str, Response]:
|
||||
"""Show_process_model."""
|
||||
process_model = ProcessModelService().get_process_model(process_model_id)
|
||||
process_model = ProcessModelService.get_process_model(process_model_id)
|
||||
files = SpecFileService.get_files(process_model, extension_filter="bpmn")
|
||||
current_file_name = process_model.primary_file_name
|
||||
if current_file_name is None:
|
||||
flash("No primary_file_name", "error")
|
||||
return redirect(url_for("admin.process_groups_list"))
|
||||
return redirect(url_for("admin.process_group_list"))
|
||||
bpmn_xml = SpecFileService.get_data(process_model, current_file_name)
|
||||
return render_template(
|
||||
"process_model_show.html",
|
||||
|
@ -64,7 +64,7 @@ def process_model_show(process_model_id: str) -> Union[str, Response]:
|
|||
)
|
||||
def process_model_show_file(process_model_id: str, file_name: str) -> str:
|
||||
"""Process_model_show_file."""
|
||||
process_model = ProcessModelService().get_process_model(process_model_id)
|
||||
process_model = ProcessModelService.get_process_model(process_model_id)
|
||||
bpmn_xml = SpecFileService.get_data(process_model, file_name)
|
||||
files = SpecFileService.get_files(process_model, extension_filter="bpmn")
|
||||
return render_template(
|
||||
|
@ -81,8 +81,7 @@ def process_model_show_file(process_model_id: str, file_name: str) -> str:
|
|||
)
|
||||
def process_model_upload_file(process_model_id: str) -> Response:
|
||||
"""Process_model_upload_file."""
|
||||
process_model_service = ProcessModelService()
|
||||
process_model = process_model_service.get_process_model(process_model_id)
|
||||
process_model = ProcessModelService.get_process_model(process_model_id)
|
||||
|
||||
if "file" not in request.files:
|
||||
flash("No file part", "error")
|
||||
|
@ -97,7 +96,7 @@ def process_model_upload_file(process_model_id: str) -> Response:
|
|||
SpecFileService.add_file(
|
||||
process_model, request_file.filename, request_file.stream.read()
|
||||
)
|
||||
process_model_service.save_process_model(process_model)
|
||||
ProcessModelService.save_process_model(process_model)
|
||||
|
||||
return redirect(
|
||||
url_for("admin.process_model_show", process_model_id=process_model.id)
|
||||
|
@ -109,7 +108,7 @@ def process_model_upload_file(process_model_id: str) -> Response:
|
|||
)
|
||||
def process_model_edit(process_model_id: str, file_name: str) -> str:
|
||||
"""Edit_bpmn."""
|
||||
process_model = ProcessModelService().get_process_model(process_model_id)
|
||||
process_model = ProcessModelService.get_process_model(process_model_id)
|
||||
bpmn_xml = SpecFileService.get_data(process_model, file_name)
|
||||
|
||||
return render_template(
|
||||
|
@ -125,11 +124,11 @@ def process_model_edit(process_model_id: str, file_name: str) -> str:
|
|||
)
|
||||
def process_model_save(process_model_id: str, file_name: str) -> Union[str, Response]:
|
||||
"""Process_model_save."""
|
||||
process_model = ProcessModelService().get_process_model(process_model_id)
|
||||
process_model = ProcessModelService.get_process_model(process_model_id)
|
||||
SpecFileService.update_file(process_model, file_name, request.get_data())
|
||||
if process_model.primary_file_name is None:
|
||||
flash("No primary_file_name", "error")
|
||||
return redirect(url_for("admin.process_groups_list"))
|
||||
return redirect(url_for("admin.process_group_list"))
|
||||
bpmn_xml = SpecFileService.get_data(process_model, process_model.primary_file_name)
|
||||
return render_template(
|
||||
"process_model_edit.html",
|
||||
|
@ -143,19 +142,21 @@ def process_model_save(process_model_id: str, file_name: str) -> Union[str, Resp
|
|||
def process_model_run(process_model_id: str) -> Union[str, Response]:
|
||||
"""Process_model_run."""
|
||||
user = UserService.create_user("internal", "Mr. Test", username="Mr. Test")
|
||||
process_instance = ProcessInstanceService.create_process_instance(
|
||||
process_model_id, user
|
||||
process_instance = (
|
||||
ProcessInstanceService.create_process_instance_from_process_model_identifier(
|
||||
process_model_id, user
|
||||
)
|
||||
)
|
||||
processor = ProcessInstanceProcessor(process_instance)
|
||||
processor.do_engine_steps()
|
||||
result = processor.get_data()
|
||||
|
||||
process_model = ProcessModelService().get_process_model(process_model_id)
|
||||
process_model = ProcessModelService.get_process_model(process_model_id)
|
||||
files = SpecFileService.get_files(process_model, extension_filter="bpmn")
|
||||
current_file_name = process_model.primary_file_name
|
||||
if current_file_name is None:
|
||||
flash("No primary_file_name", "error")
|
||||
return redirect(url_for("admin.process_groups_list"))
|
||||
return redirect(url_for("admin.process_group_list"))
|
||||
bpmn_xml = SpecFileService.get_data(process_model, current_file_name)
|
||||
|
||||
return render_template(
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
{% block content %}
|
||||
<button
|
||||
type="button"
|
||||
onclick="window.location.href='{{ url_for( 'admin.process_groups_list') }}';"
|
||||
onclick="window.location.href='{{ url_for( 'admin.process_group_list') }}';"
|
||||
>
|
||||
Back
|
||||
</button>
|
||||
|
|
|
@ -30,6 +30,7 @@ from SpiffWorkflow.task import TaskState
|
|||
from sqlalchemy import and_
|
||||
from sqlalchemy import asc
|
||||
from sqlalchemy import desc
|
||||
from sqlalchemy.orm import joinedload
|
||||
|
||||
from spiffworkflow_backend.exceptions.process_entity_not_found_error import (
|
||||
ProcessEntityNotFoundError,
|
||||
|
@ -63,6 +64,7 @@ from spiffworkflow_backend.models.spec_reference import SpecReferenceSchema
|
|||
from spiffworkflow_backend.models.spiff_logging import SpiffLoggingModel
|
||||
from spiffworkflow_backend.models.spiff_step_details import SpiffStepDetailsModel
|
||||
from spiffworkflow_backend.models.user import UserModel
|
||||
from spiffworkflow_backend.models.user_group_assignment import UserGroupAssignmentModel
|
||||
from spiffworkflow_backend.routes.user import verify_token
|
||||
from spiffworkflow_backend.services.authorization_service import AuthorizationService
|
||||
from spiffworkflow_backend.services.error_handling_service import ErrorHandlingService
|
||||
|
@ -157,9 +159,8 @@ def un_modify_modified_process_model_id(modified_process_model_id: str) -> str:
|
|||
|
||||
def process_group_add(body: dict) -> flask.wrappers.Response:
|
||||
"""Add_process_group."""
|
||||
process_model_service = ProcessModelService()
|
||||
process_group = ProcessGroup(**body)
|
||||
process_model_service.add_process_group(process_group)
|
||||
ProcessModelService.add_process_group(process_group)
|
||||
return make_response(jsonify(process_group), 201)
|
||||
|
||||
|
||||
|
@ -183,20 +184,20 @@ def process_group_update(
|
|||
|
||||
process_group_id = un_modify_modified_process_model_id(modified_process_group_id)
|
||||
process_group = ProcessGroup(id=process_group_id, **body_filtered)
|
||||
ProcessModelService().update_process_group(process_group)
|
||||
ProcessModelService.update_process_group(process_group)
|
||||
return make_response(jsonify(process_group), 200)
|
||||
|
||||
|
||||
def process_groups_list(
|
||||
def process_group_list(
|
||||
process_group_identifier: Optional[str] = None, page: int = 1, per_page: int = 100
|
||||
) -> flask.wrappers.Response:
|
||||
"""Process_groups_list."""
|
||||
"""Process_group_list."""
|
||||
if process_group_identifier is not None:
|
||||
process_groups = ProcessModelService().get_process_groups(
|
||||
process_groups = ProcessModelService.get_process_groups(
|
||||
process_group_identifier
|
||||
)
|
||||
else:
|
||||
process_groups = ProcessModelService().get_process_groups()
|
||||
process_groups = ProcessModelService.get_process_groups()
|
||||
batch = ProcessModelService().get_batch(
|
||||
items=process_groups, page=page, per_page=per_page
|
||||
)
|
||||
|
@ -222,7 +223,7 @@ def process_group_show(
|
|||
"""Process_group_show."""
|
||||
process_group_id = un_modify_modified_process_model_id(modified_process_group_id)
|
||||
try:
|
||||
process_group = ProcessModelService().get_process_group(process_group_id)
|
||||
process_group = ProcessModelService.get_process_group(process_group_id)
|
||||
except ProcessEntityNotFoundError as exception:
|
||||
raise (
|
||||
ApiError(
|
||||
|
@ -231,13 +232,17 @@ def process_group_show(
|
|||
status_code=400,
|
||||
)
|
||||
) from exception
|
||||
|
||||
process_group.parent_groups = ProcessModelService.get_parent_group_array(
|
||||
process_group.id
|
||||
)
|
||||
return make_response(jsonify(process_group), 200)
|
||||
|
||||
|
||||
def process_group_move(
|
||||
modified_process_group_identifier: str, new_location: str
|
||||
) -> flask.wrappers.Response:
|
||||
"""process_group_move."""
|
||||
"""Process_group_move."""
|
||||
original_process_group_id = un_modify_modified_process_model_id(
|
||||
modified_process_group_identifier
|
||||
)
|
||||
|
@ -268,8 +273,7 @@ def process_model_create(
|
|||
unmodified_process_group_id = un_modify_modified_process_model_id(
|
||||
modified_process_group_id
|
||||
)
|
||||
process_model_service = ProcessModelService()
|
||||
process_group = process_model_service.get_process_group(unmodified_process_group_id)
|
||||
process_group = ProcessModelService.get_process_group(unmodified_process_group_id)
|
||||
if process_group is None:
|
||||
raise ApiError(
|
||||
error_code="process_model_could_not_be_created",
|
||||
|
@ -277,7 +281,7 @@ def process_model_create(
|
|||
status_code=400,
|
||||
)
|
||||
|
||||
process_model_service.add_process_model(process_model_info)
|
||||
ProcessModelService.add_process_model(process_model_info)
|
||||
return Response(
|
||||
json.dumps(ProcessModelInfoSchema().dump(process_model_info)),
|
||||
status=201,
|
||||
|
@ -314,7 +318,7 @@ def process_model_update(
|
|||
|
||||
# process_model_identifier = f"{process_group_id}/{process_model_id}"
|
||||
process_model = get_process_model(process_model_identifier)
|
||||
ProcessModelService().update_process_model(process_model, body_filtered)
|
||||
ProcessModelService.update_process_model(process_model, body_filtered)
|
||||
return ProcessModelInfoSchema().dump(process_model)
|
||||
|
||||
|
||||
|
@ -329,14 +333,17 @@ def process_model_show(modified_process_model_identifier: str) -> Any:
|
|||
process_model.files = files
|
||||
for file in process_model.files:
|
||||
file.references = SpecFileService.get_references_for_file(file, process_model)
|
||||
process_model_json = ProcessModelInfoSchema().dump(process_model)
|
||||
return process_model_json
|
||||
|
||||
process_model.parent_groups = ProcessModelService.get_parent_group_array(
|
||||
process_model.id
|
||||
)
|
||||
return make_response(jsonify(process_model), 200)
|
||||
|
||||
|
||||
def process_model_move(
|
||||
modified_process_model_identifier: str, new_location: str
|
||||
) -> flask.wrappers.Response:
|
||||
"""process_model_move."""
|
||||
"""Process_model_move."""
|
||||
original_process_model_id = un_modify_modified_process_model_id(
|
||||
modified_process_model_identifier
|
||||
)
|
||||
|
@ -349,12 +356,15 @@ def process_model_move(
|
|||
def process_model_list(
|
||||
process_group_identifier: Optional[str] = None,
|
||||
recursive: Optional[bool] = False,
|
||||
filter_runnable_by_user: Optional[bool] = False,
|
||||
page: int = 1,
|
||||
per_page: int = 100,
|
||||
) -> flask.wrappers.Response:
|
||||
"""Process model list!"""
|
||||
process_models = ProcessModelService().get_process_models(
|
||||
process_group_id=process_group_identifier, recursive=recursive
|
||||
process_models = ProcessModelService.get_process_models(
|
||||
process_group_id=process_group_identifier,
|
||||
recursive=recursive,
|
||||
filter_runnable_by_user=filter_runnable_by_user,
|
||||
)
|
||||
batch = ProcessModelService().get_batch(
|
||||
process_models, page=page, per_page=per_page
|
||||
|
@ -483,8 +493,10 @@ def process_instance_create(modified_process_model_id: str) -> flask.wrappers.Re
|
|||
process_model_identifier = un_modify_modified_process_model_id(
|
||||
modified_process_model_id
|
||||
)
|
||||
process_instance = ProcessInstanceService.create_process_instance(
|
||||
process_model_identifier, g.user
|
||||
process_instance = (
|
||||
ProcessInstanceService.create_process_instance_from_process_model_identifier(
|
||||
process_model_identifier, g.user
|
||||
)
|
||||
)
|
||||
return Response(
|
||||
json.dumps(ProcessInstanceModelSchema().dump(process_instance)),
|
||||
|
@ -494,6 +506,7 @@ def process_instance_create(modified_process_model_id: str) -> flask.wrappers.Re
|
|||
|
||||
|
||||
def process_instance_run(
|
||||
modified_process_model_identifier: str,
|
||||
process_instance_id: int,
|
||||
do_engine_steps: bool = True,
|
||||
) -> flask.wrappers.Response:
|
||||
|
@ -758,6 +771,9 @@ def process_instance_list(
|
|||
end_from: Optional[int] = None,
|
||||
end_to: Optional[int] = None,
|
||||
process_status: Optional[str] = None,
|
||||
initiated_by_me: Optional[bool] = None,
|
||||
with_tasks_completed_by_me: Optional[bool] = None,
|
||||
with_tasks_completed_by_my_group: Optional[bool] = None,
|
||||
user_filter: Optional[bool] = False,
|
||||
report_identifier: Optional[str] = None,
|
||||
) -> flask.wrappers.Response:
|
||||
|
@ -774,6 +790,9 @@ def process_instance_list(
|
|||
end_from,
|
||||
end_to,
|
||||
process_status.split(",") if process_status else None,
|
||||
initiated_by_me,
|
||||
with_tasks_completed_by_me,
|
||||
with_tasks_completed_by_my_group,
|
||||
)
|
||||
else:
|
||||
report_filter = (
|
||||
|
@ -785,11 +804,19 @@ def process_instance_list(
|
|||
end_from,
|
||||
end_to,
|
||||
process_status,
|
||||
initiated_by_me,
|
||||
with_tasks_completed_by_me,
|
||||
with_tasks_completed_by_my_group,
|
||||
)
|
||||
)
|
||||
|
||||
# process_model_identifier = un_modify_modified_process_model_id(modified_process_model_identifier)
|
||||
process_instance_query = ProcessInstanceModel.query
|
||||
# Always join that hot user table for good performance at serialization time.
|
||||
process_instance_query = process_instance_query.options(
|
||||
joinedload(ProcessInstanceModel.process_initiator)
|
||||
)
|
||||
|
||||
if report_filter.process_model_identifier is not None:
|
||||
process_model = get_process_model(
|
||||
f"{report_filter.process_model_identifier}",
|
||||
|
@ -833,9 +860,81 @@ def process_instance_list(
|
|||
ProcessInstanceModel.status.in_(report_filter.process_status) # type: ignore
|
||||
)
|
||||
|
||||
process_instances = process_instance_query.order_by(
|
||||
ProcessInstanceModel.start_in_seconds.desc(), ProcessInstanceModel.id.desc() # type: ignore
|
||||
).paginate(page=page, per_page=per_page, error_out=False)
|
||||
if report_filter.initiated_by_me is True:
|
||||
process_instance_query = process_instance_query.filter(
|
||||
ProcessInstanceModel.status.in_(["complete", "error", "terminated"]) # type: ignore
|
||||
)
|
||||
process_instance_query = process_instance_query.filter_by(
|
||||
process_initiator=g.user
|
||||
)
|
||||
|
||||
# TODO: not sure if this is exactly what is wanted
|
||||
if report_filter.with_tasks_completed_by_me is True:
|
||||
process_instance_query = process_instance_query.filter(
|
||||
ProcessInstanceModel.status.in_(["complete", "error", "terminated"]) # type: ignore
|
||||
)
|
||||
# process_instance_query = process_instance_query.join(UserModel, UserModel.id == ProcessInstanceModel.process_initiator_id)
|
||||
# process_instance_query = process_instance_query.add_columns(UserModel.username)
|
||||
# search for process_instance.UserModel.username in this file for more details about why adding columns is annoying.
|
||||
|
||||
process_instance_query = process_instance_query.filter(
|
||||
ProcessInstanceModel.process_initiator_id != g.user.id
|
||||
)
|
||||
process_instance_query = process_instance_query.join(
|
||||
SpiffStepDetailsModel,
|
||||
ProcessInstanceModel.id == SpiffStepDetailsModel.process_instance_id,
|
||||
)
|
||||
process_instance_query = process_instance_query.join(
|
||||
SpiffLoggingModel,
|
||||
ProcessInstanceModel.id == SpiffLoggingModel.process_instance_id,
|
||||
)
|
||||
process_instance_query = process_instance_query.filter(
|
||||
SpiffLoggingModel.message.contains("COMPLETED") # type: ignore
|
||||
)
|
||||
process_instance_query = process_instance_query.filter(
|
||||
SpiffLoggingModel.spiff_step == SpiffStepDetailsModel.spiff_step
|
||||
)
|
||||
process_instance_query = process_instance_query.filter(
|
||||
SpiffStepDetailsModel.completed_by_user_id == g.user.id
|
||||
)
|
||||
|
||||
if report_filter.with_tasks_completed_by_my_group is True:
|
||||
process_instance_query = process_instance_query.filter(
|
||||
ProcessInstanceModel.status.in_(["complete", "error", "terminated"]) # type: ignore
|
||||
)
|
||||
process_instance_query = process_instance_query.join(
|
||||
SpiffStepDetailsModel,
|
||||
ProcessInstanceModel.id == SpiffStepDetailsModel.process_instance_id,
|
||||
)
|
||||
process_instance_query = process_instance_query.join(
|
||||
SpiffLoggingModel,
|
||||
ProcessInstanceModel.id == SpiffLoggingModel.process_instance_id,
|
||||
)
|
||||
process_instance_query = process_instance_query.filter(
|
||||
SpiffLoggingModel.message.contains("COMPLETED") # type: ignore
|
||||
)
|
||||
process_instance_query = process_instance_query.filter(
|
||||
SpiffLoggingModel.spiff_step == SpiffStepDetailsModel.spiff_step
|
||||
)
|
||||
process_instance_query = process_instance_query.join(
|
||||
GroupModel,
|
||||
GroupModel.id == SpiffStepDetailsModel.lane_assignment_id,
|
||||
)
|
||||
process_instance_query = process_instance_query.join(
|
||||
UserGroupAssignmentModel,
|
||||
UserGroupAssignmentModel.group_id == GroupModel.id,
|
||||
)
|
||||
process_instance_query = process_instance_query.filter(
|
||||
UserGroupAssignmentModel.user_id == g.user.id
|
||||
)
|
||||
|
||||
process_instances = (
|
||||
process_instance_query.group_by(ProcessInstanceModel.id)
|
||||
.order_by(
|
||||
ProcessInstanceModel.start_in_seconds.desc(), ProcessInstanceModel.id.desc() # type: ignore
|
||||
)
|
||||
.paginate(page=page, per_page=per_page, error_out=False)
|
||||
)
|
||||
|
||||
results = list(
|
||||
map(
|
||||
|
@ -1001,7 +1100,7 @@ def authentication_callback(
|
|||
f"{service}/{auth_method}", response, g.user.id, create_if_not_exists=True
|
||||
)
|
||||
return redirect(
|
||||
f"{current_app.config['SPIFFWORKFLOW_FRONTEND_URL']}/admin/authentications"
|
||||
f"{current_app.config['SPIFFWORKFLOW_FRONTEND_URL']}/admin/configuration"
|
||||
)
|
||||
|
||||
|
||||
|
@ -1056,7 +1155,7 @@ def task_list_my_tasks(page: int = 1, per_page: int = 100) -> flask.wrappers.Res
|
|||
# just need this add_columns to add the process_model_identifier. Then add everything back that was removed.
|
||||
.add_columns(
|
||||
ProcessInstanceModel.process_model_identifier,
|
||||
ProcessInstanceModel.process_group_identifier,
|
||||
ProcessInstanceModel.process_model_display_name,
|
||||
ProcessInstanceModel.status,
|
||||
ActiveTaskModel.task_name,
|
||||
ActiveTaskModel.task_title,
|
||||
|
@ -1197,8 +1296,10 @@ def process_instance_task_list(
|
|||
)
|
||||
.first()
|
||||
)
|
||||
if step_detail is not None:
|
||||
process_instance.bpmn_json = json.dumps(step_detail.task_json)
|
||||
if step_detail is not None and process_instance.bpmn_json is not None:
|
||||
bpmn_json = json.loads(process_instance.bpmn_json)
|
||||
bpmn_json["tasks"] = step_detail.task_json
|
||||
process_instance.bpmn_json = json.dumps(bpmn_json)
|
||||
|
||||
processor = ProcessInstanceProcessor(process_instance)
|
||||
|
||||
|
@ -1318,7 +1419,7 @@ def task_submit(
|
|||
task_id, process_instance, processor=processor
|
||||
)
|
||||
AuthorizationService.assert_user_can_complete_spiff_task(
|
||||
processor, spiff_task, principal.user
|
||||
process_instance.id, spiff_task, principal.user
|
||||
)
|
||||
|
||||
if spiff_task.state != TaskState.READY:
|
||||
|
@ -1503,7 +1604,7 @@ 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)
|
||||
process_model = ProcessModelService.get_process_model(process_model_id)
|
||||
except ProcessEntityNotFoundError as exception:
|
||||
raise (
|
||||
ApiError(
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
"""Get_env."""
|
||||
from typing import Any
|
||||
|
||||
from flask_bpmn.models.db import db
|
||||
|
||||
from spiffworkflow_backend.models.process_instance_metadata import (
|
||||
ProcessInstanceMetadataModel,
|
||||
)
|
||||
from spiffworkflow_backend.models.script_attributes_context import (
|
||||
ScriptAttributesContext,
|
||||
)
|
||||
from spiffworkflow_backend.scripts.script import Script
|
||||
|
||||
|
||||
class SaveProcessInstanceMetadata(Script):
|
||||
"""SaveProcessInstanceMetadata."""
|
||||
|
||||
def get_description(self) -> str:
|
||||
"""Get_description."""
|
||||
return """Save a given dict as process instance metadata (useful for creating reports)."""
|
||||
|
||||
def run(
|
||||
self,
|
||||
script_attributes_context: ScriptAttributesContext,
|
||||
*args: Any,
|
||||
**kwargs: Any,
|
||||
) -> Any:
|
||||
"""Run."""
|
||||
metadata_dict = args[0]
|
||||
for key, value in metadata_dict.items():
|
||||
pim = ProcessInstanceMetadataModel.query.filter_by(
|
||||
process_instance_id=script_attributes_context.process_instance_id,
|
||||
key=key,
|
||||
).first()
|
||||
if pim is None:
|
||||
pim = ProcessInstanceMetadataModel(
|
||||
process_instance_id=script_attributes_context.process_instance_id,
|
||||
key=key,
|
||||
)
|
||||
pim.value = value
|
||||
db.session.add(pim)
|
||||
db.session.commit()
|
|
@ -30,7 +30,7 @@ def load_acceptance_test_fixtures() -> list[ProcessInstanceModel]:
|
|||
process_instances = []
|
||||
for i in range(len(statuses)):
|
||||
|
||||
process_instance = ProcessInstanceService.create_process_instance(
|
||||
process_instance = ProcessInstanceService.create_process_instance_from_process_model_identifier(
|
||||
test_process_model_id, user
|
||||
)
|
||||
process_instance.status = statuses[i]
|
||||
|
|
|
@ -24,9 +24,6 @@ from spiffworkflow_backend.models.user import UserModel
|
|||
from spiffworkflow_backend.models.user import UserNotFoundError
|
||||
from spiffworkflow_backend.models.user_group_assignment import UserGroupAssignmentModel
|
||||
from spiffworkflow_backend.services.group_service import GroupService
|
||||
from spiffworkflow_backend.services.process_instance_processor import (
|
||||
ProcessInstanceProcessor,
|
||||
)
|
||||
from spiffworkflow_backend.services.user_service import UserService
|
||||
|
||||
|
||||
|
@ -393,25 +390,25 @@ class AuthorizationService:
|
|||
|
||||
@staticmethod
|
||||
def assert_user_can_complete_spiff_task(
|
||||
processor: ProcessInstanceProcessor,
|
||||
process_instance_id: int,
|
||||
spiff_task: SpiffTask,
|
||||
user: UserModel,
|
||||
) -> bool:
|
||||
"""Assert_user_can_complete_spiff_task."""
|
||||
active_task = ActiveTaskModel.query.filter_by(
|
||||
task_name=spiff_task.task_spec.name,
|
||||
process_instance_id=processor.process_instance_model.id,
|
||||
process_instance_id=process_instance_id,
|
||||
).first()
|
||||
if active_task is None:
|
||||
raise ActiveTaskNotFoundError(
|
||||
f"Could find an active task with task name '{spiff_task.task_spec.name}'"
|
||||
f" for process instance '{processor.process_instance_model.id}'"
|
||||
f" for process instance '{process_instance_id}'"
|
||||
)
|
||||
|
||||
if user not in active_task.potential_owners:
|
||||
raise UserDoesNotHaveAccessToTaskError(
|
||||
f"User {user.username} does not have access to update task'{spiff_task.task_spec.name}'"
|
||||
f" for process instance '{processor.process_instance_model.id}'"
|
||||
f" for process instance '{process_instance_id}'"
|
||||
)
|
||||
return True
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ class DataSetupService:
|
|||
|
||||
current_app.logger.debug("DataSetupService.save_all_process_models() start")
|
||||
failing_process_models = []
|
||||
process_models = ProcessModelService().get_process_models()
|
||||
process_models = ProcessModelService.get_process_models(recursive=True)
|
||||
SpecFileService.clear_caches()
|
||||
for process_model in process_models:
|
||||
current_app.logger.debug(f"Process Model: {process_model.display_name}")
|
||||
|
|
|
@ -34,7 +34,7 @@ class ErrorHandlingService:
|
|||
self, _processor: ProcessInstanceProcessor, _error: Union[ApiError, Exception]
|
||||
) -> None:
|
||||
"""On unhandled exceptions, set instance.status based on model.fault_or_suspend_on_exception."""
|
||||
process_model = ProcessModelService().get_process_model(
|
||||
process_model = ProcessModelService.get_process_model(
|
||||
_processor.process_model_identifier
|
||||
)
|
||||
if process_model.fault_or_suspend_on_exception == "suspend":
|
||||
|
|
|
@ -117,7 +117,7 @@ class MessageService:
|
|||
user: UserModel,
|
||||
) -> ProcessInstanceModel:
|
||||
"""Process_message_triggerable_process_model."""
|
||||
process_instance_receive = ProcessInstanceService.create_process_instance(
|
||||
process_instance_receive = ProcessInstanceService.create_process_instance_from_process_model_identifier(
|
||||
message_triggerable_process_model.process_model_identifier,
|
||||
user,
|
||||
)
|
||||
|
|
|
@ -349,7 +349,9 @@ 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_model_display_name = (
|
||||
process_instance_model.process_model_display_name
|
||||
)
|
||||
|
||||
try:
|
||||
self.bpmn_process_instance = self.__get_bpmn_process_instance(
|
||||
|
@ -374,7 +376,7 @@ class ProcessInstanceProcessor:
|
|||
cls, process_model_identifier: str
|
||||
) -> Tuple[BpmnProcessSpec, IdToBpmnProcessSpecMapping]:
|
||||
"""Get_process_model_and_subprocesses."""
|
||||
process_model_info = ProcessModelService().get_process_model(
|
||||
process_model_info = ProcessModelService.get_process_model(
|
||||
process_model_identifier
|
||||
)
|
||||
if process_model_info is None:
|
||||
|
@ -540,13 +542,8 @@ class ProcessInstanceProcessor:
|
|||
"""SaveSpiffStepDetails."""
|
||||
bpmn_json = self.serialize()
|
||||
wf_json = json.loads(bpmn_json)
|
||||
task_json = "{}"
|
||||
if "tasks" in wf_json:
|
||||
task_json = json.dumps(wf_json["tasks"])
|
||||
task_json = wf_json["tasks"]
|
||||
|
||||
# TODO want to just save the tasks, something wasn't immediately working
|
||||
# so after the flow works with the full wf_json revisit this
|
||||
task_json = wf_json
|
||||
return {
|
||||
"process_instance_id": self.process_instance_model.id,
|
||||
"spiff_step": self.process_instance_model.spiff_step or 1,
|
||||
|
@ -593,16 +590,12 @@ class ProcessInstanceProcessor:
|
|||
if self.bpmn_process_instance.is_completed():
|
||||
self.process_instance_model.end_in_seconds = round(time.time())
|
||||
|
||||
active_tasks = ActiveTaskModel.query.filter_by(
|
||||
process_instance_id=self.process_instance_model.id
|
||||
).all()
|
||||
if len(active_tasks) > 0:
|
||||
for at in active_tasks:
|
||||
db.session.delete(at)
|
||||
|
||||
db.session.add(self.process_instance_model)
|
||||
db.session.commit()
|
||||
|
||||
active_tasks = ActiveTaskModel.query.filter_by(
|
||||
process_instance_id=self.process_instance_model.id
|
||||
).all()
|
||||
ready_or_waiting_tasks = self.get_all_ready_or_waiting_tasks()
|
||||
for ready_or_waiting_task in ready_or_waiting_tasks:
|
||||
# filter out non-usertasks
|
||||
|
@ -629,27 +622,41 @@ class ProcessInstanceProcessor:
|
|||
if process_model_info is not None:
|
||||
process_model_display_name = process_model_info.display_name
|
||||
|
||||
active_task = ActiveTaskModel(
|
||||
process_instance_id=self.process_instance_model.id,
|
||||
process_model_display_name=process_model_display_name,
|
||||
form_file_name=form_file_name,
|
||||
ui_form_file_name=ui_form_file_name,
|
||||
task_id=str(ready_or_waiting_task.id),
|
||||
task_name=ready_or_waiting_task.task_spec.name,
|
||||
task_title=ready_or_waiting_task.task_spec.description,
|
||||
task_type=ready_or_waiting_task.task_spec.__class__.__name__,
|
||||
task_status=ready_or_waiting_task.get_state_name(),
|
||||
lane_assignment_id=potential_owner_hash["lane_assignment_id"],
|
||||
)
|
||||
db.session.add(active_task)
|
||||
db.session.commit()
|
||||
active_task = None
|
||||
for at in active_tasks:
|
||||
if at.task_id == str(ready_or_waiting_task.id):
|
||||
active_task = at
|
||||
active_tasks.remove(at)
|
||||
|
||||
for potential_owner_id in potential_owner_hash["potential_owner_ids"]:
|
||||
active_task_user = ActiveTaskUserModel(
|
||||
user_id=potential_owner_id, active_task_id=active_task.id
|
||||
if active_task is None:
|
||||
active_task = ActiveTaskModel(
|
||||
process_instance_id=self.process_instance_model.id,
|
||||
process_model_display_name=process_model_display_name,
|
||||
form_file_name=form_file_name,
|
||||
ui_form_file_name=ui_form_file_name,
|
||||
task_id=str(ready_or_waiting_task.id),
|
||||
task_name=ready_or_waiting_task.task_spec.name,
|
||||
task_title=ready_or_waiting_task.task_spec.description,
|
||||
task_type=ready_or_waiting_task.task_spec.__class__.__name__,
|
||||
task_status=ready_or_waiting_task.get_state_name(),
|
||||
lane_assignment_id=potential_owner_hash["lane_assignment_id"],
|
||||
)
|
||||
db.session.add(active_task_user)
|
||||
db.session.commit()
|
||||
db.session.add(active_task)
|
||||
db.session.commit()
|
||||
|
||||
for potential_owner_id in potential_owner_hash[
|
||||
"potential_owner_ids"
|
||||
]:
|
||||
active_task_user = ActiveTaskUserModel(
|
||||
user_id=potential_owner_id, active_task_id=active_task.id
|
||||
)
|
||||
db.session.add(active_task_user)
|
||||
db.session.commit()
|
||||
|
||||
if len(active_tasks) > 0:
|
||||
for at in active_tasks:
|
||||
db.session.delete(at)
|
||||
db.session.commit()
|
||||
|
||||
@staticmethod
|
||||
def get_parser() -> MyCustomParser:
|
||||
|
@ -662,7 +669,7 @@ class ProcessInstanceProcessor:
|
|||
bpmn_process_identifier: str,
|
||||
) -> Optional[str]:
|
||||
"""Backfill_missing_spec_reference_records."""
|
||||
process_models = ProcessModelService().get_process_models(recursive=True)
|
||||
process_models = ProcessModelService.get_process_models(recursive=True)
|
||||
for process_model in process_models:
|
||||
try:
|
||||
refs = SpecFileService.reference_map(
|
||||
|
|
|
@ -18,6 +18,9 @@ class ProcessInstanceReportFilter:
|
|||
end_from: Optional[int] = None
|
||||
end_to: Optional[int] = None
|
||||
process_status: Optional[list[str]] = None
|
||||
initiated_by_me: Optional[bool] = None
|
||||
with_tasks_completed_by_me: Optional[bool] = None
|
||||
with_tasks_completed_by_my_group: Optional[bool] = None
|
||||
|
||||
def to_dict(self) -> dict[str, str]:
|
||||
"""To_dict."""
|
||||
|
@ -35,6 +38,16 @@ class ProcessInstanceReportFilter:
|
|||
d["end_to"] = str(self.end_to)
|
||||
if self.process_status is not None:
|
||||
d["process_status"] = ",".join(self.process_status)
|
||||
if self.initiated_by_me is not None:
|
||||
d["initiated_by_me"] = str(self.initiated_by_me).lower()
|
||||
if self.with_tasks_completed_by_me is not None:
|
||||
d["with_tasks_completed_by_me"] = str(
|
||||
self.with_tasks_completed_by_me
|
||||
).lower()
|
||||
if self.with_tasks_completed_by_my_group is not None:
|
||||
d["with_tasks_completed_by_my_group"] = str(
|
||||
self.with_tasks_completed_by_my_group
|
||||
).lower()
|
||||
|
||||
return d
|
||||
|
||||
|
@ -63,48 +76,61 @@ class ProcessInstanceReportService:
|
|||
"columns": [
|
||||
{"Header": "id", "accessor": "id"},
|
||||
{
|
||||
"Header": "process_model_identifier",
|
||||
"accessor": "process_model_identifier",
|
||||
"Header": "process_model_display_name",
|
||||
"accessor": "process_model_display_name",
|
||||
},
|
||||
{"Header": "start_in_seconds", "accessor": "start_in_seconds"},
|
||||
{"Header": "end_in_seconds", "accessor": "end_in_seconds"},
|
||||
{"Header": "username", "accessor": "username"},
|
||||
{"Header": "status", "accessor": "status"},
|
||||
],
|
||||
},
|
||||
"system_report_instances_initiated_by_me": {
|
||||
"columns": [
|
||||
{"Header": "id", "accessor": "id"},
|
||||
{
|
||||
"Header": "process_model_identifier",
|
||||
"accessor": "process_model_identifier",
|
||||
"Header": "process_model_display_name",
|
||||
"accessor": "process_model_display_name",
|
||||
},
|
||||
{"Header": "start_in_seconds", "accessor": "start_in_seconds"},
|
||||
{"Header": "id", "accessor": "id"},
|
||||
{"Header": "end_in_seconds", "accessor": "end_in_seconds"},
|
||||
{"Header": "status", "accessor": "status"},
|
||||
],
|
||||
"filter_by": [{"field_name": "initiated_by_me", "field_value": True}],
|
||||
},
|
||||
"system_report_instances_with_tasks_completed_by_me": {
|
||||
"columns": [
|
||||
{"Header": "start_in_seconds", "accessor": "start_in_seconds"},
|
||||
{"Header": "end_in_seconds", "accessor": "end_in_seconds"},
|
||||
{"Header": "status", "accessor": "status"},
|
||||
{"Header": "id", "accessor": "id"},
|
||||
{
|
||||
"Header": "process_model_identifier",
|
||||
"accessor": "process_model_identifier",
|
||||
"Header": "process_model_display_name",
|
||||
"accessor": "process_model_display_name",
|
||||
},
|
||||
{"Header": "start_in_seconds", "accessor": "start_in_seconds"},
|
||||
{"Header": "end_in_seconds", "accessor": "end_in_seconds"},
|
||||
{"Header": "username", "accessor": "username"},
|
||||
{"Header": "status", "accessor": "status"},
|
||||
],
|
||||
"filter_by": [
|
||||
{"field_name": "with_tasks_completed_by_me", "field_value": True}
|
||||
],
|
||||
},
|
||||
"system_report_instances_with_tasks_completed_by_my_groups": {
|
||||
"columns": [
|
||||
{"Header": "id", "accessor": "id"},
|
||||
{
|
||||
"Header": "process_model_identifier",
|
||||
"accessor": "process_model_identifier",
|
||||
"Header": "process_model_display_name",
|
||||
"accessor": "process_model_display_name",
|
||||
},
|
||||
{"Header": "start_in_seconds", "accessor": "start_in_seconds"},
|
||||
{"Header": "end_in_seconds", "accessor": "end_in_seconds"},
|
||||
{"Header": "username", "accessor": "username"},
|
||||
{"Header": "status", "accessor": "status"},
|
||||
{"Header": "id", "accessor": "id"},
|
||||
],
|
||||
"filter_by": [
|
||||
{
|
||||
"field_name": "with_tasks_completed_by_my_group",
|
||||
"field_value": True,
|
||||
}
|
||||
],
|
||||
},
|
||||
}
|
||||
|
@ -112,7 +138,7 @@ class ProcessInstanceReportService:
|
|||
process_instance_report = ProcessInstanceReportModel(
|
||||
identifier=report_identifier,
|
||||
created_by_id=user.id,
|
||||
report_metadata=temp_system_metadata_map[report_identifier],
|
||||
report_metadata=temp_system_metadata_map[report_identifier], # type: ignore
|
||||
)
|
||||
|
||||
return process_instance_report # type: ignore
|
||||
|
@ -138,6 +164,10 @@ class ProcessInstanceReportService:
|
|||
"""Filter_from_metadata."""
|
||||
filters = cls.filter_by_to_dict(process_instance_report)
|
||||
|
||||
def bool_value(key: str) -> Optional[bool]:
|
||||
"""Bool_value."""
|
||||
return bool(filters[key]) if key in filters else None
|
||||
|
||||
def int_value(key: str) -> Optional[int]:
|
||||
"""Int_value."""
|
||||
return int(filters[key]) if key in filters else None
|
||||
|
@ -152,6 +182,11 @@ class ProcessInstanceReportService:
|
|||
end_from = int_value("end_from")
|
||||
end_to = int_value("end_to")
|
||||
process_status = list_value("process_status")
|
||||
initiated_by_me = bool_value("initiated_by_me")
|
||||
with_tasks_completed_by_me = bool_value("with_tasks_completed_by_me")
|
||||
with_tasks_completed_by_my_group = bool_value(
|
||||
"with_tasks_completed_by_my_group"
|
||||
)
|
||||
|
||||
report_filter = ProcessInstanceReportFilter(
|
||||
process_model_identifier,
|
||||
|
@ -160,6 +195,9 @@ class ProcessInstanceReportService:
|
|||
end_from,
|
||||
end_to,
|
||||
process_status,
|
||||
initiated_by_me,
|
||||
with_tasks_completed_by_me,
|
||||
with_tasks_completed_by_my_group,
|
||||
)
|
||||
|
||||
return report_filter
|
||||
|
@ -174,6 +212,9 @@ class ProcessInstanceReportService:
|
|||
end_from: Optional[int] = None,
|
||||
end_to: Optional[int] = None,
|
||||
process_status: Optional[str] = None,
|
||||
initiated_by_me: Optional[bool] = None,
|
||||
with_tasks_completed_by_me: Optional[bool] = None,
|
||||
with_tasks_completed_by_my_group: Optional[bool] = None,
|
||||
) -> ProcessInstanceReportFilter:
|
||||
"""Filter_from_metadata_with_overrides."""
|
||||
report_filter = cls.filter_from_metadata(process_instance_report)
|
||||
|
@ -190,5 +231,13 @@ class ProcessInstanceReportService:
|
|||
report_filter.end_to = end_to
|
||||
if process_status is not None:
|
||||
report_filter.process_status = process_status.split(",")
|
||||
if initiated_by_me is not None:
|
||||
report_filter.initiated_by_me = initiated_by_me
|
||||
if with_tasks_completed_by_me is not None:
|
||||
report_filter.with_tasks_completed_by_me = with_tasks_completed_by_me
|
||||
if with_tasks_completed_by_my_group is not None:
|
||||
report_filter.with_tasks_completed_by_my_group = (
|
||||
with_tasks_completed_by_my_group
|
||||
)
|
||||
|
||||
return report_filter
|
||||
|
|
|
@ -12,6 +12,7 @@ from spiffworkflow_backend.models.active_task import ActiveTaskModel
|
|||
from spiffworkflow_backend.models.process_instance import ProcessInstanceApi
|
||||
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
|
||||
from spiffworkflow_backend.models.process_instance import ProcessInstanceStatus
|
||||
from spiffworkflow_backend.models.process_model import ProcessModelInfo
|
||||
from spiffworkflow_backend.models.task import MultiInstanceType
|
||||
from spiffworkflow_backend.models.task import Task
|
||||
from spiffworkflow_backend.models.user import UserModel
|
||||
|
@ -28,9 +29,10 @@ class ProcessInstanceService:
|
|||
|
||||
TASK_STATE_LOCKED = "locked"
|
||||
|
||||
@staticmethod
|
||||
@classmethod
|
||||
def create_process_instance(
|
||||
process_model_identifier: str,
|
||||
cls,
|
||||
process_model: ProcessModelInfo,
|
||||
user: UserModel,
|
||||
) -> ProcessInstanceModel:
|
||||
"""Get_process_instance_from_spec."""
|
||||
|
@ -38,8 +40,8 @@ class ProcessInstanceService:
|
|||
process_instance_model = ProcessInstanceModel(
|
||||
status=ProcessInstanceStatus.not_started.value,
|
||||
process_initiator=user,
|
||||
process_model_identifier=process_model_identifier,
|
||||
process_group_identifier="",
|
||||
process_model_identifier=process_model.id,
|
||||
process_model_display_name=process_model.display_name,
|
||||
start_in_seconds=round(time.time()),
|
||||
bpmn_version_control_type="git",
|
||||
bpmn_version_control_identifier=current_git_revision,
|
||||
|
@ -48,6 +50,16 @@ class ProcessInstanceService:
|
|||
db.session.commit()
|
||||
return process_instance_model
|
||||
|
||||
@classmethod
|
||||
def create_process_instance_from_process_model_identifier(
|
||||
cls,
|
||||
process_model_identifier: str,
|
||||
user: UserModel,
|
||||
) -> ProcessInstanceModel:
|
||||
"""Create_process_instance_from_process_model_identifier."""
|
||||
process_model = ProcessModelService.get_process_model(process_model_identifier)
|
||||
return cls.create_process_instance(process_model, user)
|
||||
|
||||
@staticmethod
|
||||
def do_waiting() -> None:
|
||||
"""Do_waiting."""
|
||||
|
@ -88,20 +100,15 @@ class ProcessInstanceService:
|
|||
process_model = process_model_service.get_process_model(
|
||||
processor.process_model_identifier
|
||||
)
|
||||
is_review_value = process_model.is_review if process_model else False
|
||||
title_value = process_model.display_name if process_model else ""
|
||||
process_model.display_name if process_model else ""
|
||||
process_instance_api = ProcessInstanceApi(
|
||||
id=processor.get_process_instance_id(),
|
||||
status=processor.get_status(),
|
||||
next_task=None,
|
||||
# navigation=navigation,
|
||||
process_model_identifier=processor.process_model_identifier,
|
||||
process_group_identifier="",
|
||||
# total_tasks=len(navigation),
|
||||
process_model_display_name=processor.process_model_display_name,
|
||||
completed_tasks=processor.process_instance_model.completed_tasks,
|
||||
updated_at_in_seconds=processor.process_instance_model.updated_at_in_seconds,
|
||||
is_review=is_review_value,
|
||||
title=title_value,
|
||||
)
|
||||
|
||||
next_task_trying_again = next_task
|
||||
|
@ -197,7 +204,7 @@ class ProcessInstanceService:
|
|||
a multi-instance task.
|
||||
"""
|
||||
AuthorizationService.assert_user_can_complete_spiff_task(
|
||||
processor, spiff_task, user
|
||||
processor.process_instance_model.id, spiff_task, user
|
||||
)
|
||||
|
||||
dot_dct = ProcessInstanceService.create_dot_dict(data)
|
||||
|
@ -320,12 +327,13 @@ class ProcessInstanceService:
|
|||
def serialize_flat_with_task_data(
|
||||
process_instance: ProcessInstanceModel,
|
||||
) -> dict[str, Any]:
|
||||
"""NOTE: This is crazy slow. Put the latest task data in the database."""
|
||||
"""Serialize_flat_with_task_data."""
|
||||
results = {}
|
||||
try:
|
||||
processor = ProcessInstanceProcessor(process_instance)
|
||||
process_instance.data = processor.get_current_data()
|
||||
results = process_instance.serialized_flat
|
||||
except ApiError:
|
||||
results = process_instance.serialized
|
||||
# results = {}
|
||||
# try:
|
||||
# processor = ProcessInstanceProcessor(process_instance)
|
||||
# process_instance.data = processor.get_current_data()
|
||||
# results = process_instance.serialized_flat
|
||||
# except ApiError:
|
||||
results = process_instance.serialized
|
||||
return results
|
||||
|
|
|
@ -18,7 +18,9 @@ from spiffworkflow_backend.models.process_group import ProcessGroupSchema
|
|||
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
|
||||
from spiffworkflow_backend.models.process_model import ProcessModelInfo
|
||||
from spiffworkflow_backend.models.process_model import ProcessModelInfoSchema
|
||||
from spiffworkflow_backend.services.authorization_service import AuthorizationService
|
||||
from spiffworkflow_backend.services.file_system_service import FileSystemService
|
||||
from spiffworkflow_backend.services.user_service import UserService
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
@ -35,20 +37,54 @@ class ProcessModelService(FileSystemService):
|
|||
GROUP_SCHEMA = ProcessGroupSchema()
|
||||
PROCESS_MODEL_SCHEMA = ProcessModelInfoSchema()
|
||||
|
||||
def is_group(self, path: str) -> bool:
|
||||
@classmethod
|
||||
def is_group(cls, path: str) -> bool:
|
||||
"""Is_group."""
|
||||
group_json_path = os.path.join(path, self.PROCESS_GROUP_JSON_FILE)
|
||||
group_json_path = os.path.join(path, cls.PROCESS_GROUP_JSON_FILE)
|
||||
if os.path.exists(group_json_path):
|
||||
return True
|
||||
return False
|
||||
|
||||
def is_model(self, path: str) -> bool:
|
||||
@classmethod
|
||||
def is_group_identifier(cls, process_group_identifier: str) -> bool:
|
||||
"""Is_group_identifier."""
|
||||
if os.path.exists(FileSystemService.root_path()):
|
||||
process_group_path = os.path.abspath(
|
||||
os.path.join(
|
||||
FileSystemService.root_path(),
|
||||
FileSystemService.id_string_to_relative_path(
|
||||
process_group_identifier
|
||||
),
|
||||
)
|
||||
)
|
||||
return cls.is_group(process_group_path)
|
||||
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def is_model(cls, path: str) -> bool:
|
||||
"""Is_model."""
|
||||
model_json_path = os.path.join(path, self.PROCESS_MODEL_JSON_FILE)
|
||||
model_json_path = os.path.join(path, cls.PROCESS_MODEL_JSON_FILE)
|
||||
if os.path.exists(model_json_path):
|
||||
return True
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def is_model_identifier(cls, process_model_identifier: str) -> bool:
|
||||
"""Is_model_identifier."""
|
||||
if os.path.exists(FileSystemService.root_path()):
|
||||
process_model_path = os.path.abspath(
|
||||
os.path.join(
|
||||
FileSystemService.root_path(),
|
||||
FileSystemService.id_string_to_relative_path(
|
||||
process_model_identifier
|
||||
),
|
||||
)
|
||||
)
|
||||
return cls.is_model(process_model_path)
|
||||
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def write_json_file(
|
||||
file_path: str, json_data: dict, indent: int = 4, sort_keys: bool = True
|
||||
|
@ -68,37 +104,38 @@ class ProcessModelService(FileSystemService):
|
|||
end = start + per_page
|
||||
return items[start:end]
|
||||
|
||||
def add_process_model(self, process_model: ProcessModelInfo) -> None:
|
||||
@classmethod
|
||||
def add_process_model(cls, process_model: ProcessModelInfo) -> None:
|
||||
"""Add_spec."""
|
||||
display_order = self.next_display_order(process_model)
|
||||
process_model.display_order = display_order
|
||||
self.save_process_model(process_model)
|
||||
cls.save_process_model(process_model)
|
||||
|
||||
@classmethod
|
||||
def update_process_model(
|
||||
self, process_model: ProcessModelInfo, attributes_to_update: dict
|
||||
cls, process_model: ProcessModelInfo, attributes_to_update: dict
|
||||
) -> None:
|
||||
"""Update_spec."""
|
||||
for atu_key, atu_value in attributes_to_update.items():
|
||||
if hasattr(process_model, atu_key):
|
||||
setattr(process_model, atu_key, atu_value)
|
||||
self.save_process_model(process_model)
|
||||
cls.save_process_model(process_model)
|
||||
|
||||
def save_process_model(self, process_model: ProcessModelInfo) -> None:
|
||||
@classmethod
|
||||
def save_process_model(cls, process_model: ProcessModelInfo) -> None:
|
||||
"""Save_process_model."""
|
||||
process_model_path = os.path.abspath(
|
||||
os.path.join(FileSystemService.root_path(), process_model.id)
|
||||
)
|
||||
os.makedirs(process_model_path, exist_ok=True)
|
||||
json_path = os.path.abspath(
|
||||
os.path.join(process_model_path, self.PROCESS_MODEL_JSON_FILE)
|
||||
os.path.join(process_model_path, cls.PROCESS_MODEL_JSON_FILE)
|
||||
)
|
||||
process_model_id = process_model.id
|
||||
# we don't save id in the json file
|
||||
# this allows us to move models around on the filesystem
|
||||
# the id is determined by its location on the filesystem
|
||||
delattr(process_model, "id")
|
||||
json_data = self.PROCESS_MODEL_SCHEMA.dump(process_model)
|
||||
self.write_json_file(json_path, json_data)
|
||||
json_data = cls.PROCESS_MODEL_SCHEMA.dump(process_model)
|
||||
cls.write_json_file(json_path, json_data)
|
||||
process_model.id = process_model_id
|
||||
|
||||
def process_model_delete(self, process_model_id: str) -> None:
|
||||
|
@ -119,7 +156,7 @@ class ProcessModelService(FileSystemService):
|
|||
def process_model_move(
|
||||
self, original_process_model_id: str, new_location: str
|
||||
) -> ProcessModelInfo:
|
||||
"""process_model_move."""
|
||||
"""Process_model_move."""
|
||||
original_model_path = os.path.abspath(
|
||||
os.path.join(FileSystemService.root_path(), original_process_model_id)
|
||||
)
|
||||
|
@ -138,11 +175,11 @@ class ProcessModelService(FileSystemService):
|
|||
) -> ProcessModelInfo:
|
||||
"""Get_process_model_from_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_process_model(path, process_group=process_group)
|
||||
return cls.__scan_process_model(path)
|
||||
|
||||
def get_process_model(self, process_model_id: str) -> ProcessModelInfo:
|
||||
@classmethod
|
||||
def get_process_model(cls, process_model_id: str) -> ProcessModelInfo:
|
||||
"""Get a process model from a model and group id.
|
||||
|
||||
process_model_id is the full path to the model--including groups.
|
||||
|
@ -153,33 +190,16 @@ class ProcessModelService(FileSystemService):
|
|||
model_path = os.path.abspath(
|
||||
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_process_model(sd.path, sd.name, process_group)
|
||||
if cls.is_model(model_path):
|
||||
return cls.get_process_model_from_relative_path(process_model_id)
|
||||
raise ProcessEntityNotFoundError("process_model_not_found")
|
||||
|
||||
@classmethod
|
||||
def get_process_models(
|
||||
self, process_group_id: Optional[str] = None, recursive: Optional[bool] = False
|
||||
cls,
|
||||
process_group_id: Optional[str] = None,
|
||||
recursive: Optional[bool] = False,
|
||||
filter_runnable_by_user: Optional[bool] = False,
|
||||
) -> List[ProcessModelInfo]:
|
||||
"""Get process models."""
|
||||
process_models = []
|
||||
|
@ -196,22 +216,56 @@ class ProcessModelService(FileSystemService):
|
|||
process_model_relative_path = os.path.relpath(
|
||||
file, start=FileSystemService.root_path()
|
||||
)
|
||||
process_model = self.get_process_model_from_relative_path(
|
||||
process_model = cls.get_process_model_from_relative_path(
|
||||
os.path.dirname(process_model_relative_path)
|
||||
)
|
||||
process_models.append(process_model)
|
||||
process_models.sort()
|
||||
|
||||
if filter_runnable_by_user:
|
||||
user = UserService.current_user()
|
||||
new_process_model_list = []
|
||||
for process_model in process_models:
|
||||
uri = f"/v1.0/process-models/{process_model.id.replace('/', ':')}/process-instances"
|
||||
result = AuthorizationService.user_has_permission(
|
||||
user=user, permission="create", target_uri=uri
|
||||
)
|
||||
if result:
|
||||
new_process_model_list.append(process_model)
|
||||
return new_process_model_list
|
||||
|
||||
return process_models
|
||||
|
||||
@classmethod
|
||||
def get_parent_group_array(cls, process_identifier: str) -> list[dict]:
|
||||
"""Get_parent_group_array."""
|
||||
full_group_id_path = None
|
||||
parent_group_array = []
|
||||
for process_group_id_segment in process_identifier.split("/")[0:-1]:
|
||||
if full_group_id_path is None:
|
||||
full_group_id_path = process_group_id_segment
|
||||
else:
|
||||
full_group_id_path = f"{full_group_id_path}/{process_group_id_segment}" # type: ignore
|
||||
parent_group = ProcessModelService.get_process_group(full_group_id_path)
|
||||
if parent_group:
|
||||
parent_group_array.append(
|
||||
{"id": parent_group.id, "display_name": parent_group.display_name}
|
||||
)
|
||||
return parent_group_array
|
||||
|
||||
@classmethod
|
||||
def get_process_groups(
|
||||
self, process_group_id: Optional[str] = None
|
||||
cls, process_group_id: Optional[str] = None
|
||||
) -> list[ProcessGroup]:
|
||||
"""Returns the process_groups as a list in display order."""
|
||||
process_groups = self.__scan_process_groups(process_group_id)
|
||||
"""Returns the process_groups."""
|
||||
process_groups = cls.__scan_process_groups(process_group_id)
|
||||
process_groups.sort()
|
||||
return process_groups
|
||||
|
||||
def get_process_group(self, process_group_id: str) -> ProcessGroup:
|
||||
@classmethod
|
||||
def get_process_group(
|
||||
cls, process_group_id: str, find_direct_nested_items: bool = True
|
||||
) -> ProcessGroup:
|
||||
"""Look for a given process_group, and return it."""
|
||||
if os.path.exists(FileSystemService.root_path()):
|
||||
process_group_path = os.path.abspath(
|
||||
|
@ -220,48 +274,38 @@ class ProcessModelService(FileSystemService):
|
|||
FileSystemService.id_string_to_relative_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)
|
||||
if cls.is_group(process_group_path):
|
||||
return cls.find_or_create_process_group(
|
||||
process_group_path,
|
||||
find_direct_nested_items=find_direct_nested_items,
|
||||
)
|
||||
|
||||
raise ProcessEntityNotFoundError(
|
||||
"process_group_not_found", f"Process Group Id: {process_group_id}"
|
||||
)
|
||||
|
||||
def add_process_group(self, process_group: ProcessGroup) -> ProcessGroup:
|
||||
@classmethod
|
||||
def add_process_group(cls, process_group: ProcessGroup) -> ProcessGroup:
|
||||
"""Add_process_group."""
|
||||
display_order = len(self.get_process_groups())
|
||||
process_group.display_order = display_order
|
||||
return self.update_process_group(process_group)
|
||||
return cls.update_process_group(process_group)
|
||||
|
||||
def update_process_group(self, process_group: ProcessGroup) -> ProcessGroup:
|
||||
@classmethod
|
||||
def update_process_group(cls, process_group: ProcessGroup) -> ProcessGroup:
|
||||
"""Update_process_group."""
|
||||
cat_path = self.process_group_path(process_group.id)
|
||||
cat_path = cls.process_group_path(process_group.id)
|
||||
os.makedirs(cat_path, exist_ok=True)
|
||||
json_path = os.path.join(cat_path, self.PROCESS_GROUP_JSON_FILE)
|
||||
json_path = os.path.join(cat_path, cls.PROCESS_GROUP_JSON_FILE)
|
||||
serialized_process_group = process_group.serialized
|
||||
# we don't store `id` in the json files
|
||||
# this allows us to move groups around on the filesystem
|
||||
del serialized_process_group["id"]
|
||||
self.write_json_file(json_path, serialized_process_group)
|
||||
cls.write_json_file(json_path, serialized_process_group)
|
||||
return process_group
|
||||
|
||||
def process_group_move(
|
||||
self, original_process_group_id: str, new_location: str
|
||||
) -> ProcessGroup:
|
||||
"""process_group_move."""
|
||||
"""Process_group_move."""
|
||||
original_group_path = self.process_group_path(original_process_group_id)
|
||||
original_root, original_group_id = os.path.split(original_group_path)
|
||||
new_root = f"{FileSystemService.root_path()}/{new_location}"
|
||||
|
@ -278,7 +322,7 @@ class ProcessModelService(FileSystemService):
|
|||
for _root, dirs, _files in os.walk(group_path):
|
||||
for dir in dirs:
|
||||
model_dir = os.path.join(group_path, dir)
|
||||
if ProcessModelService().is_model(model_dir):
|
||||
if ProcessModelService.is_model(model_dir):
|
||||
process_model = self.get_process_model(model_dir)
|
||||
all_nested_models.append(process_model)
|
||||
return all_nested_models
|
||||
|
@ -314,8 +358,9 @@ class ProcessModelService(FileSystemService):
|
|||
index += 1
|
||||
return process_groups
|
||||
|
||||
@classmethod
|
||||
def __scan_process_groups(
|
||||
self, process_group_id: Optional[str] = None
|
||||
cls, process_group_id: Optional[str] = None
|
||||
) -> list[ProcessGroup]:
|
||||
"""__scan_process_groups."""
|
||||
if not os.path.exists(FileSystemService.root_path()):
|
||||
|
@ -329,14 +374,17 @@ class ProcessModelService(FileSystemService):
|
|||
process_groups = []
|
||||
for item in directory_items:
|
||||
# if item.is_dir() and not item.name[0] == ".":
|
||||
if item.is_dir() and self.is_group(item): # type: ignore
|
||||
scanned_process_group = self.__scan_process_group(item.path)
|
||||
if item.is_dir() and cls.is_group(item): # type: ignore
|
||||
scanned_process_group = cls.find_or_create_process_group(item.path)
|
||||
process_groups.append(scanned_process_group)
|
||||
return process_groups
|
||||
|
||||
def __scan_process_group(self, dir_path: str) -> ProcessGroup:
|
||||
@classmethod
|
||||
def find_or_create_process_group(
|
||||
cls, dir_path: str, find_direct_nested_items: bool = True
|
||||
) -> ProcessGroup:
|
||||
"""Reads the process_group.json file, and any nested directories."""
|
||||
cat_path = os.path.join(dir_path, self.PROCESS_GROUP_JSON_FILE)
|
||||
cat_path = os.path.join(dir_path, cls.PROCESS_GROUP_JSON_FILE)
|
||||
if os.path.exists(cat_path):
|
||||
with open(cat_path) as cat_json:
|
||||
data = json.load(cat_json)
|
||||
|
@ -357,40 +405,41 @@ class ProcessModelService(FileSystemService):
|
|||
display_order=10000,
|
||||
admin=False,
|
||||
)
|
||||
self.write_json_file(cat_path, self.GROUP_SCHEMA.dump(process_group))
|
||||
cls.write_json_file(cat_path, cls.GROUP_SCHEMA.dump(process_group))
|
||||
# we don't store `id` in the json files, so we add it in here
|
||||
process_group.id = process_group_id
|
||||
with os.scandir(dir_path) as nested_items:
|
||||
process_group.process_models = []
|
||||
process_group.process_groups = []
|
||||
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
|
||||
process_group.process_groups.append(
|
||||
self.__scan_process_group(nested_item.path)
|
||||
)
|
||||
elif self.is_model(nested_item.path):
|
||||
process_group.process_models.append(
|
||||
self.__scan_process_model(
|
||||
nested_item.path,
|
||||
nested_item.name,
|
||||
process_group=process_group,
|
||||
|
||||
if find_direct_nested_items:
|
||||
with os.scandir(dir_path) as nested_items:
|
||||
process_group.process_models = []
|
||||
process_group.process_groups = []
|
||||
for nested_item in nested_items:
|
||||
if nested_item.is_dir():
|
||||
# TODO: check whether this is a group or model
|
||||
if cls.is_group(nested_item.path):
|
||||
# This is a nested group
|
||||
process_group.process_groups.append(
|
||||
cls.find_or_create_process_group(nested_item.path)
|
||||
)
|
||||
)
|
||||
process_group.process_models.sort()
|
||||
# process_group.process_groups.sort()
|
||||
elif ProcessModelService.is_model(nested_item.path):
|
||||
process_group.process_models.append(
|
||||
cls.__scan_process_model(
|
||||
nested_item.path,
|
||||
nested_item.name,
|
||||
)
|
||||
)
|
||||
process_group.process_models.sort()
|
||||
# process_group.process_groups.sort()
|
||||
return process_group
|
||||
|
||||
@classmethod
|
||||
def __scan_process_model(
|
||||
self,
|
||||
cls,
|
||||
path: str,
|
||||
name: Optional[str] = None,
|
||||
process_group: Optional[ProcessGroup] = None,
|
||||
) -> ProcessModelInfo:
|
||||
"""__scan_process_model."""
|
||||
json_file_path = os.path.join(path, self.PROCESS_MODEL_JSON_FILE)
|
||||
json_file_path = os.path.join(path, cls.PROCESS_MODEL_JSON_FILE)
|
||||
|
||||
if os.path.exists(json_file_path):
|
||||
with open(json_file_path) as wf_json:
|
||||
|
@ -418,13 +467,10 @@ class ProcessModelService(FileSystemService):
|
|||
display_name=name,
|
||||
description="",
|
||||
display_order=0,
|
||||
is_review=False,
|
||||
)
|
||||
self.write_json_file(
|
||||
json_file_path, self.PROCESS_MODEL_SCHEMA.dump(process_model_info)
|
||||
cls.write_json_file(
|
||||
json_file_path, cls.PROCESS_MODEL_SCHEMA.dump(process_model_info)
|
||||
)
|
||||
# we don't store `id` in the json files, so we add it in here
|
||||
process_model_info.id = name
|
||||
if process_group:
|
||||
process_model_info.process_group = process_group.id
|
||||
return process_model_info
|
||||
|
|
|
@ -65,7 +65,7 @@ class SecretService:
|
|||
def update_secret(
|
||||
key: str,
|
||||
value: str,
|
||||
user_id: int,
|
||||
user_id: Optional[int] = None,
|
||||
create_if_not_exists: Optional[bool] = False,
|
||||
) -> None:
|
||||
"""Does this pass pre commit?"""
|
||||
|
@ -79,6 +79,12 @@ class SecretService:
|
|||
db.session.rollback()
|
||||
raise e
|
||||
elif create_if_not_exists:
|
||||
if user_id is None:
|
||||
raise ApiError(
|
||||
error_code="update_secret_error_no_user_id",
|
||||
message=f"Cannot update secret with key: {key}. Missing user id.",
|
||||
status_code=404,
|
||||
)
|
||||
SecretService.add_secret(key=key, value=value, user_id=user_id)
|
||||
else:
|
||||
raise ApiError(
|
||||
|
|
|
@ -8,6 +8,7 @@ from flask import g
|
|||
|
||||
from spiffworkflow_backend.services.file_system_service import FileSystemService
|
||||
from spiffworkflow_backend.services.secret_service import SecretService
|
||||
from spiffworkflow_backend.services.user_service import UserService
|
||||
|
||||
|
||||
class ConnectorProxyError(Exception):
|
||||
|
@ -65,7 +66,8 @@ class ServiceTaskDelegate:
|
|||
|
||||
secret_key = parsed_response["auth"]
|
||||
refreshed_token_set = json.dumps(parsed_response["refreshed_token_set"])
|
||||
SecretService().update_secret(secret_key, refreshed_token_set, g.user.id)
|
||||
user_id = g.user.id if UserService.has_user() else None
|
||||
SecretService().update_secret(secret_key, refreshed_token_set, user_id)
|
||||
|
||||
return json.dumps(parsed_response["api_response"])
|
||||
|
||||
|
|
|
@ -171,12 +171,11 @@ class SpecFileService(FileSystemService):
|
|||
ref.is_primary = True
|
||||
|
||||
if ref.is_primary:
|
||||
ProcessModelService().update_process_model(
|
||||
ProcessModelService.update_process_model(
|
||||
process_model_info,
|
||||
{
|
||||
"primary_process_id": ref.identifier,
|
||||
"primary_file_name": file_name,
|
||||
"is_review": ref.has_lanes,
|
||||
},
|
||||
)
|
||||
SpecFileService.update_caches(ref)
|
||||
|
@ -322,7 +321,6 @@ class SpecFileService(FileSystemService):
|
|||
message_triggerable_process_model = MessageTriggerableProcessModel(
|
||||
message_model_id=message_model.id,
|
||||
process_model_identifier=ref.process_model_id,
|
||||
process_group_identifier="process_group_identifier",
|
||||
)
|
||||
db.session.add(message_triggerable_process_model)
|
||||
db.session.commit()
|
||||
|
@ -330,8 +328,6 @@ class SpecFileService(FileSystemService):
|
|||
if (
|
||||
message_triggerable_process_model.process_model_identifier
|
||||
!= ref.process_model_id
|
||||
# or message_triggerable_process_model.process_group_identifier
|
||||
# != process_model_info.process_group_id
|
||||
):
|
||||
raise ValidationException(
|
||||
f"Message model is already used to start process model {ref.process_model_id}"
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
<?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_96f6665" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.0.0-dev">
|
||||
<bpmn:process id="test_save_process_instance_metadata" isExecutable="true">
|
||||
<bpmn:startEvent id="Event_0r6oru6">
|
||||
<bpmn:outgoing>Flow_1j4jzft</bpmn:outgoing>
|
||||
</bpmn:startEvent>
|
||||
<bpmn:sequenceFlow id="Flow_1j4jzft" sourceRef="Event_0r6oru6" targetRef="save_key1" />
|
||||
<bpmn:endEvent id="Event_1s123jg">
|
||||
<bpmn:incoming>Flow_01xr2ac</bpmn:incoming>
|
||||
</bpmn:endEvent>
|
||||
<bpmn:scriptTask id="save_key1">
|
||||
<bpmn:incoming>Flow_1j4jzft</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_10xyk22</bpmn:outgoing>
|
||||
<bpmn:script>save_process_instance_metadata({"key1": "value1"})</bpmn:script>
|
||||
</bpmn:scriptTask>
|
||||
<bpmn:sequenceFlow id="Flow_10xyk22" sourceRef="save_key1" targetRef="save_key2" />
|
||||
<bpmn:scriptTask id="save_key2">
|
||||
<bpmn:incoming>Flow_10xyk22</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_01xr2ac</bpmn:outgoing>
|
||||
<bpmn:script>save_process_instance_metadata({"key2": "value2", "key3": "value3"})</bpmn:script>
|
||||
</bpmn:scriptTask>
|
||||
<bpmn:sequenceFlow id="Flow_01xr2ac" sourceRef="save_key2" targetRef="Event_1s123jg" />
|
||||
</bpmn:process>
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="test_save_process_instance_metadata">
|
||||
<bpmndi:BPMNShape id="Event_0r6oru6_di" bpmnElement="Event_0r6oru6">
|
||||
<dc:Bounds x="162" y="162" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_0zfzev2_di" bpmnElement="save_key1">
|
||||
<dc:Bounds x="250" y="140" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_0d1q8x4_di" bpmnElement="save_key2">
|
||||
<dc:Bounds x="410" y="140" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Event_1s123jg_di" bpmnElement="Event_1s123jg">
|
||||
<dc:Bounds x="582" y="162" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge id="Flow_1j4jzft_di" bpmnElement="Flow_1j4jzft">
|
||||
<di:waypoint x="198" y="180" />
|
||||
<di:waypoint x="250" y="180" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_10xyk22_di" bpmnElement="Flow_10xyk22">
|
||||
<di:waypoint x="350" y="180" />
|
||||
<di:waypoint x="410" y="180" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_01xr2ac_di" bpmnElement="Flow_01xr2ac">
|
||||
<di:waypoint x="510" y="180" />
|
||||
<di:waypoint x="582" y="180" />
|
||||
</bpmndi:BPMNEdge>
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
</bpmn:definitions>
|
|
@ -140,7 +140,7 @@ class BaseTest:
|
|||
process_group_path = os.path.abspath(
|
||||
os.path.join(FileSystemService.root_path(), process_group_id)
|
||||
)
|
||||
if ProcessModelService().is_group(process_group_path):
|
||||
if ProcessModelService.is_group(process_group_path):
|
||||
|
||||
if exception_notification_addresses is None:
|
||||
exception_notification_addresses = []
|
||||
|
@ -149,7 +149,6 @@ class BaseTest:
|
|||
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,
|
||||
|
@ -253,6 +252,17 @@ class BaseTest:
|
|||
|
||||
There must be an existing process model to instantiate.
|
||||
"""
|
||||
if not ProcessModelService.is_model_identifier(test_process_model_id):
|
||||
dirname = os.path.dirname(test_process_model_id)
|
||||
if not ProcessModelService.is_group_identifier(dirname):
|
||||
process_group = ProcessGroup(id=dirname, display_name=dirname)
|
||||
ProcessModelService.add_process_group(process_group)
|
||||
basename = os.path.basename(test_process_model_id)
|
||||
load_test_spec(
|
||||
process_model_id=test_process_model_id,
|
||||
process_model_source_directory=basename,
|
||||
bpmn_file_name=basename,
|
||||
)
|
||||
modified_process_model_id = test_process_model_id.replace("/", ":")
|
||||
response = client.post(
|
||||
f"/v1.0/process-models/{modified_process_model_id}/process-instances",
|
||||
|
@ -284,7 +294,7 @@ class BaseTest:
|
|||
status=status,
|
||||
process_initiator=user,
|
||||
process_model_identifier=process_model.id,
|
||||
process_group_identifier="",
|
||||
process_model_display_name=process_model.display_name,
|
||||
updated_at_in_seconds=round(time.time()),
|
||||
start_in_seconds=current_time - (3600 * 1),
|
||||
end_in_seconds=current_time - (3600 * 1 - 20),
|
||||
|
@ -347,3 +357,16 @@ class BaseTest:
|
|||
target_uri=target_uri,
|
||||
)
|
||||
assert has_permission is expected_result
|
||||
|
||||
def modify_process_identifier_for_path_param(self, identifier: str) -> str:
|
||||
"""Identifier."""
|
||||
if "\\" in identifier:
|
||||
raise Exception(f"Found backslash in identifier: {identifier}")
|
||||
|
||||
return identifier.replace("/", ":")
|
||||
|
||||
def un_modify_modified_process_identifier_for_path_param(
|
||||
self, modified_identifier: str
|
||||
) -> str:
|
||||
"""Un_modify_modified_process_model_id."""
|
||||
return modified_identifier.replace(":", "/")
|
||||
|
|
|
@ -36,10 +36,8 @@ class ExampleDataLoader:
|
|||
display_name=display_name,
|
||||
description=description,
|
||||
display_order=display_order,
|
||||
is_review=False,
|
||||
)
|
||||
workflow_spec_service = ProcessModelService()
|
||||
workflow_spec_service.add_process_model(spec)
|
||||
ProcessModelService.add_process_model(spec)
|
||||
|
||||
bpmn_file_name_with_extension = bpmn_file_name
|
||||
if not bpmn_file_name_with_extension:
|
||||
|
@ -88,7 +86,7 @@ class ExampleDataLoader:
|
|||
)
|
||||
spec.primary_process_id = references[0].identifier
|
||||
spec.primary_file_name = filename
|
||||
ProcessModelService().save_process_model(spec)
|
||||
ProcessModelService.save_process_model(spec)
|
||||
finally:
|
||||
if file:
|
||||
file.close()
|
||||
|
|
|
@ -51,7 +51,7 @@ class TestLoggingService(BaseTest):
|
|||
assert response.json is not None
|
||||
process_instance_id = response.json["id"]
|
||||
response = client.post(
|
||||
f"/v1.0/process-instances/{process_instance_id}/run",
|
||||
f"/v1.0/process-instances/{self.modify_process_identifier_for_path_param(process_model_identifier)}/{process_instance_id}/run",
|
||||
headers=headers,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
|
|
@ -46,7 +46,7 @@ class TestNestedGroups(BaseTest):
|
|||
process_instance_id = response.json["id"]
|
||||
|
||||
client.post(
|
||||
f"/v1.0/process-instances/{process_instance_id}/run",
|
||||
f"/v1.0/process-instances/{self.modify_process_identifier_for_path_param(process_model_identifier)}/{process_instance_id}/run",
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
process_instance = ProcessInstanceService().get_process_instance(
|
||||
|
|
|
@ -133,12 +133,12 @@ class TestProcessApi(BaseTest):
|
|||
process_model_description=model_description,
|
||||
user=with_super_admin_user,
|
||||
)
|
||||
process_model = ProcessModelService().get_process_model(
|
||||
process_model = ProcessModelService.get_process_model(
|
||||
process_model_identifier,
|
||||
)
|
||||
assert model_display_name == process_model.display_name
|
||||
assert 0 == process_model.display_order
|
||||
assert 1 == len(ProcessModelService().get_process_groups())
|
||||
assert 1 == len(ProcessModelService.get_process_groups())
|
||||
|
||||
# add bpmn file to the model
|
||||
bpmn_file_name = "sample.bpmn"
|
||||
|
@ -155,9 +155,7 @@ class TestProcessApi(BaseTest):
|
|||
user=with_super_admin_user,
|
||||
)
|
||||
# get the model, assert that primary is set
|
||||
process_model = ProcessModelService().get_process_model(
|
||||
process_model_identifier
|
||||
)
|
||||
process_model = ProcessModelService.get_process_model(process_model_identifier)
|
||||
assert process_model.primary_file_name == bpmn_file_name
|
||||
assert process_model.primary_process_id == "sample"
|
||||
|
||||
|
@ -208,9 +206,7 @@ class TestProcessApi(BaseTest):
|
|||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
assert response.status_code == 200
|
||||
process_model = ProcessModelService().get_process_model(
|
||||
process_model_identifier
|
||||
)
|
||||
process_model = ProcessModelService.get_process_model(process_model_identifier)
|
||||
assert process_model.primary_file_name == bpmn_file_name
|
||||
assert process_model.primary_process_id == terminal_primary_process_id
|
||||
|
||||
|
@ -236,9 +232,7 @@ class TestProcessApi(BaseTest):
|
|||
)
|
||||
|
||||
# assert we have a model
|
||||
process_model = ProcessModelService().get_process_model(
|
||||
process_model_identifier
|
||||
)
|
||||
process_model = ProcessModelService.get_process_model(process_model_identifier)
|
||||
assert process_model is not None
|
||||
assert process_model.id == process_model_identifier
|
||||
|
||||
|
@ -254,7 +248,7 @@ class TestProcessApi(BaseTest):
|
|||
|
||||
# assert we no longer have a model
|
||||
with pytest.raises(ProcessEntityNotFoundError):
|
||||
ProcessModelService().get_process_model(process_model_identifier)
|
||||
ProcessModelService.get_process_model(process_model_identifier)
|
||||
|
||||
def test_process_model_delete_with_instances(
|
||||
self,
|
||||
|
@ -327,19 +321,15 @@ class TestProcessApi(BaseTest):
|
|||
process_model_id=process_model_identifier,
|
||||
user=with_super_admin_user,
|
||||
)
|
||||
process_model = ProcessModelService().get_process_model(
|
||||
process_model_identifier
|
||||
)
|
||||
process_model = ProcessModelService.get_process_model(process_model_identifier)
|
||||
assert process_model.id == process_model_identifier
|
||||
assert process_model.display_name == "Cooooookies"
|
||||
assert process_model.is_review is False
|
||||
assert process_model.primary_file_name is None
|
||||
assert process_model.primary_process_id is None
|
||||
|
||||
process_model.display_name = "Updated Display Name"
|
||||
process_model.primary_file_name = "superduper.bpmn"
|
||||
process_model.primary_process_id = "superduper"
|
||||
process_model.is_review = True # not in the include list, so get ignored
|
||||
|
||||
modified_process_model_identifier = process_model_identifier.replace("/", ":")
|
||||
response = client.put(
|
||||
|
@ -353,7 +343,6 @@ class TestProcessApi(BaseTest):
|
|||
assert response.json["display_name"] == "Updated Display Name"
|
||||
assert response.json["primary_file_name"] == "superduper.bpmn"
|
||||
assert response.json["primary_process_id"] == "superduper"
|
||||
assert response.json["is_review"] is False
|
||||
|
||||
def test_process_model_list_all(
|
||||
self,
|
||||
|
@ -550,7 +539,7 @@ class TestProcessApi(BaseTest):
|
|||
assert result.description == "Test Description"
|
||||
|
||||
# Check what is persisted
|
||||
persisted = ProcessModelService().get_process_group("test")
|
||||
persisted = ProcessModelService.get_process_group("test")
|
||||
assert persisted.display_name == "Another Test Category"
|
||||
assert persisted.id == "test"
|
||||
assert persisted.description == "Test Description"
|
||||
|
@ -572,7 +561,7 @@ class TestProcessApi(BaseTest):
|
|||
process_group_id,
|
||||
display_name=process_group_display_name,
|
||||
)
|
||||
persisted = ProcessModelService().get_process_group(process_group_id)
|
||||
persisted = ProcessModelService.get_process_group(process_group_id)
|
||||
assert persisted is not None
|
||||
assert persisted.id == process_group_id
|
||||
|
||||
|
@ -582,7 +571,7 @@ class TestProcessApi(BaseTest):
|
|||
)
|
||||
|
||||
with pytest.raises(ProcessEntityNotFoundError):
|
||||
ProcessModelService().get_process_group(process_group_id)
|
||||
ProcessModelService.get_process_group(process_group_id)
|
||||
|
||||
def test_process_group_update(
|
||||
self,
|
||||
|
@ -598,7 +587,7 @@ class TestProcessApi(BaseTest):
|
|||
self.create_process_group(
|
||||
client, with_super_admin_user, group_id, display_name=group_display_name
|
||||
)
|
||||
process_group = ProcessModelService().get_process_group(group_id)
|
||||
process_group = ProcessModelService.get_process_group(group_id)
|
||||
|
||||
assert process_group.display_name == group_display_name
|
||||
|
||||
|
@ -612,7 +601,7 @@ class TestProcessApi(BaseTest):
|
|||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
process_group = ProcessModelService().get_process_group(group_id)
|
||||
process_group = ProcessModelService.get_process_group(group_id)
|
||||
assert process_group.display_name == "Modified Display Name"
|
||||
|
||||
def test_process_group_list(
|
||||
|
@ -979,6 +968,43 @@ class TestProcessApi(BaseTest):
|
|||
assert response.json is not None
|
||||
assert response.json["id"] == process_group_id
|
||||
assert response.json["process_models"][0]["id"] == process_model_identifier
|
||||
assert response.json["parent_groups"] == []
|
||||
|
||||
def test_get_process_group_show_when_nested(
|
||||
self,
|
||||
app: Flask,
|
||||
client: FlaskClient,
|
||||
with_db_and_bpmn_file_cleanup: None,
|
||||
with_super_admin_user: UserModel,
|
||||
) -> None:
|
||||
"""Test_get_process_group_show_when_nested."""
|
||||
self.create_group_and_model_with_bpmn(
|
||||
client=client,
|
||||
user=with_super_admin_user,
|
||||
process_group_id="test_group_one",
|
||||
process_model_id="simple_form",
|
||||
bpmn_file_location="simple_form",
|
||||
)
|
||||
|
||||
self.create_group_and_model_with_bpmn(
|
||||
client=client,
|
||||
user=with_super_admin_user,
|
||||
process_group_id="test_group_one/test_group_two",
|
||||
process_model_id="call_activity_nested",
|
||||
bpmn_file_location="call_activity_nested",
|
||||
)
|
||||
|
||||
response = client.get(
|
||||
"/v1.0/process-groups/test_group_one:test_group_two",
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json is not None
|
||||
assert response.json["id"] == "test_group_one/test_group_two"
|
||||
assert response.json["parent_groups"] == [
|
||||
{"display_name": "test_group_one", "id": "test_group_one"}
|
||||
]
|
||||
|
||||
def test_get_process_model_when_found(
|
||||
self,
|
||||
|
@ -997,11 +1023,15 @@ class TestProcessApi(BaseTest):
|
|||
f"/v1.0/process-models/{modified_process_model_identifier}",
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json is not None
|
||||
assert response.json["id"] == process_model_identifier
|
||||
assert len(response.json["files"]) == 1
|
||||
assert response.json["files"][0]["name"] == "random_fact.bpmn"
|
||||
assert response.json["parent_groups"] == [
|
||||
{"display_name": "test_group", "id": "test_group"}
|
||||
]
|
||||
|
||||
def test_get_process_model_when_not_found(
|
||||
self,
|
||||
|
@ -1069,7 +1099,7 @@ class TestProcessApi(BaseTest):
|
|||
assert response.json is not None
|
||||
process_instance_id = response.json["id"]
|
||||
response = client.post(
|
||||
f"/v1.0/process-instances/{process_instance_id}/run",
|
||||
f"/v1.0/process-instances/{self.modify_process_identifier_for_path_param(process_model_identifier)}/{process_instance_id}/run",
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
|
||||
|
@ -1101,7 +1131,9 @@ class TestProcessApi(BaseTest):
|
|||
process_group_id=process_group_id,
|
||||
process_model_id=process_model_id,
|
||||
)
|
||||
modified_process_model_identifier = process_model_identifier.replace("/", ":")
|
||||
modified_process_model_identifier = (
|
||||
self.modify_process_identifier_for_path_param(process_model_identifier)
|
||||
)
|
||||
headers = self.logged_in_headers(with_super_admin_user)
|
||||
create_response = self.create_process_instance_from_process_model_id(
|
||||
client, process_model_identifier, headers
|
||||
|
@ -1109,7 +1141,7 @@ class TestProcessApi(BaseTest):
|
|||
assert create_response.json is not None
|
||||
process_instance_id = create_response.json["id"]
|
||||
client.post(
|
||||
f"/v1.0/process-instances/{process_instance_id}/run",
|
||||
f"/v1.0/process-instances/{modified_process_model_identifier}/{process_instance_id}/run",
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
show_response = client.get(
|
||||
|
@ -1212,7 +1244,7 @@ class TestProcessApi(BaseTest):
|
|||
process_instance_id = response.json["id"]
|
||||
|
||||
response = client.post(
|
||||
f"/v1.0/process-instances/{process_instance_id}/run",
|
||||
f"/v1.0/process-instances/{self.modify_process_identifier_for_path_param(process_model_identifier)}/{process_instance_id}/run",
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
|
||||
|
@ -1272,7 +1304,7 @@ class TestProcessApi(BaseTest):
|
|||
process_instance_id = response.json["id"]
|
||||
|
||||
response = client.post(
|
||||
f"/v1.0/process-instances/{process_instance_id}/run",
|
||||
f"/v1.0/process-instances/{self.modify_process_identifier_for_path_param(process_model_identifier)}/{process_instance_id}/run",
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
@ -1320,7 +1352,7 @@ class TestProcessApi(BaseTest):
|
|||
process_instance_id = response.json["id"]
|
||||
|
||||
response = client.post(
|
||||
f"/v1.0/process-instances/{process_instance_id}/run",
|
||||
f"/v1.0/process-instances/{self.modify_process_identifier_for_path_param(process_model_identifier)}/{process_instance_id}/run",
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
assert response.json is not None
|
||||
|
@ -1359,7 +1391,7 @@ class TestProcessApi(BaseTest):
|
|||
process_instance_id = response.json["id"]
|
||||
|
||||
response = client.post(
|
||||
f"/v1.0/process-instances/{process_instance_id}/run",
|
||||
f"/v1.0/process-instances/{self.modify_process_identifier_for_path_param(process_model_identifier)}/{process_instance_id}/run",
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
|
||||
|
@ -1516,7 +1548,7 @@ class TestProcessApi(BaseTest):
|
|||
status=ProcessInstanceStatus[statuses[i]].value,
|
||||
process_initiator=with_super_admin_user,
|
||||
process_model_identifier=process_model_identifier,
|
||||
process_group_identifier="test_process_group_id",
|
||||
process_model_display_name=process_model_identifier,
|
||||
updated_at_in_seconds=round(time.time()),
|
||||
start_in_seconds=(1000 * i) + 1000,
|
||||
end_in_seconds=(1000 * i) + 2000,
|
||||
|
@ -1818,7 +1850,7 @@ class TestProcessApi(BaseTest):
|
|||
assert process.status == "not_started"
|
||||
|
||||
response = client.post(
|
||||
f"/v1.0/process-instances/{process_instance_id}/run",
|
||||
f"/v1.0/process-instances/{self.modify_process_identifier_for_path_param(process_model_identifier)}/{process_instance_id}/run",
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
assert response.status_code == 400
|
||||
|
@ -1862,10 +1894,8 @@ class TestProcessApi(BaseTest):
|
|||
process_instance_id = self.setup_testing_instance(
|
||||
client, process_model_identifier, with_super_admin_user
|
||||
)
|
||||
process_model = ProcessModelService().get_process_model(
|
||||
process_model_identifier
|
||||
)
|
||||
ProcessModelService().update_process_model(
|
||||
process_model = ProcessModelService.get_process_model(process_model_identifier)
|
||||
ProcessModelService.update_process_model(
|
||||
process_model,
|
||||
{"fault_or_suspend_on_exception": NotificationType.suspend.value},
|
||||
)
|
||||
|
@ -1879,7 +1909,7 @@ class TestProcessApi(BaseTest):
|
|||
assert process.status == "not_started"
|
||||
|
||||
response = client.post(
|
||||
f"/v1.0/process-instances/{process_instance_id}/run",
|
||||
f"/v1.0/process-instances/{self.modify_process_identifier_for_path_param(process_model_identifier)}/{process_instance_id}/run",
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
assert response.status_code == 400
|
||||
|
@ -1917,10 +1947,8 @@ class TestProcessApi(BaseTest):
|
|||
client, process_model_identifier, with_super_admin_user
|
||||
)
|
||||
|
||||
process_model = ProcessModelService().get_process_model(
|
||||
process_model_identifier
|
||||
)
|
||||
ProcessModelService().update_process_model(
|
||||
process_model = ProcessModelService.get_process_model(process_model_identifier)
|
||||
ProcessModelService.update_process_model(
|
||||
process_model,
|
||||
{"exception_notification_addresses": ["with_super_admin_user@example.com"]},
|
||||
)
|
||||
|
@ -1929,7 +1957,7 @@ class TestProcessApi(BaseTest):
|
|||
with mail.record_messages() as outbox:
|
||||
|
||||
response = client.post(
|
||||
f"/v1.0/process-instances/{process_instance_id}/run",
|
||||
f"/v1.0/process-instances/{self.modify_process_identifier_for_path_param(process_model_identifier)}/{process_instance_id}/run",
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
assert response.status_code == 400
|
||||
|
@ -2114,7 +2142,7 @@ class TestProcessApi(BaseTest):
|
|||
assert response.json is not None
|
||||
process_instance_id = response.json["id"]
|
||||
response = client.post(
|
||||
f"/v1.0/process-instances/{process_instance_id}/run",
|
||||
f"/v1.0/process-instances/{self.modify_process_identifier_for_path_param(process_model_identifier)}/{process_instance_id}/run",
|
||||
headers=self.logged_in_headers(initiator_user),
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
@ -2319,7 +2347,7 @@ class TestProcessApi(BaseTest):
|
|||
process_instance_id = response.json["id"]
|
||||
|
||||
client.post(
|
||||
f"/v1.0/process-instances/{process_instance_id}/run",
|
||||
f"/v1.0/process-instances/{self.modify_process_identifier_for_path_param(process_model_identifier)}/{process_instance_id}/run",
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
|
||||
|
@ -2339,7 +2367,7 @@ class TestProcessApi(BaseTest):
|
|||
|
||||
# TODO: Why can I run a suspended process instance?
|
||||
response = client.post(
|
||||
f"/v1.0/process-instances/{process_instance_id}/run",
|
||||
f"/v1.0/process-instances/{self.modify_process_identifier_for_path_param(process_model_identifier)}/{process_instance_id}/run",
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
|
||||
|
@ -2408,7 +2436,7 @@ class TestProcessApi(BaseTest):
|
|||
def setup_initial_groups_for_move_tests(
|
||||
self, client: FlaskClient, with_super_admin_user: UserModel
|
||||
) -> None:
|
||||
"""setup_initial_groups_for_move_tests."""
|
||||
"""Setup_initial_groups_for_move_tests."""
|
||||
groups = ["group_a", "group_b", "group_b/group_bb"]
|
||||
# setup initial groups
|
||||
for group in groups:
|
||||
|
@ -2417,7 +2445,7 @@ class TestProcessApi(BaseTest):
|
|||
)
|
||||
# make sure initial groups exist
|
||||
for group in groups:
|
||||
persisted = ProcessModelService().get_process_group(group)
|
||||
persisted = ProcessModelService.get_process_group(group)
|
||||
assert persisted is not None
|
||||
assert persisted.id == group
|
||||
|
||||
|
@ -2428,7 +2456,7 @@ class TestProcessApi(BaseTest):
|
|||
with_db_and_bpmn_file_cleanup: None,
|
||||
with_super_admin_user: UserModel,
|
||||
) -> None:
|
||||
"""test_move_model."""
|
||||
"""Test_move_model."""
|
||||
self.setup_initial_groups_for_move_tests(client, with_super_admin_user)
|
||||
|
||||
process_model_id = "test_model"
|
||||
|
@ -2443,7 +2471,7 @@ class TestProcessApi(BaseTest):
|
|||
process_model_display_name=process_model_id,
|
||||
process_model_description=process_model_id,
|
||||
)
|
||||
persisted = ProcessModelService().get_process_model(original_process_model_path)
|
||||
persisted = ProcessModelService.get_process_model(original_process_model_path)
|
||||
assert persisted is not None
|
||||
assert persisted.id == original_process_model_path
|
||||
|
||||
|
@ -2463,11 +2491,11 @@ class TestProcessApi(BaseTest):
|
|||
|
||||
# make sure the original model does not exist
|
||||
with pytest.raises(ProcessEntityNotFoundError) as e:
|
||||
ProcessModelService().get_process_model(original_process_model_path)
|
||||
ProcessModelService.get_process_model(original_process_model_path)
|
||||
assert e.value.args[0] == "process_model_not_found"
|
||||
|
||||
# make sure the new model does exist
|
||||
new_process_model = ProcessModelService().get_process_model(
|
||||
new_process_model = ProcessModelService.get_process_model(
|
||||
new_process_model_path
|
||||
)
|
||||
assert new_process_model is not None
|
||||
|
@ -2480,7 +2508,7 @@ class TestProcessApi(BaseTest):
|
|||
with_db_and_bpmn_file_cleanup: None,
|
||||
with_super_admin_user: UserModel,
|
||||
) -> None:
|
||||
"""test_move_group."""
|
||||
"""Test_move_group."""
|
||||
self.setup_initial_groups_for_move_tests(client, with_super_admin_user)
|
||||
|
||||
# add sub group to `group_a`
|
||||
|
@ -2491,7 +2519,7 @@ class TestProcessApi(BaseTest):
|
|||
client, with_super_admin_user, original_sub_path, display_name=sub_group_id
|
||||
)
|
||||
# make sure original subgroup exists
|
||||
persisted = ProcessModelService().get_process_group(original_sub_path)
|
||||
persisted = ProcessModelService.get_process_group(original_sub_path)
|
||||
assert persisted is not None
|
||||
assert persisted.id == original_sub_path
|
||||
|
||||
|
@ -2508,11 +2536,11 @@ class TestProcessApi(BaseTest):
|
|||
|
||||
# make sure the original subgroup does not exist
|
||||
with pytest.raises(ProcessEntityNotFoundError) as e:
|
||||
ProcessModelService().get_process_group(original_sub_path)
|
||||
ProcessModelService.get_process_group(original_sub_path)
|
||||
|
||||
assert e.value.args[0] == "process_group_not_found"
|
||||
assert e.value.args[1] == f"Process Group Id: {original_sub_path}"
|
||||
|
||||
# make sure the new subgroup does exist
|
||||
new_process_group = ProcessModelService().get_process_group(new_sub_path)
|
||||
new_process_group = ProcessModelService.get_process_group(new_sub_path)
|
||||
assert new_process_group.id == new_sub_path
|
||||
|
|
|
@ -52,7 +52,7 @@ class SecretServiceTestHelpers(BaseTest):
|
|||
process_model_description=self.test_process_model_description,
|
||||
user=user,
|
||||
)
|
||||
process_model_info = ProcessModelService().get_process_model(
|
||||
process_model_info = ProcessModelService.get_process_model(
|
||||
process_model_identifier
|
||||
)
|
||||
return process_model_info
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
"""Test_get_localtime."""
|
||||
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.process_instance_metadata import (
|
||||
ProcessInstanceMetadataModel,
|
||||
)
|
||||
from spiffworkflow_backend.models.user import UserModel
|
||||
from spiffworkflow_backend.services.process_instance_processor import (
|
||||
ProcessInstanceProcessor,
|
||||
)
|
||||
|
||||
|
||||
class TestSaveProcessInstanceMetadata(BaseTest):
|
||||
"""TestSaveProcessInstanceMetadata."""
|
||||
|
||||
def test_can_save_process_instance_metadata(
|
||||
self,
|
||||
app: Flask,
|
||||
client: FlaskClient,
|
||||
with_db_and_bpmn_file_cleanup: None,
|
||||
with_super_admin_user: UserModel,
|
||||
) -> None:
|
||||
"""Test_can_save_process_instance_metadata."""
|
||||
initiator_user = self.find_or_create_user("initiator_user")
|
||||
self.create_process_group(
|
||||
client, with_super_admin_user, "test_group", "test_group"
|
||||
)
|
||||
process_model = load_test_spec(
|
||||
process_model_id="save_process_instance_metadata/save_process_instance_metadata",
|
||||
bpmn_file_name="save_process_instance_metadata.bpmn",
|
||||
process_model_source_directory="save_process_instance_metadata",
|
||||
)
|
||||
process_instance = self.create_process_instance_from_process_model(
|
||||
process_model=process_model, user=initiator_user
|
||||
)
|
||||
processor = ProcessInstanceProcessor(process_instance)
|
||||
processor.do_engine_steps(save=True)
|
||||
|
||||
process_instance_metadata = ProcessInstanceMetadataModel.query.filter_by(
|
||||
process_instance_id=process_instance.id
|
||||
).all()
|
||||
assert len(process_instance_metadata) == 3
|
|
@ -1,13 +1,38 @@
|
|||
"""Test_acceptance_test_fixtures."""
|
||||
import os
|
||||
|
||||
from flask.app import Flask
|
||||
|
||||
from spiffworkflow_backend.models.process_group import ProcessGroup
|
||||
from spiffworkflow_backend.models.process_model import ProcessModelInfo
|
||||
from spiffworkflow_backend.services.acceptance_test_fixtures import (
|
||||
load_acceptance_test_fixtures,
|
||||
)
|
||||
from spiffworkflow_backend.services.process_model_service import ProcessModelService
|
||||
|
||||
|
||||
def test_start_dates_are_one_hour_apart(app: Flask) -> None:
|
||||
"""Test_start_dates_are_one_hour_apart."""
|
||||
process_model_identifier = (
|
||||
"misc/acceptance-tests-group-one/acceptance-tests-model-1"
|
||||
)
|
||||
group_identifier = os.path.dirname(process_model_identifier)
|
||||
parent_group_identifier = os.path.dirname(group_identifier)
|
||||
if not ProcessModelService.is_group(parent_group_identifier):
|
||||
process_group = ProcessGroup(
|
||||
id=parent_group_identifier, display_name=parent_group_identifier
|
||||
)
|
||||
ProcessModelService.add_process_group(process_group)
|
||||
if not ProcessModelService.is_group(group_identifier):
|
||||
process_group = ProcessGroup(id=group_identifier, display_name=group_identifier)
|
||||
ProcessModelService.add_process_group(process_group)
|
||||
if not ProcessModelService.is_model(process_model_identifier):
|
||||
process_model = ProcessModelInfo(
|
||||
id=process_model_identifier,
|
||||
display_name=process_model_identifier,
|
||||
description="hey",
|
||||
)
|
||||
ProcessModelService.add_process_model(process_model)
|
||||
process_instances = load_acceptance_test_fixtures()
|
||||
|
||||
assert len(process_instances) > 2
|
||||
|
|
|
@ -113,7 +113,7 @@ class TestAuthorizationService(BaseTest):
|
|||
bpmn_file_location="model_with_lanes",
|
||||
)
|
||||
|
||||
process_model = ProcessModelService().get_process_model(
|
||||
process_model = ProcessModelService.get_process_model(
|
||||
process_model_id=process_model_identifier
|
||||
)
|
||||
process_instance = self.create_process_instance_from_process_model(
|
||||
|
|
|
@ -44,7 +44,7 @@ class TestMessageInstance(BaseTest):
|
|||
client, with_super_admin_user
|
||||
)
|
||||
|
||||
process_model = ProcessModelService().get_process_model(
|
||||
process_model = ProcessModelService.get_process_model(
|
||||
process_model_id=process_model_identifier
|
||||
)
|
||||
process_instance = self.create_process_instance_from_process_model(
|
||||
|
@ -81,7 +81,7 @@ class TestMessageInstance(BaseTest):
|
|||
client, with_super_admin_user
|
||||
)
|
||||
|
||||
process_model = ProcessModelService().get_process_model(
|
||||
process_model = ProcessModelService.get_process_model(
|
||||
process_model_id=process_model_identifier
|
||||
)
|
||||
process_instance = self.create_process_instance_from_process_model(
|
||||
|
@ -127,7 +127,7 @@ class TestMessageInstance(BaseTest):
|
|||
client, with_super_admin_user
|
||||
)
|
||||
|
||||
process_model = ProcessModelService().get_process_model(
|
||||
process_model = ProcessModelService.get_process_model(
|
||||
process_model_id=process_model_identifier
|
||||
)
|
||||
process_instance = self.create_process_instance_from_process_model(
|
||||
|
@ -174,7 +174,7 @@ class TestMessageInstance(BaseTest):
|
|||
client, with_super_admin_user
|
||||
)
|
||||
|
||||
process_model = ProcessModelService().get_process_model(
|
||||
process_model = ProcessModelService.get_process_model(
|
||||
process_model_id=process_model_identifier
|
||||
)
|
||||
process_instance = self.create_process_instance_from_process_model(
|
||||
|
|
|
@ -47,7 +47,7 @@ class TestMessageService(BaseTest):
|
|||
bpmn_file_name="message_sender.bpmn",
|
||||
)
|
||||
|
||||
process_instance_sender = ProcessInstanceService.create_process_instance(
|
||||
process_instance_sender = ProcessInstanceService.create_process_instance_from_process_model_identifier(
|
||||
process_model_sender.id,
|
||||
with_super_admin_user,
|
||||
)
|
||||
|
@ -154,7 +154,7 @@ class TestMessageService(BaseTest):
|
|||
|
||||
user = self.find_or_create_user()
|
||||
|
||||
process_instance_sender = ProcessInstanceService.create_process_instance(
|
||||
process_instance_sender = ProcessInstanceService.create_process_instance_from_process_model_identifier(
|
||||
process_model_sender.id,
|
||||
user,
|
||||
# process_group_identifier=process_model_sender.process_group_id,
|
||||
|
|
|
@ -9,8 +9,7 @@ def test_there_is_at_least_one_group_after_we_create_one(
|
|||
app: Flask, with_db_and_bpmn_file_cleanup: None
|
||||
) -> None:
|
||||
"""Test_there_is_at_least_one_group_after_we_create_one."""
|
||||
process_model_service = ProcessModelService()
|
||||
process_group = ProcessGroup(id="hey", display_name="sure")
|
||||
process_model_service.add_process_group(process_group)
|
||||
process_groups = ProcessModelService().get_process_groups()
|
||||
ProcessModelService.add_process_group(process_group)
|
||||
process_groups = ProcessModelService.get_process_groups()
|
||||
assert len(process_groups) > 0
|
||||
|
|
|
@ -161,6 +161,7 @@ class TestProcessInstanceProcessor(BaseTest):
|
|||
)
|
||||
processor = ProcessInstanceProcessor(process_instance)
|
||||
processor.do_engine_steps(save=True)
|
||||
processor.save()
|
||||
|
||||
assert len(process_instance.active_tasks) == 1
|
||||
active_task = process_instance.active_tasks[0]
|
||||
|
@ -241,3 +242,42 @@ class TestProcessInstanceProcessor(BaseTest):
|
|||
)
|
||||
|
||||
assert process_instance.status == ProcessInstanceStatus.complete.value
|
||||
|
||||
def test_does_not_recreate_active_tasks_on_multiple_saves(
|
||||
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")
|
||||
assert initiator_user.principal is not None
|
||||
assert finance_user_three.principal is not None
|
||||
AuthorizationService.import_permissions_from_yaml_file()
|
||||
|
||||
finance_group = GroupModel.query.filter_by(identifier="Finance Team").first()
|
||||
assert finance_group is not None
|
||||
|
||||
process_model = load_test_spec(
|
||||
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
|
||||
)
|
||||
processor = ProcessInstanceProcessor(process_instance)
|
||||
processor.do_engine_steps(save=True)
|
||||
assert len(process_instance.active_tasks) == 1
|
||||
initial_active_task_id = process_instance.active_tasks[0].id
|
||||
|
||||
# save again to ensure we go attempt to process the active tasks again
|
||||
processor.save()
|
||||
|
||||
assert len(process_instance.active_tasks) == 1
|
||||
assert initial_active_task_id == process_instance.active_tasks[0].id
|
||||
|
|
|
@ -32,7 +32,7 @@ class TestProcessModelService(BaseTest):
|
|||
primary_process_id = process_model.primary_process_id
|
||||
assert primary_process_id == "Process_HelloWorld"
|
||||
|
||||
ProcessModelService().update_process_model(
|
||||
ProcessModelService.update_process_model(
|
||||
process_model, {"display_name": "new_name"}
|
||||
)
|
||||
|
||||
|
|
|
@ -188,7 +188,7 @@ class TestSpecFileService(BaseTest):
|
|||
# ,
|
||||
# process_model_source_directory="call_activity_nested",
|
||||
# )
|
||||
process_model_info = ProcessModelService().get_process_model(
|
||||
process_model_info = ProcessModelService.get_process_model(
|
||||
process_model_identifier
|
||||
)
|
||||
files = SpecFileService.get_files(process_model_info)
|
||||
|
|
|
@ -28,7 +28,7 @@ class TestVariousBpmnConstructs(BaseTest):
|
|||
"timer_intermediate_catch_event",
|
||||
)
|
||||
|
||||
process_model = ProcessModelService().get_process_model(
|
||||
process_model = ProcessModelService.get_process_model(
|
||||
process_model_id=process_model_identifier
|
||||
)
|
||||
|
||||
|
|
|
@ -19,18 +19,12 @@ describe('process-groups', () => {
|
|||
cy.url().should('include', `process-groups/${groupId}`);
|
||||
cy.contains(`Process Group: ${groupDisplayName}`);
|
||||
|
||||
cy.contains('Edit process group').click();
|
||||
cy.getBySel('edit-process-group-button').click();
|
||||
cy.get('input[name=display_name]').clear().type(newGroupDisplayName);
|
||||
cy.contains('Submit').click();
|
||||
cy.contains(`Process Group: ${newGroupDisplayName}`);
|
||||
|
||||
cy.contains('Edit process group').click();
|
||||
cy.get('input[name=display_name]').should(
|
||||
'have.value',
|
||||
newGroupDisplayName
|
||||
);
|
||||
|
||||
cy.contains('Delete').click();
|
||||
cy.getBySel('delete-process-group-button').click();
|
||||
cy.contains('Are you sure');
|
||||
cy.getBySel('delete-process-group-button-modal-confirmation-dialog')
|
||||
.find('.cds--btn--danger')
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { modifyProcessModelPath } from '../../src/helpers';
|
||||
import { modifyProcessIdentifierForPathParam } from '../../src/helpers';
|
||||
|
||||
describe('process-models', () => {
|
||||
beforeEach(() => {
|
||||
|
@ -16,25 +16,22 @@ describe('process-models', () => {
|
|||
const modelDisplayName = `Test Model 2 ${id}`;
|
||||
const modelId = `test-model-2-${id}`;
|
||||
const newModelDisplayName = `${modelDisplayName} edited`;
|
||||
cy.contains('Misc').click();
|
||||
cy.contains('99-Shared Resources').click();
|
||||
cy.wait(500);
|
||||
cy.contains(groupDisplayName).click();
|
||||
cy.createModel(groupId, modelId, modelDisplayName);
|
||||
cy.url().should(
|
||||
'include',
|
||||
`process-models/${modifyProcessModelPath(groupId)}:${modelId}`
|
||||
`process-models/${modifyProcessIdentifierForPathParam(
|
||||
groupId
|
||||
)}:${modelId}`
|
||||
);
|
||||
cy.contains(`Process Model: ${modelDisplayName}`);
|
||||
|
||||
cy.contains('Edit process model').click();
|
||||
cy.getBySel('edit-process-model-button').click();
|
||||
cy.get('input[name=display_name]').clear().type(newModelDisplayName);
|
||||
cy.contains('Submit').click();
|
||||
cy.contains(`Process Model: ${groupId}/${modelId}`);
|
||||
cy.contains('Submit').click();
|
||||
cy.get('input[name=display_name]').should(
|
||||
'have.value',
|
||||
newModelDisplayName
|
||||
);
|
||||
cy.contains(`Process Model: ${newModelDisplayName}`);
|
||||
|
||||
// go back to process model show by clicking on the breadcrumb
|
||||
cy.contains(modelId).click();
|
||||
|
@ -46,7 +43,7 @@ describe('process-models', () => {
|
|||
.click();
|
||||
cy.url().should(
|
||||
'include',
|
||||
`process-groups/${modifyProcessModelPath(groupId)}`
|
||||
`process-groups/${modifyProcessIdentifierForPathParam(groupId)}`
|
||||
);
|
||||
cy.contains(modelId).should('not.exist');
|
||||
});
|
||||
|
@ -64,15 +61,17 @@ describe('process-models', () => {
|
|||
const dmnFileName = `dmn_test_file_${id}`;
|
||||
const jsonFileName = `json_test_file_${id}`;
|
||||
|
||||
cy.contains('Misc').click();
|
||||
cy.contains('99-Shared Resources').click();
|
||||
cy.wait(500);
|
||||
cy.contains(groupDisplayName).click();
|
||||
cy.createModel(groupId, modelId, modelDisplayName);
|
||||
cy.contains(directParentGroupId).click();
|
||||
cy.contains(modelId).click();
|
||||
cy.contains(modelDisplayName).click();
|
||||
cy.url().should(
|
||||
'include',
|
||||
`process-models/${modifyProcessModelPath(groupId)}:${modelId}`
|
||||
`process-models/${modifyProcessIdentifierForPathParam(
|
||||
groupId
|
||||
)}:${modelId}`
|
||||
);
|
||||
cy.contains(`Process Model: ${modelDisplayName}`);
|
||||
cy.contains(`${bpmnFileName}.bpmn`).should('not.exist');
|
||||
|
@ -135,8 +134,12 @@ describe('process-models', () => {
|
|||
cy.getBySel('delete-process-model-button-modal-confirmation-dialog')
|
||||
.find('.cds--btn--danger')
|
||||
.click();
|
||||
cy.url().should('include', `process-groups/${modifyProcessModelPath(groupId)}`);
|
||||
cy.url().should(
|
||||
'include',
|
||||
`process-groups/${modifyProcessIdentifierForPathParam(groupId)}`
|
||||
);
|
||||
cy.contains(modelId).should('not.exist');
|
||||
cy.contains(modelDisplayName).should('not.exist');
|
||||
});
|
||||
|
||||
it('can upload and run a bpmn file', () => {
|
||||
|
@ -148,17 +151,19 @@ describe('process-models', () => {
|
|||
const modelDisplayName = `Test Model 2 ${id}`;
|
||||
const modelId = `test-model-2-${id}`;
|
||||
cy.contains('Add a process group');
|
||||
cy.contains('Misc').click();
|
||||
cy.contains('99-Shared Resources').click();
|
||||
cy.wait(500);
|
||||
cy.contains(groupDisplayName).click();
|
||||
cy.createModel(groupId, modelId, modelDisplayName);
|
||||
|
||||
cy.contains(`${directParentGroupId}`).click();
|
||||
cy.contains('Add a process model');
|
||||
cy.contains(modelId).click();
|
||||
cy.contains(modelDisplayName).click();
|
||||
cy.url().should(
|
||||
'include',
|
||||
`process-models/${modifyProcessModelPath(groupId)}:${modelId}`
|
||||
`process-models/${modifyProcessIdentifierForPathParam(
|
||||
groupId
|
||||
)}:${modelId}`
|
||||
);
|
||||
cy.contains(`Process Model: ${modelDisplayName}`);
|
||||
|
||||
|
@ -190,17 +195,19 @@ describe('process-models', () => {
|
|||
.click();
|
||||
cy.url().should(
|
||||
'include',
|
||||
`process-groups/${modifyProcessModelPath(groupId)}`
|
||||
`process-groups/${modifyProcessIdentifierForPathParam(groupId)}`
|
||||
);
|
||||
cy.contains(modelId).should('not.exist');
|
||||
cy.contains(modelDisplayName).should('not.exist');
|
||||
});
|
||||
|
||||
it('can paginate items', () => {
|
||||
cy.contains('Misc').click();
|
||||
cy.wait(500);
|
||||
cy.contains('Acceptance Tests Group One').click();
|
||||
cy.basicPaginationTest();
|
||||
});
|
||||
// process models no longer has pagination post-tiles
|
||||
// it.only('can paginate items', () => {
|
||||
// cy.contains('99-Shared Resources').click();
|
||||
// cy.wait(500);
|
||||
// cy.contains('Acceptance Tests Group One').click();
|
||||
// cy.basicPaginationTest();
|
||||
// });
|
||||
|
||||
it('can allow searching for model', () => {
|
||||
cy.getBySel('process-model-selection').click().type('model-3');
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { string } from 'prop-types';
|
||||
import { modifyProcessModelPath } from '../../src/helpers';
|
||||
import { modifyProcessIdentifierForPathParam } from '../../src/helpers';
|
||||
|
||||
// ***********************************************
|
||||
// This example commands.js shows you how to
|
||||
|
@ -78,8 +78,7 @@ Cypress.Commands.add('createModel', (groupId, modelId, modelDisplayName) => {
|
|||
|
||||
cy.url().should(
|
||||
'include',
|
||||
`process-models/${modifyProcessModelPath(groupId)}:${modelId}`
|
||||
// `process-models/${groupId}:${modelId}`
|
||||
`process-models/${modifyProcessIdentifierForPathParam(groupId)}:${modelId}`
|
||||
);
|
||||
cy.contains(`Process Model: ${modelDisplayName}`);
|
||||
});
|
||||
|
@ -104,12 +103,12 @@ Cypress.Commands.add(
|
|||
'navigateToProcessModel',
|
||||
(groupDisplayName, modelDisplayName, modelIdentifier) => {
|
||||
cy.navigateToAdmin();
|
||||
cy.contains('Misc').click();
|
||||
cy.contains(`Process Group: 99-Misc`, { timeout: 10000 });
|
||||
cy.contains('99-Shared Resources').click();
|
||||
cy.contains(`Process Group: 99-Shared Resources`, { timeout: 10000 });
|
||||
cy.contains(groupDisplayName).click();
|
||||
cy.contains(`Process Group: ${groupDisplayName}`);
|
||||
// https://stackoverflow.com/q/51254946/6090676
|
||||
cy.getBySel('process-model-show-link').contains(modelIdentifier).click();
|
||||
cy.getBySel('process-model-show-link').contains(modelDisplayName).click();
|
||||
cy.contains(`Process Model: ${modelDisplayName}`);
|
||||
}
|
||||
);
|
||||
|
@ -133,8 +132,3 @@ Cypress.Commands.add('assertAtLeastOneItemInPaginatedResults', () => {
|
|||
Cypress.Commands.add('assertNoItemInPaginatedResults', () => {
|
||||
cy.contains(/\b0–0 of 0 items/);
|
||||
});
|
||||
|
||||
Cypress.Commands.add('modifyProcessModelPath', (path) => {
|
||||
path.replace('/', ':');
|
||||
return path;
|
||||
});
|
||||
|
|
|
@ -7980,7 +7980,7 @@
|
|||
},
|
||||
"node_modules/bpmn-js-spiffworkflow": {
|
||||
"version": "0.0.8",
|
||||
"resolved": "git+ssh://git@github.com/sartography/bpmn-js-spiffworkflow.git#e92f48da7cb4416310af71bb1699caaca87324cd",
|
||||
"resolved": "git+ssh://git@github.com/sartography/bpmn-js-spiffworkflow.git#aca23dc56e5d37aa1ed0a3cf11acb55f76a36da7",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.4",
|
||||
|
@ -37138,7 +37138,7 @@
|
|||
}
|
||||
},
|
||||
"bpmn-js-spiffworkflow": {
|
||||
"version": "git+ssh://git@github.com/sartography/bpmn-js-spiffworkflow.git#e92f48da7cb4416310af71bb1699caaca87324cd",
|
||||
"version": "git+ssh://git@github.com/sartography/bpmn-js-spiffworkflow.git#aca23dc56e5d37aa1ed0a3cf11acb55f76a36da7",
|
||||
"from": "bpmn-js-spiffworkflow@sartography/bpmn-js-spiffworkflow#main",
|
||||
"requires": {
|
||||
"inherits": "^2.0.4",
|
||||
|
|
|
@ -8,6 +8,8 @@ export default function MyCompletedInstances() {
|
|||
filtersEnabled={false}
|
||||
paginationQueryParamPrefix={paginationQueryParamPrefix}
|
||||
perPageOptions={[2, 5, 25]}
|
||||
reportIdentifier="system_report_instances_initiated_by_me"
|
||||
showReports={false}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -163,6 +163,7 @@ export default function NavigationBar() {
|
|||
</Can>
|
||||
{configurationElement()}
|
||||
<HeaderMenuItem
|
||||
hidden
|
||||
href="/admin/process-instances/reports"
|
||||
isCurrentPage={isActivePage('/admin/process-instances/reports')}
|
||||
>
|
||||
|
|
|
@ -14,6 +14,7 @@ type OwnProps = {
|
|||
pagination: PaginationObject | null;
|
||||
tableToDisplay: any;
|
||||
paginationQueryParamPrefix?: string;
|
||||
paginationClassName?: string;
|
||||
};
|
||||
|
||||
export default function PaginationForTable({
|
||||
|
@ -23,6 +24,7 @@ export default function PaginationForTable({
|
|||
pagination,
|
||||
tableToDisplay,
|
||||
paginationQueryParamPrefix,
|
||||
paginationClassName,
|
||||
}: OwnProps) {
|
||||
const PER_PAGE_OPTIONS = [2, 10, 50, 100];
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
@ -44,6 +46,7 @@ export default function PaginationForTable({
|
|||
<>
|
||||
{tableToDisplay}
|
||||
<Pagination
|
||||
className={paginationClassName}
|
||||
data-qa="pagination-options"
|
||||
backwardText="Previous page"
|
||||
forwardText="Next page"
|
||||
|
|
|
@ -3,13 +3,13 @@ import { BrowserRouter } from 'react-router-dom';
|
|||
import ProcessBreadcrumb from './ProcessBreadcrumb';
|
||||
|
||||
test('renders home link', () => {
|
||||
render(
|
||||
<BrowserRouter>
|
||||
<ProcessBreadcrumb />
|
||||
</BrowserRouter>
|
||||
);
|
||||
const homeElement = screen.getByText(/Process Groups/);
|
||||
expect(homeElement).toBeInTheDocument();
|
||||
// render(
|
||||
// <BrowserRouter>
|
||||
// <ProcessBreadcrumb />
|
||||
// </BrowserRouter>
|
||||
// );
|
||||
// const homeElement = screen.getByText(/Process Groups/);
|
||||
// expect(homeElement).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('renders hotCrumbs', () => {
|
||||
|
|
|
@ -1,123 +1,118 @@
|
|||
// @ts-ignore
|
||||
import { Breadcrumb, BreadcrumbItem } from '@carbon/react';
|
||||
import { splitProcessModelId } from '../helpers';
|
||||
import { HotCrumbItem } from '../interfaces';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { modifyProcessIdentifierForPathParam } from '../helpers';
|
||||
import {
|
||||
HotCrumbItem,
|
||||
ProcessGroup,
|
||||
ProcessGroupLite,
|
||||
ProcessModel,
|
||||
} from '../interfaces';
|
||||
import HttpService from '../services/HttpService';
|
||||
|
||||
type OwnProps = {
|
||||
processModelId?: string;
|
||||
processGroupId?: string;
|
||||
linkProcessModel?: boolean;
|
||||
hotCrumbs?: HotCrumbItem[];
|
||||
};
|
||||
|
||||
const explodeCrumb = (crumb: HotCrumbItem) => {
|
||||
const url: string = crumb[1] || '';
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const [endingUrlType, 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') {
|
||||
if (lastPathItem !== undefined) {
|
||||
paths.push(lastPathItem);
|
||||
}
|
||||
// process_model to process-models
|
||||
const lastUrl = `/admin/${endingUrlType
|
||||
.replace('_', '-')
|
||||
.replace(/s*$/, 's')}/${paths.join(':')}`;
|
||||
breadcrumbItems.push(
|
||||
<BreadcrumbItem key={lastPathItem} href={lastUrl}>
|
||||
{lastPathItem}
|
||||
</BreadcrumbItem>
|
||||
);
|
||||
} else {
|
||||
breadcrumbItems.push(
|
||||
<BreadcrumbItem isCurrentPage key={lastPathItem}>
|
||||
{lastPathItem}
|
||||
</BreadcrumbItem>
|
||||
);
|
||||
}
|
||||
return breadcrumbItems;
|
||||
};
|
||||
export default function ProcessBreadcrumb({ hotCrumbs }: OwnProps) {
|
||||
const [processEntity, setProcessEntity] = useState<
|
||||
ProcessGroup | ProcessModel | null
|
||||
>(null);
|
||||
|
||||
export default function ProcessBreadcrumb({
|
||||
processModelId,
|
||||
processGroupId,
|
||||
hotCrumbs,
|
||||
linkProcessModel = false,
|
||||
}: OwnProps) {
|
||||
let processGroupBreadcrumb = null;
|
||||
let processModelBreadcrumb = null;
|
||||
if (hotCrumbs) {
|
||||
const leadingCrumbLinks = hotCrumbs.map((crumb: any) => {
|
||||
const valueLabel = crumb[0];
|
||||
const url = crumb[1];
|
||||
if (!url) {
|
||||
return (
|
||||
<BreadcrumbItem isCurrentPage key={valueLabel}>
|
||||
{valueLabel}
|
||||
</BreadcrumbItem>
|
||||
);
|
||||
useEffect(() => {
|
||||
const explodeCrumbItemObject = (crumb: HotCrumbItem) => {
|
||||
if ('entityToExplode' in crumb) {
|
||||
const { entityToExplode, entityType } = crumb;
|
||||
if (entityType === 'process-model-id') {
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-models/${modifyProcessIdentifierForPathParam(
|
||||
entityToExplode as string
|
||||
)}`,
|
||||
successCallback: setProcessEntity,
|
||||
});
|
||||
} else if (entityType === 'process-group-id') {
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-groups/${modifyProcessIdentifierForPathParam(
|
||||
entityToExplode as string
|
||||
)}`,
|
||||
successCallback: setProcessEntity,
|
||||
});
|
||||
} else {
|
||||
setProcessEntity(entityToExplode as any);
|
||||
}
|
||||
}
|
||||
if (url && url.match(/^process[_-](model|group)s?:/)) {
|
||||
return explodeCrumb(crumb);
|
||||
}
|
||||
return (
|
||||
<BreadcrumbItem key={valueLabel} href={url}>
|
||||
{valueLabel}
|
||||
</BreadcrumbItem>
|
||||
);
|
||||
});
|
||||
return <Breadcrumb noTrailingSlash>{leadingCrumbLinks}</Breadcrumb>;
|
||||
}
|
||||
if (processModelId) {
|
||||
if (linkProcessModel) {
|
||||
processModelBreadcrumb = (
|
||||
<BreadcrumbItem
|
||||
href={`/admin/process-models/${processGroupId}/${processModelId}`}
|
||||
>
|
||||
{`Process Model: ${processModelId}`}
|
||||
</BreadcrumbItem>
|
||||
);
|
||||
} else {
|
||||
processModelBreadcrumb = (
|
||||
<BreadcrumbItem isCurrentPage>
|
||||
{`Process Model: ${processModelId}`}
|
||||
</BreadcrumbItem>
|
||||
);
|
||||
};
|
||||
if (hotCrumbs) {
|
||||
hotCrumbs.forEach(explodeCrumbItemObject);
|
||||
}
|
||||
processGroupBreadcrumb = (
|
||||
<BreadcrumbItem
|
||||
data-qa="process-group-breadcrumb-link"
|
||||
href={`/admin/process-groups/${processGroupId}`}
|
||||
>
|
||||
{`Process Group: ${processGroupId}`}
|
||||
</BreadcrumbItem>
|
||||
);
|
||||
} else if (processGroupId) {
|
||||
processGroupBreadcrumb = (
|
||||
<BreadcrumbItem isCurrentPage>
|
||||
{`Process Group: ${processGroupId}`}
|
||||
</BreadcrumbItem>
|
||||
);
|
||||
}
|
||||
}, [setProcessEntity, hotCrumbs]);
|
||||
|
||||
return (
|
||||
<Breadcrumb noTrailingSlash>
|
||||
<BreadcrumbItem href="/admin">Process Groups</BreadcrumbItem>
|
||||
{processGroupBreadcrumb}
|
||||
{processModelBreadcrumb}
|
||||
</Breadcrumb>
|
||||
);
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
const hotCrumbElement = () => {
|
||||
if (hotCrumbs) {
|
||||
const leadingCrumbLinks = hotCrumbs.map((crumb: any) => {
|
||||
if (
|
||||
'entityToExplode' in crumb &&
|
||||
processEntity &&
|
||||
processEntity.parent_groups
|
||||
) {
|
||||
const breadcrumbs = processEntity.parent_groups.map(
|
||||
(parentGroup: ProcessGroupLite) => {
|
||||
const fullUrl = `/admin/process-groups/${modifyProcessIdentifierForPathParam(
|
||||
parentGroup.id
|
||||
)}`;
|
||||
return (
|
||||
<BreadcrumbItem key={parentGroup.id} href={fullUrl}>
|
||||
{parentGroup.display_name}
|
||||
</BreadcrumbItem>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
if (crumb.linkLastItem) {
|
||||
let apiBase = '/admin/process-groups';
|
||||
if (crumb.entityType.startsWith('process-model')) {
|
||||
apiBase = '/admin/process-models';
|
||||
}
|
||||
const fullUrl = `${apiBase}/${modifyProcessIdentifierForPathParam(
|
||||
processEntity.id
|
||||
)}`;
|
||||
breadcrumbs.push(
|
||||
<BreadcrumbItem key={processEntity.id} href={fullUrl}>
|
||||
{processEntity.display_name}
|
||||
</BreadcrumbItem>
|
||||
);
|
||||
} else {
|
||||
breadcrumbs.push(
|
||||
<BreadcrumbItem key={processEntity.id} isCurrentPage>
|
||||
{processEntity.display_name}
|
||||
</BreadcrumbItem>
|
||||
);
|
||||
}
|
||||
return breadcrumbs;
|
||||
}
|
||||
const valueLabel = crumb[0];
|
||||
const url = crumb[1];
|
||||
if (!url && valueLabel) {
|
||||
return (
|
||||
<BreadcrumbItem isCurrentPage key={valueLabel}>
|
||||
{valueLabel}
|
||||
</BreadcrumbItem>
|
||||
);
|
||||
}
|
||||
if (url && valueLabel) {
|
||||
return (
|
||||
<BreadcrumbItem key={valueLabel} href={url}>
|
||||
{valueLabel}
|
||||
</BreadcrumbItem>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
return <Breadcrumb noTrailingSlash>{leadingCrumbLinks}</Breadcrumb>;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
return <Breadcrumb noTrailingSlash>{hotCrumbElement()}</Breadcrumb>;
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ import { Button, ButtonSet, Form, Stack, TextInput } from '@carbon/react';
|
|||
import { modifyProcessIdentifierForPathParam, slugifyString } from '../helpers';
|
||||
import HttpService from '../services/HttpService';
|
||||
import { ProcessGroup } from '../interfaces';
|
||||
import ButtonWithConfirmation from './ButtonWithConfirmation';
|
||||
|
||||
type OwnProps = {
|
||||
mode: string;
|
||||
|
@ -35,24 +34,10 @@ export default function ProcessGroupForm({
|
|||
}
|
||||
};
|
||||
|
||||
const navigateToProcessGroups = (_result: any) => {
|
||||
navigate(`/admin/process-groups`);
|
||||
};
|
||||
|
||||
const hasValidIdentifier = (identifierToCheck: string) => {
|
||||
return identifierToCheck.match(/^[a-z0-9][0-9a-z-]+[a-z0-9]$/);
|
||||
};
|
||||
|
||||
const deleteProcessGroup = () => {
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-groups/${modifyProcessIdentifierForPathParam(
|
||||
processGroup.id
|
||||
)}`,
|
||||
successCallback: navigateToProcessGroups,
|
||||
httpMethod: 'DELETE',
|
||||
});
|
||||
};
|
||||
|
||||
const handleFormSubmission = (event: any) => {
|
||||
const searchParams = new URLSearchParams(document.location.search);
|
||||
const parentGroupId = searchParams.get('parentGroupId');
|
||||
|
@ -172,17 +157,6 @@ export default function ProcessGroupForm({
|
|||
|
||||
const formButtons = () => {
|
||||
const buttons = [<Button type="submit">Submit</Button>];
|
||||
if (mode === 'edit') {
|
||||
buttons.push(
|
||||
<ButtonWithConfirmation
|
||||
data-qa="delete-process-group-button"
|
||||
description={`Delete Process Group ${processGroup.id}?`}
|
||||
onConfirmation={deleteProcessGroup}
|
||||
buttonLabel="Delete"
|
||||
confirmButtonLabel="Delete"
|
||||
/>
|
||||
);
|
||||
}
|
||||
return <ButtonSet>{buttons}</ButtonSet>;
|
||||
};
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@ import {
|
|||
getPageInfoFromSearchParams,
|
||||
getProcessModelFullIdentifierFromSearchParams,
|
||||
modifyProcessIdentifierForPathParam,
|
||||
refreshAtInterval,
|
||||
} from '../helpers';
|
||||
|
||||
import PaginationForTable from './PaginationForTable';
|
||||
|
@ -47,15 +48,24 @@ import {
|
|||
PaginationObject,
|
||||
ProcessModel,
|
||||
ProcessInstanceReport,
|
||||
ProcessInstance,
|
||||
} from '../interfaces';
|
||||
import ProcessModelSearch from './ProcessModelSearch';
|
||||
import ProcessInstanceReportSearch from './ProcessInstanceReportSearch';
|
||||
|
||||
const REFRESH_INTERVAL = 5;
|
||||
const REFRESH_TIMEOUT = 600;
|
||||
|
||||
type OwnProps = {
|
||||
filtersEnabled?: boolean;
|
||||
processModelFullIdentifier?: string;
|
||||
paginationQueryParamPrefix?: string;
|
||||
perPageOptions?: number[];
|
||||
showReports?: boolean;
|
||||
reportIdentifier?: string;
|
||||
textToShowIfEmpty?: string;
|
||||
paginationClassName?: string;
|
||||
autoReload?: boolean;
|
||||
};
|
||||
|
||||
interface dateParameters {
|
||||
|
@ -67,6 +77,11 @@ export default function ProcessInstanceListTable({
|
|||
processModelFullIdentifier,
|
||||
paginationQueryParamPrefix,
|
||||
perPageOptions,
|
||||
showReports = true,
|
||||
reportIdentifier,
|
||||
textToShowIfEmpty,
|
||||
paginationClassName,
|
||||
autoReload = false,
|
||||
}: OwnProps) {
|
||||
const params = useParams();
|
||||
const [searchParams] = useSearchParams();
|
||||
|
@ -171,9 +186,14 @@ export default function ProcessInstanceListTable({
|
|||
queryParamString += `&user_filter=${userAppliedFilter}`;
|
||||
}
|
||||
|
||||
const reportIdentifier = searchParams.get('report_identifier');
|
||||
if (reportIdentifier) {
|
||||
queryParamString += `&report_identifier=${reportIdentifier}`;
|
||||
let reportIdentifierToUse: any = reportIdentifier;
|
||||
|
||||
if (!reportIdentifierToUse) {
|
||||
reportIdentifierToUse = searchParams.get('report_identifier');
|
||||
}
|
||||
|
||||
if (reportIdentifierToUse) {
|
||||
queryParamString += `&report_identifier=${reportIdentifierToUse}`;
|
||||
}
|
||||
|
||||
Object.keys(dateParametersToAlwaysFilterBy).forEach(
|
||||
|
@ -250,17 +270,24 @@ export default function ProcessInstanceListTable({
|
|||
|
||||
getProcessInstances();
|
||||
}
|
||||
const checkFiltersAndRun = () => {
|
||||
if (filtersEnabled) {
|
||||
// populate process model selection
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-models?per_page=1000&recursive=true`,
|
||||
successCallback: processResultForProcessModels,
|
||||
});
|
||||
} else {
|
||||
getProcessInstances();
|
||||
}
|
||||
};
|
||||
|
||||
if (filtersEnabled) {
|
||||
// populate process model selection
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-models?per_page=1000&recursive=true`,
|
||||
successCallback: processResultForProcessModels,
|
||||
});
|
||||
} else {
|
||||
getProcessInstances();
|
||||
checkFiltersAndRun();
|
||||
if (autoReload) {
|
||||
refreshAtInterval(REFRESH_INTERVAL, REFRESH_TIMEOUT, checkFiltersAndRun);
|
||||
}
|
||||
}, [
|
||||
autoReload,
|
||||
searchParams,
|
||||
params,
|
||||
oneMonthInSeconds,
|
||||
|
@ -271,6 +298,7 @@ export default function ProcessInstanceListTable({
|
|||
paginationQueryParamPrefix,
|
||||
processModelFullIdentifier,
|
||||
perPageOptions,
|
||||
reportIdentifier,
|
||||
]);
|
||||
|
||||
// This sets the filter data using the saved reports returned from the initial instance_list query.
|
||||
|
@ -596,10 +624,12 @@ export default function ProcessInstanceListTable({
|
|||
const buildTable = () => {
|
||||
const headerLabels: Record<string, string> = {
|
||||
id: 'Id',
|
||||
process_model_identifier: 'Process Model',
|
||||
process_model_identifier: 'Process',
|
||||
process_model_display_name: 'Process',
|
||||
start_in_seconds: 'Start Time',
|
||||
end_in_seconds: 'End Time',
|
||||
status: 'Status',
|
||||
username: 'Started By',
|
||||
spiff_step: 'SpiffWorkflow Step',
|
||||
};
|
||||
const getHeaderLabel = (header: string) => {
|
||||
|
@ -610,13 +640,14 @@ export default function ProcessInstanceListTable({
|
|||
return getHeaderLabel((column as any).Header);
|
||||
});
|
||||
|
||||
const formatProcessInstanceId = (row: any, id: any) => {
|
||||
const formatProcessInstanceId = (row: ProcessInstance, id: number) => {
|
||||
const modifiedProcessModelId: String =
|
||||
modifyProcessIdentifierForPathParam(row.process_model_identifier);
|
||||
return (
|
||||
<Link
|
||||
data-qa="process-instance-show-link"
|
||||
to={`/admin/process-models/${modifiedProcessModelId}/process-instances/${row.id}`}
|
||||
to={`/admin/process-models/${modifiedProcessModelId}/process-instances/${id}`}
|
||||
title={`View process instance ${id}`}
|
||||
>
|
||||
{id}
|
||||
</Link>
|
||||
|
@ -633,6 +664,23 @@ export default function ProcessInstanceListTable({
|
|||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
const formatProcessModelDisplayName = (
|
||||
row: ProcessInstance,
|
||||
displayName: string
|
||||
) => {
|
||||
return (
|
||||
<Link
|
||||
to={`/admin/process-models/${modifyProcessIdentifierForPathParam(
|
||||
row.process_model_identifier
|
||||
)}`}
|
||||
title={row.process_model_identifier}
|
||||
>
|
||||
{displayName}
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
const formatSecondsForDisplay = (_row: any, seconds: any) => {
|
||||
return convertSecondsToFormattedDateTime(seconds) || '-';
|
||||
};
|
||||
|
@ -643,6 +691,7 @@ export default function ProcessInstanceListTable({
|
|||
const columnFormatters: Record<string, any> = {
|
||||
id: formatProcessInstanceId,
|
||||
process_model_identifier: formatProcessModelIdentifier,
|
||||
process_model_display_name: formatProcessModelDisplayName,
|
||||
start_in_seconds: formatSecondsForDisplay,
|
||||
end_in_seconds: formatSecondsForDisplay,
|
||||
};
|
||||
|
@ -704,12 +753,15 @@ export default function ProcessInstanceListTable({
|
|||
};
|
||||
|
||||
const reportSearchComponent = () => {
|
||||
return (
|
||||
<ProcessInstanceReportSearch
|
||||
onChange={processInstanceReportDidChange}
|
||||
selectedItem={processInstanceReportSelection}
|
||||
/>
|
||||
);
|
||||
if (showReports) {
|
||||
return (
|
||||
<ProcessInstanceReportSearch
|
||||
onChange={processInstanceReportDidChange}
|
||||
selectedItem={processInstanceReportSelection}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const filterComponent = () => {
|
||||
|
@ -720,13 +772,13 @@ export default function ProcessInstanceListTable({
|
|||
<>
|
||||
<Grid fullWidth>
|
||||
<Column
|
||||
className="filterIcon"
|
||||
sm={{ span: 1, offset: 3 }}
|
||||
md={{ span: 1, offset: 7 }}
|
||||
lg={{ span: 1, offset: 15 }}
|
||||
>
|
||||
<Button
|
||||
data-qa="filter-section-expand-toggle"
|
||||
kind="ghost"
|
||||
renderIcon={Filter}
|
||||
iconDescription="Filter Options"
|
||||
hasIconOnly
|
||||
|
@ -740,7 +792,7 @@ export default function ProcessInstanceListTable({
|
|||
);
|
||||
};
|
||||
|
||||
if (pagination) {
|
||||
if (pagination && (!textToShowIfEmpty || pagination.total > 0)) {
|
||||
// eslint-disable-next-line prefer-const
|
||||
let { page, perPage } = getPageInfoFromSearchParams(
|
||||
searchParams,
|
||||
|
@ -763,10 +815,18 @@ export default function ProcessInstanceListTable({
|
|||
tableToDisplay={buildTable()}
|
||||
paginationQueryParamPrefix={paginationQueryParamPrefix}
|
||||
perPageOptions={perPageOptions}
|
||||
paginationClassName={paginationClassName}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
if (textToShowIfEmpty) {
|
||||
return (
|
||||
<p className="no-results-message with-large-bottom-margin">
|
||||
{textToShowIfEmpty}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -4,21 +4,78 @@ import {
|
|||
Button,
|
||||
// @ts-ignore
|
||||
} from '@carbon/react';
|
||||
import { ProcessModel } from '../interfaces';
|
||||
import { Can } from '@casl/react';
|
||||
import {
|
||||
PermissionsToCheck,
|
||||
ProcessModel,
|
||||
RecentProcessModel,
|
||||
} from '../interfaces';
|
||||
import HttpService from '../services/HttpService';
|
||||
import ErrorContext from '../contexts/ErrorContext';
|
||||
import { modifyProcessIdentifierForPathParam } from '../helpers';
|
||||
import { usePermissionFetcher } from '../hooks/PermissionService';
|
||||
|
||||
const storeRecentProcessModelInLocalStorage = (
|
||||
processModelForStorage: ProcessModel
|
||||
) => {
|
||||
// All values stored in localStorage are strings.
|
||||
// Grab our recentProcessModels string from localStorage.
|
||||
const stringFromLocalStorage = window.localStorage.getItem(
|
||||
'recentProcessModels'
|
||||
);
|
||||
|
||||
// adapted from https://stackoverflow.com/a/59424458/6090676
|
||||
// If that value is null (meaning that we've never saved anything to that spot in localStorage before), use an empty array as our array. Otherwise, use the value we parse out.
|
||||
let array: RecentProcessModel[] = [];
|
||||
if (stringFromLocalStorage !== null) {
|
||||
// Then parse that string into an actual value.
|
||||
array = JSON.parse(stringFromLocalStorage);
|
||||
}
|
||||
|
||||
// Here's the value we want to add
|
||||
const value = {
|
||||
processModelIdentifier: processModelForStorage.id,
|
||||
processModelDisplayName: processModelForStorage.display_name,
|
||||
};
|
||||
|
||||
// anything with a processGroupIdentifier is old and busted. leave it behind.
|
||||
array = array.filter((item) => item.processGroupIdentifier === undefined);
|
||||
|
||||
// If our parsed/empty array doesn't already have this value in it...
|
||||
const matchingItem = array.find(
|
||||
(item) => item.processModelIdentifier === value.processModelIdentifier
|
||||
);
|
||||
if (matchingItem === undefined) {
|
||||
// add the value to the beginning of the array
|
||||
array.unshift(value);
|
||||
|
||||
// Keep the array to 3 items
|
||||
if (array.length > 3) {
|
||||
array.pop();
|
||||
}
|
||||
}
|
||||
|
||||
// once the old and busted serializations are gone, we can put these two statements inside the above if statement
|
||||
|
||||
// turn the array WITH THE NEW VALUE IN IT into a string to prepare it to be stored in localStorage
|
||||
const stringRepresentingArray = JSON.stringify(array);
|
||||
|
||||
// and store it in localStorage as "recentProcessModels"
|
||||
window.localStorage.setItem('recentProcessModels', stringRepresentingArray);
|
||||
};
|
||||
|
||||
type OwnProps = {
|
||||
processModel: ProcessModel;
|
||||
onSuccessCallback: Function;
|
||||
className?: string;
|
||||
checkPermissions?: boolean;
|
||||
};
|
||||
|
||||
export default function ProcessInstanceRun({
|
||||
processModel,
|
||||
onSuccessCallback,
|
||||
className,
|
||||
checkPermissions = true,
|
||||
}: OwnProps) {
|
||||
const navigate = useNavigate();
|
||||
const setErrorMessage = (useContext as any)(ErrorContext)[1];
|
||||
|
@ -26,6 +83,17 @@ export default function ProcessInstanceRun({
|
|||
processModel.id
|
||||
);
|
||||
|
||||
const processInstanceActionPath = `/v1.0/process-models/${modifiedProcessModelId}/process-instances`;
|
||||
let permissionRequestData: PermissionsToCheck = {
|
||||
[processInstanceActionPath]: ['POST'],
|
||||
};
|
||||
|
||||
if (!checkPermissions) {
|
||||
permissionRequestData = {};
|
||||
}
|
||||
|
||||
const { ability } = usePermissionFetcher(permissionRequestData);
|
||||
|
||||
const onProcessInstanceRun = (processInstance: any) => {
|
||||
// FIXME: ensure that the task is actually for the current user as well
|
||||
const processInstanceId = (processInstance as any).id;
|
||||
|
@ -38,8 +106,9 @@ export default function ProcessInstanceRun({
|
|||
|
||||
const processModelRun = (processInstance: any) => {
|
||||
setErrorMessage(null);
|
||||
storeRecentProcessModelInLocalStorage(processModel);
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-instances/${processInstance.id}/run`,
|
||||
path: `/process-instances/${modifiedProcessModelId}/${processInstance.id}/run`,
|
||||
successCallback: onProcessInstanceRun,
|
||||
failureCallback: setErrorMessage,
|
||||
httpMethod: 'POST',
|
||||
|
@ -48,19 +117,23 @@ export default function ProcessInstanceRun({
|
|||
|
||||
const processInstanceCreateAndRun = () => {
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-models/${modifiedProcessModelId}/process-instances`,
|
||||
path: processInstanceActionPath,
|
||||
successCallback: processModelRun,
|
||||
httpMethod: 'POST',
|
||||
});
|
||||
};
|
||||
|
||||
if (checkPermissions) {
|
||||
return (
|
||||
<Can I="POST" a={processInstanceActionPath} ability={ability}>
|
||||
<Button onClick={processInstanceCreateAndRun} className={className}>
|
||||
Start
|
||||
</Button>
|
||||
</Can>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Button
|
||||
onClick={processInstanceCreateAndRun}
|
||||
variant="primary"
|
||||
className={className}
|
||||
>
|
||||
Run
|
||||
<Button onClick={processInstanceCreateAndRun} className={className}>
|
||||
Start
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -51,12 +51,15 @@ export default function ProcessModelForm({
|
|||
if (hasErrors) {
|
||||
return;
|
||||
}
|
||||
const path = `/process-models/${modifyProcessIdentifierForPathParam(
|
||||
let path = `/process-models/${modifyProcessIdentifierForPathParam(
|
||||
processGroupId || ''
|
||||
)}`;
|
||||
let httpMethod = 'POST';
|
||||
if (mode === 'edit') {
|
||||
httpMethod = 'PUT';
|
||||
path = `/process-models/${modifyProcessIdentifierForPathParam(
|
||||
processModel.id
|
||||
)}`;
|
||||
}
|
||||
const postBody = {
|
||||
display_name: processModel.display_name,
|
||||
|
|
|
@ -15,11 +15,13 @@ import ProcessInstanceRun from './ProcessInstanceRun';
|
|||
type OwnProps = {
|
||||
headerElement?: ReactElement;
|
||||
processGroup?: ProcessGroup;
|
||||
checkPermissions?: boolean;
|
||||
};
|
||||
|
||||
export default function ProcessModelListTiles({
|
||||
headerElement,
|
||||
processGroup,
|
||||
checkPermissions = true,
|
||||
}: OwnProps) {
|
||||
const [searchParams] = useSearchParams();
|
||||
const [processModels, setProcessModels] = useState<ProcessModel[] | null>(
|
||||
|
@ -33,9 +35,11 @@ export default function ProcessModelListTiles({
|
|||
setProcessModels(result.results);
|
||||
};
|
||||
// only allow 10 for now until we get the backend only returning certain models for user execution
|
||||
let queryParams = '?per_page=100';
|
||||
let queryParams = '?per_page=20';
|
||||
if (processGroup) {
|
||||
queryParams = `${queryParams}&process_group_identifier=${processGroup.id}`;
|
||||
} else {
|
||||
queryParams = `${queryParams}&recursive=true&filter_runnable_by_user=true`;
|
||||
}
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-models${queryParams}`,
|
||||
|
@ -73,12 +77,19 @@ export default function ProcessModelListTiles({
|
|||
<Tile
|
||||
id={`process-model-tile-${row.id}`}
|
||||
className="tile-process-group"
|
||||
href={`/admin/process-models/${modifyProcessIdentifierForPathParam(
|
||||
row.id
|
||||
)}`}
|
||||
>
|
||||
<div className="tile-process-group-content-container">
|
||||
<div className="tile-title-top">{row.display_name}</div>
|
||||
<div className="tile-title-top">
|
||||
<a
|
||||
title={row.id}
|
||||
data-qa="process-model-show-link"
|
||||
href={`/admin/process-models/${modifyProcessIdentifierForPathParam(
|
||||
row.id
|
||||
)}`}
|
||||
>
|
||||
{row.display_name}
|
||||
</a>
|
||||
</div>
|
||||
<p className="tile-description">
|
||||
{truncateString(row.description || '', 100)}
|
||||
</p>
|
||||
|
@ -86,6 +97,7 @@ export default function ProcessModelListTiles({
|
|||
processModel={row}
|
||||
onSuccessCallback={setProcessInstance}
|
||||
className="tile-pin-bottom"
|
||||
checkPermissions={checkPermissions}
|
||||
/>
|
||||
</div>
|
||||
</Tile>
|
||||
|
|
|
@ -569,7 +569,7 @@ export default function ReactDiagramEditor({
|
|||
a={targetUris.processModelFileShowPath}
|
||||
ability={ability}
|
||||
>
|
||||
<Button onClick={downloadXmlFile}>Download xml</Button>
|
||||
<Button onClick={downloadXmlFile}>Download</Button>
|
||||
</Can>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
// @ts-ignore
|
||||
import { TimeAgo } from '../helpers/timeago';
|
||||
import { convertSecondsToFormattedDateTime } from '../helpers';
|
||||
|
||||
type OwnProps = {
|
||||
timeInSeconds: number;
|
||||
};
|
||||
|
||||
export default function TableCellWithTimeAgoInWords({
|
||||
timeInSeconds,
|
||||
}: OwnProps) {
|
||||
return (
|
||||
<td title={convertSecondsToFormattedDateTime(timeInSeconds) || '-'}>
|
||||
{timeInSeconds ? TimeAgo.inWords(timeInSeconds) : '-'}
|
||||
</td>
|
||||
);
|
||||
}
|
|
@ -7,12 +7,16 @@ import {
|
|||
convertSecondsToFormattedDateTime,
|
||||
getPageInfoFromSearchParams,
|
||||
modifyProcessIdentifierForPathParam,
|
||||
refreshAtInterval,
|
||||
} from '../helpers';
|
||||
import HttpService from '../services/HttpService';
|
||||
import { PaginationObject } from '../interfaces';
|
||||
import TableCellWithTimeAgoInWords from './TableCellWithTimeAgoInWords';
|
||||
|
||||
const PER_PAGE_FOR_TASKS_ON_HOME_PAGE = 5;
|
||||
const paginationQueryParamPrefix = 'tasks_for_my_open_processes';
|
||||
const REFRESH_INTERVAL = 5;
|
||||
const REFRESH_TIMEOUT = 600;
|
||||
|
||||
export default function MyOpenProcesses() {
|
||||
const [searchParams] = useSearchParams();
|
||||
|
@ -20,20 +24,24 @@ export default function MyOpenProcesses() {
|
|||
const [pagination, setPagination] = useState<PaginationObject | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const { page, perPage } = getPageInfoFromSearchParams(
|
||||
searchParams,
|
||||
PER_PAGE_FOR_TASKS_ON_HOME_PAGE,
|
||||
undefined,
|
||||
paginationQueryParamPrefix
|
||||
);
|
||||
const setTasksFromResult = (result: any) => {
|
||||
setTasks(result.results);
|
||||
setPagination(result.pagination);
|
||||
const getTasks = () => {
|
||||
const { page, perPage } = getPageInfoFromSearchParams(
|
||||
searchParams,
|
||||
PER_PAGE_FOR_TASKS_ON_HOME_PAGE,
|
||||
undefined,
|
||||
paginationQueryParamPrefix
|
||||
);
|
||||
const setTasksFromResult = (result: any) => {
|
||||
setTasks(result.results);
|
||||
setPagination(result.pagination);
|
||||
};
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/tasks/for-my-open-processes?per_page=${perPage}&page=${page}`,
|
||||
successCallback: setTasksFromResult,
|
||||
});
|
||||
};
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/tasks/for-my-open-processes?per_page=${perPage}&page=${page}`,
|
||||
successCallback: setTasksFromResult,
|
||||
});
|
||||
getTasks();
|
||||
refreshAtInterval(REFRESH_INTERVAL, REFRESH_TIMEOUT, getTasks);
|
||||
}, [searchParams]);
|
||||
|
||||
const buildTable = () => {
|
||||
|
@ -46,18 +54,20 @@ export default function MyOpenProcesses() {
|
|||
<tr key={rowToUse.id}>
|
||||
<td>
|
||||
<Link
|
||||
data-qa="process-model-show-link"
|
||||
to={`/admin/process-models/${modifiedProcessModelIdentifier}`}
|
||||
data-qa="process-instance-show-link"
|
||||
to={`/admin/process-models/${modifiedProcessModelIdentifier}/process-instances/${rowToUse.process_instance_id}`}
|
||||
title={`View process instance ${rowToUse.process_instance_id}`}
|
||||
>
|
||||
{rowToUse.process_model_display_name}
|
||||
{rowToUse.process_instance_id}
|
||||
</Link>
|
||||
</td>
|
||||
<td>
|
||||
<Link
|
||||
data-qa="process-instance-show-link"
|
||||
to={`/admin/process-models/${modifiedProcessModelIdentifier}/process-instances/${rowToUse.process_instance_id}`}
|
||||
data-qa="process-model-show-link"
|
||||
to={`/admin/process-models/${modifiedProcessModelIdentifier}`}
|
||||
title={rowToUse.process_model_identifier}
|
||||
>
|
||||
View {rowToUse.process_instance_id}
|
||||
{rowToUse.process_model_display_name}
|
||||
</Link>
|
||||
</td>
|
||||
<td
|
||||
|
@ -65,18 +75,15 @@ export default function MyOpenProcesses() {
|
|||
>
|
||||
{rowToUse.task_title}
|
||||
</td>
|
||||
<td>{rowToUse.process_instance_status}</td>
|
||||
<td>{rowToUse.group_identifier || '-'}</td>
|
||||
<td>
|
||||
{convertSecondsToFormattedDateTime(
|
||||
rowToUse.created_at_in_seconds
|
||||
) || '-'}
|
||||
</td>
|
||||
<td>
|
||||
{convertSecondsToFormattedDateTime(
|
||||
rowToUse.updated_at_in_seconds
|
||||
) || '-'}
|
||||
</td>
|
||||
<TableCellWithTimeAgoInWords
|
||||
timeInSeconds={rowToUse.updated_at_in_seconds}
|
||||
/>
|
||||
<td>
|
||||
<Button
|
||||
variant="primary"
|
||||
|
@ -94,13 +101,12 @@ export default function MyOpenProcesses() {
|
|||
<Table striped bordered>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Process Model</th>
|
||||
<th>Process Instance</th>
|
||||
<th>Task Name</th>
|
||||
<th>Process Instance Status</th>
|
||||
<th>Assigned Group</th>
|
||||
<th>Process Started</th>
|
||||
<th>Process Updated</th>
|
||||
<th>Id</th>
|
||||
<th>Process</th>
|
||||
<th>Task</th>
|
||||
<th>Waiting For</th>
|
||||
<th>Date Started</th>
|
||||
<th>Last Updated</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -111,7 +117,11 @@ export default function MyOpenProcesses() {
|
|||
|
||||
const tasksComponent = () => {
|
||||
if (pagination && pagination.total < 1) {
|
||||
return null;
|
||||
return (
|
||||
<p className="no-results-message with-large-bottom-margin">
|
||||
There are no tasks for processes you started at this time.
|
||||
</p>
|
||||
);
|
||||
}
|
||||
const { page, perPage } = getPageInfoFromSearchParams(
|
||||
searchParams,
|
||||
|
@ -120,22 +130,27 @@ export default function MyOpenProcesses() {
|
|||
paginationQueryParamPrefix
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<h1>Tasks for my open processes</h1>
|
||||
<PaginationForTable
|
||||
page={page}
|
||||
perPage={perPage}
|
||||
perPageOptions={[2, PER_PAGE_FOR_TASKS_ON_HOME_PAGE, 25]}
|
||||
pagination={pagination}
|
||||
tableToDisplay={buildTable()}
|
||||
paginationQueryParamPrefix={paginationQueryParamPrefix}
|
||||
/>
|
||||
</>
|
||||
<PaginationForTable
|
||||
page={page}
|
||||
perPage={perPage}
|
||||
perPageOptions={[2, PER_PAGE_FOR_TASKS_ON_HOME_PAGE, 25]}
|
||||
pagination={pagination}
|
||||
tableToDisplay={buildTable()}
|
||||
paginationQueryParamPrefix={paginationQueryParamPrefix}
|
||||
paginationClassName="with-large-bottom-margin"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
if (pagination) {
|
||||
return tasksComponent();
|
||||
}
|
||||
return null;
|
||||
return (
|
||||
<>
|
||||
<h2>My open instances</h2>
|
||||
<p className="data-table-description">
|
||||
These tasks are for processes you started which are not complete. You
|
||||
may not have an action to take at this time. See below for tasks waiting
|
||||
on you.
|
||||
</p>
|
||||
{tasksComponent()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
} from '../helpers';
|
||||
import HttpService from '../services/HttpService';
|
||||
import { PaginationObject } from '../interfaces';
|
||||
import TableCellWithTimeAgoInWords from './TableCellWithTimeAgoInWords';
|
||||
|
||||
const PER_PAGE_FOR_TASKS_ON_HOME_PAGE = 5;
|
||||
|
||||
|
@ -45,18 +46,20 @@ export default function TasksWaitingForMe() {
|
|||
<tr key={rowToUse.id}>
|
||||
<td>
|
||||
<Link
|
||||
data-qa="process-model-show-link"
|
||||
to={`/admin/process-models/${modifiedProcessModelIdentifier}`}
|
||||
data-qa="process-instance-show-link"
|
||||
to={`/admin/process-models/${modifiedProcessModelIdentifier}/process-instances/${rowToUse.process_instance_id}`}
|
||||
title={`View process instance ${rowToUse.process_instance_id}`}
|
||||
>
|
||||
{rowToUse.process_model_display_name}
|
||||
{rowToUse.process_instance_id}
|
||||
</Link>
|
||||
</td>
|
||||
<td>
|
||||
<Link
|
||||
data-qa="process-instance-show-link"
|
||||
to={`/admin/process-models/${modifiedProcessModelIdentifier}/process-instances/${rowToUse.process_instance_id}`}
|
||||
data-qa="process-model-show-link"
|
||||
to={`/admin/process-models/${modifiedProcessModelIdentifier}`}
|
||||
title={rowToUse.process_model_identifier}
|
||||
>
|
||||
View {rowToUse.process_instance_id}
|
||||
{rowToUse.process_model_display_name}
|
||||
</Link>
|
||||
</td>
|
||||
<td
|
||||
|
@ -65,18 +68,15 @@ export default function TasksWaitingForMe() {
|
|||
{rowToUse.task_title}
|
||||
</td>
|
||||
<td>{rowToUse.username}</td>
|
||||
<td>{rowToUse.process_instance_status}</td>
|
||||
<td>{rowToUse.group_identifier || '-'}</td>
|
||||
<td>
|
||||
{convertSecondsToFormattedDateTime(
|
||||
rowToUse.created_at_in_seconds
|
||||
) || '-'}
|
||||
</td>
|
||||
<td>
|
||||
{convertSecondsToFormattedDateTime(
|
||||
rowToUse.updated_at_in_seconds
|
||||
) || '-'}
|
||||
</td>
|
||||
<TableCellWithTimeAgoInWords
|
||||
timeInSeconds={rowToUse.updated_at_in_seconds}
|
||||
/>
|
||||
<td>
|
||||
<Button
|
||||
variant="primary"
|
||||
|
@ -94,14 +94,13 @@ export default function TasksWaitingForMe() {
|
|||
<Table striped bordered>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Process Model</th>
|
||||
<th>Process Instance</th>
|
||||
<th>Task Name</th>
|
||||
<th>Process Started By</th>
|
||||
<th>Process Instance Status</th>
|
||||
<th>Assigned Group</th>
|
||||
<th>Process Started</th>
|
||||
<th>Process Updated</th>
|
||||
<th>Id</th>
|
||||
<th>Process</th>
|
||||
<th>Task</th>
|
||||
<th>Started By</th>
|
||||
<th>Waiting For</th>
|
||||
<th>Date Started</th>
|
||||
<th>Last Updated</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -112,7 +111,11 @@ export default function TasksWaitingForMe() {
|
|||
|
||||
const tasksComponent = () => {
|
||||
if (pagination && pagination.total < 1) {
|
||||
return null;
|
||||
return (
|
||||
<p className="no-results-message with-large-bottom-margin">
|
||||
You have no task assignments at this time.
|
||||
</p>
|
||||
);
|
||||
}
|
||||
const { page, perPage } = getPageInfoFromSearchParams(
|
||||
searchParams,
|
||||
|
@ -121,22 +124,26 @@ export default function TasksWaitingForMe() {
|
|||
'tasks_waiting_for_me'
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<h1>Tasks waiting for me</h1>
|
||||
<PaginationForTable
|
||||
page={page}
|
||||
perPage={perPage}
|
||||
perPageOptions={[2, PER_PAGE_FOR_TASKS_ON_HOME_PAGE, 25]}
|
||||
pagination={pagination}
|
||||
tableToDisplay={buildTable()}
|
||||
paginationQueryParamPrefix="tasks_waiting_for_me"
|
||||
/>
|
||||
</>
|
||||
<PaginationForTable
|
||||
page={page}
|
||||
perPage={perPage}
|
||||
perPageOptions={[2, PER_PAGE_FOR_TASKS_ON_HOME_PAGE, 25]}
|
||||
pagination={pagination}
|
||||
tableToDisplay={buildTable()}
|
||||
paginationQueryParamPrefix="tasks_waiting_for_me"
|
||||
paginationClassName="with-large-bottom-margin"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
if (pagination) {
|
||||
return tasksComponent();
|
||||
}
|
||||
return null;
|
||||
return (
|
||||
<>
|
||||
<h2>Tasks waiting for me</h2>
|
||||
<p className="data-table-description">
|
||||
These processes are waiting on you to complete the next task. All are
|
||||
processes created by others that are now actionable by you.
|
||||
</p>
|
||||
{tasksComponent()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -7,33 +7,41 @@ import {
|
|||
convertSecondsToFormattedDateTime,
|
||||
getPageInfoFromSearchParams,
|
||||
modifyProcessIdentifierForPathParam,
|
||||
refreshAtInterval,
|
||||
} from '../helpers';
|
||||
import HttpService from '../services/HttpService';
|
||||
import { PaginationObject } from '../interfaces';
|
||||
import TableCellWithTimeAgoInWords from './TableCellWithTimeAgoInWords';
|
||||
|
||||
const PER_PAGE_FOR_TASKS_ON_HOME_PAGE = 5;
|
||||
const paginationQueryParamPrefix = 'tasks_waiting_for_my_groups';
|
||||
const REFRESH_INTERVAL = 5;
|
||||
const REFRESH_TIMEOUT = 600;
|
||||
|
||||
export default function TasksForWaitingForMyGroups() {
|
||||
export default function TasksWaitingForMyGroups() {
|
||||
const [searchParams] = useSearchParams();
|
||||
const [tasks, setTasks] = useState([]);
|
||||
const [pagination, setPagination] = useState<PaginationObject | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const { page, perPage } = getPageInfoFromSearchParams(
|
||||
searchParams,
|
||||
PER_PAGE_FOR_TASKS_ON_HOME_PAGE,
|
||||
undefined,
|
||||
paginationQueryParamPrefix
|
||||
);
|
||||
const setTasksFromResult = (result: any) => {
|
||||
setTasks(result.results);
|
||||
setPagination(result.pagination);
|
||||
const getTasks = () => {
|
||||
const { page, perPage } = getPageInfoFromSearchParams(
|
||||
searchParams,
|
||||
PER_PAGE_FOR_TASKS_ON_HOME_PAGE,
|
||||
undefined,
|
||||
paginationQueryParamPrefix
|
||||
);
|
||||
const setTasksFromResult = (result: any) => {
|
||||
setTasks(result.results);
|
||||
setPagination(result.pagination);
|
||||
};
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/tasks/for-my-groups?per_page=${perPage}&page=${page}`,
|
||||
successCallback: setTasksFromResult,
|
||||
});
|
||||
};
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/tasks/for-my-groups?per_page=${perPage}&page=${page}`,
|
||||
successCallback: setTasksFromResult,
|
||||
});
|
||||
getTasks();
|
||||
refreshAtInterval(REFRESH_INTERVAL, REFRESH_TIMEOUT, getTasks);
|
||||
}, [searchParams]);
|
||||
|
||||
const buildTable = () => {
|
||||
|
@ -46,18 +54,20 @@ export default function TasksForWaitingForMyGroups() {
|
|||
<tr key={rowToUse.id}>
|
||||
<td>
|
||||
<Link
|
||||
data-qa="process-model-show-link"
|
||||
to={`/admin/process-models/${modifiedProcessModelIdentifier}`}
|
||||
data-qa="process-instance-show-link"
|
||||
to={`/admin/process-models/${modifiedProcessModelIdentifier}/process-instances/${rowToUse.process_instance_id}`}
|
||||
title={`View process instance ${rowToUse.process_instance_id}`}
|
||||
>
|
||||
{rowToUse.process_model_display_name}
|
||||
{rowToUse.process_instance_id}
|
||||
</Link>
|
||||
</td>
|
||||
<td>
|
||||
<Link
|
||||
data-qa="process-instance-show-link"
|
||||
to={`/admin/process-models/${modifiedProcessModelIdentifier}/process-instances/${rowToUse.process_instance_id}`}
|
||||
data-qa="process-model-show-link"
|
||||
to={`/admin/process-models/${modifiedProcessModelIdentifier}`}
|
||||
title={rowToUse.process_model_identifier}
|
||||
>
|
||||
View {rowToUse.process_instance_id}
|
||||
{rowToUse.process_model_display_name}
|
||||
</Link>
|
||||
</td>
|
||||
<td
|
||||
|
@ -66,18 +76,15 @@ export default function TasksForWaitingForMyGroups() {
|
|||
{rowToUse.task_title}
|
||||
</td>
|
||||
<td>{rowToUse.username}</td>
|
||||
<td>{rowToUse.process_instance_status}</td>
|
||||
<td>{rowToUse.group_identifier || '-'}</td>
|
||||
<td>
|
||||
{convertSecondsToFormattedDateTime(
|
||||
rowToUse.created_at_in_seconds
|
||||
) || '-'}
|
||||
</td>
|
||||
<td>
|
||||
{convertSecondsToFormattedDateTime(
|
||||
rowToUse.updated_at_in_seconds
|
||||
) || '-'}
|
||||
</td>
|
||||
<TableCellWithTimeAgoInWords
|
||||
timeInSeconds={rowToUse.updated_at_in_seconds}
|
||||
/>
|
||||
<td>
|
||||
<Button
|
||||
variant="primary"
|
||||
|
@ -95,14 +102,13 @@ export default function TasksForWaitingForMyGroups() {
|
|||
<Table striped bordered>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Process Model</th>
|
||||
<th>Process Instance</th>
|
||||
<th>Task Name</th>
|
||||
<th>Process Started By</th>
|
||||
<th>Process Instance Status</th>
|
||||
<th>Assigned Group</th>
|
||||
<th>Process Started</th>
|
||||
<th>Process Updated</th>
|
||||
<th>Id</th>
|
||||
<th>Process</th>
|
||||
<th>Task</th>
|
||||
<th>Started By</th>
|
||||
<th>Waiting For</th>
|
||||
<th>Date Started</th>
|
||||
<th>Last Updated</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -113,7 +119,11 @@ export default function TasksForWaitingForMyGroups() {
|
|||
|
||||
const tasksComponent = () => {
|
||||
if (pagination && pagination.total < 1) {
|
||||
return null;
|
||||
return (
|
||||
<p className="no-results-message">
|
||||
Your groups have no task assignments at this time.
|
||||
</p>
|
||||
);
|
||||
}
|
||||
const { page, perPage } = getPageInfoFromSearchParams(
|
||||
searchParams,
|
||||
|
@ -122,22 +132,25 @@ export default function TasksForWaitingForMyGroups() {
|
|||
paginationQueryParamPrefix
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<h1>Tasks waiting for my groups</h1>
|
||||
<PaginationForTable
|
||||
page={page}
|
||||
perPage={perPage}
|
||||
perPageOptions={[2, PER_PAGE_FOR_TASKS_ON_HOME_PAGE, 25]}
|
||||
pagination={pagination}
|
||||
tableToDisplay={buildTable()}
|
||||
paginationQueryParamPrefix={paginationQueryParamPrefix}
|
||||
/>
|
||||
</>
|
||||
<PaginationForTable
|
||||
page={page}
|
||||
perPage={perPage}
|
||||
perPageOptions={[2, PER_PAGE_FOR_TASKS_ON_HOME_PAGE, 25]}
|
||||
pagination={pagination}
|
||||
tableToDisplay={buildTable()}
|
||||
paginationQueryParamPrefix={paginationQueryParamPrefix}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
if (pagination) {
|
||||
return tasksComponent();
|
||||
}
|
||||
return null;
|
||||
return (
|
||||
<>
|
||||
<h2>Tasks waiting for my groups</h2>
|
||||
<p className="data-table-description">
|
||||
This is a list of tasks for groups you belong to that can be completed
|
||||
by any member of the group.
|
||||
</p>
|
||||
{tasksComponent()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
/* eslint-disable no-restricted-syntax */
|
||||
// https://gist.github.com/caiotarifa/30ae974f2293c761f3139dd194abd9e5
|
||||
export const TimeAgo = (function awesomeFunc() {
|
||||
const self = {};
|
||||
|
||||
// Public Methods
|
||||
self.locales = {
|
||||
prefix: '',
|
||||
sufix: 'ago',
|
||||
|
||||
seconds: 'less than a minute',
|
||||
minute: 'about a minute',
|
||||
minutes: '%d minutes',
|
||||
hour: 'about an hour',
|
||||
hours: 'about %d hours',
|
||||
day: 'a day',
|
||||
days: '%d days',
|
||||
month: 'about a month',
|
||||
months: '%d months',
|
||||
year: 'about a year',
|
||||
years: '%d years',
|
||||
};
|
||||
|
||||
self.inWords = function inWords(timeAgo) {
|
||||
const milliseconds = timeAgo * 1000;
|
||||
const seconds = Math.floor(
|
||||
(new Date() - parseInt(milliseconds, 10)) / 1000
|
||||
);
|
||||
const separator = this.locales.separator || ' ';
|
||||
let words = this.locales.prefix + separator;
|
||||
let interval = 0;
|
||||
const intervals = {
|
||||
year: seconds / 31536000,
|
||||
month: seconds / 2592000,
|
||||
day: seconds / 86400,
|
||||
hour: seconds / 3600,
|
||||
minute: seconds / 60,
|
||||
};
|
||||
|
||||
let distance = this.locales.seconds;
|
||||
|
||||
// eslint-disable-next-line guard-for-in
|
||||
for (const key in intervals) {
|
||||
interval = Math.floor(intervals[key]);
|
||||
|
||||
if (interval > 1) {
|
||||
distance = this.locales[`${key}s`];
|
||||
break;
|
||||
} else if (interval === 1) {
|
||||
distance = this.locales[key];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
distance = distance.replace(/%d/i, interval);
|
||||
words += distance + separator + this.locales.sufix;
|
||||
|
||||
return words.trim();
|
||||
};
|
||||
|
||||
return self;
|
||||
})();
|
|
@ -1,7 +1,7 @@
|
|||
// We may need to update usage of Ability when we update.
|
||||
// They say they are going to rename PureAbility to Ability and remove the old class.
|
||||
import { AbilityBuilder, Ability } from '@casl/ability';
|
||||
import { useContext, useEffect } from 'react';
|
||||
import { useContext, useEffect, useState } from 'react';
|
||||
import { AbilityContext } from '../contexts/Can';
|
||||
import { PermissionCheckResponseBody, PermissionsToCheck } from '../interfaces';
|
||||
import HttpService from '../services/HttpService';
|
||||
|
@ -10,6 +10,7 @@ export const usePermissionFetcher = (
|
|||
permissionsToCheck: PermissionsToCheck
|
||||
) => {
|
||||
const ability = useContext(AbilityContext);
|
||||
const [permissionsLoaded, setPermissionsLoaded] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
const processPermissionResult = (result: PermissionCheckResponseBody) => {
|
||||
|
@ -34,15 +35,17 @@ export const usePermissionFetcher = (
|
|||
}
|
||||
});
|
||||
ability.update(rules);
|
||||
setPermissionsLoaded(true);
|
||||
};
|
||||
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/permissions-check`,
|
||||
httpMethod: 'POST',
|
||||
successCallback: processPermissionResult,
|
||||
postBody: { requests_to_check: permissionsToCheck },
|
||||
});
|
||||
if (Object.keys(permissionsToCheck).length !== 0) {
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/permissions-check`,
|
||||
httpMethod: 'POST',
|
||||
successCallback: processPermissionResult,
|
||||
postBody: { requests_to_check: permissionsToCheck },
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return { ability };
|
||||
return { ability, permissionsLoaded };
|
||||
};
|
||||
|
|
|
@ -1,20 +1,25 @@
|
|||
import { useMemo } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
export const useUriListForPermissions = () => {
|
||||
const params = useParams();
|
||||
const targetUris = {
|
||||
authenticationListPath: `/v1.0/authentications`,
|
||||
messageInstanceListPath: '/v1.0/messages',
|
||||
processGroupListPath: '/v1.0/process-groups',
|
||||
processGroupShowPath: `/v1.0/process-groups/${params.process_group_id}`,
|
||||
processInstanceActionPath: `/v1.0/process-models/${params.process_model_id}/process-instances`,
|
||||
processInstanceListPath: '/v1.0/process-instances',
|
||||
processModelCreatePath: `/v1.0/process-models/${params.process_group_id}`,
|
||||
processModelFileCreatePath: `/v1.0/process-models/${params.process_model_id}/files`,
|
||||
processModelFileShowPath: `/v1.0/process-models/${params.process_model_id}/files/${params.file_name}`,
|
||||
processModelShowPath: `/v1.0/process-models/${params.process_model_id}`,
|
||||
secretListPath: `/v1.0/secrets`,
|
||||
};
|
||||
const targetUris = useMemo(() => {
|
||||
return {
|
||||
authenticationListPath: `/v1.0/authentications`,
|
||||
messageInstanceListPath: '/v1.0/messages',
|
||||
processGroupListPath: '/v1.0/process-groups',
|
||||
processGroupShowPath: `/v1.0/process-groups/${params.process_group_id}`,
|
||||
processInstanceActionPath: `/v1.0/process-models/${params.process_model_id}/process-instances`,
|
||||
processInstanceListPath: '/v1.0/process-instances',
|
||||
processInstanceTaskListPath: `/v1.0/process-instances/${params.process_model_id}/${params.process_instance_id}/tasks`,
|
||||
processInstanceReportListPath: '/v1.0/process-instances/reports',
|
||||
processModelCreatePath: `/v1.0/process-models/${params.process_group_id}`,
|
||||
processModelFileCreatePath: `/v1.0/process-models/${params.process_model_id}/files`,
|
||||
processModelFileShowPath: `/v1.0/process-models/${params.process_model_id}/files/${params.file_name}`,
|
||||
processModelShowPath: `/v1.0/process-models/${params.process_model_id}`,
|
||||
secretListPath: `/v1.0/secrets`,
|
||||
};
|
||||
}, [params]);
|
||||
|
||||
return { targetUris };
|
||||
};
|
||||
|
|
|
@ -5,6 +5,10 @@
|
|||
color: white;
|
||||
}
|
||||
|
||||
.megacondensed {
|
||||
padding-left: 0px;
|
||||
}
|
||||
|
||||
/* defaults to 3rem, which isn't long sufficient for "elizabeth" */
|
||||
.cds--header__action.username-header-text {
|
||||
width: 5rem;
|
||||
|
@ -143,6 +147,14 @@ h1.with-icons {
|
|||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.with-top-margin {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.with-large-bottom-margin {
|
||||
margin-bottom: 3em;
|
||||
}
|
||||
|
||||
.diagram-viewer-canvas {
|
||||
border:1px solid #000000;
|
||||
height:70vh;
|
||||
|
@ -248,3 +260,40 @@ in on this with the react-jsonschema-form repo. This is just a patch fix to allo
|
|||
position: absolute;
|
||||
bottom: 1em;
|
||||
}
|
||||
|
||||
.cds--tabs .cds--tabs__nav-link {
|
||||
max-width: 20rem;
|
||||
}
|
||||
|
||||
.clear-left {
|
||||
clear: left;
|
||||
}
|
||||
|
||||
td.actions-cell {
|
||||
width: 1em;
|
||||
}
|
||||
|
||||
.no-results-message {
|
||||
font-style: italic;
|
||||
margin-left: 2em;
|
||||
margin-top: 1em;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.data-table-description {
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
letter-spacing: 0.16px;
|
||||
color: #525252;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
/* top and bottom margin since this is sort of the middle of three sections on the process model show page */
|
||||
.process-model-files-section {
|
||||
margin: 2em 0;
|
||||
}
|
||||
|
||||
.filterIcon {
|
||||
text-align: right;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
|
|
@ -46,12 +46,18 @@ export interface ProcessInstanceReport {
|
|||
display_name: string;
|
||||
}
|
||||
|
||||
export interface ProcessGroupLite {
|
||||
id: string;
|
||||
display_name: string;
|
||||
}
|
||||
|
||||
export interface ProcessModel {
|
||||
id: string;
|
||||
description: string;
|
||||
display_name: string;
|
||||
primary_file_name: string;
|
||||
files: ProcessFile[];
|
||||
parent_groups?: ProcessGroupLite[];
|
||||
}
|
||||
|
||||
export interface ProcessGroup {
|
||||
|
@ -60,10 +66,19 @@ export interface ProcessGroup {
|
|||
description?: string | null;
|
||||
process_models?: ProcessModel[];
|
||||
process_groups?: ProcessGroup[];
|
||||
parent_groups?: ProcessGroupLite[];
|
||||
}
|
||||
|
||||
export interface HotCrumbItemObject {
|
||||
entityToExplode: ProcessModel | ProcessGroup | string;
|
||||
entityType: string;
|
||||
linkLastItem?: boolean;
|
||||
}
|
||||
|
||||
export type HotCrumbItemArray = [displayValue: string, url?: string];
|
||||
|
||||
// tuple of display value and URL
|
||||
export type HotCrumbItem = [displayValue: string, url?: string];
|
||||
export type HotCrumbItem = HotCrumbItemArray | HotCrumbItemObject;
|
||||
|
||||
export interface ErrorForDisplay {
|
||||
message: string;
|
||||
|
|
|
@ -54,7 +54,7 @@ export default function AuthenticationList() {
|
|||
<Table striped bordered>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Id</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>{rows}</tbody>
|
||||
|
|
|
@ -1,5 +1,48 @@
|
|||
import MyCompletedInstances from '../components/MyCompletedInstances';
|
||||
import ProcessInstanceListTable from '../components/ProcessInstanceListTable';
|
||||
|
||||
export default function CompletedInstances() {
|
||||
return <MyCompletedInstances />;
|
||||
return (
|
||||
<>
|
||||
<h2>My completed instances</h2>
|
||||
<p className="data-table-description">
|
||||
This is a list of instances you started that are now complete.
|
||||
</p>
|
||||
<ProcessInstanceListTable
|
||||
filtersEnabled={false}
|
||||
paginationQueryParamPrefix="my_completed_instances"
|
||||
perPageOptions={[2, 5, 25]}
|
||||
reportIdentifier="system_report_instances_initiated_by_me"
|
||||
showReports={false}
|
||||
textToShowIfEmpty="You have no completed instances at this time."
|
||||
paginationClassName="with-large-bottom-margin"
|
||||
autoReload
|
||||
/>
|
||||
<h2>Tasks completed by me</h2>
|
||||
<p className="data-table-description">
|
||||
This is a list of instances where you have completed tasks.
|
||||
</p>
|
||||
<ProcessInstanceListTable
|
||||
filtersEnabled={false}
|
||||
paginationQueryParamPrefix="my_completed_tasks"
|
||||
perPageOptions={[2, 5, 25]}
|
||||
reportIdentifier="system_report_instances_with_tasks_completed_by_me"
|
||||
showReports={false}
|
||||
textToShowIfEmpty="You have no completed tasks at this time."
|
||||
paginationClassName="with-large-bottom-margin"
|
||||
/>
|
||||
<h2>Tasks completed by my groups</h2>
|
||||
<p className="data-table-description">
|
||||
This is a list of instances with tasks that were completed by groups you
|
||||
belong to.
|
||||
</p>
|
||||
<ProcessInstanceListTable
|
||||
filtersEnabled={false}
|
||||
paginationQueryParamPrefix="group_completed_tasks"
|
||||
perPageOptions={[2, 5, 25]}
|
||||
reportIdentifier="system_report_instances_with_tasks_completed_by_my_groups"
|
||||
showReports={false}
|
||||
textToShowIfEmpty="Your group has no completed tasks at this time."
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -3,7 +3,8 @@ import ProcessModelListTiles from '../components/ProcessModelListTiles';
|
|||
export default function CreateNewInstance() {
|
||||
return (
|
||||
<ProcessModelListTiles
|
||||
headerElement={<h1>Process models available to you</h1>}
|
||||
headerElement={<h2>Processes I can start</h2>}
|
||||
checkPermissions={false}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
import TasksForMyOpenProcesses from '../components/TasksForMyOpenProcesses';
|
||||
import TasksWaitingForMe from '../components/TasksWaitingForMe';
|
||||
import TasksForWaitingForMyGroups from '../components/TasksWaitingForMyGroups';
|
||||
import TasksWaitingForMyGroups from '../components/TasksWaitingForMyGroups';
|
||||
|
||||
export default function GroupedTasks() {
|
||||
return (
|
||||
<>
|
||||
{/* be careful moving these around since the first two have with-large-bottom-margin in order to get some space between the three table sections. */}
|
||||
{/* i wish Stack worked to add space just between top-level elements */}
|
||||
<TasksForMyOpenProcesses />
|
||||
<br />
|
||||
<TasksWaitingForMe />
|
||||
<br />
|
||||
<TasksForWaitingForMyGroups />
|
||||
<TasksWaitingForMyGroups />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -18,12 +18,10 @@ export default function HomePageRoutes() {
|
|||
useEffect(() => {
|
||||
setErrorMessage(null);
|
||||
let newSelectedTabIndex = 0;
|
||||
if (location.pathname.match(/^\/tasks\/grouped\b/)) {
|
||||
if (location.pathname.match(/^\/tasks\/completed-instances\b/)) {
|
||||
newSelectedTabIndex = 1;
|
||||
} else if (location.pathname.match(/^\/tasks\/completed-instances\b/)) {
|
||||
newSelectedTabIndex = 2;
|
||||
} else if (location.pathname.match(/^\/tasks\/create-new-instance\b/)) {
|
||||
newSelectedTabIndex = 3;
|
||||
newSelectedTabIndex = 2;
|
||||
}
|
||||
setSelectedTabIndex(newSelectedTabIndex);
|
||||
}, [location, setErrorMessage]);
|
||||
|
@ -36,13 +34,13 @@ export default function HomePageRoutes() {
|
|||
<>
|
||||
<Tabs selectedIndex={selectedTabIndex}>
|
||||
<TabList aria-label="List of tabs">
|
||||
<Tab onClick={() => navigate('/tasks/my-tasks')}>My Tasks</Tab>
|
||||
<Tab onClick={() => navigate('/tasks/grouped')}>Grouped Tasks</Tab>
|
||||
{/* <Tab onClick={() => navigate('/tasks/my-tasks')}>My Tasks</Tab> */}
|
||||
<Tab onClick={() => navigate('/tasks/grouped')}>In Progress</Tab>
|
||||
<Tab onClick={() => navigate('/tasks/completed-instances')}>
|
||||
Completed Instances
|
||||
Completed
|
||||
</Tab>
|
||||
<Tab onClick={() => navigate('/tasks/create-new-instance')}>
|
||||
Create New Instance +
|
||||
Start New +
|
||||
</Tab>
|
||||
</TabList>
|
||||
</Tabs>
|
||||
|
@ -55,7 +53,7 @@ export default function HomePageRoutes() {
|
|||
<>
|
||||
{renderTabs()}
|
||||
<Routes>
|
||||
<Route path="/" element={<MyTasks />} />
|
||||
<Route path="/" element={<GroupedTasks />} />
|
||||
<Route path="my-tasks" element={<MyTasks />} />
|
||||
<Route path=":process_instance_id/:task_id" element={<TaskShow />} />
|
||||
<Route path="grouped" element={<GroupedTasks />} />
|
||||
|
|
|
@ -8,7 +8,6 @@ import {
|
|||
convertSecondsToFormattedDateString,
|
||||
getPageInfoFromSearchParams,
|
||||
modifyProcessIdentifierForPathParam,
|
||||
unModifyProcessIdentifierForPathParam,
|
||||
} from '../helpers';
|
||||
import HttpService from '../services/HttpService';
|
||||
|
||||
|
@ -102,12 +101,11 @@ export default function MessageInstanceList() {
|
|||
<ProcessBreadcrumb
|
||||
hotCrumbs={[
|
||||
['Process Groups', '/admin'],
|
||||
[
|
||||
`Process Model: ${params.process_model_id}`,
|
||||
`process_model:${unModifyProcessIdentifierForPathParam(
|
||||
searchParams.get('process_model_id') || ''
|
||||
)}:link`,
|
||||
],
|
||||
{
|
||||
entityToExplode: searchParams.get('process_model_id') || '',
|
||||
entityType: 'process-model-id',
|
||||
linkLastItem: true,
|
||||
},
|
||||
[
|
||||
`Process Instance: ${searchParams.get('process_instance_id')}`,
|
||||
`/admin/process-models/${searchParams.get(
|
||||
|
|
|
@ -9,16 +9,24 @@ import {
|
|||
refreshAtInterval,
|
||||
} from '../helpers';
|
||||
import HttpService from '../services/HttpService';
|
||||
import { PaginationObject, RecentProcessModel } from '../interfaces';
|
||||
import {
|
||||
PaginationObject,
|
||||
ProcessInstance,
|
||||
ProcessModel,
|
||||
RecentProcessModel,
|
||||
} from '../interfaces';
|
||||
import ProcessInstanceRun from '../components/ProcessInstanceRun';
|
||||
|
||||
const PER_PAGE_FOR_TASKS_ON_HOME_PAGE = 5;
|
||||
const REFRESH_INTERVAL = 10;
|
||||
const REFRESH_INTERVAL = 5;
|
||||
const REFRESH_TIMEOUT = 600;
|
||||
|
||||
export default function MyTasks() {
|
||||
const [searchParams] = useSearchParams();
|
||||
const [tasks, setTasks] = useState([]);
|
||||
const [pagination, setPagination] = useState<PaginationObject | null>(null);
|
||||
const [processInstance, setProcessInstance] =
|
||||
useState<ProcessInstance | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const getTasks = () => {
|
||||
|
@ -40,6 +48,28 @@ export default function MyTasks() {
|
|||
refreshAtInterval(REFRESH_INTERVAL, REFRESH_TIMEOUT, getTasks);
|
||||
}, [searchParams]);
|
||||
|
||||
const processInstanceRunResultTag = () => {
|
||||
if (processInstance) {
|
||||
return (
|
||||
<div className="alert alert-success" role="alert">
|
||||
<p>
|
||||
Process Instance {processInstance.id} kicked off (
|
||||
<Link
|
||||
to={`/admin/process-models/${modifyProcessIdentifierForPathParam(
|
||||
processInstance.process_model_identifier
|
||||
)}/process-instances/${processInstance.id}`}
|
||||
data-qa="process-instance-show-link"
|
||||
>
|
||||
view
|
||||
</Link>
|
||||
).
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
let recentProcessModels: RecentProcessModel[] = [];
|
||||
const recentProcessModelsString = localStorage.getItem('recentProcessModels');
|
||||
if (recentProcessModelsString !== null) {
|
||||
|
@ -67,7 +97,7 @@ export default function MyTasks() {
|
|||
data-qa="process-instance-show-link"
|
||||
to={`/admin/process-models/${modifiedProcessModelIdentifier}/process-instances/${rowToUse.process_instance_id}`}
|
||||
>
|
||||
View {rowToUse.process_instance_id}
|
||||
{rowToUse.process_instance_id}
|
||||
</Link>
|
||||
</td>
|
||||
<td
|
||||
|
@ -107,33 +137,44 @@ export default function MyTasks() {
|
|||
};
|
||||
|
||||
const buildRecentProcessModelSection = () => {
|
||||
const rows = recentProcessModels.map((row) => {
|
||||
const rowToUse = row as any;
|
||||
const rows = recentProcessModels.map((row: RecentProcessModel) => {
|
||||
const processModel: ProcessModel = {
|
||||
id: row.processModelIdentifier,
|
||||
description: '',
|
||||
display_name: '',
|
||||
primary_file_name: '',
|
||||
files: [],
|
||||
};
|
||||
const modifiedProcessModelId = modifyProcessIdentifierForPathParam(
|
||||
rowToUse.processModelIdentifier
|
||||
row.processModelIdentifier
|
||||
);
|
||||
return (
|
||||
<tr
|
||||
key={`${rowToUse.processGroupIdentifier}/${rowToUse.processModelIdentifier}`}
|
||||
>
|
||||
<tr key={`${row.processGroupIdentifier}/${row.processModelIdentifier}`}>
|
||||
<td>
|
||||
<Link
|
||||
data-qa="process-model-show-link"
|
||||
to={`/admin/process-models/${modifiedProcessModelId}`}
|
||||
>
|
||||
{rowToUse.processModelDisplayName}
|
||||
{row.processModelDisplayName}
|
||||
</Link>
|
||||
</td>
|
||||
<td className="actions-cell">
|
||||
<ProcessInstanceRun
|
||||
processModel={processModel}
|
||||
onSuccessCallback={setProcessInstance}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<>
|
||||
<h1>Recently viewed process models</h1>
|
||||
<h1>Recently instantiated process models</h1>
|
||||
<Table striped bordered>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Process Model</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>{rows}</tbody>
|
||||
|
@ -175,6 +216,7 @@ export default function MyTasks() {
|
|||
}
|
||||
return (
|
||||
<>
|
||||
{processInstanceRunResultTag()}
|
||||
{tasksWaitingForMe}
|
||||
<br />
|
||||
{relevantProcessModelSection}
|
||||
|
|
|
@ -27,10 +27,11 @@ export default function ProcessGroupEdit() {
|
|||
<ProcessBreadcrumb
|
||||
hotCrumbs={[
|
||||
['Process Groups', '/admin'],
|
||||
[
|
||||
`Process Group: ${processGroup.id}:link`,
|
||||
`process_group:${processGroup.id}:link`,
|
||||
],
|
||||
{
|
||||
entityToExplode: processGroup,
|
||||
entityType: 'process-group',
|
||||
linkLastItem: true,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<h1>Edit Process Group: {(processGroup as any).id}</h1>
|
||||
|
|
|
@ -14,7 +14,11 @@ export default function ProcessGroupNew() {
|
|||
|
||||
const hotCrumbs: HotCrumbItem[] = [['Process Groups', '/admin']];
|
||||
if (parentGroupId) {
|
||||
hotCrumbs.push(['', `process_group:${parentGroupId}:link`]);
|
||||
hotCrumbs.push({
|
||||
entityToExplode: parentGroupId,
|
||||
entityType: 'process-group-id',
|
||||
linkLastItem: true,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,10 +1,19 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
import { Link, useSearchParams, useParams } from 'react-router-dom';
|
||||
import {
|
||||
// Link,
|
||||
useSearchParams,
|
||||
useParams,
|
||||
useNavigate,
|
||||
} from 'react-router-dom';
|
||||
import {
|
||||
TrashCan,
|
||||
Edit,
|
||||
// @ts-ignore
|
||||
} from '@carbon/icons-react';
|
||||
// @ts-ignore
|
||||
import { Button, Table, Stack } from '@carbon/react';
|
||||
import { Button, Stack } from '@carbon/react';
|
||||
import { Can } from '@casl/react';
|
||||
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
|
||||
import PaginationForTable from '../components/PaginationForTable';
|
||||
import HttpService from '../services/HttpService';
|
||||
import {
|
||||
getPageInfoFromSearchParams,
|
||||
|
@ -15,26 +24,28 @@ import {
|
|||
PaginationObject,
|
||||
PermissionsToCheck,
|
||||
ProcessGroup,
|
||||
ProcessModel,
|
||||
// ProcessModel,
|
||||
} from '../interfaces';
|
||||
import { useUriListForPermissions } from '../hooks/UriListForPermissions';
|
||||
import { usePermissionFetcher } from '../hooks/PermissionService';
|
||||
import ProcessGroupListTiles from '../components/ProcessGroupListTiles';
|
||||
// import ProcessModelListTiles from '../components/ProcessModelListTiles';
|
||||
import ButtonWithConfirmation from '../components/ButtonWithConfirmation';
|
||||
import ProcessModelListTiles from '../components/ProcessModelListTiles';
|
||||
|
||||
export default function ProcessGroupShow() {
|
||||
const params = useParams();
|
||||
const [searchParams] = useSearchParams();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [processGroup, setProcessGroup] = useState<ProcessGroup | null>(null);
|
||||
const [processModels, setProcessModels] = useState([]);
|
||||
// const [processModels, setProcessModels] = useState([]);
|
||||
const [modelPagination, setModelPagination] =
|
||||
useState<PaginationObject | null>(null);
|
||||
|
||||
const { targetUris } = useUriListForPermissions();
|
||||
const permissionRequestData: PermissionsToCheck = {
|
||||
[targetUris.processGroupListPath]: ['POST'],
|
||||
[targetUris.processGroupShowPath]: ['PUT'],
|
||||
[targetUris.processGroupShowPath]: ['PUT', 'DELETE'],
|
||||
[targetUris.processModelCreatePath]: ['POST'],
|
||||
};
|
||||
const { ability } = usePermissionFetcher(permissionRequestData);
|
||||
|
@ -43,7 +54,7 @@ export default function ProcessGroupShow() {
|
|||
const { page, perPage } = getPageInfoFromSearchParams(searchParams);
|
||||
|
||||
const setProcessModelFromResult = (result: any) => {
|
||||
setProcessModels(result.results);
|
||||
// setProcessModels(result.results);
|
||||
setModelPagination(result.pagination);
|
||||
};
|
||||
const processResult = (result: any) => {
|
||||
|
@ -62,45 +73,61 @@ export default function ProcessGroupShow() {
|
|||
});
|
||||
}, [params, searchParams]);
|
||||
|
||||
const buildModelTable = () => {
|
||||
if (processGroup === null) {
|
||||
return null;
|
||||
// const buildModelTable = () => {
|
||||
// if (processGroup === null) {
|
||||
// return null;
|
||||
// }
|
||||
// const rows = processModels.map((row: ProcessModel) => {
|
||||
// const modifiedProcessModelId: String =
|
||||
// modifyProcessIdentifierForPathParam((row as any).id);
|
||||
// return (
|
||||
// <tr key={row.id}>
|
||||
// <td>
|
||||
// <Link
|
||||
// to={`/admin/process-models/${modifiedProcessModelId}`}
|
||||
// data-qa="process-model-show-link"
|
||||
// >
|
||||
// {row.id}
|
||||
// </Link>
|
||||
// </td>
|
||||
// <td>{row.display_name}</td>
|
||||
// </tr>
|
||||
// );
|
||||
// });
|
||||
// return (
|
||||
// <div>
|
||||
// <h2>Process Models</h2>
|
||||
// <Table striped bordered>
|
||||
// <thead>
|
||||
// <tr>
|
||||
// <th>Process Model Id</th>
|
||||
// <th>Display Name</th>
|
||||
// </tr>
|
||||
// </thead>
|
||||
// <tbody>{rows}</tbody>
|
||||
// </Table>
|
||||
// </div>
|
||||
// );
|
||||
// };
|
||||
|
||||
const navigateToProcessGroups = (_result: any) => {
|
||||
navigate(`/admin/process-groups`);
|
||||
};
|
||||
|
||||
const deleteProcessGroup = () => {
|
||||
if (processGroup) {
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-groups/${modifyProcessIdentifierForPathParam(
|
||||
processGroup.id
|
||||
)}`,
|
||||
successCallback: navigateToProcessGroups,
|
||||
httpMethod: 'DELETE',
|
||||
});
|
||||
}
|
||||
const rows = processModels.map((row: ProcessModel) => {
|
||||
const modifiedProcessModelId: String =
|
||||
modifyProcessIdentifierForPathParam((row as any).id);
|
||||
return (
|
||||
<tr key={row.id}>
|
||||
<td>
|
||||
<Link
|
||||
to={`/admin/process-models/${modifiedProcessModelId}`}
|
||||
data-qa="process-model-show-link"
|
||||
>
|
||||
{row.id}
|
||||
</Link>
|
||||
</td>
|
||||
<td>{row.display_name}</td>
|
||||
</tr>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<div>
|
||||
<h2>Process Models</h2>
|
||||
<Table striped bordered>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Process Model Id</th>
|
||||
<th>Display Name</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>{rows}</tbody>
|
||||
</Table>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
if (processGroup && modelPagination) {
|
||||
const { page, perPage } = getPageInfoFromSearchParams(searchParams);
|
||||
// const { page, perPage } = getPageInfoFromSearchParams(searchParams);
|
||||
const modifiedProcessGroupId = modifyProcessIdentifierForPathParam(
|
||||
processGroup.id
|
||||
);
|
||||
|
@ -109,10 +136,41 @@ export default function ProcessGroupShow() {
|
|||
<ProcessBreadcrumb
|
||||
hotCrumbs={[
|
||||
['Process Groups', '/admin'],
|
||||
['', `process_group:${processGroup.id}`],
|
||||
{
|
||||
entityToExplode: processGroup,
|
||||
entityType: 'process-group',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<h1>Process Group: {processGroup.display_name}</h1>
|
||||
<Stack orientation="horizontal" gap={1}>
|
||||
<h1 className="with-icons">
|
||||
Process Group: {processGroup.display_name}
|
||||
</h1>
|
||||
<Can I="PUT" a={targetUris.processGroupShowPath} ability={ability}>
|
||||
<Button
|
||||
kind="ghost"
|
||||
data-qa="edit-process-group-button"
|
||||
renderIcon={Edit}
|
||||
iconDescription="Edit Process Group"
|
||||
hasIconOnly
|
||||
href={`/admin/process-groups/${modifiedProcessGroupId}/edit`}
|
||||
>
|
||||
Edit process group
|
||||
</Button>
|
||||
</Can>
|
||||
<Can I="DELETE" a={targetUris.processGroupShowPath} ability={ability}>
|
||||
<ButtonWithConfirmation
|
||||
kind="ghost"
|
||||
data-qa="delete-process-group-button"
|
||||
renderIcon={TrashCan}
|
||||
iconDescription="Delete Process Group"
|
||||
hasIconOnly
|
||||
description={`Delete process group: ${processGroup.display_name}`}
|
||||
onConfirmation={deleteProcessGroup}
|
||||
confirmButtonLabel="Delete"
|
||||
/>
|
||||
</Can>
|
||||
</Stack>
|
||||
<p className="process-description">{processGroup.description}</p>
|
||||
<ul>
|
||||
<Stack orientation="horizontal" gap={3}>
|
||||
|
@ -134,34 +192,27 @@ export default function ProcessGroupShow() {
|
|||
Add a process model
|
||||
</Button>
|
||||
</Can>
|
||||
<Can I="PUT" a={targetUris.processGroupShowPath} ability={ability}>
|
||||
<Button
|
||||
href={`/admin/process-groups/${modifiedProcessGroupId}/edit`}
|
||||
>
|
||||
Edit process group
|
||||
</Button>
|
||||
</Can>
|
||||
</Stack>
|
||||
<br />
|
||||
<br />
|
||||
{/* <ProcessModelListTiles
|
||||
<ProcessModelListTiles
|
||||
headerElement={<h2>Process Models</h2>}
|
||||
processGroup={processGroup}
|
||||
/> */}
|
||||
/>
|
||||
{/* eslint-disable-next-line sonarjs/no-gratuitous-expressions */}
|
||||
{modelPagination && modelPagination.total > 0 && (
|
||||
{/* {modelPagination && modelPagination.total > 0 && (
|
||||
<PaginationForTable
|
||||
page={page}
|
||||
perPage={perPage}
|
||||
pagination={modelPagination}
|
||||
tableToDisplay={buildModelTable()}
|
||||
/>
|
||||
)}
|
||||
)} */}
|
||||
<br />
|
||||
<br />
|
||||
<ProcessGroupListTiles
|
||||
processGroup={processGroup}
|
||||
headerElement={<h2>Process Groups</h2>}
|
||||
headerElement={<h2 className="clear-left">Process Groups</h2>}
|
||||
/>
|
||||
</ul>
|
||||
</>
|
||||
|
|
|
@ -7,7 +7,6 @@ import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
|
|||
import {
|
||||
getPageInfoFromSearchParams,
|
||||
modifyProcessIdentifierForPathParam,
|
||||
unModifyProcessIdentifierForPathParam,
|
||||
convertSecondsToFormattedDateTime,
|
||||
} from '../helpers';
|
||||
import HttpService from '../services/HttpService';
|
||||
|
@ -80,12 +79,11 @@ export default function ProcessInstanceLogList() {
|
|||
<ProcessBreadcrumb
|
||||
hotCrumbs={[
|
||||
['Process Groups', '/admin'],
|
||||
[
|
||||
`Process Model: ${params.process_model_id}`,
|
||||
`process_model:${unModifyProcessIdentifierForPathParam(
|
||||
params.process_model_id || ''
|
||||
)}:link`,
|
||||
],
|
||||
{
|
||||
entityToExplode: params.process_model_id || '',
|
||||
entityType: 'process-model-id',
|
||||
linkLastItem: true,
|
||||
},
|
||||
[
|
||||
`Process Instance: ${params.process_instance_id}`,
|
||||
`/admin/process-models/${params.process_model_id}/process-instances/${params.process_instance_id}`,
|
||||
|
|
|
@ -2,12 +2,22 @@ import { useEffect, useState } from 'react';
|
|||
// @ts-ignore
|
||||
import { Button, Table } from '@carbon/react';
|
||||
import { useParams, Link } from 'react-router-dom';
|
||||
import { Can } from '@casl/react';
|
||||
import HttpService from '../services/HttpService';
|
||||
import { useUriListForPermissions } from '../hooks/UriListForPermissions';
|
||||
import { PermissionsToCheck } from '../interfaces';
|
||||
import { usePermissionFetcher } from '../hooks/PermissionService';
|
||||
|
||||
export default function ProcessInstanceReportList() {
|
||||
const params = useParams();
|
||||
const [processInstanceReports, setProcessInstanceReports] = useState([]);
|
||||
|
||||
const { targetUris } = useUriListForPermissions();
|
||||
const permissionRequestData: PermissionsToCheck = {
|
||||
[targetUris.processInstanceReportListPath]: ['POST'],
|
||||
};
|
||||
const { ability } = usePermissionFetcher(permissionRequestData);
|
||||
|
||||
useEffect(() => {
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-instances/reports`,
|
||||
|
@ -45,9 +55,11 @@ export default function ProcessInstanceReportList() {
|
|||
const headerStuff = (
|
||||
<>
|
||||
<h1>Process Instance Perspectives</h1>
|
||||
<Button href="/admin/process-instances/reports/new">
|
||||
Add a process instance perspective
|
||||
</Button>
|
||||
<Can I="POST" a={targetUris.processInstanceListPath} ability={ability}>
|
||||
<Button href="/admin/process-instances/reports/new">
|
||||
Add a process instance perspective
|
||||
</Button>
|
||||
</Can>
|
||||
</>
|
||||
);
|
||||
if (processInstanceReports?.length > 0) {
|
||||
|
|
|
@ -76,9 +76,7 @@ export default function ProcessInstanceReport() {
|
|||
return (
|
||||
<main>
|
||||
<ProcessBreadcrumb
|
||||
processModelId={params.process_model_id}
|
||||
processGroupId={params.process_group_id}
|
||||
linkProcessModel
|
||||
hotCrumbs={[['Process Groups', '/admin'], ['Process Instance']]}
|
||||
/>
|
||||
<h1>Process Instance Perspective: {params.report_identifier}</h1>
|
||||
<Button
|
||||
|
|
|
@ -43,6 +43,7 @@ export default function ProcessInstanceShow() {
|
|||
|
||||
const [processInstance, setProcessInstance] = useState(null);
|
||||
const [tasks, setTasks] = useState<Array<object> | null>(null);
|
||||
const [tasksCallHadError, setTasksCallHadError] = useState<boolean>(false);
|
||||
const [taskToDisplay, setTaskToDisplay] = useState<object | null>(null);
|
||||
const [taskDataToDisplay, setTaskDataToDisplay] = useState<string>('');
|
||||
const [editingTaskData, setEditingTaskData] = useState<boolean>(false);
|
||||
|
@ -57,8 +58,11 @@ export default function ProcessInstanceShow() {
|
|||
const { targetUris } = useUriListForPermissions();
|
||||
const permissionRequestData: PermissionsToCheck = {
|
||||
[targetUris.messageInstanceListPath]: ['GET'],
|
||||
[targetUris.processInstanceTaskListPath]: ['GET'],
|
||||
};
|
||||
const { ability } = usePermissionFetcher(permissionRequestData);
|
||||
const { ability, permissionsLoaded } = usePermissionFetcher(
|
||||
permissionRequestData
|
||||
);
|
||||
|
||||
const navigateToProcessInstances = (_result: any) => {
|
||||
navigate(
|
||||
|
@ -67,21 +71,29 @@ export default function ProcessInstanceShow() {
|
|||
};
|
||||
|
||||
useEffect(() => {
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-models/${modifiedProcessModelId}/process-instances/${params.process_instance_id}`,
|
||||
successCallback: setProcessInstance,
|
||||
});
|
||||
if (typeof params.spiff_step === 'undefined')
|
||||
if (permissionsLoaded) {
|
||||
const processTaskFailure = () => {
|
||||
setTasksCallHadError(true);
|
||||
};
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-instances/${modifiedProcessModelId}/${params.process_instance_id}/tasks?all_tasks=true`,
|
||||
successCallback: setTasks,
|
||||
path: `/process-models/${modifiedProcessModelId}/process-instances/${params.process_instance_id}`,
|
||||
successCallback: setProcessInstance,
|
||||
});
|
||||
else
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-instances/${modifiedProcessModelId}/${params.process_instance_id}/tasks?all_tasks=true&spiff_step=${params.spiff_step}`,
|
||||
successCallback: setTasks,
|
||||
});
|
||||
}, [params, modifiedProcessModelId]);
|
||||
let taskParams = '?all_tasks=true';
|
||||
if (typeof params.spiff_step !== 'undefined') {
|
||||
taskParams = `${taskParams}&spiff_step=${params.spiff_step}`;
|
||||
}
|
||||
if (ability.can('GET', targetUris.processInstanceTaskListPath)) {
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-instances/${modifiedProcessModelId}/${params.process_instance_id}/tasks${taskParams}`,
|
||||
successCallback: setTasks,
|
||||
failureCallback: processTaskFailure,
|
||||
});
|
||||
} else {
|
||||
setTasksCallHadError(true);
|
||||
}
|
||||
}
|
||||
}, [params, modifiedProcessModelId, permissionsLoaded, ability, targetUris]);
|
||||
|
||||
const deleteProcessInstance = () => {
|
||||
HttpService.makeCallToBackend({
|
||||
|
@ -550,7 +562,7 @@ export default function ProcessInstanceShow() {
|
|||
return elements;
|
||||
};
|
||||
|
||||
if (processInstance && tasks) {
|
||||
if (processInstance && (tasks || tasksCallHadError)) {
|
||||
const processInstanceToUse = processInstance as any;
|
||||
const taskIds = getTaskIds();
|
||||
const processModelId = unModifyProcessIdentifierForPathParam(
|
||||
|
@ -562,10 +574,11 @@ export default function ProcessInstanceShow() {
|
|||
<ProcessBreadcrumb
|
||||
hotCrumbs={[
|
||||
['Process Groups', '/admin'],
|
||||
[
|
||||
`Process Model: ${processModelId}`,
|
||||
`process_model:${processModelId}:link`,
|
||||
],
|
||||
{
|
||||
entityToExplode: processModelId,
|
||||
entityType: 'process-model-id',
|
||||
linkLastItem: true,
|
||||
},
|
||||
[`Process Instance Id: ${processInstanceToUse.id}`],
|
||||
]}
|
||||
/>
|
||||
|
|
|
@ -24,10 +24,11 @@ export default function ProcessModelEdit() {
|
|||
<ProcessBreadcrumb
|
||||
hotCrumbs={[
|
||||
['Process Groups', '/admin'],
|
||||
[
|
||||
`Process Model: ${processModel.id}`,
|
||||
`process_model:${processModel.id}:link`,
|
||||
],
|
||||
{
|
||||
entityToExplode: processModel,
|
||||
entityType: 'process-model',
|
||||
linkLastItem: true,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<h1>Edit Process Model: {(processModel as any).id}</h1>
|
||||
|
|
|
@ -844,10 +844,11 @@ export default function ProcessModelEditDiagram() {
|
|||
<ProcessBreadcrumb
|
||||
hotCrumbs={[
|
||||
['Process Groups', '/admin'],
|
||||
[
|
||||
`Process Model: ${processModel.id}`,
|
||||
`process_model:${processModel.id}:link`,
|
||||
],
|
||||
{
|
||||
entityToExplode: processModel,
|
||||
entityType: 'process-model',
|
||||
linkLastItem: true,
|
||||
},
|
||||
[processModelFileName],
|
||||
]}
|
||||
/>
|
||||
|
|
|
@ -20,10 +20,11 @@ export default function ProcessModelNew() {
|
|||
<ProcessBreadcrumb
|
||||
hotCrumbs={[
|
||||
['Process Groups', '/admin'],
|
||||
[
|
||||
`Process Group: ${params.process_group_id}`,
|
||||
`process_group:${params.process_group_id}:link`,
|
||||
],
|
||||
{
|
||||
entityToExplode: params.process_group_id || '',
|
||||
entityType: 'process-group-id',
|
||||
linkLastItem: true,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<h1>Add Process Model</h1>
|
||||
|
|
|
@ -7,6 +7,7 @@ import {
|
|||
TrashCan,
|
||||
Favorite,
|
||||
Edit,
|
||||
View,
|
||||
ArrowRight,
|
||||
// @ts-ignore
|
||||
} from '@carbon/icons-react';
|
||||
|
@ -41,7 +42,6 @@ import {
|
|||
ProcessFile,
|
||||
ProcessInstance,
|
||||
ProcessModel,
|
||||
RecentProcessModel,
|
||||
} from '../interfaces';
|
||||
import ButtonWithConfirmation from '../components/ButtonWithConfirmation';
|
||||
import ProcessInstanceListTable from '../components/ProcessInstanceListTable';
|
||||
|
@ -49,55 +49,6 @@ import { usePermissionFetcher } from '../hooks/PermissionService';
|
|||
import { useUriListForPermissions } from '../hooks/UriListForPermissions';
|
||||
import ProcessInstanceRun from '../components/ProcessInstanceRun';
|
||||
|
||||
const storeRecentProcessModelInLocalStorage = (
|
||||
processModelForStorage: ProcessModel
|
||||
) => {
|
||||
// All values stored in localStorage are strings.
|
||||
// Grab our recentProcessModels string from localStorage.
|
||||
const stringFromLocalStorage = window.localStorage.getItem(
|
||||
'recentProcessModels'
|
||||
);
|
||||
|
||||
// adapted from https://stackoverflow.com/a/59424458/6090676
|
||||
// If that value is null (meaning that we've never saved anything to that spot in localStorage before), use an empty array as our array. Otherwise, use the value we parse out.
|
||||
let array: RecentProcessModel[] = [];
|
||||
if (stringFromLocalStorage !== null) {
|
||||
// Then parse that string into an actual value.
|
||||
array = JSON.parse(stringFromLocalStorage);
|
||||
}
|
||||
|
||||
// Here's the value we want to add
|
||||
const value = {
|
||||
processModelIdentifier: processModelForStorage.id,
|
||||
processModelDisplayName: processModelForStorage.display_name,
|
||||
};
|
||||
|
||||
// anything with a processGroupIdentifier is old and busted. leave it behind.
|
||||
array = array.filter((item) => item.processGroupIdentifier === undefined);
|
||||
|
||||
// If our parsed/empty array doesn't already have this value in it...
|
||||
const matchingItem = array.find(
|
||||
(item) => item.processModelIdentifier === value.processModelIdentifier
|
||||
);
|
||||
if (matchingItem === undefined) {
|
||||
// add the value to the beginning of the array
|
||||
array.unshift(value);
|
||||
|
||||
// Keep the array to 3 items
|
||||
if (array.length > 3) {
|
||||
array.pop();
|
||||
}
|
||||
}
|
||||
|
||||
// once the old and busted serializations are gone, we can put these two statements inside the above if statement
|
||||
|
||||
// turn the array WITH THE NEW VALUE IN IT into a string to prepare it to be stored in localStorage
|
||||
const stringRepresentingArray = JSON.stringify(array);
|
||||
|
||||
// and store it in localStorage as "recentProcessModels"
|
||||
window.localStorage.setItem('recentProcessModels', stringRepresentingArray);
|
||||
};
|
||||
|
||||
export default function ProcessModelShow() {
|
||||
const params = useParams();
|
||||
const setErrorMessage = (useContext as any)(ErrorContext)[1];
|
||||
|
@ -116,9 +67,11 @@ export default function ProcessModelShow() {
|
|||
[targetUris.processModelShowPath]: ['PUT', 'DELETE'],
|
||||
[targetUris.processInstanceListPath]: ['GET'],
|
||||
[targetUris.processInstanceActionPath]: ['POST'],
|
||||
[targetUris.processModelFileCreatePath]: ['POST', 'GET', 'DELETE'],
|
||||
[targetUris.processModelFileCreatePath]: ['POST', 'PUT', 'GET', 'DELETE'],
|
||||
};
|
||||
const { ability } = usePermissionFetcher(permissionRequestData);
|
||||
const { ability, permissionsLoaded } = usePermissionFetcher(
|
||||
permissionRequestData
|
||||
);
|
||||
|
||||
const modifiedProcessModelId = modifyProcessIdentifierForPathParam(
|
||||
`${params.process_model_id}`
|
||||
|
@ -128,7 +81,6 @@ export default function ProcessModelShow() {
|
|||
const processResult = (result: ProcessModel) => {
|
||||
setProcessModel(result);
|
||||
setReloadModel(false);
|
||||
storeRecentProcessModelInLocalStorage(result);
|
||||
};
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-models/${modifiedProcessModelId}`,
|
||||
|
@ -139,7 +91,7 @@ export default function ProcessModelShow() {
|
|||
const processInstanceRunResultTag = () => {
|
||||
if (processInstance) {
|
||||
return (
|
||||
<div className="alert alert-success" role="alert">
|
||||
<div className="alert alert-success with-top-margin" role="alert">
|
||||
<p>
|
||||
Process Instance {processInstance.id} kicked off (
|
||||
<Link
|
||||
|
@ -150,6 +102,7 @@ export default function ProcessModelShow() {
|
|||
</Link>
|
||||
).
|
||||
</p>
|
||||
<br />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -262,12 +215,18 @@ export default function ProcessModelShow() {
|
|||
isPrimaryBpmnFile: boolean
|
||||
) => {
|
||||
const elements = [];
|
||||
let icon = View;
|
||||
let actionWord = 'View';
|
||||
if (ability.can('PUT', targetUris.processModelFileCreatePath)) {
|
||||
icon = Edit;
|
||||
actionWord = 'Edit';
|
||||
}
|
||||
elements.push(
|
||||
<Can I="GET" a={targetUris.processModelFileCreatePath} ability={ability}>
|
||||
<Button
|
||||
kind="ghost"
|
||||
renderIcon={Edit}
|
||||
iconDescription="Edit File"
|
||||
renderIcon={icon}
|
||||
iconDescription={`${actionWord} File`}
|
||||
hasIconOnly
|
||||
size="lg"
|
||||
data-qa={`edit-file-${processModelFile.name.replace('.', '-')}`}
|
||||
|
@ -325,7 +284,7 @@ export default function ProcessModelShow() {
|
|||
};
|
||||
|
||||
const processModelFileList = () => {
|
||||
if (!processModel) {
|
||||
if (!processModel || !permissionsLoaded) {
|
||||
return null;
|
||||
}
|
||||
let constructedTag;
|
||||
|
@ -439,12 +398,16 @@ export default function ProcessModelShow() {
|
|||
);
|
||||
};
|
||||
|
||||
const processModelButtons = () => {
|
||||
const processModelFilesSection = () => {
|
||||
if (!processModel) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Grid condensed fullWidth>
|
||||
<Grid
|
||||
condensed
|
||||
fullWidth
|
||||
className="megacondensed process-model-files-section"
|
||||
>
|
||||
<Column md={5} lg={9} sm={3}>
|
||||
<Accordion align="end" open>
|
||||
<AccordionItem
|
||||
|
@ -518,7 +481,10 @@ export default function ProcessModelShow() {
|
|||
const processInstanceListTableButton = () => {
|
||||
if (processModel) {
|
||||
return (
|
||||
<Grid fullWidth>
|
||||
<Grid fullWidth condensed>
|
||||
<Column sm={{ span: 3 }} md={{ span: 4 }} lg={{ span: 3 }}>
|
||||
<h2>Process Instances</h2>
|
||||
</Column>
|
||||
<Column
|
||||
sm={{ span: 1, offset: 3 }}
|
||||
md={{ span: 1, offset: 7 }}
|
||||
|
@ -551,17 +517,28 @@ export default function ProcessModelShow() {
|
|||
<ProcessBreadcrumb
|
||||
hotCrumbs={[
|
||||
['Process Groups', '/admin'],
|
||||
[
|
||||
`Process Model: ${processModel.id}`,
|
||||
`process_model:${processModel.id}`,
|
||||
],
|
||||
{
|
||||
entityToExplode: processModel,
|
||||
entityType: 'process-model',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<Stack orientation="horizontal" gap={1}>
|
||||
<h1 className="with-icons">
|
||||
Process Model: {processModel.display_name}
|
||||
</h1>
|
||||
|
||||
<Can I="PUT" a={targetUris.processModelShowPath} ability={ability}>
|
||||
<Button
|
||||
kind="ghost"
|
||||
data-qa="edit-process-model-button"
|
||||
renderIcon={Edit}
|
||||
iconDescription="Edit Process Model"
|
||||
hasIconOnly
|
||||
href={`/admin/process-models/${modifiedProcessModelId}/edit`}
|
||||
>
|
||||
Edit process model
|
||||
</Button>
|
||||
</Can>
|
||||
<Can I="DELETE" a={targetUris.processModelShowPath} ability={ability}>
|
||||
<ButtonWithConfirmation
|
||||
kind="ghost"
|
||||
|
@ -582,34 +559,27 @@ export default function ProcessModelShow() {
|
|||
a={targetUris.processInstanceActionPath}
|
||||
ability={ability}
|
||||
>
|
||||
<ProcessInstanceRun
|
||||
processModel={processModel}
|
||||
onSuccessCallback={setProcessInstance}
|
||||
/>
|
||||
</Can>
|
||||
<Can I="PUT" a={targetUris.processModelShowPath} ability={ability}>
|
||||
<Button
|
||||
href={`/admin/process-models/${modifiedProcessModelId}/edit`}
|
||||
variant="secondary"
|
||||
>
|
||||
Edit process model
|
||||
</Button>
|
||||
<>
|
||||
<ProcessInstanceRun
|
||||
processModel={processModel}
|
||||
onSuccessCallback={setProcessInstance}
|
||||
/>
|
||||
<br />
|
||||
<br />
|
||||
</>
|
||||
</Can>
|
||||
</Stack>
|
||||
<br />
|
||||
<br />
|
||||
{processInstanceRunResultTag()}
|
||||
<br />
|
||||
{processModelFilesSection()}
|
||||
<Can I="GET" a={targetUris.processInstanceListPath} ability={ability}>
|
||||
{processInstanceListTableButton()}
|
||||
<ProcessInstanceListTable
|
||||
filtersEnabled={false}
|
||||
processModelFullIdentifier={processModel.id}
|
||||
perPageOptions={[2, 5, 25]}
|
||||
showReports={false}
|
||||
/>
|
||||
<br />
|
||||
</Can>
|
||||
{processModelButtons()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -6,10 +6,7 @@ import { Button, Modal } from '@carbon/react';
|
|||
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
|
||||
import HttpService from '../services/HttpService';
|
||||
import ButtonWithConfirmation from '../components/ButtonWithConfirmation';
|
||||
import {
|
||||
modifyProcessIdentifierForPathParam,
|
||||
unModifyProcessIdentifierForPathParam,
|
||||
} from '../helpers';
|
||||
import { modifyProcessIdentifierForPathParam } from '../helpers';
|
||||
import { ProcessFile } from '../interfaces';
|
||||
|
||||
// NOTE: This is mostly the same as ProcessModelEditDiagram and if we go this route could
|
||||
|
@ -159,14 +156,11 @@ export default function ReactFormEditor() {
|
|||
<ProcessBreadcrumb
|
||||
hotCrumbs={[
|
||||
['Process Groups', '/admin'],
|
||||
[
|
||||
`Process Model: ${unModifyProcessIdentifierForPathParam(
|
||||
params.process_model_id || ''
|
||||
)}`,
|
||||
`process_model:${unModifyProcessIdentifierForPathParam(
|
||||
params.process_model_id || ''
|
||||
)}:link`,
|
||||
],
|
||||
{
|
||||
entityToExplode: params.process_model_id || '',
|
||||
entityType: 'process-model-id',
|
||||
linkLastItem: true,
|
||||
},
|
||||
[processModelFileName],
|
||||
]}
|
||||
/>
|
||||
|
|
|
@ -62,7 +62,7 @@ export default function SecretList() {
|
|||
<Table striped bordered>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Id</th>
|
||||
<th>Secret Key</th>
|
||||
<th>Creator</th>
|
||||
<th>Delete</th>
|
||||
|
|
|
@ -15,6 +15,7 @@ import {
|
|||
Tabs,
|
||||
Grid,
|
||||
Column,
|
||||
Button,
|
||||
// @ts-ignore
|
||||
} from '@carbon/react';
|
||||
|
||||
|
@ -167,6 +168,14 @@ export default function TaskShow() {
|
|||
reactFragmentToHideSubmitButton = <div />;
|
||||
}
|
||||
|
||||
if (taskToUse.type === 'Manual Task') {
|
||||
reactFragmentToHideSubmitButton = (
|
||||
<div>
|
||||
<Button type="submit">Continue</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Grid fullWidth condensed>
|
||||
<Column md={5} lg={8} sm={4}>
|
||||
|
|
|
@ -66,9 +66,11 @@ backendCallProps) => {
|
|||
method: httpMethod,
|
||||
});
|
||||
|
||||
const updatedPath = path.replace(/^\/v1\.0/, '');
|
||||
|
||||
let isSuccessful = true;
|
||||
let is403 = false;
|
||||
fetch(`${BACKEND_BASE_URL}${path}`, httpArgs)
|
||||
fetch(`${BACKEND_BASE_URL}${updatedPath}`, httpArgs)
|
||||
.then((response) => {
|
||||
if (response.status === 401) {
|
||||
UserService.doLogin();
|
||||
|
|
Loading…
Reference in New Issue