Merge remote-tracking branch 'origin/main' into feature/call_activity_selection
This commit is contained in:
commit
a9df1ed23f
|
@ -279,6 +279,8 @@ jobs:
|
||||||
# if: ${{ github.event_name != 'pull_request' }}
|
# if: ${{ github.event_name != 'pull_request' }}
|
||||||
# so just skip everything but main
|
# so just skip everything but main
|
||||||
if: github.ref_name == 'main'
|
if: github.ref_name == 'main'
|
||||||
|
with:
|
||||||
|
projectBaseDir: spiffworkflow-backend
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
"""empty message
|
"""empty message
|
||||||
|
|
||||||
Revision ID: 7d1662ea1227
|
Revision ID: 7cc9bdcc309f
|
||||||
Revises:
|
Revises:
|
||||||
Create Date: 2022-11-14 21:48:34.469311
|
Create Date: 2022-11-15 09:53:53.349712
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from alembic import op
|
from alembic import op
|
||||||
|
@ -10,7 +10,7 @@ import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
revision = '7d1662ea1227'
|
revision = '7cc9bdcc309f'
|
||||||
down_revision = None
|
down_revision = None
|
||||||
branch_labels = None
|
branch_labels = None
|
||||||
depends_on = None
|
depends_on = None
|
||||||
|
@ -68,15 +68,6 @@ def upgrade():
|
||||||
sa.Column('spiff_step', sa.Integer(), nullable=False),
|
sa.Column('spiff_step', sa.Integer(), nullable=False),
|
||||||
sa.PrimaryKeyConstraint('id')
|
sa.PrimaryKeyConstraint('id')
|
||||||
)
|
)
|
||||||
op.create_table('spiff_step_details',
|
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
|
||||||
sa.Column('process_instance_id', sa.Integer(), nullable=False),
|
|
||||||
sa.Column('spiff_step', sa.Integer(), nullable=False),
|
|
||||||
sa.Column('task_json', sa.JSON(), nullable=False),
|
|
||||||
sa.Column('timestamp', sa.DECIMAL(precision=17, scale=6), nullable=False),
|
|
||||||
sa.Column('completed_by_user_id', sa.Integer(), nullable=True),
|
|
||||||
sa.PrimaryKeyConstraint('id')
|
|
||||||
)
|
|
||||||
op.create_table('user',
|
op.create_table('user',
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
sa.Column('username', sa.String(length=255), nullable=False),
|
sa.Column('username', sa.String(length=255), nullable=False),
|
||||||
|
@ -176,6 +167,17 @@ def upgrade():
|
||||||
sa.PrimaryKeyConstraint('id'),
|
sa.PrimaryKeyConstraint('id'),
|
||||||
sa.UniqueConstraint('key')
|
sa.UniqueConstraint('key')
|
||||||
)
|
)
|
||||||
|
op.create_table('spiff_step_details',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('process_instance_id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('spiff_step', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('task_json', sa.JSON(), nullable=False),
|
||||||
|
sa.Column('timestamp', sa.DECIMAL(precision=17, scale=6), nullable=False),
|
||||||
|
sa.Column('completed_by_user_id', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('lane_assignment_id', sa.Integer(), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(['lane_assignment_id'], ['group.id'], ),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
op.create_table('user_group_assignment',
|
op.create_table('user_group_assignment',
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
sa.Column('user_id', sa.Integer(), nullable=False),
|
sa.Column('user_id', sa.Integer(), nullable=False),
|
||||||
|
@ -290,6 +292,7 @@ def downgrade():
|
||||||
op.drop_table('message_correlation')
|
op.drop_table('message_correlation')
|
||||||
op.drop_table('active_task')
|
op.drop_table('active_task')
|
||||||
op.drop_table('user_group_assignment')
|
op.drop_table('user_group_assignment')
|
||||||
|
op.drop_table('spiff_step_details')
|
||||||
op.drop_table('secret')
|
op.drop_table('secret')
|
||||||
op.drop_table('refresh_token')
|
op.drop_table('refresh_token')
|
||||||
op.drop_index(op.f('ix_process_instance_report_identifier'), table_name='process_instance_report')
|
op.drop_index(op.f('ix_process_instance_report_identifier'), table_name='process_instance_report')
|
||||||
|
@ -305,7 +308,6 @@ def downgrade():
|
||||||
op.drop_index(op.f('ix_message_correlation_property_identifier'), table_name='message_correlation_property')
|
op.drop_index(op.f('ix_message_correlation_property_identifier'), table_name='message_correlation_property')
|
||||||
op.drop_table('message_correlation_property')
|
op.drop_table('message_correlation_property')
|
||||||
op.drop_table('user')
|
op.drop_table('user')
|
||||||
op.drop_table('spiff_step_details')
|
|
||||||
op.drop_table('spiff_logging')
|
op.drop_table('spiff_logging')
|
||||||
op.drop_index(op.f('ix_spec_reference_cache_type'), table_name='spec_reference_cache')
|
op.drop_index(op.f('ix_spec_reference_cache_type'), table_name='spec_reference_cache')
|
||||||
op.drop_index(op.f('ix_spec_reference_cache_identifier'), table_name='spec_reference_cache')
|
op.drop_index(op.f('ix_spec_reference_cache_identifier'), table_name='spec_reference_cache')
|
|
@ -918,7 +918,7 @@ paths:
|
||||||
items:
|
items:
|
||||||
$ref: "#/components/schemas/Task"
|
$ref: "#/components/schemas/Task"
|
||||||
|
|
||||||
/tasks/for-processes-started-by-others:
|
/tasks/for-me:
|
||||||
parameters:
|
parameters:
|
||||||
- name: page
|
- name: page
|
||||||
in: query
|
in: query
|
||||||
|
@ -935,7 +935,36 @@ paths:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- Process Instances
|
- Process Instances
|
||||||
operationId: spiffworkflow_backend.routes.process_api_blueprint.task_list_for_processes_started_by_others
|
operationId: spiffworkflow_backend.routes.process_api_blueprint.task_list_for_me
|
||||||
|
summary: returns the list of tasks for given user's open process instances
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: list of tasks
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/Task"
|
||||||
|
|
||||||
|
/tasks/for-my-groups:
|
||||||
|
parameters:
|
||||||
|
- name: page
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
description: The page number to return. Defaults to page 1.
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
- name: per_page
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
description: The page number to return. Defaults to page 1.
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- Process Instances
|
||||||
|
operationId: spiffworkflow_backend.routes.process_api_blueprint.task_list_for_my_groups
|
||||||
summary: returns the list of tasks for given user's open process instances
|
summary: returns the list of tasks for given user's open process instances
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
|
|
|
@ -64,6 +64,7 @@ CONTENT_TYPES = {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(order=True)
|
@dataclass(order=True)
|
||||||
class File:
|
class File:
|
||||||
"""File."""
|
"""File."""
|
||||||
|
|
|
@ -59,7 +59,7 @@ class MessageInstanceModel(SpiffworkflowBaseDBModel):
|
||||||
updated_at_in_seconds: int = db.Column(db.Integer)
|
updated_at_in_seconds: int = db.Column(db.Integer)
|
||||||
created_at_in_seconds: int = db.Column(db.Integer)
|
created_at_in_seconds: int = db.Column(db.Integer)
|
||||||
|
|
||||||
message_correlations: dict | None = None
|
message_correlations: Optional[dict] = None
|
||||||
|
|
||||||
@validates("message_type")
|
@validates("message_type")
|
||||||
def validate_message_type(self, key: str, value: Any) -> Any:
|
def validate_message_type(self, key: str, value: Any) -> Any:
|
||||||
|
|
|
@ -1,10 +1,14 @@
|
||||||
"""Spiff_step_details."""
|
"""Spiff_step_details."""
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from flask_bpmn.models.db import db
|
from flask_bpmn.models.db import db
|
||||||
from flask_bpmn.models.db import SpiffworkflowBaseDBModel
|
from flask_bpmn.models.db import SpiffworkflowBaseDBModel
|
||||||
|
from sqlalchemy import ForeignKey
|
||||||
from sqlalchemy.orm import deferred
|
from sqlalchemy.orm import deferred
|
||||||
|
|
||||||
|
from spiffworkflow_backend.models.group import GroupModel
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class SpiffStepDetailsModel(SpiffworkflowBaseDBModel):
|
class SpiffStepDetailsModel(SpiffworkflowBaseDBModel):
|
||||||
|
@ -17,3 +21,6 @@ class SpiffStepDetailsModel(SpiffworkflowBaseDBModel):
|
||||||
task_json: str = deferred(db.Column(db.JSON, nullable=False)) # type: ignore
|
task_json: str = deferred(db.Column(db.JSON, nullable=False)) # type: ignore
|
||||||
timestamp: float = db.Column(db.DECIMAL(17, 6), nullable=False)
|
timestamp: float = db.Column(db.DECIMAL(17, 6), nullable=False)
|
||||||
completed_by_user_id: int = db.Column(db.Integer, nullable=True)
|
completed_by_user_id: int = db.Column(db.Integer, nullable=True)
|
||||||
|
lane_assignment_id: Optional[int] = db.Column(
|
||||||
|
ForeignKey(GroupModel.id), nullable=True
|
||||||
|
)
|
||||||
|
|
|
@ -65,9 +65,7 @@ from spiffworkflow_backend.models.spiff_step_details import SpiffStepDetailsMode
|
||||||
from spiffworkflow_backend.models.user import UserModel
|
from spiffworkflow_backend.models.user import UserModel
|
||||||
from spiffworkflow_backend.routes.user import verify_token
|
from spiffworkflow_backend.routes.user import verify_token
|
||||||
from spiffworkflow_backend.services.authorization_service import AuthorizationService
|
from spiffworkflow_backend.services.authorization_service import AuthorizationService
|
||||||
from spiffworkflow_backend.services.custom_parser import MyCustomParser
|
|
||||||
from spiffworkflow_backend.services.error_handling_service import ErrorHandlingService
|
from spiffworkflow_backend.services.error_handling_service import ErrorHandlingService
|
||||||
from spiffworkflow_backend.services.file_system_service import FileSystemService
|
|
||||||
from spiffworkflow_backend.services.git_service import GitService
|
from spiffworkflow_backend.services.git_service import GitService
|
||||||
from spiffworkflow_backend.services.message_service import MessageService
|
from spiffworkflow_backend.services.message_service import MessageService
|
||||||
from spiffworkflow_backend.services.process_instance_processor import (
|
from spiffworkflow_backend.services.process_instance_processor import (
|
||||||
|
@ -1034,7 +1032,17 @@ def task_list_for_my_open_processes(
|
||||||
return get_tasks(page=page, per_page=per_page)
|
return get_tasks(page=page, per_page=per_page)
|
||||||
|
|
||||||
|
|
||||||
def task_list_for_processes_started_by_others(
|
def task_list_for_me(page: int = 1, per_page: int = 100) -> flask.wrappers.Response:
|
||||||
|
"""Task_list_for_processes_started_by_others."""
|
||||||
|
return get_tasks(
|
||||||
|
processes_started_by_user=False,
|
||||||
|
has_lane_assignment_id=False,
|
||||||
|
page=page,
|
||||||
|
per_page=per_page,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def task_list_for_my_groups(
|
||||||
page: int = 1, per_page: int = 100
|
page: int = 1, per_page: int = 100
|
||||||
) -> flask.wrappers.Response:
|
) -> flask.wrappers.Response:
|
||||||
"""Task_list_for_processes_started_by_others."""
|
"""Task_list_for_processes_started_by_others."""
|
||||||
|
@ -1042,14 +1050,21 @@ def task_list_for_processes_started_by_others(
|
||||||
|
|
||||||
|
|
||||||
def get_tasks(
|
def get_tasks(
|
||||||
processes_started_by_user: bool = True, page: int = 1, per_page: int = 100
|
processes_started_by_user: bool = True,
|
||||||
|
has_lane_assignment_id: bool = True,
|
||||||
|
page: int = 1,
|
||||||
|
per_page: int = 100,
|
||||||
) -> flask.wrappers.Response:
|
) -> flask.wrappers.Response:
|
||||||
"""Get_tasks."""
|
"""Get_tasks."""
|
||||||
user_id = g.user.id
|
user_id = g.user.id
|
||||||
|
|
||||||
|
# use distinct to ensure we only get one row per active task otherwise
|
||||||
|
# we can get back multiple for the same active task row which throws off
|
||||||
|
# pagination later on
|
||||||
|
# https://stackoverflow.com/q/34582014/6090676
|
||||||
active_tasks_query = (
|
active_tasks_query = (
|
||||||
ActiveTaskModel.query.outerjoin(
|
ActiveTaskModel.query.distinct()
|
||||||
GroupModel, GroupModel.id == ActiveTaskModel.lane_assignment_id
|
.outerjoin(GroupModel, GroupModel.id == ActiveTaskModel.lane_assignment_id)
|
||||||
)
|
|
||||||
.join(ProcessInstanceModel)
|
.join(ProcessInstanceModel)
|
||||||
.join(UserModel, UserModel.id == ProcessInstanceModel.process_initiator_id)
|
.join(UserModel, UserModel.id == ProcessInstanceModel.process_initiator_id)
|
||||||
)
|
)
|
||||||
|
@ -1057,11 +1072,29 @@ def get_tasks(
|
||||||
if processes_started_by_user:
|
if processes_started_by_user:
|
||||||
active_tasks_query = active_tasks_query.filter(
|
active_tasks_query = active_tasks_query.filter(
|
||||||
ProcessInstanceModel.process_initiator_id == user_id
|
ProcessInstanceModel.process_initiator_id == user_id
|
||||||
).outerjoin(ActiveTaskUserModel, and_(ActiveTaskUserModel.user_id == user_id))
|
).outerjoin(
|
||||||
|
ActiveTaskUserModel,
|
||||||
|
and_(
|
||||||
|
ActiveTaskUserModel.user_id == user_id,
|
||||||
|
ActiveTaskModel.id == ActiveTaskUserModel.active_task_id,
|
||||||
|
),
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
active_tasks_query = active_tasks_query.filter(
|
active_tasks_query = active_tasks_query.filter(
|
||||||
ProcessInstanceModel.process_initiator_id != user_id
|
ProcessInstanceModel.process_initiator_id != user_id
|
||||||
).join(ActiveTaskUserModel, and_(ActiveTaskUserModel.user_id == user_id))
|
).join(
|
||||||
|
ActiveTaskUserModel,
|
||||||
|
and_(
|
||||||
|
ActiveTaskUserModel.user_id == user_id,
|
||||||
|
ActiveTaskModel.id == ActiveTaskUserModel.active_task_id,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
if has_lane_assignment_id:
|
||||||
|
active_tasks_query = active_tasks_query.filter(
|
||||||
|
ActiveTaskModel.lane_assignment_id.is_not(None) # type: ignore
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
active_tasks_query = active_tasks_query.filter(ActiveTaskModel.lane_assignment_id.is_(None)) # type: ignore
|
||||||
|
|
||||||
active_tasks = active_tasks_query.add_columns(
|
active_tasks = active_tasks_query.add_columns(
|
||||||
ProcessInstanceModel.process_model_identifier,
|
ProcessInstanceModel.process_model_identifier,
|
||||||
|
@ -1238,7 +1271,25 @@ def task_submit(
|
||||||
if terminate_loop and spiff_task.is_looping():
|
if terminate_loop and spiff_task.is_looping():
|
||||||
spiff_task.terminate_loop()
|
spiff_task.terminate_loop()
|
||||||
|
|
||||||
ProcessInstanceService.complete_form_task(processor, spiff_task, body, g.user)
|
active_task = ActiveTaskModel.query.filter_by(
|
||||||
|
process_instance_id=process_instance_id, task_id=task_id
|
||||||
|
).first()
|
||||||
|
if active_task is None:
|
||||||
|
raise (
|
||||||
|
ApiError(
|
||||||
|
error_code="no_active_task",
|
||||||
|
message="Cannot find an active task with task id '{task_id}' for process instance {process_instance_id}.",
|
||||||
|
status_code=500,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
ProcessInstanceService.complete_form_task(
|
||||||
|
processor=processor,
|
||||||
|
spiff_task=spiff_task,
|
||||||
|
data=body,
|
||||||
|
user=g.user,
|
||||||
|
active_task=active_task,
|
||||||
|
)
|
||||||
|
|
||||||
# If we need to update all tasks, then get the next ready task and if it a multi-instance with the same
|
# If we need to update all tasks, then get the next ready task and if it a multi-instance with the same
|
||||||
# task spec, complete that form as well.
|
# task spec, complete that form as well.
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
from SpiffWorkflow.dmn.parser.BpmnDmnParser import BpmnDmnParser
|
"""Custom_parser."""
|
||||||
from SpiffWorkflow.spiff.parser.process import SpiffBpmnParser
|
from SpiffWorkflow.dmn.parser.BpmnDmnParser import BpmnDmnParser # type: ignore
|
||||||
|
from SpiffWorkflow.spiff.parser.process import SpiffBpmnParser # type: ignore
|
||||||
|
|
||||||
|
|
||||||
class MyCustomParser(BpmnDmnParser): # type: ignore
|
class MyCustomParser(BpmnDmnParser): # type: ignore
|
||||||
|
|
|
@ -60,8 +60,9 @@ class FileSystemService:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def workflow_path(spec: ProcessModelInfo) -> str:
|
def workflow_path(spec: ProcessModelInfo) -> str:
|
||||||
"""Workflow_path."""
|
"""Workflow_path."""
|
||||||
process_model_path = os.path.join(FileSystemService.root_path(), spec.id)
|
process_model_path = os.path.join(
|
||||||
# process_group_path = FileSystemService.process_group_path_for_spec(spec)
|
FileSystemService.root_path(), spec.id_for_file_path()
|
||||||
|
)
|
||||||
return process_model_path
|
return process_model_path
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
|
@ -38,7 +38,6 @@ from SpiffWorkflow.dmn.parser.BpmnDmnParser import BpmnDmnParser # type: ignore
|
||||||
from SpiffWorkflow.dmn.serializer.task_spec_converters import BusinessRuleTaskConverter # type: ignore
|
from SpiffWorkflow.dmn.serializer.task_spec_converters import BusinessRuleTaskConverter # type: ignore
|
||||||
from SpiffWorkflow.exceptions import WorkflowException # type: ignore
|
from SpiffWorkflow.exceptions import WorkflowException # type: ignore
|
||||||
from SpiffWorkflow.serializer.exceptions import MissingSpecError # type: ignore
|
from SpiffWorkflow.serializer.exceptions import MissingSpecError # type: ignore
|
||||||
from SpiffWorkflow.spiff.parser.process import SpiffBpmnParser # type: ignore
|
|
||||||
from SpiffWorkflow.spiff.serializer.task_spec_converters import BoundaryEventConverter # type: ignore
|
from SpiffWorkflow.spiff.serializer.task_spec_converters import BoundaryEventConverter # type: ignore
|
||||||
from SpiffWorkflow.spiff.serializer.task_spec_converters import (
|
from SpiffWorkflow.spiff.serializer.task_spec_converters import (
|
||||||
CallActivityTaskConverter,
|
CallActivityTaskConverter,
|
||||||
|
@ -95,9 +94,6 @@ from spiffworkflow_backend.services.custom_parser import MyCustomParser
|
||||||
from spiffworkflow_backend.services.file_system_service import FileSystemService
|
from spiffworkflow_backend.services.file_system_service import FileSystemService
|
||||||
from spiffworkflow_backend.services.process_model_service import ProcessModelService
|
from spiffworkflow_backend.services.process_model_service import ProcessModelService
|
||||||
from spiffworkflow_backend.services.service_task_service import ServiceTaskDelegate
|
from spiffworkflow_backend.services.service_task_service import ServiceTaskDelegate
|
||||||
from spiffworkflow_backend.services.spec_file_service import (
|
|
||||||
ProcessModelFileNotFoundError,
|
|
||||||
)
|
|
||||||
from spiffworkflow_backend.services.spec_file_service import SpecFileService
|
from spiffworkflow_backend.services.spec_file_service import SpecFileService
|
||||||
from spiffworkflow_backend.services.user_service import UserService
|
from spiffworkflow_backend.services.user_service import UserService
|
||||||
|
|
||||||
|
@ -584,9 +580,10 @@ class ProcessInstanceProcessor:
|
||||||
)
|
)
|
||||||
return details_model
|
return details_model
|
||||||
|
|
||||||
def save_spiff_step_details(self) -> None:
|
def save_spiff_step_details(self, active_task: ActiveTaskModel) -> None:
|
||||||
"""SaveSpiffStepDetails."""
|
"""SaveSpiffStepDetails."""
|
||||||
details_model = self.spiff_step_details()
|
details_model = self.spiff_step_details()
|
||||||
|
details_model.lane_assignment_id = active_task.lane_assignment_id
|
||||||
db.session.add(details_model)
|
db.session.add(details_model)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
@ -679,13 +676,13 @@ class ProcessInstanceProcessor:
|
||||||
"""Backfill_missing_spec_reference_records."""
|
"""Backfill_missing_spec_reference_records."""
|
||||||
process_models = ProcessModelService().get_process_models()
|
process_models = ProcessModelService().get_process_models()
|
||||||
for process_model in process_models:
|
for process_model in process_models:
|
||||||
refs = SpecFileService.reference_map(SpecFileService.get_references_for_process(process_model))
|
refs = SpecFileService.reference_map(
|
||||||
|
SpecFileService.get_references_for_process(process_model)
|
||||||
|
)
|
||||||
bpmn_process_identifiers = refs.keys()
|
bpmn_process_identifiers = refs.keys()
|
||||||
if bpmn_process_identifier in bpmn_process_identifiers:
|
if bpmn_process_identifier in bpmn_process_identifiers:
|
||||||
SpecFileService.update_process_cache(refs[bpmn_process_identifier])
|
SpecFileService.update_process_cache(refs[bpmn_process_identifier])
|
||||||
return FileSystemService.full_path_to_process_model_file(
|
return FileSystemService.full_path_to_process_model_file(process_model)
|
||||||
process_model
|
|
||||||
)
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -1130,11 +1127,11 @@ class ProcessInstanceProcessor:
|
||||||
)
|
)
|
||||||
return user_tasks # type: ignore
|
return user_tasks # type: ignore
|
||||||
|
|
||||||
def complete_task(self, task: SpiffTask) -> None:
|
def complete_task(self, task: SpiffTask, active_task: ActiveTaskModel) -> None:
|
||||||
"""Complete_task."""
|
"""Complete_task."""
|
||||||
self.increment_spiff_step()
|
self.increment_spiff_step()
|
||||||
self.bpmn_process_instance.complete_task_from_id(task.id)
|
self.bpmn_process_instance.complete_task_from_id(task.id)
|
||||||
self.save_spiff_step_details()
|
self.save_spiff_step_details(active_task)
|
||||||
|
|
||||||
def get_data(self) -> dict[str, Any]:
|
def get_data(self) -> dict[str, Any]:
|
||||||
"""Get_data."""
|
"""Get_data."""
|
||||||
|
|
|
@ -8,6 +8,7 @@ from flask_bpmn.api.api_error import ApiError
|
||||||
from flask_bpmn.models.db import db
|
from flask_bpmn.models.db import db
|
||||||
from SpiffWorkflow.task import Task as SpiffTask # type: ignore
|
from SpiffWorkflow.task import Task as SpiffTask # type: ignore
|
||||||
|
|
||||||
|
from spiffworkflow_backend.models.active_task import ActiveTaskModel
|
||||||
from spiffworkflow_backend.models.process_instance import ProcessInstanceApi
|
from spiffworkflow_backend.models.process_instance import ProcessInstanceApi
|
||||||
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
|
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
|
||||||
from spiffworkflow_backend.models.process_instance import ProcessInstanceStatus
|
from spiffworkflow_backend.models.process_instance import ProcessInstanceStatus
|
||||||
|
@ -188,6 +189,7 @@ class ProcessInstanceService:
|
||||||
spiff_task: SpiffTask,
|
spiff_task: SpiffTask,
|
||||||
data: dict[str, Any],
|
data: dict[str, Any],
|
||||||
user: UserModel,
|
user: UserModel,
|
||||||
|
active_task: ActiveTaskModel,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""All the things that need to happen when we complete a form.
|
"""All the things that need to happen when we complete a form.
|
||||||
|
|
||||||
|
@ -201,7 +203,7 @@ class ProcessInstanceService:
|
||||||
dot_dct = ProcessInstanceService.create_dot_dict(data)
|
dot_dct = ProcessInstanceService.create_dot_dict(data)
|
||||||
spiff_task.update_data(dot_dct)
|
spiff_task.update_data(dot_dct)
|
||||||
# ProcessInstanceService.post_process_form(spiff_task) # some properties may update the data store.
|
# ProcessInstanceService.post_process_form(spiff_task) # some properties may update the data store.
|
||||||
processor.complete_task(spiff_task)
|
processor.complete_task(spiff_task, active_task)
|
||||||
processor.do_engine_steps(save=True)
|
processor.do_engine_steps(save=True)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
|
@ -2,17 +2,10 @@
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Any, Type
|
|
||||||
from typing import List
|
from typing import List
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from SpiffWorkflow.dmn.parser.BpmnDmnParser import BpmnDmnParser
|
|
||||||
from SpiffWorkflow.bpmn.parser.ProcessParser import ProcessParser
|
|
||||||
from flask_bpmn.api.api_error import ApiError
|
|
||||||
from flask_bpmn.models.db import db
|
from flask_bpmn.models.db import db
|
||||||
from lxml import etree # type: ignore
|
|
||||||
from lxml.etree import _Element # type: ignore
|
|
||||||
from lxml.etree import Element as EtreeElement
|
|
||||||
from SpiffWorkflow.bpmn.parser.ValidationException import ValidationException # type: ignore
|
from SpiffWorkflow.bpmn.parser.ValidationException import ValidationException # type: ignore
|
||||||
|
|
||||||
from spiffworkflow_backend.models.spec_reference import SpecReferenceCache
|
from spiffworkflow_backend.models.spec_reference import SpecReferenceCache
|
||||||
|
@ -77,10 +70,13 @@ class SpecFileService(FileSystemService):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_references_for_process(process_model_info: ProcessModelInfo) -> list[SpecReference]:
|
def get_references_for_process(process_model_info: ProcessModelInfo) -> list[SpecReference]:
|
||||||
|
"""Get_references_for_process."""
|
||||||
files = SpecFileService.get_files(process_model_info)
|
files = SpecFileService.get_files(process_model_info)
|
||||||
references = []
|
references = []
|
||||||
for file in files:
|
for file in files:
|
||||||
references.extend(SpecFileService.get_references_for_file(file, process_model_info))
|
references.extend(
|
||||||
|
SpecFileService.get_references_for_file(file, process_model_info)
|
||||||
|
)
|
||||||
return references
|
return references
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -94,7 +90,7 @@ class SpecFileService(FileSystemService):
|
||||||
type = {str} 'process' / 'decision'
|
type = {str} 'process' / 'decision'
|
||||||
"""
|
"""
|
||||||
references: list[SpecReference] = []
|
references: list[SpecReference] = []
|
||||||
full_file_path = SpecFileService.file_path(process_model_info, file.name)
|
full_file_path = SpecFileService.full_file_path(process_model_info, file.name)
|
||||||
file_path = os.path.join(process_model_info.id, file.name)
|
file_path = os.path.join(process_model_info.id, file.name)
|
||||||
parser = MyCustomParser()
|
parser = MyCustomParser()
|
||||||
parser_type = None
|
parser_type = None
|
||||||
|
@ -118,19 +114,28 @@ class SpecFileService(FileSystemService):
|
||||||
else:
|
else:
|
||||||
return references
|
return references
|
||||||
for sub_parser in sub_parsers:
|
for sub_parser in sub_parsers:
|
||||||
if parser_type == 'process':
|
if parser_type == "process":
|
||||||
has_lanes = sub_parser.has_lanes()
|
has_lanes = sub_parser.has_lanes()
|
||||||
executable = sub_parser.process_executable
|
executable = sub_parser.process_executable
|
||||||
start_messages = sub_parser.start_messages()
|
start_messages = sub_parser.start_messages()
|
||||||
is_primary = sub_parser.get_id() == process_model_info.primary_process_id
|
is_primary = sub_parser.get_id() == process_model_info.primary_process_id
|
||||||
references.append(SpecReference(
|
|
||||||
identifier=sub_parser.get_id(), display_name=sub_parser.get_name(),
|
references.append(
|
||||||
|
SpecReference(
|
||||||
|
identifier=sub_parser.get_id(),
|
||||||
|
display_name=sub_parser.get_name(),
|
||||||
process_model_id=process_model_info.id,
|
process_model_id=process_model_info.id,
|
||||||
type=parser_type,
|
type=parser_type,
|
||||||
file_name=file.name, relative_path=file_path, has_lanes=has_lanes,
|
file_name=file.name,
|
||||||
is_executable=is_executable, messages=messages, is_primary=is_primary,
|
relative_path=file_path,
|
||||||
correlations=correlations, start_messages=start_messages
|
has_lanes=has_lanes,
|
||||||
))
|
is_executable=is_executable,
|
||||||
|
messages=messages,
|
||||||
|
is_primary=is_primary,
|
||||||
|
correlations=correlations,
|
||||||
|
start_messages=start_messages
|
||||||
|
)
|
||||||
|
)
|
||||||
return references
|
return references
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -142,15 +147,14 @@ class SpecFileService(FileSystemService):
|
||||||
return SpecFileService.update_file(process_model_info, file_name, binary_data)
|
return SpecFileService.update_file(process_model_info, file_name, binary_data)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def update_file(process_model_info: ProcessModelInfo, file_name: str, binary_data: bytes
|
def update_file(
|
||||||
|
process_model_info: ProcessModelInfo, file_name: str, binary_data: bytes
|
||||||
) -> File:
|
) -> File:
|
||||||
"""Update_file."""
|
"""Update_file."""
|
||||||
SpecFileService.assert_valid_file_name(file_name)
|
SpecFileService.assert_valid_file_name(file_name)
|
||||||
file_path = os.path.join(
|
full_file_path = SpecFileService.full_file_path(process_model_info, file_name)
|
||||||
FileSystemService.root_path(), process_model_info.id, file_name
|
SpecFileService.write_file_data_to_system(full_file_path, binary_data)
|
||||||
)
|
file = SpecFileService.to_file_object(file_name, full_file_path)
|
||||||
SpecFileService.write_file_data_to_system(file_path, binary_data)
|
|
||||||
file = SpecFileService.to_file_object(file_name, file_path)
|
|
||||||
|
|
||||||
references = SpecFileService.get_references_for_file(file, process_model_info)
|
references = SpecFileService.get_references_for_file(file, process_model_info)
|
||||||
primary_process_ref = next((ref for ref in references if ref.is_primary), None)
|
primary_process_ref = next((ref for ref in references if ref.is_primary), None)
|
||||||
|
@ -172,40 +176,36 @@ class SpecFileService(FileSystemService):
|
||||||
SpecFileService.update_message_cache(ref)
|
SpecFileService.update_message_cache(ref)
|
||||||
SpecFileService.update_message_trigger_cache(ref, process_model_info)
|
SpecFileService.update_message_trigger_cache(ref, process_model_info)
|
||||||
SpecFileService.update_correlation_cache(ref)
|
SpecFileService.update_correlation_cache(ref)
|
||||||
|
|
||||||
return file
|
return file
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_data(process_model_info: ProcessModelInfo, file_name: str) -> bytes:
|
def get_data(process_model_info: ProcessModelInfo, file_name: str) -> bytes:
|
||||||
"""Get_data."""
|
"""Get_data."""
|
||||||
# file_path = SpecFileService.file_path(process_model_info, file_name)
|
full_file_path = SpecFileService.full_file_path(process_model_info, file_name)
|
||||||
file_path = os.path.join(
|
if not os.path.exists(full_file_path):
|
||||||
FileSystemService.root_path(), process_model_info.id, file_name
|
|
||||||
)
|
|
||||||
if not os.path.exists(file_path):
|
|
||||||
raise ProcessModelFileNotFoundError(
|
raise ProcessModelFileNotFoundError(
|
||||||
f"No file found with name {file_name} in {process_model_info.display_name}"
|
f"No file found with name {file_name} in {process_model_info.display_name}"
|
||||||
)
|
)
|
||||||
with open(file_path, "rb") as f_handle:
|
with open(full_file_path, "rb") as f_handle:
|
||||||
spec_file_data = f_handle.read()
|
spec_file_data = f_handle.read()
|
||||||
return spec_file_data
|
return spec_file_data
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def file_path(spec: ProcessModelInfo, file_name: str) -> str:
|
def full_file_path(spec: ProcessModelInfo, file_name: str) -> str:
|
||||||
"""File_path."""
|
"""File_path."""
|
||||||
return os.path.join(SpecFileService.workflow_path(spec), file_name)
|
return os.path.join(SpecFileService.workflow_path(spec), file_name)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def last_modified(spec: ProcessModelInfo, file_name: str) -> datetime:
|
def last_modified(spec: ProcessModelInfo, file_name: str) -> datetime:
|
||||||
"""Last_modified."""
|
"""Last_modified."""
|
||||||
path = SpecFileService.file_path(spec, file_name)
|
full_file_path = SpecFileService.full_file_path(spec, file_name)
|
||||||
return FileSystemService._last_modified(path)
|
return FileSystemService._last_modified(full_file_path)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def timestamp(spec: ProcessModelInfo, file_name: str) -> float:
|
def timestamp(spec: ProcessModelInfo, file_name: str) -> float:
|
||||||
"""Timestamp."""
|
"""Timestamp."""
|
||||||
path = SpecFileService.file_path(spec, file_name)
|
full_file_path = SpecFileService.full_file_path(spec, file_name)
|
||||||
return FileSystemService._timestamp(path)
|
return FileSystemService._timestamp(full_file_path)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def delete_file(spec: ProcessModelInfo, file_name: str) -> None:
|
def delete_file(spec: ProcessModelInfo, file_name: str) -> None:
|
||||||
|
@ -215,9 +215,8 @@ class SpecFileService(FileSystemService):
|
||||||
# for lf in lookup_files:
|
# for lf in lookup_files:
|
||||||
# session.query(LookupDataModel).filter_by(lookup_file_model_id=lf.id).delete()
|
# session.query(LookupDataModel).filter_by(lookup_file_model_id=lf.id).delete()
|
||||||
# session.query(LookupFileModel).filter_by(id=lf.id).delete()
|
# session.query(LookupFileModel).filter_by(id=lf.id).delete()
|
||||||
# file_path = SpecFileService.file_path(spec, file_name)
|
full_file_path = SpecFileService.full_file_path(spec, file_name)
|
||||||
file_path = os.path.join(FileSystemService.root_path(), spec.id, file_name)
|
os.remove(full_file_path)
|
||||||
os.remove(file_path)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def delete_all_files(spec: ProcessModelInfo) -> None:
|
def delete_all_files(spec: ProcessModelInfo) -> None:
|
||||||
|
@ -226,10 +225,8 @@ class SpecFileService(FileSystemService):
|
||||||
if os.path.exists(dir_path):
|
if os.path.exists(dir_path):
|
||||||
shutil.rmtree(dir_path)
|
shutil.rmtree(dir_path)
|
||||||
|
|
||||||
|
|
||||||
# fixme: Place all the caching stuff in a different service.
|
# fixme: Place all the caching stuff in a different service.
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def update_process_cache(ref: SpecReference) -> None:
|
def update_process_cache(ref: SpecReference) -> None:
|
||||||
process_id_lookup = SpecReferenceCache.query.filter_by(identifier=ref.identifier).first()
|
process_id_lookup = SpecReferenceCache.query.filter_by(identifier=ref.identifier).first()
|
||||||
|
@ -255,23 +252,26 @@ class SpecFileService(FileSystemService):
|
||||||
db.session.add(process_id_lookup)
|
db.session.add(process_id_lookup)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def update_message_cache(ref: SpecReference) -> None:
|
def update_message_cache(ref: SpecReference) -> None:
|
||||||
"""Assure we have a record in the database of all possible message ids and names."""
|
"""Assure we have a record in the database of all possible message ids and names."""
|
||||||
for message_model_identifier in ref.messages.keys():
|
for message_model_identifier in ref.messages.keys():
|
||||||
message_model = MessageModel.query.filter_by(identifier=message_model_identifier).first()
|
message_model = MessageModel.query.filter_by(
|
||||||
|
identifier=message_model_identifier
|
||||||
|
).first()
|
||||||
if message_model is None:
|
if message_model is None:
|
||||||
message_model = MessageModel(
|
message_model = MessageModel(
|
||||||
identifier=message_model_identifier, name=ref.messages[message_model_identifier]
|
identifier=message_model_identifier,
|
||||||
|
name=ref.messages[message_model_identifier],
|
||||||
)
|
)
|
||||||
db.session.add(message_model)
|
db.session.add(message_model)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def update_message_trigger_cache(ref: SpecReference, process_model_info: ProcessModelInfo) -> None:
|
def update_message_trigger_cache(
|
||||||
"""assure we know which messages can trigger the start of a process."""
|
ref: SpecReference, process_model_info: ProcessModelInfo
|
||||||
|
) -> None:
|
||||||
|
"""Assure we know which messages can trigger the start of a process."""
|
||||||
for message_model_identifier in ref.start_messages:
|
for message_model_identifier in ref.start_messages:
|
||||||
message_model = MessageModel.query.filter_by(
|
message_model = MessageModel.query.filter_by(
|
||||||
identifier=message_model_identifier
|
identifier=message_model_identifier
|
||||||
|
@ -288,12 +288,10 @@ class SpecFileService(FileSystemService):
|
||||||
)
|
)
|
||||||
|
|
||||||
if message_triggerable_process_model is None:
|
if message_triggerable_process_model is None:
|
||||||
message_triggerable_process_model = (
|
message_triggerable_process_model = MessageTriggerableProcessModel(
|
||||||
MessageTriggerableProcessModel(
|
|
||||||
message_model_id=message_model.id,
|
message_model_id=message_model.id,
|
||||||
process_model_identifier=process_model_info.id,
|
process_model_identifier=process_model_info.id,
|
||||||
process_group_identifier="process_group_identifier"
|
process_group_identifier="process_group_identifier",
|
||||||
)
|
|
||||||
)
|
)
|
||||||
db.session.add(message_triggerable_process_model)
|
db.session.add(message_triggerable_process_model)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
@ -310,13 +308,17 @@ class SpecFileService(FileSystemService):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def update_correlation_cache(ref: SpecReference) -> None:
|
def update_correlation_cache(ref: SpecReference) -> None:
|
||||||
|
"""Update_correlation_cache."""
|
||||||
for correlation_identifier in ref.correlations.keys():
|
for correlation_identifier in ref.correlations.keys():
|
||||||
correlation_property_retrieval_expressions = \
|
correlation_property_retrieval_expressions = ref.correlations[
|
||||||
ref.correlations[correlation_identifier]['retrieval_expressions']
|
correlation_identifier
|
||||||
|
]["retrieval_expressions"]
|
||||||
|
|
||||||
for cpre in correlation_property_retrieval_expressions:
|
for cpre in correlation_property_retrieval_expressions:
|
||||||
message_model_identifier = cpre["messageRef"]
|
message_model_identifier = cpre["messageRef"]
|
||||||
message_model = MessageModel.query.filter_by(identifier=message_model_identifier).first()
|
message_model = MessageModel.query.filter_by(
|
||||||
|
identifier=message_model_identifier
|
||||||
|
).first()
|
||||||
if message_model is None:
|
if message_model is None:
|
||||||
raise ValidationException(
|
raise ValidationException(
|
||||||
f"Could not find message model with identifier '{message_model_identifier}'"
|
f"Could not find message model with identifier '{message_model_identifier}'"
|
||||||
|
|
|
@ -40,7 +40,7 @@
|
||||||
}</spiffworkflow:messagePayload>
|
}</spiffworkflow:messagePayload>
|
||||||
</bpmn:extensionElements>
|
</bpmn:extensionElements>
|
||||||
</bpmn:message>
|
</bpmn:message>
|
||||||
<bpmn:process id="message_receiver_process_one" name="Message Receiver Process" isExecutable="true">
|
<bpmn:process id="message_receiver_process_one" name="Message Receiver Process One" isExecutable="true">
|
||||||
<bpmn:sequenceFlow id="Flow_11r9uiw" sourceRef="send_message_response" targetRef="Event_0q5otqd" />
|
<bpmn:sequenceFlow id="Flow_11r9uiw" sourceRef="send_message_response" targetRef="Event_0q5otqd" />
|
||||||
<bpmn:endEvent id="Event_0q5otqd">
|
<bpmn:endEvent id="Event_0q5otqd">
|
||||||
<bpmn:incoming>Flow_11r9uiw</bpmn:incoming>
|
<bpmn:incoming>Flow_11r9uiw</bpmn:incoming>
|
||||||
|
|
|
@ -40,7 +40,7 @@
|
||||||
}</spiffworkflow:messagePayload>
|
}</spiffworkflow:messagePayload>
|
||||||
</bpmn:extensionElements>
|
</bpmn:extensionElements>
|
||||||
</bpmn:message>
|
</bpmn:message>
|
||||||
<bpmn:process id="message_receiver_process_two" name="Message Receiver Process" isExecutable="true">
|
<bpmn:process id="message_receiver_process_two" name="Message Receiver Process Two" isExecutable="true">
|
||||||
<bpmn:sequenceFlow id="Flow_11r9uiw" sourceRef="send_message_response" targetRef="Event_0q5otqd" />
|
<bpmn:sequenceFlow id="Flow_11r9uiw" sourceRef="send_message_response" targetRef="Event_0q5otqd" />
|
||||||
<bpmn:endEvent id="Event_0q5otqd">
|
<bpmn:endEvent id="Event_0q5otqd">
|
||||||
<bpmn:incoming>Flow_11r9uiw</bpmn:incoming>
|
<bpmn:incoming>Flow_11r9uiw</bpmn:incoming>
|
||||||
|
|
|
@ -25,11 +25,9 @@ class ExampleDataLoader:
|
||||||
"""Assumes that process_model_source_directory exists in static/bpmn and contains bpmn_file_name.
|
"""Assumes that process_model_source_directory exists in static/bpmn and contains bpmn_file_name.
|
||||||
|
|
||||||
further assumes that bpmn_file_name is the primary file for the process model.
|
further assumes that bpmn_file_name is the primary file for the process model.
|
||||||
|
|
||||||
if bpmn_file_name is None we load all files in process_model_source_directory,
|
if bpmn_file_name is None we load all files in process_model_source_directory,
|
||||||
otherwise, we only load bpmn_file_name
|
otherwise, we only load bpmn_file_name
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if process_model_source_directory is None:
|
if process_model_source_directory is None:
|
||||||
raise Exception("You must include `process_model_source_directory`.")
|
raise Exception("You must include `process_model_source_directory`.")
|
||||||
|
|
||||||
|
@ -85,7 +83,9 @@ class ExampleDataLoader:
|
||||||
process_model_info=spec, file_name=filename, binary_data=data
|
process_model_info=spec, file_name=filename, binary_data=data
|
||||||
)
|
)
|
||||||
if is_primary:
|
if is_primary:
|
||||||
references = SpecFileService.get_references_for_file(file_info, spec)
|
references = SpecFileService.get_references_for_file(
|
||||||
|
file_info, spec
|
||||||
|
)
|
||||||
spec.primary_process_id = references[0].identifier
|
spec.primary_process_id = references[0].identifier
|
||||||
spec.primary_file_name = filename
|
spec.primary_file_name = filename
|
||||||
ProcessModelService().save_process_model(spec)
|
ProcessModelService().save_process_model(spec)
|
||||||
|
|
|
@ -2367,4 +2367,3 @@ class TestProcessApi(BaseTest):
|
||||||
)
|
)
|
||||||
|
|
||||||
print("test_script_unit_test_run")
|
print("test_script_unit_test_run")
|
||||||
|
|
||||||
|
|
|
@ -74,7 +74,11 @@ class TestGetLocaltime(BaseTest):
|
||||||
)
|
)
|
||||||
|
|
||||||
ProcessInstanceService.complete_form_task(
|
ProcessInstanceService.complete_form_task(
|
||||||
processor, spiff_task, {"timezone": "US/Pacific"}, initiator_user
|
processor,
|
||||||
|
spiff_task,
|
||||||
|
{"timezone": "US/Pacific"},
|
||||||
|
initiator_user,
|
||||||
|
active_task,
|
||||||
)
|
)
|
||||||
|
|
||||||
active_task = process_instance.active_tasks[0]
|
active_task = process_instance.active_tasks[0]
|
||||||
|
|
|
@ -126,7 +126,7 @@ class TestAuthorizationService(BaseTest):
|
||||||
active_task.task_name, processor.bpmn_process_instance
|
active_task.task_name, processor.bpmn_process_instance
|
||||||
)
|
)
|
||||||
ProcessInstanceService.complete_form_task(
|
ProcessInstanceService.complete_form_task(
|
||||||
processor, spiff_task, {}, initiator_user
|
processor, spiff_task, {}, initiator_user, active_task
|
||||||
)
|
)
|
||||||
|
|
||||||
active_task = process_instance.active_tasks[0]
|
active_task = process_instance.active_tasks[0]
|
||||||
|
@ -137,5 +137,5 @@ class TestAuthorizationService(BaseTest):
|
||||||
{"username": "testuser2", "sub": "open_id"}
|
{"username": "testuser2", "sub": "open_id"}
|
||||||
)
|
)
|
||||||
ProcessInstanceService.complete_form_task(
|
ProcessInstanceService.complete_form_task(
|
||||||
processor, spiff_task, {}, finance_user
|
processor, spiff_task, {}, finance_user, active_task
|
||||||
)
|
)
|
||||||
|
|
|
@ -47,6 +47,7 @@ class TestDotNotation(BaseTest):
|
||||||
|
|
||||||
processor = ProcessInstanceProcessor(process_instance)
|
processor = ProcessInstanceProcessor(process_instance)
|
||||||
processor.do_engine_steps(save=True)
|
processor.do_engine_steps(save=True)
|
||||||
|
active_task = process_instance.active_tasks[0]
|
||||||
|
|
||||||
user_task = processor.get_ready_user_tasks()[0]
|
user_task = processor.get_ready_user_tasks()[0]
|
||||||
form_data = {
|
form_data = {
|
||||||
|
@ -57,7 +58,7 @@ class TestDotNotation(BaseTest):
|
||||||
"invoice.dueDate": "09/30/2022",
|
"invoice.dueDate": "09/30/2022",
|
||||||
}
|
}
|
||||||
ProcessInstanceService.complete_form_task(
|
ProcessInstanceService.complete_form_task(
|
||||||
processor, user_task, form_data, with_super_admin_user
|
processor, user_task, form_data, with_super_admin_user, active_task
|
||||||
)
|
)
|
||||||
|
|
||||||
expected = {
|
expected = {
|
||||||
|
|
|
@ -91,10 +91,10 @@ class TestProcessInstanceProcessor(BaseTest):
|
||||||
)
|
)
|
||||||
with pytest.raises(UserDoesNotHaveAccessToTaskError):
|
with pytest.raises(UserDoesNotHaveAccessToTaskError):
|
||||||
ProcessInstanceService.complete_form_task(
|
ProcessInstanceService.complete_form_task(
|
||||||
processor, spiff_task, {}, finance_user
|
processor, spiff_task, {}, finance_user, active_task
|
||||||
)
|
)
|
||||||
ProcessInstanceService.complete_form_task(
|
ProcessInstanceService.complete_form_task(
|
||||||
processor, spiff_task, {}, initiator_user
|
processor, spiff_task, {}, initiator_user, active_task
|
||||||
)
|
)
|
||||||
|
|
||||||
assert len(process_instance.active_tasks) == 1
|
assert len(process_instance.active_tasks) == 1
|
||||||
|
@ -108,11 +108,11 @@ class TestProcessInstanceProcessor(BaseTest):
|
||||||
)
|
)
|
||||||
with pytest.raises(UserDoesNotHaveAccessToTaskError):
|
with pytest.raises(UserDoesNotHaveAccessToTaskError):
|
||||||
ProcessInstanceService.complete_form_task(
|
ProcessInstanceService.complete_form_task(
|
||||||
processor, spiff_task, {}, initiator_user
|
processor, spiff_task, {}, initiator_user, active_task
|
||||||
)
|
)
|
||||||
|
|
||||||
ProcessInstanceService.complete_form_task(
|
ProcessInstanceService.complete_form_task(
|
||||||
processor, spiff_task, {}, finance_user
|
processor, spiff_task, {}, finance_user, active_task
|
||||||
)
|
)
|
||||||
assert len(process_instance.active_tasks) == 1
|
assert len(process_instance.active_tasks) == 1
|
||||||
active_task = process_instance.active_tasks[0]
|
active_task = process_instance.active_tasks[0]
|
||||||
|
@ -124,7 +124,7 @@ class TestProcessInstanceProcessor(BaseTest):
|
||||||
active_task.task_name, processor.bpmn_process_instance
|
active_task.task_name, processor.bpmn_process_instance
|
||||||
)
|
)
|
||||||
ProcessInstanceService.complete_form_task(
|
ProcessInstanceService.complete_form_task(
|
||||||
processor, spiff_task, {}, initiator_user
|
processor, spiff_task, {}, initiator_user, active_task
|
||||||
)
|
)
|
||||||
|
|
||||||
assert process_instance.status == ProcessInstanceStatus.complete.value
|
assert process_instance.status == ProcessInstanceStatus.complete.value
|
||||||
|
@ -173,10 +173,10 @@ class TestProcessInstanceProcessor(BaseTest):
|
||||||
)
|
)
|
||||||
with pytest.raises(UserDoesNotHaveAccessToTaskError):
|
with pytest.raises(UserDoesNotHaveAccessToTaskError):
|
||||||
ProcessInstanceService.complete_form_task(
|
ProcessInstanceService.complete_form_task(
|
||||||
processor, spiff_task, {}, finance_user_three
|
processor, spiff_task, {}, finance_user_three, active_task
|
||||||
)
|
)
|
||||||
ProcessInstanceService.complete_form_task(
|
ProcessInstanceService.complete_form_task(
|
||||||
processor, spiff_task, {}, initiator_user
|
processor, spiff_task, {}, initiator_user, active_task
|
||||||
)
|
)
|
||||||
|
|
||||||
assert len(process_instance.active_tasks) == 1
|
assert len(process_instance.active_tasks) == 1
|
||||||
|
@ -190,12 +190,12 @@ class TestProcessInstanceProcessor(BaseTest):
|
||||||
)
|
)
|
||||||
with pytest.raises(UserDoesNotHaveAccessToTaskError):
|
with pytest.raises(UserDoesNotHaveAccessToTaskError):
|
||||||
ProcessInstanceService.complete_form_task(
|
ProcessInstanceService.complete_form_task(
|
||||||
processor, spiff_task, {}, initiator_user
|
processor, spiff_task, {}, initiator_user, active_task
|
||||||
)
|
)
|
||||||
|
|
||||||
g.user = finance_user_three
|
g.user = finance_user_three
|
||||||
ProcessInstanceService.complete_form_task(
|
ProcessInstanceService.complete_form_task(
|
||||||
processor, spiff_task, {}, finance_user_three
|
processor, spiff_task, {}, finance_user_three, active_task
|
||||||
)
|
)
|
||||||
assert len(process_instance.active_tasks) == 1
|
assert len(process_instance.active_tasks) == 1
|
||||||
active_task = process_instance.active_tasks[0]
|
active_task = process_instance.active_tasks[0]
|
||||||
|
@ -208,11 +208,11 @@ class TestProcessInstanceProcessor(BaseTest):
|
||||||
)
|
)
|
||||||
with pytest.raises(UserDoesNotHaveAccessToTaskError):
|
with pytest.raises(UserDoesNotHaveAccessToTaskError):
|
||||||
ProcessInstanceService.complete_form_task(
|
ProcessInstanceService.complete_form_task(
|
||||||
processor, spiff_task, {}, initiator_user
|
processor, spiff_task, {}, initiator_user, active_task
|
||||||
)
|
)
|
||||||
|
|
||||||
ProcessInstanceService.complete_form_task(
|
ProcessInstanceService.complete_form_task(
|
||||||
processor, spiff_task, {}, finance_user_four
|
processor, spiff_task, {}, finance_user_four, active_task
|
||||||
)
|
)
|
||||||
assert len(process_instance.active_tasks) == 1
|
assert len(process_instance.active_tasks) == 1
|
||||||
active_task = process_instance.active_tasks[0]
|
active_task = process_instance.active_tasks[0]
|
||||||
|
@ -224,7 +224,7 @@ class TestProcessInstanceProcessor(BaseTest):
|
||||||
active_task.task_name, processor.bpmn_process_instance
|
active_task.task_name, processor.bpmn_process_instance
|
||||||
)
|
)
|
||||||
ProcessInstanceService.complete_form_task(
|
ProcessInstanceService.complete_form_task(
|
||||||
processor, spiff_task, {}, initiator_user
|
processor, spiff_task, {}, initiator_user, active_task
|
||||||
)
|
)
|
||||||
|
|
||||||
assert len(process_instance.active_tasks) == 1
|
assert len(process_instance.active_tasks) == 1
|
||||||
|
@ -234,8 +234,10 @@ class TestProcessInstanceProcessor(BaseTest):
|
||||||
)
|
)
|
||||||
with pytest.raises(UserDoesNotHaveAccessToTaskError):
|
with pytest.raises(UserDoesNotHaveAccessToTaskError):
|
||||||
ProcessInstanceService.complete_form_task(
|
ProcessInstanceService.complete_form_task(
|
||||||
processor, spiff_task, {}, initiator_user
|
processor, spiff_task, {}, initiator_user, active_task
|
||||||
|
)
|
||||||
|
ProcessInstanceService.complete_form_task(
|
||||||
|
processor, spiff_task, {}, testadmin1, active_task
|
||||||
)
|
)
|
||||||
ProcessInstanceService.complete_form_task(processor, spiff_task, {}, testadmin1)
|
|
||||||
|
|
||||||
assert process_instance.status == ProcessInstanceStatus.complete.value
|
assert process_instance.status == ProcessInstanceStatus.complete.value
|
||||||
|
|
|
@ -4,9 +4,8 @@ import os
|
||||||
import pytest
|
import pytest
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
from flask.testing import FlaskClient
|
from flask.testing import FlaskClient
|
||||||
from flask_bpmn.api.api_error import ApiError
|
|
||||||
from flask_bpmn.models.db import db
|
from flask_bpmn.models.db import db
|
||||||
from SpiffWorkflow.dmn.parser.BpmnDmnParser import BpmnDmnParser # type: ignore
|
from SpiffWorkflow.bpmn.parser.ValidationException import ValidationException # type: ignore
|
||||||
from tests.spiffworkflow_backend.helpers.base_test import BaseTest
|
from tests.spiffworkflow_backend.helpers.base_test import BaseTest
|
||||||
from tests.spiffworkflow_backend.helpers.test_data import load_test_spec
|
from tests.spiffworkflow_backend.helpers.test_data import load_test_spec
|
||||||
|
|
||||||
|
@ -14,9 +13,7 @@ from spiffworkflow_backend.models.spec_reference import SpecReferenceCache
|
||||||
from spiffworkflow_backend.models.user import UserModel
|
from spiffworkflow_backend.models.user import UserModel
|
||||||
from spiffworkflow_backend.services.process_model_service import ProcessModelService
|
from spiffworkflow_backend.services.process_model_service import ProcessModelService
|
||||||
from spiffworkflow_backend.services.spec_file_service import SpecFileService
|
from spiffworkflow_backend.services.spec_file_service import SpecFileService
|
||||||
from tests.spiffworkflow_backend.helpers.base_test import BaseTest
|
|
||||||
from tests.spiffworkflow_backend.helpers.test_data import load_test_spec
|
|
||||||
from SpiffWorkflow.bpmn.parser.ValidationException import ValidationException
|
|
||||||
|
|
||||||
class TestSpecFileService(BaseTest):
|
class TestSpecFileService(BaseTest):
|
||||||
"""TestSpecFileService."""
|
"""TestSpecFileService."""
|
||||||
|
@ -86,8 +83,9 @@ class TestSpecFileService(BaseTest):
|
||||||
process_model_source_directory="call_activity_duplicate",
|
process_model_source_directory="call_activity_duplicate",
|
||||||
bpmn_file_name="call_activity_nested_duplicate",
|
bpmn_file_name="call_activity_nested_duplicate",
|
||||||
)
|
)
|
||||||
assert f"Process id ({bpmn_process_identifier}) has already been used" in str(
|
assert (
|
||||||
exception.value
|
f"Process id ({bpmn_process_identifier}) has already been used"
|
||||||
|
in str(exception.value)
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_updates_relative_file_path_when_appropriate(
|
def test_updates_relative_file_path_when_appropriate(
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
import ProcessInstanceListTable from './ProcessInstanceListTable';
|
||||||
|
|
||||||
|
const paginationQueryParamPrefix = 'my_completed_instances';
|
||||||
|
|
||||||
|
export default function MyCompletedInstances() {
|
||||||
|
return (
|
||||||
|
<ProcessInstanceListTable
|
||||||
|
filtersEnabled={false}
|
||||||
|
paginationQueryParamPrefix={paginationQueryParamPrefix}
|
||||||
|
perPageOptions={[2, 5, 25]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { Pagination } from '@carbon/react';
|
import { Pagination } from '@carbon/react';
|
||||||
|
@ -13,8 +13,7 @@ type OwnProps = {
|
||||||
perPageOptions?: number[];
|
perPageOptions?: number[];
|
||||||
pagination: PaginationObject | null;
|
pagination: PaginationObject | null;
|
||||||
tableToDisplay: any;
|
tableToDisplay: any;
|
||||||
queryParamString?: string;
|
paginationQueryParamPrefix?: string;
|
||||||
path: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function PaginationForTable({
|
export default function PaginationForTable({
|
||||||
|
@ -23,16 +22,21 @@ export default function PaginationForTable({
|
||||||
perPageOptions,
|
perPageOptions,
|
||||||
pagination,
|
pagination,
|
||||||
tableToDisplay,
|
tableToDisplay,
|
||||||
queryParamString = '',
|
paginationQueryParamPrefix,
|
||||||
path,
|
|
||||||
}: OwnProps) {
|
}: OwnProps) {
|
||||||
const PER_PAGE_OPTIONS = [2, 10, 50, 100];
|
const PER_PAGE_OPTIONS = [2, 10, 50, 100];
|
||||||
const navigate = useNavigate();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
|
const paginationQueryParamPrefixToUse = paginationQueryParamPrefix
|
||||||
|
? `${paginationQueryParamPrefix}_`
|
||||||
|
: '';
|
||||||
|
|
||||||
const updateRows = (args: any) => {
|
const updateRows = (args: any) => {
|
||||||
const newPage = args.page;
|
const newPage = args.page;
|
||||||
const { pageSize } = args;
|
const { pageSize } = args;
|
||||||
navigate(`${path}?page=${newPage}&per_page=${pageSize}${queryParamString}`);
|
|
||||||
|
searchParams.set(`${paginationQueryParamPrefixToUse}page`, newPage);
|
||||||
|
searchParams.set(`${paginationQueryParamPrefixToUse}per_page`, pageSize);
|
||||||
|
setSearchParams(searchParams);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (pagination) {
|
if (pagination) {
|
||||||
|
|
|
@ -0,0 +1,557 @@
|
||||||
|
import { useContext, useEffect, useMemo, useState } from 'react';
|
||||||
|
import {
|
||||||
|
Link,
|
||||||
|
useNavigate,
|
||||||
|
useParams,
|
||||||
|
useSearchParams,
|
||||||
|
} from 'react-router-dom';
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
import { Filter } from '@carbon/icons-react';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
ButtonSet,
|
||||||
|
DatePicker,
|
||||||
|
DatePickerInput,
|
||||||
|
Table,
|
||||||
|
Grid,
|
||||||
|
Column,
|
||||||
|
MultiSelect,
|
||||||
|
TableHeader,
|
||||||
|
TableHead,
|
||||||
|
TableRow,
|
||||||
|
// @ts-ignore
|
||||||
|
} from '@carbon/react';
|
||||||
|
import { PROCESS_STATUSES, DATE_FORMAT, DATE_FORMAT_CARBON } from '../config';
|
||||||
|
import {
|
||||||
|
convertDateStringToSeconds,
|
||||||
|
convertSecondsToFormattedDate,
|
||||||
|
getPageInfoFromSearchParams,
|
||||||
|
getProcessModelFullIdentifierFromSearchParams,
|
||||||
|
modifyProcessModelPath,
|
||||||
|
} from '../helpers';
|
||||||
|
|
||||||
|
import PaginationForTable from './PaginationForTable';
|
||||||
|
import 'react-datepicker/dist/react-datepicker.css';
|
||||||
|
|
||||||
|
import ErrorContext from '../contexts/ErrorContext';
|
||||||
|
import HttpService from '../services/HttpService';
|
||||||
|
|
||||||
|
import 'react-bootstrap-typeahead/css/Typeahead.css';
|
||||||
|
import 'react-bootstrap-typeahead/css/Typeahead.bs5.css';
|
||||||
|
import { PaginationObject, ProcessModel } from '../interfaces';
|
||||||
|
import ProcessModelSearch from './ProcessModelSearch';
|
||||||
|
|
||||||
|
type OwnProps = {
|
||||||
|
filtersEnabled?: boolean;
|
||||||
|
processModelFullIdentifier?: string;
|
||||||
|
paginationQueryParamPrefix?: string;
|
||||||
|
perPageOptions?: number[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function ProcessInstanceListTable({
|
||||||
|
filtersEnabled = true,
|
||||||
|
processModelFullIdentifier,
|
||||||
|
paginationQueryParamPrefix,
|
||||||
|
perPageOptions,
|
||||||
|
}: OwnProps) {
|
||||||
|
const params = useParams();
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const [processInstances, setProcessInstances] = useState([]);
|
||||||
|
const [reportMetadata, setReportMetadata] = useState({});
|
||||||
|
const [pagination, setPagination] = useState<PaginationObject | null>(null);
|
||||||
|
|
||||||
|
const oneHourInSeconds = 3600;
|
||||||
|
const oneMonthInSeconds = oneHourInSeconds * 24 * 30;
|
||||||
|
const [startFrom, setStartFrom] = useState<string>('');
|
||||||
|
const [startTo, setStartTo] = useState<string>('');
|
||||||
|
const [endFrom, setEndFrom] = useState<string>('');
|
||||||
|
const [endTo, setEndTo] = useState<string>('');
|
||||||
|
const [showFilterOptions, setShowFilterOptions] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const setErrorMessage = (useContext as any)(ErrorContext)[1];
|
||||||
|
|
||||||
|
const [processStatusAllOptions, setProcessStatusAllOptions] = useState<any[]>(
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
const [processStatusSelection, setProcessStatusSelection] = useState<
|
||||||
|
string[]
|
||||||
|
>([]);
|
||||||
|
const [processModelAvailableItems, setProcessModelAvailableItems] = useState<
|
||||||
|
ProcessModel[]
|
||||||
|
>([]);
|
||||||
|
const [processModelSelection, setProcessModelSelection] =
|
||||||
|
useState<ProcessModel | null>(null);
|
||||||
|
|
||||||
|
const parametersToAlwaysFilterBy = useMemo(() => {
|
||||||
|
return {
|
||||||
|
start_from: setStartFrom,
|
||||||
|
start_to: setStartTo,
|
||||||
|
end_from: setEndFrom,
|
||||||
|
end_to: setEndTo,
|
||||||
|
};
|
||||||
|
}, [setStartFrom, setStartTo, setEndFrom, setEndTo]);
|
||||||
|
|
||||||
|
const parametersToGetFromSearchParams = useMemo(() => {
|
||||||
|
return {
|
||||||
|
process_model_identifier: null,
|
||||||
|
process_status: null,
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||||
|
useEffect(() => {
|
||||||
|
function setProcessInstancesFromResult(result: any) {
|
||||||
|
const processInstancesFromApi = result.results;
|
||||||
|
setProcessInstances(processInstancesFromApi);
|
||||||
|
setReportMetadata(result.report_metadata);
|
||||||
|
setPagination(result.pagination);
|
||||||
|
}
|
||||||
|
function getProcessInstances() {
|
||||||
|
// eslint-disable-next-line prefer-const
|
||||||
|
let { page, perPage } = getPageInfoFromSearchParams(
|
||||||
|
searchParams,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
paginationQueryParamPrefix
|
||||||
|
);
|
||||||
|
if (perPageOptions && !perPageOptions.includes(perPage)) {
|
||||||
|
// eslint-disable-next-line prefer-destructuring
|
||||||
|
perPage = perPageOptions[1];
|
||||||
|
}
|
||||||
|
let queryParamString = `per_page=${perPage}&page=${page}`;
|
||||||
|
|
||||||
|
Object.keys(parametersToAlwaysFilterBy).forEach((paramName: string) => {
|
||||||
|
// @ts-expect-error TS(7053) FIXME:
|
||||||
|
const functionToCall = parametersToAlwaysFilterBy[paramName];
|
||||||
|
const searchParamValue = searchParams.get(paramName);
|
||||||
|
if (searchParamValue) {
|
||||||
|
queryParamString += `&${paramName}=${searchParamValue}`;
|
||||||
|
const dateString = convertSecondsToFormattedDate(
|
||||||
|
searchParamValue as any
|
||||||
|
);
|
||||||
|
functionToCall(dateString);
|
||||||
|
setShowFilterOptions(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.keys(parametersToGetFromSearchParams).forEach(
|
||||||
|
(paramName: string) => {
|
||||||
|
if (
|
||||||
|
paramName === 'process_model_identifier' &&
|
||||||
|
processModelFullIdentifier
|
||||||
|
) {
|
||||||
|
queryParamString += `&process_model_identifier=${processModelFullIdentifier}`;
|
||||||
|
} else if (searchParams.get(paramName)) {
|
||||||
|
// @ts-expect-error TS(7053) FIXME:
|
||||||
|
const functionToCall = parametersToGetFromSearchParams[paramName];
|
||||||
|
queryParamString += `&${paramName}=${searchParams.get(paramName)}`;
|
||||||
|
if (functionToCall !== null) {
|
||||||
|
functionToCall(searchParams.get(paramName) || '');
|
||||||
|
}
|
||||||
|
setShowFilterOptions(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
HttpService.makeCallToBackend({
|
||||||
|
path: `/process-instances?${queryParamString}`,
|
||||||
|
successCallback: setProcessInstancesFromResult,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function processResultForProcessModels(result: any) {
|
||||||
|
const processModelFullIdentifierFromSearchParams =
|
||||||
|
getProcessModelFullIdentifierFromSearchParams(searchParams);
|
||||||
|
const selectionArray = result.results.map((item: any) => {
|
||||||
|
const label = `${item.id}`;
|
||||||
|
Object.assign(item, { label });
|
||||||
|
if (label === processModelFullIdentifierFromSearchParams) {
|
||||||
|
setProcessModelSelection(item);
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
setProcessModelAvailableItems(selectionArray);
|
||||||
|
|
||||||
|
const processStatusSelectedArray: string[] = [];
|
||||||
|
const processStatusAllOptionsArray = PROCESS_STATUSES.map(
|
||||||
|
(processStatusOption: any) => {
|
||||||
|
const regex = new RegExp(`\\b${processStatusOption}\\b`);
|
||||||
|
if ((searchParams.get('process_status') || '').match(regex)) {
|
||||||
|
processStatusSelectedArray.push(processStatusOption);
|
||||||
|
}
|
||||||
|
return processStatusOption;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
setProcessStatusSelection(processStatusSelectedArray);
|
||||||
|
setProcessStatusAllOptions(processStatusAllOptionsArray);
|
||||||
|
|
||||||
|
getProcessInstances();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filtersEnabled) {
|
||||||
|
// populate process model selection
|
||||||
|
HttpService.makeCallToBackend({
|
||||||
|
path: `/process-models?per_page=1000`,
|
||||||
|
successCallback: processResultForProcessModels,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
getProcessInstances();
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
searchParams,
|
||||||
|
params,
|
||||||
|
oneMonthInSeconds,
|
||||||
|
oneHourInSeconds,
|
||||||
|
parametersToAlwaysFilterBy,
|
||||||
|
parametersToGetFromSearchParams,
|
||||||
|
filtersEnabled,
|
||||||
|
paginationQueryParamPrefix,
|
||||||
|
processModelFullIdentifier,
|
||||||
|
perPageOptions,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 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) => {
|
||||||
|
if (param1 && param2) {
|
||||||
|
switch (operation) {
|
||||||
|
case '<':
|
||||||
|
return param1 < param2;
|
||||||
|
case '>':
|
||||||
|
return param1 > param2;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const applyFilter = (event: any) => {
|
||||||
|
event.preventDefault();
|
||||||
|
const { page, perPage } = getPageInfoFromSearchParams(
|
||||||
|
searchParams,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
paginationQueryParamPrefix
|
||||||
|
);
|
||||||
|
let queryParamString = `per_page=${perPage}&page=${page}`;
|
||||||
|
|
||||||
|
const startFromSeconds = convertDateStringToSeconds(startFrom);
|
||||||
|
const endFromSeconds = convertDateStringToSeconds(endFrom);
|
||||||
|
const startToSeconds = convertDateStringToSeconds(startTo);
|
||||||
|
const endToSeconds = convertDateStringToSeconds(endTo);
|
||||||
|
if (isTrueComparison(startFromSeconds, '>', startToSeconds)) {
|
||||||
|
setErrorMessage({
|
||||||
|
message: '"Start date from" cannot be after "start date to"',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isTrueComparison(endFromSeconds, '>', endToSeconds)) {
|
||||||
|
setErrorMessage({
|
||||||
|
message: '"End date from" cannot be after "end date to"',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isTrueComparison(startFromSeconds, '>', endFromSeconds)) {
|
||||||
|
setErrorMessage({
|
||||||
|
message: '"Start date from" cannot be after "end date from"',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isTrueComparison(startToSeconds, '>', endToSeconds)) {
|
||||||
|
setErrorMessage({
|
||||||
|
message: '"Start date to" cannot be after "end date to"',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startFromSeconds) {
|
||||||
|
queryParamString += `&start_from=${startFromSeconds}`;
|
||||||
|
}
|
||||||
|
if (startToSeconds) {
|
||||||
|
queryParamString += `&start_to=${startToSeconds}`;
|
||||||
|
}
|
||||||
|
if (endFromSeconds) {
|
||||||
|
queryParamString += `&end_from=${endFromSeconds}`;
|
||||||
|
}
|
||||||
|
if (endToSeconds) {
|
||||||
|
queryParamString += `&end_to=${endToSeconds}`;
|
||||||
|
}
|
||||||
|
if (processStatusSelection.length > 0) {
|
||||||
|
queryParamString += `&process_status=${processStatusSelection}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (processModelSelection) {
|
||||||
|
queryParamString += `&process_model_identifier=${processModelSelection.id}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
setErrorMessage(null);
|
||||||
|
navigate(`/admin/process-instances?${queryParamString}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const dateComponent = (
|
||||||
|
labelString: any,
|
||||||
|
name: any,
|
||||||
|
initialDate: any,
|
||||||
|
onChangeFunction: any
|
||||||
|
) => {
|
||||||
|
return (
|
||||||
|
<DatePicker dateFormat={DATE_FORMAT_CARBON} datePickerType="single">
|
||||||
|
<DatePickerInput
|
||||||
|
id={`date-picker-${name}`}
|
||||||
|
placeholder={DATE_FORMAT}
|
||||||
|
labelText={labelString}
|
||||||
|
type="text"
|
||||||
|
size="md"
|
||||||
|
autocomplete="off"
|
||||||
|
allowInput={false}
|
||||||
|
onChange={(dateChangeEvent: any) => {
|
||||||
|
onChangeFunction(dateChangeEvent.srcElement.value);
|
||||||
|
}}
|
||||||
|
value={initialDate}
|
||||||
|
/>
|
||||||
|
</DatePicker>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const processStatusSearch = () => {
|
||||||
|
return (
|
||||||
|
<MultiSelect
|
||||||
|
label="Choose Status"
|
||||||
|
className="our-class"
|
||||||
|
id="process-instance-status-select"
|
||||||
|
titleText="Status"
|
||||||
|
items={processStatusAllOptions}
|
||||||
|
onChange={(selection: any) => {
|
||||||
|
setProcessStatusSelection(selection.selectedItems);
|
||||||
|
}}
|
||||||
|
itemToString={(item: any) => {
|
||||||
|
return item || '';
|
||||||
|
}}
|
||||||
|
selectionFeedback="top-after-reopen"
|
||||||
|
selectedItems={processStatusSelection}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearFilters = () => {
|
||||||
|
setProcessModelSelection(null);
|
||||||
|
setProcessStatusSelection([]);
|
||||||
|
setStartFrom('');
|
||||||
|
setStartTo('');
|
||||||
|
setEndFrom('');
|
||||||
|
setEndTo('');
|
||||||
|
};
|
||||||
|
|
||||||
|
const filterOptions = () => {
|
||||||
|
if (!showFilterOptions) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Grid fullWidth className="with-bottom-margin">
|
||||||
|
<Column md={8}>
|
||||||
|
<ProcessModelSearch
|
||||||
|
onChange={(selection: any) =>
|
||||||
|
setProcessModelSelection(selection.selectedItem)
|
||||||
|
}
|
||||||
|
processModels={processModelAvailableItems}
|
||||||
|
selectedItem={processModelSelection}
|
||||||
|
/>
|
||||||
|
</Column>
|
||||||
|
<Column md={8}>{processStatusSearch()}</Column>
|
||||||
|
</Grid>
|
||||||
|
<Grid fullWidth className="with-bottom-margin">
|
||||||
|
<Column md={4}>
|
||||||
|
{dateComponent(
|
||||||
|
'Start date from',
|
||||||
|
'start-from',
|
||||||
|
startFrom,
|
||||||
|
setStartFrom
|
||||||
|
)}
|
||||||
|
</Column>
|
||||||
|
<Column md={4}>
|
||||||
|
{dateComponent('Start date to', 'start-to', startTo, setStartTo)}
|
||||||
|
</Column>
|
||||||
|
<Column md={4}>
|
||||||
|
{dateComponent('End date from', 'end-from', endFrom, setEndFrom)}
|
||||||
|
</Column>
|
||||||
|
<Column md={4}>
|
||||||
|
{dateComponent('End date to', 'end-to', endTo, setEndTo)}
|
||||||
|
</Column>
|
||||||
|
</Grid>
|
||||||
|
<Grid fullWidth className="with-bottom-margin">
|
||||||
|
<Column md={4}>
|
||||||
|
<ButtonSet>
|
||||||
|
<Button
|
||||||
|
kind=""
|
||||||
|
className="button-white-background"
|
||||||
|
onClick={clearFilters}
|
||||||
|
>
|
||||||
|
Clear
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
kind="secondary"
|
||||||
|
onClick={applyFilter}
|
||||||
|
data-qa="filter-button"
|
||||||
|
>
|
||||||
|
Filter
|
||||||
|
</Button>
|
||||||
|
</ButtonSet>
|
||||||
|
</Column>
|
||||||
|
</Grid>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildTable = () => {
|
||||||
|
const headerLabels: Record<string, string> = {
|
||||||
|
id: 'Process Instance Id',
|
||||||
|
process_model_identifier: 'Process Model',
|
||||||
|
start_in_seconds: 'Start Time',
|
||||||
|
end_in_seconds: 'End Time',
|
||||||
|
status: 'Status',
|
||||||
|
spiff_step: 'SpiffWorkflow Step',
|
||||||
|
};
|
||||||
|
const getHeaderLabel = (header: string) => {
|
||||||
|
return headerLabels[header] ?? header;
|
||||||
|
};
|
||||||
|
const headers = (reportMetadata as any).columns.map((column: any) => {
|
||||||
|
// return <th>{getHeaderLabel((column as any).Header)}</th>;
|
||||||
|
return getHeaderLabel((column as any).Header);
|
||||||
|
});
|
||||||
|
|
||||||
|
const formatProcessInstanceId = (row: any, id: any) => {
|
||||||
|
const modifiedProcessModelId: String = modifyProcessModelPath(
|
||||||
|
row.process_model_identifier
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
data-qa="process-instance-show-link"
|
||||||
|
to={`/admin/process-models/${modifiedProcessModelId}/process-instances/${row.id}`}
|
||||||
|
>
|
||||||
|
{id}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const formatProcessModelIdentifier = (_row: any, identifier: any) => {
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
to={`/admin/process-models/${modifyProcessModelPath(identifier)}`}
|
||||||
|
>
|
||||||
|
{identifier}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const formatSecondsForDisplay = (_row: any, seconds: any) => {
|
||||||
|
return convertSecondsToFormattedDate(seconds) || '-';
|
||||||
|
};
|
||||||
|
const defaultFormatter = (_row: any, value: any) => {
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
const columnFormatters: Record<string, any> = {
|
||||||
|
id: formatProcessInstanceId,
|
||||||
|
process_model_identifier: formatProcessModelIdentifier,
|
||||||
|
start_in_seconds: formatSecondsForDisplay,
|
||||||
|
end_in_seconds: formatSecondsForDisplay,
|
||||||
|
};
|
||||||
|
const formattedColumn = (row: any, column: any) => {
|
||||||
|
const formatter = columnFormatters[column.accessor] ?? defaultFormatter;
|
||||||
|
const value = row[column.accessor];
|
||||||
|
if (column.accessor === 'status') {
|
||||||
|
return (
|
||||||
|
<td data-qa={`process-instance-status-${value}`}>
|
||||||
|
{formatter(row, value)}
|
||||||
|
</td>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return <td>{formatter(row, value)}</td>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const rows = processInstances.map((row: any) => {
|
||||||
|
const currentRow = (reportMetadata as any).columns.map((column: any) => {
|
||||||
|
return formattedColumn(row, column);
|
||||||
|
});
|
||||||
|
return <tr key={row.id}>{currentRow}</tr>;
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Table size="lg">
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
{headers.map((header: any) => (
|
||||||
|
<TableHeader key={header}>{header}</TableHeader>
|
||||||
|
))}
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<tbody>{rows}</tbody>
|
||||||
|
</Table>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleShowFilterOptions = () => {
|
||||||
|
setShowFilterOptions(!showFilterOptions);
|
||||||
|
};
|
||||||
|
|
||||||
|
const filterComponent = () => {
|
||||||
|
if (!filtersEnabled) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Grid fullWidth>
|
||||||
|
<Column
|
||||||
|
sm={{ span: 1, offset: 3 }}
|
||||||
|
md={{ span: 1, offset: 7 }}
|
||||||
|
lg={{ span: 1, offset: 15 }}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
data-qa="filter-section-expand-toggle"
|
||||||
|
kind="ghost"
|
||||||
|
renderIcon={Filter}
|
||||||
|
iconDescription="Filter Options"
|
||||||
|
hasIconOnly
|
||||||
|
size="lg"
|
||||||
|
onClick={toggleShowFilterOptions}
|
||||||
|
/>
|
||||||
|
</Column>
|
||||||
|
</Grid>
|
||||||
|
{filterOptions()}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (pagination) {
|
||||||
|
// eslint-disable-next-line prefer-const
|
||||||
|
let { page, perPage } = getPageInfoFromSearchParams(
|
||||||
|
searchParams,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
paginationQueryParamPrefix
|
||||||
|
);
|
||||||
|
if (perPageOptions && !perPageOptions.includes(perPage)) {
|
||||||
|
// eslint-disable-next-line prefer-destructuring
|
||||||
|
perPage = perPageOptions[1];
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{filterComponent()}
|
||||||
|
<br />
|
||||||
|
<PaginationForTable
|
||||||
|
page={page}
|
||||||
|
perPage={perPage}
|
||||||
|
pagination={pagination}
|
||||||
|
tableToDisplay={buildTable()}
|
||||||
|
paginationQueryParamPrefix={paginationQueryParamPrefix}
|
||||||
|
perPageOptions={perPageOptions}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
|
@ -12,6 +12,7 @@ import HttpService from '../services/HttpService';
|
||||||
import { PaginationObject } from '../interfaces';
|
import { PaginationObject } from '../interfaces';
|
||||||
|
|
||||||
const PER_PAGE_FOR_TASKS_ON_HOME_PAGE = 5;
|
const PER_PAGE_FOR_TASKS_ON_HOME_PAGE = 5;
|
||||||
|
const paginationQueryParamPrefix = 'tasks_for_my_open_processes';
|
||||||
|
|
||||||
export default function MyOpenProcesses() {
|
export default function MyOpenProcesses() {
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
|
@ -21,7 +22,9 @@ export default function MyOpenProcesses() {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const { page, perPage } = getPageInfoFromSearchParams(
|
const { page, perPage } = getPageInfoFromSearchParams(
|
||||||
searchParams,
|
searchParams,
|
||||||
PER_PAGE_FOR_TASKS_ON_HOME_PAGE
|
PER_PAGE_FOR_TASKS_ON_HOME_PAGE,
|
||||||
|
undefined,
|
||||||
|
paginationQueryParamPrefix
|
||||||
);
|
);
|
||||||
const setTasksFromResult = (result: any) => {
|
const setTasksFromResult = (result: any) => {
|
||||||
setTasks(result.results);
|
setTasks(result.results);
|
||||||
|
@ -113,7 +116,9 @@ export default function MyOpenProcesses() {
|
||||||
}
|
}
|
||||||
const { page, perPage } = getPageInfoFromSearchParams(
|
const { page, perPage } = getPageInfoFromSearchParams(
|
||||||
searchParams,
|
searchParams,
|
||||||
PER_PAGE_FOR_TASKS_ON_HOME_PAGE
|
PER_PAGE_FOR_TASKS_ON_HOME_PAGE,
|
||||||
|
undefined,
|
||||||
|
paginationQueryParamPrefix
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -124,7 +129,7 @@ export default function MyOpenProcesses() {
|
||||||
perPageOptions={[2, PER_PAGE_FOR_TASKS_ON_HOME_PAGE, 25]}
|
perPageOptions={[2, PER_PAGE_FOR_TASKS_ON_HOME_PAGE, 25]}
|
||||||
pagination={pagination}
|
pagination={pagination}
|
||||||
tableToDisplay={buildTable()}
|
tableToDisplay={buildTable()}
|
||||||
path="/tasks/for-my-open-processes"
|
paginationQueryParamPrefix={paginationQueryParamPrefix}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -13,7 +13,7 @@ import { PaginationObject } from '../interfaces';
|
||||||
|
|
||||||
const PER_PAGE_FOR_TASKS_ON_HOME_PAGE = 5;
|
const PER_PAGE_FOR_TASKS_ON_HOME_PAGE = 5;
|
||||||
|
|
||||||
export default function MyTasksForProcessesStartedByOthers() {
|
export default function TasksWaitingForMe() {
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const [tasks, setTasks] = useState([]);
|
const [tasks, setTasks] = useState([]);
|
||||||
const [pagination, setPagination] = useState<PaginationObject | null>(null);
|
const [pagination, setPagination] = useState<PaginationObject | null>(null);
|
||||||
|
@ -21,14 +21,16 @@ export default function MyTasksForProcessesStartedByOthers() {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const { page, perPage } = getPageInfoFromSearchParams(
|
const { page, perPage } = getPageInfoFromSearchParams(
|
||||||
searchParams,
|
searchParams,
|
||||||
PER_PAGE_FOR_TASKS_ON_HOME_PAGE
|
PER_PAGE_FOR_TASKS_ON_HOME_PAGE,
|
||||||
|
undefined,
|
||||||
|
'tasks_waiting_for_me'
|
||||||
);
|
);
|
||||||
const setTasksFromResult = (result: any) => {
|
const setTasksFromResult = (result: any) => {
|
||||||
setTasks(result.results);
|
setTasks(result.results);
|
||||||
setPagination(result.pagination);
|
setPagination(result.pagination);
|
||||||
};
|
};
|
||||||
HttpService.makeCallToBackend({
|
HttpService.makeCallToBackend({
|
||||||
path: `/tasks/for-processes-started-by-others?per_page=${perPage}&page=${page}`,
|
path: `/tasks/for-me?per_page=${perPage}&page=${page}`,
|
||||||
successCallback: setTasksFromResult,
|
successCallback: setTasksFromResult,
|
||||||
});
|
});
|
||||||
}, [searchParams]);
|
}, [searchParams]);
|
||||||
|
@ -115,7 +117,9 @@ export default function MyTasksForProcessesStartedByOthers() {
|
||||||
}
|
}
|
||||||
const { page, perPage } = getPageInfoFromSearchParams(
|
const { page, perPage } = getPageInfoFromSearchParams(
|
||||||
searchParams,
|
searchParams,
|
||||||
PER_PAGE_FOR_TASKS_ON_HOME_PAGE
|
PER_PAGE_FOR_TASKS_ON_HOME_PAGE,
|
||||||
|
undefined,
|
||||||
|
'tasks_waiting_for_me'
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -126,7 +130,7 @@ export default function MyTasksForProcessesStartedByOthers() {
|
||||||
perPageOptions={[2, PER_PAGE_FOR_TASKS_ON_HOME_PAGE, 25]}
|
perPageOptions={[2, PER_PAGE_FOR_TASKS_ON_HOME_PAGE, 25]}
|
||||||
pagination={pagination}
|
pagination={pagination}
|
||||||
tableToDisplay={buildTable()}
|
tableToDisplay={buildTable()}
|
||||||
path="/tasks/for-my-open-processes"
|
paginationQueryParamPrefix="tasks_waiting_for_me"
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
|
@ -0,0 +1,144 @@
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
// @ts-ignore
|
||||||
|
import { Button, Table } from '@carbon/react';
|
||||||
|
import { Link, useSearchParams } from 'react-router-dom';
|
||||||
|
import PaginationForTable from './PaginationForTable';
|
||||||
|
import {
|
||||||
|
convertSecondsToFormattedDateTime,
|
||||||
|
getPageInfoFromSearchParams,
|
||||||
|
modifyProcessModelPath,
|
||||||
|
} from '../helpers';
|
||||||
|
import HttpService from '../services/HttpService';
|
||||||
|
import { PaginationObject } from '../interfaces';
|
||||||
|
|
||||||
|
const PER_PAGE_FOR_TASKS_ON_HOME_PAGE = 5;
|
||||||
|
const paginationQueryParamPrefix = 'tasks_waiting_for_my_groups';
|
||||||
|
|
||||||
|
export default function TasksForWaitingForMyGroups() {
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
const [tasks, setTasks] = useState([]);
|
||||||
|
const [pagination, setPagination] = useState<PaginationObject | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const { page, perPage } = getPageInfoFromSearchParams(
|
||||||
|
searchParams,
|
||||||
|
PER_PAGE_FOR_TASKS_ON_HOME_PAGE,
|
||||||
|
undefined,
|
||||||
|
paginationQueryParamPrefix
|
||||||
|
);
|
||||||
|
const setTasksFromResult = (result: any) => {
|
||||||
|
setTasks(result.results);
|
||||||
|
setPagination(result.pagination);
|
||||||
|
};
|
||||||
|
HttpService.makeCallToBackend({
|
||||||
|
path: `/tasks/for-my-groups?per_page=${perPage}&page=${page}`,
|
||||||
|
successCallback: setTasksFromResult,
|
||||||
|
});
|
||||||
|
}, [searchParams]);
|
||||||
|
|
||||||
|
const buildTable = () => {
|
||||||
|
const rows = tasks.map((row) => {
|
||||||
|
const rowToUse = row as any;
|
||||||
|
const taskUrl = `/tasks/${rowToUse.process_instance_id}/${rowToUse.task_id}`;
|
||||||
|
const modifiedProcessModelIdentifier = modifyProcessModelPath(
|
||||||
|
rowToUse.process_model_identifier
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<tr key={rowToUse.id}>
|
||||||
|
<td>
|
||||||
|
<Link
|
||||||
|
data-qa="process-model-show-link"
|
||||||
|
to={`/admin/process-models/${modifiedProcessModelIdentifier}`}
|
||||||
|
>
|
||||||
|
{rowToUse.process_model_display_name}
|
||||||
|
</Link>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<Link
|
||||||
|
data-qa="process-instance-show-link"
|
||||||
|
to={`/admin/process-models/${modifiedProcessModelIdentifier}/process-instances/${rowToUse.process_instance_id}`}
|
||||||
|
>
|
||||||
|
View {rowToUse.process_instance_id}
|
||||||
|
</Link>
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
title={`task id: ${rowToUse.name}, spiffworkflow task guid: ${rowToUse.id}`}
|
||||||
|
>
|
||||||
|
{rowToUse.task_title}
|
||||||
|
</td>
|
||||||
|
<td>{rowToUse.username}</td>
|
||||||
|
<td>{rowToUse.process_instance_status}</td>
|
||||||
|
<td>{rowToUse.group_identifier || '-'}</td>
|
||||||
|
<td>
|
||||||
|
{convertSecondsToFormattedDateTime(
|
||||||
|
rowToUse.created_at_in_seconds
|
||||||
|
) || '-'}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{convertSecondsToFormattedDateTime(
|
||||||
|
rowToUse.updated_at_in_seconds
|
||||||
|
) || '-'}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
href={taskUrl}
|
||||||
|
hidden={rowToUse.process_instance_status === 'suspended'}
|
||||||
|
disabled={!rowToUse.current_user_is_potential_owner}
|
||||||
|
>
|
||||||
|
Go
|
||||||
|
</Button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<Table striped bordered>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Process Model</th>
|
||||||
|
<th>Process Instance</th>
|
||||||
|
<th>Task Name</th>
|
||||||
|
<th>Process Started By</th>
|
||||||
|
<th>Process Instance Status</th>
|
||||||
|
<th>Assigned Group</th>
|
||||||
|
<th>Process Started</th>
|
||||||
|
<th>Process Updated</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>{rows}</tbody>
|
||||||
|
</Table>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const tasksComponent = () => {
|
||||||
|
if (pagination && pagination.total < 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const { page, perPage } = getPageInfoFromSearchParams(
|
||||||
|
searchParams,
|
||||||
|
PER_PAGE_FOR_TASKS_ON_HOME_PAGE,
|
||||||
|
undefined,
|
||||||
|
paginationQueryParamPrefix
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h1>Tasks waiting for my groups</h1>
|
||||||
|
<PaginationForTable
|
||||||
|
page={page}
|
||||||
|
perPage={perPage}
|
||||||
|
perPageOptions={[2, PER_PAGE_FOR_TASKS_ON_HOME_PAGE, 25]}
|
||||||
|
pagination={pagination}
|
||||||
|
tableToDisplay={buildTable()}
|
||||||
|
paginationQueryParamPrefix={paginationQueryParamPrefix}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (pagination) {
|
||||||
|
return tasksComponent();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
|
@ -79,11 +79,20 @@ export const objectIsEmpty = (obj: object) => {
|
||||||
export const getPageInfoFromSearchParams = (
|
export const getPageInfoFromSearchParams = (
|
||||||
searchParams: any,
|
searchParams: any,
|
||||||
defaultPerPage: string | number = DEFAULT_PER_PAGE,
|
defaultPerPage: string | number = DEFAULT_PER_PAGE,
|
||||||
defaultPage: string | number = DEFAULT_PAGE
|
defaultPage: string | number = DEFAULT_PAGE,
|
||||||
|
paginationQueryParamPrefix: string | null = null
|
||||||
) => {
|
) => {
|
||||||
const page = parseInt(searchParams.get('page') || defaultPage.toString(), 10);
|
const paginationQueryParamPrefixToUse = paginationQueryParamPrefix
|
||||||
|
? `${paginationQueryParamPrefix}_`
|
||||||
|
: '';
|
||||||
|
const page = parseInt(
|
||||||
|
searchParams.get(`${paginationQueryParamPrefixToUse}page`) ||
|
||||||
|
defaultPage.toString(),
|
||||||
|
10
|
||||||
|
);
|
||||||
const perPage = parseInt(
|
const perPage = parseInt(
|
||||||
searchParams.get('per_page') || defaultPerPage.toString(),
|
searchParams.get(`${paginationQueryParamPrefixToUse}per_page`) ||
|
||||||
|
defaultPerPage.toString(),
|
||||||
10
|
10
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
import MyCompletedInstances from '../components/MyCompletedInstances';
|
||||||
|
|
||||||
|
export default function CompletedInstances() {
|
||||||
|
return <MyCompletedInstances />;
|
||||||
|
}
|
|
@ -1,12 +1,15 @@
|
||||||
import MyTasksForProcessesStartedByOthers from '../components/MyTasksForProcessesStartedByOthers';
|
|
||||||
import TasksForMyOpenProcesses from '../components/TasksForMyOpenProcesses';
|
import TasksForMyOpenProcesses from '../components/TasksForMyOpenProcesses';
|
||||||
|
import TasksWaitingForMe from '../components/TasksWaitingForMe';
|
||||||
|
import TasksForWaitingForMyGroups from '../components/TasksWaitingForMyGroups';
|
||||||
|
|
||||||
export default function GroupedTasks() {
|
export default function GroupedTasks() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TasksForMyOpenProcesses />
|
<TasksForMyOpenProcesses />
|
||||||
<br />
|
<br />
|
||||||
<MyTasksForProcessesStartedByOthers />
|
<TasksWaitingForMe />
|
||||||
|
<br />
|
||||||
|
<TasksForWaitingForMyGroups />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import TaskShow from './TaskShow';
|
||||||
import ErrorContext from '../contexts/ErrorContext';
|
import ErrorContext from '../contexts/ErrorContext';
|
||||||
import MyTasks from './MyTasks';
|
import MyTasks from './MyTasks';
|
||||||
import GroupedTasks from './GroupedTasks';
|
import GroupedTasks from './GroupedTasks';
|
||||||
|
import CompletedInstances from './CompletedInstances';
|
||||||
|
|
||||||
export default function HomePageRoutes() {
|
export default function HomePageRoutes() {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
@ -18,6 +19,8 @@ export default function HomePageRoutes() {
|
||||||
let newSelectedTabIndex = 0;
|
let newSelectedTabIndex = 0;
|
||||||
if (location.pathname.match(/^\/tasks\/grouped\b/)) {
|
if (location.pathname.match(/^\/tasks\/grouped\b/)) {
|
||||||
newSelectedTabIndex = 1;
|
newSelectedTabIndex = 1;
|
||||||
|
} else if (location.pathname.match(/^\/tasks\/completed-instances\b/)) {
|
||||||
|
newSelectedTabIndex = 2;
|
||||||
}
|
}
|
||||||
setSelectedTabIndex(newSelectedTabIndex);
|
setSelectedTabIndex(newSelectedTabIndex);
|
||||||
}, [location, setErrorMessage]);
|
}, [location, setErrorMessage]);
|
||||||
|
@ -28,6 +31,9 @@ export default function HomePageRoutes() {
|
||||||
<TabList aria-label="List of tabs">
|
<TabList aria-label="List of tabs">
|
||||||
<Tab onClick={() => navigate('/tasks/my-tasks')}>My Tasks</Tab>
|
<Tab onClick={() => navigate('/tasks/my-tasks')}>My Tasks</Tab>
|
||||||
<Tab onClick={() => navigate('/tasks/grouped')}>Grouped Tasks</Tab>
|
<Tab onClick={() => navigate('/tasks/grouped')}>Grouped Tasks</Tab>
|
||||||
|
<Tab onClick={() => navigate('/tasks/completed-instances')}>
|
||||||
|
Completed Instances
|
||||||
|
</Tab>
|
||||||
</TabList>
|
</TabList>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
<br />
|
<br />
|
||||||
|
@ -36,6 +42,7 @@ export default function HomePageRoutes() {
|
||||||
<Route path="my-tasks" element={<MyTasks />} />
|
<Route path="my-tasks" element={<MyTasks />} />
|
||||||
<Route path=":process_instance_id/:task_id" element={<TaskShow />} />
|
<Route path=":process_instance_id/:task_id" element={<TaskShow />} />
|
||||||
<Route path="grouped" element={<GroupedTasks />} />
|
<Route path="grouped" element={<GroupedTasks />} />
|
||||||
|
<Route path="completed-instances" element={<CompletedInstances />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -94,14 +94,8 @@ export default function MessageInstanceList() {
|
||||||
|
|
||||||
if (pagination) {
|
if (pagination) {
|
||||||
const { page, perPage } = getPageInfoFromSearchParams(searchParams);
|
const { page, perPage } = getPageInfoFromSearchParams(searchParams);
|
||||||
let queryParamString = '';
|
|
||||||
let breadcrumbElement = null;
|
let breadcrumbElement = null;
|
||||||
if (searchParams.get('process_instance_id')) {
|
if (searchParams.get('process_instance_id')) {
|
||||||
queryParamString += `&process_group_id=${searchParams.get(
|
|
||||||
'process_group_id'
|
|
||||||
)}&process_model_id=${searchParams.get(
|
|
||||||
'process_model_id'
|
|
||||||
)}&process_instance_id=${searchParams.get('process_instance_id')}`;
|
|
||||||
breadcrumbElement = (
|
breadcrumbElement = (
|
||||||
<ProcessBreadcrumb
|
<ProcessBreadcrumb
|
||||||
hotCrumbs={[
|
hotCrumbs={[
|
||||||
|
@ -132,8 +126,6 @@ export default function MessageInstanceList() {
|
||||||
perPage={perPage}
|
perPage={perPage}
|
||||||
pagination={pagination}
|
pagination={pagination}
|
||||||
tableToDisplay={buildTable()}
|
tableToDisplay={buildTable()}
|
||||||
queryParamString={queryParamString}
|
|
||||||
path="/admin/messages"
|
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -152,7 +152,6 @@ export default function MyTasks() {
|
||||||
perPageOptions={[2, PER_PAGE_FOR_TASKS_ON_HOME_PAGE, 25]}
|
perPageOptions={[2, PER_PAGE_FOR_TASKS_ON_HOME_PAGE, 25]}
|
||||||
pagination={pagination}
|
pagination={pagination}
|
||||||
tableToDisplay={buildTable()}
|
tableToDisplay={buildTable()}
|
||||||
path="/tasks"
|
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -109,7 +109,6 @@ export default function ProcessGroupList() {
|
||||||
perPage={perPage}
|
perPage={perPage}
|
||||||
pagination={pagination as any}
|
pagination={pagination as any}
|
||||||
tableToDisplay={buildTable()}
|
tableToDisplay={buildTable()}
|
||||||
path="/admin/process-groups"
|
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -170,7 +170,6 @@ export default function ProcessGroupShow() {
|
||||||
perPage={perPage}
|
perPage={perPage}
|
||||||
pagination={modelPagination}
|
pagination={modelPagination}
|
||||||
tableToDisplay={buildModelTable()}
|
tableToDisplay={buildModelTable()}
|
||||||
path={`/admin/process-groups/${processGroup.id}`}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<br />
|
<br />
|
||||||
|
@ -182,7 +181,6 @@ export default function ProcessGroupShow() {
|
||||||
perPage={perPage}
|
perPage={perPage}
|
||||||
pagination={groupPagination}
|
pagination={groupPagination}
|
||||||
tableToDisplay={buildGroupTable()}
|
tableToDisplay={buildGroupTable()}
|
||||||
path={`/admin/process-groups/${processGroup.id}`}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -1,482 +1,15 @@
|
||||||
import { useContext, useEffect, useMemo, useState } from 'react';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
import {
|
|
||||||
Link,
|
|
||||||
useNavigate,
|
|
||||||
useParams,
|
|
||||||
useSearchParams,
|
|
||||||
} from 'react-router-dom';
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
import { Filter } from '@carbon/icons-react';
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
ButtonSet,
|
|
||||||
DatePicker,
|
|
||||||
DatePickerInput,
|
|
||||||
Table,
|
|
||||||
Grid,
|
|
||||||
Column,
|
|
||||||
MultiSelect,
|
|
||||||
TableHeader,
|
|
||||||
TableHead,
|
|
||||||
TableRow,
|
|
||||||
// @ts-ignore
|
|
||||||
} from '@carbon/react';
|
|
||||||
import { PROCESS_STATUSES, DATE_FORMAT, DATE_FORMAT_CARBON } from '../config';
|
|
||||||
import {
|
|
||||||
convertDateStringToSeconds,
|
|
||||||
convertSecondsToFormattedDate,
|
|
||||||
getPageInfoFromSearchParams,
|
|
||||||
getProcessModelFullIdentifierFromSearchParams,
|
|
||||||
modifyProcessModelPath,
|
|
||||||
} from '../helpers';
|
|
||||||
|
|
||||||
import PaginationForTable from '../components/PaginationForTable';
|
|
||||||
import 'react-datepicker/dist/react-datepicker.css';
|
import 'react-datepicker/dist/react-datepicker.css';
|
||||||
|
|
||||||
import ErrorContext from '../contexts/ErrorContext';
|
|
||||||
import HttpService from '../services/HttpService';
|
|
||||||
|
|
||||||
import 'react-bootstrap-typeahead/css/Typeahead.css';
|
import 'react-bootstrap-typeahead/css/Typeahead.css';
|
||||||
import 'react-bootstrap-typeahead/css/Typeahead.bs5.css';
|
import 'react-bootstrap-typeahead/css/Typeahead.bs5.css';
|
||||||
import { PaginationObject, ProcessModel } from '../interfaces';
|
|
||||||
import ProcessModelSearch from '../components/ProcessModelSearch';
|
|
||||||
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
|
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
|
||||||
|
import ProcessInstanceListTable from '../components/ProcessInstanceListTable';
|
||||||
|
import { getProcessModelFullIdentifierFromSearchParams } from '../helpers';
|
||||||
|
|
||||||
export default function ProcessInstanceList() {
|
export default function ProcessInstanceList() {
|
||||||
const params = useParams();
|
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
const [processInstances, setProcessInstances] = useState([]);
|
|
||||||
const [reportMetadata, setReportMetadata] = useState({});
|
|
||||||
const [pagination, setPagination] = useState<PaginationObject | null>(null);
|
|
||||||
|
|
||||||
const oneHourInSeconds = 3600;
|
|
||||||
const oneMonthInSeconds = oneHourInSeconds * 24 * 30;
|
|
||||||
const [startFrom, setStartFrom] = useState<string>('');
|
|
||||||
const [startTo, setStartTo] = useState<string>('');
|
|
||||||
const [endFrom, setEndFrom] = useState<string>('');
|
|
||||||
const [endTo, setEndTo] = useState<string>('');
|
|
||||||
const [showFilterOptions, setShowFilterOptions] = useState<boolean>(false);
|
|
||||||
|
|
||||||
const setErrorMessage = (useContext as any)(ErrorContext)[1];
|
|
||||||
|
|
||||||
const [processStatusAllOptions, setProcessStatusAllOptions] = useState<any[]>(
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
const [processStatusSelection, setProcessStatusSelection] = useState<
|
|
||||||
string[]
|
|
||||||
>([]);
|
|
||||||
const [processModelAvailableItems, setProcessModelAvailableItems] = useState<
|
|
||||||
ProcessModel[]
|
|
||||||
>([]);
|
|
||||||
const [processModelSelection, setProcessModelSelection] =
|
|
||||||
useState<ProcessModel | null>(null);
|
|
||||||
|
|
||||||
const parametersToAlwaysFilterBy = useMemo(() => {
|
|
||||||
return {
|
|
||||||
start_from: setStartFrom,
|
|
||||||
start_to: setStartTo,
|
|
||||||
end_from: setEndFrom,
|
|
||||||
end_to: setEndTo,
|
|
||||||
};
|
|
||||||
}, [setStartFrom, setStartTo, setEndFrom, setEndTo]);
|
|
||||||
|
|
||||||
const parametersToGetFromSearchParams = useMemo(() => {
|
|
||||||
return {
|
|
||||||
process_model_identifier: null,
|
|
||||||
process_status: null,
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
|
||||||
useEffect(() => {
|
|
||||||
function setProcessInstancesFromResult(result: any) {
|
|
||||||
const processInstancesFromApi = result.results;
|
|
||||||
setProcessInstances(processInstancesFromApi);
|
|
||||||
setReportMetadata(result.report_metadata);
|
|
||||||
setPagination(result.pagination);
|
|
||||||
}
|
|
||||||
function getProcessInstances() {
|
|
||||||
const { page, perPage } = getPageInfoFromSearchParams(searchParams);
|
|
||||||
let queryParamString = `per_page=${perPage}&page=${page}`;
|
|
||||||
|
|
||||||
Object.keys(parametersToAlwaysFilterBy).forEach((paramName: string) => {
|
|
||||||
// @ts-expect-error TS(7053) FIXME:
|
|
||||||
const functionToCall = parametersToAlwaysFilterBy[paramName];
|
|
||||||
const searchParamValue = searchParams.get(paramName);
|
|
||||||
if (searchParamValue) {
|
|
||||||
queryParamString += `&${paramName}=${searchParamValue}`;
|
|
||||||
const dateString = convertSecondsToFormattedDate(
|
|
||||||
searchParamValue as any
|
|
||||||
);
|
|
||||||
functionToCall(dateString);
|
|
||||||
setShowFilterOptions(true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Object.keys(parametersToGetFromSearchParams).forEach(
|
|
||||||
(paramName: string) => {
|
|
||||||
if (searchParams.get(paramName)) {
|
|
||||||
// @ts-expect-error TS(7053) FIXME:
|
|
||||||
const functionToCall = parametersToGetFromSearchParams[paramName];
|
|
||||||
queryParamString += `&${paramName}=${searchParams.get(paramName)}`;
|
|
||||||
if (functionToCall !== null) {
|
|
||||||
functionToCall(searchParams.get(paramName) || '');
|
|
||||||
}
|
|
||||||
setShowFilterOptions(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
HttpService.makeCallToBackend({
|
|
||||||
path: `/process-instances?${queryParamString}`,
|
|
||||||
successCallback: setProcessInstancesFromResult,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
function processResultForProcessModels(result: any) {
|
|
||||||
const processModelFullIdentifier =
|
|
||||||
getProcessModelFullIdentifierFromSearchParams(searchParams);
|
|
||||||
const selectionArray = result.results.map((item: any) => {
|
|
||||||
const label = `${item.id}`;
|
|
||||||
Object.assign(item, { label });
|
|
||||||
if (label === processModelFullIdentifier) {
|
|
||||||
setProcessModelSelection(item);
|
|
||||||
}
|
|
||||||
return item;
|
|
||||||
});
|
|
||||||
setProcessModelAvailableItems(selectionArray);
|
|
||||||
|
|
||||||
const processStatusSelectedArray: string[] = [];
|
|
||||||
const processStatusAllOptionsArray = PROCESS_STATUSES.map(
|
|
||||||
(processStatusOption: any) => {
|
|
||||||
const regex = new RegExp(`\\b${processStatusOption}\\b`);
|
|
||||||
if ((searchParams.get('process_status') || '').match(regex)) {
|
|
||||||
processStatusSelectedArray.push(processStatusOption);
|
|
||||||
}
|
|
||||||
return processStatusOption;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
setProcessStatusSelection(processStatusSelectedArray);
|
|
||||||
setProcessStatusAllOptions(processStatusAllOptionsArray);
|
|
||||||
|
|
||||||
getProcessInstances();
|
|
||||||
}
|
|
||||||
|
|
||||||
// populate process model selection
|
|
||||||
HttpService.makeCallToBackend({
|
|
||||||
path: `/process-models?per_page=1000`,
|
|
||||||
successCallback: processResultForProcessModels,
|
|
||||||
});
|
|
||||||
}, [
|
|
||||||
searchParams,
|
|
||||||
params,
|
|
||||||
oneMonthInSeconds,
|
|
||||||
oneHourInSeconds,
|
|
||||||
parametersToAlwaysFilterBy,
|
|
||||||
parametersToGetFromSearchParams,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// 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) => {
|
|
||||||
if (param1 && param2) {
|
|
||||||
switch (operation) {
|
|
||||||
case '<':
|
|
||||||
return param1 < param2;
|
|
||||||
case '>':
|
|
||||||
return param1 > param2;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const applyFilter = (event: any) => {
|
|
||||||
event.preventDefault();
|
|
||||||
const { page, perPage } = getPageInfoFromSearchParams(searchParams);
|
|
||||||
let queryParamString = `per_page=${perPage}&page=${page}`;
|
|
||||||
|
|
||||||
const startFromSeconds = convertDateStringToSeconds(startFrom);
|
|
||||||
const endFromSeconds = convertDateStringToSeconds(endFrom);
|
|
||||||
const startToSeconds = convertDateStringToSeconds(startTo);
|
|
||||||
const endToSeconds = convertDateStringToSeconds(endTo);
|
|
||||||
if (isTrueComparison(startFromSeconds, '>', startToSeconds)) {
|
|
||||||
setErrorMessage({
|
|
||||||
message: '"Start date from" cannot be after "start date to"',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (isTrueComparison(endFromSeconds, '>', endToSeconds)) {
|
|
||||||
setErrorMessage({
|
|
||||||
message: '"End date from" cannot be after "end date to"',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (isTrueComparison(startFromSeconds, '>', endFromSeconds)) {
|
|
||||||
setErrorMessage({
|
|
||||||
message: '"Start date from" cannot be after "end date from"',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (isTrueComparison(startToSeconds, '>', endToSeconds)) {
|
|
||||||
setErrorMessage({
|
|
||||||
message: '"Start date to" cannot be after "end date to"',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (startFromSeconds) {
|
|
||||||
queryParamString += `&start_from=${startFromSeconds}`;
|
|
||||||
}
|
|
||||||
if (startToSeconds) {
|
|
||||||
queryParamString += `&start_to=${startToSeconds}`;
|
|
||||||
}
|
|
||||||
if (endFromSeconds) {
|
|
||||||
queryParamString += `&end_from=${endFromSeconds}`;
|
|
||||||
}
|
|
||||||
if (endToSeconds) {
|
|
||||||
queryParamString += `&end_to=${endToSeconds}`;
|
|
||||||
}
|
|
||||||
if (processStatusSelection.length > 0) {
|
|
||||||
queryParamString += `&process_status=${processStatusSelection}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (processModelSelection) {
|
|
||||||
queryParamString += `&process_model_identifier=${processModelSelection.id}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
setErrorMessage(null);
|
|
||||||
navigate(`/admin/process-instances?${queryParamString}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
const dateComponent = (
|
|
||||||
labelString: any,
|
|
||||||
name: any,
|
|
||||||
initialDate: any,
|
|
||||||
onChangeFunction: any
|
|
||||||
) => {
|
|
||||||
return (
|
|
||||||
<DatePicker dateFormat={DATE_FORMAT_CARBON} datePickerType="single">
|
|
||||||
<DatePickerInput
|
|
||||||
id={`date-picker-${name}`}
|
|
||||||
placeholder={DATE_FORMAT}
|
|
||||||
labelText={labelString}
|
|
||||||
type="text"
|
|
||||||
size="md"
|
|
||||||
autocomplete="off"
|
|
||||||
allowInput={false}
|
|
||||||
onChange={(dateChangeEvent: any) => {
|
|
||||||
onChangeFunction(dateChangeEvent.srcElement.value);
|
|
||||||
}}
|
|
||||||
value={initialDate}
|
|
||||||
/>
|
|
||||||
</DatePicker>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
const processStatusSearch = () => {
|
|
||||||
return (
|
|
||||||
<MultiSelect
|
|
||||||
label="Choose Status"
|
|
||||||
className="our-class"
|
|
||||||
id="process-instance-status-select"
|
|
||||||
titleText="Status"
|
|
||||||
items={processStatusAllOptions}
|
|
||||||
onChange={(selection: any) => {
|
|
||||||
setProcessStatusSelection(selection.selectedItems);
|
|
||||||
}}
|
|
||||||
itemToString={(item: any) => {
|
|
||||||
return item || '';
|
|
||||||
}}
|
|
||||||
selectionFeedback="top-after-reopen"
|
|
||||||
selectedItems={processStatusSelection}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
const clearFilters = () => {
|
|
||||||
setProcessModelSelection(null);
|
|
||||||
setProcessStatusSelection([]);
|
|
||||||
setStartFrom('');
|
|
||||||
setStartTo('');
|
|
||||||
setEndFrom('');
|
|
||||||
setEndTo('');
|
|
||||||
};
|
|
||||||
const filterOptions = () => {
|
|
||||||
if (!showFilterOptions) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Grid fullWidth className="with-bottom-margin">
|
|
||||||
<Column md={8}>
|
|
||||||
<ProcessModelSearch
|
|
||||||
onChange={(selection: any) =>
|
|
||||||
setProcessModelSelection(selection.selectedItem)
|
|
||||||
}
|
|
||||||
processModels={processModelAvailableItems}
|
|
||||||
selectedItem={processModelSelection}
|
|
||||||
/>
|
|
||||||
</Column>
|
|
||||||
<Column md={8}>{processStatusSearch()}</Column>
|
|
||||||
</Grid>
|
|
||||||
<Grid fullWidth className="with-bottom-margin">
|
|
||||||
<Column md={4}>
|
|
||||||
{dateComponent(
|
|
||||||
'Start date from',
|
|
||||||
'start-from',
|
|
||||||
startFrom,
|
|
||||||
setStartFrom
|
|
||||||
)}
|
|
||||||
</Column>
|
|
||||||
<Column md={4}>
|
|
||||||
{dateComponent('Start date to', 'start-to', startTo, setStartTo)}
|
|
||||||
</Column>
|
|
||||||
<Column md={4}>
|
|
||||||
{dateComponent('End date from', 'end-from', endFrom, setEndFrom)}
|
|
||||||
</Column>
|
|
||||||
<Column md={4}>
|
|
||||||
{dateComponent('End date to', 'end-to', endTo, setEndTo)}
|
|
||||||
</Column>
|
|
||||||
</Grid>
|
|
||||||
<Grid fullWidth className="with-bottom-margin">
|
|
||||||
<Column md={4}>
|
|
||||||
<ButtonSet>
|
|
||||||
<Button
|
|
||||||
kind=""
|
|
||||||
className="button-white-background"
|
|
||||||
onClick={clearFilters}
|
|
||||||
>
|
|
||||||
Clear
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
kind="secondary"
|
|
||||||
onClick={applyFilter}
|
|
||||||
data-qa="filter-button"
|
|
||||||
>
|
|
||||||
Filter
|
|
||||||
</Button>
|
|
||||||
</ButtonSet>
|
|
||||||
</Column>
|
|
||||||
</Grid>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
const toggleShowFilterOptions = () => {
|
|
||||||
setShowFilterOptions(!showFilterOptions);
|
|
||||||
};
|
|
||||||
const filterComponent = () => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Grid fullWidth>
|
|
||||||
<Column
|
|
||||||
sm={{ span: 1, offset: 3 }}
|
|
||||||
md={{ span: 1, offset: 7 }}
|
|
||||||
lg={{ span: 1, offset: 15 }}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
data-qa="filter-section-expand-toggle"
|
|
||||||
kind="ghost"
|
|
||||||
renderIcon={Filter}
|
|
||||||
iconDescription="Filter Options"
|
|
||||||
hasIconOnly
|
|
||||||
size="lg"
|
|
||||||
onClick={toggleShowFilterOptions}
|
|
||||||
/>
|
|
||||||
</Column>
|
|
||||||
</Grid>
|
|
||||||
{filterOptions()}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const buildTable = () => {
|
|
||||||
const headerLabels: Record<string, string> = {
|
|
||||||
id: 'Process Instance Id',
|
|
||||||
process_model_identifier: 'Process Model',
|
|
||||||
start_in_seconds: 'Start Time',
|
|
||||||
end_in_seconds: 'End Time',
|
|
||||||
status: 'Status',
|
|
||||||
spiff_step: 'SpiffWorkflow Step',
|
|
||||||
};
|
|
||||||
const getHeaderLabel = (header: string) => {
|
|
||||||
return headerLabels[header] ?? header;
|
|
||||||
};
|
|
||||||
const headers = (reportMetadata as any).columns.map((column: any) => {
|
|
||||||
// return <th>{getHeaderLabel((column as any).Header)}</th>;
|
|
||||||
return getHeaderLabel((column as any).Header);
|
|
||||||
});
|
|
||||||
|
|
||||||
const formatProcessInstanceId = (row: any, id: any) => {
|
|
||||||
const modifiedProcessModelId: String = modifyProcessModelPath(
|
|
||||||
row.process_model_identifier
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
<Link
|
|
||||||
data-qa="process-instance-show-link"
|
|
||||||
to={`/admin/process-models/${modifiedProcessModelId}/process-instances/${row.id}`}
|
|
||||||
>
|
|
||||||
{id}
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
const formatProcessModelIdentifier = (_row: any, identifier: any) => {
|
|
||||||
return (
|
|
||||||
<Link
|
|
||||||
to={`/admin/process-models/${modifyProcessModelPath(identifier)}`}
|
|
||||||
>
|
|
||||||
{identifier}
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
const formatSecondsForDisplay = (_row: any, seconds: any) => {
|
|
||||||
return convertSecondsToFormattedDate(seconds) || '-';
|
|
||||||
};
|
|
||||||
const defaultFormatter = (_row: any, value: any) => {
|
|
||||||
return value;
|
|
||||||
};
|
|
||||||
|
|
||||||
const columnFormatters: Record<string, any> = {
|
|
||||||
id: formatProcessInstanceId,
|
|
||||||
process_model_identifier: formatProcessModelIdentifier,
|
|
||||||
start_in_seconds: formatSecondsForDisplay,
|
|
||||||
end_in_seconds: formatSecondsForDisplay,
|
|
||||||
};
|
|
||||||
const formattedColumn = (row: any, column: any) => {
|
|
||||||
const formatter = columnFormatters[column.accessor] ?? defaultFormatter;
|
|
||||||
const value = row[column.accessor];
|
|
||||||
if (column.accessor === 'status') {
|
|
||||||
return (
|
|
||||||
<td data-qa={`process-instance-status-${value}`}>
|
|
||||||
{formatter(row, value)}
|
|
||||||
</td>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return <td>{formatter(row, value)}</td>;
|
|
||||||
};
|
|
||||||
|
|
||||||
const rows = processInstances.map((row: any) => {
|
|
||||||
const currentRow = (reportMetadata as any).columns.map((column: any) => {
|
|
||||||
return formattedColumn(row, column);
|
|
||||||
});
|
|
||||||
return <tr key={row.id}>{currentRow}</tr>;
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Table size="lg">
|
|
||||||
<TableHead>
|
|
||||||
<TableRow>
|
|
||||||
{headers.map((header: any) => (
|
|
||||||
<TableHeader key={header}>{header}</TableHeader>
|
|
||||||
))}
|
|
||||||
</TableRow>
|
|
||||||
</TableHead>
|
|
||||||
<tbody>{rows}</tbody>
|
|
||||||
</Table>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const processInstanceBreadcrumbElement = () => {
|
const processInstanceBreadcrumbElement = () => {
|
||||||
const processModelFullIdentifier =
|
const processModelFullIdentifier =
|
||||||
getProcessModelFullIdentifierFromSearchParams(searchParams);
|
getProcessModelFullIdentifierFromSearchParams(searchParams);
|
||||||
|
@ -498,48 +31,14 @@ export default function ProcessInstanceList() {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getSearchParamsAsQueryString = () => {
|
|
||||||
let queryParamString = '';
|
|
||||||
Object.keys(parametersToAlwaysFilterBy).forEach((paramName) => {
|
|
||||||
const searchParamValue = searchParams.get(paramName);
|
|
||||||
if (searchParamValue) {
|
|
||||||
queryParamString += `&${paramName}=${searchParamValue}`;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Object.keys(parametersToGetFromSearchParams).forEach(
|
|
||||||
(paramName: string) => {
|
|
||||||
if (searchParams.get(paramName)) {
|
|
||||||
queryParamString += `&${paramName}=${searchParams.get(paramName)}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return queryParamString;
|
|
||||||
};
|
|
||||||
|
|
||||||
const processInstanceTitleElement = () => {
|
const processInstanceTitleElement = () => {
|
||||||
return <h1>Process Instances</h1>;
|
return <h1>Process Instances</h1>;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (pagination) {
|
|
||||||
const { page, perPage } = getPageInfoFromSearchParams(searchParams);
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{processInstanceBreadcrumbElement()}
|
{processInstanceBreadcrumbElement()}
|
||||||
{processInstanceTitleElement()}
|
{processInstanceTitleElement()}
|
||||||
{filterComponent()}
|
<ProcessInstanceListTable />
|
||||||
<br />
|
|
||||||
<PaginationForTable
|
|
||||||
page={page}
|
|
||||||
perPage={perPage}
|
|
||||||
pagination={pagination}
|
|
||||||
tableToDisplay={buildTable()}
|
|
||||||
queryParamString={getSearchParamsAsQueryString()}
|
|
||||||
path="/admin/process-instances"
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,7 +99,6 @@ export default function ProcessInstanceLogList() {
|
||||||
perPage={perPage}
|
perPage={perPage}
|
||||||
pagination={pagination}
|
pagination={pagination}
|
||||||
tableToDisplay={buildTable()}
|
tableToDisplay={buildTable()}
|
||||||
path={`/admin/process-models/${modifiedProcessModelId}/process-instances/${params.process_instance_id}/logs`}
|
|
||||||
/>
|
/>
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
|
|
|
@ -91,7 +91,6 @@ export default function ProcessInstanceReport() {
|
||||||
perPage={perPage}
|
perPage={perPage}
|
||||||
pagination={pagination}
|
pagination={pagination}
|
||||||
tableToDisplay={buildTable()}
|
tableToDisplay={buildTable()}
|
||||||
path={`/admin/process-models/${params.process_group_id}/${params.process_model_id}/process-instances/report`}
|
|
||||||
/>
|
/>
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
|
|
|
@ -33,6 +33,7 @@ import ErrorContext from '../contexts/ErrorContext';
|
||||||
import { modifyProcessModelPath, unModifyProcessModelPath } from '../helpers';
|
import { modifyProcessModelPath, unModifyProcessModelPath } from '../helpers';
|
||||||
import { ProcessFile, ProcessModel, RecentProcessModel } from '../interfaces';
|
import { ProcessFile, ProcessModel, RecentProcessModel } from '../interfaces';
|
||||||
import ButtonWithConfirmation from '../components/ButtonWithConfirmation';
|
import ButtonWithConfirmation from '../components/ButtonWithConfirmation';
|
||||||
|
import ProcessInstanceListTable from '../components/ProcessInstanceListTable';
|
||||||
|
|
||||||
const storeRecentProcessModelInLocalStorage = (
|
const storeRecentProcessModelInLocalStorage = (
|
||||||
processModelForStorage: ProcessModel
|
processModelForStorage: ProcessModel
|
||||||
|
@ -538,6 +539,11 @@ export default function ProcessModelShow() {
|
||||||
{processInstancesUl()}
|
{processInstancesUl()}
|
||||||
<br />
|
<br />
|
||||||
{processModelButtons()}
|
{processModelButtons()}
|
||||||
|
<br />
|
||||||
|
<ProcessInstanceListTable
|
||||||
|
filtersEnabled={false}
|
||||||
|
processModelFullIdentifier={processModel.id}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,7 +83,6 @@ export default function SecretList() {
|
||||||
perPage={perPage}
|
perPage={perPage}
|
||||||
pagination={pagination as any}
|
pagination={pagination as any}
|
||||||
tableToDisplay={buildTable()}
|
tableToDisplay={buildTable()}
|
||||||
path="/admin/secrets"
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|
Loading…
Reference in New Issue