Merge remote-tracking branch 'origin/main' into feature/interstitial

This commit is contained in:
Dan 2023-04-19 15:18:10 -04:00
commit d73baedcbe
41 changed files with 17243 additions and 15133 deletions

View File

@ -56,7 +56,7 @@ jobs:
run: echo "date=$(date -u +'%Y-%m-%d_%H-%M-%S')" >> "$GITHUB_OUTPUT"
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v4.3.0
uses: docker/metadata-action@v4.4.0
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
@ -100,7 +100,7 @@ jobs:
run: echo "date=$(date -u +'%Y-%m-%d_%H-%M-%S')" >> "$GITHUB_OUTPUT"
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v4.3.0
uses: docker/metadata-action@v4.4.0
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |

View File

@ -25,7 +25,7 @@ jobs:
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v4.3.0
uses: docker/metadata-action@v4.4.0
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
@ -58,7 +58,7 @@ jobs:
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v4.3.0
uses: docker/metadata-action@v4.4.0
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
@ -92,7 +92,7 @@ jobs:
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v4.3.0
uses: docker/metadata-action@v4.4.0
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}

1
.gitignore vendored
View File

@ -5,3 +5,4 @@ t
.dccache
*~
version_info.json
.coverage*

View File

@ -19,3 +19,4 @@ node_modules
/bin/import_secrets.py
/src/spiffworkflow_backend/config/secrets.py
*null-ls_*
/local_wheels/*.whl

View File

@ -0,0 +1,15 @@
# Local Wheels
If you have any wheels you wish to test locally, copy them into this folder then run:
```
poetry add local_wheels/my.whl
```
Alternatively you can sideload it:
```
poetry run pip install local_wheels/*.whl
```
when you boot the backend.

View File

@ -14,15 +14,30 @@ config = context.config
fileConfig(config.config_file_name)
logger = logging.getLogger('alembic.env')
def get_engine():
try:
# this works with Flask-SQLAlchemy<3 and Alchemical
return current_app.extensions['migrate'].db.get_engine()
except TypeError:
# this works with Flask-SQLAlchemy>=3
return current_app.extensions['migrate'].db.engine
def get_engine_url():
try:
return get_engine().url.render_as_string(hide_password=False).replace(
'%', '%%')
except AttributeError:
return str(get_engine().url).replace('%', '%%')
# 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
config.set_main_option('sqlalchemy.url', get_engine_url())
target_db = current_app.extensions['migrate'].db
# other values from the config, defined by the needs of env.py,
# can be acquired:
@ -30,6 +45,12 @@ target_metadata = current_app.extensions['migrate'].db.metadata
# ... etc.
def get_metadata():
if hasattr(target_db, 'metadatas'):
return target_db.metadatas[None]
return target_db.metadata
def run_migrations_offline():
"""Run migrations in 'offline' mode.
@ -44,7 +65,7 @@ def run_migrations_offline():
"""
url = config.get_main_option("sqlalchemy.url")
context.configure(
url=url, target_metadata=target_metadata, literal_binds=True
url=url, target_metadata=get_metadata(), literal_binds=True
)
with context.begin_transaction():
@ -69,12 +90,12 @@ def run_migrations_online():
directives[:] = []
logger.info('No changes in schema detected.')
connectable = current_app.extensions['migrate'].db.get_engine()
connectable = get_engine()
with connectable.connect() as connection:
context.configure(
connection=connection,
target_metadata=target_metadata,
target_metadata=get_metadata(),
process_revision_directives=process_revision_directives,
**current_app.extensions['migrate'].configure_args
)

View File

@ -1,8 +1,8 @@
"""empty message
Revision ID: 0b5dd14bfbac
Revision ID: 44a8f46cc508
Revises:
Create Date: 2023-03-23 16:25:33.288500
Create Date: 2023-04-17 15:40:28.658588
"""
from alembic import op
@ -10,7 +10,7 @@ import sqlalchemy as sa
from sqlalchemy.dialects import mysql
# revision identifiers, used by Alembic.
revision = '0b5dd14bfbac'
revision = '44a8f46cc508'
down_revision = None
branch_labels = None
depends_on = None
@ -20,7 +20,8 @@ def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('bpmn_process_definition',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('hash', sa.String(length=255), nullable=False),
sa.Column('single_process_hash', sa.String(length=255), nullable=False),
sa.Column('full_process_model_hash', sa.String(length=255), nullable=True),
sa.Column('bpmn_identifier', sa.String(length=255), nullable=False),
sa.Column('bpmn_name', sa.String(length=255), nullable=True),
sa.Column('properties_json', sa.JSON(), nullable=False),
@ -29,10 +30,13 @@ def upgrade():
sa.Column('updated_at_in_seconds', sa.Integer(), nullable=True),
sa.Column('created_at_in_seconds', sa.Integer(), nullable=True),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('hash')
sa.UniqueConstraint('full_process_model_hash'),
sa.UniqueConstraint('full_process_model_hash', 'single_process_hash', name='process_hash_unique')
)
op.create_index(op.f('ix_bpmn_process_definition_bpmn_identifier'), 'bpmn_process_definition', ['bpmn_identifier'], unique=False)
op.create_index(op.f('ix_bpmn_process_definition_bpmn_name'), 'bpmn_process_definition', ['bpmn_name'], unique=False)
with op.batch_alter_table('bpmn_process_definition', schema=None) as batch_op:
batch_op.create_index(batch_op.f('ix_bpmn_process_definition_bpmn_identifier'), ['bpmn_identifier'], unique=False)
batch_op.create_index(batch_op.f('ix_bpmn_process_definition_bpmn_name'), ['bpmn_name'], unique=False)
op.create_table('correlation_property_cache',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=50), nullable=False),
@ -41,16 +45,20 @@ def upgrade():
sa.Column('retrieval_expression', sa.String(length=255), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_correlation_property_cache_message_name'), 'correlation_property_cache', ['message_name'], unique=False)
op.create_index(op.f('ix_correlation_property_cache_name'), 'correlation_property_cache', ['name'], unique=False)
with op.batch_alter_table('correlation_property_cache', schema=None) as batch_op:
batch_op.create_index(batch_op.f('ix_correlation_property_cache_message_name'), ['message_name'], unique=False)
batch_op.create_index(batch_op.f('ix_correlation_property_cache_name'), ['name'], unique=False)
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_index(op.f('ix_group_identifier'), 'group', ['identifier'], unique=False)
op.create_index(op.f('ix_group_name'), 'group', ['name'], unique=False)
with op.batch_alter_table('group', schema=None) as batch_op:
batch_op.create_index(batch_op.f('ix_group_identifier'), ['identifier'], unique=False)
batch_op.create_index(batch_op.f('ix_group_name'), ['name'], unique=False)
op.create_table('json_data',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('hash', sa.String(length=255), nullable=False),
@ -66,8 +74,10 @@ def upgrade():
sa.Column('created_at_in_seconds', sa.Integer(), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_message_triggerable_process_model_message_name'), 'message_triggerable_process_model', ['message_name'], unique=False)
op.create_index(op.f('ix_message_triggerable_process_model_process_model_identifier'), 'message_triggerable_process_model', ['process_model_identifier'], unique=False)
with op.batch_alter_table('message_triggerable_process_model', schema=None) as batch_op:
batch_op.create_index(batch_op.f('ix_message_triggerable_process_model_message_name'), ['message_name'], unique=False)
batch_op.create_index(batch_op.f('ix_message_triggerable_process_model_process_model_identifier'), ['process_model_identifier'], unique=False)
op.create_table('permission_target',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('uri', sa.String(length=255), nullable=False),
@ -88,10 +98,12 @@ def upgrade():
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('identifier', 'type', name='_identifier_type_unique')
)
op.create_index(op.f('ix_spec_reference_cache_display_name'), 'spec_reference_cache', ['display_name'], unique=False)
op.create_index(op.f('ix_spec_reference_cache_identifier'), 'spec_reference_cache', ['identifier'], unique=False)
op.create_index(op.f('ix_spec_reference_cache_process_model_id'), 'spec_reference_cache', ['process_model_id'], unique=False)
op.create_index(op.f('ix_spec_reference_cache_type'), 'spec_reference_cache', ['type'], unique=False)
with op.batch_alter_table('spec_reference_cache', schema=None) as batch_op:
batch_op.create_index(batch_op.f('ix_spec_reference_cache_display_name'), ['display_name'], unique=False)
batch_op.create_index(batch_op.f('ix_spec_reference_cache_identifier'), ['identifier'], unique=False)
batch_op.create_index(batch_op.f('ix_spec_reference_cache_process_model_id'), ['process_model_id'], unique=False)
batch_op.create_index(batch_op.f('ix_spec_reference_cache_type'), ['type'], unique=False)
op.create_table('user',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('username', sa.String(length=255), nullable=False),
@ -108,9 +120,11 @@ def upgrade():
sa.UniqueConstraint('service', 'service_id', name='service_key'),
sa.UniqueConstraint('username')
)
op.create_index(op.f('ix_user_email'), 'user', ['email'], unique=False)
op.create_index(op.f('ix_user_service'), 'user', ['service'], unique=False)
op.create_index(op.f('ix_user_service_id'), 'user', ['service_id'], unique=False)
with op.batch_alter_table('user', schema=None) as batch_op:
batch_op.create_index(batch_op.f('ix_user_email'), ['email'], unique=False)
batch_op.create_index(batch_op.f('ix_user_service'), ['service'], unique=False)
batch_op.create_index(batch_op.f('ix_user_service_id'), ['service_id'], unique=False)
op.create_table('bpmn_process',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('guid', sa.String(length=36), nullable=True),
@ -127,10 +141,12 @@ def upgrade():
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('guid')
)
op.create_index(op.f('ix_bpmn_process_bpmn_process_definition_id'), 'bpmn_process', ['bpmn_process_definition_id'], unique=False)
op.create_index(op.f('ix_bpmn_process_direct_parent_process_id'), 'bpmn_process', ['direct_parent_process_id'], unique=False)
op.create_index(op.f('ix_bpmn_process_json_data_hash'), 'bpmn_process', ['json_data_hash'], unique=False)
op.create_index(op.f('ix_bpmn_process_top_level_process_id'), 'bpmn_process', ['top_level_process_id'], unique=False)
with op.batch_alter_table('bpmn_process', schema=None) as batch_op:
batch_op.create_index(batch_op.f('ix_bpmn_process_bpmn_process_definition_id'), ['bpmn_process_definition_id'], unique=False)
batch_op.create_index(batch_op.f('ix_bpmn_process_direct_parent_process_id'), ['direct_parent_process_id'], unique=False)
batch_op.create_index(batch_op.f('ix_bpmn_process_json_data_hash'), ['json_data_hash'], unique=False)
batch_op.create_index(batch_op.f('ix_bpmn_process_top_level_process_id'), ['top_level_process_id'], unique=False)
op.create_table('bpmn_process_definition_relationship',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('bpmn_process_definition_parent_id', sa.Integer(), nullable=False),
@ -140,8 +156,10 @@ def upgrade():
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('bpmn_process_definition_parent_id', 'bpmn_process_definition_child_id', name='bpmn_process_definition_relationship_unique')
)
op.create_index(op.f('ix_bpmn_process_definition_relationship_bpmn_process_definition_parent_id'), 'bpmn_process_definition_relationship', ['bpmn_process_definition_parent_id'], unique=False)
op.create_index(op.f('ix_bpmn_process_definition_relationship_bpmn_process_definition_child_id'), 'bpmn_process_definition_relationship', ['bpmn_process_definition_child_id'], unique=False)
with op.batch_alter_table('bpmn_process_definition_relationship', schema=None) as batch_op:
batch_op.create_index(batch_op.f('ix_bpmn_process_definition_relationship_bpmn_process_definition_parent_id'), ['bpmn_process_definition_parent_id'], unique=False)
batch_op.create_index(batch_op.f('ix_bpmn_process_definition_relationship_bpmn_process_definition_child_id'), ['bpmn_process_definition_child_id'], unique=False)
op.create_table('principal',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('user_id', sa.Integer(), nullable=True),
@ -164,8 +182,10 @@ def upgrade():
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)
with op.batch_alter_table('process_instance_report', schema=None) as batch_op:
batch_op.create_index(batch_op.f('ix_process_instance_report_created_by_id'), ['created_by_id'], unique=False)
batch_op.create_index(batch_op.f('ix_process_instance_report_identifier'), ['identifier'], unique=False)
op.create_table('refresh_token',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('user_id', sa.Integer(), nullable=False),
@ -185,7 +205,9 @@ def upgrade():
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('key')
)
op.create_index(op.f('ix_secret_user_id'), 'secret', ['user_id'], unique=False)
with op.batch_alter_table('secret', schema=None) as batch_op:
batch_op.create_index(batch_op.f('ix_secret_user_id'), ['user_id'], unique=False)
op.create_table('task_definition',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('bpmn_process_definition_id', sa.Integer(), nullable=False),
@ -199,10 +221,12 @@ def upgrade():
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('bpmn_process_definition_id', 'bpmn_identifier', name='task_definition_unique')
)
op.create_index(op.f('ix_task_definition_bpmn_identifier'), 'task_definition', ['bpmn_identifier'], unique=False)
op.create_index(op.f('ix_task_definition_bpmn_name'), 'task_definition', ['bpmn_name'], unique=False)
op.create_index(op.f('ix_task_definition_bpmn_process_definition_id'), 'task_definition', ['bpmn_process_definition_id'], unique=False)
op.create_index(op.f('ix_task_definition_typename'), 'task_definition', ['typename'], unique=False)
with op.batch_alter_table('task_definition', schema=None) as batch_op:
batch_op.create_index(batch_op.f('ix_task_definition_bpmn_identifier'), ['bpmn_identifier'], unique=False)
batch_op.create_index(batch_op.f('ix_task_definition_bpmn_name'), ['bpmn_name'], unique=False)
batch_op.create_index(batch_op.f('ix_task_definition_bpmn_process_definition_id'), ['bpmn_process_definition_id'], unique=False)
batch_op.create_index(batch_op.f('ix_task_definition_typename'), ['typename'], unique=False)
op.create_table('user_group_assignment',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('user_id', sa.Integer(), nullable=False),
@ -212,8 +236,10 @@ def upgrade():
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('user_id', 'group_id', name='user_group_assignment_unique')
)
op.create_index(op.f('ix_user_group_assignment_group_id'), 'user_group_assignment', ['group_id'], unique=False)
op.create_index(op.f('ix_user_group_assignment_user_id'), 'user_group_assignment', ['user_id'], unique=False)
with op.batch_alter_table('user_group_assignment', schema=None) as batch_op:
batch_op.create_index(batch_op.f('ix_user_group_assignment_group_id'), ['group_id'], unique=False)
batch_op.create_index(batch_op.f('ix_user_group_assignment_user_id'), ['user_id'], unique=False)
op.create_table('user_group_assignment_waiting',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('username', sa.String(length=255), nullable=False),
@ -222,7 +248,9 @@ def upgrade():
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('username', 'group_id', name='user_group_assignment_staged_unique')
)
op.create_index(op.f('ix_user_group_assignment_waiting_group_id'), 'user_group_assignment_waiting', ['group_id'], unique=False)
with op.batch_alter_table('user_group_assignment_waiting', schema=None) as batch_op:
batch_op.create_index(batch_op.f('ix_user_group_assignment_waiting_group_id'), ['group_id'], unique=False)
op.create_table('permission_assignment',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('principal_id', sa.Integer(), nullable=False),
@ -234,8 +262,10 @@ def upgrade():
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('principal_id', 'permission_target_id', 'permission', name='permission_assignment_uniq')
)
op.create_index(op.f('ix_permission_assignment_permission_target_id'), 'permission_assignment', ['permission_target_id'], unique=False)
op.create_index(op.f('ix_permission_assignment_principal_id'), 'permission_assignment', ['principal_id'], unique=False)
with op.batch_alter_table('permission_assignment', schema=None) as batch_op:
batch_op.create_index(batch_op.f('ix_permission_assignment_permission_target_id'), ['permission_target_id'], unique=False)
batch_op.create_index(batch_op.f('ix_permission_assignment_principal_id'), ['principal_id'], unique=False)
op.create_table('process_instance',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('process_model_identifier', sa.String(length=255), nullable=False),
@ -256,14 +286,16 @@ def upgrade():
sa.ForeignKeyConstraint(['process_initiator_id'], ['user.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_process_instance_bpmn_process_definition_id'), 'process_instance', ['bpmn_process_definition_id'], unique=False)
op.create_index(op.f('ix_process_instance_bpmn_process_id'), 'process_instance', ['bpmn_process_id'], unique=False)
op.create_index(op.f('ix_process_instance_end_in_seconds'), 'process_instance', ['end_in_seconds'], unique=False)
op.create_index(op.f('ix_process_instance_process_initiator_id'), 'process_instance', ['process_initiator_id'], unique=False)
op.create_index(op.f('ix_process_instance_process_model_display_name'), 'process_instance', ['process_model_display_name'], unique=False)
op.create_index(op.f('ix_process_instance_process_model_identifier'), 'process_instance', ['process_model_identifier'], unique=False)
op.create_index(op.f('ix_process_instance_start_in_seconds'), 'process_instance', ['start_in_seconds'], unique=False)
op.create_index(op.f('ix_process_instance_status'), 'process_instance', ['status'], unique=False)
with op.batch_alter_table('process_instance', schema=None) as batch_op:
batch_op.create_index(batch_op.f('ix_process_instance_bpmn_process_definition_id'), ['bpmn_process_definition_id'], unique=False)
batch_op.create_index(batch_op.f('ix_process_instance_bpmn_process_id'), ['bpmn_process_id'], unique=False)
batch_op.create_index(batch_op.f('ix_process_instance_end_in_seconds'), ['end_in_seconds'], unique=False)
batch_op.create_index(batch_op.f('ix_process_instance_process_initiator_id'), ['process_initiator_id'], unique=False)
batch_op.create_index(batch_op.f('ix_process_instance_process_model_display_name'), ['process_model_display_name'], unique=False)
batch_op.create_index(batch_op.f('ix_process_instance_process_model_identifier'), ['process_model_identifier'], unique=False)
batch_op.create_index(batch_op.f('ix_process_instance_start_in_seconds'), ['start_in_seconds'], unique=False)
batch_op.create_index(batch_op.f('ix_process_instance_status'), ['status'], unique=False)
op.create_table('message_instance',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('process_instance_id', sa.Integer(), nullable=True),
@ -281,9 +313,11 @@ def upgrade():
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_message_instance_process_instance_id'), 'message_instance', ['process_instance_id'], unique=False)
op.create_index(op.f('ix_message_instance_status'), 'message_instance', ['status'], unique=False)
op.create_index(op.f('ix_message_instance_user_id'), 'message_instance', ['user_id'], unique=False)
with op.batch_alter_table('message_instance', schema=None) as batch_op:
batch_op.create_index(batch_op.f('ix_message_instance_process_instance_id'), ['process_instance_id'], unique=False)
batch_op.create_index(batch_op.f('ix_message_instance_status'), ['status'], unique=False)
batch_op.create_index(batch_op.f('ix_message_instance_user_id'), ['user_id'], unique=False)
op.create_table('process_instance_event',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('task_guid', sa.String(length=36), nullable=True),
@ -295,11 +329,13 @@ def upgrade():
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_process_instance_event_event_type'), 'process_instance_event', ['event_type'], unique=False)
op.create_index(op.f('ix_process_instance_event_process_instance_id'), 'process_instance_event', ['process_instance_id'], unique=False)
op.create_index(op.f('ix_process_instance_event_task_guid'), 'process_instance_event', ['task_guid'], unique=False)
op.create_index(op.f('ix_process_instance_event_timestamp'), 'process_instance_event', ['timestamp'], unique=False)
op.create_index(op.f('ix_process_instance_event_user_id'), 'process_instance_event', ['user_id'], unique=False)
with op.batch_alter_table('process_instance_event', schema=None) as batch_op:
batch_op.create_index(batch_op.f('ix_process_instance_event_event_type'), ['event_type'], unique=False)
batch_op.create_index(batch_op.f('ix_process_instance_event_process_instance_id'), ['process_instance_id'], unique=False)
batch_op.create_index(batch_op.f('ix_process_instance_event_task_guid'), ['task_guid'], unique=False)
batch_op.create_index(batch_op.f('ix_process_instance_event_timestamp'), ['timestamp'], unique=False)
batch_op.create_index(batch_op.f('ix_process_instance_event_user_id'), ['user_id'], unique=False)
op.create_table('process_instance_file_data',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('process_instance_id', sa.Integer(), nullable=False),
@ -314,8 +350,10 @@ def upgrade():
sa.ForeignKeyConstraint(['process_instance_id'], ['process_instance.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_process_instance_file_data_digest'), 'process_instance_file_data', ['digest'], unique=False)
op.create_index(op.f('ix_process_instance_file_data_process_instance_id'), 'process_instance_file_data', ['process_instance_id'], unique=False)
with op.batch_alter_table('process_instance_file_data', schema=None) as batch_op:
batch_op.create_index(batch_op.f('ix_process_instance_file_data_digest'), ['digest'], unique=False)
batch_op.create_index(batch_op.f('ix_process_instance_file_data_process_instance_id'), ['process_instance_id'], unique=False)
op.create_table('process_instance_metadata',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('process_instance_id', sa.Integer(), nullable=False),
@ -327,8 +365,10 @@ def upgrade():
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('process_instance_id', 'key', name='process_instance_metadata_unique')
)
op.create_index(op.f('ix_process_instance_metadata_key'), 'process_instance_metadata', ['key'], unique=False)
op.create_index(op.f('ix_process_instance_metadata_process_instance_id'), 'process_instance_metadata', ['process_instance_id'], unique=False)
with op.batch_alter_table('process_instance_metadata', schema=None) as batch_op:
batch_op.create_index(batch_op.f('ix_process_instance_metadata_key'), ['key'], unique=False)
batch_op.create_index(batch_op.f('ix_process_instance_metadata_process_instance_id'), ['process_instance_id'], unique=False)
op.create_table('process_instance_queue',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('process_instance_id', sa.Integer(), nullable=False),
@ -343,9 +383,11 @@ def upgrade():
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('process_instance_id')
)
op.create_index(op.f('ix_process_instance_queue_locked_at_in_seconds'), 'process_instance_queue', ['locked_at_in_seconds'], unique=False)
op.create_index(op.f('ix_process_instance_queue_locked_by'), 'process_instance_queue', ['locked_by'], unique=False)
op.create_index(op.f('ix_process_instance_queue_status'), 'process_instance_queue', ['status'], unique=False)
with op.batch_alter_table('process_instance_queue', schema=None) as batch_op:
batch_op.create_index(batch_op.f('ix_process_instance_queue_locked_at_in_seconds'), ['locked_at_in_seconds'], unique=False)
batch_op.create_index(batch_op.f('ix_process_instance_queue_locked_by'), ['locked_by'], unique=False)
batch_op.create_index(batch_op.f('ix_process_instance_queue_status'), ['status'], unique=False)
op.create_table('task',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('guid', sa.String(length=36), nullable=False),
@ -364,12 +406,14 @@ def upgrade():
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('guid')
)
op.create_index(op.f('ix_task_bpmn_process_id'), 'task', ['bpmn_process_id'], unique=False)
op.create_index(op.f('ix_task_json_data_hash'), 'task', ['json_data_hash'], unique=False)
op.create_index(op.f('ix_task_process_instance_id'), 'task', ['process_instance_id'], unique=False)
op.create_index(op.f('ix_task_python_env_data_hash'), 'task', ['python_env_data_hash'], unique=False)
op.create_index(op.f('ix_task_state'), 'task', ['state'], unique=False)
op.create_index(op.f('ix_task_task_definition_id'), 'task', ['task_definition_id'], unique=False)
with op.batch_alter_table('task', schema=None) as batch_op:
batch_op.create_index(batch_op.f('ix_task_bpmn_process_id'), ['bpmn_process_id'], unique=False)
batch_op.create_index(batch_op.f('ix_task_json_data_hash'), ['json_data_hash'], unique=False)
batch_op.create_index(batch_op.f('ix_task_process_instance_id'), ['process_instance_id'], unique=False)
batch_op.create_index(batch_op.f('ix_task_python_env_data_hash'), ['python_env_data_hash'], unique=False)
batch_op.create_index(batch_op.f('ix_task_state'), ['state'], unique=False)
batch_op.create_index(batch_op.f('ix_task_task_definition_id'), ['task_definition_id'], unique=False)
op.create_table('human_task',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('process_instance_id', sa.Integer(), nullable=False),
@ -396,12 +440,14 @@ def upgrade():
sa.ForeignKeyConstraint(['task_model_id'], ['task.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_human_task_actual_owner_id'), 'human_task', ['actual_owner_id'], unique=False)
op.create_index(op.f('ix_human_task_completed'), 'human_task', ['completed'], unique=False)
op.create_index(op.f('ix_human_task_completed_by_user_id'), 'human_task', ['completed_by_user_id'], unique=False)
op.create_index(op.f('ix_human_task_lane_assignment_id'), 'human_task', ['lane_assignment_id'], unique=False)
op.create_index(op.f('ix_human_task_process_instance_id'), 'human_task', ['process_instance_id'], unique=False)
op.create_index(op.f('ix_human_task_task_model_id'), 'human_task', ['task_model_id'], unique=False)
with op.batch_alter_table('human_task', schema=None) as batch_op:
batch_op.create_index(batch_op.f('ix_human_task_actual_owner_id'), ['actual_owner_id'], unique=False)
batch_op.create_index(batch_op.f('ix_human_task_completed'), ['completed'], unique=False)
batch_op.create_index(batch_op.f('ix_human_task_completed_by_user_id'), ['completed_by_user_id'], unique=False)
batch_op.create_index(batch_op.f('ix_human_task_lane_assignment_id'), ['lane_assignment_id'], unique=False)
batch_op.create_index(batch_op.f('ix_human_task_process_instance_id'), ['process_instance_id'], unique=False)
batch_op.create_index(batch_op.f('ix_human_task_task_model_id'), ['task_model_id'], unique=False)
op.create_table('message_instance_correlation_rule',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('message_instance_id', sa.Integer(), nullable=False),
@ -413,8 +459,10 @@ def upgrade():
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('message_instance_id', 'name', name='message_instance_id_name_unique')
)
op.create_index(op.f('ix_message_instance_correlation_rule_message_instance_id'), 'message_instance_correlation_rule', ['message_instance_id'], unique=False)
op.create_index(op.f('ix_message_instance_correlation_rule_name'), 'message_instance_correlation_rule', ['name'], unique=False)
with op.batch_alter_table('message_instance_correlation_rule', schema=None) as batch_op:
batch_op.create_index(batch_op.f('ix_message_instance_correlation_rule_message_instance_id'), ['message_instance_id'], unique=False)
batch_op.create_index(batch_op.f('ix_message_instance_correlation_rule_name'), ['name'], unique=False)
op.create_table('human_task_user',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('human_task_id', sa.Integer(), nullable=False),
@ -424,111 +472,161 @@ def upgrade():
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('human_task_id', 'user_id', name='human_task_user_unique')
)
op.create_index(op.f('ix_human_task_user_human_task_id'), 'human_task_user', ['human_task_id'], unique=False)
op.create_index(op.f('ix_human_task_user_user_id'), 'human_task_user', ['user_id'], unique=False)
with op.batch_alter_table('human_task_user', schema=None) as batch_op:
batch_op.create_index(batch_op.f('ix_human_task_user_human_task_id'), ['human_task_id'], unique=False)
batch_op.create_index(batch_op.f('ix_human_task_user_user_id'), ['user_id'], unique=False)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f('ix_human_task_user_user_id'), table_name='human_task_user')
op.drop_index(op.f('ix_human_task_user_human_task_id'), table_name='human_task_user')
with op.batch_alter_table('human_task_user', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_human_task_user_user_id'))
batch_op.drop_index(batch_op.f('ix_human_task_user_human_task_id'))
op.drop_table('human_task_user')
op.drop_index(op.f('ix_message_instance_correlation_rule_name'), table_name='message_instance_correlation_rule')
op.drop_index(op.f('ix_message_instance_correlation_rule_message_instance_id'), table_name='message_instance_correlation_rule')
with op.batch_alter_table('message_instance_correlation_rule', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_message_instance_correlation_rule_name'))
batch_op.drop_index(batch_op.f('ix_message_instance_correlation_rule_message_instance_id'))
op.drop_table('message_instance_correlation_rule')
op.drop_index(op.f('ix_human_task_task_model_id'), table_name='human_task')
op.drop_index(op.f('ix_human_task_process_instance_id'), table_name='human_task')
op.drop_index(op.f('ix_human_task_lane_assignment_id'), table_name='human_task')
op.drop_index(op.f('ix_human_task_completed_by_user_id'), table_name='human_task')
op.drop_index(op.f('ix_human_task_completed'), table_name='human_task')
op.drop_index(op.f('ix_human_task_actual_owner_id'), table_name='human_task')
with op.batch_alter_table('human_task', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_human_task_task_model_id'))
batch_op.drop_index(batch_op.f('ix_human_task_process_instance_id'))
batch_op.drop_index(batch_op.f('ix_human_task_lane_assignment_id'))
batch_op.drop_index(batch_op.f('ix_human_task_completed_by_user_id'))
batch_op.drop_index(batch_op.f('ix_human_task_completed'))
batch_op.drop_index(batch_op.f('ix_human_task_actual_owner_id'))
op.drop_table('human_task')
op.drop_index(op.f('ix_task_task_definition_id'), table_name='task')
op.drop_index(op.f('ix_task_state'), table_name='task')
op.drop_index(op.f('ix_task_python_env_data_hash'), table_name='task')
op.drop_index(op.f('ix_task_process_instance_id'), table_name='task')
op.drop_index(op.f('ix_task_json_data_hash'), table_name='task')
op.drop_index(op.f('ix_task_bpmn_process_id'), table_name='task')
with op.batch_alter_table('task', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_task_task_definition_id'))
batch_op.drop_index(batch_op.f('ix_task_state'))
batch_op.drop_index(batch_op.f('ix_task_python_env_data_hash'))
batch_op.drop_index(batch_op.f('ix_task_process_instance_id'))
batch_op.drop_index(batch_op.f('ix_task_json_data_hash'))
batch_op.drop_index(batch_op.f('ix_task_bpmn_process_id'))
op.drop_table('task')
op.drop_index(op.f('ix_process_instance_queue_status'), table_name='process_instance_queue')
op.drop_index(op.f('ix_process_instance_queue_locked_by'), table_name='process_instance_queue')
op.drop_index(op.f('ix_process_instance_queue_locked_at_in_seconds'), table_name='process_instance_queue')
with op.batch_alter_table('process_instance_queue', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_process_instance_queue_status'))
batch_op.drop_index(batch_op.f('ix_process_instance_queue_locked_by'))
batch_op.drop_index(batch_op.f('ix_process_instance_queue_locked_at_in_seconds'))
op.drop_table('process_instance_queue')
op.drop_index(op.f('ix_process_instance_metadata_process_instance_id'), table_name='process_instance_metadata')
op.drop_index(op.f('ix_process_instance_metadata_key'), table_name='process_instance_metadata')
with op.batch_alter_table('process_instance_metadata', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_process_instance_metadata_process_instance_id'))
batch_op.drop_index(batch_op.f('ix_process_instance_metadata_key'))
op.drop_table('process_instance_metadata')
op.drop_index(op.f('ix_process_instance_file_data_process_instance_id'), table_name='process_instance_file_data')
op.drop_index(op.f('ix_process_instance_file_data_digest'), table_name='process_instance_file_data')
with op.batch_alter_table('process_instance_file_data', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_process_instance_file_data_process_instance_id'))
batch_op.drop_index(batch_op.f('ix_process_instance_file_data_digest'))
op.drop_table('process_instance_file_data')
op.drop_index(op.f('ix_process_instance_event_user_id'), table_name='process_instance_event')
op.drop_index(op.f('ix_process_instance_event_timestamp'), table_name='process_instance_event')
op.drop_index(op.f('ix_process_instance_event_task_guid'), table_name='process_instance_event')
op.drop_index(op.f('ix_process_instance_event_process_instance_id'), table_name='process_instance_event')
op.drop_index(op.f('ix_process_instance_event_event_type'), table_name='process_instance_event')
with op.batch_alter_table('process_instance_event', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_process_instance_event_user_id'))
batch_op.drop_index(batch_op.f('ix_process_instance_event_timestamp'))
batch_op.drop_index(batch_op.f('ix_process_instance_event_task_guid'))
batch_op.drop_index(batch_op.f('ix_process_instance_event_process_instance_id'))
batch_op.drop_index(batch_op.f('ix_process_instance_event_event_type'))
op.drop_table('process_instance_event')
op.drop_index(op.f('ix_message_instance_user_id'), table_name='message_instance')
op.drop_index(op.f('ix_message_instance_status'), table_name='message_instance')
op.drop_index(op.f('ix_message_instance_process_instance_id'), table_name='message_instance')
with op.batch_alter_table('message_instance', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_message_instance_user_id'))
batch_op.drop_index(batch_op.f('ix_message_instance_status'))
batch_op.drop_index(batch_op.f('ix_message_instance_process_instance_id'))
op.drop_table('message_instance')
op.drop_index(op.f('ix_process_instance_status'), table_name='process_instance')
op.drop_index(op.f('ix_process_instance_start_in_seconds'), table_name='process_instance')
op.drop_index(op.f('ix_process_instance_process_model_identifier'), table_name='process_instance')
op.drop_index(op.f('ix_process_instance_process_model_display_name'), table_name='process_instance')
op.drop_index(op.f('ix_process_instance_process_initiator_id'), table_name='process_instance')
op.drop_index(op.f('ix_process_instance_end_in_seconds'), table_name='process_instance')
op.drop_index(op.f('ix_process_instance_bpmn_process_id'), table_name='process_instance')
op.drop_index(op.f('ix_process_instance_bpmn_process_definition_id'), table_name='process_instance')
with op.batch_alter_table('process_instance', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_process_instance_status'))
batch_op.drop_index(batch_op.f('ix_process_instance_start_in_seconds'))
batch_op.drop_index(batch_op.f('ix_process_instance_process_model_identifier'))
batch_op.drop_index(batch_op.f('ix_process_instance_process_model_display_name'))
batch_op.drop_index(batch_op.f('ix_process_instance_process_initiator_id'))
batch_op.drop_index(batch_op.f('ix_process_instance_end_in_seconds'))
batch_op.drop_index(batch_op.f('ix_process_instance_bpmn_process_id'))
batch_op.drop_index(batch_op.f('ix_process_instance_bpmn_process_definition_id'))
op.drop_table('process_instance')
op.drop_index(op.f('ix_permission_assignment_principal_id'), table_name='permission_assignment')
op.drop_index(op.f('ix_permission_assignment_permission_target_id'), table_name='permission_assignment')
with op.batch_alter_table('permission_assignment', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_permission_assignment_principal_id'))
batch_op.drop_index(batch_op.f('ix_permission_assignment_permission_target_id'))
op.drop_table('permission_assignment')
op.drop_index(op.f('ix_user_group_assignment_waiting_group_id'), table_name='user_group_assignment_waiting')
with op.batch_alter_table('user_group_assignment_waiting', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_user_group_assignment_waiting_group_id'))
op.drop_table('user_group_assignment_waiting')
op.drop_index(op.f('ix_user_group_assignment_user_id'), table_name='user_group_assignment')
op.drop_index(op.f('ix_user_group_assignment_group_id'), table_name='user_group_assignment')
with op.batch_alter_table('user_group_assignment', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_user_group_assignment_user_id'))
batch_op.drop_index(batch_op.f('ix_user_group_assignment_group_id'))
op.drop_table('user_group_assignment')
op.drop_index(op.f('ix_task_definition_typename'), table_name='task_definition')
op.drop_index(op.f('ix_task_definition_bpmn_process_definition_id'), table_name='task_definition')
op.drop_index(op.f('ix_task_definition_bpmn_name'), table_name='task_definition')
op.drop_index(op.f('ix_task_definition_bpmn_identifier'), table_name='task_definition')
with op.batch_alter_table('task_definition', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_task_definition_typename'))
batch_op.drop_index(batch_op.f('ix_task_definition_bpmn_process_definition_id'))
batch_op.drop_index(batch_op.f('ix_task_definition_bpmn_name'))
batch_op.drop_index(batch_op.f('ix_task_definition_bpmn_identifier'))
op.drop_table('task_definition')
op.drop_index(op.f('ix_secret_user_id'), table_name='secret')
with op.batch_alter_table('secret', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_secret_user_id'))
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')
with op.batch_alter_table('process_instance_report', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_process_instance_report_identifier'))
batch_op.drop_index(batch_op.f('ix_process_instance_report_created_by_id'))
op.drop_table('process_instance_report')
op.drop_table('principal')
op.drop_index(op.f('ix_bpmn_process_definition_relationship_bpmn_process_definition_child_id'), table_name='bpmn_process_definition_relationship')
op.drop_index(op.f('ix_bpmn_process_definition_relationship_bpmn_process_definition_parent_id'), table_name='bpmn_process_definition_relationship')
with op.batch_alter_table('bpmn_process_definition_relationship', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_bpmn_process_definition_relationship_bpmn_process_definition_child_id'))
batch_op.drop_index(batch_op.f('ix_bpmn_process_definition_relationship_bpmn_process_definition_parent_id'))
op.drop_table('bpmn_process_definition_relationship')
op.drop_index(op.f('ix_bpmn_process_top_level_process_id'), table_name='bpmn_process')
op.drop_index(op.f('ix_bpmn_process_json_data_hash'), table_name='bpmn_process')
op.drop_index(op.f('ix_bpmn_process_direct_parent_process_id'), table_name='bpmn_process')
op.drop_index(op.f('ix_bpmn_process_bpmn_process_definition_id'), table_name='bpmn_process')
with op.batch_alter_table('bpmn_process', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_bpmn_process_top_level_process_id'))
batch_op.drop_index(batch_op.f('ix_bpmn_process_json_data_hash'))
batch_op.drop_index(batch_op.f('ix_bpmn_process_direct_parent_process_id'))
batch_op.drop_index(batch_op.f('ix_bpmn_process_bpmn_process_definition_id'))
op.drop_table('bpmn_process')
op.drop_index(op.f('ix_user_service_id'), table_name='user')
op.drop_index(op.f('ix_user_service'), table_name='user')
op.drop_index(op.f('ix_user_email'), table_name='user')
with op.batch_alter_table('user', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_user_service_id'))
batch_op.drop_index(batch_op.f('ix_user_service'))
batch_op.drop_index(batch_op.f('ix_user_email'))
op.drop_table('user')
op.drop_index(op.f('ix_spec_reference_cache_type'), table_name='spec_reference_cache')
op.drop_index(op.f('ix_spec_reference_cache_process_model_id'), 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_display_name'), table_name='spec_reference_cache')
with op.batch_alter_table('spec_reference_cache', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_spec_reference_cache_type'))
batch_op.drop_index(batch_op.f('ix_spec_reference_cache_process_model_id'))
batch_op.drop_index(batch_op.f('ix_spec_reference_cache_identifier'))
batch_op.drop_index(batch_op.f('ix_spec_reference_cache_display_name'))
op.drop_table('spec_reference_cache')
op.drop_table('permission_target')
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_message_name'), table_name='message_triggerable_process_model')
with op.batch_alter_table('message_triggerable_process_model', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_message_triggerable_process_model_process_model_identifier'))
batch_op.drop_index(batch_op.f('ix_message_triggerable_process_model_message_name'))
op.drop_table('message_triggerable_process_model')
op.drop_table('json_data')
op.drop_index(op.f('ix_group_name'), table_name='group')
op.drop_index(op.f('ix_group_identifier'), table_name='group')
with op.batch_alter_table('group', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_group_name'))
batch_op.drop_index(batch_op.f('ix_group_identifier'))
op.drop_table('group')
op.drop_index(op.f('ix_correlation_property_cache_name'), table_name='correlation_property_cache')
op.drop_index(op.f('ix_correlation_property_cache_message_name'), table_name='correlation_property_cache')
with op.batch_alter_table('correlation_property_cache', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_correlation_property_cache_name'))
batch_op.drop_index(batch_op.f('ix_correlation_property_cache_message_name'))
op.drop_table('correlation_property_cache')
op.drop_index(op.f('ix_bpmn_process_definition_bpmn_name'), table_name='bpmn_process_definition')
op.drop_index(op.f('ix_bpmn_process_definition_bpmn_identifier'), table_name='bpmn_process_definition')
with op.batch_alter_table('bpmn_process_definition', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_bpmn_process_definition_bpmn_name'))
batch_op.drop_index(batch_op.f('ix_bpmn_process_definition_bpmn_identifier'))
op.drop_table('bpmn_process_definition')
# ### end Alembic commands ###

View File

@ -1,36 +0,0 @@
"""empty message
Revision ID: 5d8e49f9c560
Revises: 0b5dd14bfbac
Create Date: 2023-04-17 11:28:39.714193
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql
# revision identifiers, used by Alembic.
revision = '5d8e49f9c560'
down_revision = '0b5dd14bfbac'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('bpmn_process_definition', schema=None) as batch_op:
batch_op.alter_column('hash', existing_type=sa.String(length=255), new_column_name='single_process_hash')
batch_op.add_column(sa.Column('full_process_model_hash', sa.String(length=255), nullable=True))
batch_op.create_unique_constraint(None, ['full_process_model_hash'])
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('bpmn_process_definition', schema=None) as batch_op:
batch_op.drop_constraint('full_process_model_hash', type_='unique')
batch_op.drop_column('full_process_model_hash')
batch_op.alter_column('single_process_hash', existing_type=sa.String(length=255), new_column_name='hash')
# ### end Alembic commands ###

View File

@ -1898,6 +1898,14 @@ python-versions = ">=3.5"
lint = ["docutils-stubs", "flake8", "mypy"]
test = ["pytest"]
[[package]]
name = "spiff-element-units"
version = "0.1.0"
description = ""
category = "main"
optional = false
python-versions = ">=3.7"
[[package]]
name = "SpiffWorkflow"
version = "1.2.1"
@ -1916,7 +1924,7 @@ lxml = "*"
type = "git"
url = "https://github.com/sartography/SpiffWorkflow"
reference = "main"
resolved_reference = "162a1c5f56cf12fc589a1e368704c0819bfcc0cd"
resolved_reference = "7211e67ee0dfecbabaeb7cec8f0e0373bd7cdc10"
[[package]]
name = "sqlalchemy"
@ -2299,7 +2307,7 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more
[metadata]
lock-version = "1.1"
python-versions = ">=3.9,<3.12"
content-hash = "554d5da592e80f26a3f6bb2322a397a6512c09b1a3891584d4d38a21fedc0445"
content-hash = "994c36ab39238500b4fd05bc1ccdd2d729dd5f66749ab77b1028371147bdf753"
[metadata.files]
alabaster = [
@ -3661,6 +3669,19 @@ sphinxcontrib-serializinghtml = [
{file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"},
{file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"},
]
spiff-element-units = [
{file = "spiff_element_units-0.1.0-cp39-abi3-macosx_10_7_x86_64.whl", hash = "sha256:fc34e1012a922037cf5d04c154a37119bc1ba83cc536d79fde601da42703d5f7"},
{file = "spiff_element_units-0.1.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:664197124c2a81c780d83a60750ad4411dda22a31e0db7615007a3905393fa4b"},
{file = "spiff_element_units-0.1.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34409c2a1f24dfca99afafd3b1caa9e2fba66c81864954f7f9ebf8030bc632c8"},
{file = "spiff_element_units-0.1.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dcc1307447f30f597d31224d855c979f5c8cca5906792cbf7d9418ee2bcac54b"},
{file = "spiff_element_units-0.1.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f40788203884db9d15e1e2c6b5d7b019b38606750f3df75f66d6c14fe492b5b4"},
{file = "spiff_element_units-0.1.0-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:78afa90e8ee48dfe84b8accb3399212297cb2b05f7e5288ae57614f68a2cffd8"},
{file = "spiff_element_units-0.1.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aab909b3d8f896eb2f2ed64273a7e013564a708e875883dafe76cdb34e459cb3"},
{file = "spiff_element_units-0.1.0-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a264c4ad717d83fe1d7b31d4c9a26143a3a9eff9cfdb09216f4fc1ef029e178"},
{file = "spiff_element_units-0.1.0-cp39-abi3-win32.whl", hash = "sha256:9b2e25b8b18ae006c39cd0b8bac11b08d60bf0e53b60a9cca9a3cec3750f0176"},
{file = "spiff_element_units-0.1.0-cp39-abi3-win_amd64.whl", hash = "sha256:825594fa95496db3a9a7826c9cbf95b90b6a5676838bbd9fe23c5ff32a2d2920"},
{file = "spiff_element_units-0.1.0.tar.gz", hash = "sha256:807e207562220f350cd0f0b6a46484c7b976c4effe4b194701179add7abe871a"},
]
SpiffWorkflow = []
sqlalchemy = [
{file = "SQLAlchemy-2.0.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:734805708632e3965c2c40081f9a59263c29ffa27cba9b02d4d92dfd57ba869f"},

View File

@ -86,6 +86,7 @@ prometheus-flask-exporter = "^0.22.3"
safety = "^2.3.5"
sqlalchemy = "^2.0.7"
marshmallow-sqlalchemy = "^0.29.0"
spiff-element-units = "^0.1.0"
[tool.poetry.dev-dependencies]
pytest = "^7.1.2"

View File

@ -1980,10 +1980,34 @@ paths:
description: Show the detailed view, which includes all log entries
schema:
type: boolean
- name: bpmn_name
in: query
required: false
description: The bpmn name of the task to search for.
schema:
type: string
- name: bpmn_identifier
in: query
required: false
description: The bpmn identifier of the task to search for.
schema:
type: string
- name: task_type
in: query
required: false
description: The task type of the task to search for.
schema:
type: string
- name: event_type
in: query
required: false
description: The type of the event to search for.
schema:
type: string
get:
tags:
- Process Instances
operationId: spiffworkflow_backend.routes.process_instances_controller.process_instance_log_list
- Process Instance Events
operationId: spiffworkflow_backend.routes.process_instance_events_controller.log_list
summary: returns a list of logs associated with the process instance
responses:
"200":
@ -1993,6 +2017,20 @@ paths:
schema:
$ref: "#/components/schemas/ProcessInstanceLog"
/logs/types:
get:
tags:
- Process Instance Events
operationId: spiffworkflow_backend.routes.process_instance_events_controller.types
summary: returns a list of task types and event typs. useful for building log queries.
responses:
"200":
description: list of types
content:
application/json:
schema:
$ref: "#/components/schemas/ProcessInstanceLog"
/secrets:
parameters:
- name: page

View File

@ -145,3 +145,11 @@ SPIFFWORKFLOW_BACKEND_ENGINE_STEP_DEFAULT_STRATEGY_WEB = environ.get(
# this is only used in CI. use SPIFFWORKFLOW_BACKEND_DATABASE_URI instead for real configuration
SPIFFWORKFLOW_BACKEND_DATABASE_PASSWORD = environ.get("SPIFFWORKFLOW_BACKEND_DATABASE_PASSWORD", default=None)
SPIFFWORKFLOW_BACKEND_FEATURE_ELEMENT_UNITS_ENABLED = (
environ.get("SPIFFWORKFLOW_BACKEND_FEATURE_ELEMENT_UNITS_ENABLED", default="false") == "true"
)
SPIFFWORKFLOW_BACKEND_ELEMENT_UNITS_CACHE_DIR = environ.get(
"SPIFFWORKFLOW_BACKEND_ELEMENT_UNITS_CACHE_DIR", default=None
)

View File

@ -1,11 +1,7 @@
"""Spiff_enum."""
import enum
class SpiffEnum(enum.Enum):
"""SpiffEnum."""
@classmethod
def list(cls) -> list[str]:
"""List."""
return [el.value for el in cls]

View File

@ -2,6 +2,8 @@ from __future__ import annotations
from dataclasses import dataclass
from sqlalchemy import UniqueConstraint
from spiffworkflow_backend.models.db import db
from spiffworkflow_backend.models.db import SpiffworkflowBaseDBModel
@ -15,17 +17,25 @@ from spiffworkflow_backend.models.db import SpiffworkflowBaseDBModel
@dataclass
class BpmnProcessDefinitionModel(SpiffworkflowBaseDBModel):
__tablename__ = "bpmn_process_definition"
__table_args__ = (
UniqueConstraint(
"full_process_model_hash",
"single_process_hash",
name="process_hash_unique",
),
)
id: int = db.Column(db.Integer, primary_key=True)
# this is a sha256 hash of spec and serializer_version
# note that a call activity is its own row in this table, with its own hash,
# and therefore it only gets stored once per version, and can be reused
# by multiple calling processes.
single_process_hash: str = db.Column(db.String(255), nullable=False, unique=True)
single_process_hash: str = db.Column(db.String(255), nullable=False)
# only the top level parent will have this set
# it includes all subprocesses and call activities
full_process_model_hash: str | None = db.Column(db.String(255), nullable=True, unique=True, default=None)
full_process_model_hash: str | None = db.Column(db.String(255), nullable=True, unique=True)
bpmn_identifier: str = db.Column(db.String(255), nullable=False, index=True)
bpmn_name: str = db.Column(db.String(255), nullable=True, index=True)

View File

@ -0,0 +1,93 @@
from typing import Optional
import flask.wrappers
from flask import jsonify
from flask import make_response
from sqlalchemy import and_
from spiffworkflow_backend.models.bpmn_process_definition import BpmnProcessDefinitionModel
from spiffworkflow_backend.models.db import db
from spiffworkflow_backend.models.process_instance_event import ProcessInstanceEventModel
from spiffworkflow_backend.models.process_instance_event import ProcessInstanceEventType
from spiffworkflow_backend.models.task import TaskModel # noqa: F401
from spiffworkflow_backend.models.task_definition import TaskDefinitionModel
from spiffworkflow_backend.models.user import UserModel
from spiffworkflow_backend.routes.process_api_blueprint import (
_find_process_instance_by_id_or_raise,
)
def log_list(
modified_process_model_identifier: str,
process_instance_id: int,
page: int = 1,
per_page: int = 100,
detailed: bool = False,
bpmn_name: Optional[str] = None,
bpmn_identifier: Optional[str] = None,
task_type: Optional[str] = None,
event_type: Optional[str] = None,
) -> flask.wrappers.Response:
# to make sure the process instance exists
process_instance = _find_process_instance_by_id_or_raise(process_instance_id)
log_query = (
ProcessInstanceEventModel.query.filter_by(process_instance_id=process_instance.id)
.outerjoin(TaskModel, TaskModel.guid == ProcessInstanceEventModel.task_guid)
.outerjoin(TaskDefinitionModel, TaskDefinitionModel.id == TaskModel.task_definition_id)
.outerjoin(
BpmnProcessDefinitionModel,
BpmnProcessDefinitionModel.id == TaskDefinitionModel.bpmn_process_definition_id,
)
)
if not detailed:
log_query = log_query.filter(
and_(
TaskModel.state.in_(["COMPLETED"]), # type: ignore
TaskDefinitionModel.typename.in_(["IntermediateThrowEvent"]), # type: ignore
)
)
if bpmn_name is not None:
log_query = log_query.filter(TaskDefinitionModel.bpmn_name == bpmn_name)
if bpmn_identifier is not None:
log_query = log_query.filter(TaskDefinitionModel.bpmn_identifier == bpmn_identifier)
if task_type is not None:
log_query = log_query.filter(TaskDefinitionModel.typename == task_type)
if event_type is not None:
log_query = log_query.filter(ProcessInstanceEventModel.event_type == event_type)
logs = (
log_query.order_by(
ProcessInstanceEventModel.timestamp.desc(), ProcessInstanceEventModel.id.desc() # type: ignore
)
.outerjoin(UserModel, UserModel.id == ProcessInstanceEventModel.user_id)
.add_columns(
TaskModel.guid.label("spiff_task_guid"), # type: ignore
UserModel.username,
BpmnProcessDefinitionModel.bpmn_identifier.label("bpmn_process_definition_identifier"), # type: ignore
BpmnProcessDefinitionModel.bpmn_name.label("bpmn_process_definition_name"), # type: ignore
TaskDefinitionModel.bpmn_identifier.label("task_definition_identifier"), # type: ignore
TaskDefinitionModel.bpmn_name.label("task_definition_name"), # type: ignore
TaskDefinitionModel.typename.label("bpmn_task_type"), # type: ignore
)
.paginate(page=page, per_page=per_page, error_out=False)
)
response_json = {
"results": logs.items,
"pagination": {
"count": len(logs.items),
"total": logs.total,
"pages": logs.pages,
},
}
return make_response(jsonify(response_json), 200)
def types() -> flask.wrappers.Response:
query = db.session.query(TaskDefinitionModel.typename).distinct() # type: ignore
task_types = [t.typename for t in query]
event_types = ProcessInstanceEventType.list()
return make_response(jsonify({"task_types": task_types, "event_types": event_types}), 200)

View File

@ -30,9 +30,6 @@ from spiffworkflow_backend.models.process_instance import (
)
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
from spiffworkflow_backend.models.process_instance import ProcessInstanceModelSchema
from spiffworkflow_backend.models.process_instance_event import (
ProcessInstanceEventModel,
)
from spiffworkflow_backend.models.process_instance_metadata import (
ProcessInstanceMetadataModel,
)
@ -47,7 +44,6 @@ from spiffworkflow_backend.models.spec_reference import SpecReferenceCache
from spiffworkflow_backend.models.spec_reference import SpecReferenceNotFoundError
from spiffworkflow_backend.models.task import TaskModel
from spiffworkflow_backend.models.task_definition import TaskDefinitionModel
from spiffworkflow_backend.models.user import UserModel
from spiffworkflow_backend.routes.process_api_blueprint import (
_find_process_instance_by_id_or_raise,
)
@ -224,63 +220,6 @@ def process_instance_resume(
return Response(json.dumps({"ok": True}), status=200, mimetype="application/json")
def process_instance_log_list(
modified_process_model_identifier: str,
process_instance_id: int,
page: int = 1,
per_page: int = 100,
detailed: bool = False,
) -> flask.wrappers.Response:
"""Process_instance_log_list."""
# to make sure the process instance exists
process_instance = _find_process_instance_by_id_or_raise(process_instance_id)
log_query = (
ProcessInstanceEventModel.query.filter_by(process_instance_id=process_instance.id)
.outerjoin(TaskModel, TaskModel.guid == ProcessInstanceEventModel.task_guid)
.outerjoin(TaskDefinitionModel, TaskDefinitionModel.id == TaskModel.task_definition_id)
.outerjoin(
BpmnProcessDefinitionModel,
BpmnProcessDefinitionModel.id == TaskDefinitionModel.bpmn_process_definition_id,
)
)
if not detailed:
log_query = log_query.filter(
and_(
TaskModel.state.in_(["COMPLETED"]), # type: ignore
TaskDefinitionModel.typename.in_(["IntermediateThrowEvent"]), # type: ignore
)
)
logs = (
log_query.order_by(
ProcessInstanceEventModel.timestamp.desc(), ProcessInstanceEventModel.id.desc() # type: ignore
)
.outerjoin(UserModel, UserModel.id == ProcessInstanceEventModel.user_id)
.add_columns(
TaskModel.guid.label("spiff_task_guid"), # type: ignore
UserModel.username,
BpmnProcessDefinitionModel.bpmn_identifier.label("bpmn_process_definition_identifier"), # type: ignore
BpmnProcessDefinitionModel.bpmn_name.label("bpmn_process_definition_name"), # type: ignore
TaskDefinitionModel.bpmn_identifier.label("task_definition_identifier"), # type: ignore
TaskDefinitionModel.bpmn_name.label("task_definition_name"), # type: ignore
TaskDefinitionModel.typename.label("bpmn_task_type"), # type: ignore
)
.paginate(page=page, per_page=per_page, error_out=False)
)
response_json = {
"results": logs.items,
"pagination": {
"count": len(logs.items),
"total": logs.total,
"pages": logs.pages,
},
}
return make_response(jsonify(response_json), 200)
def process_instance_list_for_me(
process_model_identifier: Optional[str] = None,
page: int = 1,
@ -691,19 +630,29 @@ def process_instance_task_list(
task_models = task_model_query.all()
if most_recent_tasks_only:
most_recent_tasks = {}
most_recent_subprocesses = set()
# if you have a loop and there is a subprocess, and you are going around for the second time,
# ignore the tasks in the "first loop" subprocess
relevant_subprocess_guids = {bpmn_process_guid, None}
bpmn_process_cache: dict[str, list[str]] = {}
for task_model in task_models:
bpmn_process_guid = task_model.bpmn_process_guid or "TOP"
row_key = f"{bpmn_process_guid}:::{task_model.bpmn_identifier}"
if task_model.bpmn_process_guid not in bpmn_process_cache:
bpmn_process = BpmnProcessModel.query.filter_by(guid=task_model.bpmn_process_guid).first()
full_bpmn_process_path = TaskService.full_bpmn_process_path(bpmn_process)
bpmn_process_cache[task_model.bpmn_process_guid] = full_bpmn_process_path
else:
full_bpmn_process_path = bpmn_process_cache[task_model.bpmn_process_guid]
row_key = f"{':::'.join(full_bpmn_process_path)}:::{task_model.bpmn_identifier}"
if row_key not in most_recent_tasks:
most_recent_tasks[row_key] = task_model
if task_model.typename in ["SubWorkflowTask", "CallActivity"]:
most_recent_subprocesses.add(task_model.guid)
relevant_subprocess_guids.add(task_model.guid)
task_models = [
task_model
for task_model in most_recent_tasks.values()
if task_model.bpmn_process_guid in most_recent_subprocesses or task_model.bpmn_process_guid is None
if task_model.bpmn_process_guid in relevant_subprocess_guids
]
if to_task_model is not None:

View File

@ -567,6 +567,7 @@ class AuthorizationService:
permissions_to_assign.append(PermissionToAssign(permission="read", target_uri="/processes"))
permissions_to_assign.append(PermissionToAssign(permission="read", target_uri="/service-tasks"))
permissions_to_assign.append(PermissionToAssign(permission="read", target_uri="/user-groups/for-current-user"))
permissions_to_assign.append(PermissionToAssign(permission="read", target_uri="/logs/types"))
permissions_to_assign.append(PermissionToAssign(permission="create", target_uri="/users/exists/by-username"))
permissions_to_assign.append(
PermissionToAssign(permission="read", target_uri="/process-instances/find-by-id/*")

View File

@ -0,0 +1,57 @@
import json
from typing import Any
from typing import Dict
from typing import Optional
from flask import current_app
BpmnSpecDict = Dict[str, Any]
class ElementUnitsService:
"""Feature gated glue between the backend and spiff-element-units."""
@classmethod
def _cache_dir(cls) -> Optional[str]:
return current_app.config["SPIFFWORKFLOW_BACKEND_ELEMENT_UNITS_CACHE_DIR"] # type: ignore
@classmethod
def _enabled(cls) -> bool:
enabled = current_app.config["SPIFFWORKFLOW_BACKEND_FEATURE_ELEMENT_UNITS_ENABLED"]
return enabled and cls._cache_dir() is not None
@classmethod
def cache_element_units_for_workflow(cls, cache_key: str, bpmn_spec_dict: BpmnSpecDict) -> None:
if not cls._enabled():
return None
try:
# for now we are importing inside each of these functions, not sure the best
# way to do this in an overall feature flagged strategy but this gets things
# moving
import spiff_element_units # type: ignore
bpmn_spec_json = json.dumps(bpmn_spec_dict)
spiff_element_units.cache_element_units_for_workflow(cls._cache_dir(), cache_key, bpmn_spec_json)
except Exception as e:
current_app.logger.exception(e)
return None
@classmethod
def workflow_from_cached_element_unit(cls, cache_key: str, element_id: str) -> Optional[BpmnSpecDict]:
if not cls._enabled():
return None
try:
# for now we are importing inside each of these functions, not sure the best
# way to do this in an overall feature flagged strategy but this gets things
# moving
import spiff_element_units
bpmn_spec_json = spiff_element_units.workflow_from_cached_element_unit(
cls._cache_dir(), cache_key, element_id
)
return json.loads(bpmn_spec_json) # type: ignore
except Exception as e:
current_app.logger.exception(e)
return None

View File

@ -129,7 +129,7 @@ def setup_logger(app: Flask) -> None:
spiff_logger_filehandler.setFormatter(log_formatter)
# these loggers have been deemed too verbose to be useful
garbage_loggers_to_exclude = ["connexion"]
garbage_loggers_to_exclude = ["connexion", "flask_cors.extension"]
# make all loggers act the same
for name in logging.root.manager.loggerDict:

View File

@ -91,6 +91,9 @@ from spiffworkflow_backend.models.task_definition import TaskDefinitionModel
from spiffworkflow_backend.models.user import UserModel
from spiffworkflow_backend.scripts.script import Script
from spiffworkflow_backend.services.custom_parser import MyCustomParser
from spiffworkflow_backend.services.element_units_service import (
ElementUnitsService,
)
from spiffworkflow_backend.services.file_system_service import FileSystemService
from spiffworkflow_backend.services.process_instance_queue_service import ProcessInstanceQueueService
from spiffworkflow_backend.services.process_model_service import ProcessModelService
@ -673,6 +676,25 @@ class ProcessInstanceProcessor:
bpmn_definition_to_task_definitions_mappings,
)
#
# see if we have any cached element units and if so step on the spec and subprocess_specs.
# in the early stages of development this will return the full workflow when the feature
# flag is set to on. as time goes we will need to think about how this plays in with the
# bpmn definition tables more.
#
element_unit_process_dict = None
full_process_model_hash = bpmn_process_definition.full_process_model_hash
if full_process_model_hash is not None:
element_unit_process_dict = ElementUnitsService.workflow_from_cached_element_unit(
full_process_model_hash,
bpmn_process_definition.bpmn_identifier,
)
if element_unit_process_dict is not None:
spiff_bpmn_process_dict["spec"] = element_unit_process_dict["spec"]
spiff_bpmn_process_dict["subprocess_specs"] = element_unit_process_dict["subprocess_specs"]
bpmn_process = process_instance_model.bpmn_process
if bpmn_process is not None:
single_bpmn_process_dict = cls._get_bpmn_process_dict(bpmn_process, get_tasks=True)
@ -1073,6 +1095,26 @@ class ProcessInstanceProcessor:
)
self.process_instance_model.bpmn_process_definition = bpmn_process_definition_parent
#
# builds and caches the element units for the parent bpmn process defintion. these
# element units can then be queried using the same hash for later execution.
#
# TODO: this seems to be run each time a process instance is started, so element
# units will only be queried after a save/resume point. the hash used as the key
# can be anything, so possibly some hash of all files required to form the process
# definition and their hashes could be used? Not sure how that plays in with the
# bpmn_process_defintion hash though.
#
# TODO: first time through for an instance the bpmn_spec_dict seems to get mutated,
# so for now we don't seed the cache until the second instance. not immediately a
# problem and can be part of the larger discussion mentioned in the TODO above.
full_process_model_hash = bpmn_process_definition_parent.full_process_model_hash
if full_process_model_hash is not None and "task_specs" in bpmn_spec_dict["spec"]:
ElementUnitsService.cache_element_units_for_workflow(full_process_model_hash, bpmn_spec_dict)
def save(self) -> None:
"""Saves the current state of this processor to the database."""
self.process_instance_model.spiff_serializer_version = self.SERIALIZER_VERSION

View File

@ -617,6 +617,10 @@ class ProcessInstanceReportService:
HumanTaskUserModel,
and_(HumanTaskUserModel.human_task_id == HumanTaskModel.id, HumanTaskUserModel.user_id == user.id),
)
if report_filter.has_active_status:
process_instance_query = process_instance_query.filter(
HumanTaskModel.completed.is_(False) # type: ignore
)
if report_filter.with_tasks_assigned_to_my_group is True:
group_model_join_conditions = [GroupModel.id == HumanTaskModel.lane_assignment_id]

View File

@ -511,6 +511,17 @@ class TaskService:
return (bpmn_processes + b, [parent_task_model] + t)
return (bpmn_processes, task_models)
@classmethod
def full_bpmn_process_path(cls, bpmn_process: BpmnProcessModel) -> list[str]:
"""Returns a list of bpmn process identifiers pointing the given bpmn_process."""
bpmn_process_identifiers: list[str] = [bpmn_process.bpmn_process_definition.bpmn_identifier]
if bpmn_process.direct_parent_process_id is not None:
parent_bpmn_process = BpmnProcessModel.query.filter_by(id=bpmn_process.direct_parent_process_id).first()
if parent_bpmn_process is not None:
# always prepend new identifiers since they come first in the path
bpmn_process_identifiers = cls.full_bpmn_process_path(parent_bpmn_process) + bpmn_process_identifiers
return bpmn_process_identifiers
@classmethod
def reset_task_model_dict(
cls,

View File

@ -0,0 +1,116 @@
{
"serializer_version": "spiff-element-units-integration",
"spec": {
"correlation_keys": {},
"data_objects": {},
"description": "No Tasks",
"file": "tests/data/process-models/test-cases/no-tasks/no-tasks.bpmn",
"io_specification": null,
"name": "no_tasks",
"task_specs": {
"End": {
"description": "",
"id": "no_tasks_3",
"inputs": [
"no_tasks.EndJoin"
],
"internal": false,
"lookahead": 2,
"manual": false,
"name": "End",
"outputs": [],
"typename": "Simple"
},
"Event_0qq9il3": {
"data_input_associations": [],
"data_output_associations": [],
"description": null,
"documentation": null,
"event_definition": {
"external": false,
"internal": false,
"typename": "NoneEventDefinition"
},
"extensions": {},
"id": "no_tasks_5",
"inputs": [
"StartEvent_1"
],
"internal": false,
"io_specification": null,
"lane": null,
"lookahead": 2,
"manual": false,
"name": "Event_0qq9il3",
"outputs": [
"no_tasks.EndJoin"
],
"position": {
"x": 272.0,
"y": 159.0
},
"typename": "EndEvent"
},
"Start": {
"description": "",
"id": "no_tasks_1",
"inputs": [],
"internal": false,
"lookahead": 2,
"manual": false,
"name": "Start",
"outputs": [
"StartEvent_1"
],
"typename": "StartTask"
},
"StartEvent_1": {
"data_input_associations": [],
"data_output_associations": [],
"description": null,
"documentation": null,
"event_definition": {
"external": false,
"internal": false,
"typename": "NoneEventDefinition"
},
"extensions": {},
"id": "no_tasks_4",
"inputs": [
"Start"
],
"internal": false,
"io_specification": null,
"lane": null,
"lookahead": 2,
"manual": false,
"name": "StartEvent_1",
"outputs": [
"Event_0qq9il3"
],
"position": {
"x": 179.0,
"y": 159.0
},
"typename": "StartEvent"
},
"no_tasks.EndJoin": {
"description": "",
"id": "no_tasks_2",
"inputs": [
"Event_0qq9il3"
],
"internal": false,
"lookahead": 2,
"manual": false,
"name": "no_tasks.EndJoin",
"outputs": [
"End"
],
"typename": "_EndJoin"
}
},
"typename": "BpmnProcessSpec"
},
"subprocess_specs": {}
}

View File

@ -275,6 +275,7 @@ class TestAuthorizationService(BaseTest):
) -> None:
"""Test_explode_permissions_basic."""
expected_permissions = [
("/logs/types", "read"),
("/process-instances/find-by-id/*", "read"),
("/process-instances/for-me", "read"),
("/process-instances/reports/*", "create"),

View File

@ -0,0 +1,133 @@
import json
import os
import tempfile
from typing import Generator
import pytest
from flask.app import Flask
from tests.spiffworkflow_backend.helpers.base_test import BaseTest
from spiffworkflow_backend.services.element_units_service import BpmnSpecDict
from spiffworkflow_backend.services.element_units_service import ElementUnitsService
#
# we don't want to fully flex every aspect of the spiff-element-units
# library here, mainly just checking that our interaction with it is
# as expected.
#
@pytest.fixture()
def app_no_cache_dir(app: Flask) -> Generator[Flask, None, None]:
app.config["SPIFFWORKFLOW_BACKEND_ELEMENT_UNITS_CACHE_DIR"] = None
yield app
@pytest.fixture()
def app_some_cache_dir(app: Flask) -> Generator[Flask, None, None]:
app.config["SPIFFWORKFLOW_BACKEND_ELEMENT_UNITS_CACHE_DIR"] = "some_cache_dir"
yield app
@pytest.fixture()
def app_disabled(app: Flask) -> Generator[Flask, None, None]:
app.config["SPIFFWORKFLOW_BACKEND_FEATURE_ELEMENT_UNITS_ENABLED"] = False
yield app
@pytest.fixture()
def app_enabled(app_some_cache_dir: Flask) -> Generator[Flask, None, None]:
app_some_cache_dir.config["SPIFFWORKFLOW_BACKEND_FEATURE_ELEMENT_UNITS_ENABLED"] = True
yield app_some_cache_dir
@pytest.fixture()
def app_enabled_tmp_cache_dir(app_enabled: Flask) -> Generator[Flask, None, None]:
with tempfile.TemporaryDirectory() as tmpdirname:
app_enabled.config["SPIFFWORKFLOW_BACKEND_ELEMENT_UNITS_CACHE_DIR"] = tmpdirname
yield app_enabled
@pytest.fixture()
def example_specs_dict(app: Flask) -> Generator[BpmnSpecDict, None, None]:
path = os.path.join(app.instance_path, "..", "..", "tests", "data", "specs-json", "no-tasks.json")
with open(path) as f:
yield json.loads(f.read())
class TestElementUnitsService(BaseTest):
"""Tests the ElementUnitsService."""
def test_cache_dir_env_is_respected(
self,
app_some_cache_dir: Flask,
) -> None:
assert ElementUnitsService._cache_dir() == "some_cache_dir"
def test_feature_disabled_if_env_is_false(
self,
app_disabled: Flask,
) -> None:
assert not ElementUnitsService._enabled()
def test_feature_enabled_if_env_is_true(
self,
app_enabled: Flask,
) -> None:
assert ElementUnitsService._enabled()
def test_is_disabled_when_no_cache_dir(
self,
app_no_cache_dir: Flask,
) -> None:
assert not ElementUnitsService._enabled()
def test_ok_to_cache_when_disabled(
self,
app_disabled: Flask,
) -> None:
result = ElementUnitsService.cache_element_units_for_workflow("", {})
assert result is None
def test_ok_to_read_workflow_from_cached_element_unit_when_disabled(
self,
app_disabled: Flask,
) -> None:
result = ElementUnitsService.workflow_from_cached_element_unit("", "")
assert result is None
def test_can_write_to_cache(
self,
app_enabled_tmp_cache_dir: Flask,
example_specs_dict: BpmnSpecDict,
) -> None:
result = ElementUnitsService.cache_element_units_for_workflow("testing", example_specs_dict)
assert result is None
def test_can_write_to_cache_multiple_times(
self,
app_enabled_tmp_cache_dir: Flask,
example_specs_dict: BpmnSpecDict,
) -> None:
result = ElementUnitsService.cache_element_units_for_workflow("testing", example_specs_dict)
assert result is None
result = ElementUnitsService.cache_element_units_for_workflow("testing", example_specs_dict)
assert result is None
result = ElementUnitsService.cache_element_units_for_workflow("testing", example_specs_dict)
assert result is None
def test_can_read_element_unit_for_process_from_cache(
self,
app_enabled_tmp_cache_dir: Flask,
example_specs_dict: BpmnSpecDict,
) -> None:
ElementUnitsService.cache_element_units_for_workflow("testing", example_specs_dict)
cached_specs_dict = ElementUnitsService.workflow_from_cached_element_unit("testing", "no_tasks")
assert cached_specs_dict == example_specs_dict
def test_reading_element_unit_for_uncached_process_returns_none(
self,
app_enabled_tmp_cache_dir: Flask,
) -> None:
cached_specs_dict = ElementUnitsService.workflow_from_cached_element_unit("testing", "no_tasks")
assert cached_specs_dict is None

View File

@ -82,9 +82,9 @@ const submitWithUser = (
//Consulting Fees Path - Without Files
describe('Consulting Fees Path - Without Files', () => {
Cypress._.times(5, () => {
Cypress._.times(1, () => {
//Budget owner approves the request
it('Budget owner approves', () => {
it.only('Budget owner approves', () => {
let username = Cypress.env('requestor_username');
let password = Cypress.env('requestor_password');
cy.log('=====username : ' + username);
@ -94,24 +94,25 @@ describe('Consulting Fees Path - Without Files', () => {
cy.visit('/');
cy.contains('Start New +').click();
cy.contains('Raise New Demand Request');
cy.contains('Request Goods/Services');
cy.runPrimaryBpmnFile(true);
cy.contains('Please select the type of request to start the process.');
// wait a second to ensure we can click the radio button
cy.wait(2000);
cy.get('input#root-procurement').click();
cy.wait(2000);
cy.get('button')
.contains(/^Submit$/)
.click();
/* cy.contains('Please select the type of request to start the process.');
// wait a second to ensure we can click the radio button
cy.wait(2000);
cy.get('input#root-procurement').click();
cy.wait(2000);
cy.get('button')
.contains(/^Submit$/)
.click();
*/
cy.contains(
'Submit a new demand request for the procurement of needed items',
'Request Goods/Services',
{ timeout: 60000 }
);
@ -121,45 +122,47 @@ describe('Consulting Fees Path - Without Files', () => {
const processInstanceId = currentUrl.match(/(?<=\/tasks\/)\d+/)[0];
cy.log('==###############===processInstanceId : ', processInstanceId);
let projectId = Cypress.env('project_id');
cy.wait(2000);
cy.get('#root_project').select(projectId);
cy.get('#root_category').select('consult_fees');
cy.get('#root_purpose').clear().type('Consulting ==== Management consulting includes a broad range of activities, and the many firms and their members often define these practices quite differently. One way to categorize the activities is in terms of the professionals area of expertise.');
cy.get('#root_criticality').select('High');
cy.get('#root_period').clear().type('2025-12-25');
cy.get('#root_period').clear().type('25-12-2025');
cy.get('body').click();
cy.get('#root_vendor').clear().type('Embassar');
cy.get('#root_payment_method').select('Bank Transfer');
cy.get('button')
/*cy.get('button')
.contains(/^Submit$/)
.click();
cy.contains('Task: Enter NDR Items', { timeout: 60000 });
cy.contains('Task: Enter NDR Items', { timeout: 60000 });*/
//item 0
cy.get('#root_0_sub_category').select('ambassadors');
cy.get('#root_0_item').clear().type('An ambassador is an official envoy, especially a high-ranking diplomat who represents a state.');
cy.get('#root_0_qty').clear().type('4');
cy.get('#root_0_currency_type').select('Crypto');
cy.get('#root_0_currency').select('ETH');
cy.get('#root_0_unit_price').type('1.15');
cy.get('#root_item_0_sub_category').select('ambassadors');
cy.get('#root_item_0_item_name').clear().type('An ambassador is an official envoy, especially a high-ranking diplomat who represents a state.');
cy.get('#root_item_0_qty').clear().type('4');
cy.get('#root_item_0_currency_type').select('Crypto');
cy.get('#root_item_0_currency').select('ETH');
cy.get('#root_item_0_unit_price').type('1.15');
cy.get('#root > div:nth-child(3) > p > button').click();
cy.get('#root_item > div:nth-child(3) > p > button').click();
//item 1
cy.get('#root_1_sub_category').select('consultants');
cy.get('#root_1_item').clear().type('A consultant (from Latin: consultare "to deliberate") is a professional');
cy.get('#root_1_qty').clear().type('1');
cy.get('#root_1_currency_type').select('Fiat');
cy.get('#root_1_currency').select('CAD');
cy.get('#root_1_unit_price').type('1355');
cy.get('#root_item_1_sub_category').select('consultants');
cy.get('#root_item_1_item_name').clear().type('A consultant (from Latin: consultare "to deliberate") is a professional');
cy.get('#root_item_1_qty').clear().type('1');
cy.get('#root_item_1_currency_type').select('Fiat');
cy.get('#root_item_1_currency').select('CAD');
cy.get('#root_item_1_unit_price').type('1355');
cy.get('#root > div:nth-child(3) > p > button').click();
cy.get('#root_item > div:nth-child(3) > p > button').click();
//item 2
cy.get('#root_2_sub_category').select('freelancers');
cy.get('#root_2_item').clear().type('Find & hire top freelancers, web developers & designers inexpensively. ');
cy.get('#root_2_qty').clear().type('6');
cy.get('#root_2_currency_type').select('Crypto');
cy.get('#root_2_currency').select('SNT');
cy.get('#root_2_unit_price').type('2300');
cy.get('#root_item_2_sub_category').select('freelancers');
cy.get('#root_item_2_item_name').clear().type('Find & hire top freelancers, web developers & designers inexpensively. ');
cy.get('#root_item_2_qty').clear().type('6');
cy.get('#root_item_2_currency_type').select('Crypto');
cy.get('#root_item_2_currency').select('SNT');
cy.get('#root_item_2_unit_price').type('2300');
cy.get('button')
@ -183,8 +186,9 @@ describe('Consulting Fees Path - Without Files', () => {
.click();
cy.contains('Tasks for my open instances', { timeout: 60000 });
cy.contains('Started by me', { timeout: 60000 });
cy.logout();
cy.wait(1000);
let budgetOwnerUsername = Cypress.env('budgetowner_username');
let budgetOwnerPassword = Cypress.env('budgetowner_password');
@ -195,7 +199,7 @@ describe('Consulting Fees Path - Without Files', () => {
budgetOwnerUsername,
budgetOwnerPassword,
processInstanceId,
'Task: Reminder: Request Additional Budget',
'Task: Reminder: Check Existing Budget',
"approve"
);
@ -213,24 +217,25 @@ describe('Consulting Fees Path - Without Files', () => {
cy.visit('/');
cy.contains('Start New +').click();
cy.contains('Raise New Demand Request');
cy.contains('Request Goods/Services');
cy.runPrimaryBpmnFile(true);
cy.contains('Please select the type of request to start the process.');
// wait a second to ensure we can click the radio button
cy.wait(2000);
cy.get('input#root-procurement').click();
cy.wait(2000);
cy.get('button')
.contains(/^Submit$/)
.click();
/* cy.contains('Please select the type of request to start the process.');
// wait a second to ensure we can click the radio button
cy.wait(2000);
cy.get('input#root-procurement').click();
cy.wait(2000);
cy.get('button')
.contains(/^Submit$/)
.click();
*/
cy.contains(
'Submit a new demand request for the procurement of needed items',
'Request Goods/Services',
{ timeout: 60000 }
);
@ -240,46 +245,48 @@ describe('Consulting Fees Path - Without Files', () => {
const processInstanceId = currentUrl.match(/(?<=\/tasks\/)\d+/)[0];
cy.log('==###############===processInstanceId : ', processInstanceId);
let projectId = Cypress.env('project_id');
cy.wait(2000);
cy.get('#root_project').select(projectId);
cy.get('#root_category').select('consult_fees');
cy.get('#root_purpose').clear().type('Consulting is defined as the practise of providing a third party with expertise on a matter in exchange for a fee. The service may involve either advisory or implementation services.');
cy.get('#root_criticality').select('Medium');
cy.get('#root_period').clear().type('2024-10-02');
cy.get('#root_period').clear().type('24-10-2032');
cy.get('body').click();
cy.get('#root_vendor').clear().type('Consultancy.uk');
cy.get('#root_payment_method').select('Crypto Transfer');
cy.get('button')
/*cy.get('button')
.contains(/^Submit$/)
.click();
cy.contains('Task: Enter NDR Items', { timeout: 60000 });
cy.contains('Task: Enter NDR Items', { timeout: 60000 });*/
//Item 0
cy.get('#root_0_sub_category').select('consultants');
cy.get('#root_0_item').clear().type('Software development consultants with Python background');
cy.get('#root_0_qty').clear().type('5');
cy.get('#root_0_currency_type').select('Crypto');
cy.get('#root_0_currency').select('DAI');
cy.get('#root_0_unit_price').type('1500');
cy.get('#root_item_0_sub_category').select('consultants');
cy.get('#root_item_0_item_name').clear().type('Software development consultants with Python background');
cy.get('#root_item_0_qty').clear().type('5');
cy.get('#root_item_0_currency_type').select('Crypto');
cy.get('#root_item_0_currency').select('DAI');
cy.get('#root_item_0_unit_price').type('1500');
cy.get('#root > div:nth-child(3) > p > button').click();
cy.get('#root_item > div:nth-child(3) > p > button').click();
//item 1
cy.get('#root_1_sub_category').select('consultants');
cy.get('#root_1_item').clear().type('A consultant (from Latin: consultare "to deliberate") is a professional');
cy.get('#root_1_qty').clear().type('1');
cy.get('#root_1_currency_type').select('Fiat');
cy.get('#root_1_currency').select('CAD');
cy.get('#root_1_unit_price').type('1355');
cy.get('#root_item_1_sub_category').select('consultants');
cy.get('#root_item_1_item_name').clear().type('A consultant (from Latin: consultare "to deliberate") is a professional');
cy.get('#root_item_1_qty').clear().type('1');
cy.get('#root_item_1_currency_type').select('Fiat');
cy.get('#root_item_1_currency').select('CAD');
cy.get('#root_item_1_unit_price').type('1355');
cy.get('#root > div:nth-child(3) > p > button').click();
cy.get('#root_item > div:nth-child(3) > p > button').click();
//item 2
cy.get('#root_2_sub_category').select('freelancers');
cy.get('#root_2_item').clear().type('Find & hire top freelancers, web developers & designers inexpensively. ');
cy.get('#root_2_qty').clear().type('6');
cy.get('#root_2_currency_type').select('Crypto');
cy.get('#root_2_currency').select('SNT');
cy.get('#root_2_unit_price').type('2300');
cy.get('#root_item_2_sub_category').select('freelancers');
cy.get('#root_item_2_item_name').clear().type('Find & hire top freelancers, web developers & designers inexpensively. ');
cy.get('#root_item_2_qty').clear().type('6');
cy.get('#root_item_2_currency_type').select('Crypto');
cy.get('#root_item_2_currency').select('SNT');
cy.get('#root_item_2_unit_price').type('2300');
cy.get('button')
@ -302,8 +309,9 @@ describe('Consulting Fees Path - Without Files', () => {
.contains(/^Submit$/)
.click();
cy.contains('Tasks for my open instances', { timeout: 60000 });
cy.contains('Started by me', { timeout: 60000 });
cy.logout();
cy.wait(1000);
let budgetOwnerUsername = Cypress.env('budgetowner_username');
let budgetOwnerPassword = Cypress.env('budgetowner_password');
@ -332,24 +340,25 @@ describe('Consulting Fees Path - Without Files', () => {
cy.visit('/');
cy.contains('Start New +').click();
cy.contains('Raise New Demand Request');
cy.contains('Request Goods/Services');
cy.runPrimaryBpmnFile(true);
cy.contains('Please select the type of request to start the process.');
// wait a second to ensure we can click the radio button
cy.wait(2000);
cy.get('input#root-procurement').click();
cy.wait(2000);
cy.get('button')
.contains(/^Submit$/)
.click();
/* cy.contains('Please select the type of request to start the process.');
// wait a second to ensure we can click the radio button
cy.wait(2000);
cy.get('input#root-procurement').click();
cy.wait(2000);
cy.get('button')
.contains(/^Submit$/)
.click();
*/
cy.contains(
'Submit a new demand request for the procurement of needed items',
'Request Goods/Services',
{ timeout: 60000 }
);
@ -359,46 +368,48 @@ describe('Consulting Fees Path - Without Files', () => {
const processInstanceId = currentUrl.match(/(?<=\/tasks\/)\d+/)[0];
cy.log('==###############===processInstanceId : ', processInstanceId);
let projectId = Cypress.env('project_id');
cy.wait(2000);
cy.get('#root_project').select(projectId);
cy.get('#root_category').select('consult_fees');
cy.get('#root_purpose').clear().type('Freelancing - Freelancing is doing specific work for clients without committing to full-time employment. Freelancers often take on multiple projects with different clients simultaneously. IRS considers freelancers to be self-employed individuals.');
cy.get('#root_criticality').select('Low');
cy.get('#root_period').clear().type('2025-04-15');
cy.get('#root_period').clear().type('05-04-2028');
cy.get('body').click();
cy.get('#root_vendor').clear().type('Upwork');
cy.get('#root_payment_method').select('Debit Card');
cy.get('button')
/*cy.get('button')
.contains(/^Submit$/)
.click();
cy.contains('Task: Enter NDR Items', { timeout: 60000 });
cy.contains('Task: Enter NDR Items', { timeout: 60000 });*/
//item 0
cy.get('#root_0_sub_category').select('freelancers');
cy.get('#root_0_item').clear().type('Freelancers to do the Python development and front end react app development');
cy.get('#root_0_qty').clear().type('4');
cy.get('#root_0_currency_type').select('Crypto');
cy.get('#root_0_currency').select('SNT');
cy.get('#root_0_unit_price').type('1750');
cy.get('#root_item_0_sub_category').select('freelancers');
cy.get('#root_item_0_item_name').clear().type('Freelancers to do the Python development and front end react app development');
cy.get('#root_item_0_qty').clear().type('4');
cy.get('#root_item_0_currency_type').select('Crypto');
cy.get('#root_item_0_currency').select('SNT');
cy.get('#root_item_0_unit_price').type('1750');
cy.get('#root > div:nth-child(3) > p > button').click();
cy.get('#root_item > div:nth-child(3) > p > button').click();
//item 1
cy.get('#root_1_sub_category').select('consultants');
cy.get('#root_1_item').clear().type('A consultant (from Latin: consultare "to deliberate") is a professional');
cy.get('#root_1_qty').clear().type('1');
cy.get('#root_1_currency_type').select('Fiat');
cy.get('#root_1_currency').select('CAD');
cy.get('#root_1_unit_price').type('1355');
cy.get('#root_item_1_sub_category').select('consultants');
cy.get('#root_item_1_item_name').clear().type('A consultant (from Latin: consultare "to deliberate") is a professional');
cy.get('#root_item_1_qty').clear().type('1');
cy.get('#root_item_1_currency_type').select('Fiat');
cy.get('#root_item_1_currency').select('CAD');
cy.get('#root_item_1_unit_price').type('1355');
cy.get('#root > div:nth-child(3) > p > button').click();
cy.get('#root_item > div:nth-child(3) > p > button').click();
//item 2
cy.get('#root_2_sub_category').select('freelancers');
cy.get('#root_2_item').clear().type('Find & hire top freelancers, web developers & designers inexpensively. ');
cy.get('#root_2_qty').clear().type('6');
cy.get('#root_2_currency_type').select('Crypto');
cy.get('#root_2_currency').select('SNT');
cy.get('#root_2_unit_price').type('2300');
cy.get('#root_item_2_sub_category').select('freelancers');
cy.get('#root_item_2_item_name').clear().type('Find & hire top freelancers, web developers & designers inexpensively. ');
cy.get('#root_item_2_qty').clear().type('6');
cy.get('#root_item_2_currency_type').select('Crypto');
cy.get('#root_item_2_currency').select('SNT');
cy.get('#root_item_2_unit_price').type('2300');
@ -422,8 +433,9 @@ describe('Consulting Fees Path - Without Files', () => {
.contains(/^Submit$/)
.click();
cy.contains('Tasks for my open instances', { timeout: 60000 });
cy.contains('Started by me', { timeout: 60000 });
cy.logout();
cy.wait(1000);
let budgetOwnerUsername = Cypress.env('budgetowner_username');
let budgetOwnerPassword = Cypress.env('budgetowner_password');
@ -452,7 +464,7 @@ describe('Consulting Fees Path - Without Files', () => {
budgetOwnerUsername,
budgetOwnerPassword,
processInstanceId,
'Task: Reminder: Request Additional Budget',
'Task: Reminder: Check Existing Budget',
"approve"
);
@ -466,7 +478,7 @@ describe('Consulting Fees Path - Without Files', () => {
describe('Consulting Fees Path - With Files', () => {
Cypress._.times(1, () => {
//Budget owner approves the request
it('Budget owner approves', () => {
it.only('Budget owner approves', () => {
let username = Cypress.env('requestor_username');
let password = Cypress.env('requestor_password');
cy.log('=====username : ' + username);
@ -476,24 +488,25 @@ describe('Consulting Fees Path - With Files', () => {
cy.visit('/');
cy.contains('Start New +').click();
cy.contains('Raise New Demand Request');
cy.contains('Request Goods/Services');
cy.runPrimaryBpmnFile(true);
cy.contains('Please select the type of request to start the process.');
// wait a second to ensure we can click the radio button
cy.wait(2000);
cy.get('input#root-procurement').click();
cy.wait(2000);
cy.get('button')
.contains(/^Submit$/)
.click();
/* cy.contains('Please select the type of request to start the process.');
// wait a second to ensure we can click the radio button
cy.wait(2000);
cy.get('input#root-procurement').click();
cy.wait(2000);
cy.get('button')
.contains(/^Submit$/)
.click();
*/
cy.contains(
'Submit a new demand request for the procurement of needed items',
'Request Goods/Services',
{ timeout: 60000 }
);
@ -503,46 +516,48 @@ describe('Consulting Fees Path - With Files', () => {
const processInstanceId = currentUrl.match(/(?<=\/tasks\/)\d+/)[0];
cy.log('==###############===processInstanceId : ', processInstanceId);
let projectId = Cypress.env('project_id');
cy.wait(2000);
cy.get('#root_project').select(projectId);
cy.get('#root_category').select('consult_fees');
cy.get('#root_purpose').clear().type('Consulting ==== Management consulting includes a broad range of activities, and the many firms and their members often define these practices quite differently. One way to categorize the activities is in terms of the professionals area of expertise.');
cy.get('#root_criticality').select('High');
cy.get('#root_period').clear().type('2025-12-25');
cy.get('#root_period').clear().type('05-12-2025');
cy.get('body').click();
cy.get('#root_vendor').clear().type('Embassar');
cy.get('#root_payment_method').select('Bank Transfer');
cy.get('button')
/*cy.get('button')
.contains(/^Submit$/)
.click();
cy.contains('Task: Enter NDR Items', { timeout: 60000 });
cy.contains('Task: Enter NDR Items', { timeout: 60000 });*/
//item 0
cy.get('#root_0_sub_category').select('ambassadors');
cy.get('#root_0_item').clear().type('An ambassador is an official envoy, especially a high-ranking diplomat who represents a state.');
cy.get('#root_0_qty').clear().type('4');
cy.get('#root_0_currency_type').select('Crypto');
cy.get('#root_0_currency').select('ETH');
cy.get('#root_0_unit_price').type('1.15');
cy.get('#root_item_0_sub_category').select('ambassadors');
cy.get('#root_item_0_item_name').clear().type('An ambassador is an official envoy, especially a high-ranking diplomat who represents a state.');
cy.get('#root_item_0_qty').clear().type('4');
cy.get('#root_item_0_currency_type').select('Crypto');
cy.get('#root_item_0_currency').select('ETH');
cy.get('#root_item_0_unit_price').type('1.15');
cy.get('#root > div:nth-child(3) > p > button').click();
cy.get('#root_item > div:nth-child(3) > p > button').click();
//item 1
cy.get('#root_1_sub_category').select('consultants');
cy.get('#root_1_item').clear().type('A consultant (from Latin: consultare "to deliberate") is a professional');
cy.get('#root_1_qty').clear().type('1');
cy.get('#root_1_currency_type').select('Fiat');
cy.get('#root_1_currency').select('CAD');
cy.get('#root_1_unit_price').type('1355');
cy.get('#root_item_1_sub_category').select('consultants');
cy.get('#root_item_1_item_name').clear().type('A consultant (from Latin: consultare "to deliberate") is a professional');
cy.get('#root_item_1_qty').clear().type('1');
cy.get('#root_item_1_currency_type').select('Fiat');
cy.get('#root_item_1_currency').select('CAD');
cy.get('#root_item_1_unit_price').type('1355');
cy.get('#root > div:nth-child(3) > p > button').click();
cy.get('#root_item > div:nth-child(3) > p > button').click();
//item 2
cy.get('#root_2_sub_category').select('freelancers');
cy.get('#root_2_item').clear().type('Find & hire top freelancers, web developers & designers inexpensively. ');
cy.get('#root_2_qty').clear().type('6');
cy.get('#root_2_currency_type').select('Crypto');
cy.get('#root_2_currency').select('SNT');
cy.get('#root_2_unit_price').type('2300');
cy.get('#root_item_2_sub_category').select('freelancers');
cy.get('#root_item_2_item_name').clear().type('Find & hire top freelancers, web developers & designers inexpensively. ');
cy.get('#root_item_2_qty').clear().type('6');
cy.get('#root_item_2_currency_type').select('Crypto');
cy.get('#root_item_2_currency').select('SNT');
cy.get('#root_item_2_unit_price').type('2300');
cy.get('button')
@ -556,8 +571,47 @@ describe('Consulting Fees Path - With Files', () => {
cy.get('.cds--text-area__wrapper').find('#root').type('For professionals working in the professional services, consultant and advisor are often used and fall under common terminology. Consultancy.uk zooms in on this field to get a closer look. \n https://www.consultancy.uk/career/what-is-consulting');
cy.get('#root > div:nth-child(3) > p > button').click();
cy.get("input[type=file]")
.attachFile(['lorem-ipsum.pdf', 'png-5mb-1.png', 'Free_Test_Data_1MB_PDF.pdf', 'sampletext.txt']);
.attachFile(['lorem-ipsum.pdf']);
cy.wait(1000);
cy.get('#root > div:nth-child(3) > p > button').click();
cy.wait(1000);
cy.get('#root > div.row.array-item-list > div:nth-child(2) > div > div.cds--sm\\:col-span-1.cds--md\\:col-span-1.cds--lg\\:col-span-1.cds--css-grid-column > div > div > button.btn.btn-default.array-item-move-up > svg').click();
cy.wait(1000);
cy.get("input[type=file]")
.attachFile(['png-5mb-1.png']);
cy.wait(1000);
cy.get('#root > div:nth-child(3) > p > button').click();
cy.wait(1000);
cy.get('#root > div.row.array-item-list > div:nth-child(3) > div > div.cds--sm\\:col-span-1.cds--md\\:col-span-1.cds--lg\\:col-span-1.cds--css-grid-column > div > div > button.btn.btn-default.array-item-move-up > svg').click();
cy.wait(1000);
cy.get('#root > div.row.array-item-list > div:nth-child(2) > div > div.cds--sm\\:col-span-1.cds--md\\:col-span-1.cds--lg\\:col-span-1.cds--css-grid-column > div > div > button.btn.btn-default.array-item-move-up > svg').click();
cy.wait(1000);
cy.get("input[type=file]")
.attachFile(['Free_Test_Data_1MB_PDF.pdf']);
cy.wait(1000);
cy.get('#root > div:nth-child(3) > p > button').click();
cy.wait(1000);
cy.get('#root > div.row.array-item-list > div:nth-child(4) > div > div.cds--sm\\:col-span-1.cds--md\\:col-span-1.cds--lg\\:col-span-1.cds--css-grid-column > div > div > button.btn.btn-default.array-item-move-up > svg').click();
cy.wait(1000);
cy.get('#root > div.row.array-item-list > div:nth-child(3) > div > div.cds--sm\\:col-span-1.cds--md\\:col-span-1.cds--lg\\:col-span-1.cds--css-grid-column > div > div > button.btn.btn-default.array-item-move-up > svg').click();
cy.wait(1000);
cy.get('#root > div.row.array-item-list > div:nth-child(2) > div > div.cds--sm\\:col-span-1.cds--md\\:col-span-1.cds--lg\\:col-span-1.cds--css-grid-column > div > div > button.btn.btn-default.array-item-move-up > svg').click();
cy.wait(1000);
cy.get("input[type=file]")
.attachFile(['sampletext.txt']);
cy.wait(2000);
cy.contains('Submit the Request').click();
@ -569,8 +623,9 @@ describe('Consulting Fees Path - With Files', () => {
.click();
cy.contains('Tasks for my open instances', { timeout: 60000 });
cy.contains('Started by me', { timeout: 60000 });
cy.logout();
cy.wait(1000);
let budgetOwnerUsername = Cypress.env('budgetowner_username');
let budgetOwnerPassword = Cypress.env('budgetowner_password');
@ -581,7 +636,7 @@ describe('Consulting Fees Path - With Files', () => {
budgetOwnerUsername,
budgetOwnerPassword,
processInstanceId,
'Task: Reminder: Request Additional Budget',
'Task: Reminder: Check Existing Budget',
"approve"
);
@ -599,24 +654,25 @@ describe('Consulting Fees Path - With Files', () => {
cy.visit('/');
cy.contains('Start New +').click();
cy.contains('Raise New Demand Request');
cy.contains('Request Goods/Services');
cy.runPrimaryBpmnFile(true);
cy.contains('Please select the type of request to start the process.');
// wait a second to ensure we can click the radio button
cy.wait(2000);
cy.get('input#root-procurement').click();
cy.wait(2000);
cy.get('button')
.contains(/^Submit$/)
.click();
/* cy.contains('Please select the type of request to start the process.');
// wait a second to ensure we can click the radio button
cy.wait(2000);
cy.get('input#root-procurement').click();
cy.wait(2000);
cy.get('button')
.contains(/^Submit$/)
.click();
*/
cy.contains(
'Submit a new demand request for the procurement of needed items',
'Request Goods/Services',
{ timeout: 60000 }
);
@ -626,46 +682,48 @@ describe('Consulting Fees Path - With Files', () => {
const processInstanceId = currentUrl.match(/(?<=\/tasks\/)\d+/)[0];
cy.log('==###############===processInstanceId : ', processInstanceId);
let projectId = Cypress.env('project_id');
cy.wait(2000);
cy.get('#root_project').select(projectId);
cy.get('#root_category').select('consult_fees');
cy.get('#root_purpose').clear().type('Consulting is defined as the practise of providing a third party with expertise on a matter in exchange for a fee. The service may involve either advisory or implementation services.');
cy.get('#root_criticality').select('Medium');
cy.get('#root_period').clear().type('2024-10-02');
cy.get('#root_period').clear().type('14-10-2029');
cy.get('body').click();
cy.get('#root_vendor').clear().type('Consultancy.uk');
cy.get('#root_payment_method').select('Crypto Transfer');
cy.get('button')
/*cy.get('button')
.contains(/^Submit$/)
.click();
cy.contains('Task: Enter NDR Items', { timeout: 60000 });
cy.contains('Task: Enter NDR Items', { timeout: 60000 });*/
//item 0
cy.get('#root_0_sub_category').select('consultants');
cy.get('#root_0_item').clear().type('Software development consultants with Python background');
cy.get('#root_0_qty').clear().type('5');
cy.get('#root_0_currency_type').select('Crypto');
cy.get('#root_0_currency').select('DAI');
cy.get('#root_0_unit_price').type('1500');
cy.get('#root_item_0_sub_category').select('consultants');
cy.get('#root_item_0_item_name').clear().type('Software development consultants with Python background');
cy.get('#root_item_0_qty').clear().type('5');
cy.get('#root_item_0_currency_type').select('Crypto');
cy.get('#root_item_0_currency').select('DAI');
cy.get('#root_item_0_unit_price').type('1500');
cy.get('#root > div:nth-child(3) > p > button').click();
cy.get('#root_item > div:nth-child(3) > p > button').click();
//item 1
cy.get('#root_1_sub_category').select('consultants');
cy.get('#root_1_item').clear().type('A consultant (from Latin: consultare "to deliberate") is a professional');
cy.get('#root_1_qty').clear().type('1');
cy.get('#root_1_currency_type').select('Fiat');
cy.get('#root_1_currency').select('CAD');
cy.get('#root_1_unit_price').type('1355');
cy.get('#root_item_1_sub_category').select('consultants');
cy.get('#root_item_1_item_name').clear().type('A consultant (from Latin: consultare "to deliberate") is a professional');
cy.get('#root_item_1_qty').clear().type('1');
cy.get('#root_item_1_currency_type').select('Fiat');
cy.get('#root_item_1_currency').select('CAD');
cy.get('#root_item_1_unit_price').type('1355');
cy.get('#root > div:nth-child(3) > p > button').click();
cy.get('#root_item > div:nth-child(3) > p > button').click();
//item 2
cy.get('#root_2_sub_category').select('freelancers');
cy.get('#root_2_item').clear().type('Find & hire top freelancers, web developers & designers inexpensively. ');
cy.get('#root_2_qty').clear().type('6');
cy.get('#root_2_currency_type').select('Crypto');
cy.get('#root_2_currency').select('SNT');
cy.get('#root_2_unit_price').type('2300');
cy.get('#root_item_2_sub_category').select('freelancers');
cy.get('#root_item_2_item_name').clear().type('Find & hire top freelancers, web developers & designers inexpensively. ');
cy.get('#root_item_2_qty').clear().type('6');
cy.get('#root_item_2_currency_type').select('Crypto');
cy.get('#root_item_2_currency').select('SNT');
cy.get('#root_item_2_unit_price').type('2300');
cy.get('button')
@ -679,8 +737,47 @@ describe('Consulting Fees Path - With Files', () => {
cy.get('.cds--text-area__wrapper').find('#root').type('For professionals working in the professional services, consultant and advisor are often used and fall under common terminology. Consultancy.uk zooms in on this field to get a closer look. \n https://www.consultancy.uk/career/what-is-consulting');
cy.get('#root > div:nth-child(3) > p > button').click();
cy.get("input[type=file]")
.attachFile(['lorem-ipsum.pdf', 'png-5mb-1.png', 'Free_Test_Data_1MB_PDF.pdf', 'sampletext.txt']);
.attachFile(['lorem-ipsum.pdf']);
cy.wait(1000);
cy.get('#root > div:nth-child(3) > p > button').click();
cy.wait(1000);
cy.get('#root > div.row.array-item-list > div:nth-child(2) > div > div.cds--sm\\:col-span-1.cds--md\\:col-span-1.cds--lg\\:col-span-1.cds--css-grid-column > div > div > button.btn.btn-default.array-item-move-up > svg').click();
cy.wait(1000);
cy.get("input[type=file]")
.attachFile(['png-5mb-1.png']);
cy.wait(1000);
cy.get('#root > div:nth-child(3) > p > button').click();
cy.wait(1000);
cy.get('#root > div.row.array-item-list > div:nth-child(3) > div > div.cds--sm\\:col-span-1.cds--md\\:col-span-1.cds--lg\\:col-span-1.cds--css-grid-column > div > div > button.btn.btn-default.array-item-move-up > svg').click();
cy.wait(1000);
cy.get('#root > div.row.array-item-list > div:nth-child(2) > div > div.cds--sm\\:col-span-1.cds--md\\:col-span-1.cds--lg\\:col-span-1.cds--css-grid-column > div > div > button.btn.btn-default.array-item-move-up > svg').click();
cy.wait(1000);
cy.get("input[type=file]")
.attachFile(['Free_Test_Data_1MB_PDF.pdf']);
cy.wait(1000);
cy.get('#root > div:nth-child(3) > p > button').click();
cy.wait(1000);
cy.get('#root > div.row.array-item-list > div:nth-child(4) > div > div.cds--sm\\:col-span-1.cds--md\\:col-span-1.cds--lg\\:col-span-1.cds--css-grid-column > div > div > button.btn.btn-default.array-item-move-up > svg').click();
cy.wait(1000);
cy.get('#root > div.row.array-item-list > div:nth-child(3) > div > div.cds--sm\\:col-span-1.cds--md\\:col-span-1.cds--lg\\:col-span-1.cds--css-grid-column > div > div > button.btn.btn-default.array-item-move-up > svg').click();
cy.wait(1000);
cy.get('#root > div.row.array-item-list > div:nth-child(2) > div > div.cds--sm\\:col-span-1.cds--md\\:col-span-1.cds--lg\\:col-span-1.cds--css-grid-column > div > div > button.btn.btn-default.array-item-move-up > svg').click();
cy.wait(1000);
cy.get("input[type=file]")
.attachFile(['sampletext.txt']);
cy.wait(2000);
cy.contains('Submit the Request').click();
@ -691,8 +788,9 @@ describe('Consulting Fees Path - With Files', () => {
.contains(/^Submit$/)
.click();
cy.contains('Tasks for my open instances', { timeout: 60000 });
cy.contains('Started by me', { timeout: 60000 });
cy.logout();
cy.wait(1000);
let budgetOwnerUsername = Cypress.env('budgetowner_username');
let budgetOwnerPassword = Cypress.env('budgetowner_password');
@ -721,24 +819,25 @@ describe('Consulting Fees Path - With Files', () => {
cy.visit('/');
cy.contains('Start New +').click();
cy.contains('Raise New Demand Request');
cy.contains('Request Goods/Services');
cy.runPrimaryBpmnFile(true);
cy.contains('Please select the type of request to start the process.');
// wait a second to ensure we can click the radio button
cy.wait(2000);
cy.get('input#root-procurement').click();
cy.wait(2000);
cy.get('button')
.contains(/^Submit$/)
.click();
/* cy.contains('Please select the type of request to start the process.');
// wait a second to ensure we can click the radio button
cy.wait(2000);
cy.get('input#root-procurement').click();
cy.wait(2000);
cy.get('button')
.contains(/^Submit$/)
.click();
*/
cy.contains(
'Submit a new demand request for the procurement of needed items',
'Request Goods/Services',
{ timeout: 60000 }
);
@ -748,45 +847,47 @@ describe('Consulting Fees Path - With Files', () => {
const processInstanceId = currentUrl.match(/(?<=\/tasks\/)\d+/)[0];
cy.log('==###############===processInstanceId : ', processInstanceId);
let projectId = Cypress.env('project_id');
cy.wait(2000);
cy.get('#root_project').select(projectId);
cy.get('#root_category').select('consult_fees');
cy.get('#root_purpose').clear().type('Freelancing - Freelancing is doing specific work for clients without committing to full-time employment. Freelancers often take on multiple projects with different clients simultaneously. IRS considers freelancers to be self-employed individuals.');
cy.get('#root_criticality').select('Low');
cy.get('#root_period').clear().type('2025-04-15');
cy.get('#root_period').clear().type('05-04-2024');
cy.get('body').click();
cy.get('#root_vendor').clear().type('Upwork');
cy.get('#root_payment_method').select('Debit Card');
cy.get('button')
/*cy.get('button')
.contains(/^Submit$/)
.click();
cy.contains('Task: Enter NDR Items', { timeout: 60000 });
cy.contains('Task: Enter NDR Items', { timeout: 60000 });*/
//item 0
cy.get('#root_0_sub_category').select('freelancers');
cy.get('#root_0_item').clear().type('Freelancers to do the Python development and front end react app development');
cy.get('#root_0_qty').clear().type('4');
cy.get('#root_0_currency_type').select('Crypto');
cy.get('#root_0_currency').select('SNT');
cy.get('#root_0_unit_price').type('1750');
cy.get('#root_item_0_sub_category').select('freelancers');
cy.get('#root_item_0_item_name').clear().type('Freelancers to do the Python development and front end react app development');
cy.get('#root_item_0_qty').clear().type('4');
cy.get('#root_item_0_currency_type').select('Crypto');
cy.get('#root_item_0_currency').select('SNT');
cy.get('#root_item_0_unit_price').type('1750');
cy.get('#root > div:nth-child(3) > p > button').click();
cy.get('#root_item > div:nth-child(3) > p > button').click();
//item 1
cy.get('#root_1_sub_category').select('consultants');
cy.get('#root_1_item').clear().type('A consultant (from Latin: consultare "to deliberate") is a professional');
cy.get('#root_1_qty').clear().type('1');
cy.get('#root_1_currency_type').select('Fiat');
cy.get('#root_1_currency').select('CAD');
cy.get('#root_1_unit_price').type('1355');
cy.get('#root_item_1_sub_category').select('consultants');
cy.get('#root_item_1_item_name').clear().type('A consultant (from Latin: consultare "to deliberate") is a professional');
cy.get('#root_item_1_qty').clear().type('1');
cy.get('#root_item_1_currency_type').select('Fiat');
cy.get('#root_item_1_currency').select('CAD');
cy.get('#root_item_1_unit_price').type('1355');
cy.get('#root > div:nth-child(3) > p > button').click();
cy.get('#root_item > div:nth-child(3) > p > button').click();
//item 2
cy.get('#root_2_sub_category').select('freelancers');
cy.get('#root_2_item').clear().type('Find & hire top freelancers, web developers & designers inexpensively. ');
cy.get('#root_2_qty').clear().type('6');
cy.get('#root_2_currency_type').select('Crypto');
cy.get('#root_2_currency').select('SNT');
cy.get('#root_2_unit_price').type('2300');
cy.get('#root_item_2_sub_category').select('freelancers');
cy.get('#root_item_2_item_name').clear().type('Find & hire top freelancers, web developers & designers inexpensively. ');
cy.get('#root_item_2_qty').clear().type('6');
cy.get('#root_item_2_currency_type').select('Crypto');
cy.get('#root_item_2_currency').select('SNT');
cy.get('#root_item_2_unit_price').type('2300');
cy.get('button')
@ -800,8 +901,47 @@ describe('Consulting Fees Path - With Files', () => {
cy.get('.cds--text-area__wrapper').find('#root').type('It\s free and easy to post a job. Simply fill in a title, description and budget and competitive bids come within minutes. No job is too big or too small. We\'ve got freelancers for jobs of any size or budget across 1800 skills. No job is too complex.');
cy.get('#root > div:nth-child(3) > p > button').click();
cy.get("input[type=file]")
.attachFile(['lorem-ipsum.pdf', 'png-5mb-1.png', 'Free_Test_Data_1MB_PDF.pdf', 'sampletext.txt']);
.attachFile(['lorem-ipsum.pdf']);
cy.wait(1000);
cy.get('#root > div:nth-child(3) > p > button').click();
cy.wait(1000);
cy.get('#root > div.row.array-item-list > div:nth-child(2) > div > div.cds--sm\\:col-span-1.cds--md\\:col-span-1.cds--lg\\:col-span-1.cds--css-grid-column > div > div > button.btn.btn-default.array-item-move-up > svg').click();
cy.wait(1000);
cy.get("input[type=file]")
.attachFile(['png-5mb-1.png']);
cy.wait(1000);
cy.get('#root > div:nth-child(3) > p > button').click();
cy.wait(1000);
cy.get('#root > div.row.array-item-list > div:nth-child(3) > div > div.cds--sm\\:col-span-1.cds--md\\:col-span-1.cds--lg\\:col-span-1.cds--css-grid-column > div > div > button.btn.btn-default.array-item-move-up > svg').click();
cy.wait(1000);
cy.get('#root > div.row.array-item-list > div:nth-child(2) > div > div.cds--sm\\:col-span-1.cds--md\\:col-span-1.cds--lg\\:col-span-1.cds--css-grid-column > div > div > button.btn.btn-default.array-item-move-up > svg').click();
cy.wait(1000);
cy.get("input[type=file]")
.attachFile(['Free_Test_Data_1MB_PDF.pdf']);
cy.wait(1000);
cy.get('#root > div:nth-child(3) > p > button').click();
cy.wait(1000);
cy.get('#root > div.row.array-item-list > div:nth-child(4) > div > div.cds--sm\\:col-span-1.cds--md\\:col-span-1.cds--lg\\:col-span-1.cds--css-grid-column > div > div > button.btn.btn-default.array-item-move-up > svg').click();
cy.wait(1000);
cy.get('#root > div.row.array-item-list > div:nth-child(3) > div > div.cds--sm\\:col-span-1.cds--md\\:col-span-1.cds--lg\\:col-span-1.cds--css-grid-column > div > div > button.btn.btn-default.array-item-move-up > svg').click();
cy.wait(1000);
cy.get('#root > div.row.array-item-list > div:nth-child(2) > div > div.cds--sm\\:col-span-1.cds--md\\:col-span-1.cds--lg\\:col-span-1.cds--css-grid-column > div > div > button.btn.btn-default.array-item-move-up > svg').click();
cy.wait(1000);
cy.get("input[type=file]")
.attachFile(['sampletext.txt']);
cy.wait(2000);
cy.contains('Submit the Request').click();
@ -812,8 +952,9 @@ describe('Consulting Fees Path - With Files', () => {
.contains(/^Submit$/)
.click();
cy.contains('Tasks for my open instances', { timeout: 60000 });
cy.contains('Started by me', { timeout: 60000 });
cy.logout();
cy.wait(1000);
let budgetOwnerUsername = Cypress.env('budgetowner_username');
let budgetOwnerPassword = Cypress.env('budgetowner_password');
@ -842,7 +983,7 @@ describe('Consulting Fees Path - With Files', () => {
budgetOwnerUsername,
budgetOwnerPassword,
processInstanceId,
'Task: Reminder: Request Additional Budget',
'Task: Reminder: Check Existing Budget',
"approve"
);

File diff suppressed because it is too large Load Diff

View File

@ -84,7 +84,7 @@ describe('Other Fees Path - Without Files', () => {
Cypress._.times(1, () => {
//Budget owner approves the request
it('Budget owner approves', () => {
it.only('Budget owner approves', () => {
let username = Cypress.env('requestor_username');
let password = Cypress.env('requestor_password');
cy.log('=====username : ' + username);
@ -94,24 +94,25 @@ describe('Other Fees Path - Without Files', () => {
cy.visit('/');
cy.contains('Start New +').click();
cy.contains('Raise New Demand Request');
cy.contains('Request Goods/Services');
cy.runPrimaryBpmnFile(true);
cy.contains('Please select the type of request to start the process.');
// wait a second to ensure we can click the radio button
cy.wait(2000);
cy.get('input#root-procurement').click();
cy.wait(2000);
cy.get('button')
.contains(/^Submit$/)
.click();
/* cy.contains('Please select the type of request to start the process.');
// wait a second to ensure we can click the radio button
cy.wait(2000);
cy.get('input#root-procurement').click();
cy.wait(2000);
cy.get('button')
.contains(/^Submit$/)
.click();
*/
cy.contains(
'Submit a new demand request for the procurement of needed items',
'Request Goods/Services',
{ timeout: 60000 }
);
@ -121,36 +122,39 @@ describe('Other Fees Path - Without Files', () => {
const processInstanceId = currentUrl.match(/(?<=\/tasks\/)\d+/)[0];
cy.log('==###############===processInstanceId : ', processInstanceId);
let projectId = Cypress.env('project_id');
cy.wait(2000);
cy.get('#root_project').select(projectId);
cy.get('#root_category').select('other_fees');
cy.get('#root_purpose').clear().type('Other Fees and Expenses means, collectively, all fees and expenses payable to Lenders under the Loan Documents, other than principal, interest and default interest/penalty amounts.');
cy.get('#root_criticality').select('High');
cy.get('#root_period').clear().type('2025-11-25');
cy.get('#root_period').clear().type('25-11-2025');
cy.get('body').click();
cy.get('#root_vendor').clear().type('ABC CO');
cy.get('#root_payment_method').select('Reimbursement');
cy.get('button')
/*cy.get('button')
.contains(/^Submit$/)
.click();
cy.contains('Task: Enter NDR Items', { timeout: 60000 });
*/
//item 0
cy.get('#root_0_sub_category').select('bounties');
cy.get('#root_0_item').clear().type('A bounty is a payment or reward of money to locate');
cy.get('#root_0_qty').clear().type('2');
cy.get('#root_0_currency_type').select('Fiat');
cy.get('#root_0_currency').select('AUD');
cy.get('#root_0_unit_price').type('2416');
cy.get('#root_item_0_sub_category').select('bounties');
cy.get('#root_item_0_item_name').clear().type('A bounty is a payment or reward of money to locate');
cy.get('#root_item_0_qty').clear().type('2');
cy.get('#root_item_0_currency_type').select('Fiat');
cy.get('#root_item_0_currency').select('AUD');
cy.get('#root_item_0_unit_price').type('2416');
cy.get('#root > div:nth-child(3) > p > button').click();
cy.get('#root_item > div:nth-child(3) > p > button').click();
//item 1
cy.get('#root_1_sub_category').select('coworking');
cy.get('#root_1_item').clear().type('A consultant (from Latin: consultare "to deliberate") is a professional');
cy.get('#root_1_qty').clear().type('1');
cy.get('#root_1_currency_type').select('Crypto');
cy.get('#root_1_currency').select('SNT');
cy.get('#root_1_unit_price').type('1355');
cy.get('#root_item_1_sub_category').select('coworking');
cy.get('#root_item_1_item_name').clear().type('A consultant (from Latin: consultare "to deliberate") is a professional');
cy.get('#root_item_1_qty').clear().type('1');
cy.get('#root_item_1_currency_type').select('Crypto');
cy.get('#root_item_1_currency').select('SNT');
cy.get('#root_item_1_unit_price').type('1355');
cy.get('button')
@ -174,8 +178,9 @@ describe('Other Fees Path - Without Files', () => {
.click();
cy.contains('Tasks for my open instances', { timeout: 60000 });
cy.contains('Started by me', { timeout: 60000 });
cy.logout();
cy.wait(1000);
let budgetOwnerUsername = Cypress.env('budgetowner_username');
let budgetOwnerPassword = Cypress.env('budgetowner_password');
@ -186,7 +191,7 @@ describe('Other Fees Path - Without Files', () => {
budgetOwnerUsername,
budgetOwnerPassword,
processInstanceId,
'Task: Reminder: Request Additional Budget',
'Task: Reminder: Check Existing Budget',
"approve"
);
@ -204,24 +209,25 @@ describe('Other Fees Path - Without Files', () => {
cy.visit('/');
cy.contains('Start New +').click();
cy.contains('Raise New Demand Request');
cy.contains('Request Goods/Services');
cy.runPrimaryBpmnFile(true);
cy.contains('Please select the type of request to start the process.');
// wait a second to ensure we can click the radio button
cy.wait(2000);
cy.get('input#root-procurement').click();
cy.wait(2000);
cy.get('button')
.contains(/^Submit$/)
.click();
/* cy.contains('Please select the type of request to start the process.');
// wait a second to ensure we can click the radio button
cy.wait(2000);
cy.get('input#root-procurement').click();
cy.wait(2000);
cy.get('button')
.contains(/^Submit$/)
.click();
*/
cy.contains(
'Submit a new demand request for the procurement of needed items',
'Request Goods/Services',
{ timeout: 60000 }
);
@ -231,24 +237,27 @@ describe('Other Fees Path - Without Files', () => {
const processInstanceId = currentUrl.match(/(?<=\/tasks\/)\d+/)[0];
cy.log('==###############===processInstanceId : ', processInstanceId);
let projectId = Cypress.env('project_id');
cy.wait(2000);
cy.get('#root_project').select(projectId);
cy.get('#root_category').select('other_fees');
cy.get('#root_purpose').clear().type('Other Fees and Expenses means, collectively, all fees and expenses payable to Lenders under the Loan Documents, other than principal, interest and default interest/penalty amounts.');
cy.get('#root_criticality').select('Medium');
cy.get('#root_period').clear().type('2024-02-06');
cy.get('#root_period').clear().type('24-02-2036');
cy.get('body').click();
cy.get('#root_vendor').clear().type('CO-WORK ENG');
cy.get('#root_payment_method').select('Bank Transfer');
cy.get('button')
/*cy.get('button')
.contains(/^Submit$/)
.click();
cy.contains('Task: Enter NDR Items', { timeout: 60000 });
cy.get('#root_0_sub_category').select('coworking');
cy.get('#root_0_item').clear().type('Coworking is an arrangement in which workers for different companies share an office space');
cy.get('#root_0_qty').clear().type('5');
cy.get('#root_0_currency_type').select('Fiat');
cy.get('#root_0_currency').select('EUR');
cy.get('#root_0_unit_price').type('250');
*/
cy.get('#root_item_0_sub_category').select('coworking');
cy.get('#root_item_0_item_name').clear().type('Coworking is an arrangement in which workers for different companies share an office space');
cy.get('#root_item_0_qty').clear().type('5');
cy.get('#root_item_0_currency_type').select('Fiat');
cy.get('#root_item_0_currency').select('EUR');
cy.get('#root_item_0_unit_price').type('250');
cy.get('button')
@ -271,8 +280,9 @@ describe('Other Fees Path - Without Files', () => {
.contains(/^Submit$/)
.click();
cy.contains('Tasks for my open instances', { timeout: 60000 });
cy.contains('Started by me', { timeout: 60000 });
cy.logout();
cy.wait(1000);
let budgetOwnerUsername = Cypress.env('budgetowner_username');
let budgetOwnerPassword = Cypress.env('budgetowner_password');
@ -301,24 +311,25 @@ describe('Other Fees Path - Without Files', () => {
cy.visit('/');
cy.contains('Start New +').click();
cy.contains('Raise New Demand Request');
cy.contains('Request Goods/Services');
cy.runPrimaryBpmnFile(true);
cy.contains('Please select the type of request to start the process.');
// wait a second to ensure we can click the radio button
cy.wait(2000);
cy.get('input#root-procurement').click();
cy.wait(2000);
cy.get('button')
.contains(/^Submit$/)
.click();
/* cy.contains('Please select the type of request to start the process.');
// wait a second to ensure we can click the radio button
cy.wait(2000);
cy.get('input#root-procurement').click();
cy.wait(2000);
cy.get('button')
.contains(/^Submit$/)
.click();
*/
cy.contains(
'Submit a new demand request for the procurement of needed items',
'Request Goods/Services',
{ timeout: 60000 }
);
@ -328,24 +339,27 @@ describe('Other Fees Path - Without Files', () => {
const processInstanceId = currentUrl.match(/(?<=\/tasks\/)\d+/)[0];
cy.log('==###############===processInstanceId : ', processInstanceId);
let projectId = Cypress.env('project_id');
cy.wait(2000);
cy.get('#root_project').select(projectId);
cy.get('#root_category').select('other_fees');
cy.get('#root_purpose').clear().type(' It allows cost savings and convenience through the use of common infrastructures, such as equipment, utilities and receptionist and custodial services, and in some cases refreshments and parcel services.\nhttps://en.wikipedia.org/wiki/Coworking');
cy.get('#root_criticality').select('Low');
cy.get('#root_period').clear().type('2025-02-25');
cy.get('#root_period').clear().type('05-02-2025');
cy.get('body').click();
cy.get('#root_vendor').clear().type('Bounty Co');
cy.get('#root_payment_method').select('Crypto Transfer');
cy.get('button')
/*cy.get('button')
.contains(/^Submit$/)
.click();
cy.contains('Task: Enter NDR Items', { timeout: 60000 });
cy.get('#root_0_sub_category').select('bounties');
cy.get('#root_0_item').clear().type('Coworking is not only about providing a physical place, but also about establishing a community.');
cy.get('#root_0_qty').clear().type('4');
cy.get('#root_0_currency_type').select('Crypto');
cy.get('#root_0_currency').select('SNT');
cy.get('#root_0_unit_price').type('450');
*/
cy.get('#root_item_0_sub_category').select('bounties');
cy.get('#root_item_0_item_name').clear().type('Coworking is not only about providing a physical place, but also about establishing a community.');
cy.get('#root_item_0_qty').clear().type('4');
cy.get('#root_item_0_currency_type').select('Crypto');
cy.get('#root_item_0_currency').select('SNT');
cy.get('#root_item_0_unit_price').type('450');
cy.get('button')
@ -368,8 +382,9 @@ describe('Other Fees Path - Without Files', () => {
.contains(/^Submit$/)
.click();
cy.contains('Tasks for my open instances', { timeout: 60000 });
cy.contains('Started by me', { timeout: 60000 });
cy.logout();
cy.wait(1000);
let budgetOwnerUsername = Cypress.env('budgetowner_username');
let budgetOwnerPassword = Cypress.env('budgetowner_password');
@ -398,7 +413,7 @@ describe('Other Fees Path - Without Files', () => {
budgetOwnerUsername,
budgetOwnerPassword,
processInstanceId,
'Task: Reminder: Request Additional Budget',
'Task: Reminder: Check Existing Budget',
"approve"
);
@ -412,7 +427,7 @@ describe('Other Fees Path - With Files', () => {
Cypress._.times(1, () => {
//Budget owner approves the request
it('Budget owner approves', () => {
it.only('Budget owner approves', () => {
let username = Cypress.env('requestor_username');
let password = Cypress.env('requestor_password');
cy.log('=====username : ' + username);
@ -422,24 +437,25 @@ describe('Other Fees Path - With Files', () => {
cy.visit('/');
cy.contains('Start New +').click();
cy.contains('Raise New Demand Request');
cy.contains('Request Goods/Services');
cy.runPrimaryBpmnFile(true);
cy.contains('Please select the type of request to start the process.');
// wait a second to ensure we can click the radio button
cy.wait(2000);
cy.get('input#root-procurement').click();
cy.wait(2000);
cy.get('button')
.contains(/^Submit$/)
.click();
/* cy.contains('Please select the type of request to start the process.');
// wait a second to ensure we can click the radio button
cy.wait(2000);
cy.get('input#root-procurement').click();
cy.wait(2000);
cy.get('button')
.contains(/^Submit$/)
.click();
*/
cy.contains(
'Submit a new demand request for the procurement of needed items',
'Request Goods/Services',
{ timeout: 60000 }
);
@ -449,35 +465,38 @@ describe('Other Fees Path - With Files', () => {
const processInstanceId = currentUrl.match(/(?<=\/tasks\/)\d+/)[0];
cy.log('==###############===processInstanceId : ', processInstanceId);
let projectId = Cypress.env('project_id');
cy.wait(2000);
cy.get('#root_project').select(projectId);
cy.get('#root_category').select('other_fees');
cy.get('#root_purpose').clear().type('It allows cost savings and convenience through the use of common infrastructures, such as equipment, utilities and receptionist and custodial services, and in some cases refreshments and parcel acceptance services');
cy.get('#root_criticality').select('High');
cy.get('#root_period').clear().type('2025-11-25');
cy.get('#root_period').clear().type('15-11-2025');
cy.get('body').click();
cy.get('#root_vendor').clear().type('Embassar');
cy.get('#root_payment_method').select('Reimbursement');
cy.get('button')
/*cy.get('button')
.contains(/^Submit$/)
.click();
cy.contains('Task: Enter NDR Items', { timeout: 60000 });
*/
//item 0
cy.get('#root_0_sub_category').select('bounties');
cy.get('#root_0_item').clear().type('A bounty is a payment or reward of money to locate');
cy.get('#root_0_qty').clear().type('2');
cy.get('#root_0_currency_type').select('Fiat');
cy.get('#root_0_currency').select('AUD');
cy.get('#root_0_unit_price').type('2416');
cy.get('#root_item_0_sub_category').select('bounties');
cy.get('#root_item_0_item_name').clear().type('A bounty is a payment or reward of money to locate');
cy.get('#root_item_0_qty').clear().type('2');
cy.get('#root_item_0_currency_type').select('Fiat');
cy.get('#root_item_0_currency').select('AUD');
cy.get('#root_item_0_unit_price').type('2416');
cy.get('#root > div:nth-child(3) > p > button').click();
cy.get('#root_item > div:nth-child(3) > p > button').click();
//item 1
cy.get('#root_1_sub_category').select('coworking');
cy.get('#root_1_item').clear().type('A consultant (from Latin: consultare "to deliberate") is a professional');
cy.get('#root_1_qty').clear().type('1');
cy.get('#root_1_currency_type').select('Crypto');
cy.get('#root_1_currency').select('DAI');
cy.get('#root_1_unit_price').type('4250');
cy.get('#root_item_1_sub_category').select('coworking');
cy.get('#root_item_1_item_name').clear().type('A consultant (from Latin: consultare "to deliberate") is a professional');
cy.get('#root_item_1_qty').clear().type('1');
cy.get('#root_item_1_currency_type').select('Crypto');
cy.get('#root_item_1_currency').select('DAI');
cy.get('#root_item_1_unit_price').type('4250');
cy.get('button')
.contains(/^Submit$/)
@ -490,8 +509,47 @@ describe('Other Fees Path - With Files', () => {
cy.get('.cds--text-area__wrapper').find('#root').type('For professionals working in the professional services, consultant and advisor are often used and fall under common terminology. Consultancy.uk zooms in on this field to get a closer look. \n https://www.consultancy.uk/career/what-is-consulting');
cy.get('#root > div:nth-child(3) > p > button').click();
cy.get("input[type=file]")
.attachFile(['lorem-ipsum.pdf', 'png-5mb-1.png', 'Free_Test_Data_1MB_PDF.pdf', 'sampletext.txt']);
.attachFile(['lorem-ipsum.pdf']);
cy.wait(1000);
cy.get('#root > div:nth-child(3) > p > button').click();
cy.wait(1000);
cy.get('#root > div.row.array-item-list > div:nth-child(2) > div > div.cds--sm\\:col-span-1.cds--md\\:col-span-1.cds--lg\\:col-span-1.cds--css-grid-column > div > div > button.btn.btn-default.array-item-move-up > svg').click();
cy.wait(1000);
cy.get("input[type=file]")
.attachFile(['png-5mb-1.png']);
cy.wait(1000);
cy.get('#root > div:nth-child(3) > p > button').click();
cy.wait(1000);
cy.get('#root > div.row.array-item-list > div:nth-child(3) > div > div.cds--sm\\:col-span-1.cds--md\\:col-span-1.cds--lg\\:col-span-1.cds--css-grid-column > div > div > button.btn.btn-default.array-item-move-up > svg').click();
cy.wait(1000);
cy.get('#root > div.row.array-item-list > div:nth-child(2) > div > div.cds--sm\\:col-span-1.cds--md\\:col-span-1.cds--lg\\:col-span-1.cds--css-grid-column > div > div > button.btn.btn-default.array-item-move-up > svg').click();
cy.wait(1000);
cy.get("input[type=file]")
.attachFile(['Free_Test_Data_1MB_PDF.pdf']);
cy.wait(1000);
cy.get('#root > div:nth-child(3) > p > button').click();
cy.wait(1000);
cy.get('#root > div.row.array-item-list > div:nth-child(4) > div > div.cds--sm\\:col-span-1.cds--md\\:col-span-1.cds--lg\\:col-span-1.cds--css-grid-column > div > div > button.btn.btn-default.array-item-move-up > svg').click();
cy.wait(1000);
cy.get('#root > div.row.array-item-list > div:nth-child(3) > div > div.cds--sm\\:col-span-1.cds--md\\:col-span-1.cds--lg\\:col-span-1.cds--css-grid-column > div > div > button.btn.btn-default.array-item-move-up > svg').click();
cy.wait(1000);
cy.get('#root > div.row.array-item-list > div:nth-child(2) > div > div.cds--sm\\:col-span-1.cds--md\\:col-span-1.cds--lg\\:col-span-1.cds--css-grid-column > div > div > button.btn.btn-default.array-item-move-up > svg').click();
cy.wait(1000);
cy.get("input[type=file]")
.attachFile(['sampletext.txt']);
cy.wait(2000);
cy.contains('Submit the Request').click();
@ -503,8 +561,9 @@ describe('Other Fees Path - With Files', () => {
.click();
cy.contains('Tasks for my open instances', { timeout: 60000 });
cy.contains('Started by me', { timeout: 60000 });
cy.logout();
cy.wait(1000);
let budgetOwnerUsername = Cypress.env('budgetowner_username');
let budgetOwnerPassword = Cypress.env('budgetowner_password');
@ -515,7 +574,7 @@ describe('Other Fees Path - With Files', () => {
budgetOwnerUsername,
budgetOwnerPassword,
processInstanceId,
'Task: Reminder: Request Additional Budget',
'Task: Reminder: Check Existing Budget',
"approve"
);
@ -533,24 +592,25 @@ describe('Other Fees Path - With Files', () => {
cy.visit('/');
cy.contains('Start New +').click();
cy.contains('Raise New Demand Request');
cy.contains('Request Goods/Services');
cy.runPrimaryBpmnFile(true);
cy.contains('Please select the type of request to start the process.');
// wait a second to ensure we can click the radio button
cy.wait(2000);
cy.get('input#root-procurement').click();
cy.wait(2000);
cy.get('button')
.contains(/^Submit$/)
.click();
/* cy.contains('Please select the type of request to start the process.');
// wait a second to ensure we can click the radio button
cy.wait(2000);
cy.get('input#root-procurement').click();
cy.wait(2000);
cy.get('button')
.contains(/^Submit$/)
.click();
*/
cy.contains(
'Submit a new demand request for the procurement of needed items',
'Request Goods/Services',
{ timeout: 60000 }
);
@ -560,24 +620,27 @@ describe('Other Fees Path - With Files', () => {
const processInstanceId = currentUrl.match(/(?<=\/tasks\/)\d+/)[0];
cy.log('==###############===processInstanceId : ', processInstanceId);
let projectId = Cypress.env('project_id');
cy.wait(2000);
cy.get('#root_project').select(projectId);
cy.get('#root_category').select('other_fees');
cy.get('#root_purpose').clear().type('Other Fees and Expenses means, collectively, all fees and expenses payable to Lenders under the Loan Documents, other than principal, interest and default interest/penalty amounts.');
cy.get('#root_criticality').select('Medium');
cy.get('#root_period').clear().type('2024-02-06');
cy.get('#root_period').clear().type('20-02-2026');
cy.get('body').click();
cy.get('#root_vendor').clear().type('CO-WORK ENG');
cy.get('#root_payment_method').select('Bank Transfer');
cy.get('button')
/*cy.get('button')
.contains(/^Submit$/)
.click();
cy.contains('Task: Enter NDR Items', { timeout: 60000 });
cy.get('#root_0_sub_category').select('coworking');
cy.get('#root_0_item').clear().type('Coworking is not only about providing a physical place, but also about establishing a community');
cy.get('#root_0_qty').clear().type('5');
cy.get('#root_0_currency_type').select('Fiat');
cy.get('#root_0_currency').select('EUR');
cy.get('#root_0_unit_price').type('250');
*/
cy.get('#root_item_0_sub_category').select('coworking');
cy.get('#root_item_0_item_name').clear().type('Coworking is not only about providing a physical place, but also about establishing a community');
cy.get('#root_item_0_qty').clear().type('5');
cy.get('#root_item_0_currency_type').select('Fiat');
cy.get('#root_item_0_currency').select('EUR');
cy.get('#root_item_0_unit_price').type('250');
cy.get('button')
@ -591,8 +654,47 @@ describe('Other Fees Path - With Files', () => {
cy.get('.cds--text-area__wrapper').find('#root').type('For professionals working in the professional services, consultant and advisor are often used and fall under common terminology. Consultancy.uk zooms in on this field to get a closer look. \n https://www.consultancy.uk/career/what-is-consulting');
cy.get('#root > div:nth-child(3) > p > button').click();
cy.get("input[type=file]")
.attachFile(['lorem-ipsum.pdf', 'png-5mb-1.png', 'Free_Test_Data_1MB_PDF.pdf', 'sampletext.txt']);
.attachFile(['lorem-ipsum.pdf']);
cy.wait(1000);
cy.get('#root > div:nth-child(3) > p > button').click();
cy.wait(1000);
cy.get('#root > div.row.array-item-list > div:nth-child(2) > div > div.cds--sm\\:col-span-1.cds--md\\:col-span-1.cds--lg\\:col-span-1.cds--css-grid-column > div > div > button.btn.btn-default.array-item-move-up > svg').click();
cy.wait(1000);
cy.get("input[type=file]")
.attachFile(['png-5mb-1.png']);
cy.wait(1000);
cy.get('#root > div:nth-child(3) > p > button').click();
cy.wait(1000);
cy.get('#root > div.row.array-item-list > div:nth-child(3) > div > div.cds--sm\\:col-span-1.cds--md\\:col-span-1.cds--lg\\:col-span-1.cds--css-grid-column > div > div > button.btn.btn-default.array-item-move-up > svg').click();
cy.wait(1000);
cy.get('#root > div.row.array-item-list > div:nth-child(2) > div > div.cds--sm\\:col-span-1.cds--md\\:col-span-1.cds--lg\\:col-span-1.cds--css-grid-column > div > div > button.btn.btn-default.array-item-move-up > svg').click();
cy.wait(1000);
cy.get("input[type=file]")
.attachFile(['Free_Test_Data_1MB_PDF.pdf']);
cy.wait(1000);
cy.get('#root > div:nth-child(3) > p > button').click();
cy.wait(1000);
cy.get('#root > div.row.array-item-list > div:nth-child(4) > div > div.cds--sm\\:col-span-1.cds--md\\:col-span-1.cds--lg\\:col-span-1.cds--css-grid-column > div > div > button.btn.btn-default.array-item-move-up > svg').click();
cy.wait(1000);
cy.get('#root > div.row.array-item-list > div:nth-child(3) > div > div.cds--sm\\:col-span-1.cds--md\\:col-span-1.cds--lg\\:col-span-1.cds--css-grid-column > div > div > button.btn.btn-default.array-item-move-up > svg').click();
cy.wait(1000);
cy.get('#root > div.row.array-item-list > div:nth-child(2) > div > div.cds--sm\\:col-span-1.cds--md\\:col-span-1.cds--lg\\:col-span-1.cds--css-grid-column > div > div > button.btn.btn-default.array-item-move-up > svg').click();
cy.wait(1000);
cy.get("input[type=file]")
.attachFile(['sampletext.txt']);
cy.wait(2000);
cy.contains('Submit the Request').click();
@ -603,8 +705,9 @@ describe('Other Fees Path - With Files', () => {
.contains(/^Submit$/)
.click();
cy.contains('Tasks for my open instances', { timeout: 60000 });
cy.contains('Started by me', { timeout: 60000 });
cy.logout();
cy.wait(1000);
let budgetOwnerUsername = Cypress.env('budgetowner_username');
let budgetOwnerPassword = Cypress.env('budgetowner_password');
@ -633,24 +736,25 @@ describe('Other Fees Path - With Files', () => {
cy.visit('/');
cy.contains('Start New +').click();
cy.contains('Raise New Demand Request');
cy.contains('Request Goods/Services');
cy.runPrimaryBpmnFile(true);
cy.contains('Please select the type of request to start the process.');
// wait a second to ensure we can click the radio button
cy.wait(2000);
cy.get('input#root-procurement').click();
cy.wait(2000);
cy.get('button')
.contains(/^Submit$/)
.click();
/* cy.contains('Please select the type of request to start the process.');
// wait a second to ensure we can click the radio button
cy.wait(2000);
cy.get('input#root-procurement').click();
cy.wait(2000);
cy.get('button')
.contains(/^Submit$/)
.click();
*/
cy.contains(
'Submit a new demand request for the procurement of needed items',
'Request Goods/Services',
{ timeout: 60000 }
);
@ -660,24 +764,27 @@ describe('Other Fees Path - With Files', () => {
const processInstanceId = currentUrl.match(/(?<=\/tasks\/)\d+/)[0];
cy.log('==###############===processInstanceId : ', processInstanceId);
let projectId = Cypress.env('project_id');
cy.wait(2000);
cy.get('#root_project').select(projectId);
cy.get('#root_category').select('other_fees');
cy.get('#root_purpose').clear().type('It allows cost savings and convenience through the use of common infrastructures, such as equipment, utilities and receptionist and custodial services, and in some cases refreshments and parcel services.\nhttps://en.wikipedia.org/wiki/Coworking');
cy.get('#root_criticality').select('Low');
cy.get('#root_period').clear().type('2025-02-25');
cy.get('#root_period').clear().type('12-02-2025');
cy.get('body').click();
cy.get('#root_vendor').clear().type('BOUNTY');
cy.get('#root_payment_method').select('Crypto Transfer');
cy.get('button')
/*cy.get('button')
.contains(/^Submit$/)
.click();
cy.contains('Task: Enter NDR Items', { timeout: 60000 });
cy.get('#root_0_sub_category').select('bounties');
cy.get('#root_0_item').clear().type('Coworking is distinct from business accelerators, business incubators, and executive suites.');
cy.get('#root_0_qty').clear().type('4');
cy.get('#root_0_currency_type').select('Crypto');
cy.get('#root_0_currency').select('SNT');
cy.get('#root_0_unit_price').type('450');
*/
cy.get('#root_item_0_sub_category').select('bounties');
cy.get('#root_item_0_item_name').clear().type('Coworking is distinct from business accelerators, business incubators, and executive suites.');
cy.get('#root_item_0_qty').clear().type('4');
cy.get('#root_item_0_currency_type').select('Crypto');
cy.get('#root_item_0_currency').select('SNT');
cy.get('#root_item_0_unit_price').type('450');
cy.get('button')
@ -691,8 +798,47 @@ describe('Other Fees Path - With Files', () => {
cy.get('.cds--text-area__wrapper').find('#root').type('It\s free and easy to post a job. Simply fill in a title, description and budget and competitive bids come within minutes. No job is too big or too small. We\'ve got freelancers for jobs of any size or budget across 1800 skills. No job is too complex.');
cy.get('#root > div:nth-child(3) > p > button').click();
cy.get("input[type=file]")
.attachFile(['lorem-ipsum.pdf', 'png-5mb-1.png', 'Free_Test_Data_1MB_PDF.pdf', 'sampletext.txt']);
.attachFile(['lorem-ipsum.pdf']);
cy.wait(1000);
cy.get('#root > div:nth-child(3) > p > button').click();
cy.wait(1000);
cy.get('#root > div.row.array-item-list > div:nth-child(2) > div > div.cds--sm\\:col-span-1.cds--md\\:col-span-1.cds--lg\\:col-span-1.cds--css-grid-column > div > div > button.btn.btn-default.array-item-move-up > svg').click();
cy.wait(1000);
cy.get("input[type=file]")
.attachFile(['png-5mb-1.png']);
cy.wait(1000);
cy.get('#root > div:nth-child(3) > p > button').click();
cy.wait(1000);
cy.get('#root > div.row.array-item-list > div:nth-child(3) > div > div.cds--sm\\:col-span-1.cds--md\\:col-span-1.cds--lg\\:col-span-1.cds--css-grid-column > div > div > button.btn.btn-default.array-item-move-up > svg').click();
cy.wait(1000);
cy.get('#root > div.row.array-item-list > div:nth-child(2) > div > div.cds--sm\\:col-span-1.cds--md\\:col-span-1.cds--lg\\:col-span-1.cds--css-grid-column > div > div > button.btn.btn-default.array-item-move-up > svg').click();
cy.wait(1000);
cy.get("input[type=file]")
.attachFile(['Free_Test_Data_1MB_PDF.pdf']);
cy.wait(1000);
cy.get('#root > div:nth-child(3) > p > button').click();
cy.wait(1000);
cy.get('#root > div.row.array-item-list > div:nth-child(4) > div > div.cds--sm\\:col-span-1.cds--md\\:col-span-1.cds--lg\\:col-span-1.cds--css-grid-column > div > div > button.btn.btn-default.array-item-move-up > svg').click();
cy.wait(1000);
cy.get('#root > div.row.array-item-list > div:nth-child(3) > div > div.cds--sm\\:col-span-1.cds--md\\:col-span-1.cds--lg\\:col-span-1.cds--css-grid-column > div > div > button.btn.btn-default.array-item-move-up > svg').click();
cy.wait(1000);
cy.get('#root > div.row.array-item-list > div:nth-child(2) > div > div.cds--sm\\:col-span-1.cds--md\\:col-span-1.cds--lg\\:col-span-1.cds--css-grid-column > div > div > button.btn.btn-default.array-item-move-up > svg').click();
cy.wait(1000);
cy.get("input[type=file]")
.attachFile(['sampletext.txt']);
cy.wait(2000);
cy.contains('Submit the Request').click();
@ -703,8 +849,9 @@ describe('Other Fees Path - With Files', () => {
.contains(/^Submit$/)
.click();
cy.contains('Tasks for my open instances', { timeout: 60000 });
cy.contains('Started by me', { timeout: 60000 });
cy.logout();
cy.wait(1000);
let budgetOwnerUsername = Cypress.env('budgetowner_username');
let budgetOwnerPassword = Cypress.env('budgetowner_password');
@ -733,7 +880,7 @@ describe('Other Fees Path - With Files', () => {
budgetOwnerUsername,
budgetOwnerPassword,
processInstanceId,
'Task: Reminder: Request Additional Budget',
'Task: Reminder: Check Existing Budget',
"approve"
);

File diff suppressed because it is too large Load Diff

View File

@ -7,19 +7,18 @@
"@babel/plugin-transform-react-jsx": "^7.18.6",
"@babel/preset-react": "^7.18.6",
"@carbon/icons-react": "^11.10.0",
"@carbon/react": "^1.16.0",
"@carbon/react": "^1.27.0",
"@carbon/styles": "^1.16.0",
"@casl/ability": "^6.3.2",
"@casl/react": "^3.1.0",
"@ginkgo-bioworks/react-json-schema-form-builder": "^2.9.0",
"@microsoft/fetch-event-source": "^2.0.1",
"@monaco-editor/react": "^4.4.5",
"@mui/material": "^5.10.14",
"@react-icons/all-files": "^4.1.0",
"@rjsf/core": "*",
"@rjsf/mui": "^5.0.0-beta.13",
"@rjsf/utils": "^5.0.0-beta.13",
"@rjsf/validator-ajv8": "^5.0.0-beta.16",
"@rjsf/core": "5.0.0-beta.20",
"@rjsf/mui": "5.0.0-beta.20",
"@rjsf/utils": "5.0.0-beta.20",
"@rjsf/validator-ajv8": "5.0.0-beta.20",
"@tanstack/react-table": "^8.2.2",
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.3.0",
@ -55,24 +54,17 @@
"react-icons": "^4.4.0",
"react-jsonschema-form": "^1.8.1",
"react-router": "^6.3.0",
"react-router-dom": "^6.3.0",
"react-router-dom": "6.3.0",
"react-scripts": "^5.0.1",
"serve": "^14.0.0",
"timepicker": "^1.13.18",
"typescript": "^4.7.4",
"use-debounce": "^9.0.4",
"web-vitals": "^3.0.2"
},
"overrides": {
"postcss-preset-env": {
"autoprefixer": "10.4.5"
},
"@ginkgo-bioworks/react-json-schema-form-builder": {
"react": "^18.2.0",
"bootstrap": "^5.2.0-beta1"
},
"@carbon/react": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
}
},
"scripts": {

View File

@ -0,0 +1,63 @@
// @ts-ignore
import { Filter } from '@carbon/icons-react';
import {
Button,
Grid,
Column,
// @ts-ignore
} from '@carbon/react';
type OwnProps = {
showFilterOptions: boolean;
setShowFilterOptions: Function;
filterOptions: Function;
filtersEnabled?: boolean;
reportSearchComponent?: Function | null;
};
export default function Filters({
showFilterOptions,
setShowFilterOptions,
filterOptions,
reportSearchComponent = null,
filtersEnabled = true,
}: OwnProps) {
const toggleShowFilterOptions = () => {
setShowFilterOptions(!showFilterOptions);
};
if (filtersEnabled) {
let reportSearchSection = null;
if (reportSearchComponent) {
reportSearchSection = (
<Column sm={2} md={4} lg={7}>
{reportSearchComponent()}
</Column>
);
}
return (
<>
<Grid fullWidth>
{reportSearchSection}
<Column
className="filterIcon"
sm={{ span: 1, offset: 3 }}
md={{ span: 1, offset: 7 }}
lg={{ span: 1, offset: 15 }}
>
<Button
data-qa="filter-section-expand-toggle"
renderIcon={Filter}
iconDescription="Filter Options"
hasIconOnly
size="lg"
onClick={toggleShowFilterOptions}
/>
</Column>
</Grid>
{filterOptions()}
</>
);
}
return null;
}

View File

@ -2,7 +2,7 @@ import { useEffect, useMemo, useRef, useState } from 'react';
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
// @ts-ignore
import { Filter, Close, AddAlt } from '@carbon/icons-react';
import { Close, AddAlt } from '@carbon/icons-react';
import {
Button,
ButtonSet,
@ -71,6 +71,7 @@ import { usePermissionFetcher } from '../hooks/PermissionService';
import { Can } from '../contexts/Can';
import TableCellWithTimeAgoInWords from './TableCellWithTimeAgoInWords';
import UserService from '../services/UserService';
import Filters from './Filters';
type OwnProps = {
filtersEnabled?: boolean;
@ -1396,12 +1397,22 @@ export default function ProcessInstanceListTable({
);
}
if (column.accessor === 'waiting_for') {
return <td>{getWaitingForTableCellComponent(row)}</td>;
return (
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
<td
onClick={navigateToProcessInstance}
onKeyDown={navigateToProcessInstance}
>
{getWaitingForTableCellComponent(row)}
</td>
);
}
if (column.accessor === 'updated_at_in_seconds') {
return (
<TableCellWithTimeAgoInWords
timeInSeconds={row.updated_at_in_seconds}
onClick={navigateToProcessInstance}
onKeyDown={navigateToProcessInstance}
/>
);
}
@ -1409,7 +1420,7 @@ export default function ProcessInstanceListTable({
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
<td
data-qa={`process-instance-show-link-${column.accessor}`}
onKeyDown={navigateToProcessModel}
onKeyDown={navigateToProcessInstance}
onClick={navigateToProcessInstance}
>
{formatter(row, value)}
@ -1495,10 +1506,6 @@ export default function ProcessInstanceListTable({
);
};
const toggleShowFilterOptions = () => {
setShowFilterOptions(!showFilterOptions);
};
const reportSearchComponent = () => {
if (showReports) {
const columns = [
@ -1518,37 +1525,6 @@ export default function ProcessInstanceListTable({
return null;
};
const filterComponent = () => {
if (!filtersEnabled) {
return null;
}
return (
<>
<Grid fullWidth>
<Column sm={2} md={4} lg={7}>
{reportSearchComponent()}
</Column>
<Column
className="filterIcon"
sm={{ span: 1, offset: 3 }}
md={{ span: 1, offset: 7 }}
lg={{ span: 1, offset: 15 }}
>
<Button
data-qa="filter-section-expand-toggle"
renderIcon={Filter}
iconDescription="Filter Options"
hasIconOnly
size="lg"
onClick={toggleShowFilterOptions}
/>
</Column>
</Grid>
{filterOptions()}
</>
);
};
if (pagination && (!textToShowIfEmpty || pagination.total > 0)) {
// eslint-disable-next-line prefer-const
let { page, perPage } = getPageInfoFromSearchParams(
@ -1588,7 +1564,13 @@ export default function ProcessInstanceListTable({
<>
{reportColumnForm()}
{processInstanceReportSaveTag()}
{filterComponent()}
<Filters
filterOptions={filterOptions}
showFilterOptions={showFilterOptions}
setShowFilterOptions={setShowFilterOptions}
reportSearchComponent={reportSearchComponent}
filtersEnabled={filtersEnabled}
/>
{resultsTable}
</>
);

View File

@ -4,13 +4,22 @@ import { convertSecondsToFormattedDateTime } from '../helpers';
type OwnProps = {
timeInSeconds: number;
onClick?: any;
onKeyDown?: any;
};
export default function TableCellWithTimeAgoInWords({
timeInSeconds,
onClick = null,
onKeyDown = null,
}: OwnProps) {
return (
<td title={convertSecondsToFormattedDateTime(timeInSeconds) || '-'}>
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
<td
title={convertSecondsToFormattedDateTime(timeInSeconds) || '-'}
onClick={onClick}
onKeyDown={onKeyDown}
>
{timeInSeconds ? TimeAgo.inWords(timeInSeconds) : '-'}
</td>
);

View File

@ -26,6 +26,17 @@ export const underscorizeString = (inputString: string) => {
return slugifyString(inputString).replace(/-/g, '_');
};
export const selectKeysFromSearchParams = (obj: any, keys: string[]) => {
const newSearchParams: { [key: string]: string } = {};
keys.forEach((key: string) => {
const value = obj.get(key);
if (value) {
newSearchParams[key] = value;
}
});
return newSearchParams;
};
export const capitalizeFirstLetter = (string: any) => {
return string.charAt(0).toUpperCase() + string.slice(1);
};

View File

@ -1,16 +1,35 @@
import { useEffect, useState } from 'react';
// @ts-ignore
import { Table, Tabs, TabList, Tab } from '@carbon/react';
import { Link, useParams, useSearchParams } from 'react-router-dom';
import {
Table,
Tabs,
TabList,
Tab,
Grid,
Column,
ButtonSet,
Button,
TextInput,
ComboBox,
// @ts-ignore
} from '@carbon/react';
import {
createSearchParams,
Link,
useParams,
useSearchParams,
} from 'react-router-dom';
import { useDebouncedCallback } from 'use-debounce';
import PaginationForTable from '../components/PaginationForTable';
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
import {
getPageInfoFromSearchParams,
convertSecondsToFormattedDateTime,
selectKeysFromSearchParams,
} from '../helpers';
import HttpService from '../services/HttpService';
import { useUriListForPermissions } from '../hooks/UriListForPermissions';
import { ProcessInstanceLogEntry } from '../interfaces';
import Filters from '../components/Filters';
type OwnProps = {
variant: string;
@ -21,14 +40,42 @@ export default function ProcessInstanceLogList({ variant }: OwnProps) {
const [searchParams, setSearchParams] = useSearchParams();
const [processInstanceLogs, setProcessInstanceLogs] = useState([]);
const [pagination, setPagination] = useState(null);
const [taskName, setTaskName] = useState<string>('');
const [taskIdentifier, setTaskIdentifier] = useState<string>('');
const [taskTypes, setTaskTypes] = useState<string[]>([]);
const [eventTypes, setEventTypes] = useState<string[]>([]);
const { targetUris } = useUriListForPermissions();
const isDetailedView = searchParams.get('detailed') === 'true';
const taskNameHeader = isDetailedView ? 'Task Name' : 'Milestone';
const [showFilterOptions, setShowFilterOptions] = useState<boolean>(false);
let processInstanceShowPageBaseUrl = `/admin/process-instances/for-me/${params.process_model_id}`;
if (variant === 'all') {
processInstanceShowPageBaseUrl = `/admin/process-instances/${params.process_model_id}`;
}
const updateSearchParams = (value: string, key: string) => {
if (value) {
searchParams.set(key, value);
} else {
searchParams.delete(key);
}
setSearchParams(searchParams);
};
const addDebouncedSearchParams = useDebouncedCallback(
(value: string, key: string) => {
updateSearchParams(value, key);
},
// delay in ms
1000
);
useEffect(() => {
// Clear out any previous results to avoid a "flicker" effect where columns
// are updated above the incorrect data.
@ -39,11 +86,41 @@ export default function ProcessInstanceLogList({ variant }: OwnProps) {
setProcessInstanceLogs(result.results);
setPagination(result.pagination);
};
const { page, perPage } = getPageInfoFromSearchParams(searchParams);
const searchParamsToInclude = [
'detailed',
'page',
'per_page',
'bpmn_name',
'bpmn_identifier',
'task_type',
'event_type',
];
const pickedSearchParams = selectKeysFromSearchParams(
searchParams,
searchParamsToInclude
);
if ('bpmn_name' in pickedSearchParams) {
setTaskName(pickedSearchParams.bpmn_name);
}
if ('bpmn_identifier' in pickedSearchParams) {
setTaskIdentifier(pickedSearchParams.bpmn_identifier);
}
HttpService.makeCallToBackend({
path: `${targetUris.processInstanceLogListPath}?per_page=${perPage}&page=${page}&detailed=${isDetailedView}`,
path: `${targetUris.processInstanceLogListPath}?${createSearchParams(
pickedSearchParams
)}`,
successCallback: setProcessInstanceLogListFromResult,
});
HttpService.makeCallToBackend({
path: `/v1.0/logs/types`,
successCallback: (result: any) => {
setTaskTypes(result.task_types);
setEventTypes(result.event_types);
},
});
}, [
searchParams,
params,
@ -85,6 +162,7 @@ export default function ProcessInstanceLogList({ variant }: OwnProps) {
if (isDetailedView) {
tableRow.push(
<>
<td>{logEntry.task_definition_identifier}</td>
<td>{logEntry.bpmn_task_type}</td>
<td>{logEntry.event_type}</td>
<td>
@ -130,13 +208,13 @@ export default function ProcessInstanceLogList({ variant }: OwnProps) {
<>
<th>Id</th>
<th>Bpmn Process</th>
<th>Task Name</th>
<th>{taskNameHeader}</th>
</>
);
} else {
tableHeaders.push(
<>
<th>Event</th>
<th>{taskNameHeader}</th>
<th>Bpmn Process</th>
</>
);
@ -144,8 +222,9 @@ export default function ProcessInstanceLogList({ variant }: OwnProps) {
if (isDetailedView) {
tableHeaders.push(
<>
<th>Task Identifier</th>
<th>Task Type</th>
<th>Event</th>
<th>Event Type</th>
<th>User</th>
</>
);
@ -160,60 +239,185 @@ export default function ProcessInstanceLogList({ variant }: OwnProps) {
</Table>
);
};
const selectedTabIndex = isDetailedView ? 1 : 0;
if (pagination) {
const { page, perPage } = getPageInfoFromSearchParams(searchParams);
const resetFilters = () => {
setTaskIdentifier('');
setTaskName('');
['bpmn_name', 'bpmn_identifier', 'task_type', 'event_type'].forEach(
(value: string) => searchParams.delete(value)
);
setSearchParams(searchParams);
};
const shouldFilterStringItem = (options: any) => {
const stringItem = options.item;
let { inputValue } = options;
if (!inputValue) {
inputValue = '';
}
return stringItem.toLowerCase().includes(inputValue.toLowerCase());
};
const filterOptions = () => {
if (!showFilterOptions) {
return null;
}
const filterElements = [];
filterElements.push(
<Column md={4}>
<TextInput
id="task-name-filter"
labelText={taskNameHeader}
value={taskName}
onChange={(event: any) => {
const newValue = event.target.value;
setTaskName(newValue);
addDebouncedSearchParams(newValue, 'bpmn_name');
}}
/>
</Column>
);
if (isDetailedView) {
filterElements.push(
<>
<Column md={4}>
<TextInput
id="task-identifier-filter"
labelText="Task Identifier"
value={taskIdentifier}
onChange={(event: any) => {
const newValue = event.target.value;
setTaskIdentifier(newValue);
addDebouncedSearchParams(newValue, 'bpmn_identifier');
}}
/>
</Column>
<Column md={4}>
<ComboBox
onChange={(value: any) => {
updateSearchParams(value.selectedItem, 'task_type');
}}
id="task-type-select"
data-qa="task-type-select"
items={taskTypes}
itemToString={(value: string) => {
return value;
}}
shouldFilterItem={shouldFilterStringItem}
placeholder="Choose a process model"
titleText="Task Type"
selectedItem={searchParams.get('task_type')}
/>
</Column>
<Column md={4}>
<ComboBox
onChange={(value: any) => {
updateSearchParams(value.selectedItem, 'event_type');
}}
id="event-type-select"
data-qa="event-type-select"
items={eventTypes}
itemToString={(value: string) => {
return value;
}}
shouldFilterItem={shouldFilterStringItem}
placeholder="Choose a process model"
titleText="Event Type"
selectedItem={searchParams.get('event_type')}
/>
</Column>
</>
);
}
return (
<>
<ProcessBreadcrumb
hotCrumbs={[
['Process Groups', '/admin'],
{
entityToExplode: params.process_model_id || '',
entityType: 'process-model-id',
linkLastItem: true,
},
[
`Process Instance: ${params.process_instance_id}`,
`${processInstanceShowPageBaseUrl}/${params.process_instance_id}`,
],
['Logs'],
]}
/>
<Tabs selectedIndex={selectedTabIndex}>
<TabList aria-label="List of tabs">
<Tab
title="Only show a subset of the logs, and show fewer columns"
data-qa="process-instance-log-simple"
onClick={() => {
searchParams.set('detailed', 'false');
setSearchParams(searchParams);
}}
>
Milestones
</Tab>
<Tab
title="Show all logs for this process instance, and show extra columns that may be useful for debugging"
data-qa="process-instance-log-detailed"
onClick={() => {
searchParams.set('detailed', 'true');
setSearchParams(searchParams);
}}
>
Events
</Tab>
</TabList>
</Tabs>
<br />
<PaginationForTable
page={page}
perPage={perPage}
pagination={pagination}
tableToDisplay={buildTable()}
/>
<Grid fullWidth className="with-bottom-margin">
{filterElements}
</Grid>
<Grid fullWidth className="with-bottom-margin">
<Column sm={4} md={4} lg={8}>
<ButtonSet>
<Button
kind=""
className="button-white-background narrow-button"
onClick={resetFilters}
>
Reset
</Button>
</ButtonSet>
</Column>
</Grid>
</>
);
}
return null;
};
const tabs = () => {
const selectedTabIndex = isDetailedView ? 1 : 0;
return (
<Tabs selectedIndex={selectedTabIndex}>
<TabList aria-label="List of tabs">
<Tab
title="Only show a subset of the logs, and show fewer columns"
data-qa="process-instance-log-simple"
onClick={() => {
searchParams.set('detailed', 'false');
setSearchParams(searchParams);
}}
>
Milestones
</Tab>
<Tab
title="Show all logs for this process instance, and show extra columns that may be useful for debugging"
data-qa="process-instance-log-detailed"
onClick={() => {
searchParams.set('detailed', 'true');
setSearchParams(searchParams);
}}
>
Events
</Tab>
</TabList>
</Tabs>
);
};
const { page, perPage } = getPageInfoFromSearchParams(searchParams);
return (
<>
<ProcessBreadcrumb
hotCrumbs={[
['Process Groups', '/admin'],
{
entityToExplode: params.process_model_id || '',
entityType: 'process-model-id',
linkLastItem: true,
},
[
`Process Instance: ${params.process_instance_id}`,
`${processInstanceShowPageBaseUrl}/${params.process_instance_id}`,
],
['Logs'],
]}
/>
{tabs()}
<Filters
filterOptions={filterOptions}
showFilterOptions={showFilterOptions}
setShowFilterOptions={setShowFilterOptions}
filtersEnabled
/>
<br />
<PaginationForTable
page={page}
perPage={perPage}
pagination={pagination}
tableToDisplay={buildTable()}
/>
</>
);
}

View File

@ -11,7 +11,6 @@ import {
ComboBox,
Button,
ButtonSet,
// @ts-ignore
} from '@carbon/react';
import MDEditor from '@uiw/react-md-editor';

View File

@ -54,13 +54,6 @@ export default function BaseInputTemplate<
...getInputProps<T, S, F>(schema, type, options),
};
let inputValue;
if (inputProps.type === 'number' || inputProps.type === 'integer') {
inputValue = value || value === 0 ? value : '';
} else {
inputValue = value == null ? '' : value;
}
const _onChange = useCallback(
({ target: { value } }: React.ChangeEvent<HTMLInputElement>) =>
onChange(value === '' ? options.emptyValue : value),
@ -143,7 +136,6 @@ export default function BaseInputTemplate<
<>
<TextInput
id={id}
name={id}
className="text-input"
helperText={helperText}
invalid={invalid}

View File

@ -1,4 +1,3 @@
// @ts-ignore
import { Select, SelectItem } from '@carbon/react';
import { WidgetProps, processSelectValue } from '@rjsf/utils';

View File

@ -0,0 +1,2 @@
// carbon/react is not very typescript safe so ignore it
declare module '@carbon/react';