From fcccbdc845f2d466ef7cf8a793afa7c9d666d38a Mon Sep 17 00:00:00 2001 From: jasquat Date: Mon, 14 Nov 2022 12:06:39 -0500 Subject: [PATCH 1/8] added remaining task tables w/ burnettk --- .../src/spiffworkflow_backend/api.yml | 33 ++++- .../routes/process_api_blueprint.py | 49 +++++- .../components/TasksForMyOpenProcesses.tsx | 2 +- ...rtedByOthers.tsx => TasksWaitingForMe.tsx} | 6 +- .../components/TasksWaitingForMyGroups.tsx | 139 ++++++++++++++++++ .../src/routes/GroupedTasks.tsx | 7 +- 6 files changed, 221 insertions(+), 15 deletions(-) rename spiffworkflow-frontend/src/components/{MyTasksForProcessesStartedByOthers.tsx => TasksWaitingForMe.tsx} (95%) create mode 100644 spiffworkflow-frontend/src/components/TasksWaitingForMyGroups.tsx diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/api.yml b/spiffworkflow-backend/src/spiffworkflow_backend/api.yml index a87d5a2ff..f2135cb38 100755 --- a/spiffworkflow-backend/src/spiffworkflow_backend/api.yml +++ b/spiffworkflow-backend/src/spiffworkflow_backend/api.yml @@ -901,7 +901,7 @@ paths: items: $ref: "#/components/schemas/Task" - /tasks/for-processes-started-by-others: + /tasks/for-me: parameters: - name: page in: query @@ -918,7 +918,36 @@ paths: get: tags: - Process Instances - operationId: spiffworkflow_backend.routes.process_api_blueprint.task_list_for_processes_started_by_others + operationId: spiffworkflow_backend.routes.process_api_blueprint.task_list_for_me + summary: returns the list of tasks for given user's open process instances + responses: + "200": + description: list of tasks + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Task" + + /tasks/for-my-groups: + parameters: + - name: page + in: query + required: false + description: The page number to return. Defaults to page 1. + schema: + type: integer + - name: per_page + in: query + required: false + description: The page number to return. Defaults to page 1. + schema: + type: integer + get: + tags: + - Process Instances + operationId: spiffworkflow_backend.routes.process_api_blueprint.task_list_for_my_groups summary: returns the list of tasks for given user's open process instances responses: "200": diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py index 98f8abce8..d3f5c274c 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py @@ -1029,7 +1029,17 @@ def task_list_for_my_open_processes( return get_tasks(page=page, per_page=per_page) -def task_list_for_processes_started_by_others( +def task_list_for_me(page: int = 1, per_page: int = 100) -> flask.wrappers.Response: + """Task_list_for_processes_started_by_others.""" + return get_tasks( + processes_started_by_user=False, + has_lane_assignment_id=False, + page=page, + per_page=per_page, + ) + + +def task_list_for_my_groups( page: int = 1, per_page: int = 100 ) -> flask.wrappers.Response: """Task_list_for_processes_started_by_others.""" @@ -1037,14 +1047,21 @@ def task_list_for_processes_started_by_others( def get_tasks( - processes_started_by_user: bool = True, page: int = 1, per_page: int = 100 + processes_started_by_user: bool = True, + has_lane_assignment_id: bool = True, + page: int = 1, + per_page: int = 100, ) -> flask.wrappers.Response: """Get_tasks.""" user_id = g.user.id + + # use distinct to ensure we only get one row per active task otherwise + # we can get back multiple for the same active task row which throws off + # pagination later on + # https://stackoverflow.com/q/34582014/6090676 active_tasks_query = ( - ActiveTaskModel.query.outerjoin( - GroupModel, GroupModel.id == ActiveTaskModel.lane_assignment_id - ) + ActiveTaskModel.query.distinct() + .outerjoin(GroupModel, GroupModel.id == ActiveTaskModel.lane_assignment_id) .join(ProcessInstanceModel) .join(UserModel, UserModel.id == ProcessInstanceModel.process_initiator_id) ) @@ -1052,11 +1069,29 @@ def get_tasks( if processes_started_by_user: active_tasks_query = active_tasks_query.filter( ProcessInstanceModel.process_initiator_id == user_id - ).outerjoin(ActiveTaskUserModel, and_(ActiveTaskUserModel.user_id == user_id)) + ).outerjoin( + ActiveTaskUserModel, + and_( + ActiveTaskUserModel.user_id == user_id, + ActiveTaskModel.id == ActiveTaskUserModel.active_task_id, + ), + ) else: active_tasks_query = active_tasks_query.filter( ProcessInstanceModel.process_initiator_id != user_id - ).join(ActiveTaskUserModel, and_(ActiveTaskUserModel.user_id == user_id)) + ).join( + ActiveTaskUserModel, + and_( + ActiveTaskUserModel.user_id == user_id, + ActiveTaskModel.id == ActiveTaskUserModel.active_task_id, + ), + ) + if has_lane_assignment_id: + active_tasks_query = active_tasks_query.filter( + ActiveTaskModel.lane_assignment_id.is_not(None) # type: ignore + ) + else: + active_tasks_query = active_tasks_query.filter(ActiveTaskModel.lane_assignment_id.is_(None)) # type: ignore active_tasks = active_tasks_query.add_columns( ProcessInstanceModel.process_model_identifier, diff --git a/spiffworkflow-frontend/src/components/TasksForMyOpenProcesses.tsx b/spiffworkflow-frontend/src/components/TasksForMyOpenProcesses.tsx index 8a2f0bddb..5c3a0ec67 100644 --- a/spiffworkflow-frontend/src/components/TasksForMyOpenProcesses.tsx +++ b/spiffworkflow-frontend/src/components/TasksForMyOpenProcesses.tsx @@ -124,7 +124,7 @@ export default function MyOpenProcesses() { perPageOptions={[2, PER_PAGE_FOR_TASKS_ON_HOME_PAGE, 25]} pagination={pagination} tableToDisplay={buildTable()} - path="/tasks/for-my-open-processes" + path="/tasks/grouped" /> ); diff --git a/spiffworkflow-frontend/src/components/MyTasksForProcessesStartedByOthers.tsx b/spiffworkflow-frontend/src/components/TasksWaitingForMe.tsx similarity index 95% rename from spiffworkflow-frontend/src/components/MyTasksForProcessesStartedByOthers.tsx rename to spiffworkflow-frontend/src/components/TasksWaitingForMe.tsx index d1fafd395..a24eeb8c2 100644 --- a/spiffworkflow-frontend/src/components/MyTasksForProcessesStartedByOthers.tsx +++ b/spiffworkflow-frontend/src/components/TasksWaitingForMe.tsx @@ -13,7 +13,7 @@ import { PaginationObject } from '../interfaces'; const PER_PAGE_FOR_TASKS_ON_HOME_PAGE = 5; -export default function MyTasksForProcessesStartedByOthers() { +export default function TasksWaitingForMe() { const [searchParams] = useSearchParams(); const [tasks, setTasks] = useState([]); const [pagination, setPagination] = useState(null); @@ -28,7 +28,7 @@ export default function MyTasksForProcessesStartedByOthers() { setPagination(result.pagination); }; HttpService.makeCallToBackend({ - path: `/tasks/for-processes-started-by-others?per_page=${perPage}&page=${page}`, + path: `/tasks/for-me?per_page=${perPage}&page=${page}`, successCallback: setTasksFromResult, }); }, [searchParams]); @@ -126,7 +126,7 @@ export default function MyTasksForProcessesStartedByOthers() { perPageOptions={[2, PER_PAGE_FOR_TASKS_ON_HOME_PAGE, 25]} pagination={pagination} tableToDisplay={buildTable()} - path="/tasks/for-my-open-processes" + path="/tasks/grouped" /> ); diff --git a/spiffworkflow-frontend/src/components/TasksWaitingForMyGroups.tsx b/spiffworkflow-frontend/src/components/TasksWaitingForMyGroups.tsx new file mode 100644 index 000000000..6190e0e8d --- /dev/null +++ b/spiffworkflow-frontend/src/components/TasksWaitingForMyGroups.tsx @@ -0,0 +1,139 @@ +import { useEffect, useState } from 'react'; +// @ts-ignore +import { Button, Table } from '@carbon/react'; +import { Link, useSearchParams } from 'react-router-dom'; +import PaginationForTable from './PaginationForTable'; +import { + convertSecondsToFormattedDateTime, + getPageInfoFromSearchParams, + modifyProcessModelPath, +} from '../helpers'; +import HttpService from '../services/HttpService'; +import { PaginationObject } from '../interfaces'; + +const PER_PAGE_FOR_TASKS_ON_HOME_PAGE = 5; + +export default function TasksForWaitingForMyGroups() { + const [searchParams] = useSearchParams(); + const [tasks, setTasks] = useState([]); + const [pagination, setPagination] = useState(null); + + useEffect(() => { + const { page, perPage } = getPageInfoFromSearchParams( + searchParams, + PER_PAGE_FOR_TASKS_ON_HOME_PAGE + ); + const setTasksFromResult = (result: any) => { + setTasks(result.results); + setPagination(result.pagination); + }; + HttpService.makeCallToBackend({ + path: `/tasks/for-my-groups?per_page=${perPage}&page=${page}`, + successCallback: setTasksFromResult, + }); + }, [searchParams]); + + const buildTable = () => { + const rows = tasks.map((row) => { + const rowToUse = row as any; + const taskUrl = `/tasks/${rowToUse.process_instance_id}/${rowToUse.task_id}`; + const modifiedProcessModelIdentifier = modifyProcessModelPath( + rowToUse.process_model_identifier + ); + return ( + + + + {rowToUse.process_model_display_name} + + + + + View {rowToUse.process_instance_id} + + + + {rowToUse.task_title} + + {rowToUse.username} + {rowToUse.process_instance_status} + {rowToUse.group_identifier || '-'} + + {convertSecondsToFormattedDateTime( + rowToUse.created_at_in_seconds + ) || '-'} + + + {convertSecondsToFormattedDateTime( + rowToUse.updated_at_in_seconds + ) || '-'} + + + + + + ); + }); + return ( + + + + + + + + + + + + + + + {rows} +
Process ModelProcess InstanceTask NameProcess Started ByProcess Instance StatusAssigned GroupProcess StartedProcess UpdatedActions
+ ); + }; + + const tasksComponent = () => { + if (pagination && pagination.total < 1) { + return null; + } + const { page, perPage } = getPageInfoFromSearchParams( + searchParams, + PER_PAGE_FOR_TASKS_ON_HOME_PAGE + ); + return ( + <> +

Tasks waiting for my groups

+ + + ); + }; + + if (pagination) { + return tasksComponent(); + } + return null; +} diff --git a/spiffworkflow-frontend/src/routes/GroupedTasks.tsx b/spiffworkflow-frontend/src/routes/GroupedTasks.tsx index 7312bc554..a08959c5e 100644 --- a/spiffworkflow-frontend/src/routes/GroupedTasks.tsx +++ b/spiffworkflow-frontend/src/routes/GroupedTasks.tsx @@ -1,12 +1,15 @@ -import MyTasksForProcessesStartedByOthers from '../components/MyTasksForProcessesStartedByOthers'; import TasksForMyOpenProcesses from '../components/TasksForMyOpenProcesses'; +import TasksWaitingForMe from '../components/TasksWaitingForMe'; +import TasksForWaitingForMyGroups from '../components/TasksWaitingForMyGroups'; export default function GroupedTasks() { return ( <>
- + +
+ ); } From 9ed04d63faaa786c92a034e93a2af8c97593422e Mon Sep 17 00:00:00 2001 From: jasquat Date: Mon, 14 Nov 2022 12:24:23 -0500 Subject: [PATCH 2/8] merged in main and resolved pyl issues w/ burnettk --- .../src/spiffworkflow_backend/models/file.py | 1 + .../routes/process_api_blueprint.py | 2 - .../services/custom_parser.py | 5 +- .../services/process_instance_processor.py | 17 +- .../services/spec_file_service.py | 165 +++++++++--------- .../helpers/example_data.py | 6 +- .../integration/test_process_api.py | 1 - .../unit/test_spec_file_service.py | 12 +- 8 files changed, 106 insertions(+), 103 deletions(-) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/models/file.py b/spiffworkflow-backend/src/spiffworkflow_backend/models/file.py index dbb092df7..c5fd75a2b 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/models/file.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/models/file.py @@ -82,6 +82,7 @@ class FileReference: correlations: dict start_messages: list + @dataclass(order=True) class File: """File.""" diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py index c061ecb5d..27d813a82 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py @@ -64,9 +64,7 @@ from spiffworkflow_backend.models.spiff_step_details import SpiffStepDetailsMode from spiffworkflow_backend.models.user import UserModel from spiffworkflow_backend.routes.user import verify_token from spiffworkflow_backend.services.authorization_service import AuthorizationService -from spiffworkflow_backend.services.custom_parser import MyCustomParser from spiffworkflow_backend.services.error_handling_service import ErrorHandlingService -from spiffworkflow_backend.services.file_system_service import FileSystemService from spiffworkflow_backend.services.git_service import GitService from spiffworkflow_backend.services.message_service import MessageService from spiffworkflow_backend.services.process_instance_processor import ( diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/custom_parser.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/custom_parser.py index 27a2ff029..c54c195fd 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/custom_parser.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/custom_parser.py @@ -1,5 +1,6 @@ -from SpiffWorkflow.dmn.parser.BpmnDmnParser import BpmnDmnParser -from SpiffWorkflow.spiff.parser.process import SpiffBpmnParser +"""Custom_parser.""" +from SpiffWorkflow.dmn.parser.BpmnDmnParser import BpmnDmnParser # type: ignore +from SpiffWorkflow.spiff.parser.process import SpiffBpmnParser # type: ignore class MyCustomParser(BpmnDmnParser): # type: ignore diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py index 6c472e2a6..91db99804 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py @@ -38,7 +38,6 @@ from SpiffWorkflow.dmn.parser.BpmnDmnParser import BpmnDmnParser # type: ignore from SpiffWorkflow.dmn.serializer.task_spec_converters import BusinessRuleTaskConverter # type: ignore from SpiffWorkflow.exceptions import WorkflowException # type: ignore from SpiffWorkflow.serializer.exceptions import MissingSpecError # type: ignore -from SpiffWorkflow.spiff.parser.process import SpiffBpmnParser # type: ignore from SpiffWorkflow.spiff.serializer.task_spec_converters import BoundaryEventConverter # type: ignore from SpiffWorkflow.spiff.serializer.task_spec_converters import ( CallActivityTaskConverter, @@ -95,9 +94,6 @@ from spiffworkflow_backend.services.custom_parser import MyCustomParser from spiffworkflow_backend.services.file_system_service import FileSystemService from spiffworkflow_backend.services.process_model_service import ProcessModelService from spiffworkflow_backend.services.service_task_service import ServiceTaskDelegate -from spiffworkflow_backend.services.spec_file_service import ( - ProcessModelFileNotFoundError, -) from spiffworkflow_backend.services.spec_file_service import SpecFileService from spiffworkflow_backend.services.user_service import UserService @@ -674,18 +670,19 @@ class ProcessInstanceProcessor: return parser @staticmethod - def backfill_missing_bpmn_process_id_lookup_records(bpmn_process_identifier: str) -> Optional[str]: - + def backfill_missing_bpmn_process_id_lookup_records( + bpmn_process_identifier: str, + ) -> Optional[str]: """Backfill_missing_bpmn_process_id_lookup_records.""" process_models = ProcessModelService().get_process_models() for process_model in process_models: - refs = SpecFileService.reference_map(SpecFileService.get_references_for_process(process_model)) + refs = SpecFileService.reference_map( + SpecFileService.get_references_for_process(process_model) + ) bpmn_process_identifiers = refs.keys() if bpmn_process_identifier in bpmn_process_identifiers: SpecFileService.update_process_cache(refs[bpmn_process_identifier]) - return FileSystemService.full_path_to_process_model_file( - process_model - ) + return FileSystemService.full_path_to_process_model_file(process_model) return None @staticmethod diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/spec_file_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/spec_file_service.py index a3c44a1ec..f43497bbb 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/spec_file_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/spec_file_service.py @@ -2,17 +2,10 @@ import os import shutil from datetime import datetime -from typing import Any, Type from typing import List from typing import Optional -from SpiffWorkflow.dmn.parser.BpmnDmnParser import BpmnDmnParser -from SpiffWorkflow.bpmn.parser.ProcessParser import ProcessParser -from flask_bpmn.api.api_error import ApiError from flask_bpmn.models.db import db -from lxml import etree # type: ignore -from lxml.etree import _Element # type: ignore -from lxml.etree import Element as EtreeElement from SpiffWorkflow.bpmn.parser.ValidationException import ValidationException # type: ignore from spiffworkflow_backend.models.bpmn_process_id_lookup import BpmnProcessIdLookup @@ -62,29 +55,29 @@ class SpecFileService(FileSystemService): @staticmethod def reference_map(references: list[FileReference]) -> dict[str, FileReference]: - """ Creates a dict with provided references organized by id. """ + """Creates a dict with provided references organized by id.""" ref_map = {} for ref in references: ref_map[ref.id] = ref return ref_map @staticmethod - def get_references(process_models: List[ProcessModelInfo]) -> list[FileReference]: - """Returns all references -- process_ids, and decision ids, across all process models provided""" - references = [] - for process_model in process_models: - references.extend(SpecFileService.get_references_for_process(process_model)) - - @staticmethod - def get_references_for_process(process_model_info: ProcessModelInfo) -> list[FileReference]: + def get_references_for_process( + process_model_info: ProcessModelInfo, + ) -> list[FileReference]: + """Get_references_for_process.""" files = SpecFileService.get_files(process_model_info) references = [] for file in files: - references.extend(SpecFileService.get_references_for_file(file, process_model_info)) + references.extend( + SpecFileService.get_references_for_file(file, process_model_info) + ) return references @staticmethod - def get_references_for_file(file: File, process_model_info: ProcessModelInfo) -> list[FileReference]: + def get_references_for_file( + file: File, process_model_info: ProcessModelInfo + ) -> list[FileReference]: """Uses spiffworkflow to parse BPMN and DMN files to determine how they can be externally referenced. Returns a list of Reference objects that contain the type of reference, the id, the name. @@ -117,16 +110,24 @@ class SpecFileService(FileSystemService): else: return references for sub_parser in sub_parsers: - if parser_type == 'process': + if parser_type == "process": has_lanes = sub_parser.has_lanes() executable = sub_parser.process_executable start_messages = sub_parser.start_messages() - references.append(FileReference( - id=sub_parser.get_id(), name=sub_parser.get_name(), type=parser_type, - file_name=file.name, file_path=file_path, has_lanes=has_lanes, - executable=executable, messages=messages, - correlations=correlations, start_messages=start_messages - )) + references.append( + FileReference( + id=sub_parser.get_id(), + name=sub_parser.get_name(), + type=parser_type, + file_name=file.name, + file_path=file_path, + has_lanes=has_lanes, + executable=executable, + messages=messages, + correlations=correlations, + start_messages=start_messages, + ) + ) return references @staticmethod @@ -138,7 +139,8 @@ class SpecFileService(FileSystemService): return SpecFileService.update_file(process_model_info, file_name, binary_data) @staticmethod - def update_file(process_model_info: ProcessModelInfo, file_name: str, binary_data: bytes + def update_file( + process_model_info: ProcessModelInfo, file_name: str, binary_data: bytes ) -> File: """Update_file.""" SpecFileService.assert_valid_file_name(file_name) @@ -150,26 +152,29 @@ class SpecFileService(FileSystemService): file = SpecFileService.to_file_object(file_name, file_path) if file.type == FileType.bpmn.value: - set_primary_file = False if ( process_model_info.primary_file_name is None or file_name == process_model_info.primary_file_name ): # If no primary process exists, make this primary process. - set_primary_file = True - references = SpecFileService.get_references_for_file(file, process_model_info) + references = SpecFileService.get_references_for_file( + file, process_model_info + ) for ref in references: if ref.type == "process": ProcessModelService().update_spec( - process_model_info, { + process_model_info, + { "primary_process_id": ref.id, "primary_file_name": file_name, "is_review": ref.has_lanes, - } + }, ) SpecFileService.update_process_cache(ref) SpecFileService.update_message_cache(ref) - SpecFileService.update_message_trigger_cache(ref, process_model_info) + SpecFileService.update_message_trigger_cache( + ref, process_model_info + ) SpecFileService.update_correlation_cache(ref) break @@ -226,13 +231,14 @@ class SpecFileService(FileSystemService): if os.path.exists(dir_path): shutil.rmtree(dir_path) - # fixme: Place all the caching stuff in a different service. - @staticmethod def update_process_cache(ref: FileReference) -> None: - process_id_lookup = BpmnProcessIdLookup.query.filter_by(bpmn_process_identifier=ref.id).first() + """Update_process_cache.""" + process_id_lookup = BpmnProcessIdLookup.query.filter_by( + bpmn_process_identifier=ref.id + ).first() if process_id_lookup is None: process_id_lookup = BpmnProcessIdLookup( bpmn_process_identifier=ref.id, @@ -253,74 +259,77 @@ class SpecFileService(FileSystemService): f"{process_id_lookup.bpmn_file_relative_path}. It cannot be reused." ) else: - process_id_lookup.bpmn_file_relative_path = ( - ref.file_path - ) + process_id_lookup.bpmn_file_relative_path = ref.file_path db.session.add(process_id_lookup) db.session.commit() - - @staticmethod def update_message_cache(ref: FileReference) -> None: """Assure we have a record in the database of all possible message ids and names.""" for message_model_identifier in ref.messages.keys(): - message_model = MessageModel.query.filter_by(identifier=message_model_identifier).first() + message_model = MessageModel.query.filter_by( + identifier=message_model_identifier + ).first() if message_model is None: message_model = MessageModel( - identifier=message_model_identifier, name=ref.messages[message_model_identifier] + identifier=message_model_identifier, + name=ref.messages[message_model_identifier], ) db.session.add(message_model) db.session.commit() @staticmethod - def update_message_trigger_cache(ref: FileReference, process_model_info: ProcessModelInfo) -> None: - """assure we know which messages can trigger the start of a process.""" + def update_message_trigger_cache( + ref: FileReference, process_model_info: ProcessModelInfo + ) -> None: + """Assure we know which messages can trigger the start of a process.""" for message_model_identifier in ref.start_messages: - message_model = MessageModel.query.filter_by( - identifier=message_model_identifier - ).first() - if message_model is None: - raise ValidationException( - f"Could not find message model with identifier '{message_model_identifier}'" - f"Required by a Start Event in : {ref.file_name}" - ) - message_triggerable_process_model = ( - MessageTriggerableProcessModel.query.filter_by( - message_model_id=message_model.id, - ).first() + message_model = MessageModel.query.filter_by( + identifier=message_model_identifier + ).first() + if message_model is None: + raise ValidationException( + f"Could not find message model with identifier '{message_model_identifier}'" + f"Required by a Start Event in : {ref.file_name}" ) + message_triggerable_process_model = ( + MessageTriggerableProcessModel.query.filter_by( + message_model_id=message_model.id, + ).first() + ) - if message_triggerable_process_model is None: - message_triggerable_process_model = ( - MessageTriggerableProcessModel( - message_model_id=message_model.id, - process_model_identifier=process_model_info.id, - process_group_identifier="process_group_identifier" - ) + if message_triggerable_process_model is None: + message_triggerable_process_model = MessageTriggerableProcessModel( + message_model_id=message_model.id, + process_model_identifier=process_model_info.id, + process_group_identifier="process_group_identifier", + ) + db.session.add(message_triggerable_process_model) + db.session.commit() + else: + if ( + message_triggerable_process_model.process_model_identifier + != process_model_info.id + # or message_triggerable_process_model.process_group_identifier + # != process_model_info.process_group_id + ): + raise ValidationException( + f"Message model is already used to start process model {process_model_info.id}" ) - db.session.add(message_triggerable_process_model) - db.session.commit() - else: - if ( - message_triggerable_process_model.process_model_identifier - != process_model_info.id - # or message_triggerable_process_model.process_group_identifier - # != process_model_info.process_group_id - ): - raise ValidationException( - f"Message model is already used to start process model {process_model_info.id}" - ) @staticmethod def update_correlation_cache(ref: FileReference) -> None: + """Update_correlation_cache.""" for correlation_identifier in ref.correlations.keys(): - correlation_property_retrieval_expressions = \ - ref.correlations[correlation_identifier]['retrieval_expressions'] + correlation_property_retrieval_expressions = ref.correlations[ + correlation_identifier + ]["retrieval_expressions"] for cpre in correlation_property_retrieval_expressions: message_model_identifier = cpre["messageRef"] - message_model = MessageModel.query.filter_by(identifier=message_model_identifier).first() + message_model = MessageModel.query.filter_by( + identifier=message_model_identifier + ).first() if message_model is None: raise ValidationException( f"Could not find message model with identifier '{message_model_identifier}'" diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/helpers/example_data.py b/spiffworkflow-backend/tests/spiffworkflow_backend/helpers/example_data.py index b5261a26b..ba9e1e98c 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/helpers/example_data.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/helpers/example_data.py @@ -25,11 +25,9 @@ class ExampleDataLoader: """Assumes that process_model_source_directory exists in static/bpmn and contains bpmn_file_name. further assumes that bpmn_file_name is the primary file for the process model. - if bpmn_file_name is None we load all files in process_model_source_directory, otherwise, we only load bpmn_file_name """ - if process_model_source_directory is None: raise Exception("You must include `process_model_source_directory`.") @@ -85,7 +83,9 @@ class ExampleDataLoader: process_model_info=spec, file_name=filename, binary_data=data ) if is_primary: - references = SpecFileService.get_references_for_file(file_info, spec) + references = SpecFileService.get_references_for_file( + file_info, spec + ) spec.primary_process_id = references[0].id spec.primary_file_name = filename ProcessModelService().save_process_model(spec) diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py b/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py index 5960500f8..7623537d2 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py @@ -2325,4 +2325,3 @@ class TestProcessApi(BaseTest): ) print("test_script_unit_test_run") - diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_spec_file_service.py b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_spec_file_service.py index 004d98593..f776a13ae 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_spec_file_service.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_spec_file_service.py @@ -4,9 +4,8 @@ import os import pytest from flask import Flask from flask.testing import FlaskClient -from flask_bpmn.api.api_error import ApiError from flask_bpmn.models.db import db -from SpiffWorkflow.dmn.parser.BpmnDmnParser import BpmnDmnParser # type: ignore +from SpiffWorkflow.bpmn.parser.ValidationException import ValidationException # type: ignore from tests.spiffworkflow_backend.helpers.base_test import BaseTest from tests.spiffworkflow_backend.helpers.test_data import load_test_spec @@ -14,9 +13,7 @@ from spiffworkflow_backend.models.bpmn_process_id_lookup import BpmnProcessIdLoo from spiffworkflow_backend.models.user import UserModel from spiffworkflow_backend.services.process_model_service import ProcessModelService from spiffworkflow_backend.services.spec_file_service import SpecFileService -from tests.spiffworkflow_backend.helpers.base_test import BaseTest -from tests.spiffworkflow_backend.helpers.test_data import load_test_spec -from SpiffWorkflow.bpmn.parser.ValidationException import ValidationException + class TestSpecFileService(BaseTest): """TestSpecFileService.""" @@ -86,8 +83,9 @@ class TestSpecFileService(BaseTest): process_model_source_directory="call_activity_duplicate", bpmn_file_name="call_activity_nested_duplicate", ) - assert f"Process id ({bpmn_process_identifier}) has already been used" in str( - exception.value + assert ( + f"Process id ({bpmn_process_identifier}) has already been used" + in str(exception.value) ) def test_updates_relative_file_path_when_appropriate( From 71a277e37b17a064844b215972542dfe011dc219 Mon Sep 17 00:00:00 2001 From: jasquat Date: Mon, 14 Nov 2022 13:55:22 -0500 Subject: [PATCH 3/8] some fixes for windows and python 3.9 w/ burnettk --- .../src/spiffworkflow_backend/models/message_instance.py | 2 +- .../spiffworkflow_backend/services/file_system_service.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/models/message_instance.py b/spiffworkflow-backend/src/spiffworkflow_backend/models/message_instance.py index fd454fd6a..2559a6352 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/models/message_instance.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/models/message_instance.py @@ -59,7 +59,7 @@ class MessageInstanceModel(SpiffworkflowBaseDBModel): updated_at_in_seconds: int = db.Column(db.Integer) created_at_in_seconds: int = db.Column(db.Integer) - message_correlations: dict | None = None + message_correlations: Optional[dict] = None @validates("message_type") def validate_message_type(self, key: str, value: Any) -> Any: diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/file_system_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/file_system_service.py index 15327400b..36570aded 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/file_system_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/file_system_service.py @@ -60,8 +60,9 @@ class FileSystemService: @staticmethod def workflow_path(spec: ProcessModelInfo) -> str: """Workflow_path.""" - process_model_path = os.path.join(FileSystemService.root_path(), spec.id) - # process_group_path = FileSystemService.process_group_path_for_spec(spec) + process_model_path = os.path.join( + FileSystemService.root_path(), spec.id_for_file_path() + ) return process_model_path @staticmethod From 2cd80594d85bba454d51b333413a21f3f8609932 Mon Sep 17 00:00:00 2001 From: jasquat Date: Mon, 14 Nov 2022 14:25:27 -0500 Subject: [PATCH 4/8] use the same file path method in spec file service w/ burnettk --- .github/workflows/backend_tests.yml | 2 + .../services/spec_file_service.py | 41 ++++++++----------- 2 files changed, 20 insertions(+), 23 deletions(-) diff --git a/.github/workflows/backend_tests.yml b/.github/workflows/backend_tests.yml index ef70a5c30..d80dd8f3e 100644 --- a/.github/workflows/backend_tests.yml +++ b/.github/workflows/backend_tests.yml @@ -279,6 +279,8 @@ jobs: # if: ${{ github.event_name != 'pull_request' }} # so just skip everything but main if: github.ref_name == 'main' + with: + projectBaseDir: spiffworkflow-backend env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/spec_file_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/spec_file_service.py index f43497bbb..63eafe98a 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/spec_file_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/spec_file_service.py @@ -87,8 +87,10 @@ class SpecFileService(FileSystemService): type = {str} 'process' / 'decision' """ references: list[FileReference] = [] - full_file_path = SpecFileService.file_path(process_model_info, file.name) - file_path = os.path.join(process_model_info.id, file.name) + full_file_path = SpecFileService.full_file_path(process_model_info, file.name) + relative_file_path = os.path.join( + process_model_info.id_for_file_path(), file.name + ) parser = MyCustomParser() parser_type = None sub_parser = None @@ -120,7 +122,7 @@ class SpecFileService(FileSystemService): name=sub_parser.get_name(), type=parser_type, file_name=file.name, - file_path=file_path, + file_path=relative_file_path, has_lanes=has_lanes, executable=executable, messages=messages, @@ -144,12 +146,9 @@ class SpecFileService(FileSystemService): ) -> File: """Update_file.""" SpecFileService.assert_valid_file_name(file_name) - # file_path = SpecFileService.file_path(process_model_info, file_name) - file_path = os.path.join( - FileSystemService.root_path(), process_model_info.id, file_name - ) - SpecFileService.write_file_data_to_system(file_path, binary_data) - file = SpecFileService.to_file_object(file_name, file_path) + full_file_path = SpecFileService.full_file_path(process_model_info, file_name) + SpecFileService.write_file_data_to_system(full_file_path, binary_data) + file = SpecFileService.to_file_object(file_name, full_file_path) if file.type == FileType.bpmn.value: if ( @@ -183,34 +182,31 @@ class SpecFileService(FileSystemService): @staticmethod def get_data(process_model_info: ProcessModelInfo, file_name: str) -> bytes: """Get_data.""" - # file_path = SpecFileService.file_path(process_model_info, file_name) - file_path = os.path.join( - FileSystemService.root_path(), process_model_info.id, file_name - ) - if not os.path.exists(file_path): + full_file_path = SpecFileService.full_file_path(process_model_info, file_name) + if not os.path.exists(full_file_path): raise ProcessModelFileNotFoundError( f"No file found with name {file_name} in {process_model_info.display_name}" ) - with open(file_path, "rb") as f_handle: + with open(full_file_path, "rb") as f_handle: spec_file_data = f_handle.read() return spec_file_data @staticmethod - def file_path(spec: ProcessModelInfo, file_name: str) -> str: + def full_file_path(spec: ProcessModelInfo, file_name: str) -> str: """File_path.""" return os.path.join(SpecFileService.workflow_path(spec), file_name) @staticmethod def last_modified(spec: ProcessModelInfo, file_name: str) -> datetime: """Last_modified.""" - path = SpecFileService.file_path(spec, file_name) - return FileSystemService._last_modified(path) + full_file_path = SpecFileService.full_file_path(spec, file_name) + return FileSystemService._last_modified(full_file_path) @staticmethod def timestamp(spec: ProcessModelInfo, file_name: str) -> float: """Timestamp.""" - path = SpecFileService.file_path(spec, file_name) - return FileSystemService._timestamp(path) + full_file_path = SpecFileService.full_file_path(spec, file_name) + return FileSystemService._timestamp(full_file_path) @staticmethod def delete_file(spec: ProcessModelInfo, file_name: str) -> None: @@ -220,9 +216,8 @@ class SpecFileService(FileSystemService): # for lf in lookup_files: # session.query(LookupDataModel).filter_by(lookup_file_model_id=lf.id).delete() # session.query(LookupFileModel).filter_by(id=lf.id).delete() - # file_path = SpecFileService.file_path(spec, file_name) - file_path = os.path.join(FileSystemService.root_path(), spec.id, file_name) - os.remove(file_path) + full_file_path = SpecFileService.full_file_path(spec, file_name) + os.remove(full_file_path) @staticmethod def delete_all_files(spec: ProcessModelInfo) -> None: From e6db091ba1db9ab51882fe593c1a88ba39de6b13 Mon Sep 17 00:00:00 2001 From: jasquat Date: Mon, 14 Nov 2022 16:29:04 -0500 Subject: [PATCH 5/8] refactored pagination table to allow prefixing page options w/ burnettk --- spiffworkflow-backend/migrations/env.py | 27 +- .../migrations/versions/88c2d7081664_.py | 313 ++++++++++ .../migrations/versions/fd00c59e1f60_.py | 550 ------------------ .../models/spiff_step_details.py | 6 + .../routes/process_api_blueprint.py | 12 +- .../services/process_instance_processor.py | 7 +- .../services/process_instance_service.py | 6 +- .../src/components/PaginationForTable.tsx | 17 +- .../components/ProcessInstanceListTable.tsx | 541 +++++++++++++++++ .../components/TasksForMyOpenProcesses.tsx | 11 +- .../src/components/TasksWaitingForMe.tsx | 10 +- .../components/TasksWaitingForMyGroups.tsx | 11 +- spiffworkflow-frontend/src/helpers.tsx | 15 +- .../src/routes/CompletedInstances.tsx | 3 + .../src/routes/HomePageRoutes.tsx | 7 + .../src/routes/MessageInstanceList.tsx | 1 - spiffworkflow-frontend/src/routes/MyTasks.tsx | 1 - .../src/routes/ProcessGroupList.tsx | 1 - .../src/routes/ProcessGroupShow.tsx | 2 - .../src/routes/ProcessInstanceList.tsx | 1 - .../src/routes/ProcessInstanceLogList.tsx | 1 - .../src/routes/ProcessInstanceReportShow.tsx | 1 - .../src/routes/SecretList.tsx | 1 - 23 files changed, 951 insertions(+), 594 deletions(-) create mode 100644 spiffworkflow-backend/migrations/versions/88c2d7081664_.py delete mode 100644 spiffworkflow-backend/migrations/versions/fd00c59e1f60_.py create mode 100644 spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx create mode 100644 spiffworkflow-frontend/src/routes/CompletedInstances.tsx diff --git a/spiffworkflow-backend/migrations/env.py b/spiffworkflow-backend/migrations/env.py index 4bd0316c2..68feded2a 100644 --- a/spiffworkflow-backend/migrations/env.py +++ b/spiffworkflow-backend/migrations/env.py @@ -1,9 +1,12 @@ +from __future__ import with_statement + import logging from logging.config import fileConfig -from alembic import context from flask import current_app +from alembic import context + # this is the Alembic Config object, which provides # access to the values within the .ini file in use. config = context.config @@ -11,17 +14,17 @@ config = context.config # Interpret the config file for Python logging. # This line sets up loggers basically. fileConfig(config.config_file_name) -logger = logging.getLogger("alembic.env") +logger = logging.getLogger('alembic.env') # add your model's MetaData object here # for 'autogenerate' support # from myapp import mymodel # target_metadata = mymodel.Base.metadata config.set_main_option( - "sqlalchemy.url", - str(current_app.extensions["migrate"].db.get_engine().url).replace("%", "%%"), -) -target_metadata = current_app.extensions["migrate"].db.metadata + 'sqlalchemy.url', + str(current_app.extensions['migrate'].db.get_engine().url).replace( + '%', '%%')) +target_metadata = current_app.extensions['migrate'].db.metadata # other values from the config, defined by the needs of env.py, # can be acquired: @@ -42,7 +45,9 @@ def run_migrations_offline(): """ url = config.get_main_option("sqlalchemy.url") - context.configure(url=url, target_metadata=target_metadata, literal_binds=True) + context.configure( + url=url, target_metadata=target_metadata, literal_binds=True + ) with context.begin_transaction(): context.run_migrations() @@ -60,20 +65,20 @@ def run_migrations_online(): # when there are no changes to the schema # reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html def process_revision_directives(context, revision, directives): - if getattr(config.cmd_opts, "autogenerate", False): + if getattr(config.cmd_opts, 'autogenerate', False): script = directives[0] if script.upgrade_ops.is_empty(): directives[:] = [] - logger.info("No changes in schema detected.") + logger.info('No changes in schema detected.') - connectable = current_app.extensions["migrate"].db.get_engine() + connectable = current_app.extensions['migrate'].db.get_engine() with connectable.connect() as connection: context.configure( connection=connection, target_metadata=target_metadata, process_revision_directives=process_revision_directives, - **current_app.extensions["migrate"].configure_args + **current_app.extensions['migrate'].configure_args ) with context.begin_transaction(): diff --git a/spiffworkflow-backend/migrations/versions/88c2d7081664_.py b/spiffworkflow-backend/migrations/versions/88c2d7081664_.py new file mode 100644 index 000000000..50d710804 --- /dev/null +++ b/spiffworkflow-backend/migrations/versions/88c2d7081664_.py @@ -0,0 +1,313 @@ +"""empty message + +Revision ID: 88c2d7081664 +Revises: +Create Date: 2022-11-14 15:16:42.833331 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '88c2d7081664' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('bpmn_process_id_lookup', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('bpmn_process_identifier', sa.String(length=255), nullable=True), + sa.Column('display_name', sa.String(length=255), nullable=True), + sa.Column('bpmn_file_relative_path', sa.String(length=255), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_bpmn_process_id_lookup_bpmn_process_identifier'), 'bpmn_process_id_lookup', ['bpmn_process_identifier'], unique=True) + op.create_index(op.f('ix_bpmn_process_id_lookup_display_name'), 'bpmn_process_id_lookup', ['display_name'], unique=True) + op.create_table('group', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=255), nullable=True), + sa.Column('identifier', sa.String(length=255), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('message_model', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('identifier', sa.String(length=50), nullable=True), + sa.Column('name', sa.String(length=50), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_message_model_identifier'), 'message_model', ['identifier'], unique=True) + op.create_index(op.f('ix_message_model_name'), 'message_model', ['name'], unique=True) + op.create_table('permission_target', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('uri', sa.String(length=255), nullable=False), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('uri') + ) + op.create_table('spiff_logging', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('process_instance_id', sa.Integer(), nullable=False), + sa.Column('bpmn_process_identifier', sa.String(length=255), nullable=False), + sa.Column('bpmn_task_identifier', sa.String(length=255), nullable=False), + sa.Column('bpmn_task_name', sa.String(length=255), nullable=True), + sa.Column('bpmn_task_type', sa.String(length=255), nullable=True), + sa.Column('spiff_task_guid', sa.String(length=50), nullable=False), + sa.Column('timestamp', sa.DECIMAL(precision=17, scale=6), nullable=False), + sa.Column('message', sa.String(length=255), nullable=True), + sa.Column('current_user_id', sa.Integer(), nullable=True), + sa.Column('spiff_step', sa.Integer(), nullable=False), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('user', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('username', sa.String(length=255), nullable=False), + sa.Column('uid', sa.String(length=50), nullable=True), + sa.Column('service', sa.String(length=50), nullable=False), + sa.Column('service_id', sa.String(length=255), nullable=False), + sa.Column('name', sa.String(length=255), nullable=True), + sa.Column('email', sa.String(length=255), nullable=True), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('service', 'service_id', name='service_key'), + sa.UniqueConstraint('uid'), + sa.UniqueConstraint('username') + ) + op.create_table('message_correlation_property', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('identifier', sa.String(length=50), nullable=True), + sa.Column('message_model_id', sa.Integer(), nullable=False), + sa.Column('updated_at_in_seconds', sa.Integer(), nullable=True), + sa.Column('created_at_in_seconds', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['message_model_id'], ['message_model.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('identifier', 'message_model_id', name='message_correlation_property_unique') + ) + op.create_index(op.f('ix_message_correlation_property_identifier'), 'message_correlation_property', ['identifier'], unique=False) + op.create_table('message_triggerable_process_model', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('message_model_id', sa.Integer(), nullable=False), + sa.Column('process_model_identifier', sa.String(length=50), nullable=False), + sa.Column('process_group_identifier', sa.String(length=50), nullable=False), + sa.Column('updated_at_in_seconds', sa.Integer(), nullable=True), + sa.Column('created_at_in_seconds', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['message_model_id'], ['message_model.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('message_model_id') + ) + op.create_index(op.f('ix_message_triggerable_process_model_process_group_identifier'), 'message_triggerable_process_model', ['process_group_identifier'], unique=False) + op.create_index(op.f('ix_message_triggerable_process_model_process_model_identifier'), 'message_triggerable_process_model', ['process_model_identifier'], unique=False) + op.create_table('principal', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('user_id', sa.Integer(), nullable=True), + sa.Column('group_id', sa.Integer(), nullable=True), + sa.CheckConstraint('NOT(user_id IS NULL AND group_id IS NULL)'), + sa.ForeignKeyConstraint(['group_id'], ['group.id'], ), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('group_id'), + sa.UniqueConstraint('user_id') + ) + op.create_table('process_instance', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('process_model_identifier', sa.String(length=255), nullable=False), + sa.Column('process_group_identifier', sa.String(length=50), nullable=False), + sa.Column('process_initiator_id', sa.Integer(), nullable=False), + sa.Column('bpmn_json', sa.JSON(), nullable=True), + sa.Column('start_in_seconds', sa.Integer(), nullable=True), + sa.Column('end_in_seconds', sa.Integer(), nullable=True), + sa.Column('updated_at_in_seconds', sa.Integer(), nullable=True), + sa.Column('created_at_in_seconds', sa.Integer(), nullable=True), + sa.Column('status', sa.String(length=50), nullable=True), + sa.Column('bpmn_version_control_type', sa.String(length=50), nullable=True), + sa.Column('bpmn_version_control_identifier', sa.String(length=255), nullable=True), + sa.Column('spiff_step', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['process_initiator_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_process_instance_process_group_identifier'), 'process_instance', ['process_group_identifier'], unique=False) + op.create_index(op.f('ix_process_instance_process_model_identifier'), 'process_instance', ['process_model_identifier'], unique=False) + op.create_table('process_instance_report', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('identifier', sa.String(length=50), nullable=False), + sa.Column('report_metadata', sa.JSON(), nullable=True), + sa.Column('created_by_id', sa.Integer(), nullable=False), + sa.Column('created_at_in_seconds', sa.Integer(), nullable=True), + sa.Column('updated_at_in_seconds', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['created_by_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('created_by_id', 'identifier', name='process_instance_report_unique') + ) + op.create_index(op.f('ix_process_instance_report_created_by_id'), 'process_instance_report', ['created_by_id'], unique=False) + op.create_index(op.f('ix_process_instance_report_identifier'), 'process_instance_report', ['identifier'], unique=False) + op.create_table('refresh_token', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('user_id', sa.Integer(), nullable=False), + sa.Column('token', sa.String(length=1024), nullable=False), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('user_id') + ) + op.create_table('secret', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('key', sa.String(length=50), nullable=False), + sa.Column('value', sa.Text(), nullable=False), + sa.Column('user_id', sa.Integer(), nullable=False), + sa.Column('updated_at_in_seconds', sa.Integer(), nullable=True), + sa.Column('created_at_in_seconds', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('key') + ) + op.create_table('spiff_step_details', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('process_instance_id', sa.Integer(), nullable=False), + sa.Column('spiff_step', sa.Integer(), nullable=False), + sa.Column('task_json', sa.JSON(), nullable=False), + sa.Column('timestamp', sa.DECIMAL(precision=17, scale=6), nullable=False), + sa.Column('completed_by_user_id', sa.Integer(), nullable=True), + sa.Column('lane_assignment_id', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['lane_assignment_id'], ['group.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('user_group_assignment', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('user_id', sa.Integer(), nullable=False), + sa.Column('group_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['group_id'], ['group.id'], ), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('user_id', 'group_id', name='user_group_assignment_unique') + ) + op.create_table('active_task', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('process_instance_id', sa.Integer(), nullable=False), + sa.Column('actual_owner_id', sa.Integer(), nullable=True), + sa.Column('lane_assignment_id', sa.Integer(), nullable=True), + sa.Column('form_file_name', sa.String(length=50), nullable=True), + sa.Column('ui_form_file_name', sa.String(length=50), nullable=True), + sa.Column('updated_at_in_seconds', sa.Integer(), nullable=True), + sa.Column('created_at_in_seconds', sa.Integer(), nullable=True), + sa.Column('task_id', sa.String(length=50), nullable=True), + sa.Column('task_name', sa.String(length=50), nullable=True), + sa.Column('task_title', sa.String(length=50), nullable=True), + sa.Column('task_type', sa.String(length=50), nullable=True), + sa.Column('task_status', sa.String(length=50), nullable=True), + sa.Column('process_model_display_name', sa.String(length=255), nullable=True), + sa.ForeignKeyConstraint(['actual_owner_id'], ['user.id'], ), + sa.ForeignKeyConstraint(['lane_assignment_id'], ['group.id'], ), + sa.ForeignKeyConstraint(['process_instance_id'], ['process_instance.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('task_id', 'process_instance_id', name='active_task_unique') + ) + op.create_table('message_correlation', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('process_instance_id', sa.Integer(), nullable=False), + sa.Column('message_correlation_property_id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=255), nullable=False), + sa.Column('value', sa.String(length=255), nullable=False), + sa.Column('updated_at_in_seconds', sa.Integer(), nullable=True), + sa.Column('created_at_in_seconds', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['message_correlation_property_id'], ['message_correlation_property.id'], ), + sa.ForeignKeyConstraint(['process_instance_id'], ['process_instance.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('process_instance_id', 'message_correlation_property_id', 'name', name='message_instance_id_name_unique') + ) + op.create_index(op.f('ix_message_correlation_message_correlation_property_id'), 'message_correlation', ['message_correlation_property_id'], unique=False) + op.create_index(op.f('ix_message_correlation_name'), 'message_correlation', ['name'], unique=False) + op.create_index(op.f('ix_message_correlation_process_instance_id'), 'message_correlation', ['process_instance_id'], unique=False) + op.create_index(op.f('ix_message_correlation_value'), 'message_correlation', ['value'], unique=False) + op.create_table('message_instance', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('process_instance_id', sa.Integer(), nullable=False), + sa.Column('message_model_id', sa.Integer(), nullable=False), + sa.Column('message_type', sa.String(length=20), nullable=False), + sa.Column('payload', sa.JSON(), nullable=True), + sa.Column('status', sa.String(length=20), nullable=False), + sa.Column('failure_cause', sa.Text(), nullable=True), + sa.Column('updated_at_in_seconds', sa.Integer(), nullable=True), + sa.Column('created_at_in_seconds', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['message_model_id'], ['message_model.id'], ), + sa.ForeignKeyConstraint(['process_instance_id'], ['process_instance.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('permission_assignment', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('principal_id', sa.Integer(), nullable=False), + sa.Column('permission_target_id', sa.Integer(), nullable=False), + sa.Column('grant_type', sa.String(length=50), nullable=False), + sa.Column('permission', sa.String(length=50), nullable=False), + sa.ForeignKeyConstraint(['permission_target_id'], ['permission_target.id'], ), + sa.ForeignKeyConstraint(['principal_id'], ['principal.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('principal_id', 'permission_target_id', 'permission', name='permission_assignment_uniq') + ) + op.create_table('active_task_user', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('active_task_id', sa.Integer(), nullable=False), + sa.Column('user_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['active_task_id'], ['active_task.id'], ), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('active_task_id', 'user_id', name='active_task_user_unique') + ) + op.create_index(op.f('ix_active_task_user_active_task_id'), 'active_task_user', ['active_task_id'], unique=False) + op.create_index(op.f('ix_active_task_user_user_id'), 'active_task_user', ['user_id'], unique=False) + op.create_table('message_correlation_message_instance', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('message_instance_id', sa.Integer(), nullable=False), + sa.Column('message_correlation_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['message_correlation_id'], ['message_correlation.id'], ), + sa.ForeignKeyConstraint(['message_instance_id'], ['message_instance.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('message_instance_id', 'message_correlation_id', name='message_correlation_message_instance_unique') + ) + op.create_index(op.f('ix_message_correlation_message_instance_message_correlation_id'), 'message_correlation_message_instance', ['message_correlation_id'], unique=False) + op.create_index(op.f('ix_message_correlation_message_instance_message_instance_id'), 'message_correlation_message_instance', ['message_instance_id'], unique=False) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_index(op.f('ix_message_correlation_message_instance_message_instance_id'), table_name='message_correlation_message_instance') + op.drop_index(op.f('ix_message_correlation_message_instance_message_correlation_id'), table_name='message_correlation_message_instance') + op.drop_table('message_correlation_message_instance') + op.drop_index(op.f('ix_active_task_user_user_id'), table_name='active_task_user') + op.drop_index(op.f('ix_active_task_user_active_task_id'), table_name='active_task_user') + op.drop_table('active_task_user') + op.drop_table('permission_assignment') + op.drop_table('message_instance') + op.drop_index(op.f('ix_message_correlation_value'), table_name='message_correlation') + op.drop_index(op.f('ix_message_correlation_process_instance_id'), table_name='message_correlation') + op.drop_index(op.f('ix_message_correlation_name'), table_name='message_correlation') + op.drop_index(op.f('ix_message_correlation_message_correlation_property_id'), table_name='message_correlation') + op.drop_table('message_correlation') + op.drop_table('active_task') + op.drop_table('user_group_assignment') + op.drop_table('spiff_step_details') + op.drop_table('secret') + op.drop_table('refresh_token') + op.drop_index(op.f('ix_process_instance_report_identifier'), table_name='process_instance_report') + op.drop_index(op.f('ix_process_instance_report_created_by_id'), table_name='process_instance_report') + op.drop_table('process_instance_report') + op.drop_index(op.f('ix_process_instance_process_model_identifier'), table_name='process_instance') + op.drop_index(op.f('ix_process_instance_process_group_identifier'), table_name='process_instance') + op.drop_table('process_instance') + op.drop_table('principal') + op.drop_index(op.f('ix_message_triggerable_process_model_process_model_identifier'), table_name='message_triggerable_process_model') + op.drop_index(op.f('ix_message_triggerable_process_model_process_group_identifier'), table_name='message_triggerable_process_model') + op.drop_table('message_triggerable_process_model') + op.drop_index(op.f('ix_message_correlation_property_identifier'), table_name='message_correlation_property') + op.drop_table('message_correlation_property') + op.drop_table('user') + op.drop_table('spiff_logging') + op.drop_table('permission_target') + op.drop_index(op.f('ix_message_model_name'), table_name='message_model') + op.drop_index(op.f('ix_message_model_identifier'), table_name='message_model') + op.drop_table('message_model') + op.drop_table('group') + op.drop_index(op.f('ix_bpmn_process_id_lookup_display_name'), table_name='bpmn_process_id_lookup') + op.drop_index(op.f('ix_bpmn_process_id_lookup_bpmn_process_identifier'), table_name='bpmn_process_id_lookup') + op.drop_table('bpmn_process_id_lookup') + # ### end Alembic commands ### diff --git a/spiffworkflow-backend/migrations/versions/fd00c59e1f60_.py b/spiffworkflow-backend/migrations/versions/fd00c59e1f60_.py deleted file mode 100644 index 55f1ca3f6..000000000 --- a/spiffworkflow-backend/migrations/versions/fd00c59e1f60_.py +++ /dev/null @@ -1,550 +0,0 @@ -"""empty message - -Revision ID: fd00c59e1f60 -Revises: -Create Date: 2022-11-09 14:04:14.169379 - -""" -import sqlalchemy as sa -from alembic import op - - -# revision identifiers, used by Alembic. -revision = "fd00c59e1f60" -down_revision = None -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.create_table( - "bpmn_process_id_lookup", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("bpmn_process_identifier", sa.String(length=255), nullable=True), - sa.Column("bpmn_file_relative_path", sa.String(length=255), nullable=True), - sa.Column("display_name", sa.String(length=255), nullable=True), - sa.PrimaryKeyConstraint("id"), - ) - op.create_index( - op.f("ix_bpmn_process_id_lookup_bpmn_process_identifier"), - "bpmn_process_id_lookup", - ["bpmn_process_identifier"], - unique=True, - ) - op.create_table( - "group", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("name", sa.String(length=255), nullable=True), - sa.Column("identifier", sa.String(length=255), nullable=True), - sa.PrimaryKeyConstraint("id"), - ) - op.create_table( - "message_model", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("identifier", sa.String(length=50), nullable=True), - sa.Column("name", sa.String(length=50), nullable=True), - sa.PrimaryKeyConstraint("id"), - ) - op.create_index( - op.f("ix_message_model_identifier"), - "message_model", - ["identifier"], - unique=True, - ) - op.create_index( - op.f("ix_message_model_name"), "message_model", ["name"], unique=True - ) - op.create_table( - "permission_target", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("uri", sa.String(length=255), nullable=False), - sa.PrimaryKeyConstraint("id"), - sa.UniqueConstraint("uri"), - ) - op.create_table( - "spiff_logging", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("process_instance_id", sa.Integer(), nullable=False), - sa.Column("bpmn_process_identifier", sa.String(length=255), nullable=False), - sa.Column("bpmn_task_identifier", sa.String(length=255), nullable=False), - sa.Column("bpmn_task_name", sa.String(length=255), nullable=True), - sa.Column("bpmn_task_type", sa.String(length=255), nullable=True), - sa.Column("spiff_task_guid", sa.String(length=50), nullable=False), - sa.Column("timestamp", sa.DECIMAL(precision=17, scale=6), nullable=False), - sa.Column("message", sa.String(length=255), nullable=True), - sa.Column("current_user_id", sa.Integer(), nullable=True), - sa.Column("spiff_step", sa.Integer(), nullable=False), - sa.PrimaryKeyConstraint("id"), - ) - op.create_table( - "spiff_step_details", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("process_instance_id", sa.Integer(), nullable=False), - sa.Column("spiff_step", sa.Integer(), nullable=False), - sa.Column("task_json", sa.JSON(), nullable=False), - sa.Column("timestamp", sa.DECIMAL(precision=17, scale=6), nullable=False), - sa.Column("completed_by_user_id", sa.Integer(), nullable=True), - sa.PrimaryKeyConstraint("id"), - ) - op.create_table( - "user", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("username", sa.String(length=255), nullable=False), - sa.Column("uid", sa.String(length=50), nullable=True), - sa.Column("service", sa.String(length=50), nullable=False), - sa.Column("service_id", sa.String(length=255), nullable=False), - sa.Column("name", sa.String(length=255), nullable=True), - sa.Column("email", sa.String(length=255), nullable=True), - sa.PrimaryKeyConstraint("id"), - sa.UniqueConstraint("service", "service_id", name="service_key"), - sa.UniqueConstraint("uid"), - sa.UniqueConstraint("username"), - ) - op.create_table( - "message_correlation_property", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("identifier", sa.String(length=50), nullable=True), - sa.Column("message_model_id", sa.Integer(), nullable=False), - sa.Column("updated_at_in_seconds", sa.Integer(), nullable=True), - sa.Column("created_at_in_seconds", sa.Integer(), nullable=True), - sa.ForeignKeyConstraint( - ["message_model_id"], - ["message_model.id"], - ), - sa.PrimaryKeyConstraint("id"), - sa.UniqueConstraint( - "identifier", "message_model_id", name="message_correlation_property_unique" - ), - ) - op.create_index( - op.f("ix_message_correlation_property_identifier"), - "message_correlation_property", - ["identifier"], - unique=False, - ) - op.create_table( - "message_triggerable_process_model", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("message_model_id", sa.Integer(), nullable=False), - sa.Column("process_model_identifier", sa.String(length=50), nullable=False), - sa.Column("process_group_identifier", sa.String(length=50), nullable=False), - sa.Column("updated_at_in_seconds", sa.Integer(), nullable=True), - sa.Column("created_at_in_seconds", sa.Integer(), nullable=True), - sa.ForeignKeyConstraint( - ["message_model_id"], - ["message_model.id"], - ), - sa.PrimaryKeyConstraint("id"), - sa.UniqueConstraint("message_model_id"), - ) - op.create_index( - op.f("ix_message_triggerable_process_model_process_group_identifier"), - "message_triggerable_process_model", - ["process_group_identifier"], - unique=False, - ) - op.create_index( - op.f("ix_message_triggerable_process_model_process_model_identifier"), - "message_triggerable_process_model", - ["process_model_identifier"], - unique=False, - ) - op.create_table( - "principal", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("user_id", sa.Integer(), nullable=True), - sa.Column("group_id", sa.Integer(), nullable=True), - sa.CheckConstraint("NOT(user_id IS NULL AND group_id IS NULL)"), - sa.ForeignKeyConstraint( - ["group_id"], - ["group.id"], - ), - sa.ForeignKeyConstraint( - ["user_id"], - ["user.id"], - ), - sa.PrimaryKeyConstraint("id"), - sa.UniqueConstraint("group_id"), - sa.UniqueConstraint("user_id"), - ) - op.create_table( - "process_instance", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("process_model_identifier", sa.String(length=255), nullable=False), - sa.Column("process_group_identifier", sa.String(length=50), nullable=False), - sa.Column("process_initiator_id", sa.Integer(), nullable=False), - sa.Column("bpmn_json", sa.JSON(), nullable=True), - sa.Column("start_in_seconds", sa.Integer(), nullable=True), - sa.Column("end_in_seconds", sa.Integer(), nullable=True), - sa.Column("updated_at_in_seconds", sa.Integer(), nullable=True), - sa.Column("created_at_in_seconds", sa.Integer(), nullable=True), - sa.Column("status", sa.String(length=50), nullable=True), - sa.Column("bpmn_version_control_type", sa.String(length=50), nullable=True), - sa.Column( - "bpmn_version_control_identifier", sa.String(length=255), nullable=True - ), - sa.Column("spiff_step", sa.Integer(), nullable=True), - sa.ForeignKeyConstraint( - ["process_initiator_id"], - ["user.id"], - ), - sa.PrimaryKeyConstraint("id"), - ) - op.create_index( - op.f("ix_process_instance_process_group_identifier"), - "process_instance", - ["process_group_identifier"], - unique=False, - ) - op.create_index( - op.f("ix_process_instance_process_model_identifier"), - "process_instance", - ["process_model_identifier"], - unique=False, - ) - op.create_table( - "process_instance_report", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("identifier", sa.String(length=50), nullable=False), - sa.Column("report_metadata", sa.JSON(), nullable=True), - sa.Column("created_by_id", sa.Integer(), nullable=False), - sa.Column("created_at_in_seconds", sa.Integer(), nullable=True), - sa.Column("updated_at_in_seconds", sa.Integer(), nullable=True), - sa.ForeignKeyConstraint( - ["created_by_id"], - ["user.id"], - ), - sa.PrimaryKeyConstraint("id"), - sa.UniqueConstraint( - "created_by_id", "identifier", name="process_instance_report_unique" - ), - ) - op.create_index( - op.f("ix_process_instance_report_created_by_id"), - "process_instance_report", - ["created_by_id"], - unique=False, - ) - op.create_index( - op.f("ix_process_instance_report_identifier"), - "process_instance_report", - ["identifier"], - unique=False, - ) - op.create_table( - "refresh_token", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("user_id", sa.Integer(), nullable=False), - sa.Column("token", sa.String(length=1024), nullable=False), - sa.ForeignKeyConstraint( - ["user_id"], - ["user.id"], - ), - sa.PrimaryKeyConstraint("id"), - sa.UniqueConstraint("user_id"), - ) - op.create_table( - "secret", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("key", sa.String(length=50), nullable=False), - sa.Column("value", sa.Text(), nullable=False), - sa.Column("user_id", sa.Integer(), nullable=False), - sa.Column("updated_at_in_seconds", sa.Integer(), nullable=True), - sa.Column("created_at_in_seconds", sa.Integer(), nullable=True), - sa.ForeignKeyConstraint( - ["user_id"], - ["user.id"], - ), - sa.PrimaryKeyConstraint("id"), - sa.UniqueConstraint("key"), - ) - op.create_table( - "user_group_assignment", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("user_id", sa.Integer(), nullable=False), - sa.Column("group_id", sa.Integer(), nullable=False), - sa.ForeignKeyConstraint( - ["group_id"], - ["group.id"], - ), - sa.ForeignKeyConstraint( - ["user_id"], - ["user.id"], - ), - sa.PrimaryKeyConstraint("id"), - sa.UniqueConstraint("user_id", "group_id", name="user_group_assignment_unique"), - ) - op.create_table( - "active_task", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("process_instance_id", sa.Integer(), nullable=False), - sa.Column("actual_owner_id", sa.Integer(), nullable=True), - sa.Column("lane_assignment_id", sa.Integer(), nullable=True), - sa.Column("form_file_name", sa.String(length=50), nullable=True), - sa.Column("ui_form_file_name", sa.String(length=50), nullable=True), - sa.Column("updated_at_in_seconds", sa.Integer(), nullable=True), - sa.Column("created_at_in_seconds", sa.Integer(), nullable=True), - sa.Column("task_id", sa.String(length=50), nullable=True), - sa.Column("task_name", sa.String(length=50), nullable=True), - sa.Column("task_title", sa.String(length=50), nullable=True), - sa.Column("task_type", sa.String(length=50), nullable=True), - sa.Column("task_status", sa.String(length=50), nullable=True), - sa.Column("process_model_display_name", sa.String(length=255), nullable=True), - sa.ForeignKeyConstraint( - ["actual_owner_id"], - ["user.id"], - ), - sa.ForeignKeyConstraint( - ["lane_assignment_id"], - ["group.id"], - ), - sa.ForeignKeyConstraint( - ["process_instance_id"], - ["process_instance.id"], - ), - sa.PrimaryKeyConstraint("id"), - sa.UniqueConstraint( - "task_id", "process_instance_id", name="active_task_unique" - ), - ) - op.create_table( - "message_correlation", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("process_instance_id", sa.Integer(), nullable=False), - sa.Column("message_correlation_property_id", sa.Integer(), nullable=False), - sa.Column("name", sa.String(length=255), nullable=False), - sa.Column("value", sa.String(length=255), nullable=False), - sa.Column("updated_at_in_seconds", sa.Integer(), nullable=True), - sa.Column("created_at_in_seconds", sa.Integer(), nullable=True), - sa.ForeignKeyConstraint( - ["message_correlation_property_id"], - ["message_correlation_property.id"], - ), - sa.ForeignKeyConstraint( - ["process_instance_id"], - ["process_instance.id"], - ), - sa.PrimaryKeyConstraint("id"), - sa.UniqueConstraint( - "process_instance_id", - "message_correlation_property_id", - "name", - name="message_instance_id_name_unique", - ), - ) - op.create_index( - op.f("ix_message_correlation_message_correlation_property_id"), - "message_correlation", - ["message_correlation_property_id"], - unique=False, - ) - op.create_index( - op.f("ix_message_correlation_name"), - "message_correlation", - ["name"], - unique=False, - ) - op.create_index( - op.f("ix_message_correlation_process_instance_id"), - "message_correlation", - ["process_instance_id"], - unique=False, - ) - op.create_index( - op.f("ix_message_correlation_value"), - "message_correlation", - ["value"], - unique=False, - ) - op.create_table( - "message_instance", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("process_instance_id", sa.Integer(), nullable=False), - sa.Column("message_model_id", sa.Integer(), nullable=False), - sa.Column("message_type", sa.String(length=20), nullable=False), - sa.Column("payload", sa.JSON(), nullable=True), - sa.Column("status", sa.String(length=20), nullable=False), - sa.Column("failure_cause", sa.Text(), nullable=True), - sa.Column("updated_at_in_seconds", sa.Integer(), nullable=True), - sa.Column("created_at_in_seconds", sa.Integer(), nullable=True), - sa.ForeignKeyConstraint( - ["message_model_id"], - ["message_model.id"], - ), - sa.ForeignKeyConstraint( - ["process_instance_id"], - ["process_instance.id"], - ), - sa.PrimaryKeyConstraint("id"), - ) - op.create_table( - "permission_assignment", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("principal_id", sa.Integer(), nullable=False), - sa.Column("permission_target_id", sa.Integer(), nullable=False), - sa.Column("grant_type", sa.String(length=50), nullable=False), - sa.Column("permission", sa.String(length=50), nullable=False), - sa.ForeignKeyConstraint( - ["permission_target_id"], - ["permission_target.id"], - ), - sa.ForeignKeyConstraint( - ["principal_id"], - ["principal.id"], - ), - sa.PrimaryKeyConstraint("id"), - sa.UniqueConstraint( - "principal_id", - "permission_target_id", - "permission", - name="permission_assignment_uniq", - ), - ) - op.create_table( - "active_task_user", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("active_task_id", sa.Integer(), nullable=False), - sa.Column("user_id", sa.Integer(), nullable=False), - sa.ForeignKeyConstraint( - ["active_task_id"], - ["active_task.id"], - ), - sa.ForeignKeyConstraint( - ["user_id"], - ["user.id"], - ), - sa.PrimaryKeyConstraint("id"), - sa.UniqueConstraint( - "active_task_id", "user_id", name="active_task_user_unique" - ), - ) - op.create_index( - op.f("ix_active_task_user_active_task_id"), - "active_task_user", - ["active_task_id"], - unique=False, - ) - op.create_index( - op.f("ix_active_task_user_user_id"), - "active_task_user", - ["user_id"], - unique=False, - ) - op.create_table( - "message_correlation_message_instance", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("message_instance_id", sa.Integer(), nullable=False), - sa.Column("message_correlation_id", sa.Integer(), nullable=False), - sa.ForeignKeyConstraint( - ["message_correlation_id"], - ["message_correlation.id"], - ), - sa.ForeignKeyConstraint( - ["message_instance_id"], - ["message_instance.id"], - ), - sa.PrimaryKeyConstraint("id"), - sa.UniqueConstraint( - "message_instance_id", - "message_correlation_id", - name="message_correlation_message_instance_unique", - ), - ) - op.create_index( - op.f("ix_message_correlation_message_instance_message_correlation_id"), - "message_correlation_message_instance", - ["message_correlation_id"], - unique=False, - ) - op.create_index( - op.f("ix_message_correlation_message_instance_message_instance_id"), - "message_correlation_message_instance", - ["message_instance_id"], - unique=False, - ) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_index( - op.f("ix_message_correlation_message_instance_message_instance_id"), - table_name="message_correlation_message_instance", - ) - op.drop_index( - op.f("ix_message_correlation_message_instance_message_correlation_id"), - table_name="message_correlation_message_instance", - ) - op.drop_table("message_correlation_message_instance") - op.drop_index(op.f("ix_active_task_user_user_id"), table_name="active_task_user") - op.drop_index( - op.f("ix_active_task_user_active_task_id"), table_name="active_task_user" - ) - op.drop_table("active_task_user") - op.drop_table("permission_assignment") - op.drop_table("message_instance") - op.drop_index( - op.f("ix_message_correlation_value"), table_name="message_correlation" - ) - op.drop_index( - op.f("ix_message_correlation_process_instance_id"), - table_name="message_correlation", - ) - op.drop_index(op.f("ix_message_correlation_name"), table_name="message_correlation") - op.drop_index( - op.f("ix_message_correlation_message_correlation_property_id"), - table_name="message_correlation", - ) - op.drop_table("message_correlation") - op.drop_table("active_task") - op.drop_table("user_group_assignment") - op.drop_table("secret") - op.drop_table("refresh_token") - op.drop_index( - op.f("ix_process_instance_report_identifier"), - table_name="process_instance_report", - ) - op.drop_index( - op.f("ix_process_instance_report_created_by_id"), - table_name="process_instance_report", - ) - op.drop_table("process_instance_report") - op.drop_index( - op.f("ix_process_instance_process_model_identifier"), - table_name="process_instance", - ) - op.drop_index( - op.f("ix_process_instance_process_group_identifier"), - table_name="process_instance", - ) - op.drop_table("process_instance") - op.drop_table("principal") - op.drop_index( - op.f("ix_message_triggerable_process_model_process_model_identifier"), - table_name="message_triggerable_process_model", - ) - op.drop_index( - op.f("ix_message_triggerable_process_model_process_group_identifier"), - table_name="message_triggerable_process_model", - ) - op.drop_table("message_triggerable_process_model") - op.drop_index( - op.f("ix_message_correlation_property_identifier"), - table_name="message_correlation_property", - ) - op.drop_table("message_correlation_property") - op.drop_table("user") - op.drop_table("spiff_step_details") - op.drop_table("spiff_logging") - op.drop_table("permission_target") - op.drop_index(op.f("ix_message_model_name"), table_name="message_model") - op.drop_index(op.f("ix_message_model_identifier"), table_name="message_model") - op.drop_table("message_model") - op.drop_table("group") - op.drop_index( - op.f("ix_bpmn_process_id_lookup_bpmn_process_identifier"), - table_name="bpmn_process_id_lookup", - ) - op.drop_table("bpmn_process_id_lookup") - # ### end Alembic commands ### diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/models/spiff_step_details.py b/spiffworkflow-backend/src/spiffworkflow_backend/models/spiff_step_details.py index 56499e91d..3bba5aed6 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/models/spiff_step_details.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/models/spiff_step_details.py @@ -1,10 +1,15 @@ """Spiff_step_details.""" from dataclasses import dataclass +from typing import Optional + +from sqlalchemy import ForeignKey from flask_bpmn.models.db import db from flask_bpmn.models.db import SpiffworkflowBaseDBModel from sqlalchemy.orm import deferred +from spiffworkflow_backend.models.group import GroupModel + @dataclass class SpiffStepDetailsModel(SpiffworkflowBaseDBModel): @@ -17,3 +22,4 @@ class SpiffStepDetailsModel(SpiffworkflowBaseDBModel): task_json: str = deferred(db.Column(db.JSON, nullable=False)) # type: ignore timestamp: float = db.Column(db.DECIMAL(17, 6), nullable=False) completed_by_user_id: int = db.Column(db.Integer, nullable=True) + lane_assignment_id: Optional[int] = db.Column(ForeignKey(GroupModel.id), nullable=True) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py index 27d813a82..c4044b2a2 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py @@ -1264,7 +1264,17 @@ def task_submit( if terminate_loop and spiff_task.is_looping(): spiff_task.terminate_loop() - ProcessInstanceService.complete_form_task(processor, spiff_task, body, g.user) + active_task = ActiveTaskModel.query.filter_by(process_instance_id=process_instance_id, task_id=task_id).first() + if active_task is None: + raise ( + ApiError( + error_code="no_active_task", + message="Cannot find an active task with task id '{task_id}' for process instance {process_instance_id}.", + status_code=500, + ) + ) + + ProcessInstanceService.complete_form_task(processor=processor, spiff_task=spiff_task, data=body, user=g.user, active_task=active_task) # If we need to update all tasks, then get the next ready task and if it a multi-instance with the same # task spec, complete that form as well. diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py index 91db99804..d9ac6c05b 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py @@ -580,9 +580,10 @@ class ProcessInstanceProcessor: ) return details_model - def save_spiff_step_details(self) -> None: + def save_spiff_step_details(self, active_task: ActiveTaskModel) -> None: """SaveSpiffStepDetails.""" details_model = self.spiff_step_details() + details_model.lane_assignment_id = active_task.lane_assignment_id db.session.add(details_model) db.session.commit() @@ -1129,11 +1130,11 @@ class ProcessInstanceProcessor: ) return user_tasks # type: ignore - def complete_task(self, task: SpiffTask) -> None: + def complete_task(self, task: SpiffTask, active_task: ActiveTaskModel) -> None: """Complete_task.""" self.increment_spiff_step() self.bpmn_process_instance.complete_task_from_id(task.id) - self.save_spiff_step_details() + self.save_spiff_step_details(active_task) def get_data(self) -> dict[str, Any]: """Get_data.""" diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_service.py index ca9f66aa7..ccf362ab7 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_service.py @@ -6,7 +6,8 @@ from typing import List from flask import current_app from flask_bpmn.api.api_error import ApiError from flask_bpmn.models.db import db -from SpiffWorkflow.task import Task as SpiffTask # type: ignore +from SpiffWorkflow.task import Task as SpiffTask +from spiffworkflow_backend.models.active_task import ActiveTaskModel # type: ignore from spiffworkflow_backend.models.process_instance import ProcessInstanceApi from spiffworkflow_backend.models.process_instance import ProcessInstanceModel @@ -188,6 +189,7 @@ class ProcessInstanceService: spiff_task: SpiffTask, data: dict[str, Any], user: UserModel, + active_task: ActiveTaskModel ) -> None: """All the things that need to happen when we complete a form. @@ -201,7 +203,7 @@ class ProcessInstanceService: dot_dct = ProcessInstanceService.create_dot_dict(data) spiff_task.update_data(dot_dct) # ProcessInstanceService.post_process_form(spiff_task) # some properties may update the data store. - processor.complete_task(spiff_task) + processor.complete_task(spiff_task, active_task) processor.do_engine_steps(save=True) @staticmethod diff --git a/spiffworkflow-frontend/src/components/PaginationForTable.tsx b/spiffworkflow-frontend/src/components/PaginationForTable.tsx index 3b65c78c2..21813952c 100644 --- a/spiffworkflow-frontend/src/components/PaginationForTable.tsx +++ b/spiffworkflow-frontend/src/components/PaginationForTable.tsx @@ -1,4 +1,4 @@ -import { useNavigate } from 'react-router-dom'; +import { useNavigate, useSearchParams } from 'react-router-dom'; // @ts-ignore import { Pagination } from '@carbon/react'; @@ -14,7 +14,7 @@ type OwnProps = { pagination: PaginationObject | null; tableToDisplay: any; queryParamString?: string; - path: string; + paginationQueryParamPrefix?: string; }; export default function PaginationForTable({ @@ -23,16 +23,21 @@ export default function PaginationForTable({ perPageOptions, pagination, tableToDisplay, - queryParamString = '', - path, + paginationQueryParamPrefix, }: OwnProps) { const PER_PAGE_OPTIONS = [2, 10, 50, 100]; - const navigate = useNavigate(); + const [searchParams, setSearchParams] = useSearchParams(); + const paginationQueryParamPrefixToUse = paginationQueryParamPrefix + ? `${paginationQueryParamPrefix}_` + : ''; const updateRows = (args: any) => { const newPage = args.page; const { pageSize } = args; - navigate(`${path}?page=${newPage}&per_page=${pageSize}${queryParamString}`); + + searchParams.set(`${paginationQueryParamPrefixToUse}page`, newPage); + searchParams.set(`${paginationQueryParamPrefixToUse}per_page`, pageSize); + setSearchParams(searchParams); }; if (pagination) { diff --git a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx new file mode 100644 index 000000000..f09ae7113 --- /dev/null +++ b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx @@ -0,0 +1,541 @@ +import { useContext, useEffect, useMemo, useState } from 'react'; +import { + Link, + useNavigate, + useParams, + useSearchParams, +} from 'react-router-dom'; + +// @ts-ignore +import { Filter } from '@carbon/icons-react'; +import { + Button, + ButtonSet, + DatePicker, + DatePickerInput, + Table, + Grid, + Column, + MultiSelect, + TableHeader, + TableHead, + TableRow, + // @ts-ignore +} from '@carbon/react'; +import { PROCESS_STATUSES, DATE_FORMAT, DATE_FORMAT_CARBON } from '../config'; +import { + convertDateStringToSeconds, + convertSecondsToFormattedDate, + getPageInfoFromSearchParams, + getProcessModelFullIdentifierFromSearchParams, + modifyProcessModelPath, +} from '../helpers'; + +import PaginationForTable from './PaginationForTable'; +import 'react-datepicker/dist/react-datepicker.css'; + +import ErrorContext from '../contexts/ErrorContext'; +import HttpService from '../services/HttpService'; + +import 'react-bootstrap-typeahead/css/Typeahead.css'; +import 'react-bootstrap-typeahead/css/Typeahead.bs5.css'; +import { PaginationObject, ProcessModel } from '../interfaces'; +import ProcessModelSearch from './ProcessModelSearch'; +import ProcessBreadcrumb from './ProcessBreadcrumb'; + +export default function ProcessInstanceList() { + const params = useParams(); + const [searchParams] = useSearchParams(); + const navigate = useNavigate(); + + const [processInstances, setProcessInstances] = useState([]); + const [reportMetadata, setReportMetadata] = useState({}); + const [pagination, setPagination] = useState(null); + + const oneHourInSeconds = 3600; + const oneMonthInSeconds = oneHourInSeconds * 24 * 30; + const [startFrom, setStartFrom] = useState(''); + const [startTo, setStartTo] = useState(''); + const [endFrom, setEndFrom] = useState(''); + const [endTo, setEndTo] = useState(''); + const [showFilterOptions, setShowFilterOptions] = useState(false); + + const setErrorMessage = (useContext as any)(ErrorContext)[1]; + + const [processStatusAllOptions, setProcessStatusAllOptions] = useState( + [] + ); + const [processStatusSelection, setProcessStatusSelection] = useState< + string[] + >([]); + const [processModelAvailableItems, setProcessModelAvailableItems] = useState< + ProcessModel[] + >([]); + const [processModelSelection, setProcessModelSelection] = + useState(null); + + const parametersToAlwaysFilterBy = useMemo(() => { + return { + start_from: setStartFrom, + start_to: setStartTo, + end_from: setEndFrom, + end_to: setEndTo, + }; + }, [setStartFrom, setStartTo, setEndFrom, setEndTo]); + + const parametersToGetFromSearchParams = useMemo(() => { + return { + process_model_identifier: null, + process_status: null, + }; + }, []); + + // eslint-disable-next-line sonarjs/cognitive-complexity + useEffect(() => { + function setProcessInstancesFromResult(result: any) { + const processInstancesFromApi = result.results; + setProcessInstances(processInstancesFromApi); + setReportMetadata(result.report_metadata); + setPagination(result.pagination); + } + function getProcessInstances() { + const { page, perPage } = getPageInfoFromSearchParams(searchParams); + let queryParamString = `per_page=${perPage}&page=${page}`; + + Object.keys(parametersToAlwaysFilterBy).forEach((paramName: string) => { + // @ts-expect-error TS(7053) FIXME: + const functionToCall = parametersToAlwaysFilterBy[paramName]; + const searchParamValue = searchParams.get(paramName); + if (searchParamValue) { + queryParamString += `&${paramName}=${searchParamValue}`; + const dateString = convertSecondsToFormattedDate( + searchParamValue as any + ); + functionToCall(dateString); + setShowFilterOptions(true); + } + }); + + Object.keys(parametersToGetFromSearchParams).forEach( + (paramName: string) => { + if (searchParams.get(paramName)) { + // @ts-expect-error TS(7053) FIXME: + const functionToCall = parametersToGetFromSearchParams[paramName]; + queryParamString += `&${paramName}=${searchParams.get(paramName)}`; + if (functionToCall !== null) { + functionToCall(searchParams.get(paramName) || ''); + } + setShowFilterOptions(true); + } + } + ); + HttpService.makeCallToBackend({ + path: `/process-instances?${queryParamString}`, + successCallback: setProcessInstancesFromResult, + }); + } + function processResultForProcessModels(result: any) { + const processModelFullIdentifier = + getProcessModelFullIdentifierFromSearchParams(searchParams); + const selectionArray = result.results.map((item: any) => { + const label = `${item.id}`; + Object.assign(item, { label }); + if (label === processModelFullIdentifier) { + setProcessModelSelection(item); + } + return item; + }); + setProcessModelAvailableItems(selectionArray); + + const processStatusSelectedArray: string[] = []; + const processStatusAllOptionsArray = PROCESS_STATUSES.map( + (processStatusOption: any) => { + const regex = new RegExp(`\\b${processStatusOption}\\b`); + if ((searchParams.get('process_status') || '').match(regex)) { + processStatusSelectedArray.push(processStatusOption); + } + return processStatusOption; + } + ); + setProcessStatusSelection(processStatusSelectedArray); + setProcessStatusAllOptions(processStatusAllOptionsArray); + + getProcessInstances(); + } + + // populate process model selection + HttpService.makeCallToBackend({ + path: `/process-models?per_page=1000`, + successCallback: processResultForProcessModels, + }); + }, [ + searchParams, + params, + oneMonthInSeconds, + oneHourInSeconds, + parametersToAlwaysFilterBy, + parametersToGetFromSearchParams, + ]); + + // does the comparison, but also returns false if either argument + // is not truthy and therefore not comparable. + const isTrueComparison = (param1: any, operation: any, param2: any) => { + if (param1 && param2) { + switch (operation) { + case '<': + return param1 < param2; + case '>': + return param1 > param2; + default: + return false; + } + } else { + return false; + } + }; + + const applyFilter = (event: any) => { + event.preventDefault(); + const { page, perPage } = getPageInfoFromSearchParams(searchParams); + let queryParamString = `per_page=${perPage}&page=${page}`; + + const startFromSeconds = convertDateStringToSeconds(startFrom); + const endFromSeconds = convertDateStringToSeconds(endFrom); + const startToSeconds = convertDateStringToSeconds(startTo); + const endToSeconds = convertDateStringToSeconds(endTo); + if (isTrueComparison(startFromSeconds, '>', startToSeconds)) { + setErrorMessage({ + message: '"Start date from" cannot be after "start date to"', + }); + return; + } + if (isTrueComparison(endFromSeconds, '>', endToSeconds)) { + setErrorMessage({ + message: '"End date from" cannot be after "end date to"', + }); + return; + } + if (isTrueComparison(startFromSeconds, '>', endFromSeconds)) { + setErrorMessage({ + message: '"Start date from" cannot be after "end date from"', + }); + return; + } + if (isTrueComparison(startToSeconds, '>', endToSeconds)) { + setErrorMessage({ + message: '"Start date to" cannot be after "end date to"', + }); + return; + } + + if (startFromSeconds) { + queryParamString += `&start_from=${startFromSeconds}`; + } + if (startToSeconds) { + queryParamString += `&start_to=${startToSeconds}`; + } + if (endFromSeconds) { + queryParamString += `&end_from=${endFromSeconds}`; + } + if (endToSeconds) { + queryParamString += `&end_to=${endToSeconds}`; + } + if (processStatusSelection.length > 0) { + queryParamString += `&process_status=${processStatusSelection}`; + } + + if (processModelSelection) { + queryParamString += `&process_model_identifier=${processModelSelection.id}`; + } + + setErrorMessage(null); + navigate(`/admin/process-instances?${queryParamString}`); + }; + + const dateComponent = ( + labelString: any, + name: any, + initialDate: any, + onChangeFunction: any + ) => { + return ( + + { + onChangeFunction(dateChangeEvent.srcElement.value); + }} + value={initialDate} + /> + + ); + }; + + const getSearchParamsAsQueryString = () => { + let queryParamString = ''; + Object.keys(parametersToAlwaysFilterBy).forEach((paramName) => { + const searchParamValue = searchParams.get(paramName); + if (searchParamValue) { + queryParamString += `&${paramName}=${searchParamValue}`; + } + }); + + Object.keys(parametersToGetFromSearchParams).forEach( + (paramName: string) => { + if (searchParams.get(paramName)) { + queryParamString += `&${paramName}=${searchParams.get(paramName)}`; + } + } + ); + return queryParamString; + }; + + const processStatusSearch = () => { + return ( + { + setProcessStatusSelection(selection.selectedItems); + }} + itemToString={(item: any) => { + return item || ''; + }} + selectionFeedback="top-after-reopen" + selectedItems={processStatusSelection} + /> + ); + }; + + const clearFilters = () => { + setProcessModelSelection(null); + setProcessStatusSelection([]); + setStartFrom(''); + setStartTo(''); + setEndFrom(''); + setEndTo(''); + }; + + const filterOptions = () => { + if (!showFilterOptions) { + return null; + } + return ( + <> + + + + setProcessModelSelection(selection.selectedItem) + } + processModels={processModelAvailableItems} + selectedItem={processModelSelection} + /> + + {processStatusSearch()} + + + + {dateComponent( + 'Start date from', + 'start-from', + startFrom, + setStartFrom + )} + + + {dateComponent('Start date to', 'start-to', startTo, setStartTo)} + + + {dateComponent('End date from', 'end-from', endFrom, setEndFrom)} + + + {dateComponent('End date to', 'end-to', endTo, setEndTo)} + + + + + + + + + + + + ); + }; + + const buildTable = () => { + const headerLabels: Record = { + id: 'Process Instance Id', + process_model_identifier: 'Process Model', + start_in_seconds: 'Start Time', + end_in_seconds: 'End Time', + status: 'Status', + spiff_step: 'SpiffWorkflow Step', + }; + const getHeaderLabel = (header: string) => { + return headerLabels[header] ?? header; + }; + const headers = (reportMetadata as any).columns.map((column: any) => { + // return {getHeaderLabel((column as any).Header)}; + return getHeaderLabel((column as any).Header); + }); + + const formatProcessInstanceId = (row: any, id: any) => { + const modifiedProcessModelId: String = modifyProcessModelPath( + row.process_model_identifier + ); + return ( + + {id} + + ); + }; + const formatProcessModelIdentifier = (_row: any, identifier: any) => { + return ( + + {identifier} + + ); + }; + const formatSecondsForDisplay = (_row: any, seconds: any) => { + return convertSecondsToFormattedDate(seconds) || '-'; + }; + const defaultFormatter = (_row: any, value: any) => { + return value; + }; + + const columnFormatters: Record = { + id: formatProcessInstanceId, + process_model_identifier: formatProcessModelIdentifier, + start_in_seconds: formatSecondsForDisplay, + end_in_seconds: formatSecondsForDisplay, + }; + const formattedColumn = (row: any, column: any) => { + const formatter = columnFormatters[column.accessor] ?? defaultFormatter; + const value = row[column.accessor]; + if (column.accessor === 'status') { + return ( + + {formatter(row, value)} + + ); + } + return {formatter(row, value)}; + }; + + const rows = processInstances.map((row: any) => { + const currentRow = (reportMetadata as any).columns.map((column: any) => { + return formattedColumn(row, column); + }); + return {currentRow}; + }); + + return ( + + + + {headers.map((header: any) => ( + {header} + ))} + + + {rows} +
+ ); + }; + + const processInstanceBreadcrumbElement = () => { + const processModelFullIdentifier = + getProcessModelFullIdentifierFromSearchParams(searchParams); + if (processModelFullIdentifier === null) { + return null; + } + + return ( + + ); + }; + + const processInstanceTitleElement = () => { + return

Process Instances

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