From e6db091ba1db9ab51882fe593c1a88ba39de6b13 Mon Sep 17 00:00:00 2001 From: jasquat Date: Mon, 14 Nov 2022 16:29:04 -0500 Subject: [PATCH 1/4] refactored pagination table to allow prefixing page options w/ burnettk --- spiffworkflow-backend/migrations/env.py | 27 +- .../migrations/versions/88c2d7081664_.py | 313 ++++++++++ .../migrations/versions/fd00c59e1f60_.py | 550 ------------------ .../models/spiff_step_details.py | 6 + .../routes/process_api_blueprint.py | 12 +- .../services/process_instance_processor.py | 7 +- .../services/process_instance_service.py | 6 +- .../src/components/PaginationForTable.tsx | 17 +- .../components/ProcessInstanceListTable.tsx | 541 +++++++++++++++++ .../components/TasksForMyOpenProcesses.tsx | 11 +- .../src/components/TasksWaitingForMe.tsx | 10 +- .../components/TasksWaitingForMyGroups.tsx | 11 +- spiffworkflow-frontend/src/helpers.tsx | 15 +- .../src/routes/CompletedInstances.tsx | 3 + .../src/routes/HomePageRoutes.tsx | 7 + .../src/routes/MessageInstanceList.tsx | 1 - spiffworkflow-frontend/src/routes/MyTasks.tsx | 1 - .../src/routes/ProcessGroupList.tsx | 1 - .../src/routes/ProcessGroupShow.tsx | 2 - .../src/routes/ProcessInstanceList.tsx | 1 - .../src/routes/ProcessInstanceLogList.tsx | 1 - .../src/routes/ProcessInstanceReportShow.tsx | 1 - .../src/routes/SecretList.tsx | 1 - 23 files changed, 951 insertions(+), 594 deletions(-) create mode 100644 spiffworkflow-backend/migrations/versions/88c2d7081664_.py delete mode 100644 spiffworkflow-backend/migrations/versions/fd00c59e1f60_.py create mode 100644 spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx create mode 100644 spiffworkflow-frontend/src/routes/CompletedInstances.tsx diff --git a/spiffworkflow-backend/migrations/env.py b/spiffworkflow-backend/migrations/env.py index 4bd0316c2..68feded2a 100644 --- a/spiffworkflow-backend/migrations/env.py +++ b/spiffworkflow-backend/migrations/env.py @@ -1,9 +1,12 @@ +from __future__ import with_statement + import logging from logging.config import fileConfig -from alembic import context from flask import current_app +from alembic import context + # this is the Alembic Config object, which provides # access to the values within the .ini file in use. config = context.config @@ -11,17 +14,17 @@ config = context.config # Interpret the config file for Python logging. # This line sets up loggers basically. fileConfig(config.config_file_name) -logger = logging.getLogger("alembic.env") +logger = logging.getLogger('alembic.env') # add your model's MetaData object here # for 'autogenerate' support # from myapp import mymodel # target_metadata = mymodel.Base.metadata config.set_main_option( - "sqlalchemy.url", - str(current_app.extensions["migrate"].db.get_engine().url).replace("%", "%%"), -) -target_metadata = current_app.extensions["migrate"].db.metadata + 'sqlalchemy.url', + str(current_app.extensions['migrate'].db.get_engine().url).replace( + '%', '%%')) +target_metadata = current_app.extensions['migrate'].db.metadata # other values from the config, defined by the needs of env.py, # can be acquired: @@ -42,7 +45,9 @@ def run_migrations_offline(): """ url = config.get_main_option("sqlalchemy.url") - context.configure(url=url, target_metadata=target_metadata, literal_binds=True) + context.configure( + url=url, target_metadata=target_metadata, literal_binds=True + ) with context.begin_transaction(): context.run_migrations() @@ -60,20 +65,20 @@ def run_migrations_online(): # when there are no changes to the schema # reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html def process_revision_directives(context, revision, directives): - if getattr(config.cmd_opts, "autogenerate", False): + if getattr(config.cmd_opts, 'autogenerate', False): script = directives[0] if script.upgrade_ops.is_empty(): directives[:] = [] - logger.info("No changes in schema detected.") + logger.info('No changes in schema detected.') - connectable = current_app.extensions["migrate"].db.get_engine() + connectable = current_app.extensions['migrate'].db.get_engine() with connectable.connect() as connection: context.configure( connection=connection, target_metadata=target_metadata, process_revision_directives=process_revision_directives, - **current_app.extensions["migrate"].configure_args + **current_app.extensions['migrate'].configure_args ) with context.begin_transaction(): diff --git a/spiffworkflow-backend/migrations/versions/88c2d7081664_.py b/spiffworkflow-backend/migrations/versions/88c2d7081664_.py new file mode 100644 index 000000000..50d710804 --- /dev/null +++ b/spiffworkflow-backend/migrations/versions/88c2d7081664_.py @@ -0,0 +1,313 @@ +"""empty message + +Revision ID: 88c2d7081664 +Revises: +Create Date: 2022-11-14 15:16:42.833331 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '88c2d7081664' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('bpmn_process_id_lookup', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('bpmn_process_identifier', sa.String(length=255), nullable=True), + sa.Column('display_name', sa.String(length=255), nullable=True), + sa.Column('bpmn_file_relative_path', sa.String(length=255), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_bpmn_process_id_lookup_bpmn_process_identifier'), 'bpmn_process_id_lookup', ['bpmn_process_identifier'], unique=True) + op.create_index(op.f('ix_bpmn_process_id_lookup_display_name'), 'bpmn_process_id_lookup', ['display_name'], unique=True) + op.create_table('group', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=255), nullable=True), + sa.Column('identifier', sa.String(length=255), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('message_model', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('identifier', sa.String(length=50), nullable=True), + sa.Column('name', sa.String(length=50), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_message_model_identifier'), 'message_model', ['identifier'], unique=True) + op.create_index(op.f('ix_message_model_name'), 'message_model', ['name'], unique=True) + op.create_table('permission_target', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('uri', sa.String(length=255), nullable=False), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('uri') + ) + op.create_table('spiff_logging', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('process_instance_id', sa.Integer(), nullable=False), + sa.Column('bpmn_process_identifier', sa.String(length=255), nullable=False), + sa.Column('bpmn_task_identifier', sa.String(length=255), nullable=False), + sa.Column('bpmn_task_name', sa.String(length=255), nullable=True), + sa.Column('bpmn_task_type', sa.String(length=255), nullable=True), + sa.Column('spiff_task_guid', sa.String(length=50), nullable=False), + sa.Column('timestamp', sa.DECIMAL(precision=17, scale=6), nullable=False), + sa.Column('message', sa.String(length=255), nullable=True), + sa.Column('current_user_id', sa.Integer(), nullable=True), + sa.Column('spiff_step', sa.Integer(), nullable=False), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('user', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('username', sa.String(length=255), nullable=False), + sa.Column('uid', sa.String(length=50), nullable=True), + sa.Column('service', sa.String(length=50), nullable=False), + sa.Column('service_id', sa.String(length=255), nullable=False), + sa.Column('name', sa.String(length=255), nullable=True), + sa.Column('email', sa.String(length=255), nullable=True), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('service', 'service_id', name='service_key'), + sa.UniqueConstraint('uid'), + sa.UniqueConstraint('username') + ) + op.create_table('message_correlation_property', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('identifier', sa.String(length=50), nullable=True), + sa.Column('message_model_id', sa.Integer(), nullable=False), + sa.Column('updated_at_in_seconds', sa.Integer(), nullable=True), + sa.Column('created_at_in_seconds', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['message_model_id'], ['message_model.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('identifier', 'message_model_id', name='message_correlation_property_unique') + ) + op.create_index(op.f('ix_message_correlation_property_identifier'), 'message_correlation_property', ['identifier'], unique=False) + op.create_table('message_triggerable_process_model', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('message_model_id', sa.Integer(), nullable=False), + sa.Column('process_model_identifier', sa.String(length=50), nullable=False), + sa.Column('process_group_identifier', sa.String(length=50), nullable=False), + sa.Column('updated_at_in_seconds', sa.Integer(), nullable=True), + sa.Column('created_at_in_seconds', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['message_model_id'], ['message_model.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('message_model_id') + ) + op.create_index(op.f('ix_message_triggerable_process_model_process_group_identifier'), 'message_triggerable_process_model', ['process_group_identifier'], unique=False) + op.create_index(op.f('ix_message_triggerable_process_model_process_model_identifier'), 'message_triggerable_process_model', ['process_model_identifier'], unique=False) + op.create_table('principal', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('user_id', sa.Integer(), nullable=True), + sa.Column('group_id', sa.Integer(), nullable=True), + sa.CheckConstraint('NOT(user_id IS NULL AND group_id IS NULL)'), + sa.ForeignKeyConstraint(['group_id'], ['group.id'], ), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('group_id'), + sa.UniqueConstraint('user_id') + ) + op.create_table('process_instance', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('process_model_identifier', sa.String(length=255), nullable=False), + sa.Column('process_group_identifier', sa.String(length=50), nullable=False), + sa.Column('process_initiator_id', sa.Integer(), nullable=False), + sa.Column('bpmn_json', sa.JSON(), nullable=True), + sa.Column('start_in_seconds', sa.Integer(), nullable=True), + sa.Column('end_in_seconds', sa.Integer(), nullable=True), + sa.Column('updated_at_in_seconds', sa.Integer(), nullable=True), + sa.Column('created_at_in_seconds', sa.Integer(), nullable=True), + sa.Column('status', sa.String(length=50), nullable=True), + sa.Column('bpmn_version_control_type', sa.String(length=50), nullable=True), + sa.Column('bpmn_version_control_identifier', sa.String(length=255), nullable=True), + sa.Column('spiff_step', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['process_initiator_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_process_instance_process_group_identifier'), 'process_instance', ['process_group_identifier'], unique=False) + op.create_index(op.f('ix_process_instance_process_model_identifier'), 'process_instance', ['process_model_identifier'], unique=False) + op.create_table('process_instance_report', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('identifier', sa.String(length=50), nullable=False), + sa.Column('report_metadata', sa.JSON(), nullable=True), + sa.Column('created_by_id', sa.Integer(), nullable=False), + sa.Column('created_at_in_seconds', sa.Integer(), nullable=True), + sa.Column('updated_at_in_seconds', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['created_by_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('created_by_id', 'identifier', name='process_instance_report_unique') + ) + op.create_index(op.f('ix_process_instance_report_created_by_id'), 'process_instance_report', ['created_by_id'], unique=False) + op.create_index(op.f('ix_process_instance_report_identifier'), 'process_instance_report', ['identifier'], unique=False) + op.create_table('refresh_token', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('user_id', sa.Integer(), nullable=False), + sa.Column('token', sa.String(length=1024), nullable=False), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('user_id') + ) + op.create_table('secret', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('key', sa.String(length=50), nullable=False), + sa.Column('value', sa.Text(), nullable=False), + sa.Column('user_id', sa.Integer(), nullable=False), + sa.Column('updated_at_in_seconds', sa.Integer(), nullable=True), + sa.Column('created_at_in_seconds', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('key') + ) + op.create_table('spiff_step_details', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('process_instance_id', sa.Integer(), nullable=False), + sa.Column('spiff_step', sa.Integer(), nullable=False), + sa.Column('task_json', sa.JSON(), nullable=False), + sa.Column('timestamp', sa.DECIMAL(precision=17, scale=6), nullable=False), + sa.Column('completed_by_user_id', sa.Integer(), nullable=True), + sa.Column('lane_assignment_id', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['lane_assignment_id'], ['group.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('user_group_assignment', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('user_id', sa.Integer(), nullable=False), + sa.Column('group_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['group_id'], ['group.id'], ), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('user_id', 'group_id', name='user_group_assignment_unique') + ) + op.create_table('active_task', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('process_instance_id', sa.Integer(), nullable=False), + sa.Column('actual_owner_id', sa.Integer(), nullable=True), + sa.Column('lane_assignment_id', sa.Integer(), nullable=True), + sa.Column('form_file_name', sa.String(length=50), nullable=True), + sa.Column('ui_form_file_name', sa.String(length=50), nullable=True), + sa.Column('updated_at_in_seconds', sa.Integer(), nullable=True), + sa.Column('created_at_in_seconds', sa.Integer(), nullable=True), + sa.Column('task_id', sa.String(length=50), nullable=True), + sa.Column('task_name', sa.String(length=50), nullable=True), + sa.Column('task_title', sa.String(length=50), nullable=True), + sa.Column('task_type', sa.String(length=50), nullable=True), + sa.Column('task_status', sa.String(length=50), nullable=True), + sa.Column('process_model_display_name', sa.String(length=255), nullable=True), + sa.ForeignKeyConstraint(['actual_owner_id'], ['user.id'], ), + sa.ForeignKeyConstraint(['lane_assignment_id'], ['group.id'], ), + sa.ForeignKeyConstraint(['process_instance_id'], ['process_instance.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('task_id', 'process_instance_id', name='active_task_unique') + ) + op.create_table('message_correlation', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('process_instance_id', sa.Integer(), nullable=False), + sa.Column('message_correlation_property_id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=255), nullable=False), + sa.Column('value', sa.String(length=255), nullable=False), + sa.Column('updated_at_in_seconds', sa.Integer(), nullable=True), + sa.Column('created_at_in_seconds', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['message_correlation_property_id'], ['message_correlation_property.id'], ), + sa.ForeignKeyConstraint(['process_instance_id'], ['process_instance.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('process_instance_id', 'message_correlation_property_id', 'name', name='message_instance_id_name_unique') + ) + op.create_index(op.f('ix_message_correlation_message_correlation_property_id'), 'message_correlation', ['message_correlation_property_id'], unique=False) + op.create_index(op.f('ix_message_correlation_name'), 'message_correlation', ['name'], unique=False) + op.create_index(op.f('ix_message_correlation_process_instance_id'), 'message_correlation', ['process_instance_id'], unique=False) + op.create_index(op.f('ix_message_correlation_value'), 'message_correlation', ['value'], unique=False) + op.create_table('message_instance', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('process_instance_id', sa.Integer(), nullable=False), + sa.Column('message_model_id', sa.Integer(), nullable=False), + sa.Column('message_type', sa.String(length=20), nullable=False), + sa.Column('payload', sa.JSON(), nullable=True), + sa.Column('status', sa.String(length=20), nullable=False), + sa.Column('failure_cause', sa.Text(), nullable=True), + sa.Column('updated_at_in_seconds', sa.Integer(), nullable=True), + sa.Column('created_at_in_seconds', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['message_model_id'], ['message_model.id'], ), + sa.ForeignKeyConstraint(['process_instance_id'], ['process_instance.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('permission_assignment', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('principal_id', sa.Integer(), nullable=False), + sa.Column('permission_target_id', sa.Integer(), nullable=False), + sa.Column('grant_type', sa.String(length=50), nullable=False), + sa.Column('permission', sa.String(length=50), nullable=False), + sa.ForeignKeyConstraint(['permission_target_id'], ['permission_target.id'], ), + sa.ForeignKeyConstraint(['principal_id'], ['principal.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('principal_id', 'permission_target_id', 'permission', name='permission_assignment_uniq') + ) + op.create_table('active_task_user', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('active_task_id', sa.Integer(), nullable=False), + sa.Column('user_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['active_task_id'], ['active_task.id'], ), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('active_task_id', 'user_id', name='active_task_user_unique') + ) + op.create_index(op.f('ix_active_task_user_active_task_id'), 'active_task_user', ['active_task_id'], unique=False) + op.create_index(op.f('ix_active_task_user_user_id'), 'active_task_user', ['user_id'], unique=False) + op.create_table('message_correlation_message_instance', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('message_instance_id', sa.Integer(), nullable=False), + sa.Column('message_correlation_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['message_correlation_id'], ['message_correlation.id'], ), + sa.ForeignKeyConstraint(['message_instance_id'], ['message_instance.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('message_instance_id', 'message_correlation_id', name='message_correlation_message_instance_unique') + ) + op.create_index(op.f('ix_message_correlation_message_instance_message_correlation_id'), 'message_correlation_message_instance', ['message_correlation_id'], unique=False) + op.create_index(op.f('ix_message_correlation_message_instance_message_instance_id'), 'message_correlation_message_instance', ['message_instance_id'], unique=False) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_index(op.f('ix_message_correlation_message_instance_message_instance_id'), table_name='message_correlation_message_instance') + op.drop_index(op.f('ix_message_correlation_message_instance_message_correlation_id'), table_name='message_correlation_message_instance') + op.drop_table('message_correlation_message_instance') + op.drop_index(op.f('ix_active_task_user_user_id'), table_name='active_task_user') + op.drop_index(op.f('ix_active_task_user_active_task_id'), table_name='active_task_user') + op.drop_table('active_task_user') + op.drop_table('permission_assignment') + op.drop_table('message_instance') + op.drop_index(op.f('ix_message_correlation_value'), table_name='message_correlation') + op.drop_index(op.f('ix_message_correlation_process_instance_id'), table_name='message_correlation') + op.drop_index(op.f('ix_message_correlation_name'), table_name='message_correlation') + op.drop_index(op.f('ix_message_correlation_message_correlation_property_id'), table_name='message_correlation') + op.drop_table('message_correlation') + op.drop_table('active_task') + op.drop_table('user_group_assignment') + op.drop_table('spiff_step_details') + op.drop_table('secret') + op.drop_table('refresh_token') + op.drop_index(op.f('ix_process_instance_report_identifier'), table_name='process_instance_report') + op.drop_index(op.f('ix_process_instance_report_created_by_id'), table_name='process_instance_report') + op.drop_table('process_instance_report') + op.drop_index(op.f('ix_process_instance_process_model_identifier'), table_name='process_instance') + op.drop_index(op.f('ix_process_instance_process_group_identifier'), table_name='process_instance') + op.drop_table('process_instance') + op.drop_table('principal') + op.drop_index(op.f('ix_message_triggerable_process_model_process_model_identifier'), table_name='message_triggerable_process_model') + op.drop_index(op.f('ix_message_triggerable_process_model_process_group_identifier'), table_name='message_triggerable_process_model') + op.drop_table('message_triggerable_process_model') + op.drop_index(op.f('ix_message_correlation_property_identifier'), table_name='message_correlation_property') + op.drop_table('message_correlation_property') + op.drop_table('user') + op.drop_table('spiff_logging') + op.drop_table('permission_target') + op.drop_index(op.f('ix_message_model_name'), table_name='message_model') + op.drop_index(op.f('ix_message_model_identifier'), table_name='message_model') + op.drop_table('message_model') + op.drop_table('group') + op.drop_index(op.f('ix_bpmn_process_id_lookup_display_name'), table_name='bpmn_process_id_lookup') + op.drop_index(op.f('ix_bpmn_process_id_lookup_bpmn_process_identifier'), table_name='bpmn_process_id_lookup') + op.drop_table('bpmn_process_id_lookup') + # ### end Alembic commands ### diff --git a/spiffworkflow-backend/migrations/versions/fd00c59e1f60_.py b/spiffworkflow-backend/migrations/versions/fd00c59e1f60_.py deleted file mode 100644 index 55f1ca3f6..000000000 --- a/spiffworkflow-backend/migrations/versions/fd00c59e1f60_.py +++ /dev/null @@ -1,550 +0,0 @@ -"""empty message - -Revision ID: fd00c59e1f60 -Revises: -Create Date: 2022-11-09 14:04:14.169379 - -""" -import sqlalchemy as sa -from alembic import op - - -# revision identifiers, used by Alembic. -revision = "fd00c59e1f60" -down_revision = None -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.create_table( - "bpmn_process_id_lookup", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("bpmn_process_identifier", sa.String(length=255), nullable=True), - sa.Column("bpmn_file_relative_path", sa.String(length=255), nullable=True), - sa.Column("display_name", sa.String(length=255), nullable=True), - sa.PrimaryKeyConstraint("id"), - ) - op.create_index( - op.f("ix_bpmn_process_id_lookup_bpmn_process_identifier"), - "bpmn_process_id_lookup", - ["bpmn_process_identifier"], - unique=True, - ) - op.create_table( - "group", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("name", sa.String(length=255), nullable=True), - sa.Column("identifier", sa.String(length=255), nullable=True), - sa.PrimaryKeyConstraint("id"), - ) - op.create_table( - "message_model", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("identifier", sa.String(length=50), nullable=True), - sa.Column("name", sa.String(length=50), nullable=True), - sa.PrimaryKeyConstraint("id"), - ) - op.create_index( - op.f("ix_message_model_identifier"), - "message_model", - ["identifier"], - unique=True, - ) - op.create_index( - op.f("ix_message_model_name"), "message_model", ["name"], unique=True - ) - op.create_table( - "permission_target", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("uri", sa.String(length=255), nullable=False), - sa.PrimaryKeyConstraint("id"), - sa.UniqueConstraint("uri"), - ) - op.create_table( - "spiff_logging", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("process_instance_id", sa.Integer(), nullable=False), - sa.Column("bpmn_process_identifier", sa.String(length=255), nullable=False), - sa.Column("bpmn_task_identifier", sa.String(length=255), nullable=False), - sa.Column("bpmn_task_name", sa.String(length=255), nullable=True), - sa.Column("bpmn_task_type", sa.String(length=255), nullable=True), - sa.Column("spiff_task_guid", sa.String(length=50), nullable=False), - sa.Column("timestamp", sa.DECIMAL(precision=17, scale=6), nullable=False), - sa.Column("message", sa.String(length=255), nullable=True), - sa.Column("current_user_id", sa.Integer(), nullable=True), - sa.Column("spiff_step", sa.Integer(), nullable=False), - 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", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("username", sa.String(length=255), nullable=False), - sa.Column("uid", sa.String(length=50), nullable=True), - sa.Column("service", sa.String(length=50), nullable=False), - sa.Column("service_id", sa.String(length=255), nullable=False), - sa.Column("name", sa.String(length=255), nullable=True), - sa.Column("email", sa.String(length=255), nullable=True), - sa.PrimaryKeyConstraint("id"), - sa.UniqueConstraint("service", "service_id", name="service_key"), - sa.UniqueConstraint("uid"), - sa.UniqueConstraint("username"), - ) - op.create_table( - "message_correlation_property", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("identifier", sa.String(length=50), nullable=True), - sa.Column("message_model_id", sa.Integer(), nullable=False), - sa.Column("updated_at_in_seconds", sa.Integer(), nullable=True), - sa.Column("created_at_in_seconds", sa.Integer(), nullable=True), - sa.ForeignKeyConstraint( - ["message_model_id"], - ["message_model.id"], - ), - sa.PrimaryKeyConstraint("id"), - sa.UniqueConstraint( - "identifier", "message_model_id", name="message_correlation_property_unique" - ), - ) - op.create_index( - op.f("ix_message_correlation_property_identifier"), - "message_correlation_property", - ["identifier"], - unique=False, - ) - op.create_table( - "message_triggerable_process_model", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("message_model_id", sa.Integer(), nullable=False), - sa.Column("process_model_identifier", sa.String(length=50), nullable=False), - sa.Column("process_group_identifier", sa.String(length=50), nullable=False), - sa.Column("updated_at_in_seconds", sa.Integer(), nullable=True), - sa.Column("created_at_in_seconds", sa.Integer(), nullable=True), - sa.ForeignKeyConstraint( - ["message_model_id"], - ["message_model.id"], - ), - sa.PrimaryKeyConstraint("id"), - sa.UniqueConstraint("message_model_id"), - ) - op.create_index( - op.f("ix_message_triggerable_process_model_process_group_identifier"), - "message_triggerable_process_model", - ["process_group_identifier"], - unique=False, - ) - op.create_index( - op.f("ix_message_triggerable_process_model_process_model_identifier"), - "message_triggerable_process_model", - ["process_model_identifier"], - unique=False, - ) - op.create_table( - "principal", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("user_id", sa.Integer(), nullable=True), - sa.Column("group_id", sa.Integer(), nullable=True), - sa.CheckConstraint("NOT(user_id IS NULL AND group_id IS NULL)"), - sa.ForeignKeyConstraint( - ["group_id"], - ["group.id"], - ), - sa.ForeignKeyConstraint( - ["user_id"], - ["user.id"], - ), - sa.PrimaryKeyConstraint("id"), - sa.UniqueConstraint("group_id"), - sa.UniqueConstraint("user_id"), - ) - op.create_table( - "process_instance", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("process_model_identifier", sa.String(length=255), nullable=False), - sa.Column("process_group_identifier", sa.String(length=50), nullable=False), - sa.Column("process_initiator_id", sa.Integer(), nullable=False), - sa.Column("bpmn_json", sa.JSON(), nullable=True), - sa.Column("start_in_seconds", sa.Integer(), nullable=True), - sa.Column("end_in_seconds", sa.Integer(), nullable=True), - sa.Column("updated_at_in_seconds", sa.Integer(), nullable=True), - sa.Column("created_at_in_seconds", sa.Integer(), nullable=True), - sa.Column("status", sa.String(length=50), nullable=True), - sa.Column("bpmn_version_control_type", sa.String(length=50), nullable=True), - sa.Column( - "bpmn_version_control_identifier", sa.String(length=255), nullable=True - ), - sa.Column("spiff_step", sa.Integer(), nullable=True), - sa.ForeignKeyConstraint( - ["process_initiator_id"], - ["user.id"], - ), - sa.PrimaryKeyConstraint("id"), - ) - op.create_index( - op.f("ix_process_instance_process_group_identifier"), - "process_instance", - ["process_group_identifier"], - unique=False, - ) - op.create_index( - op.f("ix_process_instance_process_model_identifier"), - "process_instance", - ["process_model_identifier"], - unique=False, - ) - op.create_table( - "process_instance_report", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("identifier", sa.String(length=50), nullable=False), - sa.Column("report_metadata", sa.JSON(), nullable=True), - sa.Column("created_by_id", sa.Integer(), nullable=False), - sa.Column("created_at_in_seconds", sa.Integer(), nullable=True), - sa.Column("updated_at_in_seconds", sa.Integer(), nullable=True), - sa.ForeignKeyConstraint( - ["created_by_id"], - ["user.id"], - ), - sa.PrimaryKeyConstraint("id"), - sa.UniqueConstraint( - "created_by_id", "identifier", name="process_instance_report_unique" - ), - ) - op.create_index( - op.f("ix_process_instance_report_created_by_id"), - "process_instance_report", - ["created_by_id"], - unique=False, - ) - op.create_index( - op.f("ix_process_instance_report_identifier"), - "process_instance_report", - ["identifier"], - unique=False, - ) - op.create_table( - "refresh_token", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("user_id", sa.Integer(), nullable=False), - sa.Column("token", sa.String(length=1024), nullable=False), - sa.ForeignKeyConstraint( - ["user_id"], - ["user.id"], - ), - sa.PrimaryKeyConstraint("id"), - sa.UniqueConstraint("user_id"), - ) - op.create_table( - "secret", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("key", sa.String(length=50), nullable=False), - sa.Column("value", sa.Text(), nullable=False), - sa.Column("user_id", sa.Integer(), nullable=False), - sa.Column("updated_at_in_seconds", sa.Integer(), nullable=True), - sa.Column("created_at_in_seconds", sa.Integer(), nullable=True), - sa.ForeignKeyConstraint( - ["user_id"], - ["user.id"], - ), - sa.PrimaryKeyConstraint("id"), - sa.UniqueConstraint("key"), - ) - op.create_table( - "user_group_assignment", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("user_id", sa.Integer(), nullable=False), - sa.Column("group_id", sa.Integer(), nullable=False), - sa.ForeignKeyConstraint( - ["group_id"], - ["group.id"], - ), - sa.ForeignKeyConstraint( - ["user_id"], - ["user.id"], - ), - sa.PrimaryKeyConstraint("id"), - sa.UniqueConstraint("user_id", "group_id", name="user_group_assignment_unique"), - ) - op.create_table( - "active_task", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("process_instance_id", sa.Integer(), nullable=False), - sa.Column("actual_owner_id", sa.Integer(), nullable=True), - sa.Column("lane_assignment_id", sa.Integer(), nullable=True), - sa.Column("form_file_name", sa.String(length=50), nullable=True), - sa.Column("ui_form_file_name", sa.String(length=50), nullable=True), - sa.Column("updated_at_in_seconds", sa.Integer(), nullable=True), - sa.Column("created_at_in_seconds", sa.Integer(), nullable=True), - sa.Column("task_id", sa.String(length=50), nullable=True), - sa.Column("task_name", sa.String(length=50), nullable=True), - sa.Column("task_title", sa.String(length=50), nullable=True), - sa.Column("task_type", sa.String(length=50), nullable=True), - sa.Column("task_status", sa.String(length=50), nullable=True), - sa.Column("process_model_display_name", sa.String(length=255), nullable=True), - sa.ForeignKeyConstraint( - ["actual_owner_id"], - ["user.id"], - ), - sa.ForeignKeyConstraint( - ["lane_assignment_id"], - ["group.id"], - ), - sa.ForeignKeyConstraint( - ["process_instance_id"], - ["process_instance.id"], - ), - sa.PrimaryKeyConstraint("id"), - sa.UniqueConstraint( - "task_id", "process_instance_id", name="active_task_unique" - ), - ) - op.create_table( - "message_correlation", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("process_instance_id", sa.Integer(), nullable=False), - sa.Column("message_correlation_property_id", sa.Integer(), nullable=False), - sa.Column("name", sa.String(length=255), nullable=False), - sa.Column("value", sa.String(length=255), nullable=False), - sa.Column("updated_at_in_seconds", sa.Integer(), nullable=True), - sa.Column("created_at_in_seconds", sa.Integer(), nullable=True), - sa.ForeignKeyConstraint( - ["message_correlation_property_id"], - ["message_correlation_property.id"], - ), - sa.ForeignKeyConstraint( - ["process_instance_id"], - ["process_instance.id"], - ), - sa.PrimaryKeyConstraint("id"), - sa.UniqueConstraint( - "process_instance_id", - "message_correlation_property_id", - "name", - name="message_instance_id_name_unique", - ), - ) - op.create_index( - op.f("ix_message_correlation_message_correlation_property_id"), - "message_correlation", - ["message_correlation_property_id"], - unique=False, - ) - op.create_index( - op.f("ix_message_correlation_name"), - "message_correlation", - ["name"], - unique=False, - ) - op.create_index( - op.f("ix_message_correlation_process_instance_id"), - "message_correlation", - ["process_instance_id"], - unique=False, - ) - op.create_index( - op.f("ix_message_correlation_value"), - "message_correlation", - ["value"], - unique=False, - ) - op.create_table( - "message_instance", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("process_instance_id", sa.Integer(), nullable=False), - sa.Column("message_model_id", sa.Integer(), nullable=False), - sa.Column("message_type", sa.String(length=20), nullable=False), - sa.Column("payload", sa.JSON(), nullable=True), - sa.Column("status", sa.String(length=20), nullable=False), - sa.Column("failure_cause", sa.Text(), nullable=True), - sa.Column("updated_at_in_seconds", sa.Integer(), nullable=True), - sa.Column("created_at_in_seconds", sa.Integer(), nullable=True), - sa.ForeignKeyConstraint( - ["message_model_id"], - ["message_model.id"], - ), - sa.ForeignKeyConstraint( - ["process_instance_id"], - ["process_instance.id"], - ), - sa.PrimaryKeyConstraint("id"), - ) - op.create_table( - "permission_assignment", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("principal_id", sa.Integer(), nullable=False), - sa.Column("permission_target_id", sa.Integer(), nullable=False), - sa.Column("grant_type", sa.String(length=50), nullable=False), - sa.Column("permission", sa.String(length=50), nullable=False), - sa.ForeignKeyConstraint( - ["permission_target_id"], - ["permission_target.id"], - ), - sa.ForeignKeyConstraint( - ["principal_id"], - ["principal.id"], - ), - sa.PrimaryKeyConstraint("id"), - sa.UniqueConstraint( - "principal_id", - "permission_target_id", - "permission", - name="permission_assignment_uniq", - ), - ) - op.create_table( - "active_task_user", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("active_task_id", sa.Integer(), nullable=False), - sa.Column("user_id", sa.Integer(), nullable=False), - sa.ForeignKeyConstraint( - ["active_task_id"], - ["active_task.id"], - ), - sa.ForeignKeyConstraint( - ["user_id"], - ["user.id"], - ), - sa.PrimaryKeyConstraint("id"), - sa.UniqueConstraint( - "active_task_id", "user_id", name="active_task_user_unique" - ), - ) - op.create_index( - op.f("ix_active_task_user_active_task_id"), - "active_task_user", - ["active_task_id"], - unique=False, - ) - op.create_index( - op.f("ix_active_task_user_user_id"), - "active_task_user", - ["user_id"], - unique=False, - ) - op.create_table( - "message_correlation_message_instance", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("message_instance_id", sa.Integer(), nullable=False), - sa.Column("message_correlation_id", sa.Integer(), nullable=False), - sa.ForeignKeyConstraint( - ["message_correlation_id"], - ["message_correlation.id"], - ), - sa.ForeignKeyConstraint( - ["message_instance_id"], - ["message_instance.id"], - ), - sa.PrimaryKeyConstraint("id"), - sa.UniqueConstraint( - "message_instance_id", - "message_correlation_id", - name="message_correlation_message_instance_unique", - ), - ) - op.create_index( - op.f("ix_message_correlation_message_instance_message_correlation_id"), - "message_correlation_message_instance", - ["message_correlation_id"], - unique=False, - ) - op.create_index( - op.f("ix_message_correlation_message_instance_message_instance_id"), - "message_correlation_message_instance", - ["message_instance_id"], - unique=False, - ) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_index( - op.f("ix_message_correlation_message_instance_message_instance_id"), - table_name="message_correlation_message_instance", - ) - op.drop_index( - op.f("ix_message_correlation_message_instance_message_correlation_id"), - table_name="message_correlation_message_instance", - ) - op.drop_table("message_correlation_message_instance") - op.drop_index(op.f("ix_active_task_user_user_id"), table_name="active_task_user") - op.drop_index( - op.f("ix_active_task_user_active_task_id"), table_name="active_task_user" - ) - op.drop_table("active_task_user") - op.drop_table("permission_assignment") - op.drop_table("message_instance") - op.drop_index( - op.f("ix_message_correlation_value"), table_name="message_correlation" - ) - op.drop_index( - op.f("ix_message_correlation_process_instance_id"), - table_name="message_correlation", - ) - op.drop_index(op.f("ix_message_correlation_name"), table_name="message_correlation") - op.drop_index( - op.f("ix_message_correlation_message_correlation_property_id"), - table_name="message_correlation", - ) - op.drop_table("message_correlation") - op.drop_table("active_task") - op.drop_table("user_group_assignment") - op.drop_table("secret") - op.drop_table("refresh_token") - op.drop_index( - op.f("ix_process_instance_report_identifier"), - table_name="process_instance_report", - ) - op.drop_index( - op.f("ix_process_instance_report_created_by_id"), - table_name="process_instance_report", - ) - op.drop_table("process_instance_report") - op.drop_index( - op.f("ix_process_instance_process_model_identifier"), - table_name="process_instance", - ) - op.drop_index( - op.f("ix_process_instance_process_group_identifier"), - table_name="process_instance", - ) - op.drop_table("process_instance") - op.drop_table("principal") - op.drop_index( - op.f("ix_message_triggerable_process_model_process_model_identifier"), - table_name="message_triggerable_process_model", - ) - op.drop_index( - op.f("ix_message_triggerable_process_model_process_group_identifier"), - table_name="message_triggerable_process_model", - ) - op.drop_table("message_triggerable_process_model") - op.drop_index( - op.f("ix_message_correlation_property_identifier"), - table_name="message_correlation_property", - ) - op.drop_table("message_correlation_property") - op.drop_table("user") - op.drop_table("spiff_step_details") - op.drop_table("spiff_logging") - op.drop_table("permission_target") - op.drop_index(op.f("ix_message_model_name"), table_name="message_model") - op.drop_index(op.f("ix_message_model_identifier"), table_name="message_model") - op.drop_table("message_model") - op.drop_table("group") - op.drop_index( - op.f("ix_bpmn_process_id_lookup_bpmn_process_identifier"), - table_name="bpmn_process_id_lookup", - ) - op.drop_table("bpmn_process_id_lookup") - # ### end Alembic commands ### diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/models/spiff_step_details.py b/spiffworkflow-backend/src/spiffworkflow_backend/models/spiff_step_details.py index 56499e91d..3bba5aed6 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/models/spiff_step_details.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/models/spiff_step_details.py @@ -1,10 +1,15 @@ """Spiff_step_details.""" from dataclasses import dataclass +from typing import Optional + +from sqlalchemy import ForeignKey from flask_bpmn.models.db import db from flask_bpmn.models.db import SpiffworkflowBaseDBModel from sqlalchemy.orm import deferred +from spiffworkflow_backend.models.group import GroupModel + @dataclass class SpiffStepDetailsModel(SpiffworkflowBaseDBModel): @@ -17,3 +22,4 @@ class SpiffStepDetailsModel(SpiffworkflowBaseDBModel): task_json: str = deferred(db.Column(db.JSON, nullable=False)) # type: ignore timestamp: float = db.Column(db.DECIMAL(17, 6), nullable=False) completed_by_user_id: int = db.Column(db.Integer, nullable=True) + lane_assignment_id: Optional[int] = db.Column(ForeignKey(GroupModel.id), nullable=True) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py index 27d813a82..c4044b2a2 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py @@ -1264,7 +1264,17 @@ def task_submit( if terminate_loop and spiff_task.is_looping(): 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 # task spec, complete that form as well. diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py index 91db99804..d9ac6c05b 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py @@ -580,9 +580,10 @@ class ProcessInstanceProcessor: ) return details_model - def save_spiff_step_details(self) -> None: + def save_spiff_step_details(self, active_task: ActiveTaskModel) -> None: """SaveSpiffStepDetails.""" details_model = self.spiff_step_details() + details_model.lane_assignment_id = active_task.lane_assignment_id db.session.add(details_model) db.session.commit() @@ -1129,11 +1130,11 @@ class ProcessInstanceProcessor: ) return user_tasks # type: ignore - def complete_task(self, task: SpiffTask) -> None: + def complete_task(self, task: SpiffTask, active_task: ActiveTaskModel) -> None: """Complete_task.""" self.increment_spiff_step() 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]: """Get_data.""" diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_service.py index ca9f66aa7..ccf362ab7 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_service.py @@ -6,7 +6,8 @@ from typing import List from flask import current_app from flask_bpmn.api.api_error import ApiError from flask_bpmn.models.db import db -from SpiffWorkflow.task import Task as SpiffTask # type: ignore +from SpiffWorkflow.task import Task as SpiffTask +from spiffworkflow_backend.models.active_task import ActiveTaskModel # type: ignore from spiffworkflow_backend.models.process_instance import ProcessInstanceApi from spiffworkflow_backend.models.process_instance import ProcessInstanceModel @@ -188,6 +189,7 @@ class ProcessInstanceService: spiff_task: SpiffTask, data: dict[str, Any], user: UserModel, + active_task: ActiveTaskModel ) -> None: """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) spiff_task.update_data(dot_dct) # 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) @staticmethod diff --git a/spiffworkflow-frontend/src/components/PaginationForTable.tsx b/spiffworkflow-frontend/src/components/PaginationForTable.tsx index 3b65c78c2..21813952c 100644 --- a/spiffworkflow-frontend/src/components/PaginationForTable.tsx +++ b/spiffworkflow-frontend/src/components/PaginationForTable.tsx @@ -1,4 +1,4 @@ -import { useNavigate } from 'react-router-dom'; +import { useNavigate, useSearchParams } from 'react-router-dom'; // @ts-ignore import { Pagination } from '@carbon/react'; @@ -14,7 +14,7 @@ type OwnProps = { pagination: PaginationObject | null; tableToDisplay: any; queryParamString?: string; - path: string; + paginationQueryParamPrefix?: string; }; export default function PaginationForTable({ @@ -23,16 +23,21 @@ export default function PaginationForTable({ perPageOptions, pagination, tableToDisplay, - queryParamString = '', - path, + paginationQueryParamPrefix, }: OwnProps) { const PER_PAGE_OPTIONS = [2, 10, 50, 100]; - const navigate = useNavigate(); + const [searchParams, setSearchParams] = useSearchParams(); + const paginationQueryParamPrefixToUse = paginationQueryParamPrefix + ? `${paginationQueryParamPrefix}_` + : ''; const updateRows = (args: any) => { const newPage = args.page; 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) { diff --git a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx new file mode 100644 index 000000000..f09ae7113 --- /dev/null +++ b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx @@ -0,0 +1,541 @@ +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'; +import ProcessBreadcrumb from './ProcessBreadcrumb'; + +export default function ProcessInstanceList() { + const params = useParams(); + const [searchParams] = useSearchParams(); + const navigate = useNavigate(); + + const [processInstances, setProcessInstances] = useState([]); + const [reportMetadata, setReportMetadata] = useState({}); + const [pagination, setPagination] = useState(null); + + const oneHourInSeconds = 3600; + const oneMonthInSeconds = oneHourInSeconds * 24 * 30; + const [startFrom, setStartFrom] = useState(''); + const [startTo, setStartTo] = useState(''); + const [endFrom, setEndFrom] = useState(''); + const [endTo, setEndTo] = useState(''); + const [showFilterOptions, setShowFilterOptions] = useState(false); + + const setErrorMessage = (useContext as any)(ErrorContext)[1]; + + const [processStatusAllOptions, setProcessStatusAllOptions] = useState( + [] + ); + const [processStatusSelection, setProcessStatusSelection] = useState< + string[] + >([]); + const [processModelAvailableItems, setProcessModelAvailableItems] = useState< + ProcessModel[] + >([]); + const [processModelSelection, setProcessModelSelection] = + useState(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 ( + + { + onChangeFunction(dateChangeEvent.srcElement.value); + }} + value={initialDate} + /> + + ); + }; + + 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 processStatusSearch = () => { + return ( + { + 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 ( + <> + + + + setProcessModelSelection(selection.selectedItem) + } + processModels={processModelAvailableItems} + selectedItem={processModelSelection} + /> + + {processStatusSearch()} + + + + {dateComponent( + 'Start date from', + 'start-from', + startFrom, + setStartFrom + )} + + + {dateComponent('Start date to', 'start-to', startTo, setStartTo)} + + + {dateComponent('End date from', 'end-from', endFrom, setEndFrom)} + + + {dateComponent('End date to', 'end-to', endTo, setEndTo)} + + + + + + + + + + + + ); + }; + + const buildTable = () => { + const headerLabels: Record = { + 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 {getHeaderLabel((column as any).Header)}; + return getHeaderLabel((column as any).Header); + }); + + const formatProcessInstanceId = (row: any, id: any) => { + const modifiedProcessModelId: String = modifyProcessModelPath( + row.process_model_identifier + ); + return ( + + {id} + + ); + }; + const formatProcessModelIdentifier = (_row: any, identifier: any) => { + return ( + + {identifier} + + ); + }; + const formatSecondsForDisplay = (_row: any, seconds: any) => { + return convertSecondsToFormattedDate(seconds) || '-'; + }; + const defaultFormatter = (_row: any, value: any) => { + return value; + }; + + const columnFormatters: Record = { + 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 ( + + {formatter(row, value)} + + ); + } + return {formatter(row, value)}; + }; + + const rows = processInstances.map((row: any) => { + const currentRow = (reportMetadata as any).columns.map((column: any) => { + return formattedColumn(row, column); + }); + return {currentRow}; + }); + + return ( + + + + {headers.map((header: any) => ( + {header} + ))} + + + {rows} +
+ ); + }; + + const processInstanceBreadcrumbElement = () => { + const processModelFullIdentifier = + getProcessModelFullIdentifierFromSearchParams(searchParams); + if (processModelFullIdentifier === null) { + return null; + } + + return ( + + ); + }; + + const processInstanceTitleElement = () => { + return

Process Instances

; + }; + + const toggleShowFilterOptions = () => { + setShowFilterOptions(!showFilterOptions); + }; + + if (pagination) { + const { page, perPage } = getPageInfoFromSearchParams(searchParams); + return ( + <> + {processInstanceBreadcrumbElement()} + {processInstanceTitleElement()} + + + - - - - - - ); - }; - const toggleShowFilterOptions = () => { - setShowFilterOptions(!showFilterOptions); - }; - const filterComponent = () => { - return ( - <> - - -