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
|
||||
nox==2022.8.7
|
||||
nox-poetry==1.0.1
|
||||
nox==2022.11.21
|
||||
nox-poetry==1.0.2
|
||||
poetry==1.2.2
|
||||
virtualenv==20.16.5
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
"""empty message
|
||||
|
||||
Revision ID: ff1c1628337c
|
||||
Revision ID: 40a2ed63cc5a
|
||||
Revises:
|
||||
Create Date: 2022-11-28 15:08:52.014254
|
||||
Create Date: 2022-11-29 16:59:02.980181
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
|
@ -10,7 +10,7 @@ import sqlalchemy as sa
|
|||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'ff1c1628337c'
|
||||
revision = '40a2ed63cc5a'
|
||||
down_revision = None
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
@ -249,6 +249,7 @@ def upgrade():
|
|||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('process_instance_id', 'key', name='process_instance_metadata_unique')
|
||||
)
|
||||
op.create_index(op.f('ix_process_instance_metadata_key'), 'process_instance_metadata', ['key'], unique=False)
|
||||
op.create_table('spiff_step_details',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('process_instance_id', sa.Integer(), nullable=False),
|
||||
|
@ -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_table('active_task_user')
|
||||
op.drop_table('spiff_step_details')
|
||||
op.drop_index(op.f('ix_process_instance_metadata_key'), table_name='process_instance_metadata')
|
||||
op.drop_table('process_instance_metadata')
|
||||
op.drop_table('permission_assignment')
|
||||
op.drop_table('message_instance')
|
|
@ -1851,7 +1851,7 @@ lxml = "*"
|
|||
type = "git"
|
||||
url = "https://github.com/sartography/SpiffWorkflow"
|
||||
reference = "main"
|
||||
resolved_reference = "062eaf15d28c66f8cf07f68409429560251b12c7"
|
||||
resolved_reference = "ffb1686757f944065580dd2db8def73d6c1f0134"
|
||||
|
||||
[[package]]
|
||||
name = "SQLAlchemy"
|
||||
|
@ -2989,7 +2989,18 @@ psycopg2 = [
|
|||
{file = "psycopg2-2.9.4.tar.gz", hash = "sha256:d529926254e093a1b669f692a3aa50069bc71faf5b0ecd91686a78f62767d52f"},
|
||||
]
|
||||
pyasn1 = [
|
||||
{file = "pyasn1-0.4.8-py2.4.egg", hash = "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"},
|
||||
{file = "pyasn1-0.4.8-py2.5.egg", hash = "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf"},
|
||||
{file = "pyasn1-0.4.8-py2.6.egg", hash = "sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00"},
|
||||
{file = "pyasn1-0.4.8-py2.7.egg", hash = "sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8"},
|
||||
{file = "pyasn1-0.4.8-py2.py3-none-any.whl", hash = "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d"},
|
||||
{file = "pyasn1-0.4.8-py3.1.egg", hash = "sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86"},
|
||||
{file = "pyasn1-0.4.8-py3.2.egg", hash = "sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7"},
|
||||
{file = "pyasn1-0.4.8-py3.3.egg", hash = "sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576"},
|
||||
{file = "pyasn1-0.4.8-py3.4.egg", hash = "sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12"},
|
||||
{file = "pyasn1-0.4.8-py3.5.egg", hash = "sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2"},
|
||||
{file = "pyasn1-0.4.8-py3.6.egg", hash = "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359"},
|
||||
{file = "pyasn1-0.4.8-py3.7.egg", hash = "sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776"},
|
||||
{file = "pyasn1-0.4.8.tar.gz", hash = "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba"},
|
||||
]
|
||||
pycodestyle = [
|
||||
|
|
|
@ -544,6 +544,12 @@ paths:
|
|||
description: Specifies the identifier of a report to use, if any
|
||||
schema:
|
||||
type: string
|
||||
- name: report_id
|
||||
in: query
|
||||
required: false
|
||||
description: Specifies the identifier of a report to use, if any
|
||||
schema:
|
||||
type: integer
|
||||
get:
|
||||
operationId: spiffworkflow_backend.routes.process_api_blueprint.process_instance_list
|
||||
summary: Returns a list of process instances for a given process model
|
||||
|
@ -841,14 +847,30 @@ paths:
|
|||
schema:
|
||||
$ref: "#/components/schemas/OkTrue"
|
||||
|
||||
/process-instances/reports/{report_identifier}:
|
||||
/process-instances/reports/columns:
|
||||
get:
|
||||
operationId: spiffworkflow_backend.routes.process_api_blueprint.process_instance_report_column_list
|
||||
summary: Returns all available columns for a process instance report.
|
||||
tags:
|
||||
- Process Instances
|
||||
responses:
|
||||
"200":
|
||||
description: Workflow.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/Workflow"
|
||||
|
||||
/process-instances/reports/{report_id}:
|
||||
parameters:
|
||||
- name: report_identifier
|
||||
- name: report_id
|
||||
in: path
|
||||
required: true
|
||||
description: The unique id of an existing report
|
||||
schema:
|
||||
type: string
|
||||
type: integer
|
||||
- name: page
|
||||
in: query
|
||||
required: false
|
||||
|
|
|
@ -23,7 +23,7 @@ class ProcessInstanceMetadataModel(SpiffworkflowBaseDBModel):
|
|||
process_instance_id: int = db.Column(
|
||||
ForeignKey(ProcessInstanceModel.id), nullable=False # type: ignore
|
||||
)
|
||||
key: str = db.Column(db.String(255), nullable=False)
|
||||
key: str = db.Column(db.String(255), nullable=False, index=True)
|
||||
value: str = db.Column(db.String(255), nullable=False)
|
||||
|
||||
updated_at_in_seconds: int = db.Column(db.Integer, nullable=False)
|
||||
|
|
|
@ -26,6 +26,10 @@ from spiffworkflow_backend.services.process_instance_processor import (
|
|||
ReportMetadata = dict[str, Any]
|
||||
|
||||
|
||||
class ProcessInstanceReportAlreadyExistsError(Exception):
|
||||
"""ProcessInstanceReportAlreadyExistsError."""
|
||||
|
||||
|
||||
class ProcessInstanceReportResult(TypedDict):
|
||||
"""ProcessInstanceReportResult."""
|
||||
|
||||
|
@ -63,7 +67,7 @@ class ProcessInstanceReportModel(SpiffworkflowBaseDBModel):
|
|||
),
|
||||
)
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
id: int = db.Column(db.Integer, primary_key=True)
|
||||
identifier: str = db.Column(db.String(50), nullable=False, index=True)
|
||||
report_metadata: dict = deferred(db.Column(db.JSON)) # type: ignore
|
||||
created_by_id = db.Column(ForeignKey(UserModel.id), nullable=False, index=True)
|
||||
|
@ -71,6 +75,11 @@ class ProcessInstanceReportModel(SpiffworkflowBaseDBModel):
|
|||
created_at_in_seconds = db.Column(db.Integer)
|
||||
updated_at_in_seconds = db.Column(db.Integer)
|
||||
|
||||
@classmethod
|
||||
def default_order_by(cls) -> list[str]:
|
||||
"""Default_order_by."""
|
||||
return ["-start_in_seconds", "-id"]
|
||||
|
||||
@classmethod
|
||||
def add_fixtures(cls) -> None:
|
||||
"""Add_fixtures."""
|
||||
|
@ -120,21 +129,27 @@ class ProcessInstanceReportModel(SpiffworkflowBaseDBModel):
|
|||
identifier: str,
|
||||
user: UserModel,
|
||||
report_metadata: ReportMetadata,
|
||||
) -> None:
|
||||
) -> ProcessInstanceReportModel:
|
||||
"""Make_fixture_report."""
|
||||
process_instance_report = ProcessInstanceReportModel.query.filter_by(
|
||||
identifier=identifier,
|
||||
created_by_id=user.id,
|
||||
).first()
|
||||
|
||||
if process_instance_report is None:
|
||||
process_instance_report = cls(
|
||||
identifier=identifier,
|
||||
created_by_id=user.id,
|
||||
report_metadata=report_metadata,
|
||||
if process_instance_report is not None:
|
||||
raise ProcessInstanceReportAlreadyExistsError(
|
||||
f"Process instance report with identifier already exists: {identifier}"
|
||||
)
|
||||
db.session.add(process_instance_report)
|
||||
db.session.commit()
|
||||
|
||||
process_instance_report = cls(
|
||||
identifier=identifier,
|
||||
created_by_id=user.id,
|
||||
report_metadata=report_metadata,
|
||||
)
|
||||
db.session.add(process_instance_report)
|
||||
db.session.commit()
|
||||
|
||||
return process_instance_report # type: ignore
|
||||
|
||||
@classmethod
|
||||
def ticket_for_month_report(cls) -> dict:
|
||||
|
@ -204,18 +219,8 @@ class ProcessInstanceReportModel(SpiffworkflowBaseDBModel):
|
|||
user: UserModel,
|
||||
) -> ProcessInstanceReportModel:
|
||||
"""Create_with_attributes."""
|
||||
# <<<<<<< HEAD
|
||||
# process_model = ProcessModelService.get_process_model(
|
||||
# process_model_id=f"{process_model_identifier}"
|
||||
# )
|
||||
# process_instance_report = cls(
|
||||
# identifier=identifier,
|
||||
# process_group_identifier="process_model.process_group_id",
|
||||
# process_model_identifier=process_model.id,
|
||||
# =======
|
||||
process_instance_report = cls(
|
||||
identifier=identifier,
|
||||
# >>>>>>> main
|
||||
created_by_id=user.id,
|
||||
report_metadata=report_metadata,
|
||||
)
|
||||
|
|
|
@ -38,6 +38,7 @@ class ProcessModelInfo:
|
|||
fault_or_suspend_on_exception: str = NotificationType.fault.value
|
||||
exception_notification_addresses: list[str] = field(default_factory=list)
|
||||
parent_groups: list[dict] | None = None
|
||||
metadata_extraction_paths: list[dict[str, str]] | None = None
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
"""__post_init__."""
|
||||
|
@ -76,6 +77,13 @@ class ProcessModelInfoSchema(Schema):
|
|||
exception_notification_addresses = marshmallow.fields.List(
|
||||
marshmallow.fields.String
|
||||
)
|
||||
metadata_extraction_paths = marshmallow.fields.List(
|
||||
marshmallow.fields.Dict(
|
||||
keys=marshmallow.fields.Str(required=False),
|
||||
values=marshmallow.fields.Str(required=False),
|
||||
required=False,
|
||||
)
|
||||
)
|
||||
|
||||
@post_load
|
||||
def make_spec(
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
"""APIs for dealing with process groups, process models, and process instances."""
|
||||
import json
|
||||
import random
|
||||
import re
|
||||
import string
|
||||
import uuid
|
||||
from typing import Any
|
||||
|
@ -30,6 +31,8 @@ from SpiffWorkflow.task import TaskState
|
|||
from sqlalchemy import and_
|
||||
from sqlalchemy import asc
|
||||
from sqlalchemy import desc
|
||||
from sqlalchemy import func
|
||||
from sqlalchemy.orm import aliased
|
||||
from sqlalchemy.orm import joinedload
|
||||
|
||||
from 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 ProcessInstanceModelSchema
|
||||
from spiffworkflow_backend.models.process_instance import ProcessInstanceStatus
|
||||
from spiffworkflow_backend.models.process_instance_metadata import (
|
||||
ProcessInstanceMetadataModel,
|
||||
)
|
||||
from spiffworkflow_backend.models.process_instance_report import (
|
||||
ProcessInstanceReportModel,
|
||||
)
|
||||
|
@ -256,19 +262,26 @@ def process_model_create(
|
|||
modified_process_group_id: str, body: Dict[str, Union[str, bool, int]]
|
||||
) -> flask.wrappers.Response:
|
||||
"""Process_model_create."""
|
||||
process_model_info = ProcessModelInfoSchema().load(body)
|
||||
body_include_list = [
|
||||
"id",
|
||||
"display_name",
|
||||
"primary_file_name",
|
||||
"primary_process_id",
|
||||
"description",
|
||||
"metadata_extraction_paths",
|
||||
]
|
||||
body_filtered = {
|
||||
include_item: body[include_item]
|
||||
for include_item in body_include_list
|
||||
if include_item in body
|
||||
}
|
||||
|
||||
if modified_process_group_id is None:
|
||||
raise ApiError(
|
||||
error_code="process_group_id_not_specified",
|
||||
message="Process Model could not be created when process_group_id path param is unspecified",
|
||||
status_code=400,
|
||||
)
|
||||
if process_model_info is None:
|
||||
raise ApiError(
|
||||
error_code="process_model_could_not_be_created",
|
||||
message=f"Process Model could not be created from given body: {body}",
|
||||
status_code=400,
|
||||
)
|
||||
|
||||
unmodified_process_group_id = un_modify_modified_process_model_id(
|
||||
modified_process_group_id
|
||||
|
@ -281,6 +294,14 @@ def process_model_create(
|
|||
status_code=400,
|
||||
)
|
||||
|
||||
process_model_info = ProcessModelInfo(**body_filtered) # type: ignore
|
||||
if process_model_info is None:
|
||||
raise ApiError(
|
||||
error_code="process_model_could_not_be_created",
|
||||
message=f"Process Model could not be created from given body: {body}",
|
||||
status_code=400,
|
||||
)
|
||||
|
||||
ProcessModelService.add_process_model(process_model_info)
|
||||
return Response(
|
||||
json.dumps(ProcessModelInfoSchema().dump(process_model_info)),
|
||||
|
@ -294,7 +315,6 @@ def process_model_delete(
|
|||
) -> flask.wrappers.Response:
|
||||
"""Process_model_delete."""
|
||||
process_model_identifier = modified_process_model_identifier.replace(":", "/")
|
||||
# process_model_identifier = f"{process_group_id}/{process_model_id}"
|
||||
ProcessModelService().process_model_delete(process_model_identifier)
|
||||
return Response(json.dumps({"ok": True}), status=200, mimetype="application/json")
|
||||
|
||||
|
@ -309,6 +329,7 @@ def process_model_update(
|
|||
"primary_file_name",
|
||||
"primary_process_id",
|
||||
"description",
|
||||
"metadata_extraction_paths",
|
||||
]
|
||||
body_filtered = {
|
||||
include_item: body[include_item]
|
||||
|
@ -316,7 +337,6 @@ def process_model_update(
|
|||
if include_item in body
|
||||
}
|
||||
|
||||
# process_model_identifier = f"{process_group_id}/{process_model_id}"
|
||||
process_model = get_process_model(process_model_identifier)
|
||||
ProcessModelService.update_process_model(process_model, body_filtered)
|
||||
return ProcessModelInfoSchema().dump(process_model)
|
||||
|
@ -325,10 +345,7 @@ def process_model_update(
|
|||
def process_model_show(modified_process_model_identifier: str) -> Any:
|
||||
"""Process_model_show."""
|
||||
process_model_identifier = modified_process_model_identifier.replace(":", "/")
|
||||
# process_model_identifier = f"{process_group_id}/{process_model_id}"
|
||||
process_model = get_process_model(process_model_identifier)
|
||||
# TODO: Temporary. Should not need the next line once models have correct ids
|
||||
# process_model.id = process_model_identifier
|
||||
files = sorted(SpecFileService.get_files(process_model))
|
||||
process_model.files = files
|
||||
for file in process_model.files:
|
||||
|
@ -420,7 +437,6 @@ def process_model_file_update(
|
|||
) -> flask.wrappers.Response:
|
||||
"""Process_model_file_update."""
|
||||
process_model_identifier = modified_process_model_id.replace(":", "/")
|
||||
# process_model_identifier = f"{process_group_id}/{process_model_id}"
|
||||
process_model = get_process_model(process_model_identifier)
|
||||
|
||||
request_file = get_file_from_request()
|
||||
|
@ -642,6 +658,7 @@ def message_instance_list(
|
|||
.add_columns(
|
||||
MessageModel.identifier.label("message_identifier"),
|
||||
ProcessInstanceModel.process_model_identifier,
|
||||
ProcessInstanceModel.process_model_display_name,
|
||||
)
|
||||
.paginate(page=page, per_page=per_page, error_out=False)
|
||||
)
|
||||
|
@ -776,10 +793,11 @@ def process_instance_list(
|
|||
with_tasks_completed_by_my_group: Optional[bool] = None,
|
||||
user_filter: Optional[bool] = False,
|
||||
report_identifier: Optional[str] = None,
|
||||
report_id: Optional[int] = None,
|
||||
) -> flask.wrappers.Response:
|
||||
"""Process_instance_list."""
|
||||
process_instance_report = ProcessInstanceReportService.report_with_identifier(
|
||||
g.user, report_identifier
|
||||
g.user, report_id, report_identifier
|
||||
)
|
||||
|
||||
if user_filter:
|
||||
|
@ -810,7 +828,6 @@ def process_instance_list(
|
|||
)
|
||||
)
|
||||
|
||||
# process_model_identifier = un_modify_modified_process_model_id(modified_process_model_identifier)
|
||||
process_instance_query = ProcessInstanceModel.query
|
||||
# Always join that hot user table for good performance at serialization time.
|
||||
process_instance_query = process_instance_query.options(
|
||||
|
@ -928,25 +945,78 @@ def process_instance_list(
|
|||
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_instance_query.group_by(ProcessInstanceModel.id)
|
||||
.order_by(
|
||||
ProcessInstanceModel.start_in_seconds.desc(), ProcessInstanceModel.id.desc() # type: ignore
|
||||
)
|
||||
.add_columns(ProcessInstanceModel.id)
|
||||
.order_by(*order_by_query_array)
|
||||
.paginate(page=page, per_page=per_page, error_out=False)
|
||||
)
|
||||
|
||||
results = list(
|
||||
map(
|
||||
ProcessInstanceService.serialize_flat_with_task_data,
|
||||
process_instances.items,
|
||||
)
|
||||
results = ProcessInstanceReportService.add_metadata_columns_to_process_instance(
|
||||
process_instances.items, process_instance_report.report_metadata["columns"]
|
||||
)
|
||||
report_metadata = process_instance_report.report_metadata
|
||||
|
||||
response_json = {
|
||||
"report_identifier": process_instance_report.identifier,
|
||||
"report_metadata": report_metadata,
|
||||
"report": process_instance_report,
|
||||
"results": results,
|
||||
"filters": report_filter.to_dict(),
|
||||
"pagination": {
|
||||
|
@ -959,6 +1029,22 @@ def process_instance_list(
|
|||
return make_response(jsonify(response_json), 200)
|
||||
|
||||
|
||||
def process_instance_report_column_list() -> flask.wrappers.Response:
|
||||
"""Process_instance_report_column_list."""
|
||||
table_columns = ProcessInstanceReportService.builtin_column_options()
|
||||
columns_for_metadata = (
|
||||
db.session.query(ProcessInstanceMetadataModel.key)
|
||||
.order_by(ProcessInstanceMetadataModel.key)
|
||||
.distinct() # type: ignore
|
||||
.all()
|
||||
)
|
||||
columns_for_metadata_strings = [
|
||||
{"Header": i[0], "accessor": i[0], "filterable": True}
|
||||
for i in columns_for_metadata
|
||||
]
|
||||
return make_response(jsonify(table_columns + columns_for_metadata_strings), 200)
|
||||
|
||||
|
||||
def process_instance_show(
|
||||
modified_process_model_identifier: str, process_instance_id: int
|
||||
) -> flask.wrappers.Response:
|
||||
|
@ -1015,22 +1101,22 @@ def process_instance_report_list(
|
|||
|
||||
def process_instance_report_create(body: Dict[str, Any]) -> flask.wrappers.Response:
|
||||
"""Process_instance_report_create."""
|
||||
ProcessInstanceReportModel.create_report(
|
||||
process_instance_report = ProcessInstanceReportModel.create_report(
|
||||
identifier=body["identifier"],
|
||||
user=g.user,
|
||||
report_metadata=body["report_metadata"],
|
||||
)
|
||||
|
||||
return Response(json.dumps({"ok": True}), status=200, mimetype="application/json")
|
||||
return make_response(jsonify(process_instance_report), 201)
|
||||
|
||||
|
||||
def process_instance_report_update(
|
||||
report_identifier: str,
|
||||
report_id: int,
|
||||
body: Dict[str, Any],
|
||||
) -> flask.wrappers.Response:
|
||||
"""Process_instance_report_create."""
|
||||
process_instance_report = ProcessInstanceReportModel.query.filter_by(
|
||||
identifier=report_identifier,
|
||||
id=report_id,
|
||||
created_by_id=g.user.id,
|
||||
).first()
|
||||
if process_instance_report is None:
|
||||
|
@ -1043,15 +1129,15 @@ def process_instance_report_update(
|
|||
process_instance_report.report_metadata = body["report_metadata"]
|
||||
db.session.commit()
|
||||
|
||||
return Response(json.dumps({"ok": True}), status=200, mimetype="application/json")
|
||||
return make_response(jsonify(process_instance_report), 201)
|
||||
|
||||
|
||||
def process_instance_report_delete(
|
||||
report_identifier: str,
|
||||
report_id: int,
|
||||
) -> flask.wrappers.Response:
|
||||
"""Process_instance_report_create."""
|
||||
process_instance_report = ProcessInstanceReportModel.query.filter_by(
|
||||
identifier=report_identifier,
|
||||
id=report_id,
|
||||
created_by_id=g.user.id,
|
||||
).first()
|
||||
if process_instance_report is None:
|
||||
|
@ -1070,8 +1156,6 @@ def process_instance_report_delete(
|
|||
def service_tasks_show() -> flask.wrappers.Response:
|
||||
"""Service_tasks_show."""
|
||||
available_connectors = ServiceTaskService.available_connectors()
|
||||
print(available_connectors)
|
||||
|
||||
return Response(
|
||||
json.dumps(available_connectors), status=200, mimetype="application/json"
|
||||
)
|
||||
|
@ -1105,19 +1189,17 @@ def authentication_callback(
|
|||
|
||||
|
||||
def process_instance_report_show(
|
||||
report_identifier: str,
|
||||
report_id: int,
|
||||
page: int = 1,
|
||||
per_page: int = 100,
|
||||
) -> flask.wrappers.Response:
|
||||
"""Process_instance_list."""
|
||||
process_instances = ProcessInstanceModel.query.order_by( # .filter_by(process_model_identifier=process_model.id)
|
||||
"""Process_instance_report_show."""
|
||||
process_instances = ProcessInstanceModel.query.order_by(
|
||||
ProcessInstanceModel.start_in_seconds.desc(), ProcessInstanceModel.id.desc() # type: ignore
|
||||
).paginate(
|
||||
page=page, per_page=per_page, error_out=False
|
||||
)
|
||||
).paginate(page=page, per_page=per_page, error_out=False)
|
||||
|
||||
process_instance_report = ProcessInstanceReportModel.query.filter_by(
|
||||
identifier=report_identifier,
|
||||
id=report_id,
|
||||
created_by_id=g.user.id,
|
||||
).first()
|
||||
if process_instance_report is None:
|
||||
|
@ -1394,9 +1476,6 @@ def task_show(process_instance_id: int, task_id: str) -> flask.wrappers.Response
|
|||
task.form_ui_schema = ui_form_contents
|
||||
|
||||
if task.properties and task.data and "instructionsForEndUser" in task.properties:
|
||||
print(
|
||||
f"task.properties['instructionsForEndUser']: {task.properties['instructionsForEndUser']}"
|
||||
)
|
||||
if task.properties["instructionsForEndUser"]:
|
||||
task.properties["instructionsForEndUser"] = render_jinja_template(
|
||||
task.properties["instructionsForEndUser"], task.data
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
"""Get_env."""
|
||||
"""Save process instance metadata."""
|
||||
from typing import Any
|
||||
|
||||
from flask_bpmn.models.db import db
|
||||
|
|
|
@ -235,8 +235,9 @@ class AuthenticationService:
|
|||
refresh_token_object: RefreshTokenModel = RefreshTokenModel.query.filter(
|
||||
RefreshTokenModel.user_id == user_id
|
||||
).first()
|
||||
assert refresh_token_object # noqa: S101
|
||||
return refresh_token_object.token
|
||||
if refresh_token_object:
|
||||
return refresh_token_object.token
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def get_auth_token_from_refresh_token(cls, refresh_token: str) -> dict:
|
||||
|
|
|
@ -81,6 +81,9 @@ from spiffworkflow_backend.models.message_instance import MessageInstanceModel
|
|||
from spiffworkflow_backend.models.message_instance import MessageModel
|
||||
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
|
||||
from spiffworkflow_backend.models.process_instance import ProcessInstanceStatus
|
||||
from spiffworkflow_backend.models.process_instance_metadata import (
|
||||
ProcessInstanceMetadataModel,
|
||||
)
|
||||
from spiffworkflow_backend.models.process_model import ProcessModelInfo
|
||||
from spiffworkflow_backend.models.script_attributes_context import (
|
||||
ScriptAttributesContext,
|
||||
|
@ -178,7 +181,12 @@ class CustomBpmnScriptEngine(PythonScriptEngine): # type: ignore
|
|||
)
|
||||
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."""
|
||||
return self._evaluate(expression, task.data, task, external_methods)
|
||||
|
||||
|
@ -571,6 +579,36 @@ class ProcessInstanceProcessor:
|
|||
db.session.add(details_model)
|
||||
db.session.commit()
|
||||
|
||||
def extract_metadata(self, process_model_info: ProcessModelInfo) -> None:
|
||||
"""Extract_metadata."""
|
||||
metadata_extraction_paths = process_model_info.metadata_extraction_paths
|
||||
if metadata_extraction_paths is None:
|
||||
return
|
||||
if len(metadata_extraction_paths) <= 0:
|
||||
return
|
||||
|
||||
current_data = self.get_current_data()
|
||||
for metadata_extraction_path in metadata_extraction_paths:
|
||||
key = metadata_extraction_path["key"]
|
||||
path = metadata_extraction_path["path"]
|
||||
path_segments = path.split(".")
|
||||
data_for_key = current_data
|
||||
for path_segment in path_segments:
|
||||
data_for_key = data_for_key[path_segment]
|
||||
|
||||
pim = ProcessInstanceMetadataModel.query.filter_by(
|
||||
process_instance_id=self.process_instance_model.id,
|
||||
key=key,
|
||||
).first()
|
||||
if pim is None:
|
||||
pim = ProcessInstanceMetadataModel(
|
||||
process_instance_id=self.process_instance_model.id,
|
||||
key=key,
|
||||
)
|
||||
pim.value = data_for_key
|
||||
db.session.add(pim)
|
||||
db.session.commit()
|
||||
|
||||
def save(self) -> None:
|
||||
"""Saves the current state of this processor to the database."""
|
||||
self.process_instance_model.bpmn_json = self.serialize()
|
||||
|
@ -597,6 +635,15 @@ class ProcessInstanceProcessor:
|
|||
process_instance_id=self.process_instance_model.id
|
||||
).all()
|
||||
ready_or_waiting_tasks = self.get_all_ready_or_waiting_tasks()
|
||||
process_model_display_name = ""
|
||||
process_model_info = self.process_model_service.get_process_model(
|
||||
self.process_instance_model.process_model_identifier
|
||||
)
|
||||
if process_model_info is not None:
|
||||
process_model_display_name = process_model_info.display_name
|
||||
|
||||
self.extract_metadata(process_model_info)
|
||||
|
||||
for ready_or_waiting_task in ready_or_waiting_tasks:
|
||||
# filter out non-usertasks
|
||||
task_spec = ready_or_waiting_task.task_spec
|
||||
|
@ -615,13 +662,6 @@ class ProcessInstanceProcessor:
|
|||
if "formUiSchemaFilename" in properties:
|
||||
ui_form_file_name = properties["formUiSchemaFilename"]
|
||||
|
||||
process_model_display_name = ""
|
||||
process_model_info = self.process_model_service.get_process_model(
|
||||
self.process_instance_model.process_model_identifier
|
||||
)
|
||||
if process_model_info is not None:
|
||||
process_model_display_name = process_model_info.display_name
|
||||
|
||||
active_task = None
|
||||
for at in active_tasks:
|
||||
if at.task_id == str(ready_or_waiting_task.id):
|
||||
|
@ -1146,8 +1186,8 @@ class ProcessInstanceProcessor:
|
|||
def get_current_data(self) -> dict[str, Any]:
|
||||
"""Get the current data for the process.
|
||||
|
||||
Return either most recent task data or the process data
|
||||
if the process instance is complete
|
||||
Return either the most recent task data or--if the process instance is complete--
|
||||
the process data.
|
||||
"""
|
||||
if self.process_instance_model.status == "complete":
|
||||
return self.get_data()
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
|
||||
import sqlalchemy
|
||||
from flask_bpmn.models.db import db
|
||||
|
||||
from spiffworkflow_backend.models.process_instance_report import (
|
||||
ProcessInstanceReportModel,
|
||||
)
|
||||
|
@ -57,12 +60,21 @@ class ProcessInstanceReportService:
|
|||
|
||||
@classmethod
|
||||
def report_with_identifier(
|
||||
cls, user: UserModel, report_identifier: Optional[str] = None
|
||||
cls,
|
||||
user: UserModel,
|
||||
report_id: Optional[int] = None,
|
||||
report_identifier: Optional[str] = None,
|
||||
) -> ProcessInstanceReportModel:
|
||||
"""Report_with_filter."""
|
||||
if report_id is not None:
|
||||
process_instance_report = ProcessInstanceReportModel.query.filter_by(
|
||||
id=report_id, created_by_id=user.id
|
||||
).first()
|
||||
if process_instance_report is not None:
|
||||
return process_instance_report # type: ignore
|
||||
|
||||
if report_identifier is None:
|
||||
report_identifier = "default"
|
||||
|
||||
process_instance_report = ProcessInstanceReportModel.query.filter_by(
|
||||
identifier=report_identifier, created_by_id=user.id
|
||||
).first()
|
||||
|
@ -73,17 +85,9 @@ class ProcessInstanceReportService:
|
|||
# TODO replace with system reports that are loaded on launch (or similar)
|
||||
temp_system_metadata_map = {
|
||||
"default": {
|
||||
"columns": [
|
||||
{"Header": "id", "accessor": "id"},
|
||||
{
|
||||
"Header": "process_model_display_name",
|
||||
"accessor": "process_model_display_name",
|
||||
},
|
||||
{"Header": "start_in_seconds", "accessor": "start_in_seconds"},
|
||||
{"Header": "end_in_seconds", "accessor": "end_in_seconds"},
|
||||
{"Header": "username", "accessor": "username"},
|
||||
{"Header": "status", "accessor": "status"},
|
||||
],
|
||||
"columns": cls.builtin_column_options(),
|
||||
"filter_by": [],
|
||||
"order_by": ["-start_in_seconds", "-id"],
|
||||
},
|
||||
"system_report_instances_initiated_by_me": {
|
||||
"columns": [
|
||||
|
@ -97,48 +101,31 @@ class ProcessInstanceReportService:
|
|||
{"Header": "status", "accessor": "status"},
|
||||
],
|
||||
"filter_by": [{"field_name": "initiated_by_me", "field_value": True}],
|
||||
"order_by": ["-start_in_seconds", "-id"],
|
||||
},
|
||||
"system_report_instances_with_tasks_completed_by_me": {
|
||||
"columns": [
|
||||
{"Header": "id", "accessor": "id"},
|
||||
{
|
||||
"Header": "process_model_display_name",
|
||||
"accessor": "process_model_display_name",
|
||||
},
|
||||
{"Header": "start_in_seconds", "accessor": "start_in_seconds"},
|
||||
{"Header": "end_in_seconds", "accessor": "end_in_seconds"},
|
||||
{"Header": "username", "accessor": "username"},
|
||||
{"Header": "status", "accessor": "status"},
|
||||
],
|
||||
"columns": cls.builtin_column_options(),
|
||||
"filter_by": [
|
||||
{"field_name": "with_tasks_completed_by_me", "field_value": True}
|
||||
],
|
||||
"order_by": ["-start_in_seconds", "-id"],
|
||||
},
|
||||
"system_report_instances_with_tasks_completed_by_my_groups": {
|
||||
"columns": [
|
||||
{"Header": "id", "accessor": "id"},
|
||||
{
|
||||
"Header": "process_model_display_name",
|
||||
"accessor": "process_model_display_name",
|
||||
},
|
||||
{"Header": "start_in_seconds", "accessor": "start_in_seconds"},
|
||||
{"Header": "end_in_seconds", "accessor": "end_in_seconds"},
|
||||
{"Header": "username", "accessor": "username"},
|
||||
{"Header": "status", "accessor": "status"},
|
||||
],
|
||||
"columns": cls.builtin_column_options(),
|
||||
"filter_by": [
|
||||
{
|
||||
"field_name": "with_tasks_completed_by_my_group",
|
||||
"field_value": True,
|
||||
}
|
||||
],
|
||||
"order_by": ["-start_in_seconds", "-id"],
|
||||
},
|
||||
}
|
||||
|
||||
process_instance_report = ProcessInstanceReportModel(
|
||||
identifier=report_identifier,
|
||||
created_by_id=user.id,
|
||||
report_metadata=temp_system_metadata_map[report_identifier], # type: ignore
|
||||
report_metadata=temp_system_metadata_map[report_identifier],
|
||||
)
|
||||
|
||||
return process_instance_report # type: ignore
|
||||
|
@ -241,3 +228,43 @@ class ProcessInstanceReportService:
|
|||
)
|
||||
|
||||
return report_filter
|
||||
|
||||
@classmethod
|
||||
def add_metadata_columns_to_process_instance(
|
||||
cls,
|
||||
process_instance_sqlalchemy_rows: list[sqlalchemy.engine.row.Row], # type: ignore
|
||||
metadata_columns: list[dict],
|
||||
) -> list[dict]:
|
||||
"""Add_metadata_columns_to_process_instance."""
|
||||
results = []
|
||||
for process_instance in process_instance_sqlalchemy_rows:
|
||||
process_instance_dict = process_instance["ProcessInstanceModel"].serialized
|
||||
for metadata_column in metadata_columns:
|
||||
if metadata_column["accessor"] not in process_instance_dict:
|
||||
process_instance_dict[
|
||||
metadata_column["accessor"]
|
||||
] = process_instance[metadata_column["accessor"]]
|
||||
|
||||
results.append(process_instance_dict)
|
||||
return results
|
||||
|
||||
@classmethod
|
||||
def get_column_names_for_model(cls, model: db.Model) -> list[str]: # type: ignore
|
||||
"""Get_column_names_for_model."""
|
||||
return [i.name for i in model.__table__.columns]
|
||||
|
||||
@classmethod
|
||||
def builtin_column_options(cls) -> list[dict]:
|
||||
"""Builtin_column_options."""
|
||||
return [
|
||||
{"Header": "Id", "accessor": "id", "filterable": False},
|
||||
{
|
||||
"Header": "Process",
|
||||
"accessor": "process_model_display_name",
|
||||
"filterable": False,
|
||||
},
|
||||
{"Header": "Start", "accessor": "start_in_seconds", "filterable": False},
|
||||
{"Header": "End", "accessor": "end_in_seconds", "filterable": False},
|
||||
{"Header": "Username", "accessor": "username", "filterable": False},
|
||||
{"Header": "Status", "accessor": "status", "filterable": False},
|
||||
]
|
||||
|
|
|
@ -322,18 +322,3 @@ class ProcessInstanceService:
|
|||
)
|
||||
|
||||
return task
|
||||
|
||||
@staticmethod
|
||||
def serialize_flat_with_task_data(
|
||||
process_instance: ProcessInstanceModel,
|
||||
) -> dict[str, Any]:
|
||||
"""NOTE: This is crazy slow. Put the latest task data in the database."""
|
||||
"""Serialize_flat_with_task_data."""
|
||||
# results = {}
|
||||
# try:
|
||||
# processor = ProcessInstanceProcessor(process_instance)
|
||||
# process_instance.data = processor.get_current_data()
|
||||
# results = process_instance.serialized_flat
|
||||
# except ApiError:
|
||||
results = process_instance.serialized
|
||||
return results
|
||||
|
|
|
@ -19,7 +19,11 @@
|
|||
<bpmn:scriptTask id="hot_script_task_OH_YEEEEEEEEEEEEEEEEEEEEAH" name="OHHHHHHHHHHYEEEESSSSSSSSSS">
|
||||
<bpmn:incoming>Flow_0bazl8x</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_1mcaszp</bpmn:outgoing>
|
||||
<bpmn:script>a = 1</bpmn:script>
|
||||
<bpmn:script>a = 1
|
||||
b = 2
|
||||
outer = {}
|
||||
outer["inner"] = 'sweet1'
|
||||
</bpmn:script>
|
||||
</bpmn:scriptTask>
|
||||
<bpmn:endEvent id="Event_1vch1y0">
|
||||
<bpmn:incoming>Flow_1mcaszp</bpmn:incoming>
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_96f6665" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.0.0-dev">
|
||||
<bpmn:process id="Process_hk6nsfl" isExecutable="true">
|
||||
<bpmn:startEvent id="StartEvent_1">
|
||||
<bpmn:outgoing>Flow_1ohrjz9</bpmn:outgoing>
|
||||
</bpmn:startEvent>
|
||||
<bpmn:sequenceFlow id="Flow_1ohrjz9" sourceRef="StartEvent_1" targetRef="Activity_0fah9rm" />
|
||||
<bpmn:endEvent id="Event_1tk4dsv">
|
||||
<bpmn:incoming>Flow_1flxgry</bpmn:incoming>
|
||||
</bpmn:endEvent>
|
||||
<bpmn:sequenceFlow id="Flow_18gs4jt" sourceRef="Activity_0fah9rm" targetRef="Activity_1bvyv67" />
|
||||
<bpmn:scriptTask id="Activity_0fah9rm" name="First setting of data">
|
||||
<bpmn:incoming>Flow_1ohrjz9</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_18gs4jt</bpmn:outgoing>
|
||||
<bpmn:script>outer = {}
|
||||
invoice_number = 123
|
||||
outer["inner"] = 'sweet1'
|
||||
outer['time'] = time.time_ns()</bpmn:script>
|
||||
</bpmn:scriptTask>
|
||||
<bpmn:sequenceFlow id="Flow_1flxgry" sourceRef="Activity_1bvyv67" targetRef="Event_1tk4dsv" />
|
||||
<bpmn:scriptTask id="Activity_1bvyv67" name="First setting of data">
|
||||
<bpmn:incoming>Flow_18gs4jt</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_1flxgry</bpmn:outgoing>
|
||||
<bpmn:script>outer["inner"] = 'sweet2'</bpmn:script>
|
||||
</bpmn:scriptTask>
|
||||
</bpmn:process>
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_hk6nsfl">
|
||||
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
|
||||
<dc:Bounds x="179" y="159" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_1c5bi8c_di" bpmnElement="Activity_0fah9rm">
|
||||
<dc:Bounds x="270" y="137" width="100" height="80" />
|
||||
<bpmndi:BPMNLabel />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Event_1tk4dsv_di" bpmnElement="Event_1tk4dsv">
|
||||
<dc:Bounds x="612" y="159" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_1ay4o3w_di" bpmnElement="Activity_1bvyv67">
|
||||
<dc:Bounds x="430" y="137" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge id="Flow_1ohrjz9_di" bpmnElement="Flow_1ohrjz9">
|
||||
<di:waypoint x="215" y="177" />
|
||||
<di:waypoint x="270" y="177" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_18gs4jt_di" bpmnElement="Flow_18gs4jt">
|
||||
<di:waypoint x="370" y="177" />
|
||||
<di:waypoint x="430" y="177" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_1flxgry_di" bpmnElement="Flow_1flxgry">
|
||||
<di:waypoint x="530" y="177" />
|
||||
<di:waypoint x="612" y="177" />
|
||||
</bpmndi:BPMNEdge>
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
</bpmn:definitions>
|
|
@ -20,6 +20,9 @@ from spiffworkflow_backend.models.group import GroupModel
|
|||
from spiffworkflow_backend.models.process_group import ProcessGroup
|
||||
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
|
||||
from spiffworkflow_backend.models.process_instance import ProcessInstanceStatus
|
||||
from spiffworkflow_backend.models.process_instance_metadata import (
|
||||
ProcessInstanceMetadataModel,
|
||||
)
|
||||
from spiffworkflow_backend.models.process_instance_report import (
|
||||
ProcessInstanceReportModel,
|
||||
)
|
||||
|
@ -330,6 +333,9 @@ class TestProcessApi(BaseTest):
|
|||
process_model.display_name = "Updated Display Name"
|
||||
process_model.primary_file_name = "superduper.bpmn"
|
||||
process_model.primary_process_id = "superduper"
|
||||
process_model.metadata_extraction_paths = [
|
||||
{"key": "extraction1", "path": "path1"}
|
||||
]
|
||||
|
||||
modified_process_model_identifier = process_model_identifier.replace("/", ":")
|
||||
response = client.put(
|
||||
|
@ -343,6 +349,9 @@ class TestProcessApi(BaseTest):
|
|||
assert response.json["display_name"] == "Updated Display Name"
|
||||
assert response.json["primary_file_name"] == "superduper.bpmn"
|
||||
assert response.json["primary_process_id"] == "superduper"
|
||||
assert response.json["metadata_extraction_paths"] == [
|
||||
{"key": "extraction1", "path": "path1"}
|
||||
]
|
||||
|
||||
def test_process_model_list_all(
|
||||
self,
|
||||
|
@ -1723,14 +1732,14 @@ class TestProcessApi(BaseTest):
|
|||
],
|
||||
}
|
||||
|
||||
ProcessInstanceReportModel.create_with_attributes(
|
||||
report = ProcessInstanceReportModel.create_with_attributes(
|
||||
identifier="sure",
|
||||
report_metadata=report_metadata,
|
||||
user=with_super_admin_user,
|
||||
)
|
||||
|
||||
response = client.get(
|
||||
"/v1.0/process-instances/reports/sure",
|
||||
f"/v1.0/process-instances/reports/{report.id}",
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
@ -1769,14 +1778,14 @@ class TestProcessApi(BaseTest):
|
|||
],
|
||||
}
|
||||
|
||||
ProcessInstanceReportModel.create_with_attributes(
|
||||
report = ProcessInstanceReportModel.create_with_attributes(
|
||||
identifier="sure",
|
||||
report_metadata=report_metadata,
|
||||
user=with_super_admin_user,
|
||||
)
|
||||
|
||||
response = client.get(
|
||||
"/v1.0/process-instances/reports/sure?grade_level=1",
|
||||
f"/v1.0/process-instances/reports/{report.id}?grade_level=1",
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
@ -1791,9 +1800,9 @@ class TestProcessApi(BaseTest):
|
|||
with_super_admin_user: UserModel,
|
||||
setup_process_instances_for_reports: list[ProcessInstanceModel],
|
||||
) -> None:
|
||||
"""Test_process_instance_report_show_with_default_list."""
|
||||
"""Test_process_instance_report_show_with_bad_identifier."""
|
||||
response = client.get(
|
||||
"/v1.0/process-instances/reports/sure?grade_level=1",
|
||||
"/v1.0/process-instances/reports/13000000?grade_level=1",
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
assert response.status_code == 404
|
||||
|
@ -2544,3 +2553,191 @@ class TestProcessApi(BaseTest):
|
|||
# make sure the new subgroup does exist
|
||||
new_process_group = ProcessModelService.get_process_group(new_sub_path)
|
||||
assert new_process_group.id == new_sub_path
|
||||
|
||||
def test_can_get_process_instance_list_with_report_metadata(
|
||||
self,
|
||||
app: Flask,
|
||||
client: FlaskClient,
|
||||
with_db_and_bpmn_file_cleanup: None,
|
||||
with_super_admin_user: UserModel,
|
||||
) -> None:
|
||||
"""Test_can_get_process_instance_list_with_report_metadata."""
|
||||
process_model = load_test_spec(
|
||||
process_model_id="save_process_instance_metadata/save_process_instance_metadata",
|
||||
bpmn_file_name="save_process_instance_metadata.bpmn",
|
||||
process_model_source_directory="save_process_instance_metadata",
|
||||
)
|
||||
process_instance = self.create_process_instance_from_process_model(
|
||||
process_model=process_model, user=with_super_admin_user
|
||||
)
|
||||
|
||||
processor = ProcessInstanceProcessor(process_instance)
|
||||
processor.do_engine_steps(save=True)
|
||||
process_instance_metadata = ProcessInstanceMetadataModel.query.filter_by(
|
||||
process_instance_id=process_instance.id
|
||||
).all()
|
||||
assert len(process_instance_metadata) == 3
|
||||
|
||||
report_metadata = {
|
||||
"columns": [
|
||||
{"Header": "ID", "accessor": "id"},
|
||||
{"Header": "Status", "accessor": "status"},
|
||||
{"Header": "Key One", "accessor": "key1"},
|
||||
{"Header": "Key Two", "accessor": "key2"},
|
||||
],
|
||||
"order_by": ["status"],
|
||||
"filter_by": [],
|
||||
}
|
||||
process_instance_report = ProcessInstanceReportModel.create_with_attributes(
|
||||
identifier="sure",
|
||||
report_metadata=report_metadata,
|
||||
user=with_super_admin_user,
|
||||
)
|
||||
|
||||
response = client.get(
|
||||
f"/v1.0/process-instances?report_identifier={process_instance_report.identifier}",
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
|
||||
assert response.json is not None
|
||||
assert response.status_code == 200
|
||||
|
||||
assert len(response.json["results"]) == 1
|
||||
assert response.json["results"][0]["status"] == "complete"
|
||||
assert response.json["results"][0]["id"] == process_instance.id
|
||||
assert response.json["results"][0]["key1"] == "value1"
|
||||
assert response.json["results"][0]["key2"] == "value2"
|
||||
assert response.json["pagination"]["count"] == 1
|
||||
assert response.json["pagination"]["pages"] == 1
|
||||
assert response.json["pagination"]["total"] == 1
|
||||
|
||||
def test_can_get_process_instance_report_column_list(
|
||||
self,
|
||||
app: Flask,
|
||||
client: FlaskClient,
|
||||
with_db_and_bpmn_file_cleanup: None,
|
||||
with_super_admin_user: UserModel,
|
||||
) -> None:
|
||||
"""Test_can_get_process_instance_list_with_report_metadata."""
|
||||
process_model = load_test_spec(
|
||||
process_model_id="save_process_instance_metadata/save_process_instance_metadata",
|
||||
bpmn_file_name="save_process_instance_metadata.bpmn",
|
||||
process_model_source_directory="save_process_instance_metadata",
|
||||
)
|
||||
process_instance = self.create_process_instance_from_process_model(
|
||||
process_model=process_model, user=with_super_admin_user
|
||||
)
|
||||
|
||||
processor = ProcessInstanceProcessor(process_instance)
|
||||
processor.do_engine_steps(save=True)
|
||||
process_instance_metadata = ProcessInstanceMetadataModel.query.filter_by(
|
||||
process_instance_id=process_instance.id
|
||||
).all()
|
||||
assert len(process_instance_metadata) == 3
|
||||
|
||||
response = client.get(
|
||||
"/v1.0/process-instances/reports/columns",
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
|
||||
assert response.json is not None
|
||||
assert response.status_code == 200
|
||||
assert response.json == [
|
||||
{"Header": "Id", "accessor": "id", "filterable": False},
|
||||
{
|
||||
"Header": "Process",
|
||||
"accessor": "process_model_display_name",
|
||||
"filterable": False,
|
||||
},
|
||||
{"Header": "Start", "accessor": "start_in_seconds", "filterable": False},
|
||||
{"Header": "End", "accessor": "end_in_seconds", "filterable": False},
|
||||
{"Header": "Username", "accessor": "username", "filterable": False},
|
||||
{"Header": "Status", "accessor": "status", "filterable": False},
|
||||
{"Header": "key1", "accessor": "key1", "filterable": True},
|
||||
{"Header": "key2", "accessor": "key2", "filterable": True},
|
||||
{"Header": "key3", "accessor": "key3", "filterable": True},
|
||||
]
|
||||
|
||||
def test_process_instance_list_can_order_by_metadata(
|
||||
self,
|
||||
app: Flask,
|
||||
client: FlaskClient,
|
||||
with_db_and_bpmn_file_cleanup: None,
|
||||
with_super_admin_user: UserModel,
|
||||
) -> None:
|
||||
"""Test_process_instance_list_can_order_by_metadata."""
|
||||
self.create_process_group(
|
||||
client, with_super_admin_user, "test_group", "test_group"
|
||||
)
|
||||
process_model = load_test_spec(
|
||||
"test_group/hello_world",
|
||||
process_model_source_directory="nested-task-data-structure",
|
||||
)
|
||||
ProcessModelService.update_process_model(
|
||||
process_model,
|
||||
{
|
||||
"metadata_extraction_paths": [
|
||||
{"key": "time_ns", "path": "outer.time"},
|
||||
]
|
||||
},
|
||||
)
|
||||
|
||||
process_instance_one = self.create_process_instance_from_process_model(
|
||||
process_model
|
||||
)
|
||||
processor = ProcessInstanceProcessor(process_instance_one)
|
||||
processor.do_engine_steps(save=True)
|
||||
assert process_instance_one.status == "complete"
|
||||
process_instance_two = self.create_process_instance_from_process_model(
|
||||
process_model
|
||||
)
|
||||
processor = ProcessInstanceProcessor(process_instance_two)
|
||||
processor.do_engine_steps(save=True)
|
||||
assert process_instance_two.status == "complete"
|
||||
|
||||
report_metadata = {
|
||||
"columns": [
|
||||
{"Header": "id", "accessor": "id"},
|
||||
{"Header": "Time", "accessor": "time_ns"},
|
||||
],
|
||||
"order_by": ["time_ns"],
|
||||
}
|
||||
report_one = ProcessInstanceReportModel.create_with_attributes(
|
||||
identifier="report_one",
|
||||
report_metadata=report_metadata,
|
||||
user=with_super_admin_user,
|
||||
)
|
||||
|
||||
response = client.get(
|
||||
f"/v1.0/process-instances?report_id={report_one.id}",
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json is not None
|
||||
assert len(response.json["results"]) == 2
|
||||
assert response.json["results"][0]["id"] == process_instance_one.id
|
||||
assert response.json["results"][1]["id"] == process_instance_two.id
|
||||
|
||||
report_metadata = {
|
||||
"columns": [
|
||||
{"Header": "id", "accessor": "id"},
|
||||
{"Header": "Time", "accessor": "time_ns"},
|
||||
],
|
||||
"order_by": ["-time_ns"],
|
||||
}
|
||||
report_two = ProcessInstanceReportModel.create_with_attributes(
|
||||
identifier="report_two",
|
||||
report_metadata=report_metadata,
|
||||
user=with_super_admin_user,
|
||||
)
|
||||
|
||||
response = client.get(
|
||||
f"/v1.0/process-instances?report_id={report_two.id}",
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json is not None
|
||||
assert len(response.json["results"]) == 2
|
||||
assert response.json["results"][1]["id"] == process_instance_one.id
|
||||
assert response.json["results"][0]["id"] == process_instance_two.id
|
||||
|
|
|
@ -37,7 +37,7 @@ def test_generate_report_with_filter_by_with_variable_substitution(
|
|||
with_db_and_bpmn_file_cleanup: None,
|
||||
setup_process_instances_for_reports: list[ProcessInstanceModel],
|
||||
) -> None:
|
||||
"""Test_user_can_be_given_permission_to_administer_process_group."""
|
||||
"""Test_generate_report_with_filter_by_with_variable_substitution."""
|
||||
process_instances = setup_process_instances_for_reports
|
||||
report_metadata = {
|
||||
"filter_by": [
|
||||
|
@ -61,7 +61,7 @@ def test_generate_report_with_order_by_and_one_field(
|
|||
with_db_and_bpmn_file_cleanup: None,
|
||||
setup_process_instances_for_reports: list[ProcessInstanceModel],
|
||||
) -> None:
|
||||
"""Test_user_can_be_given_permission_to_administer_process_group."""
|
||||
"""Test_generate_report_with_order_by_and_one_field."""
|
||||
process_instances = setup_process_instances_for_reports
|
||||
report_metadata = {"order_by": ["test_score"]}
|
||||
results = do_report_with_metadata_and_instances(report_metadata, process_instances)
|
||||
|
@ -75,7 +75,7 @@ def test_generate_report_with_order_by_and_two_fields(
|
|||
with_db_and_bpmn_file_cleanup: None,
|
||||
setup_process_instances_for_reports: list[ProcessInstanceModel],
|
||||
) -> None:
|
||||
"""Test_user_can_be_given_permission_to_administer_process_group."""
|
||||
"""Test_generate_report_with_order_by_and_two_fields."""
|
||||
process_instances = setup_process_instances_for_reports
|
||||
report_metadata = {"order_by": ["grade_level", "test_score"]}
|
||||
results = do_report_with_metadata_and_instances(report_metadata, process_instances)
|
||||
|
@ -89,7 +89,7 @@ def test_generate_report_with_order_by_desc(
|
|||
with_db_and_bpmn_file_cleanup: None,
|
||||
setup_process_instances_for_reports: list[ProcessInstanceModel],
|
||||
) -> None:
|
||||
"""Test_user_can_be_given_permission_to_administer_process_group."""
|
||||
"""Test_generate_report_with_order_by_desc."""
|
||||
process_instances = setup_process_instances_for_reports
|
||||
report_metadata = {"order_by": ["grade_level", "-test_score"]}
|
||||
results = do_report_with_metadata_and_instances(report_metadata, process_instances)
|
||||
|
@ -103,7 +103,7 @@ def test_generate_report_with_columns(
|
|||
with_db_and_bpmn_file_cleanup: None,
|
||||
setup_process_instances_for_reports: list[ProcessInstanceModel],
|
||||
) -> None:
|
||||
"""Test_user_can_be_given_permission_to_administer_process_group."""
|
||||
"""Test_generate_report_with_columns."""
|
||||
process_instances = setup_process_instances_for_reports
|
||||
report_metadata = {
|
||||
"columns": [
|
||||
|
|
|
@ -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.test_data import load_test_spec
|
||||
|
||||
from spiffworkflow_backend.models.process_instance_metadata import (
|
||||
ProcessInstanceMetadataModel,
|
||||
)
|
||||
from spiffworkflow_backend.models.process_model import ProcessModelInfo
|
||||
from spiffworkflow_backend.models.spec_reference import SpecReferenceCache
|
||||
from spiffworkflow_backend.models.user import UserModel
|
||||
from spiffworkflow_backend.services.process_instance_processor import (
|
||||
ProcessInstanceProcessor,
|
||||
)
|
||||
from spiffworkflow_backend.services.process_model_service import ProcessModelService
|
||||
|
||||
|
||||
class TestProcessModel(BaseTest):
|
||||
|
@ -122,6 +126,53 @@ class TestProcessModel(BaseTest):
|
|||
processor.do_engine_steps(save=True)
|
||||
assert process_instance.status == "complete"
|
||||
|
||||
def test_extract_metadata(
|
||||
self,
|
||||
app: Flask,
|
||||
client: FlaskClient,
|
||||
with_db_and_bpmn_file_cleanup: None,
|
||||
with_super_admin_user: UserModel,
|
||||
) -> None:
|
||||
"""Test_can_run_process_model_with_call_activities."""
|
||||
self.create_process_group(
|
||||
client, with_super_admin_user, "test_group", "test_group"
|
||||
)
|
||||
process_model = load_test_spec(
|
||||
"test_group/hello_world",
|
||||
process_model_source_directory="nested-task-data-structure",
|
||||
)
|
||||
ProcessModelService.update_process_model(
|
||||
process_model,
|
||||
{
|
||||
"metadata_extraction_paths": [
|
||||
{"key": "awesome_var", "path": "outer.inner"},
|
||||
{"key": "invoice_number", "path": "invoice_number"},
|
||||
]
|
||||
},
|
||||
)
|
||||
|
||||
process_instance = self.create_process_instance_from_process_model(
|
||||
process_model
|
||||
)
|
||||
processor = ProcessInstanceProcessor(process_instance)
|
||||
processor.do_engine_steps(save=True)
|
||||
assert process_instance.status == "complete"
|
||||
|
||||
process_instance_metadata_awesome_var = (
|
||||
ProcessInstanceMetadataModel.query.filter_by(
|
||||
process_instance_id=process_instance.id, key="awesome_var"
|
||||
).first()
|
||||
)
|
||||
assert process_instance_metadata_awesome_var is not None
|
||||
assert process_instance_metadata_awesome_var.value == "sweet2"
|
||||
process_instance_metadata_awesome_var = (
|
||||
ProcessInstanceMetadataModel.query.filter_by(
|
||||
process_instance_id=process_instance.id, key="invoice_number"
|
||||
).first()
|
||||
)
|
||||
assert process_instance_metadata_awesome_var is not None
|
||||
assert process_instance_metadata_awesome_var.value == "123"
|
||||
|
||||
def create_test_process_model(self, id: str, display_name: str) -> ProcessModelInfo:
|
||||
"""Create_test_process_model."""
|
||||
return ProcessModelInfo(
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
import { Link } from 'react-router-dom';
|
||||
import { modifyProcessIdentifierForPathParam } from '../helpers';
|
||||
import { MessageInstance, ProcessInstance } from '../interfaces';
|
||||
|
||||
export function FormatProcessModelDisplayName(
|
||||
instanceObject: ProcessInstance | MessageInstance
|
||||
) {
|
||||
const {
|
||||
process_model_identifier: processModelIdentifier,
|
||||
process_model_display_name: processModelDisplayName,
|
||||
} = instanceObject;
|
||||
return (
|
||||
<Link
|
||||
to={`/admin/process-models/${modifyProcessIdentifierForPathParam(
|
||||
processModelIdentifier
|
||||
)}`}
|
||||
title={processModelIdentifier}
|
||||
>
|
||||
{processModelDisplayName}
|
||||
</Link>
|
||||
);
|
||||
}
|
|
@ -115,7 +115,6 @@ export default function ProcessGroupForm({
|
|||
labelText="Display Name*"
|
||||
value={processGroup.display_name}
|
||||
onChange={(event: any) => onDisplayNameChanged(event.target.value)}
|
||||
onBlur={(event: any) => console.log('event', event)}
|
||||
/>,
|
||||
];
|
||||
|
||||
|
|
|
@ -0,0 +1,205 @@
|
|||
import { useState } from 'react';
|
||||
import {
|
||||
Button,
|
||||
TextInput,
|
||||
Stack,
|
||||
Modal,
|
||||
// @ts-ignore
|
||||
} from '@carbon/react';
|
||||
import {
|
||||
ReportFilter,
|
||||
ProcessInstanceReport,
|
||||
ProcessModel,
|
||||
ReportColumn,
|
||||
ReportMetadata,
|
||||
} from '../interfaces';
|
||||
import HttpService from '../services/HttpService';
|
||||
|
||||
type OwnProps = {
|
||||
onSuccess: (..._args: any[]) => any;
|
||||
columnArray: ReportColumn[];
|
||||
orderBy: string;
|
||||
processModelSelection: ProcessModel | null;
|
||||
processStatusSelection: string[];
|
||||
startFromSeconds: string | null;
|
||||
startToSeconds: string | null;
|
||||
endFromSeconds: string | null;
|
||||
endToSeconds: string | null;
|
||||
buttonText?: string;
|
||||
buttonClassName?: string;
|
||||
processInstanceReportSelection?: ProcessInstanceReport | null;
|
||||
reportMetadata: ReportMetadata;
|
||||
};
|
||||
|
||||
export default function ProcessInstanceListSaveAsReport({
|
||||
onSuccess,
|
||||
columnArray,
|
||||
orderBy,
|
||||
processModelSelection,
|
||||
processInstanceReportSelection,
|
||||
processStatusSelection,
|
||||
startFromSeconds,
|
||||
startToSeconds,
|
||||
endFromSeconds,
|
||||
endToSeconds,
|
||||
buttonClassName,
|
||||
buttonText = 'Save as Perspective',
|
||||
reportMetadata,
|
||||
}: OwnProps) {
|
||||
const [identifier, setIdentifier] = useState<string>(
|
||||
processInstanceReportSelection?.identifier || ''
|
||||
);
|
||||
const [showSaveForm, setShowSaveForm] = useState<boolean>(false);
|
||||
|
||||
const isEditMode = () => {
|
||||
return (
|
||||
processInstanceReportSelection &&
|
||||
processInstanceReportSelection.identifier === identifier
|
||||
);
|
||||
};
|
||||
|
||||
const responseHandler = (result: any) => {
|
||||
if (result) {
|
||||
onSuccess(result, isEditMode() ? 'edit' : 'new');
|
||||
}
|
||||
};
|
||||
|
||||
const handleSaveFormClose = () => {
|
||||
setIdentifier(processInstanceReportSelection?.identifier || '');
|
||||
setShowSaveForm(false);
|
||||
};
|
||||
|
||||
const addProcessInstanceReport = (event: any) => {
|
||||
event.preventDefault();
|
||||
|
||||
// TODO: make a field to set this
|
||||
let orderByArray = ['-start_in_seconds', '-id'];
|
||||
if (orderBy) {
|
||||
orderByArray = orderBy.split(',').filter((n) => n);
|
||||
}
|
||||
const filterByArray: any = [];
|
||||
|
||||
if (processModelSelection) {
|
||||
filterByArray.push({
|
||||
field_name: 'process_model_identifier',
|
||||
field_value: processModelSelection.id,
|
||||
});
|
||||
}
|
||||
|
||||
if (processStatusSelection.length > 0) {
|
||||
filterByArray.push({
|
||||
field_name: 'process_status',
|
||||
field_value: processStatusSelection.join(','),
|
||||
operator: 'in',
|
||||
});
|
||||
}
|
||||
|
||||
if (startFromSeconds) {
|
||||
filterByArray.push({
|
||||
field_name: 'start_from',
|
||||
field_value: startFromSeconds,
|
||||
});
|
||||
}
|
||||
|
||||
if (startToSeconds) {
|
||||
filterByArray.push({
|
||||
field_name: 'start_to',
|
||||
field_value: startToSeconds,
|
||||
});
|
||||
}
|
||||
|
||||
if (endFromSeconds) {
|
||||
filterByArray.push({
|
||||
field_name: 'end_from',
|
||||
field_value: endFromSeconds,
|
||||
});
|
||||
}
|
||||
|
||||
if (endToSeconds) {
|
||||
filterByArray.push({
|
||||
field_name: 'end_to',
|
||||
field_value: endToSeconds,
|
||||
});
|
||||
}
|
||||
|
||||
reportMetadata.filter_by.forEach((reportFilter: ReportFilter) => {
|
||||
columnArray.forEach((reportColumn: ReportColumn) => {
|
||||
if (
|
||||
reportColumn.accessor === reportFilter.field_name &&
|
||||
reportColumn.filterable
|
||||
) {
|
||||
filterByArray.push(reportFilter);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
let path = `/process-instances/reports`;
|
||||
let httpMethod = 'POST';
|
||||
if (isEditMode() && processInstanceReportSelection) {
|
||||
httpMethod = 'PUT';
|
||||
path = `${path}/${processInstanceReportSelection.id}`;
|
||||
}
|
||||
|
||||
HttpService.makeCallToBackend({
|
||||
path,
|
||||
successCallback: responseHandler,
|
||||
httpMethod,
|
||||
postBody: {
|
||||
identifier,
|
||||
report_metadata: {
|
||||
columns: columnArray,
|
||||
order_by: orderByArray,
|
||||
filter_by: filterByArray,
|
||||
},
|
||||
},
|
||||
});
|
||||
handleSaveFormClose();
|
||||
};
|
||||
|
||||
let textInputComponent = null;
|
||||
textInputComponent = (
|
||||
<TextInput
|
||||
id="identifier"
|
||||
name="identifier"
|
||||
labelText="Identifier"
|
||||
className="no-wrap"
|
||||
inline
|
||||
value={identifier}
|
||||
onChange={(e: any) => setIdentifier(e.target.value)}
|
||||
/>
|
||||
);
|
||||
|
||||
let descriptionText =
|
||||
'Save the current columns and filters as a perspective so you can come back to this view in the future.';
|
||||
if (processInstanceReportSelection) {
|
||||
descriptionText =
|
||||
'Keep the identifier the same and click Save to update the current perspective. Change the identifier if you want to save the current view with a new name.';
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack gap={5} orientation="horizontal">
|
||||
<Modal
|
||||
open={showSaveForm}
|
||||
modalHeading="Save Perspective"
|
||||
primaryButtonText="Save"
|
||||
primaryButtonDisabled={!identifier}
|
||||
onRequestSubmit={addProcessInstanceReport}
|
||||
onRequestClose={handleSaveFormClose}
|
||||
hasScrollingContent
|
||||
>
|
||||
<p className="data-table-description">{descriptionText}</p>
|
||||
{textInputComponent}
|
||||
</Modal>
|
||||
<Button
|
||||
kind=""
|
||||
className={buttonClassName}
|
||||
onClick={() => {
|
||||
setIdentifier(processInstanceReportSelection?.identifier || '');
|
||||
setShowSaveForm(true);
|
||||
}}
|
||||
>
|
||||
{buttonText}
|
||||
</Button>
|
||||
</Stack>
|
||||
);
|
||||
}
|
|
@ -7,7 +7,7 @@ import {
|
|||
} from 'react-router-dom';
|
||||
|
||||
// @ts-ignore
|
||||
import { Filter } from '@carbon/icons-react';
|
||||
import { Filter, Close, AddAlt } from '@carbon/icons-react';
|
||||
import {
|
||||
Button,
|
||||
ButtonSet,
|
||||
|
@ -21,6 +21,13 @@ import {
|
|||
TableHead,
|
||||
TableRow,
|
||||
TimePicker,
|
||||
Tag,
|
||||
InlineNotification,
|
||||
Stack,
|
||||
Modal,
|
||||
ComboBox,
|
||||
TextInput,
|
||||
FormLabel,
|
||||
// @ts-ignore
|
||||
} from '@carbon/react';
|
||||
import { PROCESS_STATUSES, DATE_FORMAT, DATE_FORMAT_CARBON } from '../config';
|
||||
|
@ -49,9 +56,15 @@ import {
|
|||
ProcessModel,
|
||||
ProcessInstanceReport,
|
||||
ProcessInstance,
|
||||
ReportColumn,
|
||||
ReportColumnForEditing,
|
||||
ReportMetadata,
|
||||
ReportFilter,
|
||||
} from '../interfaces';
|
||||
import ProcessModelSearch from './ProcessModelSearch';
|
||||
import ProcessInstanceReportSearch from './ProcessInstanceReportSearch';
|
||||
import ProcessInstanceListSaveAsReport from './ProcessInstanceListSaveAsReport';
|
||||
import { FormatProcessModelDisplayName } from './MiniComponents';
|
||||
|
||||
const REFRESH_INTERVAL = 5;
|
||||
const REFRESH_TIMEOUT = 600;
|
||||
|
@ -88,7 +101,7 @@ export default function ProcessInstanceListTable({
|
|||
const navigate = useNavigate();
|
||||
|
||||
const [processInstances, setProcessInstances] = useState([]);
|
||||
const [reportMetadata, setReportMetadata] = useState({});
|
||||
const [reportMetadata, setReportMetadata] = useState<ReportMetadata | null>();
|
||||
const [pagination, setPagination] = useState<PaginationObject | null>(null);
|
||||
const [processInstanceFilters, setProcessInstanceFilters] = useState({});
|
||||
|
||||
|
@ -125,6 +138,17 @@ export default function ProcessInstanceListTable({
|
|||
const [processInstanceReportSelection, setProcessInstanceReportSelection] =
|
||||
useState<ProcessInstanceReport | null>(null);
|
||||
|
||||
const [availableReportColumns, setAvailableReportColumns] = useState<
|
||||
ReportColumn[]
|
||||
>([]);
|
||||
const [processInstanceReportJustSaved, setProcessInstanceReportJustSaved] =
|
||||
useState<string | null>(null);
|
||||
const [showReportColumnForm, setShowReportColumnForm] =
|
||||
useState<boolean>(false);
|
||||
const [reportColumnToOperateOn, setReportColumnToOperateOn] =
|
||||
useState<ReportColumnForEditing | null>(null);
|
||||
const [reportColumnFormMode, setReportColumnFormMode] = useState<string>('');
|
||||
|
||||
const dateParametersToAlwaysFilterBy: dateParameters = useMemo(() => {
|
||||
return {
|
||||
start_from: [setStartFromDate, setStartFromTime],
|
||||
|
@ -155,16 +179,12 @@ export default function ProcessInstanceListTable({
|
|||
function setProcessInstancesFromResult(result: any) {
|
||||
const processInstancesFromApi = result.results;
|
||||
setProcessInstances(processInstancesFromApi);
|
||||
setReportMetadata(result.report_metadata);
|
||||
setPagination(result.pagination);
|
||||
setProcessInstanceFilters(result.filters);
|
||||
|
||||
// TODO: need to iron out this interaction some more
|
||||
if (result.report_identifier !== 'default') {
|
||||
setProcessInstanceReportSelection({
|
||||
id: result.report_identifier,
|
||||
display_name: result.report_identifier,
|
||||
});
|
||||
setReportMetadata(result.report.report_metadata);
|
||||
if (result.report.id) {
|
||||
setProcessInstanceReportSelection(result.report);
|
||||
}
|
||||
}
|
||||
function getProcessInstances() {
|
||||
|
@ -186,14 +206,10 @@ export default function ProcessInstanceListTable({
|
|||
queryParamString += `&user_filter=${userAppliedFilter}`;
|
||||
}
|
||||
|
||||
let reportIdentifierToUse: any = reportIdentifier;
|
||||
|
||||
if (!reportIdentifierToUse) {
|
||||
reportIdentifierToUse = searchParams.get('report_identifier');
|
||||
}
|
||||
|
||||
if (reportIdentifierToUse) {
|
||||
queryParamString += `&report_identifier=${reportIdentifierToUse}`;
|
||||
if (searchParams.get('report_id')) {
|
||||
queryParamString += `&report_id=${searchParams.get('report_id')}`;
|
||||
} else if (reportIdentifier) {
|
||||
queryParamString += `&report_identifier=${reportIdentifier}`;
|
||||
}
|
||||
|
||||
Object.keys(dateParametersToAlwaysFilterBy).forEach(
|
||||
|
@ -349,6 +365,27 @@ export default function ProcessInstanceListTable({
|
|||
processModelAvailableItems,
|
||||
]);
|
||||
|
||||
const processInstanceReportSaveTag = () => {
|
||||
if (processInstanceReportJustSaved) {
|
||||
let titleOperation = 'Updated';
|
||||
if (processInstanceReportJustSaved === 'new') {
|
||||
titleOperation = 'Created';
|
||||
}
|
||||
return (
|
||||
<InlineNotification
|
||||
title={`Perspective ${titleOperation}:`}
|
||||
subtitle={`'${
|
||||
processInstanceReportSelection
|
||||
? processInstanceReportSelection.identifier
|
||||
: ''
|
||||
}'`}
|
||||
kind="success"
|
||||
/>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
// does the comparison, but also returns false if either argument
|
||||
// is not truthy and therefore not comparable.
|
||||
const isTrueComparison = (param1: any, operation: any, param2: any) => {
|
||||
|
@ -366,16 +403,8 @@ export default function ProcessInstanceListTable({
|
|||
}
|
||||
};
|
||||
|
||||
const applyFilter = (event: any) => {
|
||||
event.preventDefault();
|
||||
const { page, perPage } = getPageInfoFromSearchParams(
|
||||
searchParams,
|
||||
undefined,
|
||||
undefined,
|
||||
paginationQueryParamPrefix
|
||||
);
|
||||
let queryParamString = `per_page=${perPage}&page=${page}&user_filter=true`;
|
||||
|
||||
// TODO: after factoring this out page hangs when invalid date ranges and applying the filter
|
||||
const calculateStartAndEndSeconds = () => {
|
||||
const startFromSeconds = convertDateAndTimeStringsToSeconds(
|
||||
startFromDate,
|
||||
startFromTime || '00:00:00'
|
||||
|
@ -392,28 +421,59 @@ export default function ProcessInstanceListTable({
|
|||
endToDate,
|
||||
endToTime || '00:00:00'
|
||||
);
|
||||
let valid = true;
|
||||
if (isTrueComparison(startFromSeconds, '>', startToSeconds)) {
|
||||
setErrorMessage({
|
||||
message: '"Start date from" cannot be after "start date to"',
|
||||
});
|
||||
return;
|
||||
valid = false;
|
||||
}
|
||||
if (isTrueComparison(endFromSeconds, '>', endToSeconds)) {
|
||||
setErrorMessage({
|
||||
message: '"End date from" cannot be after "end date to"',
|
||||
});
|
||||
return;
|
||||
valid = false;
|
||||
}
|
||||
if (isTrueComparison(startFromSeconds, '>', endFromSeconds)) {
|
||||
setErrorMessage({
|
||||
message: '"Start date from" cannot be after "end date from"',
|
||||
});
|
||||
return;
|
||||
valid = false;
|
||||
}
|
||||
if (isTrueComparison(startToSeconds, '>', endToSeconds)) {
|
||||
setErrorMessage({
|
||||
message: '"Start date to" cannot be after "end date to"',
|
||||
});
|
||||
valid = false;
|
||||
}
|
||||
|
||||
return {
|
||||
valid,
|
||||
startFromSeconds,
|
||||
startToSeconds,
|
||||
endFromSeconds,
|
||||
endToSeconds,
|
||||
};
|
||||
};
|
||||
|
||||
const applyFilter = (event: any) => {
|
||||
event.preventDefault();
|
||||
const { page, perPage } = getPageInfoFromSearchParams(
|
||||
searchParams,
|
||||
undefined,
|
||||
undefined,
|
||||
paginationQueryParamPrefix
|
||||
);
|
||||
let queryParamString = `per_page=${perPage}&page=${page}&user_filter=true`;
|
||||
const {
|
||||
valid,
|
||||
startFromSeconds,
|
||||
startToSeconds,
|
||||
endFromSeconds,
|
||||
endToSeconds,
|
||||
} = calculateStartAndEndSeconds();
|
||||
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -438,10 +498,11 @@ export default function ProcessInstanceListTable({
|
|||
}
|
||||
|
||||
if (processInstanceReportSelection) {
|
||||
queryParamString += `&report_identifier=${processInstanceReportSelection.id}`;
|
||||
queryParamString += `&report_id=${processInstanceReportSelection.id}`;
|
||||
}
|
||||
|
||||
setErrorMessage(null);
|
||||
setProcessInstanceReportJustSaved(null);
|
||||
navigate(`/admin/process-instances?${queryParamString}`);
|
||||
};
|
||||
|
||||
|
@ -529,12 +590,360 @@ export default function ProcessInstanceListTable({
|
|||
setEndToTime('');
|
||||
};
|
||||
|
||||
const processInstanceReportDidChange = (selection: any, mode?: string) => {
|
||||
clearFilters();
|
||||
const selectedReport = selection.selectedItem;
|
||||
setProcessInstanceReportSelection(selectedReport);
|
||||
|
||||
let queryParamString = '';
|
||||
if (selectedReport) {
|
||||
queryParamString = `?report_id=${selectedReport.id}`;
|
||||
}
|
||||
|
||||
setErrorMessage(null);
|
||||
setProcessInstanceReportJustSaved(mode || null);
|
||||
navigate(`/admin/process-instances${queryParamString}`);
|
||||
};
|
||||
|
||||
const reportColumns = () => {
|
||||
return (reportMetadata as any).columns;
|
||||
};
|
||||
|
||||
const reportColumnAccessors = () => {
|
||||
return reportColumns().map((reportColumn: ReportColumn) => {
|
||||
return reportColumn.accessor;
|
||||
});
|
||||
};
|
||||
|
||||
// TODO onSuccess reload/select the new report in the report search
|
||||
const onSaveReportSuccess = (result: any, mode: string) => {
|
||||
processInstanceReportDidChange(
|
||||
{
|
||||
selectedItem: result,
|
||||
},
|
||||
mode
|
||||
);
|
||||
};
|
||||
|
||||
const saveAsReportComponent = () => {
|
||||
const {
|
||||
valid,
|
||||
startFromSeconds,
|
||||
startToSeconds,
|
||||
endFromSeconds,
|
||||
endToSeconds,
|
||||
} = calculateStartAndEndSeconds();
|
||||
|
||||
if (!valid || !reportMetadata) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<ProcessInstanceListSaveAsReport
|
||||
onSuccess={onSaveReportSuccess}
|
||||
buttonClassName="button-white-background narrow-button"
|
||||
columnArray={reportColumns()}
|
||||
orderBy=""
|
||||
buttonText="Save"
|
||||
processModelSelection={processModelSelection}
|
||||
processStatusSelection={processStatusSelection}
|
||||
processInstanceReportSelection={processInstanceReportSelection}
|
||||
reportMetadata={reportMetadata}
|
||||
startFromSeconds={startFromSeconds}
|
||||
startToSeconds={startToSeconds}
|
||||
endFromSeconds={endFromSeconds}
|
||||
endToSeconds={endToSeconds}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const removeColumn = (reportColumn: ReportColumn) => {
|
||||
if (reportMetadata) {
|
||||
const reportMetadataCopy = { ...reportMetadata };
|
||||
const newColumns = reportColumns().filter(
|
||||
(rc: ReportColumn) => rc.accessor !== reportColumn.accessor
|
||||
);
|
||||
Object.assign(reportMetadataCopy, { columns: newColumns });
|
||||
setReportMetadata(reportMetadataCopy);
|
||||
}
|
||||
};
|
||||
|
||||
const handleColumnFormClose = () => {
|
||||
setShowReportColumnForm(false);
|
||||
setReportColumnFormMode('');
|
||||
setReportColumnToOperateOn(null);
|
||||
};
|
||||
|
||||
const getFilterByFromReportMetadata = (reportColumnAccessor: string) => {
|
||||
if (reportMetadata) {
|
||||
return reportMetadata.filter_by.find((reportFilter: ReportFilter) => {
|
||||
return reportColumnAccessor === reportFilter.field_name;
|
||||
});
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const getNewFiltersFromReportForEditing = (
|
||||
reportColumnForEditing: ReportColumnForEditing
|
||||
) => {
|
||||
if (!reportMetadata) {
|
||||
return null;
|
||||
}
|
||||
const reportMetadataCopy = { ...reportMetadata };
|
||||
let newReportFilters = reportMetadataCopy.filter_by;
|
||||
if (reportColumnForEditing.filterable) {
|
||||
const newReportFilter: ReportFilter = {
|
||||
field_name: reportColumnForEditing.accessor,
|
||||
field_value: reportColumnForEditing.filter_field_value,
|
||||
operator: reportColumnForEditing.filter_operator || 'equals',
|
||||
};
|
||||
const existingReportFilter = getFilterByFromReportMetadata(
|
||||
reportColumnForEditing.accessor
|
||||
);
|
||||
if (existingReportFilter) {
|
||||
const existingReportFilterIndex =
|
||||
reportMetadataCopy.filter_by.indexOf(existingReportFilter);
|
||||
if (reportColumnForEditing.filter_field_value) {
|
||||
newReportFilters[existingReportFilterIndex] = newReportFilter;
|
||||
} else {
|
||||
newReportFilters.splice(existingReportFilterIndex, 1);
|
||||
}
|
||||
} else if (reportColumnForEditing.filter_field_value) {
|
||||
newReportFilters = newReportFilters.concat([newReportFilter]);
|
||||
}
|
||||
}
|
||||
return newReportFilters;
|
||||
};
|
||||
|
||||
const handleUpdateReportColumn = () => {
|
||||
if (reportColumnToOperateOn && reportMetadata) {
|
||||
const reportMetadataCopy = { ...reportMetadata };
|
||||
let newReportColumns = null;
|
||||
if (reportColumnFormMode === 'new') {
|
||||
newReportColumns = reportColumns().concat([reportColumnToOperateOn]);
|
||||
} else {
|
||||
newReportColumns = reportColumns().map((rc: ReportColumn) => {
|
||||
if (rc.accessor === reportColumnToOperateOn.accessor) {
|
||||
return reportColumnToOperateOn;
|
||||
}
|
||||
return rc;
|
||||
});
|
||||
}
|
||||
Object.assign(reportMetadataCopy, {
|
||||
columns: newReportColumns,
|
||||
filter_by: getNewFiltersFromReportForEditing(reportColumnToOperateOn),
|
||||
});
|
||||
setReportMetadata(reportMetadataCopy);
|
||||
setReportColumnToOperateOn(null);
|
||||
setShowReportColumnForm(false);
|
||||
setShowReportColumnForm(false);
|
||||
}
|
||||
};
|
||||
|
||||
const reportColumnToReportColumnForEditing = (reportColumn: ReportColumn) => {
|
||||
const reportColumnForEditing: ReportColumnForEditing = Object.assign(
|
||||
reportColumn,
|
||||
{ filter_field_value: '', filter_operator: '' }
|
||||
);
|
||||
const reportFilter = getFilterByFromReportMetadata(
|
||||
reportColumnForEditing.accessor
|
||||
);
|
||||
if (reportFilter) {
|
||||
reportColumnForEditing.filter_field_value = reportFilter.field_value;
|
||||
reportColumnForEditing.filter_operator =
|
||||
reportFilter.operator || 'equals';
|
||||
}
|
||||
return reportColumnForEditing;
|
||||
};
|
||||
|
||||
const updateReportColumn = (event: any) => {
|
||||
const reportColumnForEditing = reportColumnToReportColumnForEditing(
|
||||
event.selectedItem
|
||||
);
|
||||
setReportColumnToOperateOn(reportColumnForEditing);
|
||||
};
|
||||
|
||||
// options includes item and inputValue
|
||||
const shouldFilterReportColumn = (options: any) => {
|
||||
const reportColumn: ReportColumn = options.item;
|
||||
const { inputValue } = options;
|
||||
return (
|
||||
!reportColumnAccessors().includes(reportColumn.accessor) &&
|
||||
(reportColumn.accessor || '')
|
||||
.toLowerCase()
|
||||
.includes((inputValue || '').toLowerCase())
|
||||
);
|
||||
};
|
||||
|
||||
const setReportColumnConditionValue = (event: any) => {
|
||||
if (reportColumnToOperateOn) {
|
||||
const reportColumnToOperateOnCopy = {
|
||||
...reportColumnToOperateOn,
|
||||
};
|
||||
reportColumnToOperateOnCopy.filter_field_value = event.target.value;
|
||||
setReportColumnToOperateOn(reportColumnToOperateOnCopy);
|
||||
}
|
||||
};
|
||||
|
||||
const reportColumnForm = () => {
|
||||
if (reportColumnFormMode === '') {
|
||||
return null;
|
||||
}
|
||||
const formElements = [
|
||||
<TextInput
|
||||
id="report-column-display-name"
|
||||
name="report-column-display-name"
|
||||
labelText="Display Name"
|
||||
disabled={!reportColumnToOperateOn}
|
||||
value={reportColumnToOperateOn ? reportColumnToOperateOn.Header : ''}
|
||||
onChange={(event: any) => {
|
||||
if (reportColumnToOperateOn) {
|
||||
const reportColumnToOperateOnCopy = {
|
||||
...reportColumnToOperateOn,
|
||||
};
|
||||
reportColumnToOperateOnCopy.Header = event.target.value;
|
||||
setReportColumnToOperateOn(reportColumnToOperateOnCopy);
|
||||
}
|
||||
}}
|
||||
/>,
|
||||
];
|
||||
if (reportColumnToOperateOn && reportColumnToOperateOn.filterable) {
|
||||
formElements.push(
|
||||
<TextInput
|
||||
id="report-column-condition-value"
|
||||
name="report-column-condition-value"
|
||||
labelText="Condition Value"
|
||||
value={
|
||||
reportColumnToOperateOn
|
||||
? reportColumnToOperateOn.filter_field_value
|
||||
: ''
|
||||
}
|
||||
onChange={setReportColumnConditionValue}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (reportColumnFormMode === 'new') {
|
||||
formElements.push(
|
||||
<ComboBox
|
||||
onChange={updateReportColumn}
|
||||
className="combo-box-in-modal"
|
||||
id="report-column-selection"
|
||||
data-qa="report-column-selection"
|
||||
data-modal-primary-focus
|
||||
items={availableReportColumns}
|
||||
itemToString={(reportColumn: ReportColumn) => {
|
||||
if (reportColumn) {
|
||||
return reportColumn.accessor;
|
||||
}
|
||||
return null;
|
||||
}}
|
||||
shouldFilterItem={shouldFilterReportColumn}
|
||||
placeholder="Choose a report column"
|
||||
titleText="Report Column"
|
||||
/>
|
||||
);
|
||||
}
|
||||
const modalHeading =
|
||||
reportColumnFormMode === 'new'
|
||||
? 'Add Column'
|
||||
: `Edit ${
|
||||
reportColumnToOperateOn ? reportColumnToOperateOn.accessor : ''
|
||||
} column`;
|
||||
return (
|
||||
<Modal
|
||||
open={showReportColumnForm}
|
||||
modalHeading={modalHeading}
|
||||
primaryButtonText="Save"
|
||||
primaryButtonDisabled={!reportColumnToOperateOn}
|
||||
onRequestSubmit={handleUpdateReportColumn}
|
||||
onRequestClose={handleColumnFormClose}
|
||||
hasScrollingContent
|
||||
>
|
||||
{formElements}
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
const columnSelections = () => {
|
||||
if (reportColumns()) {
|
||||
const tags: any = [];
|
||||
|
||||
(reportColumns() as any).forEach((reportColumn: ReportColumn) => {
|
||||
const reportColumnForEditing =
|
||||
reportColumnToReportColumnForEditing(reportColumn);
|
||||
|
||||
let tagType = 'cool-gray';
|
||||
let tagTypeClass = '';
|
||||
if (reportColumnForEditing.filterable) {
|
||||
tagType = 'green';
|
||||
tagTypeClass = 'tag-type-green';
|
||||
}
|
||||
let reportColumnLabel = reportColumnForEditing.Header;
|
||||
if (reportColumnForEditing.filter_field_value) {
|
||||
reportColumnLabel = `${reportColumnLabel}=${reportColumnForEditing.filter_field_value}`;
|
||||
}
|
||||
tags.push(
|
||||
<Tag type={tagType} size="sm">
|
||||
<Button
|
||||
kind="ghost"
|
||||
size="sm"
|
||||
className={`button-tag-icon ${tagTypeClass}`}
|
||||
title={`Edit ${reportColumnForEditing.accessor}`}
|
||||
onClick={() => {
|
||||
setReportColumnToOperateOn(reportColumnForEditing);
|
||||
setShowReportColumnForm(true);
|
||||
setReportColumnFormMode('edit');
|
||||
}}
|
||||
>
|
||||
{reportColumnLabel}
|
||||
</Button>
|
||||
<Button
|
||||
data-qa="remove-report-column"
|
||||
renderIcon={Close}
|
||||
iconDescription="Remove Column"
|
||||
className={`button-tag-icon ${tagTypeClass}`}
|
||||
hasIconOnly
|
||||
size="sm"
|
||||
kind="ghost"
|
||||
onClick={() => removeColumn(reportColumnForEditing)}
|
||||
/>
|
||||
</Tag>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<Stack orientation="horizontal">
|
||||
{tags}
|
||||
<Button
|
||||
data-qa="add-column-button"
|
||||
renderIcon={AddAlt}
|
||||
iconDescription="Filter Options"
|
||||
className="with-tiny-top-margin"
|
||||
kind="ghost"
|
||||
hasIconOnly
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setShowReportColumnForm(true);
|
||||
setReportColumnFormMode('new');
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const filterOptions = () => {
|
||||
if (!showFilterOptions) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<Grid fullWidth className="with-bottom-margin">
|
||||
<Column md={8} lg={16} sm={4}>
|
||||
<FormLabel>Columns</FormLabel>
|
||||
<br />
|
||||
{columnSelections()}
|
||||
</Column>
|
||||
</Grid>
|
||||
<Grid fullWidth className="with-bottom-margin">
|
||||
<Column md={8}>
|
||||
<ProcessModelSearch
|
||||
|
@ -598,11 +1007,11 @@ export default function ProcessInstanceListTable({
|
|||
</Column>
|
||||
</Grid>
|
||||
<Grid fullWidth className="with-bottom-margin">
|
||||
<Column md={4}>
|
||||
<Column sm={4} md={4} lg={8}>
|
||||
<ButtonSet>
|
||||
<Button
|
||||
kind=""
|
||||
className="button-white-background"
|
||||
className="button-white-background narrow-button"
|
||||
onClick={clearFilters}
|
||||
>
|
||||
Clear
|
||||
|
@ -611,11 +1020,15 @@ export default function ProcessInstanceListTable({
|
|||
kind="secondary"
|
||||
onClick={applyFilter}
|
||||
data-qa="filter-button"
|
||||
className="narrow-button"
|
||||
>
|
||||
Filter
|
||||
</Button>
|
||||
</ButtonSet>
|
||||
</Column>
|
||||
<Column sm={4} md={4} lg={8}>
|
||||
{saveAsReportComponent()}
|
||||
</Column>
|
||||
</Grid>
|
||||
</>
|
||||
);
|
||||
|
@ -635,7 +1048,7 @@ export default function ProcessInstanceListTable({
|
|||
const getHeaderLabel = (header: string) => {
|
||||
return headerLabels[header] ?? header;
|
||||
};
|
||||
const headers = (reportMetadata as any).columns.map((column: any) => {
|
||||
const headers = reportColumns().map((column: any) => {
|
||||
// return <th>{getHeaderLabel((column as any).Header)}</th>;
|
||||
return getHeaderLabel((column as any).Header);
|
||||
});
|
||||
|
@ -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) => {
|
||||
return convertSecondsToFormattedDateTime(seconds) || '-';
|
||||
};
|
||||
|
@ -688,15 +1085,16 @@ export default function ProcessInstanceListTable({
|
|||
return value;
|
||||
};
|
||||
|
||||
const columnFormatters: Record<string, any> = {
|
||||
const reportColumnFormatters: Record<string, any> = {
|
||||
id: formatProcessInstanceId,
|
||||
process_model_identifier: formatProcessModelIdentifier,
|
||||
process_model_display_name: formatProcessModelDisplayName,
|
||||
process_model_display_name: FormatProcessModelDisplayName,
|
||||
start_in_seconds: formatSecondsForDisplay,
|
||||
end_in_seconds: formatSecondsForDisplay,
|
||||
};
|
||||
const formattedColumn = (row: any, column: any) => {
|
||||
const formatter = columnFormatters[column.accessor] ?? defaultFormatter;
|
||||
const formatter =
|
||||
reportColumnFormatters[column.accessor] ?? defaultFormatter;
|
||||
const value = row[column.accessor];
|
||||
if (column.accessor === 'status') {
|
||||
return (
|
||||
|
@ -709,7 +1107,7 @@ export default function ProcessInstanceListTable({
|
|||
};
|
||||
|
||||
const rows = processInstances.map((row: any) => {
|
||||
const currentRow = (reportMetadata as any).columns.map((column: any) => {
|
||||
const currentRow = reportColumns().map((column: any) => {
|
||||
return formattedColumn(row, column);
|
||||
});
|
||||
return <tr key={row.id}>{currentRow}</tr>;
|
||||
|
@ -736,29 +1134,26 @@ export default function ProcessInstanceListTable({
|
|||
|
||||
const toggleShowFilterOptions = () => {
|
||||
setShowFilterOptions(!showFilterOptions);
|
||||
};
|
||||
|
||||
const processInstanceReportDidChange = (selection: any) => {
|
||||
clearFilters();
|
||||
|
||||
const selectedReport = selection.selectedItem;
|
||||
setProcessInstanceReportSelection(selectedReport);
|
||||
|
||||
const queryParamString = selectedReport
|
||||
? `&report_identifier=${selectedReport.id}`
|
||||
: '';
|
||||
|
||||
setErrorMessage(null);
|
||||
navigate(`/admin/process-instances?${queryParamString}`);
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-instances/reports/columns`,
|
||||
successCallback: setAvailableReportColumns,
|
||||
});
|
||||
};
|
||||
|
||||
const reportSearchComponent = () => {
|
||||
if (showReports) {
|
||||
const columns = [
|
||||
<Column sm={2} md={4} lg={7}>
|
||||
<ProcessInstanceReportSearch
|
||||
onChange={processInstanceReportDidChange}
|
||||
selectedItem={processInstanceReportSelection}
|
||||
/>
|
||||
</Column>,
|
||||
];
|
||||
return (
|
||||
<ProcessInstanceReportSearch
|
||||
onChange={processInstanceReportDidChange}
|
||||
selectedItem={processInstanceReportSelection}
|
||||
/>
|
||||
<Grid className="with-tiny-bottom-margin" fullWidth>
|
||||
{columns}
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
|
@ -806,6 +1201,8 @@ export default function ProcessInstanceListTable({
|
|||
}
|
||||
return (
|
||||
<>
|
||||
{reportColumnForm()}
|
||||
{processInstanceReportSaveTag()}
|
||||
{filterComponent()}
|
||||
{reportSearchComponent()}
|
||||
<PaginationForTable
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
import { useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import {
|
||||
ComboBox,
|
||||
Stack,
|
||||
FormLabel,
|
||||
// @ts-ignore
|
||||
} from '@carbon/react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { truncateString } from '../helpers';
|
||||
import { ProcessInstanceReport } from '../interfaces';
|
||||
import HttpService from '../services/HttpService';
|
||||
|
@ -22,52 +25,61 @@ export default function ProcessInstanceReportSearch({
|
|||
ProcessInstanceReport[] | null
|
||||
>(null);
|
||||
|
||||
function setProcessInstanceReportsFromResult(result: any) {
|
||||
const processInstanceReportsFromApi = result.map((item: any) => {
|
||||
return { id: item.identifier, display_name: item.identifier };
|
||||
});
|
||||
setProcessInstanceReports(processInstanceReportsFromApi);
|
||||
}
|
||||
const [searchParams] = useSearchParams();
|
||||
const reportId = searchParams.get('report_id');
|
||||
|
||||
useEffect(() => {
|
||||
function setProcessInstanceReportsFromResult(
|
||||
result: ProcessInstanceReport[]
|
||||
) {
|
||||
setProcessInstanceReports(result);
|
||||
}
|
||||
|
||||
if (processInstanceReports === null) {
|
||||
setProcessInstanceReports([]);
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-instances/reports`,
|
||||
successCallback: setProcessInstanceReportsFromResult,
|
||||
});
|
||||
}
|
||||
}, [reportId]);
|
||||
|
||||
const reportSelectionString = (
|
||||
processInstanceReport: ProcessInstanceReport
|
||||
) => {
|
||||
return `${truncateString(processInstanceReport.identifier, 20)} (Id: ${
|
||||
processInstanceReport.id
|
||||
})`;
|
||||
};
|
||||
|
||||
const shouldFilterProcessInstanceReport = (options: any) => {
|
||||
const processInstanceReport: ProcessInstanceReport = options.item;
|
||||
const { inputValue } = options;
|
||||
return `${processInstanceReport.id} (${processInstanceReport.display_name})`.includes(
|
||||
inputValue
|
||||
);
|
||||
return reportSelectionString(processInstanceReport).includes(inputValue);
|
||||
};
|
||||
|
||||
const reportsAvailable = () => {
|
||||
return processInstanceReports && processInstanceReports.length > 0;
|
||||
};
|
||||
|
||||
return reportsAvailable() ? (
|
||||
<ComboBox
|
||||
onChange={onChange}
|
||||
id="process-instance-report-select"
|
||||
data-qa="process-instance-report-selection"
|
||||
items={processInstanceReports}
|
||||
itemToString={(processInstanceReport: ProcessInstanceReport) => {
|
||||
if (processInstanceReport) {
|
||||
return `${processInstanceReport.id} (${truncateString(
|
||||
processInstanceReport.display_name,
|
||||
20
|
||||
)})`;
|
||||
}
|
||||
return null;
|
||||
}}
|
||||
shouldFilterItem={shouldFilterProcessInstanceReport}
|
||||
placeholder="Choose a process instance perspective"
|
||||
titleText={titleText}
|
||||
selectedItem={selectedItem}
|
||||
/>
|
||||
) : null;
|
||||
if (reportsAvailable()) {
|
||||
return (
|
||||
<Stack orientation="horizontal" gap={2}>
|
||||
<FormLabel className="with-top-margin">{titleText}</FormLabel>
|
||||
<ComboBox
|
||||
onChange={onChange}
|
||||
id="process-instance-report-select"
|
||||
data-qa="process-instance-report-selection"
|
||||
items={processInstanceReports}
|
||||
itemToString={(processInstanceReport: ProcessInstanceReport) => {
|
||||
if (processInstanceReport) {
|
||||
return reportSelectionString(processInstanceReport);
|
||||
}
|
||||
return null;
|
||||
}}
|
||||
shouldFilterItem={shouldFilterProcessInstanceReport}
|
||||
placeholder="Choose a process instance perspective"
|
||||
selectedItem={selectedItem}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -1,10 +1,20 @@
|
|||
import { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
Button,
|
||||
ButtonSet,
|
||||
Form,
|
||||
Stack,
|
||||
TextInput,
|
||||
Grid,
|
||||
Column,
|
||||
// @ts-ignore
|
||||
} from '@carbon/react';
|
||||
// @ts-ignore
|
||||
import { Button, ButtonSet, Form, Stack, TextInput } from '@carbon/react';
|
||||
import { AddAlt, TrashCan } from '@carbon/icons-react';
|
||||
import { modifyProcessIdentifierForPathParam, slugifyString } from '../helpers';
|
||||
import HttpService from '../services/HttpService';
|
||||
import { ProcessModel } from '../interfaces';
|
||||
import { MetadataExtractionPath, ProcessModel } from '../interfaces';
|
||||
|
||||
type OwnProps = {
|
||||
mode: string;
|
||||
|
@ -23,6 +33,7 @@ export default function ProcessModelForm({
|
|||
const [idHasBeenUpdatedByUser, setIdHasBeenUpdatedByUser] =
|
||||
useState<boolean>(false);
|
||||
const [displayNameInvalid, setDisplayNameInvalid] = useState<boolean>(false);
|
||||
useState<boolean>(false);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const navigateToProcessModel = (result: ProcessModel) => {
|
||||
|
@ -64,6 +75,7 @@ export default function ProcessModelForm({
|
|||
const postBody = {
|
||||
display_name: processModel.display_name,
|
||||
description: processModel.description,
|
||||
metadata_extraction_paths: processModel.metadata_extraction_paths,
|
||||
};
|
||||
if (mode === 'new') {
|
||||
Object.assign(postBody, {
|
||||
|
@ -87,6 +99,80 @@ export default function ProcessModelForm({
|
|||
setProcessModel(processModelToCopy);
|
||||
};
|
||||
|
||||
const metadataExtractionPathForm = (
|
||||
index: number,
|
||||
metadataExtractionPath: MetadataExtractionPath
|
||||
) => {
|
||||
return (
|
||||
<Grid>
|
||||
<Column md={3} lg={7} sm={1}>
|
||||
<TextInput
|
||||
id={`process-model-metadata-extraction-path-key-${index}`}
|
||||
labelText="Extraction Key"
|
||||
value={metadataExtractionPath.key}
|
||||
onChange={(event: any) => {
|
||||
const cep: MetadataExtractionPath[] =
|
||||
processModel.metadata_extraction_paths || [];
|
||||
const newMeta = { ...metadataExtractionPath };
|
||||
newMeta.key = event.target.value;
|
||||
cep[index] = newMeta;
|
||||
updateProcessModel({ metadata_extraction_paths: cep });
|
||||
}}
|
||||
/>
|
||||
</Column>
|
||||
<Column md={4} lg={8} sm={2}>
|
||||
<TextInput
|
||||
id={`process-model-metadata-extraction-path-${index}`}
|
||||
labelText="Extraction Path"
|
||||
value={metadataExtractionPath.path}
|
||||
onChange={(event: any) => {
|
||||
const cep: MetadataExtractionPath[] =
|
||||
processModel.metadata_extraction_paths || [];
|
||||
const newMeta = { ...metadataExtractionPath };
|
||||
newMeta.path = event.target.value;
|
||||
cep[index] = newMeta;
|
||||
updateProcessModel({ metadata_extraction_paths: cep });
|
||||
}}
|
||||
/>
|
||||
</Column>
|
||||
<Column md={1} lg={1} sm={1}>
|
||||
<Button
|
||||
kind="ghost"
|
||||
renderIcon={TrashCan}
|
||||
iconDescription="Remove Key"
|
||||
hasIconOnly
|
||||
size="lg"
|
||||
className="with-extra-top-margin"
|
||||
onClick={() => {
|
||||
const cep: MetadataExtractionPath[] =
|
||||
processModel.metadata_extraction_paths || [];
|
||||
cep.splice(index, 1);
|
||||
updateProcessModel({ metadata_extraction_paths: cep });
|
||||
}}
|
||||
/>
|
||||
</Column>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
const metadataExtractionPathFormArea = () => {
|
||||
if (processModel.metadata_extraction_paths) {
|
||||
return processModel.metadata_extraction_paths.map(
|
||||
(metadataExtractionPath: MetadataExtractionPath, index: number) => {
|
||||
return metadataExtractionPathForm(index, metadataExtractionPath);
|
||||
}
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const addBlankMetadataExtractionPath = () => {
|
||||
const cep: MetadataExtractionPath[] =
|
||||
processModel.metadata_extraction_paths || [];
|
||||
cep.push({ key: '', path: '' });
|
||||
updateProcessModel({ metadata_extraction_paths: cep });
|
||||
};
|
||||
|
||||
const onDisplayNameChanged = (newDisplayName: any) => {
|
||||
setDisplayNameInvalid(false);
|
||||
const updateDict = { display_name: newDisplayName };
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ export const PROCESS_STATUSES = [
|
|||
'complete',
|
||||
'error',
|
||||
'suspended',
|
||||
'terminated',
|
||||
];
|
||||
|
||||
// with time: yyyy-MM-dd HH:mm:ss
|
||||
|
|
|
@ -69,6 +69,24 @@ h2 {
|
|||
color: black;
|
||||
}
|
||||
|
||||
/* match normal link colors */
|
||||
.cds--btn--ghost.button-link {
|
||||
color: #0062fe;
|
||||
padding-left: 0;
|
||||
}
|
||||
.cds--btn--ghost.button-link:visited {
|
||||
color: #0062fe;
|
||||
padding-left: 0;
|
||||
}
|
||||
.cds--btn--ghost.button-link:hover {
|
||||
color: #0062fe;
|
||||
padding-left: 0;
|
||||
}
|
||||
.cds--btn--ghost.button-link:visited:hover {
|
||||
color: #0062fe;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.cds--header__global .cds--btn--primary {
|
||||
background-color: #161616
|
||||
}
|
||||
|
@ -151,10 +169,22 @@ h1.with-icons {
|
|||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.with-extra-top-margin {
|
||||
margin-top: 1.3em;
|
||||
}
|
||||
|
||||
.with-tiny-top-margin {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.with-large-bottom-margin {
|
||||
margin-bottom: 3em;
|
||||
}
|
||||
|
||||
.with-tiny-bottom-margin {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.diagram-viewer-canvas {
|
||||
border:1px solid #000000;
|
||||
height:70vh;
|
||||
|
@ -297,3 +327,38 @@ td.actions-cell {
|
|||
text-align: right;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.cds--btn--ghost:not([disabled]) svg.red-icon {
|
||||
fill: red;
|
||||
}
|
||||
|
||||
.failure-string {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.cds--btn--ghost.cds--btn--sm.button-tag-icon {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
/* .no-wrap cds--label cds--label--inline cds--label--inline--md{ */
|
||||
.no-wrap .cds--label--inline{
|
||||
word-break: normal;
|
||||
}
|
||||
|
||||
.combo-box-in-modal {
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.cds--btn.narrow-button {
|
||||
max-width: 10rem;
|
||||
min-width: 5rem;
|
||||
word-break: normal;
|
||||
|
||||
}
|
||||
|
||||
.tag-type-green:hover {
|
||||
background-color: #00FF00;
|
||||
}
|
||||
|
||||
|
|
|
@ -38,11 +38,58 @@ export interface ProcessFile {
|
|||
export interface ProcessInstance {
|
||||
id: number;
|
||||
process_model_identifier: string;
|
||||
process_model_display_name: string;
|
||||
}
|
||||
|
||||
export interface MessageCorrelationProperties {
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
export interface MessageCorrelations {
|
||||
[key: string]: MessageCorrelationProperties;
|
||||
}
|
||||
|
||||
export interface MessageInstance {
|
||||
id: number;
|
||||
process_model_identifier: string;
|
||||
process_model_display_name: string;
|
||||
process_instance_id: number;
|
||||
message_identifier: string;
|
||||
message_type: string;
|
||||
failure_cause: string;
|
||||
status: string;
|
||||
created_at_in_seconds: number;
|
||||
message_correlations?: MessageCorrelations;
|
||||
}
|
||||
|
||||
export interface ReportFilter {
|
||||
field_name: string;
|
||||
field_value: string;
|
||||
operator?: string;
|
||||
}
|
||||
|
||||
export interface ReportColumn {
|
||||
Header: string;
|
||||
accessor: string;
|
||||
filterable: boolean;
|
||||
}
|
||||
|
||||
export interface ReportColumnForEditing extends ReportColumn {
|
||||
filter_field_value: string;
|
||||
filter_operator: string;
|
||||
}
|
||||
|
||||
export interface ReportMetadata {
|
||||
columns: ReportColumn[];
|
||||
filter_by: ReportFilter[];
|
||||
order_by: string[];
|
||||
}
|
||||
|
||||
export interface ProcessInstanceReport {
|
||||
id: string;
|
||||
display_name: string;
|
||||
id: number;
|
||||
identifier: string;
|
||||
name: string;
|
||||
report_metadata: ReportMetadata;
|
||||
}
|
||||
|
||||
export interface ProcessGroupLite {
|
||||
|
@ -50,6 +97,11 @@ export interface ProcessGroupLite {
|
|||
display_name: string;
|
||||
}
|
||||
|
||||
export interface MetadataExtractionPath {
|
||||
key: string;
|
||||
path: string;
|
||||
}
|
||||
|
||||
export interface ProcessModel {
|
||||
id: string;
|
||||
description: string;
|
||||
|
@ -57,6 +109,7 @@ export interface ProcessModel {
|
|||
primary_file_name: string;
|
||||
files: ProcessFile[];
|
||||
parent_groups?: ProcessGroupLite[];
|
||||
metadata_extraction_paths?: MetadataExtractionPath[];
|
||||
}
|
||||
|
||||
export interface ProcessGroup {
|
||||
|
|
|
@ -1,15 +1,19 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
// @ts-ignore
|
||||
import { Table } from '@carbon/react';
|
||||
import { ErrorOutline } from '@carbon/icons-react';
|
||||
// @ts-ignore
|
||||
import { Table, Modal, Button } from '@carbon/react';
|
||||
import { Link, useParams, useSearchParams } from 'react-router-dom';
|
||||
import PaginationForTable from '../components/PaginationForTable';
|
||||
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
|
||||
import {
|
||||
convertSecondsToFormattedDateString,
|
||||
convertSecondsToFormattedDateTime,
|
||||
getPageInfoFromSearchParams,
|
||||
modifyProcessIdentifierForPathParam,
|
||||
} from '../helpers';
|
||||
import HttpService from '../services/HttpService';
|
||||
import { FormatProcessModelDisplayName } from '../components/MiniComponents';
|
||||
import { MessageInstance } from '../interfaces';
|
||||
|
||||
export default function MessageInstanceList() {
|
||||
const params = useParams();
|
||||
|
@ -17,6 +21,9 @@ export default function MessageInstanceList() {
|
|||
const [messageIntances, setMessageInstances] = useState([]);
|
||||
const [pagination, setPagination] = useState(null);
|
||||
|
||||
const [messageInstanceForModal, setMessageInstanceForModal] =
|
||||
useState<MessageInstance | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const setMessageInstanceListFromResult = (result: any) => {
|
||||
setMessageInstances(result.results);
|
||||
|
@ -35,41 +42,89 @@ export default function MessageInstanceList() {
|
|||
});
|
||||
}, [searchParams, params]);
|
||||
|
||||
const buildTable = () => {
|
||||
// return null;
|
||||
const rows = messageIntances.map((row) => {
|
||||
const rowToUse = row as any;
|
||||
const handleCorrelationDisplayClose = () => {
|
||||
setMessageInstanceForModal(null);
|
||||
};
|
||||
|
||||
const correlationsDisplayModal = () => {
|
||||
if (messageInstanceForModal) {
|
||||
let failureCausePre = null;
|
||||
if (messageInstanceForModal.failure_cause) {
|
||||
failureCausePre = (
|
||||
<>
|
||||
<p className="failure-string">
|
||||
{messageInstanceForModal.failure_cause}
|
||||
</p>
|
||||
<br />
|
||||
</>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<tr key={rowToUse.id}>
|
||||
<td>{rowToUse.id}</td>
|
||||
<td>
|
||||
<Link
|
||||
data-qa="process-model-show-link"
|
||||
to={`/admin/process-models/${modifyProcessIdentifierForPathParam(
|
||||
rowToUse.process_model_identifier
|
||||
)}`}
|
||||
>
|
||||
{rowToUse.process_model_identifier}
|
||||
</Link>
|
||||
</td>
|
||||
<Modal
|
||||
open={!!messageInstanceForModal}
|
||||
passiveModal
|
||||
onRequestClose={handleCorrelationDisplayClose}
|
||||
modalHeading={`Message ${messageInstanceForModal.id} (${messageInstanceForModal.message_identifier}) ${messageInstanceForModal.message_type} data:`}
|
||||
modalLabel="Details"
|
||||
>
|
||||
{failureCausePre}
|
||||
<p>Correlations:</p>
|
||||
<pre>
|
||||
{JSON.stringify(
|
||||
messageInstanceForModal.message_correlations,
|
||||
null,
|
||||
2
|
||||
)}
|
||||
</pre>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const buildTable = () => {
|
||||
const rows = messageIntances.map((row: MessageInstance) => {
|
||||
let errorIcon = null;
|
||||
let errorTitle = null;
|
||||
if (row.failure_cause) {
|
||||
errorTitle = 'Instance has an error';
|
||||
errorIcon = (
|
||||
<>
|
||||
|
||||
<ErrorOutline className="red-icon" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<tr key={row.id}>
|
||||
<td>{row.id}</td>
|
||||
<td>{FormatProcessModelDisplayName(row)}</td>
|
||||
<td>
|
||||
<Link
|
||||
data-qa="process-instance-show-link"
|
||||
to={`/admin/process-models/${modifyProcessIdentifierForPathParam(
|
||||
rowToUse.process_model_identifier
|
||||
)}/process-instances/${rowToUse.process_instance_id}`}
|
||||
row.process_model_identifier
|
||||
)}/process-instances/${row.process_instance_id}`}
|
||||
>
|
||||
{rowToUse.process_instance_id}
|
||||
{row.process_instance_id}
|
||||
</Link>
|
||||
</td>
|
||||
<td>{rowToUse.message_identifier}</td>
|
||||
<td>{rowToUse.message_type}</td>
|
||||
<td>{rowToUse.failure_cause || '-'}</td>
|
||||
<td>{rowToUse.status}</td>
|
||||
<td>{row.message_identifier}</td>
|
||||
<td>{row.message_type}</td>
|
||||
<td>
|
||||
{convertSecondsToFormattedDateString(
|
||||
rowToUse.created_at_in_seconds
|
||||
)}
|
||||
<Button
|
||||
kind="ghost"
|
||||
className="button-link"
|
||||
onClick={() => setMessageInstanceForModal(row)}
|
||||
title={errorTitle}
|
||||
>
|
||||
View
|
||||
{errorIcon}
|
||||
</Button>
|
||||
</td>
|
||||
<td>{row.status}</td>
|
||||
<td>
|
||||
{convertSecondsToFormattedDateTime(row.created_at_in_seconds)}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
|
@ -78,12 +133,12 @@ export default function MessageInstanceList() {
|
|||
<Table striped bordered>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Instance Id</th>
|
||||
<th>Process Model</th>
|
||||
<th>Id</th>
|
||||
<th>Process</th>
|
||||
<th>Process Instance</th>
|
||||
<th>Message Model</th>
|
||||
<th>Name</th>
|
||||
<th>Type</th>
|
||||
<th>Failure Cause</th>
|
||||
<th>Details</th>
|
||||
<th>Status</th>
|
||||
<th>Created At</th>
|
||||
</tr>
|
||||
|
@ -121,6 +176,7 @@ export default function MessageInstanceList() {
|
|||
<>
|
||||
{breadcrumbElement}
|
||||
<h1>Messages</h1>
|
||||
{correlationsDisplayModal()}
|
||||
<PaginationForTable
|
||||
page={page}
|
||||
perPage={perPage}
|
||||
|
|
Loading…
Reference in New Issue