mirror of
https://github.com/status-im/spiff-arena.git
synced 2025-02-22 14:38:11 +00:00
Merge pull request #65 from sartography/new_report
Create new report based on current filter/columns
This commit is contained in:
commit
bad7adb322
@ -1,5 +1,5 @@
|
|||||||
pip==22.2.2
|
pip==22.2.2
|
||||||
nox==2022.8.7
|
nox==2022.11.21
|
||||||
nox-poetry==1.0.1
|
nox-poetry==1.0.2
|
||||||
poetry==1.2.2
|
poetry==1.2.2
|
||||||
virtualenv==20.16.5
|
virtualenv==20.16.5
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
"""empty message
|
"""empty message
|
||||||
|
|
||||||
Revision ID: ff1c1628337c
|
Revision ID: 40a2ed63cc5a
|
||||||
Revises:
|
Revises:
|
||||||
Create Date: 2022-11-28 15:08:52.014254
|
Create Date: 2022-11-29 16:59:02.980181
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from alembic import op
|
from alembic import op
|
||||||
@ -10,7 +10,7 @@ import sqlalchemy as sa
|
|||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
revision = 'ff1c1628337c'
|
revision = '40a2ed63cc5a'
|
||||||
down_revision = None
|
down_revision = None
|
||||||
branch_labels = None
|
branch_labels = None
|
||||||
depends_on = None
|
depends_on = None
|
||||||
@ -249,6 +249,7 @@ def upgrade():
|
|||||||
sa.PrimaryKeyConstraint('id'),
|
sa.PrimaryKeyConstraint('id'),
|
||||||
sa.UniqueConstraint('process_instance_id', 'key', name='process_instance_metadata_unique')
|
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',
|
op.create_table('spiff_step_details',
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
sa.Column('process_instance_id', sa.Integer(), nullable=False),
|
sa.Column('process_instance_id', sa.Integer(), nullable=False),
|
||||||
@ -295,6 +296,7 @@ def downgrade():
|
|||||||
op.drop_index(op.f('ix_active_task_user_active_task_id'), table_name='active_task_user')
|
op.drop_index(op.f('ix_active_task_user_active_task_id'), table_name='active_task_user')
|
||||||
op.drop_table('active_task_user')
|
op.drop_table('active_task_user')
|
||||||
op.drop_table('spiff_step_details')
|
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('process_instance_metadata')
|
||||||
op.drop_table('permission_assignment')
|
op.drop_table('permission_assignment')
|
||||||
op.drop_table('message_instance')
|
op.drop_table('message_instance')
|
13
spiffworkflow-backend/poetry.lock
generated
13
spiffworkflow-backend/poetry.lock
generated
@ -1851,7 +1851,7 @@ lxml = "*"
|
|||||||
type = "git"
|
type = "git"
|
||||||
url = "https://github.com/sartography/SpiffWorkflow"
|
url = "https://github.com/sartography/SpiffWorkflow"
|
||||||
reference = "main"
|
reference = "main"
|
||||||
resolved_reference = "062eaf15d28c66f8cf07f68409429560251b12c7"
|
resolved_reference = "ffb1686757f944065580dd2db8def73d6c1f0134"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "SQLAlchemy"
|
name = "SQLAlchemy"
|
||||||
@ -2989,7 +2989,18 @@ psycopg2 = [
|
|||||||
{file = "psycopg2-2.9.4.tar.gz", hash = "sha256:d529926254e093a1b669f692a3aa50069bc71faf5b0ecd91686a78f62767d52f"},
|
{file = "psycopg2-2.9.4.tar.gz", hash = "sha256:d529926254e093a1b669f692a3aa50069bc71faf5b0ecd91686a78f62767d52f"},
|
||||||
]
|
]
|
||||||
pyasn1 = [
|
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-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"},
|
{file = "pyasn1-0.4.8.tar.gz", hash = "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba"},
|
||||||
]
|
]
|
||||||
pycodestyle = [
|
pycodestyle = [
|
||||||
|
@ -544,6 +544,12 @@ paths:
|
|||||||
description: Specifies the identifier of a report to use, if any
|
description: Specifies the identifier of a report to use, if any
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
|
- name: report_id
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
description: Specifies the identifier of a report to use, if any
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
get:
|
get:
|
||||||
operationId: spiffworkflow_backend.routes.process_api_blueprint.process_instance_list
|
operationId: spiffworkflow_backend.routes.process_api_blueprint.process_instance_list
|
||||||
summary: Returns a list of process instances for a given process model
|
summary: Returns a list of process instances for a given process model
|
||||||
@ -841,14 +847,30 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/OkTrue"
|
$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:
|
parameters:
|
||||||
- name: report_identifier
|
- name: report_id
|
||||||
in: path
|
in: path
|
||||||
required: true
|
required: true
|
||||||
description: The unique id of an existing report
|
description: The unique id of an existing report
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: integer
|
||||||
- name: page
|
- name: page
|
||||||
in: query
|
in: query
|
||||||
required: false
|
required: false
|
||||||
|
@ -23,7 +23,7 @@ class ProcessInstanceMetadataModel(SpiffworkflowBaseDBModel):
|
|||||||
process_instance_id: int = db.Column(
|
process_instance_id: int = db.Column(
|
||||||
ForeignKey(ProcessInstanceModel.id), nullable=False # type: ignore
|
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)
|
value: str = db.Column(db.String(255), nullable=False)
|
||||||
|
|
||||||
updated_at_in_seconds: int = db.Column(db.Integer, 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]
|
ReportMetadata = dict[str, Any]
|
||||||
|
|
||||||
|
|
||||||
|
class ProcessInstanceReportAlreadyExistsError(Exception):
|
||||||
|
"""ProcessInstanceReportAlreadyExistsError."""
|
||||||
|
|
||||||
|
|
||||||
class ProcessInstanceReportResult(TypedDict):
|
class ProcessInstanceReportResult(TypedDict):
|
||||||
"""ProcessInstanceReportResult."""
|
"""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)
|
identifier: str = db.Column(db.String(50), nullable=False, index=True)
|
||||||
report_metadata: dict = deferred(db.Column(db.JSON)) # type: ignore
|
report_metadata: dict = deferred(db.Column(db.JSON)) # type: ignore
|
||||||
created_by_id = db.Column(ForeignKey(UserModel.id), nullable=False, index=True)
|
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)
|
created_at_in_seconds = db.Column(db.Integer)
|
||||||
updated_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
|
@classmethod
|
||||||
def add_fixtures(cls) -> None:
|
def add_fixtures(cls) -> None:
|
||||||
"""Add_fixtures."""
|
"""Add_fixtures."""
|
||||||
@ -120,14 +129,18 @@ class ProcessInstanceReportModel(SpiffworkflowBaseDBModel):
|
|||||||
identifier: str,
|
identifier: str,
|
||||||
user: UserModel,
|
user: UserModel,
|
||||||
report_metadata: ReportMetadata,
|
report_metadata: ReportMetadata,
|
||||||
) -> None:
|
) -> ProcessInstanceReportModel:
|
||||||
"""Make_fixture_report."""
|
"""Make_fixture_report."""
|
||||||
process_instance_report = ProcessInstanceReportModel.query.filter_by(
|
process_instance_report = ProcessInstanceReportModel.query.filter_by(
|
||||||
identifier=identifier,
|
identifier=identifier,
|
||||||
created_by_id=user.id,
|
created_by_id=user.id,
|
||||||
).first()
|
).first()
|
||||||
|
|
||||||
if process_instance_report is None:
|
if process_instance_report is not None:
|
||||||
|
raise ProcessInstanceReportAlreadyExistsError(
|
||||||
|
f"Process instance report with identifier already exists: {identifier}"
|
||||||
|
)
|
||||||
|
|
||||||
process_instance_report = cls(
|
process_instance_report = cls(
|
||||||
identifier=identifier,
|
identifier=identifier,
|
||||||
created_by_id=user.id,
|
created_by_id=user.id,
|
||||||
@ -136,6 +149,8 @@ class ProcessInstanceReportModel(SpiffworkflowBaseDBModel):
|
|||||||
db.session.add(process_instance_report)
|
db.session.add(process_instance_report)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
return process_instance_report # type: ignore
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def ticket_for_month_report(cls) -> dict:
|
def ticket_for_month_report(cls) -> dict:
|
||||||
"""Ticket_for_month_report."""
|
"""Ticket_for_month_report."""
|
||||||
@ -204,18 +219,8 @@ class ProcessInstanceReportModel(SpiffworkflowBaseDBModel):
|
|||||||
user: UserModel,
|
user: UserModel,
|
||||||
) -> ProcessInstanceReportModel:
|
) -> ProcessInstanceReportModel:
|
||||||
"""Create_with_attributes."""
|
"""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(
|
process_instance_report = cls(
|
||||||
identifier=identifier,
|
identifier=identifier,
|
||||||
# >>>>>>> main
|
|
||||||
created_by_id=user.id,
|
created_by_id=user.id,
|
||||||
report_metadata=report_metadata,
|
report_metadata=report_metadata,
|
||||||
)
|
)
|
||||||
|
@ -38,6 +38,7 @@ class ProcessModelInfo:
|
|||||||
fault_or_suspend_on_exception: str = NotificationType.fault.value
|
fault_or_suspend_on_exception: str = NotificationType.fault.value
|
||||||
exception_notification_addresses: list[str] = field(default_factory=list)
|
exception_notification_addresses: list[str] = field(default_factory=list)
|
||||||
parent_groups: list[dict] | None = None
|
parent_groups: list[dict] | None = None
|
||||||
|
metadata_extraction_paths: list[dict[str, str]] | None = None
|
||||||
|
|
||||||
def __post_init__(self) -> None:
|
def __post_init__(self) -> None:
|
||||||
"""__post_init__."""
|
"""__post_init__."""
|
||||||
@ -76,6 +77,13 @@ class ProcessModelInfoSchema(Schema):
|
|||||||
exception_notification_addresses = marshmallow.fields.List(
|
exception_notification_addresses = marshmallow.fields.List(
|
||||||
marshmallow.fields.String
|
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
|
@post_load
|
||||||
def make_spec(
|
def make_spec(
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
"""APIs for dealing with process groups, process models, and process instances."""
|
"""APIs for dealing with process groups, process models, and process instances."""
|
||||||
import json
|
import json
|
||||||
import random
|
import random
|
||||||
|
import re
|
||||||
import string
|
import string
|
||||||
import uuid
|
import uuid
|
||||||
from typing import Any
|
from typing import Any
|
||||||
@ -30,6 +31,8 @@ from SpiffWorkflow.task import TaskState
|
|||||||
from sqlalchemy import and_
|
from sqlalchemy import and_
|
||||||
from sqlalchemy import asc
|
from sqlalchemy import asc
|
||||||
from sqlalchemy import desc
|
from sqlalchemy import desc
|
||||||
|
from sqlalchemy import func
|
||||||
|
from sqlalchemy.orm import aliased
|
||||||
from sqlalchemy.orm import joinedload
|
from sqlalchemy.orm import joinedload
|
||||||
|
|
||||||
from spiffworkflow_backend.exceptions.process_entity_not_found_error import (
|
from spiffworkflow_backend.exceptions.process_entity_not_found_error import (
|
||||||
@ -52,6 +55,9 @@ from spiffworkflow_backend.models.process_instance import ProcessInstanceApiSche
|
|||||||
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
|
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
|
||||||
from spiffworkflow_backend.models.process_instance import ProcessInstanceModelSchema
|
from spiffworkflow_backend.models.process_instance import ProcessInstanceModelSchema
|
||||||
from spiffworkflow_backend.models.process_instance import ProcessInstanceStatus
|
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 (
|
from spiffworkflow_backend.models.process_instance_report import (
|
||||||
ProcessInstanceReportModel,
|
ProcessInstanceReportModel,
|
||||||
)
|
)
|
||||||
@ -256,19 +262,26 @@ def process_model_create(
|
|||||||
modified_process_group_id: str, body: Dict[str, Union[str, bool, int]]
|
modified_process_group_id: str, body: Dict[str, Union[str, bool, int]]
|
||||||
) -> flask.wrappers.Response:
|
) -> flask.wrappers.Response:
|
||||||
"""Process_model_create."""
|
"""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:
|
if modified_process_group_id is None:
|
||||||
raise ApiError(
|
raise ApiError(
|
||||||
error_code="process_group_id_not_specified",
|
error_code="process_group_id_not_specified",
|
||||||
message="Process Model could not be created when process_group_id path param is unspecified",
|
message="Process Model could not be created when process_group_id path param is unspecified",
|
||||||
status_code=400,
|
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(
|
unmodified_process_group_id = un_modify_modified_process_model_id(
|
||||||
modified_process_group_id
|
modified_process_group_id
|
||||||
@ -281,6 +294,14 @@ def process_model_create(
|
|||||||
status_code=400,
|
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)
|
ProcessModelService.add_process_model(process_model_info)
|
||||||
return Response(
|
return Response(
|
||||||
json.dumps(ProcessModelInfoSchema().dump(process_model_info)),
|
json.dumps(ProcessModelInfoSchema().dump(process_model_info)),
|
||||||
@ -294,7 +315,6 @@ def process_model_delete(
|
|||||||
) -> flask.wrappers.Response:
|
) -> flask.wrappers.Response:
|
||||||
"""Process_model_delete."""
|
"""Process_model_delete."""
|
||||||
process_model_identifier = modified_process_model_identifier.replace(":", "/")
|
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)
|
ProcessModelService().process_model_delete(process_model_identifier)
|
||||||
return Response(json.dumps({"ok": True}), status=200, mimetype="application/json")
|
return Response(json.dumps({"ok": True}), status=200, mimetype="application/json")
|
||||||
|
|
||||||
@ -309,6 +329,7 @@ def process_model_update(
|
|||||||
"primary_file_name",
|
"primary_file_name",
|
||||||
"primary_process_id",
|
"primary_process_id",
|
||||||
"description",
|
"description",
|
||||||
|
"metadata_extraction_paths",
|
||||||
]
|
]
|
||||||
body_filtered = {
|
body_filtered = {
|
||||||
include_item: body[include_item]
|
include_item: body[include_item]
|
||||||
@ -316,7 +337,6 @@ def process_model_update(
|
|||||||
if include_item in body
|
if include_item in body
|
||||||
}
|
}
|
||||||
|
|
||||||
# process_model_identifier = f"{process_group_id}/{process_model_id}"
|
|
||||||
process_model = get_process_model(process_model_identifier)
|
process_model = get_process_model(process_model_identifier)
|
||||||
ProcessModelService.update_process_model(process_model, body_filtered)
|
ProcessModelService.update_process_model(process_model, body_filtered)
|
||||||
return ProcessModelInfoSchema().dump(process_model)
|
return ProcessModelInfoSchema().dump(process_model)
|
||||||
@ -325,10 +345,7 @@ def process_model_update(
|
|||||||
def process_model_show(modified_process_model_identifier: str) -> Any:
|
def process_model_show(modified_process_model_identifier: str) -> Any:
|
||||||
"""Process_model_show."""
|
"""Process_model_show."""
|
||||||
process_model_identifier = modified_process_model_identifier.replace(":", "/")
|
process_model_identifier = modified_process_model_identifier.replace(":", "/")
|
||||||
# process_model_identifier = f"{process_group_id}/{process_model_id}"
|
|
||||||
process_model = get_process_model(process_model_identifier)
|
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))
|
files = sorted(SpecFileService.get_files(process_model))
|
||||||
process_model.files = files
|
process_model.files = files
|
||||||
for file in process_model.files:
|
for file in process_model.files:
|
||||||
@ -420,7 +437,6 @@ def process_model_file_update(
|
|||||||
) -> flask.wrappers.Response:
|
) -> flask.wrappers.Response:
|
||||||
"""Process_model_file_update."""
|
"""Process_model_file_update."""
|
||||||
process_model_identifier = modified_process_model_id.replace(":", "/")
|
process_model_identifier = modified_process_model_id.replace(":", "/")
|
||||||
# process_model_identifier = f"{process_group_id}/{process_model_id}"
|
|
||||||
process_model = get_process_model(process_model_identifier)
|
process_model = get_process_model(process_model_identifier)
|
||||||
|
|
||||||
request_file = get_file_from_request()
|
request_file = get_file_from_request()
|
||||||
@ -642,6 +658,7 @@ def message_instance_list(
|
|||||||
.add_columns(
|
.add_columns(
|
||||||
MessageModel.identifier.label("message_identifier"),
|
MessageModel.identifier.label("message_identifier"),
|
||||||
ProcessInstanceModel.process_model_identifier,
|
ProcessInstanceModel.process_model_identifier,
|
||||||
|
ProcessInstanceModel.process_model_display_name,
|
||||||
)
|
)
|
||||||
.paginate(page=page, per_page=per_page, error_out=False)
|
.paginate(page=page, per_page=per_page, error_out=False)
|
||||||
)
|
)
|
||||||
@ -776,10 +793,11 @@ def process_instance_list(
|
|||||||
with_tasks_completed_by_my_group: Optional[bool] = None,
|
with_tasks_completed_by_my_group: Optional[bool] = None,
|
||||||
user_filter: Optional[bool] = False,
|
user_filter: Optional[bool] = False,
|
||||||
report_identifier: Optional[str] = None,
|
report_identifier: Optional[str] = None,
|
||||||
|
report_id: Optional[int] = None,
|
||||||
) -> flask.wrappers.Response:
|
) -> flask.wrappers.Response:
|
||||||
"""Process_instance_list."""
|
"""Process_instance_list."""
|
||||||
process_instance_report = ProcessInstanceReportService.report_with_identifier(
|
process_instance_report = ProcessInstanceReportService.report_with_identifier(
|
||||||
g.user, report_identifier
|
g.user, report_id, report_identifier
|
||||||
)
|
)
|
||||||
|
|
||||||
if user_filter:
|
if user_filter:
|
||||||
@ -810,7 +828,6 @@ def process_instance_list(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
# process_model_identifier = un_modify_modified_process_model_id(modified_process_model_identifier)
|
|
||||||
process_instance_query = ProcessInstanceModel.query
|
process_instance_query = ProcessInstanceModel.query
|
||||||
# Always join that hot user table for good performance at serialization time.
|
# Always join that hot user table for good performance at serialization time.
|
||||||
process_instance_query = process_instance_query.options(
|
process_instance_query = process_instance_query.options(
|
||||||
@ -928,25 +945,78 @@ def process_instance_list(
|
|||||||
UserGroupAssignmentModel.user_id == g.user.id
|
UserGroupAssignmentModel.user_id == g.user.id
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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_instances = (
|
||||||
process_instance_query.group_by(ProcessInstanceModel.id)
|
process_instance_query.group_by(ProcessInstanceModel.id)
|
||||||
.order_by(
|
.add_columns(ProcessInstanceModel.id)
|
||||||
ProcessInstanceModel.start_in_seconds.desc(), ProcessInstanceModel.id.desc() # type: ignore
|
.order_by(*order_by_query_array)
|
||||||
)
|
|
||||||
.paginate(page=page, per_page=per_page, error_out=False)
|
.paginate(page=page, per_page=per_page, error_out=False)
|
||||||
)
|
)
|
||||||
|
|
||||||
results = list(
|
results = ProcessInstanceReportService.add_metadata_columns_to_process_instance(
|
||||||
map(
|
process_instances.items, process_instance_report.report_metadata["columns"]
|
||||||
ProcessInstanceService.serialize_flat_with_task_data,
|
|
||||||
process_instances.items,
|
|
||||||
)
|
)
|
||||||
)
|
|
||||||
report_metadata = process_instance_report.report_metadata
|
|
||||||
|
|
||||||
response_json = {
|
response_json = {
|
||||||
"report_identifier": process_instance_report.identifier,
|
"report": process_instance_report,
|
||||||
"report_metadata": report_metadata,
|
|
||||||
"results": results,
|
"results": results,
|
||||||
"filters": report_filter.to_dict(),
|
"filters": report_filter.to_dict(),
|
||||||
"pagination": {
|
"pagination": {
|
||||||
@ -959,6 +1029,22 @@ def process_instance_list(
|
|||||||
return make_response(jsonify(response_json), 200)
|
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(
|
def process_instance_show(
|
||||||
modified_process_model_identifier: str, process_instance_id: int
|
modified_process_model_identifier: str, process_instance_id: int
|
||||||
) -> flask.wrappers.Response:
|
) -> flask.wrappers.Response:
|
||||||
@ -1015,22 +1101,22 @@ def process_instance_report_list(
|
|||||||
|
|
||||||
def process_instance_report_create(body: Dict[str, Any]) -> flask.wrappers.Response:
|
def process_instance_report_create(body: Dict[str, Any]) -> flask.wrappers.Response:
|
||||||
"""Process_instance_report_create."""
|
"""Process_instance_report_create."""
|
||||||
ProcessInstanceReportModel.create_report(
|
process_instance_report = ProcessInstanceReportModel.create_report(
|
||||||
identifier=body["identifier"],
|
identifier=body["identifier"],
|
||||||
user=g.user,
|
user=g.user,
|
||||||
report_metadata=body["report_metadata"],
|
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(
|
def process_instance_report_update(
|
||||||
report_identifier: str,
|
report_id: int,
|
||||||
body: Dict[str, Any],
|
body: Dict[str, Any],
|
||||||
) -> flask.wrappers.Response:
|
) -> flask.wrappers.Response:
|
||||||
"""Process_instance_report_create."""
|
"""Process_instance_report_create."""
|
||||||
process_instance_report = ProcessInstanceReportModel.query.filter_by(
|
process_instance_report = ProcessInstanceReportModel.query.filter_by(
|
||||||
identifier=report_identifier,
|
id=report_id,
|
||||||
created_by_id=g.user.id,
|
created_by_id=g.user.id,
|
||||||
).first()
|
).first()
|
||||||
if process_instance_report is None:
|
if process_instance_report is None:
|
||||||
@ -1043,15 +1129,15 @@ def process_instance_report_update(
|
|||||||
process_instance_report.report_metadata = body["report_metadata"]
|
process_instance_report.report_metadata = body["report_metadata"]
|
||||||
db.session.commit()
|
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(
|
def process_instance_report_delete(
|
||||||
report_identifier: str,
|
report_id: int,
|
||||||
) -> flask.wrappers.Response:
|
) -> flask.wrappers.Response:
|
||||||
"""Process_instance_report_create."""
|
"""Process_instance_report_create."""
|
||||||
process_instance_report = ProcessInstanceReportModel.query.filter_by(
|
process_instance_report = ProcessInstanceReportModel.query.filter_by(
|
||||||
identifier=report_identifier,
|
id=report_id,
|
||||||
created_by_id=g.user.id,
|
created_by_id=g.user.id,
|
||||||
).first()
|
).first()
|
||||||
if process_instance_report is None:
|
if process_instance_report is None:
|
||||||
@ -1070,8 +1156,6 @@ def process_instance_report_delete(
|
|||||||
def service_tasks_show() -> flask.wrappers.Response:
|
def service_tasks_show() -> flask.wrappers.Response:
|
||||||
"""Service_tasks_show."""
|
"""Service_tasks_show."""
|
||||||
available_connectors = ServiceTaskService.available_connectors()
|
available_connectors = ServiceTaskService.available_connectors()
|
||||||
print(available_connectors)
|
|
||||||
|
|
||||||
return Response(
|
return Response(
|
||||||
json.dumps(available_connectors), status=200, mimetype="application/json"
|
json.dumps(available_connectors), status=200, mimetype="application/json"
|
||||||
)
|
)
|
||||||
@ -1105,19 +1189,17 @@ def authentication_callback(
|
|||||||
|
|
||||||
|
|
||||||
def process_instance_report_show(
|
def process_instance_report_show(
|
||||||
report_identifier: str,
|
report_id: int,
|
||||||
page: int = 1,
|
page: int = 1,
|
||||||
per_page: int = 100,
|
per_page: int = 100,
|
||||||
) -> flask.wrappers.Response:
|
) -> flask.wrappers.Response:
|
||||||
"""Process_instance_list."""
|
"""Process_instance_report_show."""
|
||||||
process_instances = ProcessInstanceModel.query.order_by( # .filter_by(process_model_identifier=process_model.id)
|
process_instances = ProcessInstanceModel.query.order_by(
|
||||||
ProcessInstanceModel.start_in_seconds.desc(), ProcessInstanceModel.id.desc() # type: ignore
|
ProcessInstanceModel.start_in_seconds.desc(), ProcessInstanceModel.id.desc() # type: ignore
|
||||||
).paginate(
|
).paginate(page=page, per_page=per_page, error_out=False)
|
||||||
page=page, per_page=per_page, error_out=False
|
|
||||||
)
|
|
||||||
|
|
||||||
process_instance_report = ProcessInstanceReportModel.query.filter_by(
|
process_instance_report = ProcessInstanceReportModel.query.filter_by(
|
||||||
identifier=report_identifier,
|
id=report_id,
|
||||||
created_by_id=g.user.id,
|
created_by_id=g.user.id,
|
||||||
).first()
|
).first()
|
||||||
if process_instance_report is None:
|
if process_instance_report is None:
|
||||||
@ -1394,9 +1476,6 @@ def task_show(process_instance_id: int, task_id: str) -> flask.wrappers.Response
|
|||||||
task.form_ui_schema = ui_form_contents
|
task.form_ui_schema = ui_form_contents
|
||||||
|
|
||||||
if task.properties and task.data and "instructionsForEndUser" in task.properties:
|
if task.properties and task.data and "instructionsForEndUser" in task.properties:
|
||||||
print(
|
|
||||||
f"task.properties['instructionsForEndUser']: {task.properties['instructionsForEndUser']}"
|
|
||||||
)
|
|
||||||
if task.properties["instructionsForEndUser"]:
|
if task.properties["instructionsForEndUser"]:
|
||||||
task.properties["instructionsForEndUser"] = render_jinja_template(
|
task.properties["instructionsForEndUser"] = render_jinja_template(
|
||||||
task.properties["instructionsForEndUser"], task.data
|
task.properties["instructionsForEndUser"], task.data
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
"""Get_env."""
|
"""Save process instance metadata."""
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from flask_bpmn.models.db import db
|
from flask_bpmn.models.db import db
|
||||||
|
@ -235,8 +235,9 @@ class AuthenticationService:
|
|||||||
refresh_token_object: RefreshTokenModel = RefreshTokenModel.query.filter(
|
refresh_token_object: RefreshTokenModel = RefreshTokenModel.query.filter(
|
||||||
RefreshTokenModel.user_id == user_id
|
RefreshTokenModel.user_id == user_id
|
||||||
).first()
|
).first()
|
||||||
assert refresh_token_object # noqa: S101
|
if refresh_token_object:
|
||||||
return refresh_token_object.token
|
return refresh_token_object.token
|
||||||
|
return None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_auth_token_from_refresh_token(cls, refresh_token: str) -> dict:
|
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.message_instance import MessageModel
|
||||||
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
|
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
|
||||||
from spiffworkflow_backend.models.process_instance import ProcessInstanceStatus
|
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.process_model import ProcessModelInfo
|
||||||
from spiffworkflow_backend.models.script_attributes_context import (
|
from spiffworkflow_backend.models.script_attributes_context import (
|
||||||
ScriptAttributesContext,
|
ScriptAttributesContext,
|
||||||
@ -178,7 +181,12 @@ class CustomBpmnScriptEngine(PythonScriptEngine): # type: ignore
|
|||||||
)
|
)
|
||||||
return Script.generate_augmented_list(script_attributes_context)
|
return Script.generate_augmented_list(script_attributes_context)
|
||||||
|
|
||||||
def evaluate(self, task: SpiffTask, expression: str, external_methods=None) -> Any:
|
def evaluate(
|
||||||
|
self,
|
||||||
|
task: SpiffTask,
|
||||||
|
expression: str,
|
||||||
|
external_methods: Optional[dict[str, Any]] = None,
|
||||||
|
) -> Any:
|
||||||
"""Evaluate."""
|
"""Evaluate."""
|
||||||
return self._evaluate(expression, task.data, task, external_methods)
|
return self._evaluate(expression, task.data, task, external_methods)
|
||||||
|
|
||||||
@ -571,6 +579,36 @@ class ProcessInstanceProcessor:
|
|||||||
db.session.add(details_model)
|
db.session.add(details_model)
|
||||||
db.session.commit()
|
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:
|
def save(self) -> None:
|
||||||
"""Saves the current state of this processor to the database."""
|
"""Saves the current state of this processor to the database."""
|
||||||
self.process_instance_model.bpmn_json = self.serialize()
|
self.process_instance_model.bpmn_json = self.serialize()
|
||||||
@ -597,6 +635,15 @@ class ProcessInstanceProcessor:
|
|||||||
process_instance_id=self.process_instance_model.id
|
process_instance_id=self.process_instance_model.id
|
||||||
).all()
|
).all()
|
||||||
ready_or_waiting_tasks = self.get_all_ready_or_waiting_tasks()
|
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:
|
for ready_or_waiting_task in ready_or_waiting_tasks:
|
||||||
# filter out non-usertasks
|
# filter out non-usertasks
|
||||||
task_spec = ready_or_waiting_task.task_spec
|
task_spec = ready_or_waiting_task.task_spec
|
||||||
@ -615,13 +662,6 @@ class ProcessInstanceProcessor:
|
|||||||
if "formUiSchemaFilename" in properties:
|
if "formUiSchemaFilename" in properties:
|
||||||
ui_form_file_name = properties["formUiSchemaFilename"]
|
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
|
active_task = None
|
||||||
for at in active_tasks:
|
for at in active_tasks:
|
||||||
if at.task_id == str(ready_or_waiting_task.id):
|
if at.task_id == str(ready_or_waiting_task.id):
|
||||||
@ -1146,8 +1186,8 @@ class ProcessInstanceProcessor:
|
|||||||
def get_current_data(self) -> dict[str, Any]:
|
def get_current_data(self) -> dict[str, Any]:
|
||||||
"""Get the current data for the process.
|
"""Get the current data for the process.
|
||||||
|
|
||||||
Return either most recent task data or the process data
|
Return either the most recent task data or--if the process instance is complete--
|
||||||
if the process instance is complete
|
the process data.
|
||||||
"""
|
"""
|
||||||
if self.process_instance_model.status == "complete":
|
if self.process_instance_model.status == "complete":
|
||||||
return self.get_data()
|
return self.get_data()
|
||||||
|
@ -2,6 +2,9 @@
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
import sqlalchemy
|
||||||
|
from flask_bpmn.models.db import db
|
||||||
|
|
||||||
from spiffworkflow_backend.models.process_instance_report import (
|
from spiffworkflow_backend.models.process_instance_report import (
|
||||||
ProcessInstanceReportModel,
|
ProcessInstanceReportModel,
|
||||||
)
|
)
|
||||||
@ -57,12 +60,21 @@ class ProcessInstanceReportService:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def report_with_identifier(
|
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:
|
) -> ProcessInstanceReportModel:
|
||||||
"""Report_with_filter."""
|
"""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:
|
if report_identifier is None:
|
||||||
report_identifier = "default"
|
report_identifier = "default"
|
||||||
|
|
||||||
process_instance_report = ProcessInstanceReportModel.query.filter_by(
|
process_instance_report = ProcessInstanceReportModel.query.filter_by(
|
||||||
identifier=report_identifier, created_by_id=user.id
|
identifier=report_identifier, created_by_id=user.id
|
||||||
).first()
|
).first()
|
||||||
@ -73,17 +85,9 @@ class ProcessInstanceReportService:
|
|||||||
# TODO replace with system reports that are loaded on launch (or similar)
|
# TODO replace with system reports that are loaded on launch (or similar)
|
||||||
temp_system_metadata_map = {
|
temp_system_metadata_map = {
|
||||||
"default": {
|
"default": {
|
||||||
"columns": [
|
"columns": cls.builtin_column_options(),
|
||||||
{"Header": "id", "accessor": "id"},
|
"filter_by": [],
|
||||||
{
|
"order_by": ["-start_in_seconds", "-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"},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
"system_report_instances_initiated_by_me": {
|
"system_report_instances_initiated_by_me": {
|
||||||
"columns": [
|
"columns": [
|
||||||
@ -97,48 +101,31 @@ class ProcessInstanceReportService:
|
|||||||
{"Header": "status", "accessor": "status"},
|
{"Header": "status", "accessor": "status"},
|
||||||
],
|
],
|
||||||
"filter_by": [{"field_name": "initiated_by_me", "field_value": True}],
|
"filter_by": [{"field_name": "initiated_by_me", "field_value": True}],
|
||||||
|
"order_by": ["-start_in_seconds", "-id"],
|
||||||
},
|
},
|
||||||
"system_report_instances_with_tasks_completed_by_me": {
|
"system_report_instances_with_tasks_completed_by_me": {
|
||||||
"columns": [
|
"columns": cls.builtin_column_options(),
|
||||||
{"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"},
|
|
||||||
],
|
|
||||||
"filter_by": [
|
"filter_by": [
|
||||||
{"field_name": "with_tasks_completed_by_me", "field_value": True}
|
{"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": {
|
"system_report_instances_with_tasks_completed_by_my_groups": {
|
||||||
"columns": [
|
"columns": cls.builtin_column_options(),
|
||||||
{"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"},
|
|
||||||
],
|
|
||||||
"filter_by": [
|
"filter_by": [
|
||||||
{
|
{
|
||||||
"field_name": "with_tasks_completed_by_my_group",
|
"field_name": "with_tasks_completed_by_my_group",
|
||||||
"field_value": True,
|
"field_value": True,
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"order_by": ["-start_in_seconds", "-id"],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
process_instance_report = ProcessInstanceReportModel(
|
process_instance_report = ProcessInstanceReportModel(
|
||||||
identifier=report_identifier,
|
identifier=report_identifier,
|
||||||
created_by_id=user.id,
|
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
|
return process_instance_report # type: ignore
|
||||||
@ -241,3 +228,43 @@ class ProcessInstanceReportService:
|
|||||||
)
|
)
|
||||||
|
|
||||||
return report_filter
|
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
|
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:scriptTask id="hot_script_task_OH_YEEEEEEEEEEEEEEEEEEEEAH" name="OHHHHHHHHHHYEEEESSSSSSSSSS">
|
||||||
<bpmn:incoming>Flow_0bazl8x</bpmn:incoming>
|
<bpmn:incoming>Flow_0bazl8x</bpmn:incoming>
|
||||||
<bpmn:outgoing>Flow_1mcaszp</bpmn:outgoing>
|
<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:scriptTask>
|
||||||
<bpmn:endEvent id="Event_1vch1y0">
|
<bpmn:endEvent id="Event_1vch1y0">
|
||||||
<bpmn:incoming>Flow_1mcaszp</bpmn:incoming>
|
<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>
|
@ -20,6 +20,9 @@ from spiffworkflow_backend.models.group import GroupModel
|
|||||||
from spiffworkflow_backend.models.process_group import ProcessGroup
|
from spiffworkflow_backend.models.process_group import ProcessGroup
|
||||||
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
|
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
|
||||||
from spiffworkflow_backend.models.process_instance import ProcessInstanceStatus
|
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 (
|
from spiffworkflow_backend.models.process_instance_report import (
|
||||||
ProcessInstanceReportModel,
|
ProcessInstanceReportModel,
|
||||||
)
|
)
|
||||||
@ -330,6 +333,9 @@ class TestProcessApi(BaseTest):
|
|||||||
process_model.display_name = "Updated Display Name"
|
process_model.display_name = "Updated Display Name"
|
||||||
process_model.primary_file_name = "superduper.bpmn"
|
process_model.primary_file_name = "superduper.bpmn"
|
||||||
process_model.primary_process_id = "superduper"
|
process_model.primary_process_id = "superduper"
|
||||||
|
process_model.metadata_extraction_paths = [
|
||||||
|
{"key": "extraction1", "path": "path1"}
|
||||||
|
]
|
||||||
|
|
||||||
modified_process_model_identifier = process_model_identifier.replace("/", ":")
|
modified_process_model_identifier = process_model_identifier.replace("/", ":")
|
||||||
response = client.put(
|
response = client.put(
|
||||||
@ -343,6 +349,9 @@ class TestProcessApi(BaseTest):
|
|||||||
assert response.json["display_name"] == "Updated Display Name"
|
assert response.json["display_name"] == "Updated Display Name"
|
||||||
assert response.json["primary_file_name"] == "superduper.bpmn"
|
assert response.json["primary_file_name"] == "superduper.bpmn"
|
||||||
assert response.json["primary_process_id"] == "superduper"
|
assert response.json["primary_process_id"] == "superduper"
|
||||||
|
assert response.json["metadata_extraction_paths"] == [
|
||||||
|
{"key": "extraction1", "path": "path1"}
|
||||||
|
]
|
||||||
|
|
||||||
def test_process_model_list_all(
|
def test_process_model_list_all(
|
||||||
self,
|
self,
|
||||||
@ -1723,14 +1732,14 @@ class TestProcessApi(BaseTest):
|
|||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
ProcessInstanceReportModel.create_with_attributes(
|
report = ProcessInstanceReportModel.create_with_attributes(
|
||||||
identifier="sure",
|
identifier="sure",
|
||||||
report_metadata=report_metadata,
|
report_metadata=report_metadata,
|
||||||
user=with_super_admin_user,
|
user=with_super_admin_user,
|
||||||
)
|
)
|
||||||
|
|
||||||
response = client.get(
|
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),
|
headers=self.logged_in_headers(with_super_admin_user),
|
||||||
)
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
@ -1769,14 +1778,14 @@ class TestProcessApi(BaseTest):
|
|||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
ProcessInstanceReportModel.create_with_attributes(
|
report = ProcessInstanceReportModel.create_with_attributes(
|
||||||
identifier="sure",
|
identifier="sure",
|
||||||
report_metadata=report_metadata,
|
report_metadata=report_metadata,
|
||||||
user=with_super_admin_user,
|
user=with_super_admin_user,
|
||||||
)
|
)
|
||||||
|
|
||||||
response = client.get(
|
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),
|
headers=self.logged_in_headers(with_super_admin_user),
|
||||||
)
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
@ -1791,9 +1800,9 @@ class TestProcessApi(BaseTest):
|
|||||||
with_super_admin_user: UserModel,
|
with_super_admin_user: UserModel,
|
||||||
setup_process_instances_for_reports: list[ProcessInstanceModel],
|
setup_process_instances_for_reports: list[ProcessInstanceModel],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test_process_instance_report_show_with_default_list."""
|
"""Test_process_instance_report_show_with_bad_identifier."""
|
||||||
response = client.get(
|
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),
|
headers=self.logged_in_headers(with_super_admin_user),
|
||||||
)
|
)
|
||||||
assert response.status_code == 404
|
assert response.status_code == 404
|
||||||
@ -2544,3 +2553,191 @@ class TestProcessApi(BaseTest):
|
|||||||
# make sure the new subgroup does exist
|
# make sure the new subgroup does exist
|
||||||
new_process_group = ProcessModelService.get_process_group(new_sub_path)
|
new_process_group = ProcessModelService.get_process_group(new_sub_path)
|
||||||
assert new_process_group.id == new_sub_path
|
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,
|
with_db_and_bpmn_file_cleanup: None,
|
||||||
setup_process_instances_for_reports: list[ProcessInstanceModel],
|
setup_process_instances_for_reports: list[ProcessInstanceModel],
|
||||||
) -> None:
|
) -> 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
|
process_instances = setup_process_instances_for_reports
|
||||||
report_metadata = {
|
report_metadata = {
|
||||||
"filter_by": [
|
"filter_by": [
|
||||||
@ -61,7 +61,7 @@ def test_generate_report_with_order_by_and_one_field(
|
|||||||
with_db_and_bpmn_file_cleanup: None,
|
with_db_and_bpmn_file_cleanup: None,
|
||||||
setup_process_instances_for_reports: list[ProcessInstanceModel],
|
setup_process_instances_for_reports: list[ProcessInstanceModel],
|
||||||
) -> None:
|
) -> 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
|
process_instances = setup_process_instances_for_reports
|
||||||
report_metadata = {"order_by": ["test_score"]}
|
report_metadata = {"order_by": ["test_score"]}
|
||||||
results = do_report_with_metadata_and_instances(report_metadata, process_instances)
|
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,
|
with_db_and_bpmn_file_cleanup: None,
|
||||||
setup_process_instances_for_reports: list[ProcessInstanceModel],
|
setup_process_instances_for_reports: list[ProcessInstanceModel],
|
||||||
) -> None:
|
) -> 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
|
process_instances = setup_process_instances_for_reports
|
||||||
report_metadata = {"order_by": ["grade_level", "test_score"]}
|
report_metadata = {"order_by": ["grade_level", "test_score"]}
|
||||||
results = do_report_with_metadata_and_instances(report_metadata, process_instances)
|
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,
|
with_db_and_bpmn_file_cleanup: None,
|
||||||
setup_process_instances_for_reports: list[ProcessInstanceModel],
|
setup_process_instances_for_reports: list[ProcessInstanceModel],
|
||||||
) -> None:
|
) -> 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
|
process_instances = setup_process_instances_for_reports
|
||||||
report_metadata = {"order_by": ["grade_level", "-test_score"]}
|
report_metadata = {"order_by": ["grade_level", "-test_score"]}
|
||||||
results = do_report_with_metadata_and_instances(report_metadata, process_instances)
|
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,
|
with_db_and_bpmn_file_cleanup: None,
|
||||||
setup_process_instances_for_reports: list[ProcessInstanceModel],
|
setup_process_instances_for_reports: list[ProcessInstanceModel],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test_user_can_be_given_permission_to_administer_process_group."""
|
"""Test_generate_report_with_columns."""
|
||||||
process_instances = setup_process_instances_for_reports
|
process_instances = setup_process_instances_for_reports
|
||||||
report_metadata = {
|
report_metadata = {
|
||||||
"columns": [
|
"columns": [
|
||||||
|
@ -5,12 +5,16 @@ from flask_bpmn.models.db import db
|
|||||||
from tests.spiffworkflow_backend.helpers.base_test import BaseTest
|
from tests.spiffworkflow_backend.helpers.base_test import BaseTest
|
||||||
from tests.spiffworkflow_backend.helpers.test_data import load_test_spec
|
from tests.spiffworkflow_backend.helpers.test_data import load_test_spec
|
||||||
|
|
||||||
|
from spiffworkflow_backend.models.process_instance_metadata import (
|
||||||
|
ProcessInstanceMetadataModel,
|
||||||
|
)
|
||||||
from spiffworkflow_backend.models.process_model import ProcessModelInfo
|
from spiffworkflow_backend.models.process_model import ProcessModelInfo
|
||||||
from spiffworkflow_backend.models.spec_reference import SpecReferenceCache
|
from spiffworkflow_backend.models.spec_reference import SpecReferenceCache
|
||||||
from spiffworkflow_backend.models.user import UserModel
|
from spiffworkflow_backend.models.user import UserModel
|
||||||
from spiffworkflow_backend.services.process_instance_processor import (
|
from spiffworkflow_backend.services.process_instance_processor import (
|
||||||
ProcessInstanceProcessor,
|
ProcessInstanceProcessor,
|
||||||
)
|
)
|
||||||
|
from spiffworkflow_backend.services.process_model_service import ProcessModelService
|
||||||
|
|
||||||
|
|
||||||
class TestProcessModel(BaseTest):
|
class TestProcessModel(BaseTest):
|
||||||
@ -122,6 +126,53 @@ class TestProcessModel(BaseTest):
|
|||||||
processor.do_engine_steps(save=True)
|
processor.do_engine_steps(save=True)
|
||||||
assert process_instance.status == "complete"
|
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:
|
def create_test_process_model(self, id: str, display_name: str) -> ProcessModelInfo:
|
||||||
"""Create_test_process_model."""
|
"""Create_test_process_model."""
|
||||||
return ProcessModelInfo(
|
return ProcessModelInfo(
|
||||||
|
22
spiffworkflow-frontend/src/components/MiniComponents.tsx
Normal file
22
spiffworkflow-frontend/src/components/MiniComponents.tsx
Normal file
@ -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*"
|
labelText="Display Name*"
|
||||||
value={processGroup.display_name}
|
value={processGroup.display_name}
|
||||||
onChange={(event: any) => onDisplayNameChanged(event.target.value)}
|
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';
|
} from 'react-router-dom';
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { Filter } from '@carbon/icons-react';
|
import { Filter, Close, AddAlt } from '@carbon/icons-react';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
ButtonSet,
|
ButtonSet,
|
||||||
@ -21,6 +21,13 @@ import {
|
|||||||
TableHead,
|
TableHead,
|
||||||
TableRow,
|
TableRow,
|
||||||
TimePicker,
|
TimePicker,
|
||||||
|
Tag,
|
||||||
|
InlineNotification,
|
||||||
|
Stack,
|
||||||
|
Modal,
|
||||||
|
ComboBox,
|
||||||
|
TextInput,
|
||||||
|
FormLabel,
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
} from '@carbon/react';
|
} from '@carbon/react';
|
||||||
import { PROCESS_STATUSES, DATE_FORMAT, DATE_FORMAT_CARBON } from '../config';
|
import { PROCESS_STATUSES, DATE_FORMAT, DATE_FORMAT_CARBON } from '../config';
|
||||||
@ -49,9 +56,15 @@ import {
|
|||||||
ProcessModel,
|
ProcessModel,
|
||||||
ProcessInstanceReport,
|
ProcessInstanceReport,
|
||||||
ProcessInstance,
|
ProcessInstance,
|
||||||
|
ReportColumn,
|
||||||
|
ReportColumnForEditing,
|
||||||
|
ReportMetadata,
|
||||||
|
ReportFilter,
|
||||||
} from '../interfaces';
|
} from '../interfaces';
|
||||||
import ProcessModelSearch from './ProcessModelSearch';
|
import ProcessModelSearch from './ProcessModelSearch';
|
||||||
import ProcessInstanceReportSearch from './ProcessInstanceReportSearch';
|
import ProcessInstanceReportSearch from './ProcessInstanceReportSearch';
|
||||||
|
import ProcessInstanceListSaveAsReport from './ProcessInstanceListSaveAsReport';
|
||||||
|
import { FormatProcessModelDisplayName } from './MiniComponents';
|
||||||
|
|
||||||
const REFRESH_INTERVAL = 5;
|
const REFRESH_INTERVAL = 5;
|
||||||
const REFRESH_TIMEOUT = 600;
|
const REFRESH_TIMEOUT = 600;
|
||||||
@ -88,7 +101,7 @@ export default function ProcessInstanceListTable({
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const [processInstances, setProcessInstances] = useState([]);
|
const [processInstances, setProcessInstances] = useState([]);
|
||||||
const [reportMetadata, setReportMetadata] = useState({});
|
const [reportMetadata, setReportMetadata] = useState<ReportMetadata | null>();
|
||||||
const [pagination, setPagination] = useState<PaginationObject | null>(null);
|
const [pagination, setPagination] = useState<PaginationObject | null>(null);
|
||||||
const [processInstanceFilters, setProcessInstanceFilters] = useState({});
|
const [processInstanceFilters, setProcessInstanceFilters] = useState({});
|
||||||
|
|
||||||
@ -125,6 +138,17 @@ export default function ProcessInstanceListTable({
|
|||||||
const [processInstanceReportSelection, setProcessInstanceReportSelection] =
|
const [processInstanceReportSelection, setProcessInstanceReportSelection] =
|
||||||
useState<ProcessInstanceReport | null>(null);
|
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(() => {
|
const dateParametersToAlwaysFilterBy: dateParameters = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
start_from: [setStartFromDate, setStartFromTime],
|
start_from: [setStartFromDate, setStartFromTime],
|
||||||
@ -155,16 +179,12 @@ export default function ProcessInstanceListTable({
|
|||||||
function setProcessInstancesFromResult(result: any) {
|
function setProcessInstancesFromResult(result: any) {
|
||||||
const processInstancesFromApi = result.results;
|
const processInstancesFromApi = result.results;
|
||||||
setProcessInstances(processInstancesFromApi);
|
setProcessInstances(processInstancesFromApi);
|
||||||
setReportMetadata(result.report_metadata);
|
|
||||||
setPagination(result.pagination);
|
setPagination(result.pagination);
|
||||||
setProcessInstanceFilters(result.filters);
|
setProcessInstanceFilters(result.filters);
|
||||||
|
|
||||||
// TODO: need to iron out this interaction some more
|
setReportMetadata(result.report.report_metadata);
|
||||||
if (result.report_identifier !== 'default') {
|
if (result.report.id) {
|
||||||
setProcessInstanceReportSelection({
|
setProcessInstanceReportSelection(result.report);
|
||||||
id: result.report_identifier,
|
|
||||||
display_name: result.report_identifier,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function getProcessInstances() {
|
function getProcessInstances() {
|
||||||
@ -186,14 +206,10 @@ export default function ProcessInstanceListTable({
|
|||||||
queryParamString += `&user_filter=${userAppliedFilter}`;
|
queryParamString += `&user_filter=${userAppliedFilter}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
let reportIdentifierToUse: any = reportIdentifier;
|
if (searchParams.get('report_id')) {
|
||||||
|
queryParamString += `&report_id=${searchParams.get('report_id')}`;
|
||||||
if (!reportIdentifierToUse) {
|
} else if (reportIdentifier) {
|
||||||
reportIdentifierToUse = searchParams.get('report_identifier');
|
queryParamString += `&report_identifier=${reportIdentifier}`;
|
||||||
}
|
|
||||||
|
|
||||||
if (reportIdentifierToUse) {
|
|
||||||
queryParamString += `&report_identifier=${reportIdentifierToUse}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.keys(dateParametersToAlwaysFilterBy).forEach(
|
Object.keys(dateParametersToAlwaysFilterBy).forEach(
|
||||||
@ -349,6 +365,27 @@ export default function ProcessInstanceListTable({
|
|||||||
processModelAvailableItems,
|
processModelAvailableItems,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const processInstanceReportSaveTag = () => {
|
||||||
|
if (processInstanceReportJustSaved) {
|
||||||
|
let titleOperation = 'Updated';
|
||||||
|
if (processInstanceReportJustSaved === 'new') {
|
||||||
|
titleOperation = 'Created';
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<InlineNotification
|
||||||
|
title={`Perspective ${titleOperation}:`}
|
||||||
|
subtitle={`'${
|
||||||
|
processInstanceReportSelection
|
||||||
|
? processInstanceReportSelection.identifier
|
||||||
|
: ''
|
||||||
|
}'`}
|
||||||
|
kind="success"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
// does the comparison, but also returns false if either argument
|
// does the comparison, but also returns false if either argument
|
||||||
// is not truthy and therefore not comparable.
|
// is not truthy and therefore not comparable.
|
||||||
const isTrueComparison = (param1: any, operation: any, param2: any) => {
|
const isTrueComparison = (param1: any, operation: any, param2: any) => {
|
||||||
@ -366,16 +403,8 @@ export default function ProcessInstanceListTable({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const applyFilter = (event: any) => {
|
// TODO: after factoring this out page hangs when invalid date ranges and applying the filter
|
||||||
event.preventDefault();
|
const calculateStartAndEndSeconds = () => {
|
||||||
const { page, perPage } = getPageInfoFromSearchParams(
|
|
||||||
searchParams,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
paginationQueryParamPrefix
|
|
||||||
);
|
|
||||||
let queryParamString = `per_page=${perPage}&page=${page}&user_filter=true`;
|
|
||||||
|
|
||||||
const startFromSeconds = convertDateAndTimeStringsToSeconds(
|
const startFromSeconds = convertDateAndTimeStringsToSeconds(
|
||||||
startFromDate,
|
startFromDate,
|
||||||
startFromTime || '00:00:00'
|
startFromTime || '00:00:00'
|
||||||
@ -392,28 +421,59 @@ export default function ProcessInstanceListTable({
|
|||||||
endToDate,
|
endToDate,
|
||||||
endToTime || '00:00:00'
|
endToTime || '00:00:00'
|
||||||
);
|
);
|
||||||
|
let valid = true;
|
||||||
if (isTrueComparison(startFromSeconds, '>', startToSeconds)) {
|
if (isTrueComparison(startFromSeconds, '>', startToSeconds)) {
|
||||||
setErrorMessage({
|
setErrorMessage({
|
||||||
message: '"Start date from" cannot be after "start date to"',
|
message: '"Start date from" cannot be after "start date to"',
|
||||||
});
|
});
|
||||||
return;
|
valid = false;
|
||||||
}
|
}
|
||||||
if (isTrueComparison(endFromSeconds, '>', endToSeconds)) {
|
if (isTrueComparison(endFromSeconds, '>', endToSeconds)) {
|
||||||
setErrorMessage({
|
setErrorMessage({
|
||||||
message: '"End date from" cannot be after "end date to"',
|
message: '"End date from" cannot be after "end date to"',
|
||||||
});
|
});
|
||||||
return;
|
valid = false;
|
||||||
}
|
}
|
||||||
if (isTrueComparison(startFromSeconds, '>', endFromSeconds)) {
|
if (isTrueComparison(startFromSeconds, '>', endFromSeconds)) {
|
||||||
setErrorMessage({
|
setErrorMessage({
|
||||||
message: '"Start date from" cannot be after "end date from"',
|
message: '"Start date from" cannot be after "end date from"',
|
||||||
});
|
});
|
||||||
return;
|
valid = false;
|
||||||
}
|
}
|
||||||
if (isTrueComparison(startToSeconds, '>', endToSeconds)) {
|
if (isTrueComparison(startToSeconds, '>', endToSeconds)) {
|
||||||
setErrorMessage({
|
setErrorMessage({
|
||||||
message: '"Start date to" cannot be after "end date to"',
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -438,10 +498,11 @@ export default function ProcessInstanceListTable({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (processInstanceReportSelection) {
|
if (processInstanceReportSelection) {
|
||||||
queryParamString += `&report_identifier=${processInstanceReportSelection.id}`;
|
queryParamString += `&report_id=${processInstanceReportSelection.id}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
setErrorMessage(null);
|
setErrorMessage(null);
|
||||||
|
setProcessInstanceReportJustSaved(null);
|
||||||
navigate(`/admin/process-instances?${queryParamString}`);
|
navigate(`/admin/process-instances?${queryParamString}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -529,12 +590,360 @@ export default function ProcessInstanceListTable({
|
|||||||
setEndToTime('');
|
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 = () => {
|
const filterOptions = () => {
|
||||||
if (!showFilterOptions) {
|
if (!showFilterOptions) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (
|
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">
|
<Grid fullWidth className="with-bottom-margin">
|
||||||
<Column md={8}>
|
<Column md={8}>
|
||||||
<ProcessModelSearch
|
<ProcessModelSearch
|
||||||
@ -598,11 +1007,11 @@ export default function ProcessInstanceListTable({
|
|||||||
</Column>
|
</Column>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid fullWidth className="with-bottom-margin">
|
<Grid fullWidth className="with-bottom-margin">
|
||||||
<Column md={4}>
|
<Column sm={4} md={4} lg={8}>
|
||||||
<ButtonSet>
|
<ButtonSet>
|
||||||
<Button
|
<Button
|
||||||
kind=""
|
kind=""
|
||||||
className="button-white-background"
|
className="button-white-background narrow-button"
|
||||||
onClick={clearFilters}
|
onClick={clearFilters}
|
||||||
>
|
>
|
||||||
Clear
|
Clear
|
||||||
@ -611,11 +1020,15 @@ export default function ProcessInstanceListTable({
|
|||||||
kind="secondary"
|
kind="secondary"
|
||||||
onClick={applyFilter}
|
onClick={applyFilter}
|
||||||
data-qa="filter-button"
|
data-qa="filter-button"
|
||||||
|
className="narrow-button"
|
||||||
>
|
>
|
||||||
Filter
|
Filter
|
||||||
</Button>
|
</Button>
|
||||||
</ButtonSet>
|
</ButtonSet>
|
||||||
</Column>
|
</Column>
|
||||||
|
<Column sm={4} md={4} lg={8}>
|
||||||
|
{saveAsReportComponent()}
|
||||||
|
</Column>
|
||||||
</Grid>
|
</Grid>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@ -635,7 +1048,7 @@ export default function ProcessInstanceListTable({
|
|||||||
const getHeaderLabel = (header: string) => {
|
const getHeaderLabel = (header: string) => {
|
||||||
return headerLabels[header] ?? header;
|
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 <th>{getHeaderLabel((column as any).Header)}</th>;
|
||||||
return getHeaderLabel((column as any).Header);
|
return getHeaderLabel((column as any).Header);
|
||||||
});
|
});
|
||||||
@ -665,22 +1078,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) => {
|
const formatSecondsForDisplay = (_row: any, seconds: any) => {
|
||||||
return convertSecondsToFormattedDateTime(seconds) || '-';
|
return convertSecondsToFormattedDateTime(seconds) || '-';
|
||||||
};
|
};
|
||||||
@ -688,15 +1085,16 @@ export default function ProcessInstanceListTable({
|
|||||||
return value;
|
return value;
|
||||||
};
|
};
|
||||||
|
|
||||||
const columnFormatters: Record<string, any> = {
|
const reportColumnFormatters: Record<string, any> = {
|
||||||
id: formatProcessInstanceId,
|
id: formatProcessInstanceId,
|
||||||
process_model_identifier: formatProcessModelIdentifier,
|
process_model_identifier: formatProcessModelIdentifier,
|
||||||
process_model_display_name: formatProcessModelDisplayName,
|
process_model_display_name: FormatProcessModelDisplayName,
|
||||||
start_in_seconds: formatSecondsForDisplay,
|
start_in_seconds: formatSecondsForDisplay,
|
||||||
end_in_seconds: formatSecondsForDisplay,
|
end_in_seconds: formatSecondsForDisplay,
|
||||||
};
|
};
|
||||||
const formattedColumn = (row: any, column: any) => {
|
const formattedColumn = (row: any, column: any) => {
|
||||||
const formatter = columnFormatters[column.accessor] ?? defaultFormatter;
|
const formatter =
|
||||||
|
reportColumnFormatters[column.accessor] ?? defaultFormatter;
|
||||||
const value = row[column.accessor];
|
const value = row[column.accessor];
|
||||||
if (column.accessor === 'status') {
|
if (column.accessor === 'status') {
|
||||||
return (
|
return (
|
||||||
@ -709,7 +1107,7 @@ export default function ProcessInstanceListTable({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const rows = processInstances.map((row: any) => {
|
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 formattedColumn(row, column);
|
||||||
});
|
});
|
||||||
return <tr key={row.id}>{currentRow}</tr>;
|
return <tr key={row.id}>{currentRow}</tr>;
|
||||||
@ -736,29 +1134,26 @@ export default function ProcessInstanceListTable({
|
|||||||
|
|
||||||
const toggleShowFilterOptions = () => {
|
const toggleShowFilterOptions = () => {
|
||||||
setShowFilterOptions(!showFilterOptions);
|
setShowFilterOptions(!showFilterOptions);
|
||||||
};
|
HttpService.makeCallToBackend({
|
||||||
|
path: `/process-instances/reports/columns`,
|
||||||
const processInstanceReportDidChange = (selection: any) => {
|
successCallback: setAvailableReportColumns,
|
||||||
clearFilters();
|
});
|
||||||
|
|
||||||
const selectedReport = selection.selectedItem;
|
|
||||||
setProcessInstanceReportSelection(selectedReport);
|
|
||||||
|
|
||||||
const queryParamString = selectedReport
|
|
||||||
? `&report_identifier=${selectedReport.id}`
|
|
||||||
: '';
|
|
||||||
|
|
||||||
setErrorMessage(null);
|
|
||||||
navigate(`/admin/process-instances?${queryParamString}`);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const reportSearchComponent = () => {
|
const reportSearchComponent = () => {
|
||||||
if (showReports) {
|
if (showReports) {
|
||||||
return (
|
const columns = [
|
||||||
|
<Column sm={2} md={4} lg={7}>
|
||||||
<ProcessInstanceReportSearch
|
<ProcessInstanceReportSearch
|
||||||
onChange={processInstanceReportDidChange}
|
onChange={processInstanceReportDidChange}
|
||||||
selectedItem={processInstanceReportSelection}
|
selectedItem={processInstanceReportSelection}
|
||||||
/>
|
/>
|
||||||
|
</Column>,
|
||||||
|
];
|
||||||
|
return (
|
||||||
|
<Grid className="with-tiny-bottom-margin" fullWidth>
|
||||||
|
{columns}
|
||||||
|
</Grid>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@ -806,6 +1201,8 @@ export default function ProcessInstanceListTable({
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
{reportColumnForm()}
|
||||||
|
{processInstanceReportSaveTag()}
|
||||||
{filterComponent()}
|
{filterComponent()}
|
||||||
{reportSearchComponent()}
|
{reportSearchComponent()}
|
||||||
<PaginationForTable
|
<PaginationForTable
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
import { useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
ComboBox,
|
ComboBox,
|
||||||
|
Stack,
|
||||||
|
FormLabel,
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
} from '@carbon/react';
|
} from '@carbon/react';
|
||||||
|
import { useSearchParams } from 'react-router-dom';
|
||||||
import { truncateString } from '../helpers';
|
import { truncateString } from '../helpers';
|
||||||
import { ProcessInstanceReport } from '../interfaces';
|
import { ProcessInstanceReport } from '../interfaces';
|
||||||
import HttpService from '../services/HttpService';
|
import HttpService from '../services/HttpService';
|
||||||
@ -22,34 +25,44 @@ export default function ProcessInstanceReportSearch({
|
|||||||
ProcessInstanceReport[] | null
|
ProcessInstanceReport[] | null
|
||||||
>(null);
|
>(null);
|
||||||
|
|
||||||
function setProcessInstanceReportsFromResult(result: any) {
|
const [searchParams] = useSearchParams();
|
||||||
const processInstanceReportsFromApi = result.map((item: any) => {
|
const reportId = searchParams.get('report_id');
|
||||||
return { id: item.identifier, display_name: item.identifier };
|
|
||||||
});
|
useEffect(() => {
|
||||||
setProcessInstanceReports(processInstanceReportsFromApi);
|
function setProcessInstanceReportsFromResult(
|
||||||
|
result: ProcessInstanceReport[]
|
||||||
|
) {
|
||||||
|
setProcessInstanceReports(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (processInstanceReports === null) {
|
|
||||||
setProcessInstanceReports([]);
|
|
||||||
HttpService.makeCallToBackend({
|
HttpService.makeCallToBackend({
|
||||||
path: `/process-instances/reports`,
|
path: `/process-instances/reports`,
|
||||||
successCallback: setProcessInstanceReportsFromResult,
|
successCallback: setProcessInstanceReportsFromResult,
|
||||||
});
|
});
|
||||||
}
|
}, [reportId]);
|
||||||
|
|
||||||
|
const reportSelectionString = (
|
||||||
|
processInstanceReport: ProcessInstanceReport
|
||||||
|
) => {
|
||||||
|
return `${truncateString(processInstanceReport.identifier, 20)} (Id: ${
|
||||||
|
processInstanceReport.id
|
||||||
|
})`;
|
||||||
|
};
|
||||||
|
|
||||||
const shouldFilterProcessInstanceReport = (options: any) => {
|
const shouldFilterProcessInstanceReport = (options: any) => {
|
||||||
const processInstanceReport: ProcessInstanceReport = options.item;
|
const processInstanceReport: ProcessInstanceReport = options.item;
|
||||||
const { inputValue } = options;
|
const { inputValue } = options;
|
||||||
return `${processInstanceReport.id} (${processInstanceReport.display_name})`.includes(
|
return reportSelectionString(processInstanceReport).includes(inputValue);
|
||||||
inputValue
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const reportsAvailable = () => {
|
const reportsAvailable = () => {
|
||||||
return processInstanceReports && processInstanceReports.length > 0;
|
return processInstanceReports && processInstanceReports.length > 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
return reportsAvailable() ? (
|
if (reportsAvailable()) {
|
||||||
|
return (
|
||||||
|
<Stack orientation="horizontal" gap={2}>
|
||||||
|
<FormLabel className="with-top-margin">{titleText}</FormLabel>
|
||||||
<ComboBox
|
<ComboBox
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
id="process-instance-report-select"
|
id="process-instance-report-select"
|
||||||
@ -57,17 +70,16 @@ export default function ProcessInstanceReportSearch({
|
|||||||
items={processInstanceReports}
|
items={processInstanceReports}
|
||||||
itemToString={(processInstanceReport: ProcessInstanceReport) => {
|
itemToString={(processInstanceReport: ProcessInstanceReport) => {
|
||||||
if (processInstanceReport) {
|
if (processInstanceReport) {
|
||||||
return `${processInstanceReport.id} (${truncateString(
|
return reportSelectionString(processInstanceReport);
|
||||||
processInstanceReport.display_name,
|
|
||||||
20
|
|
||||||
)})`;
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}}
|
}}
|
||||||
shouldFilterItem={shouldFilterProcessInstanceReport}
|
shouldFilterItem={shouldFilterProcessInstanceReport}
|
||||||
placeholder="Choose a process instance perspective"
|
placeholder="Choose a process instance perspective"
|
||||||
titleText={titleText}
|
|
||||||
selectedItem={selectedItem}
|
selectedItem={selectedItem}
|
||||||
/>
|
/>
|
||||||
) : null;
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,20 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
ButtonSet,
|
||||||
|
Form,
|
||||||
|
Stack,
|
||||||
|
TextInput,
|
||||||
|
Grid,
|
||||||
|
Column,
|
||||||
|
// @ts-ignore
|
||||||
|
} from '@carbon/react';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { Button, ButtonSet, Form, Stack, TextInput } from '@carbon/react';
|
import { AddAlt, TrashCan } from '@carbon/icons-react';
|
||||||
import { modifyProcessIdentifierForPathParam, slugifyString } from '../helpers';
|
import { modifyProcessIdentifierForPathParam, slugifyString } from '../helpers';
|
||||||
import HttpService from '../services/HttpService';
|
import HttpService from '../services/HttpService';
|
||||||
import { ProcessModel } from '../interfaces';
|
import { MetadataExtractionPath, ProcessModel } from '../interfaces';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
mode: string;
|
mode: string;
|
||||||
@ -23,6 +33,7 @@ export default function ProcessModelForm({
|
|||||||
const [idHasBeenUpdatedByUser, setIdHasBeenUpdatedByUser] =
|
const [idHasBeenUpdatedByUser, setIdHasBeenUpdatedByUser] =
|
||||||
useState<boolean>(false);
|
useState<boolean>(false);
|
||||||
const [displayNameInvalid, setDisplayNameInvalid] = useState<boolean>(false);
|
const [displayNameInvalid, setDisplayNameInvalid] = useState<boolean>(false);
|
||||||
|
useState<boolean>(false);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const navigateToProcessModel = (result: ProcessModel) => {
|
const navigateToProcessModel = (result: ProcessModel) => {
|
||||||
@ -64,6 +75,7 @@ export default function ProcessModelForm({
|
|||||||
const postBody = {
|
const postBody = {
|
||||||
display_name: processModel.display_name,
|
display_name: processModel.display_name,
|
||||||
description: processModel.description,
|
description: processModel.description,
|
||||||
|
metadata_extraction_paths: processModel.metadata_extraction_paths,
|
||||||
};
|
};
|
||||||
if (mode === 'new') {
|
if (mode === 'new') {
|
||||||
Object.assign(postBody, {
|
Object.assign(postBody, {
|
||||||
@ -87,6 +99,80 @@ export default function ProcessModelForm({
|
|||||||
setProcessModel(processModelToCopy);
|
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) => {
|
const onDisplayNameChanged = (newDisplayName: any) => {
|
||||||
setDisplayNameInvalid(false);
|
setDisplayNameInvalid(false);
|
||||||
const updateDict = { display_name: newDisplayName };
|
const updateDict = { display_name: newDisplayName };
|
||||||
@ -145,6 +231,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;
|
return textInputs;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ export const PROCESS_STATUSES = [
|
|||||||
'complete',
|
'complete',
|
||||||
'error',
|
'error',
|
||||||
'suspended',
|
'suspended',
|
||||||
|
'terminated',
|
||||||
];
|
];
|
||||||
|
|
||||||
// with time: yyyy-MM-dd HH:mm:ss
|
// with time: yyyy-MM-dd HH:mm:ss
|
||||||
|
@ -69,6 +69,24 @@ h2 {
|
|||||||
color: black;
|
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 {
|
.cds--header__global .cds--btn--primary {
|
||||||
background-color: #161616
|
background-color: #161616
|
||||||
}
|
}
|
||||||
@ -151,10 +169,22 @@ h1.with-icons {
|
|||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.with-extra-top-margin {
|
||||||
|
margin-top: 1.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.with-tiny-top-margin {
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.with-large-bottom-margin {
|
.with-large-bottom-margin {
|
||||||
margin-bottom: 3em;
|
margin-bottom: 3em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.with-tiny-bottom-margin {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.diagram-viewer-canvas {
|
.diagram-viewer-canvas {
|
||||||
border:1px solid #000000;
|
border:1px solid #000000;
|
||||||
height:70vh;
|
height:70vh;
|
||||||
@ -297,3 +327,38 @@ td.actions-cell {
|
|||||||
text-align: right;
|
text-align: right;
|
||||||
padding-bottom: 10px;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -38,11 +38,58 @@ export interface ProcessFile {
|
|||||||
export interface ProcessInstance {
|
export interface ProcessInstance {
|
||||||
id: number;
|
id: number;
|
||||||
process_model_identifier: string;
|
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 {
|
export interface ProcessInstanceReport {
|
||||||
id: string;
|
id: number;
|
||||||
display_name: string;
|
identifier: string;
|
||||||
|
name: string;
|
||||||
|
report_metadata: ReportMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ProcessGroupLite {
|
export interface ProcessGroupLite {
|
||||||
@ -50,6 +97,11 @@ export interface ProcessGroupLite {
|
|||||||
display_name: string;
|
display_name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MetadataExtractionPath {
|
||||||
|
key: string;
|
||||||
|
path: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ProcessModel {
|
export interface ProcessModel {
|
||||||
id: string;
|
id: string;
|
||||||
description: string;
|
description: string;
|
||||||
@ -57,6 +109,7 @@ export interface ProcessModel {
|
|||||||
primary_file_name: string;
|
primary_file_name: string;
|
||||||
files: ProcessFile[];
|
files: ProcessFile[];
|
||||||
parent_groups?: ProcessGroupLite[];
|
parent_groups?: ProcessGroupLite[];
|
||||||
|
metadata_extraction_paths?: MetadataExtractionPath[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ProcessGroup {
|
export interface ProcessGroup {
|
||||||
|
@ -1,15 +1,19 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
// @ts-ignore
|
// @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 { Link, useParams, useSearchParams } from 'react-router-dom';
|
||||||
import PaginationForTable from '../components/PaginationForTable';
|
import PaginationForTable from '../components/PaginationForTable';
|
||||||
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
|
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
|
||||||
import {
|
import {
|
||||||
convertSecondsToFormattedDateString,
|
convertSecondsToFormattedDateTime,
|
||||||
getPageInfoFromSearchParams,
|
getPageInfoFromSearchParams,
|
||||||
modifyProcessIdentifierForPathParam,
|
modifyProcessIdentifierForPathParam,
|
||||||
} from '../helpers';
|
} from '../helpers';
|
||||||
import HttpService from '../services/HttpService';
|
import HttpService from '../services/HttpService';
|
||||||
|
import { FormatProcessModelDisplayName } from '../components/MiniComponents';
|
||||||
|
import { MessageInstance } from '../interfaces';
|
||||||
|
|
||||||
export default function MessageInstanceList() {
|
export default function MessageInstanceList() {
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
@ -17,6 +21,9 @@ export default function MessageInstanceList() {
|
|||||||
const [messageIntances, setMessageInstances] = useState([]);
|
const [messageIntances, setMessageInstances] = useState([]);
|
||||||
const [pagination, setPagination] = useState(null);
|
const [pagination, setPagination] = useState(null);
|
||||||
|
|
||||||
|
const [messageInstanceForModal, setMessageInstanceForModal] =
|
||||||
|
useState<MessageInstance | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const setMessageInstanceListFromResult = (result: any) => {
|
const setMessageInstanceListFromResult = (result: any) => {
|
||||||
setMessageInstances(result.results);
|
setMessageInstances(result.results);
|
||||||
@ -35,41 +42,89 @@ export default function MessageInstanceList() {
|
|||||||
});
|
});
|
||||||
}, [searchParams, params]);
|
}, [searchParams, params]);
|
||||||
|
|
||||||
const buildTable = () => {
|
const handleCorrelationDisplayClose = () => {
|
||||||
// return null;
|
setMessageInstanceForModal(null);
|
||||||
const rows = messageIntances.map((row) => {
|
};
|
||||||
const rowToUse = row as any;
|
|
||||||
|
const correlationsDisplayModal = () => {
|
||||||
|
if (messageInstanceForModal) {
|
||||||
|
let failureCausePre = null;
|
||||||
|
if (messageInstanceForModal.failure_cause) {
|
||||||
|
failureCausePre = (
|
||||||
|
<>
|
||||||
|
<p className="failure-string">
|
||||||
|
{messageInstanceForModal.failure_cause}
|
||||||
|
</p>
|
||||||
|
<br />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<tr key={rowToUse.id}>
|
<Modal
|
||||||
<td>{rowToUse.id}</td>
|
open={!!messageInstanceForModal}
|
||||||
<td>
|
passiveModal
|
||||||
<Link
|
onRequestClose={handleCorrelationDisplayClose}
|
||||||
data-qa="process-model-show-link"
|
modalHeading={`Message ${messageInstanceForModal.id} (${messageInstanceForModal.message_identifier}) ${messageInstanceForModal.message_type} data:`}
|
||||||
to={`/admin/process-models/${modifyProcessIdentifierForPathParam(
|
modalLabel="Details"
|
||||||
rowToUse.process_model_identifier
|
|
||||||
)}`}
|
|
||||||
>
|
>
|
||||||
{rowToUse.process_model_identifier}
|
{failureCausePre}
|
||||||
</Link>
|
<p>Correlations:</p>
|
||||||
</td>
|
<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>
|
<td>
|
||||||
<Link
|
<Link
|
||||||
data-qa="process-instance-show-link"
|
data-qa="process-instance-show-link"
|
||||||
to={`/admin/process-models/${modifyProcessIdentifierForPathParam(
|
to={`/admin/process-models/${modifyProcessIdentifierForPathParam(
|
||||||
rowToUse.process_model_identifier
|
row.process_model_identifier
|
||||||
)}/process-instances/${rowToUse.process_instance_id}`}
|
)}/process-instances/${row.process_instance_id}`}
|
||||||
>
|
>
|
||||||
{rowToUse.process_instance_id}
|
{row.process_instance_id}
|
||||||
</Link>
|
</Link>
|
||||||
</td>
|
</td>
|
||||||
<td>{rowToUse.message_identifier}</td>
|
<td>{row.message_identifier}</td>
|
||||||
<td>{rowToUse.message_type}</td>
|
<td>{row.message_type}</td>
|
||||||
<td>{rowToUse.failure_cause || '-'}</td>
|
|
||||||
<td>{rowToUse.status}</td>
|
|
||||||
<td>
|
<td>
|
||||||
{convertSecondsToFormattedDateString(
|
<Button
|
||||||
rowToUse.created_at_in_seconds
|
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>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
@ -78,12 +133,12 @@ export default function MessageInstanceList() {
|
|||||||
<Table striped bordered>
|
<Table striped bordered>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Instance Id</th>
|
<th>Id</th>
|
||||||
<th>Process Model</th>
|
<th>Process</th>
|
||||||
<th>Process Instance</th>
|
<th>Process Instance</th>
|
||||||
<th>Message Model</th>
|
<th>Name</th>
|
||||||
<th>Type</th>
|
<th>Type</th>
|
||||||
<th>Failure Cause</th>
|
<th>Details</th>
|
||||||
<th>Status</th>
|
<th>Status</th>
|
||||||
<th>Created At</th>
|
<th>Created At</th>
|
||||||
</tr>
|
</tr>
|
||||||
@ -121,6 +176,7 @@ export default function MessageInstanceList() {
|
|||||||
<>
|
<>
|
||||||
{breadcrumbElement}
|
{breadcrumbElement}
|
||||||
<h1>Messages</h1>
|
<h1>Messages</h1>
|
||||||
|
{correlationsDisplayModal()}
|
||||||
<PaginationForTable
|
<PaginationForTable
|
||||||
page={page}
|
page={page}
|
||||||
perPage={perPage}
|
perPage={perPage}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user