Merge branch 'main' into feature/create_containers
This commit is contained in:
commit
d80e4b3456
|
@ -1,5 +1,5 @@
|
|||
pip==22.2.2
|
||||
nox==2022.8.7
|
||||
nox-poetry==1.0.1
|
||||
nox==2022.11.21
|
||||
nox-poetry==1.0.2
|
||||
poetry==1.2.2
|
||||
virtualenv==20.16.5
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
"""empty message
|
||||
|
||||
Revision ID: 3f049fa4d8ac
|
||||
Revision ID: 4d75421c0af0
|
||||
Revises:
|
||||
Create Date: 2022-11-30 16:49:54.805372
|
||||
Create Date: 2022-12-06 17:42:56.417673
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
|
@ -10,7 +10,7 @@ import sqlalchemy as sa
|
|||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '3f049fa4d8ac'
|
||||
revision = '4d75421c0af0'
|
||||
down_revision = None
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
@ -248,6 +248,7 @@ def upgrade():
|
|||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('process_instance_id', 'key', name='process_instance_metadata_unique')
|
||||
)
|
||||
op.create_index(op.f('ix_process_instance_metadata_key'), 'process_instance_metadata', ['key'], unique=False)
|
||||
op.create_table('spiff_step_details',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('process_instance_id', sa.Integer(), nullable=False),
|
||||
|
@ -294,6 +295,7 @@ def downgrade():
|
|||
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_index(op.f('ix_process_instance_metadata_key'), table_name='process_instance_metadata')
|
||||
op.drop_table('process_instance_metadata')
|
||||
op.drop_table('permission_assignment')
|
||||
op.drop_table('message_instance')
|
|
@ -1851,7 +1851,7 @@ lxml = "*"
|
|||
type = "git"
|
||||
url = "https://github.com/sartography/SpiffWorkflow"
|
||||
reference = "main"
|
||||
resolved_reference = "062eaf15d28c66f8cf07f68409429560251b12c7"
|
||||
resolved_reference = "ffb1686757f944065580dd2db8def73d6c1f0134"
|
||||
|
||||
[[package]]
|
||||
name = "SQLAlchemy"
|
||||
|
@ -2989,7 +2989,18 @@ psycopg2 = [
|
|||
{file = "psycopg2-2.9.4.tar.gz", hash = "sha256:d529926254e093a1b669f692a3aa50069bc71faf5b0ecd91686a78f62767d52f"},
|
||||
]
|
||||
pyasn1 = [
|
||||
{file = "pyasn1-0.4.8-py2.4.egg", hash = "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"},
|
||||
{file = "pyasn1-0.4.8-py2.5.egg", hash = "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf"},
|
||||
{file = "pyasn1-0.4.8-py2.6.egg", hash = "sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00"},
|
||||
{file = "pyasn1-0.4.8-py2.7.egg", hash = "sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8"},
|
||||
{file = "pyasn1-0.4.8-py2.py3-none-any.whl", hash = "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d"},
|
||||
{file = "pyasn1-0.4.8-py3.1.egg", hash = "sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86"},
|
||||
{file = "pyasn1-0.4.8-py3.2.egg", hash = "sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7"},
|
||||
{file = "pyasn1-0.4.8-py3.3.egg", hash = "sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576"},
|
||||
{file = "pyasn1-0.4.8-py3.4.egg", hash = "sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12"},
|
||||
{file = "pyasn1-0.4.8-py3.5.egg", hash = "sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2"},
|
||||
{file = "pyasn1-0.4.8-py3.6.egg", hash = "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359"},
|
||||
{file = "pyasn1-0.4.8-py3.7.egg", hash = "sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776"},
|
||||
{file = "pyasn1-0.4.8.tar.gz", hash = "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba"},
|
||||
]
|
||||
pycodestyle = [
|
||||
|
|
|
@ -108,11 +108,14 @@ def create_app() -> flask.app.Flask:
|
|||
app.register_blueprint(admin_blueprint, url_prefix="/admin")
|
||||
app.register_blueprint(openid_blueprint, url_prefix="/openid")
|
||||
|
||||
# preflight options requests will be allowed if they meet the requirements of the url regex.
|
||||
# we will add an Access-Control-Max-Age header to the response to tell the browser it doesn't
|
||||
# need to continually keep asking for the same path.
|
||||
origins_re = [
|
||||
r"^https?:\/\/%s(.*)" % o.replace(".", r"\.")
|
||||
for o in app.config["CORS_ALLOW_ORIGINS"]
|
||||
]
|
||||
CORS(app, origins=origins_re)
|
||||
CORS(app, origins=origins_re, max_age=3600)
|
||||
|
||||
connexion_app.add_api("api.yml", base_path="/v1.0")
|
||||
|
||||
|
|
|
@ -338,9 +338,9 @@ paths:
|
|||
schema:
|
||||
$ref: "#/components/schemas/ProcessModel"
|
||||
|
||||
/process-models/{modified_process_model_id}/files:
|
||||
/process-models/{modified_process_model_identifier}/files:
|
||||
parameters:
|
||||
- name: modified_process_model_id
|
||||
- name: modified_process_model_identifier
|
||||
in: path
|
||||
required: true
|
||||
description: The process_model_id, modified to replace slashes (/)
|
||||
|
@ -544,6 +544,12 @@ paths:
|
|||
description: Specifies the identifier of a report to use, if any
|
||||
schema:
|
||||
type: string
|
||||
- name: report_id
|
||||
in: query
|
||||
required: false
|
||||
description: Specifies the identifier of a report to use, if any
|
||||
schema:
|
||||
type: integer
|
||||
get:
|
||||
operationId: spiffworkflow_backend.routes.process_api_blueprint.process_instance_list
|
||||
summary: Returns a list of process instances for a given process model
|
||||
|
@ -559,33 +565,6 @@ paths:
|
|||
items:
|
||||
$ref: "#/components/schemas/Workflow"
|
||||
|
||||
/process-instances/{process_instance_id}/task/{task_id}/update:
|
||||
parameters:
|
||||
- name: process_instance_id
|
||||
in: path
|
||||
required: true
|
||||
description: The unique id of the process instance
|
||||
schema:
|
||||
type: string
|
||||
- name: task_id
|
||||
in: path
|
||||
required: true
|
||||
description: The unique id of the task
|
||||
schema:
|
||||
type: string
|
||||
post:
|
||||
operationId: spiffworkflow_backend.routes.process_api_blueprint.update_task_data
|
||||
summary: Update the task data for requested instance and task
|
||||
tags:
|
||||
- Process Instances
|
||||
responses:
|
||||
"200":
|
||||
description: Task Updated Successfully
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Workflow"
|
||||
|
||||
/process-models/{process_group_id}/{process_model_id}/script-unit-tests:
|
||||
parameters:
|
||||
- name: process_group_id
|
||||
|
@ -640,15 +619,14 @@ paths:
|
|||
schema:
|
||||
$ref: "#/components/schemas/Workflow"
|
||||
|
||||
/process-models/{modified_process_model_id}/process-instances:
|
||||
/process-instances/{modified_process_model_identifier}:
|
||||
parameters:
|
||||
- name: modified_process_model_id
|
||||
- name: modified_process_model_identifier
|
||||
in: path
|
||||
required: true
|
||||
description: The unique id of an existing process model.
|
||||
schema:
|
||||
type: string
|
||||
# process_instance_create
|
||||
post:
|
||||
operationId: spiffworkflow_backend.routes.process_api_blueprint.process_instance_create
|
||||
summary: Creates an process instance from a process model and returns the instance
|
||||
|
@ -662,28 +640,7 @@ paths:
|
|||
schema:
|
||||
$ref: "#/components/schemas/Workflow"
|
||||
|
||||
/process-instances/{process_instance_id}:
|
||||
parameters:
|
||||
- name: process_instance_id
|
||||
in: path
|
||||
required: true
|
||||
description: The unique id of an existing process instance.
|
||||
schema:
|
||||
type: integer
|
||||
delete:
|
||||
operationId: spiffworkflow_backend.routes.process_api_blueprint.process_instance_delete
|
||||
summary: Deletes a single process instance
|
||||
tags:
|
||||
- Process Instances
|
||||
responses:
|
||||
"200":
|
||||
description: The process instance was deleted.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/OkTrue"
|
||||
|
||||
/process-models/{modified_process_model_identifier}/process-instances/{process_instance_id}:
|
||||
/process-instances/{modified_process_model_identifier}/{process_instance_id}:
|
||||
parameters:
|
||||
- name: modified_process_model_identifier
|
||||
in: path
|
||||
|
@ -709,6 +666,18 @@ paths:
|
|||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Workflow"
|
||||
delete:
|
||||
operationId: spiffworkflow_backend.routes.process_api_blueprint.process_instance_delete
|
||||
summary: Deletes a single process instance
|
||||
tags:
|
||||
- Process Instances
|
||||
responses:
|
||||
"200":
|
||||
description: The process instance was deleted.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/OkTrue"
|
||||
|
||||
/process-instances/{modified_process_model_identifier}/{process_instance_id}/run:
|
||||
parameters:
|
||||
|
@ -737,7 +706,7 @@ paths:
|
|||
schema:
|
||||
$ref: "#/components/schemas/Workflow"
|
||||
|
||||
/process-instances/{process_instance_id}/terminate:
|
||||
/process-instances/{modified_process_model_identifier}/{process_instance_id}/terminate:
|
||||
parameters:
|
||||
- name: process_instance_id
|
||||
in: path
|
||||
|
@ -758,7 +727,7 @@ paths:
|
|||
schema:
|
||||
$ref: "#/components/schemas/OkTrue"
|
||||
|
||||
/process-instances/{process_instance_id}/suspend:
|
||||
/process-instances/{modified_process_model_identifier}/{process_instance_id}/suspend:
|
||||
parameters:
|
||||
- name: process_instance_id
|
||||
in: path
|
||||
|
@ -779,7 +748,7 @@ paths:
|
|||
schema:
|
||||
$ref: "#/components/schemas/OkTrue"
|
||||
|
||||
/process-instances/{process_instance_id}/resume:
|
||||
/process-instances/{modified_process_model_identifier}/{process_instance_id}/resume:
|
||||
parameters:
|
||||
- name: process_instance_id
|
||||
in: path
|
||||
|
@ -841,14 +810,30 @@ paths:
|
|||
schema:
|
||||
$ref: "#/components/schemas/OkTrue"
|
||||
|
||||
/process-instances/reports/{report_identifier}:
|
||||
/process-instances/reports/columns:
|
||||
get:
|
||||
operationId: spiffworkflow_backend.routes.process_api_blueprint.process_instance_report_column_list
|
||||
summary: Returns all available columns for a process instance report.
|
||||
tags:
|
||||
- Process Instances
|
||||
responses:
|
||||
"200":
|
||||
description: Workflow.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/Workflow"
|
||||
|
||||
/process-instances/reports/{report_id}:
|
||||
parameters:
|
||||
- name: report_identifier
|
||||
- name: report_id
|
||||
in: path
|
||||
required: true
|
||||
description: The unique id of an existing report
|
||||
schema:
|
||||
type: string
|
||||
type: integer
|
||||
- name: page
|
||||
in: query
|
||||
required: false
|
||||
|
@ -900,9 +885,9 @@ paths:
|
|||
schema:
|
||||
$ref: "#/components/schemas/OkTrue"
|
||||
|
||||
/process-models/{modified_process_model_id}/files/{file_name}:
|
||||
/process-models/{modified_process_model_identifier}/files/{file_name}:
|
||||
parameters:
|
||||
- name: modified_process_model_id
|
||||
- name: modified_process_model_identifier
|
||||
in: path
|
||||
required: true
|
||||
description: The modified process model id
|
||||
|
@ -1079,9 +1064,9 @@ paths:
|
|||
items:
|
||||
$ref: "#/components/schemas/Task"
|
||||
|
||||
/process-instances/{modified_process_model_id}/{process_instance_id}/tasks:
|
||||
/task-data/{modified_process_model_identifier}/{process_instance_id}:
|
||||
parameters:
|
||||
- name: modified_process_model_id
|
||||
- name: modified_process_model_identifier
|
||||
in: path
|
||||
required: true
|
||||
description: The modified id of an existing process model
|
||||
|
@ -1120,11 +1105,44 @@ paths:
|
|||
items:
|
||||
$ref: "#/components/schemas/Task"
|
||||
|
||||
/service_tasks:
|
||||
/task-data/{modified_process_model_identifier}/{process_instance_id}/{task_id}:
|
||||
parameters:
|
||||
- name: modified_process_model_identifier
|
||||
in: path
|
||||
required: true
|
||||
description: The modified id of an existing process model
|
||||
schema:
|
||||
type: string
|
||||
- name: process_instance_id
|
||||
in: path
|
||||
required: true
|
||||
description: The unique id of an existing process instance.
|
||||
schema:
|
||||
type: integer
|
||||
- name: task_id
|
||||
in: path
|
||||
required: true
|
||||
description: The unique id of the task.
|
||||
schema:
|
||||
type: string
|
||||
put:
|
||||
operationId: spiffworkflow_backend.routes.process_api_blueprint.update_task_data
|
||||
summary: Update the task data for requested instance and task
|
||||
tags:
|
||||
- Process Instances
|
||||
responses:
|
||||
"200":
|
||||
description: Task Updated Successfully
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Workflow"
|
||||
|
||||
/service-tasks:
|
||||
get:
|
||||
tags:
|
||||
- Service Tasks
|
||||
operationId: spiffworkflow_backend.routes.process_api_blueprint.service_tasks_show
|
||||
operationId: spiffworkflow_backend.routes.process_api_blueprint.service_task_list
|
||||
summary: Gets all available service task connectors
|
||||
responses:
|
||||
"200":
|
||||
|
@ -1304,7 +1322,7 @@ paths:
|
|||
schema:
|
||||
$ref: "#/components/schemas/Workflow"
|
||||
|
||||
/process-instances/{process_instance_id}/logs:
|
||||
/logs/{modified_process_model_identifier}/{process_instance_id}:
|
||||
parameters:
|
||||
- name: process_instance_id
|
||||
in: path
|
||||
|
@ -1324,6 +1342,12 @@ paths:
|
|||
description: The number of items to show per page. Defaults to page 10.
|
||||
schema:
|
||||
type: integer
|
||||
- name: detailed
|
||||
in: query
|
||||
required: false
|
||||
description: Show the detailed view, which includes all log entries
|
||||
schema:
|
||||
type: boolean
|
||||
get:
|
||||
tags:
|
||||
- Process Instances
|
||||
|
|
|
@ -18,7 +18,6 @@ groups:
|
|||
mike,
|
||||
jason,
|
||||
j,
|
||||
amir,
|
||||
jarrad,
|
||||
elizabeth,
|
||||
jon,
|
||||
|
@ -76,6 +75,12 @@ permissions:
|
|||
users: []
|
||||
allowed_permissions: [create, read, update, delete]
|
||||
uri: /v1.0/tasks/*
|
||||
service-tasks:
|
||||
groups: [everybody]
|
||||
users: []
|
||||
allowed_permissions: [read]
|
||||
uri: /v1.0/service-tasks
|
||||
|
||||
|
||||
# read all for everybody
|
||||
read-all-process-groups:
|
||||
|
@ -104,6 +109,12 @@ permissions:
|
|||
allowed_permissions: [read]
|
||||
uri: /v1.0/processes
|
||||
|
||||
task-data-read:
|
||||
groups: [demo]
|
||||
users: []
|
||||
allowed_permissions: [read]
|
||||
uri: /v1.0/task-data/*
|
||||
|
||||
|
||||
manage-procurement-admin:
|
||||
groups: ["Project Lead"]
|
||||
|
@ -176,17 +187,17 @@ permissions:
|
|||
uri: /v1.0/process-instances/manage-procurement:vendor-lifecycle-management:*
|
||||
|
||||
core1-admin-models-instantiate:
|
||||
groups: ["core-contributor"]
|
||||
groups: ["core-contributor", "Finance Team"]
|
||||
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"]
|
||||
groups: ["core-contributor", "Finance Team"]
|
||||
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"]
|
||||
groups: ["core-contributor", "Finance Team"]
|
||||
users: []
|
||||
allowed_permissions: [create, read]
|
||||
uri: /v1.0/process-instances/misc:category_number_one:process-model-with-form/*
|
||||
|
|
|
@ -12,7 +12,6 @@ groups:
|
|||
mike,
|
||||
jason,
|
||||
j,
|
||||
amir,
|
||||
jarrad,
|
||||
elizabeth,
|
||||
jon,
|
||||
|
@ -71,6 +70,13 @@ permissions:
|
|||
allowed_permissions: [create, read, update, delete]
|
||||
uri: /v1.0/tasks/*
|
||||
|
||||
service-tasks:
|
||||
groups: [everybody]
|
||||
users: []
|
||||
allowed_permissions: [read]
|
||||
uri: /v1.0/service-tasks
|
||||
|
||||
|
||||
# read all for everybody
|
||||
read-all-process-groups:
|
||||
groups: [everybody]
|
||||
|
@ -98,6 +104,12 @@ permissions:
|
|||
allowed_permissions: [read]
|
||||
uri: /v1.0/processes
|
||||
|
||||
task-data-read:
|
||||
groups: [demo]
|
||||
users: []
|
||||
allowed_permissions: [read]
|
||||
uri: /v1.0/task-data/*
|
||||
|
||||
|
||||
manage-procurement-admin:
|
||||
groups: ["Project Lead"]
|
||||
|
|
|
@ -23,7 +23,7 @@ class ProcessInstanceMetadataModel(SpiffworkflowBaseDBModel):
|
|||
process_instance_id: int = db.Column(
|
||||
ForeignKey(ProcessInstanceModel.id), nullable=False # type: ignore
|
||||
)
|
||||
key: str = db.Column(db.String(255), nullable=False)
|
||||
key: str = db.Column(db.String(255), nullable=False, index=True)
|
||||
value: str = db.Column(db.String(255), nullable=False)
|
||||
|
||||
updated_at_in_seconds: int = db.Column(db.Integer, nullable=False)
|
||||
|
|
|
@ -26,6 +26,10 @@ from spiffworkflow_backend.services.process_instance_processor import (
|
|||
ReportMetadata = dict[str, Any]
|
||||
|
||||
|
||||
class ProcessInstanceReportAlreadyExistsError(Exception):
|
||||
"""ProcessInstanceReportAlreadyExistsError."""
|
||||
|
||||
|
||||
class ProcessInstanceReportResult(TypedDict):
|
||||
"""ProcessInstanceReportResult."""
|
||||
|
||||
|
@ -63,7 +67,7 @@ class ProcessInstanceReportModel(SpiffworkflowBaseDBModel):
|
|||
),
|
||||
)
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
id: int = db.Column(db.Integer, primary_key=True)
|
||||
identifier: str = db.Column(db.String(50), nullable=False, index=True)
|
||||
report_metadata: dict = deferred(db.Column(db.JSON)) # type: ignore
|
||||
created_by_id = db.Column(ForeignKey(UserModel.id), nullable=False, index=True)
|
||||
|
@ -71,6 +75,11 @@ class ProcessInstanceReportModel(SpiffworkflowBaseDBModel):
|
|||
created_at_in_seconds = db.Column(db.Integer)
|
||||
updated_at_in_seconds = db.Column(db.Integer)
|
||||
|
||||
@classmethod
|
||||
def default_order_by(cls) -> list[str]:
|
||||
"""Default_order_by."""
|
||||
return ["-start_in_seconds", "-id"]
|
||||
|
||||
@classmethod
|
||||
def add_fixtures(cls) -> None:
|
||||
"""Add_fixtures."""
|
||||
|
@ -120,21 +129,27 @@ class ProcessInstanceReportModel(SpiffworkflowBaseDBModel):
|
|||
identifier: str,
|
||||
user: UserModel,
|
||||
report_metadata: ReportMetadata,
|
||||
) -> None:
|
||||
) -> ProcessInstanceReportModel:
|
||||
"""Make_fixture_report."""
|
||||
process_instance_report = ProcessInstanceReportModel.query.filter_by(
|
||||
identifier=identifier,
|
||||
created_by_id=user.id,
|
||||
).first()
|
||||
|
||||
if process_instance_report is None:
|
||||
process_instance_report = cls(
|
||||
identifier=identifier,
|
||||
created_by_id=user.id,
|
||||
report_metadata=report_metadata,
|
||||
if process_instance_report is not None:
|
||||
raise ProcessInstanceReportAlreadyExistsError(
|
||||
f"Process instance report with identifier already exists: {identifier}"
|
||||
)
|
||||
db.session.add(process_instance_report)
|
||||
db.session.commit()
|
||||
|
||||
process_instance_report = cls(
|
||||
identifier=identifier,
|
||||
created_by_id=user.id,
|
||||
report_metadata=report_metadata,
|
||||
)
|
||||
db.session.add(process_instance_report)
|
||||
db.session.commit()
|
||||
|
||||
return process_instance_report # type: ignore
|
||||
|
||||
@classmethod
|
||||
def ticket_for_month_report(cls) -> dict:
|
||||
|
@ -204,18 +219,8 @@ class ProcessInstanceReportModel(SpiffworkflowBaseDBModel):
|
|||
user: UserModel,
|
||||
) -> ProcessInstanceReportModel:
|
||||
"""Create_with_attributes."""
|
||||
# <<<<<<< HEAD
|
||||
# process_model = ProcessModelService.get_process_model(
|
||||
# process_model_id=f"{process_model_identifier}"
|
||||
# )
|
||||
# process_instance_report = cls(
|
||||
# identifier=identifier,
|
||||
# process_group_identifier="process_model.process_group_id",
|
||||
# process_model_identifier=process_model.id,
|
||||
# =======
|
||||
process_instance_report = cls(
|
||||
identifier=identifier,
|
||||
# >>>>>>> main
|
||||
created_by_id=user.id,
|
||||
report_metadata=report_metadata,
|
||||
)
|
||||
|
|
|
@ -38,6 +38,7 @@ class ProcessModelInfo:
|
|||
fault_or_suspend_on_exception: str = NotificationType.fault.value
|
||||
exception_notification_addresses: list[str] = field(default_factory=list)
|
||||
parent_groups: list[dict] | None = None
|
||||
metadata_extraction_paths: list[dict[str, str]] | None = None
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
"""__post_init__."""
|
||||
|
@ -76,6 +77,13 @@ class ProcessModelInfoSchema(Schema):
|
|||
exception_notification_addresses = marshmallow.fields.List(
|
||||
marshmallow.fields.String
|
||||
)
|
||||
metadata_extraction_paths = marshmallow.fields.List(
|
||||
marshmallow.fields.Dict(
|
||||
keys=marshmallow.fields.Str(required=False),
|
||||
values=marshmallow.fields.Str(required=False),
|
||||
required=False,
|
||||
)
|
||||
)
|
||||
|
||||
@post_load
|
||||
def make_spec(
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
"""APIs for dealing with process groups, process models, and process instances."""
|
||||
import json
|
||||
import random
|
||||
import re
|
||||
import string
|
||||
import uuid
|
||||
from flask_bpmn.api.api_error import ApiError
|
||||
|
@ -30,6 +31,9 @@ from SpiffWorkflow.task import TaskState
|
|||
from sqlalchemy import and_
|
||||
from sqlalchemy import asc
|
||||
from sqlalchemy import desc
|
||||
from sqlalchemy import func
|
||||
from sqlalchemy.orm import aliased
|
||||
from sqlalchemy.orm import joinedload
|
||||
from sqlalchemy.orm import selectinload
|
||||
|
||||
from spiffworkflow_backend.exceptions.process_entity_not_found_error import (
|
||||
|
@ -52,6 +56,9 @@ from spiffworkflow_backend.models.process_instance import ProcessInstanceApiSche
|
|||
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
|
||||
from spiffworkflow_backend.models.process_instance import ProcessInstanceModelSchema
|
||||
from spiffworkflow_backend.models.process_instance import ProcessInstanceStatus
|
||||
from spiffworkflow_backend.models.process_instance_metadata import (
|
||||
ProcessInstanceMetadataModel,
|
||||
)
|
||||
from spiffworkflow_backend.models.process_instance_report import (
|
||||
ProcessInstanceReportModel,
|
||||
)
|
||||
|
@ -152,9 +159,9 @@ def modify_process_model_id(process_model_id: str) -> str:
|
|||
return process_model_id.replace("/", ":")
|
||||
|
||||
|
||||
def un_modify_modified_process_model_id(modified_process_model_id: str) -> str:
|
||||
def un_modify_modified_process_model_id(modified_process_model_identifier: str) -> str:
|
||||
"""Un_modify_modified_process_model_id."""
|
||||
return modified_process_model_id.replace(":", "/")
|
||||
return modified_process_model_identifier.replace(":", "/")
|
||||
|
||||
|
||||
def process_group_add(body: dict) -> flask.wrappers.Response:
|
||||
|
@ -256,19 +263,26 @@ def process_model_create(
|
|||
modified_process_group_id: str, body: Dict[str, Union[str, bool, int]]
|
||||
) -> flask.wrappers.Response:
|
||||
"""Process_model_create."""
|
||||
process_model_info = ProcessModelInfoSchema().load(body)
|
||||
body_include_list = [
|
||||
"id",
|
||||
"display_name",
|
||||
"primary_file_name",
|
||||
"primary_process_id",
|
||||
"description",
|
||||
"metadata_extraction_paths",
|
||||
]
|
||||
body_filtered = {
|
||||
include_item: body[include_item]
|
||||
for include_item in body_include_list
|
||||
if include_item in body
|
||||
}
|
||||
|
||||
if modified_process_group_id is None:
|
||||
raise ApiError(
|
||||
error_code="process_group_id_not_specified",
|
||||
message="Process Model could not be created when process_group_id path param is unspecified",
|
||||
status_code=400,
|
||||
)
|
||||
if process_model_info is None:
|
||||
raise ApiError(
|
||||
error_code="process_model_could_not_be_created",
|
||||
message=f"Process Model could not be created from given body: {body}",
|
||||
status_code=400,
|
||||
)
|
||||
|
||||
unmodified_process_group_id = un_modify_modified_process_model_id(
|
||||
modified_process_group_id
|
||||
|
@ -281,6 +295,14 @@ def process_model_create(
|
|||
status_code=400,
|
||||
)
|
||||
|
||||
process_model_info = ProcessModelInfo(**body_filtered) # type: ignore
|
||||
if process_model_info is None:
|
||||
raise ApiError(
|
||||
error_code="process_model_could_not_be_created",
|
||||
message=f"Process Model could not be created from given body: {body}",
|
||||
status_code=400,
|
||||
)
|
||||
|
||||
ProcessModelService.add_process_model(process_model_info)
|
||||
return Response(
|
||||
json.dumps(ProcessModelInfoSchema().dump(process_model_info)),
|
||||
|
@ -294,7 +316,6 @@ def process_model_delete(
|
|||
) -> flask.wrappers.Response:
|
||||
"""Process_model_delete."""
|
||||
process_model_identifier = modified_process_model_identifier.replace(":", "/")
|
||||
# process_model_identifier = f"{process_group_id}/{process_model_id}"
|
||||
ProcessModelService().process_model_delete(process_model_identifier)
|
||||
return Response(json.dumps({"ok": True}), status=200, mimetype="application/json")
|
||||
|
||||
|
@ -309,6 +330,7 @@ def process_model_update(
|
|||
"primary_file_name",
|
||||
"primary_process_id",
|
||||
"description",
|
||||
"metadata_extraction_paths",
|
||||
]
|
||||
body_filtered = {
|
||||
include_item: body[include_item]
|
||||
|
@ -316,7 +338,6 @@ def process_model_update(
|
|||
if include_item in body
|
||||
}
|
||||
|
||||
# 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)
|
||||
return ProcessModelInfoSchema().dump(process_model)
|
||||
|
@ -325,10 +346,7 @@ def process_model_update(
|
|||
def process_model_show(modified_process_model_identifier: str) -> Any:
|
||||
"""Process_model_show."""
|
||||
process_model_identifier = modified_process_model_identifier.replace(":", "/")
|
||||
# process_model_identifier = f"{process_group_id}/{process_model_id}"
|
||||
process_model = get_process_model(process_model_identifier)
|
||||
# TODO: Temporary. Should not need the next line once models have correct ids
|
||||
# process_model.id = process_model_identifier
|
||||
files = sorted(SpecFileService.get_files(process_model))
|
||||
process_model.files = files
|
||||
for file in process_model.files:
|
||||
|
@ -394,9 +412,9 @@ def process_list() -> Any:
|
|||
return SpecReferenceSchema(many=True).dump(references)
|
||||
|
||||
|
||||
def get_file(modified_process_model_id: str, file_name: str) -> Any:
|
||||
def get_file(modified_process_model_identifier: str, file_name: str) -> Any:
|
||||
"""Get_file."""
|
||||
process_model_identifier = modified_process_model_id.replace(":", "/")
|
||||
process_model_identifier = modified_process_model_identifier.replace(":", "/")
|
||||
process_model = get_process_model(process_model_identifier)
|
||||
files = SpecFileService.get_files(process_model, file_name)
|
||||
if len(files) == 0:
|
||||
|
@ -416,11 +434,10 @@ def get_file(modified_process_model_id: str, file_name: str) -> Any:
|
|||
|
||||
|
||||
def process_model_file_update(
|
||||
modified_process_model_id: str, file_name: str
|
||||
modified_process_model_identifier: str, file_name: str
|
||||
) -> flask.wrappers.Response:
|
||||
"""Process_model_file_update."""
|
||||
process_model_identifier = modified_process_model_id.replace(":", "/")
|
||||
# process_model_identifier = f"{process_group_id}/{process_model_id}"
|
||||
process_model_identifier = modified_process_model_identifier.replace(":", "/")
|
||||
process_model = get_process_model(process_model_identifier)
|
||||
|
||||
request_file = get_file_from_request()
|
||||
|
@ -446,10 +463,10 @@ def process_model_file_update(
|
|||
|
||||
|
||||
def process_model_file_delete(
|
||||
modified_process_model_id: str, file_name: str
|
||||
modified_process_model_identifier: str, file_name: str
|
||||
) -> flask.wrappers.Response:
|
||||
"""Process_model_file_delete."""
|
||||
process_model_identifier = modified_process_model_id.replace(":", "/")
|
||||
process_model_identifier = modified_process_model_identifier.replace(":", "/")
|
||||
process_model = get_process_model(process_model_identifier)
|
||||
try:
|
||||
SpecFileService.delete_file(process_model, file_name)
|
||||
|
@ -465,9 +482,9 @@ def process_model_file_delete(
|
|||
return Response(json.dumps({"ok": True}), status=200, mimetype="application/json")
|
||||
|
||||
|
||||
def add_file(modified_process_model_id: str) -> flask.wrappers.Response:
|
||||
def add_file(modified_process_model_identifier: str) -> flask.wrappers.Response:
|
||||
"""Add_file."""
|
||||
process_model_identifier = modified_process_model_id.replace(":", "/")
|
||||
process_model_identifier = modified_process_model_identifier.replace(":", "/")
|
||||
process_model = get_process_model(process_model_identifier)
|
||||
request_file = get_file_from_request()
|
||||
if not request_file.filename:
|
||||
|
@ -488,10 +505,12 @@ def add_file(modified_process_model_id: str) -> flask.wrappers.Response:
|
|||
)
|
||||
|
||||
|
||||
def process_instance_create(modified_process_model_id: str) -> flask.wrappers.Response:
|
||||
def process_instance_create(
|
||||
modified_process_model_identifier: str,
|
||||
) -> flask.wrappers.Response:
|
||||
"""Create_process_instance."""
|
||||
process_model_identifier = un_modify_modified_process_model_id(
|
||||
modified_process_model_id
|
||||
modified_process_model_identifier
|
||||
)
|
||||
process_instance = (
|
||||
ProcessInstanceService.create_process_instance_from_process_model_identifier(
|
||||
|
@ -549,6 +568,7 @@ def process_instance_run(
|
|||
|
||||
def process_instance_terminate(
|
||||
process_instance_id: int,
|
||||
modified_process_model_identifier: str,
|
||||
) -> flask.wrappers.Response:
|
||||
"""Process_instance_run."""
|
||||
process_instance = ProcessInstanceService().get_process_instance(
|
||||
|
@ -561,6 +581,7 @@ def process_instance_terminate(
|
|||
|
||||
def process_instance_suspend(
|
||||
process_instance_id: int,
|
||||
modified_process_model_identifier: str,
|
||||
) -> flask.wrappers.Response:
|
||||
"""Process_instance_suspend."""
|
||||
process_instance = ProcessInstanceService().get_process_instance(
|
||||
|
@ -573,6 +594,7 @@ def process_instance_suspend(
|
|||
|
||||
def process_instance_resume(
|
||||
process_instance_id: int,
|
||||
modified_process_model_identifier: str,
|
||||
) -> flask.wrappers.Response:
|
||||
"""Process_instance_resume."""
|
||||
process_instance = ProcessInstanceService().get_process_instance(
|
||||
|
@ -584,19 +606,24 @@ def process_instance_resume(
|
|||
|
||||
|
||||
def process_instance_log_list(
|
||||
modified_process_model_identifier: str,
|
||||
process_instance_id: int,
|
||||
page: int = 1,
|
||||
per_page: int = 100,
|
||||
detailed: bool = False,
|
||||
) -> flask.wrappers.Response:
|
||||
"""Process_instance_log_list."""
|
||||
# to make sure the process instance exists
|
||||
process_instance = find_process_instance_by_id_or_raise(process_instance_id)
|
||||
|
||||
log_query = SpiffLoggingModel.query.filter(
|
||||
SpiffLoggingModel.process_instance_id == process_instance.id
|
||||
)
|
||||
if not detailed:
|
||||
log_query = log_query.filter(SpiffLoggingModel.message.in_(["State change to COMPLETED"])) # type: ignore
|
||||
|
||||
logs = (
|
||||
SpiffLoggingModel.query.filter(
|
||||
SpiffLoggingModel.process_instance_id == process_instance.id
|
||||
)
|
||||
.order_by(SpiffLoggingModel.timestamp.desc()) # type: ignore
|
||||
log_query.order_by(SpiffLoggingModel.timestamp.desc()) # type: ignore
|
||||
.join(
|
||||
UserModel, UserModel.id == SpiffLoggingModel.current_user_id, isouter=True
|
||||
) # isouter since if we don't have a user, we still want the log
|
||||
|
@ -642,6 +669,7 @@ def message_instance_list(
|
|||
.add_columns(
|
||||
MessageModel.identifier.label("message_identifier"),
|
||||
ProcessInstanceModel.process_model_identifier,
|
||||
ProcessInstanceModel.process_model_display_name,
|
||||
)
|
||||
.paginate(page=page, per_page=per_page, error_out=False)
|
||||
)
|
||||
|
@ -776,10 +804,11 @@ def process_instance_list(
|
|||
with_tasks_completed_by_my_group: Optional[bool] = None,
|
||||
user_filter: Optional[bool] = False,
|
||||
report_identifier: Optional[str] = None,
|
||||
report_id: Optional[int] = None,
|
||||
) -> flask.wrappers.Response:
|
||||
"""Process_instance_list."""
|
||||
process_instance_report = ProcessInstanceReportService.report_with_identifier(
|
||||
g.user, report_identifier
|
||||
g.user, report_id, report_identifier
|
||||
)
|
||||
|
||||
if user_filter:
|
||||
|
@ -810,7 +839,6 @@ def process_instance_list(
|
|||
)
|
||||
)
|
||||
|
||||
# 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(
|
||||
|
@ -928,28 +956,78 @@ def process_instance_list(
|
|||
UserGroupAssignmentModel.user_id == g.user.id
|
||||
)
|
||||
|
||||
try:
|
||||
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)
|
||||
)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
results = list(
|
||||
map(
|
||||
ProcessInstanceService.serialize_flat_with_task_data,
|
||||
process_instances.items,
|
||||
)
|
||||
instance_metadata_aliases = {}
|
||||
stock_columns = ProcessInstanceReportService.get_column_names_for_model(
|
||||
ProcessInstanceModel
|
||||
)
|
||||
for column in process_instance_report.report_metadata["columns"]:
|
||||
if column["accessor"] in stock_columns:
|
||||
continue
|
||||
instance_metadata_alias = aliased(ProcessInstanceMetadataModel)
|
||||
instance_metadata_aliases[column["accessor"]] = instance_metadata_alias
|
||||
|
||||
filter_for_column = None
|
||||
if "filter_by" in process_instance_report.report_metadata:
|
||||
filter_for_column = next(
|
||||
(
|
||||
f
|
||||
for f in process_instance_report.report_metadata["filter_by"]
|
||||
if f["field_name"] == column["accessor"]
|
||||
),
|
||||
None,
|
||||
)
|
||||
isouter = True
|
||||
conditions = [
|
||||
ProcessInstanceModel.id == instance_metadata_alias.process_instance_id,
|
||||
instance_metadata_alias.key == column["accessor"],
|
||||
]
|
||||
if filter_for_column:
|
||||
isouter = False
|
||||
conditions.append(
|
||||
instance_metadata_alias.value == filter_for_column["field_value"]
|
||||
)
|
||||
process_instance_query = process_instance_query.join(
|
||||
instance_metadata_alias, and_(*conditions), isouter=isouter
|
||||
).add_columns(func.max(instance_metadata_alias.value).label(column["accessor"]))
|
||||
|
||||
order_by_query_array = []
|
||||
order_by_array = process_instance_report.report_metadata["order_by"]
|
||||
if len(order_by_array) < 1:
|
||||
order_by_array = ProcessInstanceReportModel.default_order_by()
|
||||
for order_by_option in order_by_array:
|
||||
attribute = re.sub("^-", "", order_by_option)
|
||||
if attribute in stock_columns:
|
||||
if order_by_option.startswith("-"):
|
||||
order_by_query_array.append(
|
||||
getattr(ProcessInstanceModel, attribute).desc()
|
||||
)
|
||||
else:
|
||||
order_by_query_array.append(
|
||||
getattr(ProcessInstanceModel, attribute).asc()
|
||||
)
|
||||
elif attribute in instance_metadata_aliases:
|
||||
if order_by_option.startswith("-"):
|
||||
order_by_query_array.append(
|
||||
instance_metadata_aliases[attribute].value.desc()
|
||||
)
|
||||
else:
|
||||
order_by_query_array.append(
|
||||
instance_metadata_aliases[attribute].value.asc()
|
||||
)
|
||||
|
||||
process_instances = (
|
||||
process_instance_query.group_by(ProcessInstanceModel.id)
|
||||
.add_columns(ProcessInstanceModel.id)
|
||||
.order_by(*order_by_query_array)
|
||||
.paginate(page=page, per_page=per_page, error_out=False)
|
||||
)
|
||||
|
||||
results = ProcessInstanceReportService.add_metadata_columns_to_process_instance(
|
||||
process_instances.items, process_instance_report.report_metadata["columns"]
|
||||
)
|
||||
report_metadata = process_instance_report.report_metadata
|
||||
|
||||
response_json = {
|
||||
"report_identifier": process_instance_report.identifier,
|
||||
"report_metadata": report_metadata,
|
||||
"report": process_instance_report,
|
||||
"results": results,
|
||||
"filters": report_filter.to_dict(),
|
||||
"pagination": {
|
||||
|
@ -962,6 +1040,22 @@ def process_instance_list(
|
|||
return make_response(jsonify(response_json), 200)
|
||||
|
||||
|
||||
def process_instance_report_column_list() -> flask.wrappers.Response:
|
||||
"""Process_instance_report_column_list."""
|
||||
table_columns = ProcessInstanceReportService.builtin_column_options()
|
||||
columns_for_metadata = (
|
||||
db.session.query(ProcessInstanceMetadataModel.key)
|
||||
.order_by(ProcessInstanceMetadataModel.key)
|
||||
.distinct() # type: ignore
|
||||
.all()
|
||||
)
|
||||
columns_for_metadata_strings = [
|
||||
{"Header": i[0], "accessor": i[0], "filterable": True}
|
||||
for i in columns_for_metadata
|
||||
]
|
||||
return make_response(jsonify(table_columns + columns_for_metadata_strings), 200)
|
||||
|
||||
|
||||
def process_instance_show(
|
||||
modified_process_model_identifier: str, process_instance_id: int
|
||||
) -> flask.wrappers.Response:
|
||||
|
@ -988,7 +1082,9 @@ def process_instance_show(
|
|||
return make_response(jsonify(process_instance), 200)
|
||||
|
||||
|
||||
def process_instance_delete(process_instance_id: int) -> flask.wrappers.Response:
|
||||
def process_instance_delete(
|
||||
process_instance_id: int, modified_process_model_identifier: str
|
||||
) -> flask.wrappers.Response:
|
||||
"""Create_process_instance."""
|
||||
process_instance = find_process_instance_by_id_or_raise(process_instance_id)
|
||||
|
||||
|
@ -1018,22 +1114,22 @@ def process_instance_report_list(
|
|||
|
||||
def process_instance_report_create(body: Dict[str, Any]) -> flask.wrappers.Response:
|
||||
"""Process_instance_report_create."""
|
||||
ProcessInstanceReportModel.create_report(
|
||||
process_instance_report = ProcessInstanceReportModel.create_report(
|
||||
identifier=body["identifier"],
|
||||
user=g.user,
|
||||
report_metadata=body["report_metadata"],
|
||||
)
|
||||
|
||||
return Response(json.dumps({"ok": True}), status=200, mimetype="application/json")
|
||||
return make_response(jsonify(process_instance_report), 201)
|
||||
|
||||
|
||||
def process_instance_report_update(
|
||||
report_identifier: str,
|
||||
report_id: int,
|
||||
body: Dict[str, Any],
|
||||
) -> flask.wrappers.Response:
|
||||
"""Process_instance_report_create."""
|
||||
process_instance_report = ProcessInstanceReportModel.query.filter_by(
|
||||
identifier=report_identifier,
|
||||
id=report_id,
|
||||
created_by_id=g.user.id,
|
||||
).first()
|
||||
if process_instance_report is None:
|
||||
|
@ -1046,15 +1142,15 @@ def process_instance_report_update(
|
|||
process_instance_report.report_metadata = body["report_metadata"]
|
||||
db.session.commit()
|
||||
|
||||
return Response(json.dumps({"ok": True}), status=200, mimetype="application/json")
|
||||
return make_response(jsonify(process_instance_report), 201)
|
||||
|
||||
|
||||
def process_instance_report_delete(
|
||||
report_identifier: str,
|
||||
report_id: int,
|
||||
) -> flask.wrappers.Response:
|
||||
"""Process_instance_report_create."""
|
||||
process_instance_report = ProcessInstanceReportModel.query.filter_by(
|
||||
identifier=report_identifier,
|
||||
id=report_id,
|
||||
created_by_id=g.user.id,
|
||||
).first()
|
||||
if process_instance_report is None:
|
||||
|
@ -1070,11 +1166,9 @@ def process_instance_report_delete(
|
|||
return Response(json.dumps({"ok": True}), status=200, mimetype="application/json")
|
||||
|
||||
|
||||
def service_tasks_show() -> flask.wrappers.Response:
|
||||
"""Service_tasks_show."""
|
||||
def service_task_list() -> flask.wrappers.Response:
|
||||
"""Service_task_list."""
|
||||
available_connectors = ServiceTaskService.available_connectors()
|
||||
print(available_connectors)
|
||||
|
||||
return Response(
|
||||
json.dumps(available_connectors), status=200, mimetype="application/json"
|
||||
)
|
||||
|
@ -1108,19 +1202,17 @@ def authentication_callback(
|
|||
|
||||
|
||||
def process_instance_report_show(
|
||||
report_identifier: str,
|
||||
report_id: int,
|
||||
page: int = 1,
|
||||
per_page: int = 100,
|
||||
) -> flask.wrappers.Response:
|
||||
"""Process_instance_list."""
|
||||
process_instances = ProcessInstanceModel.query.order_by( # .filter_by(process_model_identifier=process_model.id)
|
||||
"""Process_instance_report_show."""
|
||||
process_instances = ProcessInstanceModel.query.order_by(
|
||||
ProcessInstanceModel.start_in_seconds.desc(), ProcessInstanceModel.id.desc() # type: ignore
|
||||
).paginate(
|
||||
page=page, per_page=per_page, error_out=False
|
||||
)
|
||||
).paginate(page=page, per_page=per_page, error_out=False)
|
||||
|
||||
process_instance_report = ProcessInstanceReportModel.query.filter_by(
|
||||
identifier=report_identifier,
|
||||
id=report_id,
|
||||
created_by_id=g.user.id,
|
||||
).first()
|
||||
if process_instance_report is None:
|
||||
|
@ -1282,7 +1374,7 @@ def get_tasks(
|
|||
|
||||
|
||||
def process_instance_task_list(
|
||||
modified_process_model_id: str,
|
||||
modified_process_model_identifier: str,
|
||||
process_instance_id: int,
|
||||
all_tasks: bool = False,
|
||||
spiff_step: int = 0,
|
||||
|
@ -1397,9 +1489,6 @@ def task_show(process_instance_id: int, task_id: str) -> flask.wrappers.Response
|
|||
task.form_ui_schema = ui_form_contents
|
||||
|
||||
if task.properties and task.data and "instructionsForEndUser" in task.properties:
|
||||
print(
|
||||
f"task.properties['instructionsForEndUser']: {task.properties['instructionsForEndUser']}"
|
||||
)
|
||||
if task.properties["instructionsForEndUser"]:
|
||||
task.properties["instructionsForEndUser"] = render_jinja_template(
|
||||
task.properties["instructionsForEndUser"], task.data
|
||||
|
@ -1846,7 +1935,12 @@ def _update_form_schema_with_task_data_as_needed(
|
|||
_update_form_schema_with_task_data_as_needed(o, task_data)
|
||||
|
||||
|
||||
def update_task_data(process_instance_id: str, task_id: str, body: Dict) -> Response:
|
||||
def update_task_data(
|
||||
process_instance_id: str,
|
||||
modified_process_model_identifier: str,
|
||||
task_id: str,
|
||||
body: Dict,
|
||||
) -> Response:
|
||||
"""Update task data."""
|
||||
process_instance = ProcessInstanceModel.query.filter(
|
||||
ProcessInstanceModel.id == int(process_instance_id)
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
"""Get_env."""
|
||||
"""Save process instance metadata."""
|
||||
from typing import Any
|
||||
|
||||
from flask_bpmn.models.db import db
|
||||
from typing import Any
|
||||
|
||||
|
|
|
@ -186,8 +186,9 @@ class AuthenticationService:
|
|||
refresh_token_object: RefreshTokenModel = RefreshTokenModel.query.filter(
|
||||
RefreshTokenModel.user_id == user_id
|
||||
).first()
|
||||
assert refresh_token_object # noqa: S101
|
||||
return refresh_token_object.token
|
||||
if refresh_token_object:
|
||||
return refresh_token_object.token
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def get_auth_token_from_refresh_token(cls, refresh_token: str) -> dict:
|
||||
|
|
|
@ -81,6 +81,9 @@ from spiffworkflow_backend.models.message_instance import MessageInstanceModel
|
|||
from spiffworkflow_backend.models.message_instance import MessageModel
|
||||
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
|
||||
from spiffworkflow_backend.models.process_instance import ProcessInstanceStatus
|
||||
from spiffworkflow_backend.models.process_instance_metadata import (
|
||||
ProcessInstanceMetadataModel,
|
||||
)
|
||||
from spiffworkflow_backend.models.process_model import ProcessModelInfo
|
||||
from spiffworkflow_backend.models.script_attributes_context import (
|
||||
ScriptAttributesContext,
|
||||
|
@ -179,7 +182,12 @@ class CustomBpmnScriptEngine(PythonScriptEngine): # type: ignore
|
|||
)
|
||||
return Script.generate_augmented_list(script_attributes_context)
|
||||
|
||||
def evaluate(self, task: SpiffTask, expression: str, external_methods: Any = None) -> Any:
|
||||
def evaluate(
|
||||
self,
|
||||
task: SpiffTask,
|
||||
expression: str,
|
||||
external_methods: Optional[dict[str, Any]] = None,
|
||||
) -> Any:
|
||||
"""Evaluate."""
|
||||
return self._evaluate(expression, task.data, task, external_methods)
|
||||
|
||||
|
@ -572,6 +580,36 @@ class ProcessInstanceProcessor:
|
|||
db.session.add(details_model)
|
||||
db.session.commit()
|
||||
|
||||
def extract_metadata(self, process_model_info: ProcessModelInfo) -> None:
|
||||
"""Extract_metadata."""
|
||||
metadata_extraction_paths = process_model_info.metadata_extraction_paths
|
||||
if metadata_extraction_paths is None:
|
||||
return
|
||||
if len(metadata_extraction_paths) <= 0:
|
||||
return
|
||||
|
||||
current_data = self.get_current_data()
|
||||
for metadata_extraction_path in metadata_extraction_paths:
|
||||
key = metadata_extraction_path["key"]
|
||||
path = metadata_extraction_path["path"]
|
||||
path_segments = path.split(".")
|
||||
data_for_key = current_data
|
||||
for path_segment in path_segments:
|
||||
data_for_key = data_for_key[path_segment]
|
||||
|
||||
pim = ProcessInstanceMetadataModel.query.filter_by(
|
||||
process_instance_id=self.process_instance_model.id,
|
||||
key=key,
|
||||
).first()
|
||||
if pim is None:
|
||||
pim = ProcessInstanceMetadataModel(
|
||||
process_instance_id=self.process_instance_model.id,
|
||||
key=key,
|
||||
)
|
||||
pim.value = data_for_key
|
||||
db.session.add(pim)
|
||||
db.session.commit()
|
||||
|
||||
def save(self) -> None:
|
||||
"""Saves the current state of this processor to the database."""
|
||||
self.process_instance_model.bpmn_json = self.serialize()
|
||||
|
@ -598,6 +636,15 @@ class ProcessInstanceProcessor:
|
|||
process_instance_id=self.process_instance_model.id
|
||||
).all()
|
||||
ready_or_waiting_tasks = self.get_all_ready_or_waiting_tasks()
|
||||
process_model_display_name = ""
|
||||
process_model_info = self.process_model_service.get_process_model(
|
||||
self.process_instance_model.process_model_identifier
|
||||
)
|
||||
if process_model_info is not None:
|
||||
process_model_display_name = process_model_info.display_name
|
||||
|
||||
self.extract_metadata(process_model_info)
|
||||
|
||||
for ready_or_waiting_task in ready_or_waiting_tasks:
|
||||
# filter out non-usertasks
|
||||
task_spec = ready_or_waiting_task.task_spec
|
||||
|
@ -616,13 +663,6 @@ class ProcessInstanceProcessor:
|
|||
if "formUiSchemaFilename" in properties:
|
||||
ui_form_file_name = properties["formUiSchemaFilename"]
|
||||
|
||||
process_model_display_name = ""
|
||||
process_model_info = self.process_model_service.get_process_model(
|
||||
self.process_instance_model.process_model_identifier
|
||||
)
|
||||
if process_model_info is not None:
|
||||
process_model_display_name = process_model_info.display_name
|
||||
|
||||
active_task = None
|
||||
for at in active_tasks:
|
||||
if at.task_id == str(ready_or_waiting_task.id):
|
||||
|
@ -1147,8 +1187,8 @@ class ProcessInstanceProcessor:
|
|||
def get_current_data(self) -> dict[str, Any]:
|
||||
"""Get the current data for the process.
|
||||
|
||||
Return either most recent task data or the process data
|
||||
if the process instance is complete
|
||||
Return either the most recent task data or--if the process instance is complete--
|
||||
the process data.
|
||||
"""
|
||||
if self.process_instance_model.status == "complete":
|
||||
return self.get_data()
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
|
||||
import sqlalchemy
|
||||
from flask_bpmn.models.db import db
|
||||
|
||||
from spiffworkflow_backend.models.process_instance_report import (
|
||||
ProcessInstanceReportModel,
|
||||
)
|
||||
|
@ -57,12 +60,21 @@ class ProcessInstanceReportService:
|
|||
|
||||
@classmethod
|
||||
def report_with_identifier(
|
||||
cls, user: UserModel, report_identifier: Optional[str] = None
|
||||
cls,
|
||||
user: UserModel,
|
||||
report_id: Optional[int] = None,
|
||||
report_identifier: Optional[str] = None,
|
||||
) -> ProcessInstanceReportModel:
|
||||
"""Report_with_filter."""
|
||||
if report_id is not None:
|
||||
process_instance_report = ProcessInstanceReportModel.query.filter_by(
|
||||
id=report_id, created_by_id=user.id
|
||||
).first()
|
||||
if process_instance_report is not None:
|
||||
return process_instance_report # type: ignore
|
||||
|
||||
if report_identifier is None:
|
||||
report_identifier = "default"
|
||||
|
||||
process_instance_report = ProcessInstanceReportModel.query.filter_by(
|
||||
identifier=report_identifier, created_by_id=user.id
|
||||
).first()
|
||||
|
@ -73,17 +85,9 @@ class ProcessInstanceReportService:
|
|||
# TODO replace with system reports that are loaded on launch (or similar)
|
||||
temp_system_metadata_map = {
|
||||
"default": {
|
||||
"columns": [
|
||||
{"Header": "id", "accessor": "id"},
|
||||
{
|
||||
"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"},
|
||||
],
|
||||
"columns": cls.builtin_column_options(),
|
||||
"filter_by": [],
|
||||
"order_by": ["-start_in_seconds", "-id"],
|
||||
},
|
||||
"system_report_instances_initiated_by_me": {
|
||||
"columns": [
|
||||
|
@ -97,48 +101,31 @@ class ProcessInstanceReportService:
|
|||
{"Header": "status", "accessor": "status"},
|
||||
],
|
||||
"filter_by": [{"field_name": "initiated_by_me", "field_value": True}],
|
||||
"order_by": ["-start_in_seconds", "-id"],
|
||||
},
|
||||
"system_report_instances_with_tasks_completed_by_me": {
|
||||
"columns": [
|
||||
{"Header": "id", "accessor": "id"},
|
||||
{
|
||||
"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"},
|
||||
],
|
||||
"columns": cls.builtin_column_options(),
|
||||
"filter_by": [
|
||||
{"field_name": "with_tasks_completed_by_me", "field_value": True}
|
||||
],
|
||||
"order_by": ["-start_in_seconds", "-id"],
|
||||
},
|
||||
"system_report_instances_with_tasks_completed_by_my_groups": {
|
||||
"columns": [
|
||||
{"Header": "id", "accessor": "id"},
|
||||
{
|
||||
"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"},
|
||||
],
|
||||
"columns": cls.builtin_column_options(),
|
||||
"filter_by": [
|
||||
{
|
||||
"field_name": "with_tasks_completed_by_my_group",
|
||||
"field_value": True,
|
||||
}
|
||||
],
|
||||
"order_by": ["-start_in_seconds", "-id"],
|
||||
},
|
||||
}
|
||||
|
||||
process_instance_report = ProcessInstanceReportModel(
|
||||
identifier=report_identifier,
|
||||
created_by_id=user.id,
|
||||
report_metadata=temp_system_metadata_map[report_identifier], # type: ignore
|
||||
report_metadata=temp_system_metadata_map[report_identifier],
|
||||
)
|
||||
|
||||
return process_instance_report # type: ignore
|
||||
|
@ -241,3 +228,43 @@ class ProcessInstanceReportService:
|
|||
)
|
||||
|
||||
return report_filter
|
||||
|
||||
@classmethod
|
||||
def add_metadata_columns_to_process_instance(
|
||||
cls,
|
||||
process_instance_sqlalchemy_rows: list[sqlalchemy.engine.row.Row], # type: ignore
|
||||
metadata_columns: list[dict],
|
||||
) -> list[dict]:
|
||||
"""Add_metadata_columns_to_process_instance."""
|
||||
results = []
|
||||
for process_instance in process_instance_sqlalchemy_rows:
|
||||
process_instance_dict = process_instance["ProcessInstanceModel"].serialized
|
||||
for metadata_column in metadata_columns:
|
||||
if metadata_column["accessor"] not in process_instance_dict:
|
||||
process_instance_dict[
|
||||
metadata_column["accessor"]
|
||||
] = process_instance[metadata_column["accessor"]]
|
||||
|
||||
results.append(process_instance_dict)
|
||||
return results
|
||||
|
||||
@classmethod
|
||||
def get_column_names_for_model(cls, model: db.Model) -> list[str]: # type: ignore
|
||||
"""Get_column_names_for_model."""
|
||||
return [i.name for i in model.__table__.columns]
|
||||
|
||||
@classmethod
|
||||
def builtin_column_options(cls) -> list[dict]:
|
||||
"""Builtin_column_options."""
|
||||
return [
|
||||
{"Header": "Id", "accessor": "id", "filterable": False},
|
||||
{
|
||||
"Header": "Process",
|
||||
"accessor": "process_model_display_name",
|
||||
"filterable": False,
|
||||
},
|
||||
{"Header": "Start", "accessor": "start_in_seconds", "filterable": False},
|
||||
{"Header": "End", "accessor": "end_in_seconds", "filterable": False},
|
||||
{"Header": "Username", "accessor": "username", "filterable": False},
|
||||
{"Header": "Status", "accessor": "status", "filterable": False},
|
||||
]
|
||||
|
|
|
@ -322,18 +322,3 @@ class ProcessInstanceService:
|
|||
)
|
||||
|
||||
return task
|
||||
|
||||
@staticmethod
|
||||
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
|
||||
return results
|
||||
|
|
|
@ -19,7 +19,11 @@
|
|||
<bpmn:scriptTask id="hot_script_task_OH_YEEEEEEEEEEEEEEEEEEEEAH" name="OHHHHHHHHHHYEEEESSSSSSSSSS">
|
||||
<bpmn:incoming>Flow_0bazl8x</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_1mcaszp</bpmn:outgoing>
|
||||
<bpmn:script>a = 1</bpmn:script>
|
||||
<bpmn:script>a = 1
|
||||
b = 2
|
||||
outer = {}
|
||||
outer["inner"] = 'sweet1'
|
||||
</bpmn:script>
|
||||
</bpmn:scriptTask>
|
||||
<bpmn:endEvent id="Event_1vch1y0">
|
||||
<bpmn:incoming>Flow_1mcaszp</bpmn:incoming>
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
<?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="Process_hk6nsfl" isExecutable="true">
|
||||
<bpmn:startEvent id="StartEvent_1">
|
||||
<bpmn:outgoing>Flow_1ohrjz9</bpmn:outgoing>
|
||||
</bpmn:startEvent>
|
||||
<bpmn:sequenceFlow id="Flow_1ohrjz9" sourceRef="StartEvent_1" targetRef="Activity_0fah9rm" />
|
||||
<bpmn:endEvent id="Event_1tk4dsv">
|
||||
<bpmn:incoming>Flow_1flxgry</bpmn:incoming>
|
||||
</bpmn:endEvent>
|
||||
<bpmn:sequenceFlow id="Flow_18gs4jt" sourceRef="Activity_0fah9rm" targetRef="Activity_1bvyv67" />
|
||||
<bpmn:scriptTask id="Activity_0fah9rm" name="First setting of data">
|
||||
<bpmn:incoming>Flow_1ohrjz9</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_18gs4jt</bpmn:outgoing>
|
||||
<bpmn:script>outer = {}
|
||||
invoice_number = 123
|
||||
outer["inner"] = 'sweet1'
|
||||
outer['time'] = time.time_ns()</bpmn:script>
|
||||
</bpmn:scriptTask>
|
||||
<bpmn:sequenceFlow id="Flow_1flxgry" sourceRef="Activity_1bvyv67" targetRef="Event_1tk4dsv" />
|
||||
<bpmn:scriptTask id="Activity_1bvyv67" name="First setting of data">
|
||||
<bpmn:incoming>Flow_18gs4jt</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_1flxgry</bpmn:outgoing>
|
||||
<bpmn:script>outer["inner"] = 'sweet2'</bpmn:script>
|
||||
</bpmn:scriptTask>
|
||||
</bpmn:process>
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_hk6nsfl">
|
||||
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
|
||||
<dc:Bounds x="179" y="159" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_1c5bi8c_di" bpmnElement="Activity_0fah9rm">
|
||||
<dc:Bounds x="270" y="137" width="100" height="80" />
|
||||
<bpmndi:BPMNLabel />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Event_1tk4dsv_di" bpmnElement="Event_1tk4dsv">
|
||||
<dc:Bounds x="612" y="159" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_1ay4o3w_di" bpmnElement="Activity_1bvyv67">
|
||||
<dc:Bounds x="430" y="137" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge id="Flow_1ohrjz9_di" bpmnElement="Flow_1ohrjz9">
|
||||
<di:waypoint x="215" y="177" />
|
||||
<di:waypoint x="270" y="177" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_18gs4jt_di" bpmnElement="Flow_18gs4jt">
|
||||
<di:waypoint x="370" y="177" />
|
||||
<di:waypoint x="430" y="177" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_1flxgry_di" bpmnElement="Flow_1flxgry">
|
||||
<di:waypoint x="530" y="177" />
|
||||
<di:waypoint x="612" y="177" />
|
||||
</bpmndi:BPMNEdge>
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
</bpmn:definitions>
|
|
@ -265,7 +265,7 @@ class BaseTest:
|
|||
)
|
||||
modified_process_model_id = test_process_model_id.replace("/", ":")
|
||||
response = client.post(
|
||||
f"/v1.0/process-models/{modified_process_model_id}/process-instances",
|
||||
f"/v1.0/process-instances/{modified_process_model_id}",
|
||||
headers=headers,
|
||||
)
|
||||
assert response.status_code == 201
|
||||
|
|
|
@ -57,7 +57,7 @@ class TestLoggingService(BaseTest):
|
|||
assert response.status_code == 200
|
||||
|
||||
log_response = client.get(
|
||||
f"/v1.0/process-instances/{process_instance_id}/logs",
|
||||
f"/v1.0/logs/{self.modify_process_identifier_for_path_param(process_model_identifier)}/{process_instance_id}",
|
||||
headers=headers,
|
||||
)
|
||||
assert log_response.status_code == 200
|
||||
|
|
|
@ -20,6 +20,9 @@ from spiffworkflow_backend.models.group import GroupModel
|
|||
from spiffworkflow_backend.models.process_group import ProcessGroup
|
||||
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
|
||||
from spiffworkflow_backend.models.process_instance import ProcessInstanceStatus
|
||||
from spiffworkflow_backend.models.process_instance_metadata import (
|
||||
ProcessInstanceMetadataModel,
|
||||
)
|
||||
from spiffworkflow_backend.models.process_instance_report import (
|
||||
ProcessInstanceReportModel,
|
||||
)
|
||||
|
@ -330,6 +333,9 @@ class TestProcessApi(BaseTest):
|
|||
process_model.display_name = "Updated Display Name"
|
||||
process_model.primary_file_name = "superduper.bpmn"
|
||||
process_model.primary_process_id = "superduper"
|
||||
process_model.metadata_extraction_paths = [
|
||||
{"key": "extraction1", "path": "path1"}
|
||||
]
|
||||
|
||||
modified_process_model_identifier = process_model_identifier.replace("/", ":")
|
||||
response = client.put(
|
||||
|
@ -343,6 +349,9 @@ 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["metadata_extraction_paths"] == [
|
||||
{"key": "extraction1", "path": "path1"}
|
||||
]
|
||||
|
||||
def test_process_model_list_all(
|
||||
self,
|
||||
|
@ -903,7 +912,7 @@ class TestProcessApi(BaseTest):
|
|||
modified_process_model_identifier = process_model_identifier.replace("/", ":")
|
||||
|
||||
response = client.post(
|
||||
f"/v1.0/process-models/{modified_process_model_identifier}/process-instances",
|
||||
f"/v1.0/process-instances/{modified_process_model_identifier}",
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
assert response.status_code == 201
|
||||
|
@ -1145,10 +1154,11 @@ class TestProcessApi(BaseTest):
|
|||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
show_response = client.get(
|
||||
f"/v1.0/process-models/{modified_process_model_identifier}/process-instances/{process_instance_id}",
|
||||
f"/v1.0/process-instances/{modified_process_model_identifier}/{process_instance_id}",
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
assert show_response.json is not None
|
||||
assert show_response.status_code == 200
|
||||
file_system_root = FileSystemService.root_path()
|
||||
file_path = (
|
||||
f"{file_system_root}/{process_model_identifier}/{process_model_id}.bpmn"
|
||||
|
@ -1311,7 +1321,7 @@ class TestProcessApi(BaseTest):
|
|||
assert response.json is not None
|
||||
|
||||
response = client.post(
|
||||
f"/v1.0/process-instances/{process_instance_id}/terminate",
|
||||
f"/v1.0/process-instances/{self.modify_process_identifier_for_path_param(process_model_identifier)}/{process_instance_id}/terminate",
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
@ -1358,7 +1368,7 @@ class TestProcessApi(BaseTest):
|
|||
assert response.json is not None
|
||||
|
||||
delete_response = client.delete(
|
||||
f"/v1.0/process-instances/{process_instance_id}",
|
||||
f"/v1.0/process-instances/{self.modify_process_identifier_for_path_param(process_model_identifier)}/{process_instance_id}",
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
assert delete_response.status_code == 200
|
||||
|
@ -1723,14 +1733,14 @@ class TestProcessApi(BaseTest):
|
|||
],
|
||||
}
|
||||
|
||||
ProcessInstanceReportModel.create_with_attributes(
|
||||
report = ProcessInstanceReportModel.create_with_attributes(
|
||||
identifier="sure",
|
||||
report_metadata=report_metadata,
|
||||
user=with_super_admin_user,
|
||||
)
|
||||
|
||||
response = client.get(
|
||||
"/v1.0/process-instances/reports/sure",
|
||||
f"/v1.0/process-instances/reports/{report.id}",
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
@ -1769,14 +1779,14 @@ class TestProcessApi(BaseTest):
|
|||
],
|
||||
}
|
||||
|
||||
ProcessInstanceReportModel.create_with_attributes(
|
||||
report = ProcessInstanceReportModel.create_with_attributes(
|
||||
identifier="sure",
|
||||
report_metadata=report_metadata,
|
||||
user=with_super_admin_user,
|
||||
)
|
||||
|
||||
response = client.get(
|
||||
"/v1.0/process-instances/reports/sure?grade_level=1",
|
||||
f"/v1.0/process-instances/reports/{report.id}?grade_level=1",
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
@ -1791,9 +1801,9 @@ class TestProcessApi(BaseTest):
|
|||
with_super_admin_user: UserModel,
|
||||
setup_process_instances_for_reports: list[ProcessInstanceModel],
|
||||
) -> None:
|
||||
"""Test_process_instance_report_show_with_default_list."""
|
||||
"""Test_process_instance_report_show_with_bad_identifier."""
|
||||
response = client.get(
|
||||
"/v1.0/process-instances/reports/sure?grade_level=1",
|
||||
"/v1.0/process-instances/reports/13000000?grade_level=1",
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
assert response.status_code == 404
|
||||
|
@ -2357,7 +2367,7 @@ class TestProcessApi(BaseTest):
|
|||
assert process_instance.status == "user_input_required"
|
||||
|
||||
client.post(
|
||||
f"/v1.0/process-instances/{process_instance_id}/suspend",
|
||||
f"/v1.0/process-instances/{self.modify_process_identifier_for_path_param(process_model_identifier)}/{process_instance_id}/suspend",
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
process_instance = ProcessInstanceService().get_process_instance(
|
||||
|
@ -2544,3 +2554,191 @@ class TestProcessApi(BaseTest):
|
|||
# make sure the new subgroup does exist
|
||||
new_process_group = ProcessModelService.get_process_group(new_sub_path)
|
||||
assert new_process_group.id == new_sub_path
|
||||
|
||||
def test_can_get_process_instance_list_with_report_metadata(
|
||||
self,
|
||||
app: Flask,
|
||||
client: FlaskClient,
|
||||
with_db_and_bpmn_file_cleanup: None,
|
||||
with_super_admin_user: UserModel,
|
||||
) -> None:
|
||||
"""Test_can_get_process_instance_list_with_report_metadata."""
|
||||
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=with_super_admin_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
|
||||
|
||||
report_metadata = {
|
||||
"columns": [
|
||||
{"Header": "ID", "accessor": "id"},
|
||||
{"Header": "Status", "accessor": "status"},
|
||||
{"Header": "Key One", "accessor": "key1"},
|
||||
{"Header": "Key Two", "accessor": "key2"},
|
||||
],
|
||||
"order_by": ["status"],
|
||||
"filter_by": [],
|
||||
}
|
||||
process_instance_report = ProcessInstanceReportModel.create_with_attributes(
|
||||
identifier="sure",
|
||||
report_metadata=report_metadata,
|
||||
user=with_super_admin_user,
|
||||
)
|
||||
|
||||
response = client.get(
|
||||
f"/v1.0/process-instances?report_identifier={process_instance_report.identifier}",
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
|
||||
assert response.json is not None
|
||||
assert response.status_code == 200
|
||||
|
||||
assert len(response.json["results"]) == 1
|
||||
assert response.json["results"][0]["status"] == "complete"
|
||||
assert response.json["results"][0]["id"] == process_instance.id
|
||||
assert response.json["results"][0]["key1"] == "value1"
|
||||
assert response.json["results"][0]["key2"] == "value2"
|
||||
assert response.json["pagination"]["count"] == 1
|
||||
assert response.json["pagination"]["pages"] == 1
|
||||
assert response.json["pagination"]["total"] == 1
|
||||
|
||||
def test_can_get_process_instance_report_column_list(
|
||||
self,
|
||||
app: Flask,
|
||||
client: FlaskClient,
|
||||
with_db_and_bpmn_file_cleanup: None,
|
||||
with_super_admin_user: UserModel,
|
||||
) -> None:
|
||||
"""Test_can_get_process_instance_list_with_report_metadata."""
|
||||
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=with_super_admin_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
|
||||
|
||||
response = client.get(
|
||||
"/v1.0/process-instances/reports/columns",
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
|
||||
assert response.json is not None
|
||||
assert response.status_code == 200
|
||||
assert response.json == [
|
||||
{"Header": "Id", "accessor": "id", "filterable": False},
|
||||
{
|
||||
"Header": "Process",
|
||||
"accessor": "process_model_display_name",
|
||||
"filterable": False,
|
||||
},
|
||||
{"Header": "Start", "accessor": "start_in_seconds", "filterable": False},
|
||||
{"Header": "End", "accessor": "end_in_seconds", "filterable": False},
|
||||
{"Header": "Username", "accessor": "username", "filterable": False},
|
||||
{"Header": "Status", "accessor": "status", "filterable": False},
|
||||
{"Header": "key1", "accessor": "key1", "filterable": True},
|
||||
{"Header": "key2", "accessor": "key2", "filterable": True},
|
||||
{"Header": "key3", "accessor": "key3", "filterable": True},
|
||||
]
|
||||
|
||||
def test_process_instance_list_can_order_by_metadata(
|
||||
self,
|
||||
app: Flask,
|
||||
client: FlaskClient,
|
||||
with_db_and_bpmn_file_cleanup: None,
|
||||
with_super_admin_user: UserModel,
|
||||
) -> None:
|
||||
"""Test_process_instance_list_can_order_by_metadata."""
|
||||
self.create_process_group(
|
||||
client, with_super_admin_user, "test_group", "test_group"
|
||||
)
|
||||
process_model = load_test_spec(
|
||||
"test_group/hello_world",
|
||||
process_model_source_directory="nested-task-data-structure",
|
||||
)
|
||||
ProcessModelService.update_process_model(
|
||||
process_model,
|
||||
{
|
||||
"metadata_extraction_paths": [
|
||||
{"key": "time_ns", "path": "outer.time"},
|
||||
]
|
||||
},
|
||||
)
|
||||
|
||||
process_instance_one = self.create_process_instance_from_process_model(
|
||||
process_model
|
||||
)
|
||||
processor = ProcessInstanceProcessor(process_instance_one)
|
||||
processor.do_engine_steps(save=True)
|
||||
assert process_instance_one.status == "complete"
|
||||
process_instance_two = self.create_process_instance_from_process_model(
|
||||
process_model
|
||||
)
|
||||
processor = ProcessInstanceProcessor(process_instance_two)
|
||||
processor.do_engine_steps(save=True)
|
||||
assert process_instance_two.status == "complete"
|
||||
|
||||
report_metadata = {
|
||||
"columns": [
|
||||
{"Header": "id", "accessor": "id"},
|
||||
{"Header": "Time", "accessor": "time_ns"},
|
||||
],
|
||||
"order_by": ["time_ns"],
|
||||
}
|
||||
report_one = ProcessInstanceReportModel.create_with_attributes(
|
||||
identifier="report_one",
|
||||
report_metadata=report_metadata,
|
||||
user=with_super_admin_user,
|
||||
)
|
||||
|
||||
response = client.get(
|
||||
f"/v1.0/process-instances?report_id={report_one.id}",
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json is not None
|
||||
assert len(response.json["results"]) == 2
|
||||
assert response.json["results"][0]["id"] == process_instance_one.id
|
||||
assert response.json["results"][1]["id"] == process_instance_two.id
|
||||
|
||||
report_metadata = {
|
||||
"columns": [
|
||||
{"Header": "id", "accessor": "id"},
|
||||
{"Header": "Time", "accessor": "time_ns"},
|
||||
],
|
||||
"order_by": ["-time_ns"],
|
||||
}
|
||||
report_two = ProcessInstanceReportModel.create_with_attributes(
|
||||
identifier="report_two",
|
||||
report_metadata=report_metadata,
|
||||
user=with_super_admin_user,
|
||||
)
|
||||
|
||||
response = client.get(
|
||||
f"/v1.0/process-instances?report_id={report_two.id}",
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json is not None
|
||||
assert len(response.json["results"]) == 2
|
||||
assert response.json["results"][1]["id"] == process_instance_one.id
|
||||
assert response.json["results"][0]["id"] == process_instance_two.id
|
||||
|
|
|
@ -37,7 +37,7 @@ def test_generate_report_with_filter_by_with_variable_substitution(
|
|||
with_db_and_bpmn_file_cleanup: None,
|
||||
setup_process_instances_for_reports: list[ProcessInstanceModel],
|
||||
) -> None:
|
||||
"""Test_user_can_be_given_permission_to_administer_process_group."""
|
||||
"""Test_generate_report_with_filter_by_with_variable_substitution."""
|
||||
process_instances = setup_process_instances_for_reports
|
||||
report_metadata = {
|
||||
"filter_by": [
|
||||
|
@ -61,7 +61,7 @@ def test_generate_report_with_order_by_and_one_field(
|
|||
with_db_and_bpmn_file_cleanup: None,
|
||||
setup_process_instances_for_reports: list[ProcessInstanceModel],
|
||||
) -> None:
|
||||
"""Test_user_can_be_given_permission_to_administer_process_group."""
|
||||
"""Test_generate_report_with_order_by_and_one_field."""
|
||||
process_instances = setup_process_instances_for_reports
|
||||
report_metadata = {"order_by": ["test_score"]}
|
||||
results = do_report_with_metadata_and_instances(report_metadata, process_instances)
|
||||
|
@ -75,7 +75,7 @@ def test_generate_report_with_order_by_and_two_fields(
|
|||
with_db_and_bpmn_file_cleanup: None,
|
||||
setup_process_instances_for_reports: list[ProcessInstanceModel],
|
||||
) -> None:
|
||||
"""Test_user_can_be_given_permission_to_administer_process_group."""
|
||||
"""Test_generate_report_with_order_by_and_two_fields."""
|
||||
process_instances = setup_process_instances_for_reports
|
||||
report_metadata = {"order_by": ["grade_level", "test_score"]}
|
||||
results = do_report_with_metadata_and_instances(report_metadata, process_instances)
|
||||
|
@ -89,7 +89,7 @@ def test_generate_report_with_order_by_desc(
|
|||
with_db_and_bpmn_file_cleanup: None,
|
||||
setup_process_instances_for_reports: list[ProcessInstanceModel],
|
||||
) -> None:
|
||||
"""Test_user_can_be_given_permission_to_administer_process_group."""
|
||||
"""Test_generate_report_with_order_by_desc."""
|
||||
process_instances = setup_process_instances_for_reports
|
||||
report_metadata = {"order_by": ["grade_level", "-test_score"]}
|
||||
results = do_report_with_metadata_and_instances(report_metadata, process_instances)
|
||||
|
@ -103,7 +103,7 @@ def test_generate_report_with_columns(
|
|||
with_db_and_bpmn_file_cleanup: None,
|
||||
setup_process_instances_for_reports: list[ProcessInstanceModel],
|
||||
) -> None:
|
||||
"""Test_user_can_be_given_permission_to_administer_process_group."""
|
||||
"""Test_generate_report_with_columns."""
|
||||
process_instances = setup_process_instances_for_reports
|
||||
report_metadata = {
|
||||
"columns": [
|
||||
|
|
|
@ -6,12 +6,16 @@ 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.process_model import ProcessModelInfo
|
||||
from spiffworkflow_backend.models.spec_reference import SpecReferenceCache
|
||||
from spiffworkflow_backend.models.user import UserModel
|
||||
from spiffworkflow_backend.services.process_instance_processor import (
|
||||
ProcessInstanceProcessor,
|
||||
)
|
||||
from spiffworkflow_backend.services.process_model_service import ProcessModelService
|
||||
|
||||
|
||||
class TestProcessModel(BaseTest):
|
||||
|
@ -123,6 +127,53 @@ class TestProcessModel(BaseTest):
|
|||
processor.do_engine_steps(save=True)
|
||||
assert process_instance.status == "complete"
|
||||
|
||||
def test_extract_metadata(
|
||||
self,
|
||||
app: Flask,
|
||||
client: FlaskClient,
|
||||
with_db_and_bpmn_file_cleanup: None,
|
||||
with_super_admin_user: UserModel,
|
||||
) -> None:
|
||||
"""Test_can_run_process_model_with_call_activities."""
|
||||
self.create_process_group(
|
||||
client, with_super_admin_user, "test_group", "test_group"
|
||||
)
|
||||
process_model = load_test_spec(
|
||||
"test_group/hello_world",
|
||||
process_model_source_directory="nested-task-data-structure",
|
||||
)
|
||||
ProcessModelService.update_process_model(
|
||||
process_model,
|
||||
{
|
||||
"metadata_extraction_paths": [
|
||||
{"key": "awesome_var", "path": "outer.inner"},
|
||||
{"key": "invoice_number", "path": "invoice_number"},
|
||||
]
|
||||
},
|
||||
)
|
||||
|
||||
process_instance = self.create_process_instance_from_process_model(
|
||||
process_model
|
||||
)
|
||||
processor = ProcessInstanceProcessor(process_instance)
|
||||
processor.do_engine_steps(save=True)
|
||||
assert process_instance.status == "complete"
|
||||
|
||||
process_instance_metadata_awesome_var = (
|
||||
ProcessInstanceMetadataModel.query.filter_by(
|
||||
process_instance_id=process_instance.id, key="awesome_var"
|
||||
).first()
|
||||
)
|
||||
assert process_instance_metadata_awesome_var is not None
|
||||
assert process_instance_metadata_awesome_var.value == "sweet2"
|
||||
process_instance_metadata_awesome_var = (
|
||||
ProcessInstanceMetadataModel.query.filter_by(
|
||||
process_instance_id=process_instance.id, key="invoice_number"
|
||||
).first()
|
||||
)
|
||||
assert process_instance_metadata_awesome_var is not None
|
||||
assert process_instance_metadata_awesome_var.value == "123"
|
||||
|
||||
def create_test_process_model(self, id: str, display_name: str) -> ProcessModelInfo:
|
||||
"""Create_test_process_model."""
|
||||
return ProcessModelInfo(
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
import { Link } from 'react-router-dom';
|
||||
import { modifyProcessIdentifierForPathParam } from '../helpers';
|
||||
import { MessageInstance, ProcessInstance } from '../interfaces';
|
||||
|
||||
export function FormatProcessModelDisplayName(
|
||||
instanceObject: ProcessInstance | MessageInstance
|
||||
) {
|
||||
const {
|
||||
process_model_identifier: processModelIdentifier,
|
||||
process_model_display_name: processModelDisplayName,
|
||||
} = instanceObject;
|
||||
return (
|
||||
<Link
|
||||
to={`/admin/process-models/${modifyProcessIdentifierForPathParam(
|
||||
processModelIdentifier
|
||||
)}`}
|
||||
title={processModelIdentifier}
|
||||
>
|
||||
{processModelDisplayName}
|
||||
</Link>
|
||||
);
|
||||
}
|
|
@ -115,7 +115,6 @@ export default function ProcessGroupForm({
|
|||
labelText="Display Name*"
|
||||
value={processGroup.display_name}
|
||||
onChange={(event: any) => onDisplayNameChanged(event.target.value)}
|
||||
onBlur={(event: any) => console.log('event', event)}
|
||||
/>,
|
||||
];
|
||||
|
||||
|
|
|
@ -0,0 +1,205 @@
|
|||
import { useState } from 'react';
|
||||
import {
|
||||
Button,
|
||||
TextInput,
|
||||
Stack,
|
||||
Modal,
|
||||
// @ts-ignore
|
||||
} from '@carbon/react';
|
||||
import {
|
||||
ReportFilter,
|
||||
ProcessInstanceReport,
|
||||
ProcessModel,
|
||||
ReportColumn,
|
||||
ReportMetadata,
|
||||
} from '../interfaces';
|
||||
import HttpService from '../services/HttpService';
|
||||
|
||||
type OwnProps = {
|
||||
onSuccess: (..._args: any[]) => any;
|
||||
columnArray: ReportColumn[];
|
||||
orderBy: string;
|
||||
processModelSelection: ProcessModel | null;
|
||||
processStatusSelection: string[];
|
||||
startFromSeconds: string | null;
|
||||
startToSeconds: string | null;
|
||||
endFromSeconds: string | null;
|
||||
endToSeconds: string | null;
|
||||
buttonText?: string;
|
||||
buttonClassName?: string;
|
||||
processInstanceReportSelection?: ProcessInstanceReport | null;
|
||||
reportMetadata: ReportMetadata;
|
||||
};
|
||||
|
||||
export default function ProcessInstanceListSaveAsReport({
|
||||
onSuccess,
|
||||
columnArray,
|
||||
orderBy,
|
||||
processModelSelection,
|
||||
processInstanceReportSelection,
|
||||
processStatusSelection,
|
||||
startFromSeconds,
|
||||
startToSeconds,
|
||||
endFromSeconds,
|
||||
endToSeconds,
|
||||
buttonClassName,
|
||||
buttonText = 'Save as Perspective',
|
||||
reportMetadata,
|
||||
}: OwnProps) {
|
||||
const [identifier, setIdentifier] = useState<string>(
|
||||
processInstanceReportSelection?.identifier || ''
|
||||
);
|
||||
const [showSaveForm, setShowSaveForm] = useState<boolean>(false);
|
||||
|
||||
const isEditMode = () => {
|
||||
return (
|
||||
processInstanceReportSelection &&
|
||||
processInstanceReportSelection.identifier === identifier
|
||||
);
|
||||
};
|
||||
|
||||
const responseHandler = (result: any) => {
|
||||
if (result) {
|
||||
onSuccess(result, isEditMode() ? 'edit' : 'new');
|
||||
}
|
||||
};
|
||||
|
||||
const handleSaveFormClose = () => {
|
||||
setIdentifier(processInstanceReportSelection?.identifier || '');
|
||||
setShowSaveForm(false);
|
||||
};
|
||||
|
||||
const addProcessInstanceReport = (event: any) => {
|
||||
event.preventDefault();
|
||||
|
||||
// TODO: make a field to set this
|
||||
let orderByArray = ['-start_in_seconds', '-id'];
|
||||
if (orderBy) {
|
||||
orderByArray = orderBy.split(',').filter((n) => n);
|
||||
}
|
||||
const filterByArray: any = [];
|
||||
|
||||
if (processModelSelection) {
|
||||
filterByArray.push({
|
||||
field_name: 'process_model_identifier',
|
||||
field_value: processModelSelection.id,
|
||||
});
|
||||
}
|
||||
|
||||
if (processStatusSelection.length > 0) {
|
||||
filterByArray.push({
|
||||
field_name: 'process_status',
|
||||
field_value: processStatusSelection.join(','),
|
||||
operator: 'in',
|
||||
});
|
||||
}
|
||||
|
||||
if (startFromSeconds) {
|
||||
filterByArray.push({
|
||||
field_name: 'start_from',
|
||||
field_value: startFromSeconds,
|
||||
});
|
||||
}
|
||||
|
||||
if (startToSeconds) {
|
||||
filterByArray.push({
|
||||
field_name: 'start_to',
|
||||
field_value: startToSeconds,
|
||||
});
|
||||
}
|
||||
|
||||
if (endFromSeconds) {
|
||||
filterByArray.push({
|
||||
field_name: 'end_from',
|
||||
field_value: endFromSeconds,
|
||||
});
|
||||
}
|
||||
|
||||
if (endToSeconds) {
|
||||
filterByArray.push({
|
||||
field_name: 'end_to',
|
||||
field_value: endToSeconds,
|
||||
});
|
||||
}
|
||||
|
||||
reportMetadata.filter_by.forEach((reportFilter: ReportFilter) => {
|
||||
columnArray.forEach((reportColumn: ReportColumn) => {
|
||||
if (
|
||||
reportColumn.accessor === reportFilter.field_name &&
|
||||
reportColumn.filterable
|
||||
) {
|
||||
filterByArray.push(reportFilter);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
let path = `/process-instances/reports`;
|
||||
let httpMethod = 'POST';
|
||||
if (isEditMode() && processInstanceReportSelection) {
|
||||
httpMethod = 'PUT';
|
||||
path = `${path}/${processInstanceReportSelection.id}`;
|
||||
}
|
||||
|
||||
HttpService.makeCallToBackend({
|
||||
path,
|
||||
successCallback: responseHandler,
|
||||
httpMethod,
|
||||
postBody: {
|
||||
identifier,
|
||||
report_metadata: {
|
||||
columns: columnArray,
|
||||
order_by: orderByArray,
|
||||
filter_by: filterByArray,
|
||||
},
|
||||
},
|
||||
});
|
||||
handleSaveFormClose();
|
||||
};
|
||||
|
||||
let textInputComponent = null;
|
||||
textInputComponent = (
|
||||
<TextInput
|
||||
id="identifier"
|
||||
name="identifier"
|
||||
labelText="Identifier"
|
||||
className="no-wrap"
|
||||
inline
|
||||
value={identifier}
|
||||
onChange={(e: any) => setIdentifier(e.target.value)}
|
||||
/>
|
||||
);
|
||||
|
||||
let descriptionText =
|
||||
'Save the current columns and filters as a perspective so you can come back to this view in the future.';
|
||||
if (processInstanceReportSelection) {
|
||||
descriptionText =
|
||||
'Keep the identifier the same and click Save to update the current perspective. Change the identifier if you want to save the current view with a new name.';
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack gap={5} orientation="horizontal">
|
||||
<Modal
|
||||
open={showSaveForm}
|
||||
modalHeading="Save Perspective"
|
||||
primaryButtonText="Save"
|
||||
primaryButtonDisabled={!identifier}
|
||||
onRequestSubmit={addProcessInstanceReport}
|
||||
onRequestClose={handleSaveFormClose}
|
||||
hasScrollingContent
|
||||
>
|
||||
<p className="data-table-description">{descriptionText}</p>
|
||||
{textInputComponent}
|
||||
</Modal>
|
||||
<Button
|
||||
kind=""
|
||||
className={buttonClassName}
|
||||
onClick={() => {
|
||||
setIdentifier(processInstanceReportSelection?.identifier || '');
|
||||
setShowSaveForm(true);
|
||||
}}
|
||||
>
|
||||
{buttonText}
|
||||
</Button>
|
||||
</Stack>
|
||||
);
|
||||
}
|
|
@ -7,7 +7,7 @@ import {
|
|||
} from 'react-router-dom';
|
||||
|
||||
// @ts-ignore
|
||||
import { Filter } from '@carbon/icons-react';
|
||||
import { Filter, Close, AddAlt } from '@carbon/icons-react';
|
||||
import {
|
||||
Button,
|
||||
ButtonSet,
|
||||
|
@ -21,6 +21,13 @@ import {
|
|||
TableHead,
|
||||
TableRow,
|
||||
TimePicker,
|
||||
Tag,
|
||||
InlineNotification,
|
||||
Stack,
|
||||
Modal,
|
||||
ComboBox,
|
||||
TextInput,
|
||||
FormLabel,
|
||||
// @ts-ignore
|
||||
} from '@carbon/react';
|
||||
import { PROCESS_STATUSES, DATE_FORMAT, DATE_FORMAT_CARBON } from '../config';
|
||||
|
@ -49,9 +56,15 @@ import {
|
|||
ProcessModel,
|
||||
ProcessInstanceReport,
|
||||
ProcessInstance,
|
||||
ReportColumn,
|
||||
ReportColumnForEditing,
|
||||
ReportMetadata,
|
||||
ReportFilter,
|
||||
} from '../interfaces';
|
||||
import ProcessModelSearch from './ProcessModelSearch';
|
||||
import ProcessInstanceReportSearch from './ProcessInstanceReportSearch';
|
||||
import ProcessInstanceListSaveAsReport from './ProcessInstanceListSaveAsReport';
|
||||
import { FormatProcessModelDisplayName } from './MiniComponents';
|
||||
|
||||
const REFRESH_INTERVAL = 5;
|
||||
const REFRESH_TIMEOUT = 600;
|
||||
|
@ -88,7 +101,7 @@ export default function ProcessInstanceListTable({
|
|||
const navigate = useNavigate();
|
||||
|
||||
const [processInstances, setProcessInstances] = useState([]);
|
||||
const [reportMetadata, setReportMetadata] = useState({});
|
||||
const [reportMetadata, setReportMetadata] = useState<ReportMetadata | null>();
|
||||
const [pagination, setPagination] = useState<PaginationObject | null>(null);
|
||||
const [processInstanceFilters, setProcessInstanceFilters] = useState({});
|
||||
|
||||
|
@ -125,6 +138,17 @@ export default function ProcessInstanceListTable({
|
|||
const [processInstanceReportSelection, setProcessInstanceReportSelection] =
|
||||
useState<ProcessInstanceReport | null>(null);
|
||||
|
||||
const [availableReportColumns, setAvailableReportColumns] = useState<
|
||||
ReportColumn[]
|
||||
>([]);
|
||||
const [processInstanceReportJustSaved, setProcessInstanceReportJustSaved] =
|
||||
useState<string | null>(null);
|
||||
const [showReportColumnForm, setShowReportColumnForm] =
|
||||
useState<boolean>(false);
|
||||
const [reportColumnToOperateOn, setReportColumnToOperateOn] =
|
||||
useState<ReportColumnForEditing | null>(null);
|
||||
const [reportColumnFormMode, setReportColumnFormMode] = useState<string>('');
|
||||
|
||||
const dateParametersToAlwaysFilterBy: dateParameters = useMemo(() => {
|
||||
return {
|
||||
start_from: [setStartFromDate, setStartFromTime],
|
||||
|
@ -155,16 +179,12 @@ export default function ProcessInstanceListTable({
|
|||
function setProcessInstancesFromResult(result: any) {
|
||||
const processInstancesFromApi = result.results;
|
||||
setProcessInstances(processInstancesFromApi);
|
||||
setReportMetadata(result.report_metadata);
|
||||
setPagination(result.pagination);
|
||||
setProcessInstanceFilters(result.filters);
|
||||
|
||||
// TODO: need to iron out this interaction some more
|
||||
if (result.report_identifier !== 'default') {
|
||||
setProcessInstanceReportSelection({
|
||||
id: result.report_identifier,
|
||||
display_name: result.report_identifier,
|
||||
});
|
||||
setReportMetadata(result.report.report_metadata);
|
||||
if (result.report.id) {
|
||||
setProcessInstanceReportSelection(result.report);
|
||||
}
|
||||
}
|
||||
function getProcessInstances() {
|
||||
|
@ -186,14 +206,10 @@ export default function ProcessInstanceListTable({
|
|||
queryParamString += `&user_filter=${userAppliedFilter}`;
|
||||
}
|
||||
|
||||
let reportIdentifierToUse: any = reportIdentifier;
|
||||
|
||||
if (!reportIdentifierToUse) {
|
||||
reportIdentifierToUse = searchParams.get('report_identifier');
|
||||
}
|
||||
|
||||
if (reportIdentifierToUse) {
|
||||
queryParamString += `&report_identifier=${reportIdentifierToUse}`;
|
||||
if (searchParams.get('report_id')) {
|
||||
queryParamString += `&report_id=${searchParams.get('report_id')}`;
|
||||
} else if (reportIdentifier) {
|
||||
queryParamString += `&report_identifier=${reportIdentifier}`;
|
||||
}
|
||||
|
||||
Object.keys(dateParametersToAlwaysFilterBy).forEach(
|
||||
|
@ -349,6 +365,30 @@ export default function ProcessInstanceListTable({
|
|||
processModelAvailableItems,
|
||||
]);
|
||||
|
||||
const processInstanceReportSaveTag = () => {
|
||||
if (processInstanceReportJustSaved) {
|
||||
let titleOperation = 'Updated';
|
||||
if (processInstanceReportJustSaved === 'new') {
|
||||
titleOperation = 'Created';
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<InlineNotification
|
||||
title={`Perspective ${titleOperation}:`}
|
||||
subtitle={`'${
|
||||
processInstanceReportSelection
|
||||
? processInstanceReportSelection.identifier
|
||||
: ''
|
||||
}'`}
|
||||
kind="success"
|
||||
/>
|
||||
<br />
|
||||
</>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
// does the comparison, but also returns false if either argument
|
||||
// is not truthy and therefore not comparable.
|
||||
const isTrueComparison = (param1: any, operation: any, param2: any) => {
|
||||
|
@ -366,16 +406,8 @@ export default function ProcessInstanceListTable({
|
|||
}
|
||||
};
|
||||
|
||||
const applyFilter = (event: any) => {
|
||||
event.preventDefault();
|
||||
const { page, perPage } = getPageInfoFromSearchParams(
|
||||
searchParams,
|
||||
undefined,
|
||||
undefined,
|
||||
paginationQueryParamPrefix
|
||||
);
|
||||
let queryParamString = `per_page=${perPage}&page=${page}&user_filter=true`;
|
||||
|
||||
// TODO: after factoring this out page hangs when invalid date ranges and applying the filter
|
||||
const calculateStartAndEndSeconds = () => {
|
||||
const startFromSeconds = convertDateAndTimeStringsToSeconds(
|
||||
startFromDate,
|
||||
startFromTime || '00:00:00'
|
||||
|
@ -392,28 +424,59 @@ export default function ProcessInstanceListTable({
|
|||
endToDate,
|
||||
endToTime || '00:00:00'
|
||||
);
|
||||
let valid = true;
|
||||
if (isTrueComparison(startFromSeconds, '>', startToSeconds)) {
|
||||
setErrorMessage({
|
||||
message: '"Start date from" cannot be after "start date to"',
|
||||
});
|
||||
return;
|
||||
valid = false;
|
||||
}
|
||||
if (isTrueComparison(endFromSeconds, '>', endToSeconds)) {
|
||||
setErrorMessage({
|
||||
message: '"End date from" cannot be after "end date to"',
|
||||
});
|
||||
return;
|
||||
valid = false;
|
||||
}
|
||||
if (isTrueComparison(startFromSeconds, '>', endFromSeconds)) {
|
||||
setErrorMessage({
|
||||
message: '"Start date from" cannot be after "end date from"',
|
||||
});
|
||||
return;
|
||||
valid = false;
|
||||
}
|
||||
if (isTrueComparison(startToSeconds, '>', endToSeconds)) {
|
||||
setErrorMessage({
|
||||
message: '"Start date to" cannot be after "end date to"',
|
||||
});
|
||||
valid = false;
|
||||
}
|
||||
|
||||
return {
|
||||
valid,
|
||||
startFromSeconds,
|
||||
startToSeconds,
|
||||
endFromSeconds,
|
||||
endToSeconds,
|
||||
};
|
||||
};
|
||||
|
||||
const applyFilter = (event: any) => {
|
||||
event.preventDefault();
|
||||
const { page, perPage } = getPageInfoFromSearchParams(
|
||||
searchParams,
|
||||
undefined,
|
||||
undefined,
|
||||
paginationQueryParamPrefix
|
||||
);
|
||||
let queryParamString = `per_page=${perPage}&page=${page}&user_filter=true`;
|
||||
const {
|
||||
valid,
|
||||
startFromSeconds,
|
||||
startToSeconds,
|
||||
endFromSeconds,
|
||||
endToSeconds,
|
||||
} = calculateStartAndEndSeconds();
|
||||
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -438,10 +501,11 @@ export default function ProcessInstanceListTable({
|
|||
}
|
||||
|
||||
if (processInstanceReportSelection) {
|
||||
queryParamString += `&report_identifier=${processInstanceReportSelection.id}`;
|
||||
queryParamString += `&report_id=${processInstanceReportSelection.id}`;
|
||||
}
|
||||
|
||||
setErrorMessage(null);
|
||||
setProcessInstanceReportJustSaved(null);
|
||||
navigate(`/admin/process-instances?${queryParamString}`);
|
||||
};
|
||||
|
||||
|
@ -529,12 +593,369 @@ export default function ProcessInstanceListTable({
|
|||
setEndToTime('');
|
||||
};
|
||||
|
||||
const processInstanceReportDidChange = (selection: any, mode?: string) => {
|
||||
clearFilters();
|
||||
const selectedReport = selection.selectedItem;
|
||||
setProcessInstanceReportSelection(selectedReport);
|
||||
|
||||
let queryParamString = '';
|
||||
if (selectedReport) {
|
||||
queryParamString = `?report_id=${selectedReport.id}`;
|
||||
}
|
||||
|
||||
setErrorMessage(null);
|
||||
setProcessInstanceReportJustSaved(mode || null);
|
||||
navigate(`/admin/process-instances${queryParamString}`);
|
||||
};
|
||||
|
||||
const reportColumns = () => {
|
||||
return (reportMetadata as any).columns;
|
||||
};
|
||||
|
||||
const reportColumnAccessors = () => {
|
||||
return reportColumns().map((reportColumn: ReportColumn) => {
|
||||
return reportColumn.accessor;
|
||||
});
|
||||
};
|
||||
|
||||
// TODO onSuccess reload/select the new report in the report search
|
||||
const onSaveReportSuccess = (result: any, mode: string) => {
|
||||
processInstanceReportDidChange(
|
||||
{
|
||||
selectedItem: result,
|
||||
},
|
||||
mode
|
||||
);
|
||||
};
|
||||
|
||||
const saveAsReportComponent = () => {
|
||||
const {
|
||||
valid,
|
||||
startFromSeconds,
|
||||
startToSeconds,
|
||||
endFromSeconds,
|
||||
endToSeconds,
|
||||
} = calculateStartAndEndSeconds();
|
||||
|
||||
if (!valid || !reportMetadata) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<ProcessInstanceListSaveAsReport
|
||||
onSuccess={onSaveReportSuccess}
|
||||
buttonClassName="button-white-background narrow-button"
|
||||
columnArray={reportColumns()}
|
||||
orderBy=""
|
||||
buttonText="Save"
|
||||
processModelSelection={processModelSelection}
|
||||
processStatusSelection={processStatusSelection}
|
||||
processInstanceReportSelection={processInstanceReportSelection}
|
||||
reportMetadata={reportMetadata}
|
||||
startFromSeconds={startFromSeconds}
|
||||
startToSeconds={startToSeconds}
|
||||
endFromSeconds={endFromSeconds}
|
||||
endToSeconds={endToSeconds}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const removeColumn = (reportColumn: ReportColumn) => {
|
||||
if (reportMetadata) {
|
||||
const reportMetadataCopy = { ...reportMetadata };
|
||||
const newColumns = reportColumns().filter(
|
||||
(rc: ReportColumn) => rc.accessor !== reportColumn.accessor
|
||||
);
|
||||
Object.assign(reportMetadataCopy, { columns: newColumns });
|
||||
setReportMetadata(reportMetadataCopy);
|
||||
}
|
||||
};
|
||||
|
||||
const handleColumnFormClose = () => {
|
||||
setShowReportColumnForm(false);
|
||||
setReportColumnFormMode('');
|
||||
setReportColumnToOperateOn(null);
|
||||
};
|
||||
|
||||
const getFilterByFromReportMetadata = (reportColumnAccessor: string) => {
|
||||
if (reportMetadata) {
|
||||
return reportMetadata.filter_by.find((reportFilter: ReportFilter) => {
|
||||
return reportColumnAccessor === reportFilter.field_name;
|
||||
});
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const getNewFiltersFromReportForEditing = (
|
||||
reportColumnForEditing: ReportColumnForEditing
|
||||
) => {
|
||||
if (!reportMetadata) {
|
||||
return null;
|
||||
}
|
||||
const reportMetadataCopy = { ...reportMetadata };
|
||||
let newReportFilters = reportMetadataCopy.filter_by;
|
||||
if (reportColumnForEditing.filterable) {
|
||||
const newReportFilter: ReportFilter = {
|
||||
field_name: reportColumnForEditing.accessor,
|
||||
field_value: reportColumnForEditing.filter_field_value,
|
||||
operator: reportColumnForEditing.filter_operator || 'equals',
|
||||
};
|
||||
const existingReportFilter = getFilterByFromReportMetadata(
|
||||
reportColumnForEditing.accessor
|
||||
);
|
||||
if (existingReportFilter) {
|
||||
const existingReportFilterIndex =
|
||||
reportMetadataCopy.filter_by.indexOf(existingReportFilter);
|
||||
if (reportColumnForEditing.filter_field_value) {
|
||||
newReportFilters[existingReportFilterIndex] = newReportFilter;
|
||||
} else {
|
||||
newReportFilters.splice(existingReportFilterIndex, 1);
|
||||
}
|
||||
} else if (reportColumnForEditing.filter_field_value) {
|
||||
newReportFilters = newReportFilters.concat([newReportFilter]);
|
||||
}
|
||||
}
|
||||
return newReportFilters;
|
||||
};
|
||||
|
||||
const handleUpdateReportColumn = () => {
|
||||
if (reportColumnToOperateOn && reportMetadata) {
|
||||
const reportMetadataCopy = { ...reportMetadata };
|
||||
let newReportColumns = null;
|
||||
if (reportColumnFormMode === 'new') {
|
||||
newReportColumns = reportColumns().concat([reportColumnToOperateOn]);
|
||||
} else {
|
||||
newReportColumns = reportColumns().map((rc: ReportColumn) => {
|
||||
if (rc.accessor === reportColumnToOperateOn.accessor) {
|
||||
return reportColumnToOperateOn;
|
||||
}
|
||||
return rc;
|
||||
});
|
||||
}
|
||||
Object.assign(reportMetadataCopy, {
|
||||
columns: newReportColumns,
|
||||
filter_by: getNewFiltersFromReportForEditing(reportColumnToOperateOn),
|
||||
});
|
||||
setReportMetadata(reportMetadataCopy);
|
||||
setReportColumnToOperateOn(null);
|
||||
setShowReportColumnForm(false);
|
||||
setShowReportColumnForm(false);
|
||||
}
|
||||
};
|
||||
|
||||
const reportColumnToReportColumnForEditing = (reportColumn: ReportColumn) => {
|
||||
const reportColumnForEditing: ReportColumnForEditing = Object.assign(
|
||||
reportColumn,
|
||||
{ filter_field_value: '', filter_operator: '' }
|
||||
);
|
||||
const reportFilter = getFilterByFromReportMetadata(
|
||||
reportColumnForEditing.accessor
|
||||
);
|
||||
if (reportFilter) {
|
||||
reportColumnForEditing.filter_field_value = reportFilter.field_value;
|
||||
reportColumnForEditing.filter_operator =
|
||||
reportFilter.operator || 'equals';
|
||||
}
|
||||
return reportColumnForEditing;
|
||||
};
|
||||
|
||||
const updateReportColumn = (event: any) => {
|
||||
const reportColumnForEditing = reportColumnToReportColumnForEditing(
|
||||
event.selectedItem
|
||||
);
|
||||
setReportColumnToOperateOn(reportColumnForEditing);
|
||||
};
|
||||
|
||||
// options includes item and inputValue
|
||||
const shouldFilterReportColumn = (options: any) => {
|
||||
const reportColumn: ReportColumn = options.item;
|
||||
const { inputValue } = options;
|
||||
return (
|
||||
!reportColumnAccessors().includes(reportColumn.accessor) &&
|
||||
(reportColumn.accessor || '')
|
||||
.toLowerCase()
|
||||
.includes((inputValue || '').toLowerCase())
|
||||
);
|
||||
};
|
||||
|
||||
const setReportColumnConditionValue = (event: any) => {
|
||||
if (reportColumnToOperateOn) {
|
||||
const reportColumnToOperateOnCopy = {
|
||||
...reportColumnToOperateOn,
|
||||
};
|
||||
reportColumnToOperateOnCopy.filter_field_value = event.target.value;
|
||||
setReportColumnToOperateOn(reportColumnToOperateOnCopy);
|
||||
}
|
||||
};
|
||||
|
||||
const reportColumnForm = () => {
|
||||
if (reportColumnFormMode === '') {
|
||||
return null;
|
||||
}
|
||||
const formElements = [
|
||||
<TextInput
|
||||
id="report-column-display-name"
|
||||
name="report-column-display-name"
|
||||
labelText="Display Name"
|
||||
disabled={!reportColumnToOperateOn}
|
||||
value={reportColumnToOperateOn ? reportColumnToOperateOn.Header : ''}
|
||||
onChange={(event: any) => {
|
||||
if (reportColumnToOperateOn) {
|
||||
const reportColumnToOperateOnCopy = {
|
||||
...reportColumnToOperateOn,
|
||||
};
|
||||
reportColumnToOperateOnCopy.Header = event.target.value;
|
||||
setReportColumnToOperateOn(reportColumnToOperateOnCopy);
|
||||
}
|
||||
}}
|
||||
/>,
|
||||
];
|
||||
if (reportColumnToOperateOn && reportColumnToOperateOn.filterable) {
|
||||
formElements.push(
|
||||
<TextInput
|
||||
id="report-column-condition-value"
|
||||
name="report-column-condition-value"
|
||||
labelText="Condition Value"
|
||||
value={
|
||||
reportColumnToOperateOn
|
||||
? reportColumnToOperateOn.filter_field_value
|
||||
: ''
|
||||
}
|
||||
onChange={setReportColumnConditionValue}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (reportColumnFormMode === 'new') {
|
||||
formElements.push(
|
||||
<ComboBox
|
||||
onChange={updateReportColumn}
|
||||
className="combo-box-in-modal"
|
||||
id="report-column-selection"
|
||||
data-qa="report-column-selection"
|
||||
data-modal-primary-focus
|
||||
items={availableReportColumns}
|
||||
itemToString={(reportColumn: ReportColumn) => {
|
||||
if (reportColumn) {
|
||||
return reportColumn.accessor;
|
||||
}
|
||||
return null;
|
||||
}}
|
||||
shouldFilterItem={shouldFilterReportColumn}
|
||||
placeholder="Choose a report column"
|
||||
titleText="Report Column"
|
||||
/>
|
||||
);
|
||||
}
|
||||
const modalHeading =
|
||||
reportColumnFormMode === 'new'
|
||||
? 'Add Column'
|
||||
: `Edit ${
|
||||
reportColumnToOperateOn ? reportColumnToOperateOn.accessor : ''
|
||||
} column`;
|
||||
return (
|
||||
<Modal
|
||||
open={showReportColumnForm}
|
||||
modalHeading={modalHeading}
|
||||
primaryButtonText="Save"
|
||||
primaryButtonDisabled={!reportColumnToOperateOn}
|
||||
onRequestSubmit={handleUpdateReportColumn}
|
||||
onRequestClose={handleColumnFormClose}
|
||||
hasScrollingContent
|
||||
>
|
||||
{formElements}
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
const columnSelections = () => {
|
||||
if (reportColumns()) {
|
||||
const tags: any = [];
|
||||
|
||||
(reportColumns() as any).forEach((reportColumn: ReportColumn) => {
|
||||
const reportColumnForEditing =
|
||||
reportColumnToReportColumnForEditing(reportColumn);
|
||||
|
||||
let tagType = 'cool-gray';
|
||||
let tagTypeClass = '';
|
||||
if (reportColumnForEditing.filterable) {
|
||||
tagType = 'green';
|
||||
tagTypeClass = 'tag-type-green';
|
||||
}
|
||||
let reportColumnLabel = reportColumnForEditing.Header;
|
||||
if (reportColumnForEditing.filter_field_value) {
|
||||
reportColumnLabel = `${reportColumnLabel}=${reportColumnForEditing.filter_field_value}`;
|
||||
}
|
||||
tags.push(
|
||||
<Tag type={tagType} size="sm">
|
||||
<Button
|
||||
kind="ghost"
|
||||
size="sm"
|
||||
className={`button-tag-icon ${tagTypeClass}`}
|
||||
title={`Edit ${reportColumnForEditing.accessor}`}
|
||||
onClick={() => {
|
||||
setReportColumnToOperateOn(reportColumnForEditing);
|
||||
setShowReportColumnForm(true);
|
||||
setReportColumnFormMode('edit');
|
||||
}}
|
||||
>
|
||||
{reportColumnLabel}
|
||||
</Button>
|
||||
<Button
|
||||
data-qa="remove-report-column"
|
||||
renderIcon={Close}
|
||||
iconDescription="Remove Column"
|
||||
className={`button-tag-icon ${tagTypeClass}`}
|
||||
hasIconOnly
|
||||
size="sm"
|
||||
kind="ghost"
|
||||
onClick={() => removeColumn(reportColumnForEditing)}
|
||||
/>
|
||||
</Tag>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<Stack orientation="horizontal">
|
||||
{tags}
|
||||
<Button
|
||||
data-qa="add-column-button"
|
||||
renderIcon={AddAlt}
|
||||
iconDescription="Filter Options"
|
||||
className="with-tiny-top-margin"
|
||||
kind="ghost"
|
||||
hasIconOnly
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setShowReportColumnForm(true);
|
||||
setReportColumnFormMode('new');
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const filterOptions = () => {
|
||||
if (!showFilterOptions) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// get the columns anytime we display the filter options if they are empty
|
||||
if (availableReportColumns.length < 1) {
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-instances/reports/columns`,
|
||||
successCallback: setAvailableReportColumns,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Grid fullWidth className="with-bottom-margin">
|
||||
<Column md={8} lg={16} sm={4}>
|
||||
<FormLabel>Columns</FormLabel>
|
||||
<br />
|
||||
{columnSelections()}
|
||||
</Column>
|
||||
</Grid>
|
||||
<Grid fullWidth className="with-bottom-margin">
|
||||
<Column md={8}>
|
||||
<ProcessModelSearch
|
||||
|
@ -598,11 +1019,11 @@ export default function ProcessInstanceListTable({
|
|||
</Column>
|
||||
</Grid>
|
||||
<Grid fullWidth className="with-bottom-margin">
|
||||
<Column md={4}>
|
||||
<Column sm={4} md={4} lg={8}>
|
||||
<ButtonSet>
|
||||
<Button
|
||||
kind=""
|
||||
className="button-white-background"
|
||||
className="button-white-background narrow-button"
|
||||
onClick={clearFilters}
|
||||
>
|
||||
Clear
|
||||
|
@ -611,11 +1032,15 @@ export default function ProcessInstanceListTable({
|
|||
kind="secondary"
|
||||
onClick={applyFilter}
|
||||
data-qa="filter-button"
|
||||
className="narrow-button"
|
||||
>
|
||||
Filter
|
||||
</Button>
|
||||
</ButtonSet>
|
||||
</Column>
|
||||
<Column sm={4} md={4} lg={8}>
|
||||
{saveAsReportComponent()}
|
||||
</Column>
|
||||
</Grid>
|
||||
</>
|
||||
);
|
||||
|
@ -635,7 +1060,7 @@ export default function ProcessInstanceListTable({
|
|||
const getHeaderLabel = (header: string) => {
|
||||
return headerLabels[header] ?? header;
|
||||
};
|
||||
const headers = (reportMetadata as any).columns.map((column: any) => {
|
||||
const headers = reportColumns().map((column: any) => {
|
||||
// return <th>{getHeaderLabel((column as any).Header)}</th>;
|
||||
return getHeaderLabel((column as any).Header);
|
||||
});
|
||||
|
@ -646,7 +1071,7 @@ export default function ProcessInstanceListTable({
|
|||
return (
|
||||
<Link
|
||||
data-qa="process-instance-show-link"
|
||||
to={`/admin/process-models/${modifiedProcessModelId}/process-instances/${id}`}
|
||||
to={`/admin/process-instances/${modifiedProcessModelId}/${id}`}
|
||||
title={`View process instance ${id}`}
|
||||
>
|
||||
{id}
|
||||
|
@ -665,22 +1090,6 @@ export default function ProcessInstanceListTable({
|
|||
);
|
||||
};
|
||||
|
||||
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) || '-';
|
||||
};
|
||||
|
@ -688,15 +1097,16 @@ export default function ProcessInstanceListTable({
|
|||
return value;
|
||||
};
|
||||
|
||||
const columnFormatters: Record<string, any> = {
|
||||
const reportColumnFormatters: Record<string, any> = {
|
||||
id: formatProcessInstanceId,
|
||||
process_model_identifier: formatProcessModelIdentifier,
|
||||
process_model_display_name: formatProcessModelDisplayName,
|
||||
process_model_display_name: FormatProcessModelDisplayName,
|
||||
start_in_seconds: formatSecondsForDisplay,
|
||||
end_in_seconds: formatSecondsForDisplay,
|
||||
};
|
||||
const formattedColumn = (row: any, column: any) => {
|
||||
const formatter = columnFormatters[column.accessor] ?? defaultFormatter;
|
||||
const formatter =
|
||||
reportColumnFormatters[column.accessor] ?? defaultFormatter;
|
||||
const value = row[column.accessor];
|
||||
if (column.accessor === 'status') {
|
||||
return (
|
||||
|
@ -709,7 +1119,7 @@ export default function ProcessInstanceListTable({
|
|||
};
|
||||
|
||||
const rows = processInstances.map((row: any) => {
|
||||
const currentRow = (reportMetadata as any).columns.map((column: any) => {
|
||||
const currentRow = reportColumns().map((column: any) => {
|
||||
return formattedColumn(row, column);
|
||||
});
|
||||
return <tr key={row.id}>{currentRow}</tr>;
|
||||
|
@ -738,27 +1148,20 @@ export default function ProcessInstanceListTable({
|
|||
setShowFilterOptions(!showFilterOptions);
|
||||
};
|
||||
|
||||
const processInstanceReportDidChange = (selection: any) => {
|
||||
clearFilters();
|
||||
|
||||
const selectedReport = selection.selectedItem;
|
||||
setProcessInstanceReportSelection(selectedReport);
|
||||
|
||||
const queryParamString = selectedReport
|
||||
? `&report_identifier=${selectedReport.id}`
|
||||
: '';
|
||||
|
||||
setErrorMessage(null);
|
||||
navigate(`/admin/process-instances?${queryParamString}`);
|
||||
};
|
||||
|
||||
const reportSearchComponent = () => {
|
||||
if (showReports) {
|
||||
const columns = [
|
||||
<Column sm={2} md={4} lg={7}>
|
||||
<ProcessInstanceReportSearch
|
||||
onChange={processInstanceReportDidChange}
|
||||
selectedItem={processInstanceReportSelection}
|
||||
/>
|
||||
</Column>,
|
||||
];
|
||||
return (
|
||||
<ProcessInstanceReportSearch
|
||||
onChange={processInstanceReportDidChange}
|
||||
selectedItem={processInstanceReportSelection}
|
||||
/>
|
||||
<Grid className="with-tiny-bottom-margin" fullWidth>
|
||||
{columns}
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
|
@ -771,6 +1174,9 @@ export default function ProcessInstanceListTable({
|
|||
return (
|
||||
<>
|
||||
<Grid fullWidth>
|
||||
<Column sm={2} md={4} lg={7}>
|
||||
{reportSearchComponent()}
|
||||
</Column>
|
||||
<Column
|
||||
className="filterIcon"
|
||||
sm={{ span: 1, offset: 3 }}
|
||||
|
@ -806,8 +1212,9 @@ export default function ProcessInstanceListTable({
|
|||
}
|
||||
return (
|
||||
<>
|
||||
{reportColumnForm()}
|
||||
{processInstanceReportSaveTag()}
|
||||
{filterComponent()}
|
||||
{reportSearchComponent()}
|
||||
<PaginationForTable
|
||||
page={page}
|
||||
perPage={perPage}
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
import { useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import {
|
||||
ComboBox,
|
||||
Stack,
|
||||
FormLabel,
|
||||
// @ts-ignore
|
||||
} from '@carbon/react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { truncateString } from '../helpers';
|
||||
import { ProcessInstanceReport } from '../interfaces';
|
||||
import HttpService from '../services/HttpService';
|
||||
|
@ -22,52 +25,61 @@ export default function ProcessInstanceReportSearch({
|
|||
ProcessInstanceReport[] | null
|
||||
>(null);
|
||||
|
||||
function setProcessInstanceReportsFromResult(result: any) {
|
||||
const processInstanceReportsFromApi = result.map((item: any) => {
|
||||
return { id: item.identifier, display_name: item.identifier };
|
||||
});
|
||||
setProcessInstanceReports(processInstanceReportsFromApi);
|
||||
}
|
||||
const [searchParams] = useSearchParams();
|
||||
const reportId = searchParams.get('report_id');
|
||||
|
||||
useEffect(() => {
|
||||
function setProcessInstanceReportsFromResult(
|
||||
result: ProcessInstanceReport[]
|
||||
) {
|
||||
setProcessInstanceReports(result);
|
||||
}
|
||||
|
||||
if (processInstanceReports === null) {
|
||||
setProcessInstanceReports([]);
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-instances/reports`,
|
||||
successCallback: setProcessInstanceReportsFromResult,
|
||||
});
|
||||
}
|
||||
}, [reportId]);
|
||||
|
||||
const reportSelectionString = (
|
||||
processInstanceReport: ProcessInstanceReport
|
||||
) => {
|
||||
return `${truncateString(processInstanceReport.identifier, 20)} (Id: ${
|
||||
processInstanceReport.id
|
||||
})`;
|
||||
};
|
||||
|
||||
const shouldFilterProcessInstanceReport = (options: any) => {
|
||||
const processInstanceReport: ProcessInstanceReport = options.item;
|
||||
const { inputValue } = options;
|
||||
return `${processInstanceReport.id} (${processInstanceReport.display_name})`.includes(
|
||||
inputValue
|
||||
);
|
||||
return reportSelectionString(processInstanceReport).includes(inputValue);
|
||||
};
|
||||
|
||||
const reportsAvailable = () => {
|
||||
return processInstanceReports && processInstanceReports.length > 0;
|
||||
};
|
||||
|
||||
return reportsAvailable() ? (
|
||||
<ComboBox
|
||||
onChange={onChange}
|
||||
id="process-instance-report-select"
|
||||
data-qa="process-instance-report-selection"
|
||||
items={processInstanceReports}
|
||||
itemToString={(processInstanceReport: ProcessInstanceReport) => {
|
||||
if (processInstanceReport) {
|
||||
return `${processInstanceReport.id} (${truncateString(
|
||||
processInstanceReport.display_name,
|
||||
20
|
||||
)})`;
|
||||
}
|
||||
return null;
|
||||
}}
|
||||
shouldFilterItem={shouldFilterProcessInstanceReport}
|
||||
placeholder="Choose a process instance perspective"
|
||||
titleText={titleText}
|
||||
selectedItem={selectedItem}
|
||||
/>
|
||||
) : null;
|
||||
if (reportsAvailable()) {
|
||||
return (
|
||||
<Stack orientation="horizontal" gap={2}>
|
||||
<FormLabel className="with-top-margin">{titleText}</FormLabel>
|
||||
<ComboBox
|
||||
onChange={onChange}
|
||||
id="process-instance-report-select"
|
||||
data-qa="process-instance-report-selection"
|
||||
items={processInstanceReports}
|
||||
itemToString={(processInstanceReport: ProcessInstanceReport) => {
|
||||
if (processInstanceReport) {
|
||||
return reportSelectionString(processInstanceReport);
|
||||
}
|
||||
return null;
|
||||
}}
|
||||
shouldFilterItem={shouldFilterProcessInstanceReport}
|
||||
placeholder="Choose a process instance perspective"
|
||||
selectedItem={selectedItem}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -83,9 +83,9 @@ export default function ProcessInstanceRun({
|
|||
processModel.id
|
||||
);
|
||||
|
||||
const processInstanceActionPath = `/v1.0/process-models/${modifiedProcessModelId}/process-instances`;
|
||||
const processInstanceCreatePath = `/v1.0/process-instances/${modifiedProcessModelId}`;
|
||||
let permissionRequestData: PermissionsToCheck = {
|
||||
[processInstanceActionPath]: ['POST'],
|
||||
[processInstanceCreatePath]: ['POST'],
|
||||
};
|
||||
|
||||
if (!checkPermissions) {
|
||||
|
@ -117,14 +117,14 @@ export default function ProcessInstanceRun({
|
|||
|
||||
const processInstanceCreateAndRun = () => {
|
||||
HttpService.makeCallToBackend({
|
||||
path: processInstanceActionPath,
|
||||
path: processInstanceCreatePath,
|
||||
successCallback: processModelRun,
|
||||
httpMethod: 'POST',
|
||||
});
|
||||
};
|
||||
if (checkPermissions) {
|
||||
return (
|
||||
<Can I="POST" a={processInstanceActionPath} ability={ability}>
|
||||
<Can I="POST" a={processInstanceCreatePath} ability={ability}>
|
||||
<Button onClick={processInstanceCreateAndRun} className={className}>
|
||||
Start
|
||||
</Button>
|
||||
|
|
|
@ -1,10 +1,20 @@
|
|||
import { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
Button,
|
||||
ButtonSet,
|
||||
Form,
|
||||
Stack,
|
||||
TextInput,
|
||||
Grid,
|
||||
Column,
|
||||
// @ts-ignore
|
||||
} from '@carbon/react';
|
||||
// @ts-ignore
|
||||
import { Button, ButtonSet, Form, Stack, TextInput } from '@carbon/react';
|
||||
import { AddAlt, TrashCan } from '@carbon/icons-react';
|
||||
import { modifyProcessIdentifierForPathParam, slugifyString } from '../helpers';
|
||||
import HttpService from '../services/HttpService';
|
||||
import { ProcessModel } from '../interfaces';
|
||||
import { MetadataExtractionPath, ProcessModel } from '../interfaces';
|
||||
|
||||
type OwnProps = {
|
||||
mode: string;
|
||||
|
@ -23,6 +33,7 @@ export default function ProcessModelForm({
|
|||
const [idHasBeenUpdatedByUser, setIdHasBeenUpdatedByUser] =
|
||||
useState<boolean>(false);
|
||||
const [displayNameInvalid, setDisplayNameInvalid] = useState<boolean>(false);
|
||||
useState<boolean>(false);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const navigateToProcessModel = (result: ProcessModel) => {
|
||||
|
@ -64,6 +75,7 @@ export default function ProcessModelForm({
|
|||
const postBody = {
|
||||
display_name: processModel.display_name,
|
||||
description: processModel.description,
|
||||
metadata_extraction_paths: processModel.metadata_extraction_paths,
|
||||
};
|
||||
if (mode === 'new') {
|
||||
Object.assign(postBody, {
|
||||
|
@ -87,6 +99,80 @@ export default function ProcessModelForm({
|
|||
setProcessModel(processModelToCopy);
|
||||
};
|
||||
|
||||
const metadataExtractionPathForm = (
|
||||
index: number,
|
||||
metadataExtractionPath: MetadataExtractionPath
|
||||
) => {
|
||||
return (
|
||||
<Grid>
|
||||
<Column md={3} lg={7} sm={1}>
|
||||
<TextInput
|
||||
id={`process-model-metadata-extraction-path-key-${index}`}
|
||||
labelText="Extraction Key"
|
||||
value={metadataExtractionPath.key}
|
||||
onChange={(event: any) => {
|
||||
const cep: MetadataExtractionPath[] =
|
||||
processModel.metadata_extraction_paths || [];
|
||||
const newMeta = { ...metadataExtractionPath };
|
||||
newMeta.key = event.target.value;
|
||||
cep[index] = newMeta;
|
||||
updateProcessModel({ metadata_extraction_paths: cep });
|
||||
}}
|
||||
/>
|
||||
</Column>
|
||||
<Column md={4} lg={8} sm={2}>
|
||||
<TextInput
|
||||
id={`process-model-metadata-extraction-path-${index}`}
|
||||
labelText="Extraction Path"
|
||||
value={metadataExtractionPath.path}
|
||||
onChange={(event: any) => {
|
||||
const cep: MetadataExtractionPath[] =
|
||||
processModel.metadata_extraction_paths || [];
|
||||
const newMeta = { ...metadataExtractionPath };
|
||||
newMeta.path = event.target.value;
|
||||
cep[index] = newMeta;
|
||||
updateProcessModel({ metadata_extraction_paths: cep });
|
||||
}}
|
||||
/>
|
||||
</Column>
|
||||
<Column md={1} lg={1} sm={1}>
|
||||
<Button
|
||||
kind="ghost"
|
||||
renderIcon={TrashCan}
|
||||
iconDescription="Remove Key"
|
||||
hasIconOnly
|
||||
size="lg"
|
||||
className="with-extra-top-margin"
|
||||
onClick={() => {
|
||||
const cep: MetadataExtractionPath[] =
|
||||
processModel.metadata_extraction_paths || [];
|
||||
cep.splice(index, 1);
|
||||
updateProcessModel({ metadata_extraction_paths: cep });
|
||||
}}
|
||||
/>
|
||||
</Column>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
const metadataExtractionPathFormArea = () => {
|
||||
if (processModel.metadata_extraction_paths) {
|
||||
return processModel.metadata_extraction_paths.map(
|
||||
(metadataExtractionPath: MetadataExtractionPath, index: number) => {
|
||||
return metadataExtractionPathForm(index, metadataExtractionPath);
|
||||
}
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const addBlankMetadataExtractionPath = () => {
|
||||
const cep: MetadataExtractionPath[] =
|
||||
processModel.metadata_extraction_paths || [];
|
||||
cep.push({ key: '', path: '' });
|
||||
updateProcessModel({ metadata_extraction_paths: cep });
|
||||
};
|
||||
|
||||
const onDisplayNameChanged = (newDisplayName: any) => {
|
||||
setDisplayNameInvalid(false);
|
||||
const updateDict = { display_name: newDisplayName };
|
||||
|
@ -108,7 +194,6 @@ export default function ProcessModelForm({
|
|||
onChange={(event: any) => {
|
||||
onDisplayNameChanged(event.target.value);
|
||||
}}
|
||||
onBlur={(event: any) => console.log('event', event)}
|
||||
/>,
|
||||
];
|
||||
|
||||
|
@ -145,6 +230,38 @@ export default function ProcessModelForm({
|
|||
/>
|
||||
);
|
||||
|
||||
textInputs.push(<h2>Metadata Extractions</h2>);
|
||||
textInputs.push(
|
||||
<Grid>
|
||||
<Column md={8} lg={16} sm={4}>
|
||||
<p className="data-table-description">
|
||||
You can provide one or more metadata extractions to pull data from
|
||||
your process instances to provide quick access in searches and
|
||||
perspectives.
|
||||
</p>
|
||||
</Column>
|
||||
</Grid>
|
||||
);
|
||||
textInputs.push(<>{metadataExtractionPathFormArea()}</>);
|
||||
textInputs.push(
|
||||
<Grid>
|
||||
<Column md={4} lg={8} sm={2}>
|
||||
<Button
|
||||
data-qa="add-metadata-extraction-path-button"
|
||||
renderIcon={AddAlt}
|
||||
className="button-white-background"
|
||||
kind=""
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
addBlankMetadataExtractionPath();
|
||||
}}
|
||||
>
|
||||
Add Metadata Extraction Path
|
||||
</Button>
|
||||
</Column>
|
||||
</Grid>
|
||||
);
|
||||
|
||||
return textInputs;
|
||||
};
|
||||
|
||||
|
|
|
@ -54,9 +54,9 @@ export default function ProcessModelListTiles({
|
|||
<p>
|
||||
Process Instance {processInstance.id} kicked off (
|
||||
<Link
|
||||
to={`/admin/process-models/${modifyProcessIdentifierForPathParam(
|
||||
to={`/admin/process-instances/${modifyProcessIdentifierForPathParam(
|
||||
processInstance.process_model_identifier
|
||||
)}/process-instances/${processInstance.id}`}
|
||||
)}/${processInstance.id}`}
|
||||
data-qa="process-instance-show-link"
|
||||
>
|
||||
view
|
||||
|
|
|
@ -55,7 +55,7 @@ export default function MyOpenProcesses() {
|
|||
<td>
|
||||
<Link
|
||||
data-qa="process-instance-show-link"
|
||||
to={`/admin/process-models/${modifiedProcessModelIdentifier}/process-instances/${rowToUse.process_instance_id}`}
|
||||
to={`/admin/process-instances/${modifiedProcessModelIdentifier}/${rowToUse.process_instance_id}`}
|
||||
title={`View process instance ${rowToUse.process_instance_id}`}
|
||||
>
|
||||
{rowToUse.process_instance_id}
|
||||
|
|
|
@ -47,7 +47,7 @@ export default function TasksWaitingForMe() {
|
|||
<td>
|
||||
<Link
|
||||
data-qa="process-instance-show-link"
|
||||
to={`/admin/process-models/${modifiedProcessModelIdentifier}/process-instances/${rowToUse.process_instance_id}`}
|
||||
to={`/admin/${modifiedProcessModelIdentifier}/${rowToUse.process_instance_id}`}
|
||||
title={`View process instance ${rowToUse.process_instance_id}`}
|
||||
>
|
||||
{rowToUse.process_instance_id}
|
||||
|
|
|
@ -55,7 +55,7 @@ export default function TasksWaitingForMyGroups() {
|
|||
<td>
|
||||
<Link
|
||||
data-qa="process-instance-show-link"
|
||||
to={`/admin/process-models/${modifiedProcessModelIdentifier}/process-instances/${rowToUse.process_instance_id}`}
|
||||
to={`/admin/process-instances/${modifiedProcessModelIdentifier}/${rowToUse.process_instance_id}`}
|
||||
title={`View process instance ${rowToUse.process_instance_id}`}
|
||||
>
|
||||
{rowToUse.process_instance_id}
|
||||
|
|
|
@ -14,6 +14,7 @@ export const PROCESS_STATUSES = [
|
|||
'complete',
|
||||
'error',
|
||||
'suspended',
|
||||
'terminated',
|
||||
];
|
||||
|
||||
// with time: yyyy-MM-dd HH:mm:ss
|
||||
|
|
|
@ -9,10 +9,12 @@ export const useUriListForPermissions = () => {
|
|||
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`,
|
||||
processInstanceCreatePath: `/v1.0/process-instances/${params.process_model_id}`,
|
||||
processInstanceActionPath: `/v1.0/process-instances/${params.process_model_id}/${params.process_instance_id}`,
|
||||
processInstanceListPath: '/v1.0/process-instances',
|
||||
processInstanceTaskListPath: `/v1.0/process-instances/${params.process_model_id}/${params.process_instance_id}/tasks`,
|
||||
processInstanceLogListPath: `/v1.0/logs/${params.process_model_id}/${params.process_instance_id}`,
|
||||
processInstanceReportListPath: '/v1.0/process-instances/reports',
|
||||
processInstanceTaskListPath: `/v1.0/task-data/${params.process_model_id}/${params.process_instance_id}`,
|
||||
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}`,
|
||||
|
|
|
@ -69,6 +69,24 @@ h2 {
|
|||
color: black;
|
||||
}
|
||||
|
||||
/* match normal link colors */
|
||||
.cds--btn--ghost.button-link {
|
||||
color: #0062fe;
|
||||
padding-left: 0;
|
||||
}
|
||||
.cds--btn--ghost.button-link:visited {
|
||||
color: #0062fe;
|
||||
padding-left: 0;
|
||||
}
|
||||
.cds--btn--ghost.button-link:hover {
|
||||
color: #0062fe;
|
||||
padding-left: 0;
|
||||
}
|
||||
.cds--btn--ghost.button-link:visited:hover {
|
||||
color: #0062fe;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.cds--header__global .cds--btn--primary {
|
||||
background-color: #161616
|
||||
}
|
||||
|
@ -151,10 +169,22 @@ h1.with-icons {
|
|||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.with-extra-top-margin {
|
||||
margin-top: 1.3em;
|
||||
}
|
||||
|
||||
.with-tiny-top-margin {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.with-large-bottom-margin {
|
||||
margin-bottom: 3em;
|
||||
}
|
||||
|
||||
.with-tiny-bottom-margin {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.diagram-viewer-canvas {
|
||||
border:1px solid #000000;
|
||||
height:70vh;
|
||||
|
@ -297,3 +327,38 @@ td.actions-cell {
|
|||
text-align: right;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.cds--btn--ghost:not([disabled]) svg.red-icon {
|
||||
fill: red;
|
||||
}
|
||||
|
||||
.failure-string {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.cds--btn--ghost.cds--btn--sm.button-tag-icon {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
/* .no-wrap cds--label cds--label--inline cds--label--inline--md{ */
|
||||
.no-wrap .cds--label--inline{
|
||||
word-break: normal;
|
||||
}
|
||||
|
||||
.combo-box-in-modal {
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.cds--btn.narrow-button {
|
||||
max-width: 10rem;
|
||||
min-width: 5rem;
|
||||
word-break: normal;
|
||||
|
||||
}
|
||||
|
||||
.tag-type-green:hover {
|
||||
background-color: #00FF00;
|
||||
}
|
||||
|
||||
|
|
|
@ -12,9 +12,8 @@ export interface RecentProcessModel {
|
|||
}
|
||||
|
||||
export interface ProcessReference {
|
||||
id: string; // The unique id of the process or decision table.
|
||||
name: string; // The process or decision Display name.
|
||||
identifier: string;
|
||||
identifier: string; // The unique id of the process
|
||||
display_name: string;
|
||||
process_group_id: string;
|
||||
process_model_id: string;
|
||||
|
@ -39,11 +38,58 @@ export interface ProcessFile {
|
|||
export interface ProcessInstance {
|
||||
id: number;
|
||||
process_model_identifier: string;
|
||||
process_model_display_name: string;
|
||||
}
|
||||
|
||||
export interface MessageCorrelationProperties {
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
export interface MessageCorrelations {
|
||||
[key: string]: MessageCorrelationProperties;
|
||||
}
|
||||
|
||||
export interface MessageInstance {
|
||||
id: number;
|
||||
process_model_identifier: string;
|
||||
process_model_display_name: string;
|
||||
process_instance_id: number;
|
||||
message_identifier: string;
|
||||
message_type: string;
|
||||
failure_cause: string;
|
||||
status: string;
|
||||
created_at_in_seconds: number;
|
||||
message_correlations?: MessageCorrelations;
|
||||
}
|
||||
|
||||
export interface ReportFilter {
|
||||
field_name: string;
|
||||
field_value: string;
|
||||
operator?: string;
|
||||
}
|
||||
|
||||
export interface ReportColumn {
|
||||
Header: string;
|
||||
accessor: string;
|
||||
filterable: boolean;
|
||||
}
|
||||
|
||||
export interface ReportColumnForEditing extends ReportColumn {
|
||||
filter_field_value: string;
|
||||
filter_operator: string;
|
||||
}
|
||||
|
||||
export interface ReportMetadata {
|
||||
columns: ReportColumn[];
|
||||
filter_by: ReportFilter[];
|
||||
order_by: string[];
|
||||
}
|
||||
|
||||
export interface ProcessInstanceReport {
|
||||
id: string;
|
||||
display_name: string;
|
||||
id: number;
|
||||
identifier: string;
|
||||
name: string;
|
||||
report_metadata: ReportMetadata;
|
||||
}
|
||||
|
||||
export interface ProcessGroupLite {
|
||||
|
@ -51,6 +97,11 @@ export interface ProcessGroupLite {
|
|||
display_name: string;
|
||||
}
|
||||
|
||||
export interface MetadataExtractionPath {
|
||||
key: string;
|
||||
path: string;
|
||||
}
|
||||
|
||||
export interface ProcessModel {
|
||||
id: string;
|
||||
description: string;
|
||||
|
@ -58,6 +109,7 @@ export interface ProcessModel {
|
|||
primary_file_name: string;
|
||||
files: ProcessFile[];
|
||||
parent_groups?: ProcessGroupLite[];
|
||||
metadata_extraction_paths?: MetadataExtractionPath[];
|
||||
}
|
||||
|
||||
export interface ProcessGroup {
|
||||
|
|
|
@ -71,11 +71,11 @@ export default function AdminRoutes() {
|
|||
element={<ProcessModelEdit />}
|
||||
/>
|
||||
<Route
|
||||
path="process-models/:process_model_id/process-instances/:process_instance_id"
|
||||
path="process-instances/:process_model_id/:process_instance_id"
|
||||
element={<ProcessInstanceShow />}
|
||||
/>
|
||||
<Route
|
||||
path="process-models/:process_model_id/process-instances/:process_instance_id/:spiff_step"
|
||||
path="process-instances/:process_model_id/:process_instance_id/:spiff_step"
|
||||
element={<ProcessInstanceShow />}
|
||||
/>
|
||||
<Route
|
||||
|
@ -103,7 +103,7 @@ export default function AdminRoutes() {
|
|||
element={<ReactFormEditor />}
|
||||
/>
|
||||
<Route
|
||||
path="process-models/:process_model_id/process-instances/:process_instance_id/logs"
|
||||
path="logs/:process_model_id/:process_instance_id"
|
||||
element={<ProcessInstanceLogList />}
|
||||
/>
|
||||
<Route path="process-instances" element={<ProcessInstanceList />} />
|
||||
|
|
|
@ -1,15 +1,19 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
// @ts-ignore
|
||||
import { Table } from '@carbon/react';
|
||||
import { ErrorOutline } from '@carbon/icons-react';
|
||||
// @ts-ignore
|
||||
import { Table, Modal, Button } from '@carbon/react';
|
||||
import { Link, useParams, useSearchParams } from 'react-router-dom';
|
||||
import PaginationForTable from '../components/PaginationForTable';
|
||||
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
|
||||
import {
|
||||
convertSecondsToFormattedDateString,
|
||||
convertSecondsToFormattedDateTime,
|
||||
getPageInfoFromSearchParams,
|
||||
modifyProcessIdentifierForPathParam,
|
||||
} from '../helpers';
|
||||
import HttpService from '../services/HttpService';
|
||||
import { FormatProcessModelDisplayName } from '../components/MiniComponents';
|
||||
import { MessageInstance } from '../interfaces';
|
||||
|
||||
export default function MessageInstanceList() {
|
||||
const params = useParams();
|
||||
|
@ -17,6 +21,9 @@ export default function MessageInstanceList() {
|
|||
const [messageIntances, setMessageInstances] = useState([]);
|
||||
const [pagination, setPagination] = useState(null);
|
||||
|
||||
const [messageInstanceForModal, setMessageInstanceForModal] =
|
||||
useState<MessageInstance | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const setMessageInstanceListFromResult = (result: any) => {
|
||||
setMessageInstances(result.results);
|
||||
|
@ -35,41 +42,89 @@ export default function MessageInstanceList() {
|
|||
});
|
||||
}, [searchParams, params]);
|
||||
|
||||
const buildTable = () => {
|
||||
// return null;
|
||||
const rows = messageIntances.map((row) => {
|
||||
const rowToUse = row as any;
|
||||
const handleCorrelationDisplayClose = () => {
|
||||
setMessageInstanceForModal(null);
|
||||
};
|
||||
|
||||
const correlationsDisplayModal = () => {
|
||||
if (messageInstanceForModal) {
|
||||
let failureCausePre = null;
|
||||
if (messageInstanceForModal.failure_cause) {
|
||||
failureCausePre = (
|
||||
<>
|
||||
<p className="failure-string">
|
||||
{messageInstanceForModal.failure_cause}
|
||||
</p>
|
||||
<br />
|
||||
</>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<tr key={rowToUse.id}>
|
||||
<td>{rowToUse.id}</td>
|
||||
<td>
|
||||
<Link
|
||||
data-qa="process-model-show-link"
|
||||
to={`/admin/process-models/${modifyProcessIdentifierForPathParam(
|
||||
rowToUse.process_model_identifier
|
||||
)}`}
|
||||
>
|
||||
{rowToUse.process_model_identifier}
|
||||
</Link>
|
||||
</td>
|
||||
<Modal
|
||||
open={!!messageInstanceForModal}
|
||||
passiveModal
|
||||
onRequestClose={handleCorrelationDisplayClose}
|
||||
modalHeading={`Message ${messageInstanceForModal.id} (${messageInstanceForModal.message_identifier}) ${messageInstanceForModal.message_type} data:`}
|
||||
modalLabel="Details"
|
||||
>
|
||||
{failureCausePre}
|
||||
<p>Correlations:</p>
|
||||
<pre>
|
||||
{JSON.stringify(
|
||||
messageInstanceForModal.message_correlations,
|
||||
null,
|
||||
2
|
||||
)}
|
||||
</pre>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const buildTable = () => {
|
||||
const rows = messageIntances.map((row: MessageInstance) => {
|
||||
let errorIcon = null;
|
||||
let errorTitle = null;
|
||||
if (row.failure_cause) {
|
||||
errorTitle = 'Instance has an error';
|
||||
errorIcon = (
|
||||
<>
|
||||
|
||||
<ErrorOutline className="red-icon" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<tr key={row.id}>
|
||||
<td>{row.id}</td>
|
||||
<td>{FormatProcessModelDisplayName(row)}</td>
|
||||
<td>
|
||||
<Link
|
||||
data-qa="process-instance-show-link"
|
||||
to={`/admin/process-models/${modifyProcessIdentifierForPathParam(
|
||||
rowToUse.process_model_identifier
|
||||
)}/process-instances/${rowToUse.process_instance_id}`}
|
||||
to={`/admin/process-instances/${modifyProcessIdentifierForPathParam(
|
||||
row.process_model_identifier
|
||||
)}/${row.process_instance_id}`}
|
||||
>
|
||||
{rowToUse.process_instance_id}
|
||||
{row.process_instance_id}
|
||||
</Link>
|
||||
</td>
|
||||
<td>{rowToUse.message_identifier}</td>
|
||||
<td>{rowToUse.message_type}</td>
|
||||
<td>{rowToUse.failure_cause || '-'}</td>
|
||||
<td>{rowToUse.status}</td>
|
||||
<td>{row.message_identifier}</td>
|
||||
<td>{row.message_type}</td>
|
||||
<td>
|
||||
{convertSecondsToFormattedDateString(
|
||||
rowToUse.created_at_in_seconds
|
||||
)}
|
||||
<Button
|
||||
kind="ghost"
|
||||
className="button-link"
|
||||
onClick={() => setMessageInstanceForModal(row)}
|
||||
title={errorTitle}
|
||||
>
|
||||
View
|
||||
{errorIcon}
|
||||
</Button>
|
||||
</td>
|
||||
<td>{row.status}</td>
|
||||
<td>
|
||||
{convertSecondsToFormattedDateTime(row.created_at_in_seconds)}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
|
@ -78,12 +133,12 @@ export default function MessageInstanceList() {
|
|||
<Table striped bordered>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Instance Id</th>
|
||||
<th>Process Model</th>
|
||||
<th>Id</th>
|
||||
<th>Process</th>
|
||||
<th>Process Instance</th>
|
||||
<th>Message Model</th>
|
||||
<th>Name</th>
|
||||
<th>Type</th>
|
||||
<th>Failure Cause</th>
|
||||
<th>Details</th>
|
||||
<th>Status</th>
|
||||
<th>Created At</th>
|
||||
</tr>
|
||||
|
@ -108,9 +163,9 @@ export default function MessageInstanceList() {
|
|||
},
|
||||
[
|
||||
`Process Instance: ${searchParams.get('process_instance_id')}`,
|
||||
`/admin/process-models/${searchParams.get(
|
||||
`/admin/process-instances/${searchParams.get(
|
||||
'process_model_id'
|
||||
)}/process-instances/${searchParams.get('process_instance_id')}`,
|
||||
)}/${searchParams.get('process_instance_id')}`,
|
||||
],
|
||||
['Messages'],
|
||||
]}
|
||||
|
@ -121,6 +176,7 @@ export default function MessageInstanceList() {
|
|||
<>
|
||||
{breadcrumbElement}
|
||||
<h1>Messages</h1>
|
||||
{correlationsDisplayModal()}
|
||||
<PaginationForTable
|
||||
page={page}
|
||||
perPage={perPage}
|
||||
|
|
|
@ -55,9 +55,9 @@ export default function MyTasks() {
|
|||
<p>
|
||||
Process Instance {processInstance.id} kicked off (
|
||||
<Link
|
||||
to={`/admin/process-models/${modifyProcessIdentifierForPathParam(
|
||||
to={`/admin/process-instances/${modifyProcessIdentifierForPathParam(
|
||||
processInstance.process_model_identifier
|
||||
)}/process-instances/${processInstance.id}`}
|
||||
)}/${processInstance.id}`}
|
||||
data-qa="process-instance-show-link"
|
||||
>
|
||||
view
|
||||
|
@ -95,7 +95,7 @@ export default function MyTasks() {
|
|||
<td>
|
||||
<Link
|
||||
data-qa="process-instance-show-link"
|
||||
to={`/admin/process-models/${modifiedProcessModelIdentifier}/process-instances/${rowToUse.process_instance_id}`}
|
||||
to={`/admin/process-instances/${modifiedProcessModelIdentifier}/${rowToUse.process_instance_id}`}
|
||||
>
|
||||
{rowToUse.process_instance_id}
|
||||
</Link>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
// @ts-ignore
|
||||
import { Table } from '@carbon/react';
|
||||
import { Table, Tabs, TabList, Tab } from '@carbon/react';
|
||||
import { useParams, useSearchParams, Link } from 'react-router-dom';
|
||||
import PaginationForTable from '../components/PaginationForTable';
|
||||
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
|
||||
|
@ -10,15 +10,18 @@ import {
|
|||
convertSecondsToFormattedDateTime,
|
||||
} from '../helpers';
|
||||
import HttpService from '../services/HttpService';
|
||||
import { useUriListForPermissions } from '../hooks/UriListForPermissions';
|
||||
|
||||
export default function ProcessInstanceLogList() {
|
||||
const params = useParams();
|
||||
const [searchParams] = useSearchParams();
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const [processInstanceLogs, setProcessInstanceLogs] = useState([]);
|
||||
const [pagination, setPagination] = useState(null);
|
||||
const modifiedProcessModelId = modifyProcessIdentifierForPathParam(
|
||||
`${params.process_model_id}`
|
||||
);
|
||||
const { targetUris } = useUriListForPermissions();
|
||||
const isDetailedView = searchParams.get('detailed') === 'true';
|
||||
|
||||
useEffect(() => {
|
||||
const setProcessInstanceLogListFromResult = (result: any) => {
|
||||
|
@ -27,26 +30,36 @@ export default function ProcessInstanceLogList() {
|
|||
};
|
||||
const { page, perPage } = getPageInfoFromSearchParams(searchParams);
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-instances/${params.process_instance_id}/logs?per_page=${perPage}&page=${page}`,
|
||||
path: `${targetUris.processInstanceLogListPath}?per_page=${perPage}&page=${page}&detailed=${isDetailedView}`,
|
||||
successCallback: setProcessInstanceLogListFromResult,
|
||||
});
|
||||
}, [searchParams, params]);
|
||||
}, [
|
||||
searchParams,
|
||||
params,
|
||||
targetUris.processInstanceLogListPath,
|
||||
isDetailedView,
|
||||
]);
|
||||
|
||||
const buildTable = () => {
|
||||
const rows = processInstanceLogs.map((row) => {
|
||||
const rowToUse = row as any;
|
||||
return (
|
||||
<tr key={rowToUse.id}>
|
||||
<td>{rowToUse.bpmn_process_identifier}</td>
|
||||
<td>{rowToUse.id}</td>
|
||||
<td>{rowToUse.message}</td>
|
||||
<td>{rowToUse.bpmn_task_identifier}</td>
|
||||
<td>{rowToUse.bpmn_task_name}</td>
|
||||
<td>{rowToUse.bpmn_task_type}</td>
|
||||
{isDetailedView && (
|
||||
<>
|
||||
<td>{rowToUse.bpmn_task_identifier}</td>
|
||||
<td>{rowToUse.bpmn_task_type}</td>
|
||||
<td>{rowToUse.bpmn_process_identifier}</td>
|
||||
</>
|
||||
)}
|
||||
<td>{rowToUse.username}</td>
|
||||
<td>
|
||||
<Link
|
||||
data-qa="process-instance-show-link"
|
||||
to={`/admin/process-models/${modifiedProcessModelId}/process-instances/${rowToUse.process_instance_id}/${rowToUse.spiff_step}`}
|
||||
to={`/admin/process-instances/${modifiedProcessModelId}/${rowToUse.process_instance_id}/${rowToUse.spiff_step}`}
|
||||
>
|
||||
{convertSecondsToFormattedDateTime(rowToUse.timestamp)}
|
||||
</Link>
|
||||
|
@ -58,11 +71,16 @@ export default function ProcessInstanceLogList() {
|
|||
<Table size="lg">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Bpmn Process Identifier</th>
|
||||
<th>Id</th>
|
||||
<th>Message</th>
|
||||
<th>Task Identifier</th>
|
||||
<th>Task Name</th>
|
||||
<th>Task Type</th>
|
||||
{isDetailedView && (
|
||||
<>
|
||||
<th>Task Identifier</th>
|
||||
<th>Task Type</th>
|
||||
<th>Bpmn Process Identifier</th>
|
||||
</>
|
||||
)}
|
||||
<th>User</th>
|
||||
<th>Timestamp</th>
|
||||
</tr>
|
||||
|
@ -71,11 +89,12 @@ export default function ProcessInstanceLogList() {
|
|||
</Table>
|
||||
);
|
||||
};
|
||||
const selectedTabIndex = isDetailedView ? 1 : 0;
|
||||
|
||||
if (pagination) {
|
||||
const { page, perPage } = getPageInfoFromSearchParams(searchParams);
|
||||
return (
|
||||
<main>
|
||||
<>
|
||||
<ProcessBreadcrumb
|
||||
hotCrumbs={[
|
||||
['Process Groups', '/admin'],
|
||||
|
@ -86,18 +105,41 @@ export default function ProcessInstanceLogList() {
|
|||
},
|
||||
[
|
||||
`Process Instance: ${params.process_instance_id}`,
|
||||
`/admin/process-models/${params.process_model_id}/process-instances/${params.process_instance_id}`,
|
||||
`/admin/process-instances/${params.process_model_id}/${params.process_instance_id}`,
|
||||
],
|
||||
['Logs'],
|
||||
]}
|
||||
/>
|
||||
<Tabs selectedIndex={selectedTabIndex}>
|
||||
<TabList aria-label="List of tabs">
|
||||
<Tab
|
||||
title="Only show a subset of the logs, and show fewer columns"
|
||||
onClick={() => {
|
||||
searchParams.set('detailed', 'false');
|
||||
setSearchParams(searchParams);
|
||||
}}
|
||||
>
|
||||
Simple
|
||||
</Tab>
|
||||
<Tab
|
||||
title="Show all logs for this process instance, and show extra columns that may be useful for debugging"
|
||||
onClick={() => {
|
||||
searchParams.set('detailed', 'true');
|
||||
setSearchParams(searchParams);
|
||||
}}
|
||||
>
|
||||
Detailed
|
||||
</Tab>
|
||||
</TabList>
|
||||
</Tabs>
|
||||
<br />
|
||||
<PaginationForTable
|
||||
page={page}
|
||||
perPage={perPage}
|
||||
pagination={pagination}
|
||||
tableToDisplay={buildTable()}
|
||||
/>
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
|
|
|
@ -59,6 +59,11 @@ export default function ProcessInstanceShow() {
|
|||
const permissionRequestData: PermissionsToCheck = {
|
||||
[targetUris.messageInstanceListPath]: ['GET'],
|
||||
[targetUris.processInstanceTaskListPath]: ['GET'],
|
||||
[targetUris.processInstanceActionPath]: ['DELETE'],
|
||||
[targetUris.processInstanceLogListPath]: ['GET'],
|
||||
[`${targetUris.processInstanceActionPath}/suspend`]: ['PUT'],
|
||||
[`${targetUris.processInstanceActionPath}/terminate`]: ['PUT'],
|
||||
[`${targetUris.processInstanceActionPath}/resume`]: ['PUT'],
|
||||
};
|
||||
const { ability, permissionsLoaded } = usePermissionFetcher(
|
||||
permissionRequestData
|
||||
|
@ -76,7 +81,7 @@ export default function ProcessInstanceShow() {
|
|||
setTasksCallHadError(true);
|
||||
};
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-models/${modifiedProcessModelId}/process-instances/${params.process_instance_id}`,
|
||||
path: `/process-instances/${modifiedProcessModelId}/${params.process_instance_id}`,
|
||||
successCallback: setProcessInstance,
|
||||
});
|
||||
let taskParams = '?all_tasks=true';
|
||||
|
@ -85,7 +90,7 @@ export default function ProcessInstanceShow() {
|
|||
}
|
||||
if (ability.can('GET', targetUris.processInstanceTaskListPath)) {
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-instances/${modifiedProcessModelId}/${params.process_instance_id}/tasks${taskParams}`,
|
||||
path: `${targetUris.processInstanceTaskListPath}${taskParams}`,
|
||||
successCallback: setTasks,
|
||||
failureCallback: processTaskFailure,
|
||||
});
|
||||
|
@ -97,7 +102,7 @@ export default function ProcessInstanceShow() {
|
|||
|
||||
const deleteProcessInstance = () => {
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-instances/${params.process_instance_id}`,
|
||||
path: targetUris.processInstanceActionPath,
|
||||
successCallback: navigateToProcessInstances,
|
||||
httpMethod: 'DELETE',
|
||||
});
|
||||
|
@ -110,7 +115,7 @@ export default function ProcessInstanceShow() {
|
|||
|
||||
const terminateProcessInstance = () => {
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-instances/${params.process_instance_id}/terminate`,
|
||||
path: `${targetUris.processInstanceActionPath}/terminate`,
|
||||
successCallback: refreshPage,
|
||||
httpMethod: 'POST',
|
||||
});
|
||||
|
@ -118,7 +123,7 @@ export default function ProcessInstanceShow() {
|
|||
|
||||
const suspendProcessInstance = () => {
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-instances/${params.process_instance_id}/suspend`,
|
||||
path: `${targetUris.processInstanceActionPath}/suspend`,
|
||||
successCallback: refreshPage,
|
||||
httpMethod: 'POST',
|
||||
});
|
||||
|
@ -126,7 +131,7 @@ export default function ProcessInstanceShow() {
|
|||
|
||||
const resumeProcessInstance = () => {
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-instances/${params.process_instance_id}/resume`,
|
||||
path: `${targetUris.processInstanceActionPath}/resume`,
|
||||
successCallback: refreshPage,
|
||||
httpMethod: 'POST',
|
||||
});
|
||||
|
@ -174,7 +179,7 @@ export default function ProcessInstanceShow() {
|
|||
<Link
|
||||
reloadDocument
|
||||
data-qa="process-instance-step-link"
|
||||
to={`/admin/process-models/${
|
||||
to={`/admin/process-instances/${
|
||||
params.process_model_id
|
||||
}/process-instances/${params.process_instance_id}/${
|
||||
currentSpiffStep(processInstanceToUse) + distance
|
||||
|
@ -209,7 +214,7 @@ export default function ProcessInstanceShow() {
|
|||
if (currentEndDate) {
|
||||
currentEndDateTag = (
|
||||
<Grid condensed fullWidth>
|
||||
<Column sm={1} md={1} lg={1} className="grid-list-title">
|
||||
<Column sm={1} md={1} lg={2} className="grid-list-title">
|
||||
Completed:{' '}
|
||||
</Column>
|
||||
<Column sm={3} md={3} lg={3} className="grid-date">
|
||||
|
@ -235,7 +240,7 @@ export default function ProcessInstanceShow() {
|
|||
return (
|
||||
<>
|
||||
<Grid condensed fullWidth>
|
||||
<Column sm={1} md={1} lg={1} className="grid-list-title">
|
||||
<Column sm={1} md={1} lg={2} className="grid-list-title">
|
||||
Started:{' '}
|
||||
</Column>
|
||||
<Column sm={3} md={3} lg={3} className="grid-date">
|
||||
|
@ -246,7 +251,7 @@ export default function ProcessInstanceShow() {
|
|||
</Grid>
|
||||
{currentEndDateTag}
|
||||
<Grid condensed fullWidth>
|
||||
<Column sm={1} md={1} lg={1} className="grid-list-title">
|
||||
<Column sm={1} md={1} lg={2} className="grid-list-title">
|
||||
Status:{' '}
|
||||
</Column>
|
||||
<Column sm={3} md={3} lg={3}>
|
||||
|
@ -259,14 +264,20 @@ export default function ProcessInstanceShow() {
|
|||
<Grid condensed fullWidth>
|
||||
<Column sm={2} md={2} lg={2}>
|
||||
<ButtonSet>
|
||||
<Button
|
||||
size="sm"
|
||||
className="button-white-background"
|
||||
data-qa="process-instance-log-list-link"
|
||||
href={`/admin/process-models/${modifiedProcessModelId}/process-instances/${params.process_instance_id}/logs`}
|
||||
<Can
|
||||
I="GET"
|
||||
a={targetUris.processInstanceLogListPath}
|
||||
ability={ability}
|
||||
>
|
||||
Logs
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
className="button-white-background"
|
||||
data-qa="process-instance-log-list-link"
|
||||
href={`/admin/logs/${modifiedProcessModelId}/${params.process_instance_id}`}
|
||||
>
|
||||
Logs
|
||||
</Button>
|
||||
</Can>
|
||||
<Can
|
||||
I="GET"
|
||||
a={targetUris.messageInstanceListPath}
|
||||
|
@ -436,8 +447,8 @@ export default function ProcessInstanceShow() {
|
|||
// taskToUse is copy of taskToDisplay, with taskDataToDisplay in data attribute
|
||||
const taskToUse: any = { ...taskToDisplay, data: taskDataToDisplay };
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-instances/${params.process_instance_id}/task/${taskToUse.id}/update`,
|
||||
httpMethod: 'POST',
|
||||
path: `/task-data/${modifiedProcessModelId}/${params.process_instance_id}/${taskToUse.id}`,
|
||||
httpMethod: 'PUT',
|
||||
successCallback: saveTaskDataResult,
|
||||
failureCallback: saveTaskDataFailure,
|
||||
postBody: {
|
||||
|
@ -544,21 +555,33 @@ export default function ProcessInstanceShow() {
|
|||
|
||||
const buttonIcons = (processInstanceToUse: any) => {
|
||||
const elements = [];
|
||||
elements.push(terminateButton(processInstanceToUse));
|
||||
elements.push(suspendButton(processInstanceToUse));
|
||||
elements.push(resumeButton(processInstanceToUse));
|
||||
elements.push(
|
||||
<ButtonWithConfirmation
|
||||
data-qa="process-instance-delete"
|
||||
kind="ghost"
|
||||
renderIcon={TrashCan}
|
||||
iconDescription="Delete"
|
||||
hasIconOnly
|
||||
description={`Delete Process Instance: ${processInstanceToUse.id}`}
|
||||
onConfirmation={deleteProcessInstance}
|
||||
confirmButtonLabel="Delete"
|
||||
/>
|
||||
);
|
||||
if (
|
||||
ability.can('POST', `${targetUris.processInstanceActionPath}/terminate`)
|
||||
) {
|
||||
elements.push(terminateButton(processInstanceToUse));
|
||||
}
|
||||
if (
|
||||
ability.can('POST', `${targetUris.processInstanceActionPath}/suspend`)
|
||||
) {
|
||||
elements.push(suspendButton(processInstanceToUse));
|
||||
}
|
||||
if (ability.can('POST', `${targetUris.processInstanceActionPath}/resume`)) {
|
||||
elements.push(resumeButton(processInstanceToUse));
|
||||
}
|
||||
if (ability.can('DELETE', targetUris.processInstanceActionPath)) {
|
||||
elements.push(
|
||||
<ButtonWithConfirmation
|
||||
data-qa="process-instance-delete"
|
||||
kind="ghost"
|
||||
renderIcon={TrashCan}
|
||||
iconDescription="Delete"
|
||||
hasIconOnly
|
||||
description={`Delete Process Instance: ${processInstanceToUse.id}`}
|
||||
onConfirmation={deleteProcessInstance}
|
||||
confirmButtonLabel="Delete"
|
||||
/>
|
||||
);
|
||||
}
|
||||
return elements;
|
||||
};
|
||||
|
||||
|
|
|
@ -283,7 +283,7 @@ export default function ProcessModelEditDiagram() {
|
|||
|
||||
const onServiceTasksRequested = (event: any) => {
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/service_tasks`,
|
||||
path: `/service-tasks`,
|
||||
successCallback: makeApiHandler(event),
|
||||
});
|
||||
};
|
||||
|
@ -735,7 +735,7 @@ export default function ProcessModelEditDiagram() {
|
|||
if (processModel) {
|
||||
const files = processModel.files.filter((f) => f.type === type);
|
||||
files.some((file) => {
|
||||
if (file.references.some((ref) => ref.id === id)) {
|
||||
if (file.references.some((ref) => ref.identifier === id)) {
|
||||
matchFile = file;
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -66,7 +66,7 @@ export default function ProcessModelShow() {
|
|||
const permissionRequestData: PermissionsToCheck = {
|
||||
[targetUris.processModelShowPath]: ['PUT', 'DELETE'],
|
||||
[targetUris.processInstanceListPath]: ['GET'],
|
||||
[targetUris.processInstanceActionPath]: ['POST'],
|
||||
[targetUris.processInstanceCreatePath]: ['POST'],
|
||||
[targetUris.processModelFileCreatePath]: ['POST', 'PUT', 'GET', 'DELETE'],
|
||||
};
|
||||
const { ability, permissionsLoaded } = usePermissionFetcher(
|
||||
|
@ -95,7 +95,7 @@ export default function ProcessModelShow() {
|
|||
<p>
|
||||
Process Instance {processInstance.id} kicked off (
|
||||
<Link
|
||||
to={`/admin/process-models/${modifiedProcessModelId}/process-instances/${processInstance.id}`}
|
||||
to={`/admin/process-instances/${modifiedProcessModelId}/${processInstance.id}`}
|
||||
data-qa="process-instance-show-link"
|
||||
>
|
||||
view
|
||||
|
@ -556,7 +556,7 @@ export default function ProcessModelShow() {
|
|||
<Stack orientation="horizontal" gap={3}>
|
||||
<Can
|
||||
I="POST"
|
||||
a={targetUris.processInstanceActionPath}
|
||||
a={targetUris.processInstanceCreatePath}
|
||||
ability={ability}
|
||||
>
|
||||
<>
|
||||
|
|
|
@ -26,6 +26,9 @@ import Form from '../themes/carbon';
|
|||
import HttpService from '../services/HttpService';
|
||||
import ErrorContext from '../contexts/ErrorContext';
|
||||
import { modifyProcessIdentifierForPathParam } from '../helpers';
|
||||
import { useUriListForPermissions } from '../hooks/UriListForPermissions';
|
||||
import { PermissionsToCheck } from '../interfaces';
|
||||
import { usePermissionFetcher } from '../hooks/PermissionService';
|
||||
|
||||
export default function TaskShow() {
|
||||
const [task, setTask] = useState(null);
|
||||
|
@ -35,24 +38,36 @@ export default function TaskShow() {
|
|||
|
||||
const setErrorMessage = (useContext as any)(ErrorContext)[1];
|
||||
|
||||
useEffect(() => {
|
||||
const processResult = (result: any) => {
|
||||
setTask(result);
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-instances/${modifyProcessIdentifierForPathParam(
|
||||
result.process_model_identifier
|
||||
)}/${params.process_instance_id}/tasks`,
|
||||
successCallback: setUserTasks,
|
||||
});
|
||||
};
|
||||
const { targetUris } = useUriListForPermissions();
|
||||
const permissionRequestData: PermissionsToCheck = {
|
||||
[targetUris.processInstanceTaskListPath]: ['GET'],
|
||||
};
|
||||
const { ability, permissionsLoaded } = usePermissionFetcher(
|
||||
permissionRequestData
|
||||
);
|
||||
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/tasks/${params.process_instance_id}/${params.task_id}`,
|
||||
successCallback: processResult,
|
||||
// This causes the page to continuously reload
|
||||
// failureCallback: setErrorMessage,
|
||||
});
|
||||
}, [params]);
|
||||
useEffect(() => {
|
||||
if (permissionsLoaded) {
|
||||
const processResult = (result: any) => {
|
||||
setTask(result);
|
||||
if (ability.can('GET', targetUris.processInstanceTaskListPath)) {
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/task-data/${modifyProcessIdentifierForPathParam(
|
||||
result.process_model_identifier
|
||||
)}/${params.process_instance_id}`,
|
||||
successCallback: setUserTasks,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/tasks/${params.process_instance_id}/${params.task_id}`,
|
||||
successCallback: processResult,
|
||||
// This causes the page to continuously reload
|
||||
// failureCallback: setErrorMessage,
|
||||
});
|
||||
}
|
||||
}, [params, permissionsLoaded, ability, targetUris]);
|
||||
|
||||
const processSubmitResult = (result: any) => {
|
||||
setErrorMessage(null);
|
||||
|
@ -116,17 +131,18 @@ export default function TaskShow() {
|
|||
}
|
||||
return null;
|
||||
});
|
||||
return (
|
||||
<Tabs
|
||||
title="Steps in this process instance involving people"
|
||||
selectedIndex={selectedTabIndex}
|
||||
>
|
||||
<TabList aria-label="List of tabs" contained>
|
||||
{userTasksElement}
|
||||
</TabList>
|
||||
</Tabs>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Tabs
|
||||
title="Steps in this process instance involving people"
|
||||
selectedIndex={selectedTabIndex}
|
||||
>
|
||||
<TabList aria-label="List of tabs" contained>
|
||||
{userTasksElement}
|
||||
</TabList>
|
||||
</Tabs>
|
||||
);
|
||||
return null;
|
||||
};
|
||||
|
||||
const formElement = (taskToUse: any) => {
|
||||
|
@ -207,7 +223,7 @@ export default function TaskShow() {
|
|||
);
|
||||
};
|
||||
|
||||
if (task && userTasks) {
|
||||
if (task) {
|
||||
const taskToUse = task as any;
|
||||
let statusString = '';
|
||||
if (taskToUse.state !== 'READY') {
|
||||
|
|
Loading…
Reference in New Issue