From 6231ccab6cf43e45686f8f3c02812f0df54790c7 Mon Sep 17 00:00:00 2001 From: jasquat Date: Fri, 9 Dec 2022 11:23:18 -0500 Subject: [PATCH 01/27] some base work to try to get display names searchable for process models w/ burnettk --- .../src/spiffworkflow_backend/api.yml | 6 ++++++ .../routes/process_api_blueprint.py | 16 ++++++++++++---- .../src/components/ProcessModelSearch.tsx | 19 +++++++++++++++---- .../src/routes/ProcessGroupList.tsx | 2 +- 4 files changed, 34 insertions(+), 9 deletions(-) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/api.yml b/spiffworkflow-backend/src/spiffworkflow_backend/api.yml index f5836d63..52890114 100755 --- a/spiffworkflow-backend/src/spiffworkflow_backend/api.yml +++ b/spiffworkflow-backend/src/spiffworkflow_backend/api.yml @@ -285,6 +285,12 @@ paths: description: Get only the process models that the user can run schema: type: boolean + - name: include_parent_groups + in: query + required: false + description: Get the display names for the parent groups as well + schema: + type: boolean - name: page in: query required: false 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 d8effe0d..7e583de9 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py @@ -388,6 +388,7 @@ def process_model_list( process_group_identifier: Optional[str] = None, recursive: Optional[bool] = False, filter_runnable_by_user: Optional[bool] = False, + include_parent_groups: Optional[bool] = False, page: int = 1, per_page: int = 100, ) -> flask.wrappers.Response: @@ -397,22 +398,29 @@ def process_model_list( recursive=recursive, filter_runnable_by_user=filter_runnable_by_user, ) - batch = ProcessModelService().get_batch( + process_models_to_return = ProcessModelService().get_batch( process_models, page=page, per_page=per_page ) + + if include_parent_groups: + for process_model in process_models_to_return: + process_model.parent_groups = ProcessModelService.get_parent_group_array( + process_model.id + ) + pages = len(process_models) // per_page remainder = len(process_models) % per_page if remainder > 0: pages += 1 response_json = { - "results": ProcessModelInfoSchema(many=True).dump(batch), + "results": process_models_to_return, "pagination": { - "count": len(batch), + "count": len(process_models_to_return), "total": len(process_models), "pages": pages, }, } - return Response(json.dumps(response_json), status=200, mimetype="application/json") + return make_response(jsonify(response_json), 200) def process_list() -> Any: diff --git a/spiffworkflow-frontend/src/components/ProcessModelSearch.tsx b/spiffworkflow-frontend/src/components/ProcessModelSearch.tsx index 8a3c0b9f..06fa4d84 100644 --- a/spiffworkflow-frontend/src/components/ProcessModelSearch.tsx +++ b/spiffworkflow-frontend/src/components/ProcessModelSearch.tsx @@ -3,7 +3,7 @@ import { // @ts-ignore } from '@carbon/react'; import { truncateString } from '../helpers'; -import { ProcessModel } from '../interfaces'; +import { ProcessGroupLite, ProcessModel } from '../interfaces'; type OwnProps = { onChange: (..._args: any[]) => any; @@ -18,12 +18,23 @@ export default function ProcessModelSearch({ onChange, titleText = 'Process model', }: OwnProps) { + const getParentGroupsDisplayName = (processModel: ProcessModel) => { + if (processModel.parent_groups) { + return processModel.parent_groups + .map((parentGroup: ProcessGroupLite) => { + return parentGroup.display_name; + }) + .join(' '); + } + return ''; + }; + const shouldFilterProcessModel = (options: any) => { const processModel: ProcessModel = options.item; const { inputValue } = options; - return `${processModel.id} (${processModel.display_name})`.includes( - inputValue - ); + return `${processModel.id} (${getParentGroupsDisplayName(processModel)} ${ + processModel.display_name + })`.includes(inputValue); }; return ( Date: Thu, 15 Dec 2022 17:49:20 -0500 Subject: [PATCH 02/27] fixed a path issue with the breadcrumb --- spiffworkflow-frontend/src/routes/ProcessInstanceList.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/spiffworkflow-frontend/src/routes/ProcessInstanceList.tsx b/spiffworkflow-frontend/src/routes/ProcessInstanceList.tsx index b6c08b21..6b0da6de 100644 --- a/spiffworkflow-frontend/src/routes/ProcessInstanceList.tsx +++ b/spiffworkflow-frontend/src/routes/ProcessInstanceList.tsx @@ -6,7 +6,7 @@ import 'react-bootstrap-typeahead/css/Typeahead.css'; import 'react-bootstrap-typeahead/css/Typeahead.bs5.css'; import ProcessBreadcrumb from '../components/ProcessBreadcrumb'; import ProcessInstanceListTable from '../components/ProcessInstanceListTable'; -import { getProcessModelFullIdentifierFromSearchParams } from '../helpers'; +import {getProcessModelFullIdentifierFromSearchParams, modifyProcessIdentifierForPathParam} from '../helpers'; export default function ProcessInstanceList() { const [searchParams] = useSearchParams(); @@ -16,6 +16,9 @@ export default function ProcessInstanceList() { if (processModelFullIdentifier === null) { return null; } + const modifiedProcessModelId = modifyProcessIdentifierForPathParam( + processModelFullIdentifier + ); return ( Date: Thu, 15 Dec 2022 17:59:01 -0500 Subject: [PATCH 03/27] fixed process model tests --- .../cypress/e2e/process_models.cy.js | 71 +++++++------------ .../cypress/support/commands.js | 21 +++++- 2 files changed, 43 insertions(+), 49 deletions(-) diff --git a/spiffworkflow-frontend/cypress/e2e/process_models.cy.js b/spiffworkflow-frontend/cypress/e2e/process_models.cy.js index 4fd1b481..395ee768 100644 --- a/spiffworkflow-frontend/cypress/e2e/process_models.cy.js +++ b/spiffworkflow-frontend/cypress/e2e/process_models.cy.js @@ -8,16 +8,19 @@ describe('process-models', () => { cy.logout(); }); + const sharedResourceString = 'Shared Resources'; + const groupDisplayName = 'Acceptance Tests Group One'; + const deleteProcessModelButtonId = 'delete-process-model-button'; + it('can perform crud operations', () => { const uuid = () => Cypress._.random(0, 1e6); const id = uuid(); const groupId = 'misc/acceptance-tests-group-one'; - const groupDisplayName = 'Acceptance Tests Group One'; const modelDisplayName = `Test Model 2 ${id}`; const modelId = `test-model-2-${id}`; const newModelDisplayName = `${modelDisplayName} edited`; - cy.contains('99-Shared Resources').click(); - cy.wait(500); + cy.contains(sharedResourceString).click(); + cy.wait(750); cy.contains(groupDisplayName).click(); cy.createModel(groupId, modelId, modelDisplayName); cy.url().should( @@ -33,18 +36,8 @@ describe('process-models', () => { cy.contains('Submit').click(); cy.contains(`Process Model: ${newModelDisplayName}`); - // go back to process model show by clicking on the breadcrumb - cy.contains(modelId).click(); + cy.deleteProcessModelAndConfirm(deleteProcessModelButtonId, groupId); - cy.getBySel('delete-process-model-button').click(); - cy.contains('Are you sure'); - cy.getBySel('delete-process-model-button-modal-confirmation-dialog') - .find('.cds--btn--danger') - .click(); - cy.url().should( - 'include', - `process-groups/${modifyProcessIdentifierForPathParam(groupId)}` - ); cy.contains(modelId).should('not.exist'); }); @@ -52,20 +45,22 @@ describe('process-models', () => { const uuid = () => Cypress._.random(0, 1e6); const id = uuid(); const directParentGroupId = 'acceptance-tests-group-one'; + const directParentGroupName = 'Acceptance Tests Group One'; const groupId = `misc/${directParentGroupId}`; - const groupDisplayName = 'Acceptance Tests Group One'; const modelDisplayName = `Test Model 2 ${id}`; const modelId = `test-model-2-${id}`; const bpmnFileName = `bpmn_test_file_${id}`; const dmnFileName = `dmn_test_file_${id}`; const jsonFileName = `json_test_file_${id}`; + const decision_acceptance_test_id = `decision_acceptance_test_${id}`; - cy.contains('99-Shared Resources').click(); + cy.contains(sharedResourceString).click(); cy.wait(500); cy.contains(groupDisplayName).click(); cy.createModel(groupId, modelId, modelDisplayName); - cy.contains(directParentGroupId).click(); + cy.contains(directParentGroupName).click(); + cy.wait(500); cy.contains(modelDisplayName).click(); cy.url().should( 'include', @@ -90,7 +85,7 @@ describe('process-models', () => { cy.get('input[name=file_name]').type(bpmnFileName); cy.contains('Save Changes').click(); cy.contains(`Process Model File: ${bpmnFileName}`); - cy.contains(modelId).click(); + cy.contains(modelDisplayName).click(); cy.contains(`Process Model: ${modelDisplayName}`); // cy.getBySel('files-accordion').click(); cy.contains(`${bpmnFileName}.bpmn`).should('exist'); @@ -102,13 +97,13 @@ describe('process-models', () => { cy.contains('General').click(); cy.get('#bio-properties-panel-id') .clear() - .type('decision_acceptance_test_1'); + .type(decision_acceptance_test_id); cy.contains('General').click(); cy.contains('Save').click(); cy.get('input[name=file_name]').type(dmnFileName); cy.contains('Save Changes').click(); cy.contains(`Process Model File: ${dmnFileName}`); - cy.contains(modelId).click(); + cy.contains(modelDisplayName).click(); cy.contains(`Process Model: ${modelDisplayName}`); // cy.getBySel('files-accordion').click(); cy.contains(`${dmnFileName}.dmn`).should('exist'); @@ -124,20 +119,12 @@ describe('process-models', () => { cy.contains(`Process Model File: ${jsonFileName}`); // wait for json to load before clicking away to avoid network errors cy.wait(500); - cy.contains(modelId).click(); + cy.contains(modelDisplayName).click(); cy.contains(`Process Model: ${modelDisplayName}`); // cy.getBySel('files-accordion').click(); cy.contains(`${jsonFileName}.json`).should('exist'); - cy.getBySel('delete-process-model-button').click(); - cy.contains('Are you sure'); - cy.getBySel('delete-process-model-button-modal-confirmation-dialog') - .find('.cds--btn--danger') - .click(); - cy.url().should( - 'include', - `process-groups/${modifyProcessIdentifierForPathParam(groupId)}` - ); + cy.deleteProcessModelAndConfirm(deleteProcessModelButtonId, groupId); cy.contains(modelId).should('not.exist'); cy.contains(modelDisplayName).should('not.exist'); }); @@ -147,16 +134,15 @@ describe('process-models', () => { const id = uuid(); const directParentGroupId = 'acceptance-tests-group-one'; const groupId = `misc/${directParentGroupId}`; - const groupDisplayName = 'Acceptance Tests Group One'; const modelDisplayName = `Test Model 2 ${id}`; const modelId = `test-model-2-${id}`; cy.contains('Add a process group'); - cy.contains('99-Shared Resources').click(); + cy.contains(sharedResourceString).click(); cy.wait(500); cy.contains(groupDisplayName).click(); cy.createModel(groupId, modelId, modelDisplayName); - cy.contains(`${directParentGroupId}`).click(); + cy.contains(`${groupDisplayName}`).click(); cy.contains('Add a process model'); cy.contains(modelDisplayName).click(); cy.url().should( @@ -188,27 +174,20 @@ describe('process-models', () => { // in breadcrumb cy.contains(modelId).click(); - cy.getBySel('delete-process-model-button').click(); + cy.getBySel(deleteProcessModelButtonId).click(); cy.contains('Are you sure'); cy.getBySel('delete-process-model-button-modal-confirmation-dialog') .find('.cds--btn--danger') .click(); - cy.url().should( - 'include', - `process-groups/${modifyProcessIdentifierForPathParam(groupId)}` - ); + // cy.url().should( + // 'include', + // `process-instances?process_model_identifier=${modifyProcessIdentifierForPathParam(groupId)}` + // ); + // cy.wait(750); cy.contains(modelId).should('not.exist'); cy.contains(modelDisplayName).should('not.exist'); }); - // process models no longer has pagination post-tiles - // it.only('can paginate items', () => { - // cy.contains('99-Shared Resources').click(); - // cy.wait(500); - // cy.contains('Acceptance Tests Group One').click(); - // cy.basicPaginationTest(); - // }); - it('can allow searching for model', () => { cy.getBySel('process-model-selection').click().type('model-3'); cy.contains('acceptance-tests-group-one/acceptance-tests-model-3').click(); diff --git a/spiffworkflow-frontend/cypress/support/commands.js b/spiffworkflow-frontend/cypress/support/commands.js index f0034168..0112ea31 100644 --- a/spiffworkflow-frontend/cypress/support/commands.js +++ b/spiffworkflow-frontend/cypress/support/commands.js @@ -86,15 +86,15 @@ Cypress.Commands.add('createModel', (groupId, modelId, modelDisplayName) => { Cypress.Commands.add( 'runPrimaryBpmnFile', (expectAutoRedirectToHumanTask = false) => { - cy.contains('Run').click(); + cy.contains('Start').click(); if (expectAutoRedirectToHumanTask) { // the url changes immediately, so also make sure we get some content from the next page, "Task:", or else when we try to interact with the page, it'll re-render and we'll get an error with cypress. cy.url().should('include', `/tasks/`); cy.contains('Task: '); } else { - cy.contains(/Process Instance.*kicked off/); + cy.contains(/Process Instance Kicked Off/); cy.reload(true); - cy.contains(/Process Instance.*kicked off/).should('not.exist'); + cy.contains(/Process Instance Kicked Off/).should('not.exist'); } } ); @@ -132,3 +132,18 @@ Cypress.Commands.add('assertAtLeastOneItemInPaginatedResults', () => { Cypress.Commands.add('assertNoItemInPaginatedResults', () => { cy.contains(/\b0–0 of 0 items/); }); + +Cypress.Commands.add( + 'deleteProcessModelAndConfirm', + (buttonId, groupId) => { + cy.getBySel(buttonId).click(); + cy.contains('Are you sure'); + cy.getBySel('delete-process-model-button-modal-confirmation-dialog') + .find('.cds--btn--danger') + .click(); + cy.url().should( + 'include', + `process-groups/${modifyProcessIdentifierForPathParam(groupId)}` + ); + } +); From 70980c5261e07afd8e30b21f77c96b28153c6ebc Mon Sep 17 00:00:00 2001 From: burnettk Date: Wed, 21 Dec 2022 09:29:54 -0500 Subject: [PATCH 04/27] lint and upgrade cypress --- spiffworkflow-frontend/package-lock.json | 12 ++++++------ .../src/routes/ProcessInstanceList.tsx | 5 +---- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/spiffworkflow-frontend/package-lock.json b/spiffworkflow-frontend/package-lock.json index 4ccea192..54cf2c89 100644 --- a/spiffworkflow-frontend/package-lock.json +++ b/spiffworkflow-frontend/package-lock.json @@ -9850,9 +9850,9 @@ "integrity": "sha512-NJGVKPS81XejHcLhaLJS7plab0fK3slPh11mESeeDq2W4ZI5kUKK/LRRdVDvjJseojbPB7ZwjnyOybg3Igea/A==" }, "node_modules/cypress": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-12.1.0.tgz", - "integrity": "sha512-7fz8N84uhN1+ePNDsfQvoWEl4P3/VGKKmAg+bJQFY4onhA37Ys+6oBkGbNdwGeC7n2QqibNVPhk8x3YuQLwzfw==", + "version": "12.2.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-12.2.0.tgz", + "integrity": "sha512-kvl95ri95KK8mAy++tEU/wUgzAOMiIciZSL97LQvnOinb532m7dGvwN0mDSIGbOd71RREtmT9o4h088RjK5pKw==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -38586,9 +38586,9 @@ "integrity": "sha512-NJGVKPS81XejHcLhaLJS7plab0fK3slPh11mESeeDq2W4ZI5kUKK/LRRdVDvjJseojbPB7ZwjnyOybg3Igea/A==" }, "cypress": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-12.1.0.tgz", - "integrity": "sha512-7fz8N84uhN1+ePNDsfQvoWEl4P3/VGKKmAg+bJQFY4onhA37Ys+6oBkGbNdwGeC7n2QqibNVPhk8x3YuQLwzfw==", + "version": "12.2.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-12.2.0.tgz", + "integrity": "sha512-kvl95ri95KK8mAy++tEU/wUgzAOMiIciZSL97LQvnOinb532m7dGvwN0mDSIGbOd71RREtmT9o4h088RjK5pKw==", "dev": true, "requires": { "@cypress/request": "^2.88.10", diff --git a/spiffworkflow-frontend/src/routes/ProcessInstanceList.tsx b/spiffworkflow-frontend/src/routes/ProcessInstanceList.tsx index 29357fe4..51c09981 100644 --- a/spiffworkflow-frontend/src/routes/ProcessInstanceList.tsx +++ b/spiffworkflow-frontend/src/routes/ProcessInstanceList.tsx @@ -9,7 +9,7 @@ import { Tabs, TabList, Tab } from '@carbon/react'; import { Can } from '@casl/react'; import ProcessBreadcrumb from '../components/ProcessBreadcrumb'; import ProcessInstanceListTable from '../components/ProcessInstanceListTable'; -import {getProcessModelFullIdentifierFromSearchParams, modifyProcessIdentifierForPathParam} from '../helpers'; +import { getProcessModelFullIdentifierFromSearchParams } from '../helpers'; import { useUriListForPermissions } from '../hooks/UriListForPermissions'; import { PermissionsToCheck } from '../interfaces'; import { usePermissionFetcher } from '../hooks/PermissionService'; @@ -34,9 +34,6 @@ export default function ProcessInstanceList({ variant }: OwnProps) { if (processModelFullIdentifier === null) { return null; } - const modifiedProcessModelId = modifyProcessIdentifierForPathParam( - processModelFullIdentifier - ); return ( Date: Fri, 30 Dec 2022 12:30:23 -0500 Subject: [PATCH 05/27] added all users to waiting for column on task list tables w/ burnettk --- .../config/permissions/development.yml | 2 +- .../routes/tasks_controller.py | 45 ++++++++---- .../services/process_instance_processor.py | 14 ++-- spiffworkflow-frontend/src/App.tsx | 2 +- .../src/components/NavigationBar.tsx | 2 +- .../src/components/TaskListTable.tsx | 70 +++++++++++++------ spiffworkflow-frontend/src/interfaces.ts | 13 +++- .../src/services/UserService.ts | 14 +++- 8 files changed, 114 insertions(+), 48 deletions(-) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/development.yml b/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/development.yml index 397f10c1..ee40f839 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/development.yml +++ b/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/development.yml @@ -63,7 +63,7 @@ groups: admin-ro: users: [ - j, + j@sartography.com, ] permissions: diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/tasks_controller.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/tasks_controller.py index c80da91f..a8f899b5 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/tasks_controller.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/tasks_controller.py @@ -15,11 +15,14 @@ from flask import jsonify from flask import make_response from flask.wrappers import Response 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 TaskState from sqlalchemy import and_ from sqlalchemy import asc from sqlalchemy import desc +from sqlalchemy import func +from sqlalchemy.orm import aliased from spiffworkflow_backend.models.group import GroupModel from spiffworkflow_backend.models.human_task import HumanTaskModel @@ -147,6 +150,18 @@ def task_show(process_instance_id: int, task_id: str) -> flask.wrappers.Response process_instance.process_model_identifier, ) + human_task = HumanTaskModel.query.filter_by( + process_instance_id=process_instance_id, task_id=task_id + ).first() + if human_task is None: + raise ( + ApiError( + error_code="no_human_task", + message=f"Cannot find a task to complete for task id '{task_id}' and process instance {process_instance_id}.", + status_code=500, + ) + ) + form_schema_file_name = "" form_ui_schema_file_name = "" spiff_task = _get_spiff_task_from_process_instance(task_id, process_instance) @@ -302,7 +317,7 @@ def task_submit( raise ( ApiError( error_code="no_human_task", - message="Cannot find an human task with task id '{task_id}' for process instance {process_instance_id}.", + message=f"Cannot find a task to complete for task id '{task_id}' and process instance {process_instance_id}.", status_code=500, ) ) @@ -357,22 +372,25 @@ def _get_tasks( # pagination later on # https://stackoverflow.com/q/34582014/6090676 human_tasks_query = ( - HumanTaskModel.query.distinct() + db.session.query(HumanTaskModel) + .group_by(HumanTaskModel.id) # type: ignore .outerjoin(GroupModel, GroupModel.id == HumanTaskModel.lane_assignment_id) .join(ProcessInstanceModel) .join(UserModel, UserModel.id == ProcessInstanceModel.process_initiator_id) .filter(HumanTaskModel.completed == False) # noqa: E712 ) + assigned_user = aliased(UserModel) if processes_started_by_user: - human_tasks_query = human_tasks_query.filter( - ProcessInstanceModel.process_initiator_id == user_id - ).outerjoin( - HumanTaskUserModel, - and_( - HumanTaskUserModel.user_id == user_id, + human_tasks_query = ( + human_tasks_query.filter( + ProcessInstanceModel.process_initiator_id == user_id + ) + .outerjoin( + HumanTaskUserModel, HumanTaskModel.id == HumanTaskUserModel.human_task_id, - ), + ) + .outerjoin(assigned_user, assigned_user.id == HumanTaskUserModel.user_id) ) else: human_tasks_query = human_tasks_query.filter( @@ -402,13 +420,15 @@ def _get_tasks( ProcessInstanceModel.status.label("process_instance_status"), # type: ignore ProcessInstanceModel.updated_at_in_seconds, ProcessInstanceModel.created_at_in_seconds, - UserModel.username, - GroupModel.identifier.label("user_group_identifier"), + UserModel.username.label("process_initiator_username"), + GroupModel.identifier.label("assigned_user_group_identifier"), HumanTaskModel.task_name, HumanTaskModel.task_title, HumanTaskModel.process_model_display_name, HumanTaskModel.process_instance_id, - HumanTaskUserModel.user_id.label("current_user_is_potential_owner"), + func.group_concat(assigned_user.username.distinct()).label( + "potential_owner_usernames" + ), ) .order_by(desc(HumanTaskModel.id)) # type: ignore .paginate(page=page, per_page=per_page, error_out=False) @@ -422,6 +442,7 @@ def _get_tasks( "pages": human_tasks.pages, }, } + return make_response(jsonify(response_json), 200) 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 f9e6c620..6b03d4ce 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py @@ -581,12 +581,6 @@ class ProcessInstanceProcessor: ) return details_model - def save_spiff_step_details(self) -> None: - """SaveSpiffStepDetails.""" - details_model = self.spiff_step_details() - db.session.add(details_model) - db.session.commit() - def extract_metadata(self, process_model_info: ProcessModelInfo) -> None: """Extract_metadata.""" metadata_extraction_paths = process_model_info.metadata_extraction_paths @@ -1233,9 +1227,13 @@ class ProcessInstanceProcessor: self.increment_spiff_step() self.bpmn_process_instance.complete_task_from_id(task.id) human_task.completed_by_user_id = user.id + human_task.completed = True db.session.add(human_task) - db.session.commit() - self.save_spiff_step_details() + details_model = self.spiff_step_details() + db.session.add(details_model) + + # this is the thing that actually commits the db transaction (on behalf of the other updates above as well) + self.save() def get_data(self) -> dict[str, Any]: """Get_data.""" diff --git a/spiffworkflow-frontend/src/App.tsx b/spiffworkflow-frontend/src/App.tsx index c052b6a0..73489083 100644 --- a/spiffworkflow-frontend/src/App.tsx +++ b/spiffworkflow-frontend/src/App.tsx @@ -49,7 +49,7 @@ export default function App() { let message =
{errorObject.message}
; let title = 'Error:'; - if ('task_name' in errorObject) { + if ('task_name' in errorObject && errorObject.task_name) { title = `Error in python script:`; message = ( <> diff --git a/spiffworkflow-frontend/src/components/NavigationBar.tsx b/spiffworkflow-frontend/src/components/NavigationBar.tsx index 7a0ffd3e..e482ae52 100644 --- a/spiffworkflow-frontend/src/components/NavigationBar.tsx +++ b/spiffworkflow-frontend/src/components/NavigationBar.tsx @@ -81,7 +81,7 @@ export default function NavigationBar() { return ( <> - {UserService.getUsername()} + {UserService.getPreferredUsername()} (null); const [pagination, setPagination] = useState(null); + const preferredUsername = UserService.getPreferredUsername(); + const userEmail = UserService.getUserEmail(); + useEffect(() => { const getTasks = () => { const { page, perPage } = getPageInfoFromSearchParams( @@ -80,56 +84,82 @@ export default function TaskListTable({ autoReload, ]); + const getWaitingForTableCellComponent = ( + processInstanceTask: ProcessInstanceTask + ) => { + let fullUsernameString = ''; + let shortUsernameString = ''; + if (processInstanceTask.assigned_user_group_identifier) { + fullUsernameString = processInstanceTask.assigned_user_group_identifier; + shortUsernameString = processInstanceTask.assigned_user_group_identifier; + } + if (processInstanceTask.potential_owner_usernames) { + fullUsernameString = processInstanceTask.potential_owner_usernames; + const usernames = + processInstanceTask.potential_owner_usernames.split(','); + const firstTwoUsernames = usernames.slice(0, 2); + if (usernames.length > 2) { + firstTwoUsernames.push('...'); + } + shortUsernameString = firstTwoUsernames.join(','); + } + return {shortUsernameString}; + }; + const buildTable = () => { if (!tasks) { return null; } - const rows = tasks.map((row) => { - const rowToUse = row as any; - const taskUrl = `/tasks/${rowToUse.process_instance_id}/${rowToUse.task_id}`; + const rows = tasks.map((row: ProcessInstanceTask) => { + const taskUrl = `/tasks/${row.process_instance_id}/${row.task_id}`; const modifiedProcessModelIdentifier = - modifyProcessIdentifierForPathParam(rowToUse.process_model_identifier); + modifyProcessIdentifierForPathParam(row.process_model_identifier); + + const regex = new RegExp(`\\b(${preferredUsername}|${userEmail})\\b`); + let hasAccessToCompleteTask = false; + if (row.potential_owner_usernames.match(regex)) { + hasAccessToCompleteTask = true; + } return ( - + - {rowToUse.process_instance_id} + {row.process_instance_id} - {rowToUse.process_model_display_name} + {row.process_model_display_name} - {rowToUse.task_title} + {row.task_title} - {showStartedBy ? {rowToUse.username} : ''} - {showWaitingOn ? {rowToUse.group_identifier || '-'} : ''} + {showStartedBy ? {row.process_initiator_username} : ''} + {showWaitingOn ? {getWaitingForTableCellComponent(row)} : ''} - {convertSecondsToFormattedDateTime( - rowToUse.created_at_in_seconds - ) || '-'} + {convertSecondsToFormattedDateTime(row.created_at_in_seconds) || + '-'} diff --git a/spiffworkflow-frontend/src/interfaces.ts b/spiffworkflow-frontend/src/interfaces.ts index e58747b1..bb368013 100644 --- a/spiffworkflow-frontend/src/interfaces.ts +++ b/spiffworkflow-frontend/src/interfaces.ts @@ -17,16 +17,23 @@ export interface RecentProcessModel { } export interface ProcessInstanceTask { - id: string; + id: number; + task_id: string; + process_instance_id: number; process_model_display_name: string; process_model_identifier: string; task_title: string; lane_assignment_id: string; - process_instance_status: number; - updated_at_in_seconds: number; + process_instance_status: string; state: string; process_identifier: string; name: string; + process_initiator_username: string; + assigned_user_group_identifier: string; + created_at_in_seconds: number; + updated_at_in_seconds: number; + current_user_is_potential_owner: number; + potential_owner_usernames: string; } export interface ProcessReference { diff --git a/spiffworkflow-frontend/src/services/UserService.ts b/spiffworkflow-frontend/src/services/UserService.ts index df0f213e..53717b8d 100644 --- a/spiffworkflow-frontend/src/services/UserService.ts +++ b/spiffworkflow-frontend/src/services/UserService.ts @@ -39,7 +39,16 @@ const isLoggedIn = () => { return !!getAuthToken(); }; -const getUsername = () => { +const getUserEmail = () => { + const idToken = getIdToken(); + if (idToken) { + const idObject = jwt(idToken); + return (idObject as any).email; + } + return null; +}; + +const getPreferredUsername = () => { const idToken = getIdToken(); if (idToken) { const idObject = jwt(idToken); @@ -78,7 +87,8 @@ const UserService = { isLoggedIn, getAuthToken, getAuthTokenFromParams, - getUsername, + getPreferredUsername, + getUserEmail, hasRole, }; From bd96844caaa2b3d4c31b4359b199ae5d993887eb Mon Sep 17 00:00:00 2001 From: jasquat Date: Fri, 30 Dec 2022 13:59:52 -0500 Subject: [PATCH 06/27] do not error when removing columns from instance column filters w/ burnettk --- .../components/ProcessInstanceListTable.tsx | 60 ++++++++++--------- spiffworkflow-frontend/src/index.css | 4 +- 2 files changed, 35 insertions(+), 29 deletions(-) diff --git a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx index 5058235b..2fc81265 100644 --- a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx +++ b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx @@ -774,7 +774,6 @@ export default function ProcessInstanceListTable({ setReportMetadata(reportMetadataCopy); setReportColumnToOperateOn(null); setShowReportColumnForm(false); - setShowReportColumnForm(false); } }; @@ -795,9 +794,12 @@ export default function ProcessInstanceListTable({ }; const updateReportColumn = (event: any) => { - const reportColumnForEditing = reportColumnToReportColumnForEditing( - event.selectedItem - ); + let reportColumnForEditing = null; + if (event.selectedItem) { + reportColumnForEditing = reportColumnToReportColumnForEditing( + event.selectedItem + ); + } setReportColumnToOperateOn(reportColumnForEditing); }; @@ -827,7 +829,29 @@ export default function ProcessInstanceListTable({ if (reportColumnFormMode === '') { return null; } - const formElements = [ + const formElements = []; + if (reportColumnFormMode === 'new') { + formElements.push( + { + if (reportColumn) { + return reportColumn.accessor; + } + return null; + }} + shouldFilterItem={shouldFilterReportColumn} + placeholder="Choose a column to show" + titleText="Column" + selectedItem={reportColumnToOperateOn} + /> + ); + } + formElements.push([ , - ]; + ]); if (reportColumnToOperateOn && reportColumnToOperateOn.filterable) { formElements.push( ); } - if (reportColumnFormMode === 'new') { - formElements.push( - { - if (reportColumn) { - return reportColumn.accessor; - } - return null; - }} - shouldFilterItem={shouldFilterReportColumn} - placeholder="Choose a column to show" - titleText="Column" - /> - ); - } + formElements.push( +
+ ); const modalHeading = reportColumnFormMode === 'new' ? 'Add Column' diff --git a/spiffworkflow-frontend/src/index.css b/spiffworkflow-frontend/src/index.css index 248a23d7..08e8341c 100644 --- a/spiffworkflow-frontend/src/index.css +++ b/spiffworkflow-frontend/src/index.css @@ -355,8 +355,8 @@ svg.notification-icon { word-break: normal; } -.combo-box-in-modal { - height: 300px; +.vertical-spacer-to-allow-combo-box-to-expand-in-modal { + height: 250px; } .cds--btn.narrow-button { From 9058a27b10ee903d39b4f3143a8e078b94f6c039 Mon Sep 17 00:00:00 2001 From: jasquat Date: Fri, 30 Dec 2022 15:05:22 -0500 Subject: [PATCH 07/27] moved error display to own component w/ burnettk --- spiffworkflow-frontend/src/App.tsx | 93 ++++++++++--------- .../src/components/ErrorDisplay.tsx | 55 +++++++++++ .../src/hooks/PermissionService.tsx | 10 +- .../src/routes/ProcessModelShow.tsx | 26 ++++-- 4 files changed, 129 insertions(+), 55 deletions(-) create mode 100644 spiffworkflow-frontend/src/components/ErrorDisplay.tsx diff --git a/spiffworkflow-frontend/src/App.tsx b/spiffworkflow-frontend/src/App.tsx index 73489083..23eefa44 100644 --- a/spiffworkflow-frontend/src/App.tsx +++ b/spiffworkflow-frontend/src/App.tsx @@ -15,6 +15,7 @@ import { ErrorForDisplay } from './interfaces'; import { AbilityContext } from './contexts/Can'; import UserService from './services/UserService'; import { Notification } from './components/Notification'; +import ErrorDisplay from './components/ErrorDisplay'; export default function App() { const [errorObject, setErrorObject] = useState(null); @@ -31,51 +32,51 @@ export default function App() { const ability = defineAbility(() => {}); - let errorTag = null; - if (errorObject) { - let sentryLinkTag = null; - if (errorObject.sentry_link) { - sentryLinkTag = ( - - { - ': Find details about this error here (it may take a moment to become available): ' - } - - {errorObject.sentry_link} - - - ); - } - - let message =
{errorObject.message}
; - let title = 'Error:'; - if ('task_name' in errorObject && errorObject.task_name) { - title = `Error in python script:`; - message = ( - <> -
-
- Task: {errorObject.task_name} ({errorObject.task_id}) -
-
File name: {errorObject.file_name}
-
Line number in script task: {errorObject.line_number}
-
-
{errorObject.message}
- - ); - } - - errorTag = ( - setErrorObject(null)} - type="error" - > - {message} - {sentryLinkTag} - - ); - } + // let errorTag = null; + // if (errorObject) { + // let sentryLinkTag = null; + // if (errorObject.sentry_link) { + // sentryLinkTag = ( + // + // { + // ': Find details about this error here (it may take a moment to become available): ' + // } + // + // {errorObject.sentry_link} + // + // + // ); + // } + // + // let message =
{errorObject.message}
; + // let title = 'Error:'; + // if ('task_name' in errorObject && errorObject.task_name) { + // title = 'Error in python script:'; + // message = ( + // <> + //
+ //
+ // Task: {errorObject.task_name} ({errorObject.task_id}) + //
+ //
File name: {errorObject.file_name}
+ //
Line number in script task: {errorObject.line_number}
+ //
+ //
{errorObject.message}
+ // + // ); + // } + // + // errorTag = ( + // setErrorObject(null)} + // type="error" + // > + // {message} + // {sentryLinkTag} + // + // ); + // } return (
@@ -85,7 +86,7 @@ export default function App() { - {errorTag} + } /> diff --git a/spiffworkflow-frontend/src/components/ErrorDisplay.tsx b/spiffworkflow-frontend/src/components/ErrorDisplay.tsx new file mode 100644 index 00000000..ca0a343a --- /dev/null +++ b/spiffworkflow-frontend/src/components/ErrorDisplay.tsx @@ -0,0 +1,55 @@ +import { useContext } from 'react'; +import ErrorContext from 'src/contexts/ErrorContext'; +import { Notification } from './Notification'; + +export default function ErrorDisplay() { + const [errorObject, setErrorObject] = (useContext as any)(ErrorContext); + + let errorTag = null; + if (errorObject) { + let sentryLinkTag = null; + if (errorObject.sentry_link) { + sentryLinkTag = ( + + { + ': Find details about this error here (it may take a moment to become available): ' + } + + {errorObject.sentry_link} + + + ); + } + + let message =
{errorObject.message}
; + let title = 'Error:'; + if ('task_name' in errorObject && errorObject.task_name) { + title = 'Error in python script:'; + message = ( + <> +
+
+ Task: {errorObject.task_name} ({errorObject.task_id}) +
+
File name: {errorObject.file_name}
+
Line number in script task: {errorObject.line_number}
+
+
{errorObject.message}
+ + ); + } + + errorTag = ( + setErrorObject(null)} + type="error" + > + {message} + {sentryLinkTag} + + ); + } + + return errorTag; +} diff --git a/spiffworkflow-frontend/src/hooks/PermissionService.tsx b/spiffworkflow-frontend/src/hooks/PermissionService.tsx index fad496a7..f7ca4e4b 100644 --- a/spiffworkflow-frontend/src/hooks/PermissionService.tsx +++ b/spiffworkflow-frontend/src/hooks/PermissionService.tsx @@ -35,6 +35,14 @@ export const usePermissionFetcher = ( } }); ability.update(rules); + console.log('SETTING PERMISSIONS'); + const thePERMMap = (ability as any).j; + console.log( + 'thePERMMAP', + thePERMMap.get( + '/v1.0/process-models/misc:category_number_one:workflow_one/files' + ) + ); setPermissionsLoaded(true); }; if (Object.keys(permissionsToCheck).length !== 0) { @@ -47,5 +55,5 @@ export const usePermissionFetcher = ( } }); - return { ability, permissionsLoaded }; + return { ability, permissionsLoaded, setPermissionsLoaded }; }; diff --git a/spiffworkflow-frontend/src/routes/ProcessModelShow.tsx b/spiffworkflow-frontend/src/routes/ProcessModelShow.tsx index adfa203b..a547638f 100644 --- a/spiffworkflow-frontend/src/routes/ProcessModelShow.tsx +++ b/spiffworkflow-frontend/src/routes/ProcessModelShow.tsx @@ -73,9 +73,8 @@ export default function ProcessModelShow() { [targetUris.processInstanceCreatePath]: ['POST'], [targetUris.processModelFileCreatePath]: ['POST', 'PUT', 'GET', 'DELETE'], }; - const { ability, permissionsLoaded } = usePermissionFetcher( - permissionRequestData - ); + const { ability, permissionsLoaded, setPermissionsLoaded } = + usePermissionFetcher(permissionRequestData); const modifiedProcessModelId = modifyProcessIdentifierForPathParam( `${params.process_model_id}` @@ -234,6 +233,10 @@ export default function ProcessModelShow() { const elements = []; let icon = View; let actionWord = 'View'; + console.log( + 'targetUris.processModelFileCreatePath', + targetUris.processModelFileCreatePath + ); if (ability.can('PUT', targetUris.processModelFileCreatePath)) { icon = Edit; actionWord = 'Edit'; @@ -306,6 +309,17 @@ export default function ProcessModelShow() { if (!processModel || !permissionsLoaded) { return null; } + const permLoad = JSON.stringify(permissionsLoaded); + console.log('permLoad', permLoad); + const theMap = (ability as any).j; + // console.log('theMap', theMap[targetUris.processModelFileCreatePath]); + // console.log('theMap', theMap); + console.log( + 'theMap', + theMap.get( + '/v1.0/process-models/misc:category_number_one:workflow_one/files' + ) + ); let constructedTag; const tags = processModel.files.map((processModelFile: ProcessFile) => { const isPrimaryBpmnFile = @@ -327,11 +341,7 @@ export default function ProcessModelShow() { let fileLink = null; const fileUrl = profileModelFileEditUrl(processModelFile); if (fileUrl) { - if (ability.can('GET', targetUris.processModelFileCreatePath)) { - fileLink = {processModelFile.name}; - } else { - fileLink = {processModelFile.name}; - } + fileLink = {processModelFile.name}; } constructedTag = ( From 3506b19f66bcc188d0711e6ca95440a99551d1ee Mon Sep 17 00:00:00 2001 From: jasquat Date: Fri, 30 Dec 2022 15:30:32 -0500 Subject: [PATCH 08/27] cleaned up some debug code w/ burnettk --- spiffworkflow-frontend/src/App.tsx | 47 ------------------- .../src/components/ErrorDisplay.tsx | 2 +- .../src/hooks/PermissionService.tsx | 10 +--- .../src/routes/ProcessModelShow.tsx | 29 +++++------- 4 files changed, 14 insertions(+), 74 deletions(-) diff --git a/spiffworkflow-frontend/src/App.tsx b/spiffworkflow-frontend/src/App.tsx index 23eefa44..ecf9fc54 100644 --- a/spiffworkflow-frontend/src/App.tsx +++ b/spiffworkflow-frontend/src/App.tsx @@ -14,7 +14,6 @@ import { ErrorForDisplay } from './interfaces'; import { AbilityContext } from './contexts/Can'; import UserService from './services/UserService'; -import { Notification } from './components/Notification'; import ErrorDisplay from './components/ErrorDisplay'; export default function App() { @@ -32,52 +31,6 @@ export default function App() { const ability = defineAbility(() => {}); - // let errorTag = null; - // if (errorObject) { - // let sentryLinkTag = null; - // if (errorObject.sentry_link) { - // sentryLinkTag = ( - // - // { - // ': Find details about this error here (it may take a moment to become available): ' - // } - // - // {errorObject.sentry_link} - // - // - // ); - // } - // - // let message =
{errorObject.message}
; - // let title = 'Error:'; - // if ('task_name' in errorObject && errorObject.task_name) { - // title = 'Error in python script:'; - // message = ( - // <> - //
- //
- // Task: {errorObject.task_name} ({errorObject.task_id}) - //
- //
File name: {errorObject.file_name}
- //
Line number in script task: {errorObject.line_number}
- //
- //
{errorObject.message}
- // - // ); - // } - // - // errorTag = ( - // setErrorObject(null)} - // type="error" - // > - // {message} - // {sentryLinkTag} - // - // ); - // } - return (
{/* @ts-ignore */} diff --git a/spiffworkflow-frontend/src/components/ErrorDisplay.tsx b/spiffworkflow-frontend/src/components/ErrorDisplay.tsx index ca0a343a..cdbed75a 100644 --- a/spiffworkflow-frontend/src/components/ErrorDisplay.tsx +++ b/spiffworkflow-frontend/src/components/ErrorDisplay.tsx @@ -1,5 +1,5 @@ import { useContext } from 'react'; -import ErrorContext from 'src/contexts/ErrorContext'; +import ErrorContext from '../contexts/ErrorContext'; import { Notification } from './Notification'; export default function ErrorDisplay() { diff --git a/spiffworkflow-frontend/src/hooks/PermissionService.tsx b/spiffworkflow-frontend/src/hooks/PermissionService.tsx index f7ca4e4b..fad496a7 100644 --- a/spiffworkflow-frontend/src/hooks/PermissionService.tsx +++ b/spiffworkflow-frontend/src/hooks/PermissionService.tsx @@ -35,14 +35,6 @@ export const usePermissionFetcher = ( } }); ability.update(rules); - console.log('SETTING PERMISSIONS'); - const thePERMMap = (ability as any).j; - console.log( - 'thePERMMAP', - thePERMMap.get( - '/v1.0/process-models/misc:category_number_one:workflow_one/files' - ) - ); setPermissionsLoaded(true); }; if (Object.keys(permissionsToCheck).length !== 0) { @@ -55,5 +47,5 @@ export const usePermissionFetcher = ( } }); - return { ability, permissionsLoaded, setPermissionsLoaded }; + return { ability, permissionsLoaded }; }; diff --git a/spiffworkflow-frontend/src/routes/ProcessModelShow.tsx b/spiffworkflow-frontend/src/routes/ProcessModelShow.tsx index a547638f..461c6d84 100644 --- a/spiffworkflow-frontend/src/routes/ProcessModelShow.tsx +++ b/spiffworkflow-frontend/src/routes/ProcessModelShow.tsx @@ -73,8 +73,9 @@ export default function ProcessModelShow() { [targetUris.processInstanceCreatePath]: ['POST'], [targetUris.processModelFileCreatePath]: ['POST', 'PUT', 'GET', 'DELETE'], }; - const { ability, permissionsLoaded, setPermissionsLoaded } = - usePermissionFetcher(permissionRequestData); + const { ability, permissionsLoaded } = usePermissionFetcher( + permissionRequestData + ); const modifiedProcessModelId = modifyProcessIdentifierForPathParam( `${params.process_model_id}` @@ -231,12 +232,17 @@ export default function ProcessModelShow() { isPrimaryBpmnFile: boolean ) => { const elements = []; + + // So there is a bug in here. Since we use a react context for error messages, and since + // its provider wraps the entire app, child components will re-render when there is an + // error displayed. This is normally fine, but it interacts badly with the casl ability.can + // functionality. We have observed that permissionsLoaded is never set to false. So when + // you run a process and it fails, for example, process model show will re-render, the ability + // will be cleared out and it will start fetching permissions from the server, but this + // component still thinks permissionsLoaded is telling the truth (it says true, but it's actually false). + // The only bad effect that we know of is that the Edit icon becomes an eye icon even for admins. let icon = View; let actionWord = 'View'; - console.log( - 'targetUris.processModelFileCreatePath', - targetUris.processModelFileCreatePath - ); if (ability.can('PUT', targetUris.processModelFileCreatePath)) { icon = Edit; actionWord = 'Edit'; @@ -309,17 +315,6 @@ export default function ProcessModelShow() { if (!processModel || !permissionsLoaded) { return null; } - const permLoad = JSON.stringify(permissionsLoaded); - console.log('permLoad', permLoad); - const theMap = (ability as any).j; - // console.log('theMap', theMap[targetUris.processModelFileCreatePath]); - // console.log('theMap', theMap); - console.log( - 'theMap', - theMap.get( - '/v1.0/process-models/misc:category_number_one:workflow_one/files' - ) - ); let constructedTag; const tags = processModel.files.map((processModelFile: ProcessFile) => { const isPrimaryBpmnFile = From 69e14c93d7d63a837b85d9db107ced7edf60f667 Mon Sep 17 00:00:00 2001 From: jasquat Date: Fri, 30 Dec 2022 15:35:16 -0500 Subject: [PATCH 09/27] do not set baseUrl since it breaks auto-import and is not used otherwise w/ burnettk --- spiffworkflow-frontend/tsconfig.json | 1 - 1 file changed, 1 deletion(-) diff --git a/spiffworkflow-frontend/tsconfig.json b/spiffworkflow-frontend/tsconfig.json index f3d7e4a4..87997aba 100644 --- a/spiffworkflow-frontend/tsconfig.json +++ b/spiffworkflow-frontend/tsconfig.json @@ -1,6 +1,5 @@ { "compilerOptions": { - "baseUrl": ".", "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "jsx": "react-jsx", From 7c88f2f756993bed452d482efdcb73d59604fba2 Mon Sep 17 00:00:00 2001 From: jasquat Date: Fri, 30 Dec 2022 15:44:58 -0500 Subject: [PATCH 10/27] adjust the process model file actions so they do not stack as easily w/ burnettk --- spiffworkflow-frontend/src/routes/ProcessModelShow.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spiffworkflow-frontend/src/routes/ProcessModelShow.tsx b/spiffworkflow-frontend/src/routes/ProcessModelShow.tsx index 461c6d84..9c1e4bef 100644 --- a/spiffworkflow-frontend/src/routes/ProcessModelShow.tsx +++ b/spiffworkflow-frontend/src/routes/ProcessModelShow.tsx @@ -485,7 +485,7 @@ export default function ProcessModelShow() { fullWidth className="megacondensed process-model-files-section" > - + Date: Fri, 30 Dec 2022 16:30:52 -0500 Subject: [PATCH 11/27] added basis of api to create process models from text w/ burnettk --- .../src/spiffworkflow_backend/api.yml | 26 ++++++++ .../routes/process_models_controller.py | 59 ++++++++++++++++++- .../integration/test_process_api.py | 34 ++++++++++- 3 files changed, 115 insertions(+), 4 deletions(-) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/api.yml b/spiffworkflow-backend/src/spiffworkflow_backend/api.yml index d26a0abc..9d15bdb9 100755 --- a/spiffworkflow-backend/src/spiffworkflow_backend/api.yml +++ b/spiffworkflow-backend/src/spiffworkflow_backend/api.yml @@ -327,6 +327,32 @@ paths: schema: $ref: "#/components/schemas/ProcessModel" + /process-models-natural-language/{modified_process_group_id}: + parameters: + - name: modified_process_group_id + in: path + required: true + description: modified id of an existing process group + schema: + type: string + post: + operationId: spiffworkflow_backend.routes.process_models_controller.process_model_create_with_natural_language + summary: Creates a new process model with the given parameters. + tags: + - Process Models + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/ProcessModel" + responses: + "201": + description: Process model created successfully. + content: + application/json: + schema: + $ref: "#/components/schemas/ProcessModel" + /process-models/{modified_process_model_identifier}/files: parameters: - name: modified_process_model_identifier diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_models_controller.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_models_controller.py index ba0fe346..b1666933 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_models_controller.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_models_controller.py @@ -1,5 +1,6 @@ """APIs for dealing with process groups, process models, and process instances.""" import json +import re from typing import Any from typing import Dict from typing import Optional @@ -299,10 +300,66 @@ def process_model_file_show( file_contents = SpecFileService.get_data(process_model, file.name) file.file_contents = file_contents file.process_model_id = process_model.id - # file.process_group_id = process_model.process_group_id return FileSchema().dump(file) +# { +# "natural_language_text": "Create a bug tracker process model with a bug-details form that collects summary, description, and priority" +# } +def process_model_create_with_natural_language( + modified_process_group_id: str, body: Dict[str, Union[str, bool, int]] +) -> flask.wrappers.Response: + # body_include_list = [ + # "id", + # "display_name", + # "primary_file_name", + # "primary_process_id", + # "description", + # "metadata_extraction_paths", + # ] + # body_filtered = { + # include_item: body[include_item] + # for include_item in body_include_list + # if include_item in body + # } + + pattern = re.compile(r"Create a (?P.*?) process model with a (?P.*?) form that collects (?P.*)") + match = pattern.match(body["natural_language_text"]) + process_model_display_name = match.group('pm_name') + process_model_identifier = re.sub(r"[ _]", '-', process_model_display_name) + process_model_identifier = re.sub(r"-{2,}", '-', process_model_identifier).lower() + print(f"process_model_identifier: {process_model_identifier}") + + form_name = match.group('form_name') + form_identifier = re.sub(r"[ _]", '-', form_name) + form_identifier = re.sub(r"-{2,}", '-', form_identifier).lower() + print(f"form_identifier: {form_identifier}") + + column_names = match.group('columns_hey') + print(f"column_names: {column_names}") + columns = re.sub(r"(, (and )?)", ',', column_names).split(',') + print(f"columns: {columns}") + # + # if modified_process_group_id is None: + # raise ApiError( + # error_code="process_group_id_not_specified", + # message="Process Model could not be created when process_group_id path param is unspecified", + # status_code=400, + # ) + # + # unmodified_process_group_id = _un_modify_modified_process_model_id( + # modified_process_group_id + # ) + # process_group = ProcessModelService.get_process_group(unmodified_process_group_id) + # if process_group is None: + # raise ApiError( + # error_code="process_model_could_not_be_created", + # message=f"Process Model could not be created from given body because Process Group could not be found: {body}", + # status_code=400, + # ) + + + def _get_file_from_request() -> Any: """Get_file_from_request.""" request_file = connexion.request.files.get("file") 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 b9c0f1b7..6c3d8f27 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py @@ -163,6 +163,37 @@ class TestProcessApi(BaseTest): assert process_model.primary_file_name == bpmn_file_name assert process_model.primary_process_id == "sample" + def test_process_model_create_with_natural_language( + self, + app: Flask, + client: FlaskClient, + with_db_and_bpmn_file_cleanup: None, + with_super_admin_user: UserModel, + ) -> None: + process_group_id = "test_process_group" + process_group_description = "Test Process Group" + process_model_id = "sample" + process_model_identifier = f"{process_group_id}/{process_model_id}" + self.create_process_group( + client, with_super_admin_user, process_group_id, process_group_description + ) + + body = { + "natural_language_text": "Create a Bug Tracker process model with a Bug Details form that collects summary, description, and priority" + } + self.create_process_model_with_api( + client, + process_model_id=process_model_identifier, + user=with_super_admin_user, + ) + response = client.post( + f"/v1.0/process-models-natural-language/{process_group_id}", + content_type="application/json", + data=json.dumps(body), + headers=self.logged_in_headers(with_super_admin_user), + ) + assert response.status_code == 201 + def test_primary_process_id_updates_via_xml( self, app: Flask, @@ -250,9 +281,6 @@ class TestProcessApi(BaseTest): assert response.json is not None assert response.json["ok"] is True - # assert we no longer have a model - with pytest.raises(ProcessEntityNotFoundError): - ProcessModelService.get_process_model(process_model_identifier) def test_process_model_delete_with_instances( self, From 11030e99bd2a947bf9c531908930c7107bd5e734 Mon Sep 17 00:00:00 2001 From: burnettk Date: Fri, 30 Dec 2022 17:00:24 -0500 Subject: [PATCH 12/27] allow specifying number of iterations --- spiffworkflow-frontend/bin/collect_cypress_stats | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/spiffworkflow-frontend/bin/collect_cypress_stats b/spiffworkflow-frontend/bin/collect_cypress_stats index 31943475..150efc80 100755 --- a/spiffworkflow-frontend/bin/collect_cypress_stats +++ b/spiffworkflow-frontend/bin/collect_cypress_stats @@ -10,6 +10,8 @@ set -o errtrace -o errexit -o nounset -o pipefail # see also: npx cypress run --env grep="can filter",grepFilterSpecs=true # https://github.com/cypress-io/cypress/tree/develop/npm/grep#pre-filter-specs-grepfilterspecs +iterations="${1:-10}" + test_case_matches="$(rg '^ it\(')" stats_file="/var/tmp/cypress_stats.txt" @@ -37,7 +39,8 @@ function run_all_test_cases() { # clear the stats file echo > "$stats_file" -for global_stat_index in {1..100}; do +for ((global_stat_index=1;global_stat_index<=$iterations;global_stat_index++)); do +# for global_stat_index in {1..$iterations}; do run_all_test_cases "$global_stat_index" done From 56b469e92e922be1235f168072e66515192c50e1 Mon Sep 17 00:00:00 2001 From: burnettk Date: Fri, 30 Dec 2022 19:51:17 -0500 Subject: [PATCH 13/27] actually create process model --- .../routes/process_models_controller.py | 158 ++++++++++++------ .../integration/test_process_api.py | 13 +- 2 files changed, 112 insertions(+), 59 deletions(-) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_models_controller.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_models_controller.py index b1666933..6276eda5 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_models_controller.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_models_controller.py @@ -16,6 +16,7 @@ from flask.wrappers import Response from flask_bpmn.api.api_error import ApiError from spiffworkflow_backend.models.file import FileSchema +from spiffworkflow_backend.models.process_group import ProcessGroup from spiffworkflow_backend.models.process_model import ProcessModelInfo from spiffworkflow_backend.models.process_model import ProcessModelInfoSchema from spiffworkflow_backend.routes.process_api_blueprint import _commit_and_push_to_git @@ -47,23 +48,7 @@ def process_model_create( if include_item in body } - if modified_process_group_id is None: - raise ApiError( - error_code="process_group_id_not_specified", - message="Process Model could not be created when process_group_id path param is unspecified", - status_code=400, - ) - - unmodified_process_group_id = _un_modify_modified_process_model_id( - modified_process_group_id - ) - process_group = ProcessModelService.get_process_group(unmodified_process_group_id) - if process_group is None: - raise ApiError( - error_code="process_model_could_not_be_created", - message=f"Process Model could not be created from given body because Process Group could not be found: {body}", - status_code=400, - ) + _get_process_group_from_modified_identifier(modified_process_group_id) process_model_info = ProcessModelInfo(**body_filtered) # type: ignore if process_model_info is None: @@ -151,7 +136,8 @@ def process_model_move( original_process_model_id, new_location ) _commit_and_push_to_git( - f"User: {g.user.username} moved process model {original_process_model_id} to {new_process_model.id}" + f"User: {g.user.username} moved process model {original_process_model_id} to" + f" {new_process_model.id}" ) return make_response(jsonify(new_process_model), 200) @@ -224,7 +210,8 @@ def process_model_file_update( SpecFileService.update_file(process_model, file_name, request_file_contents) _commit_and_push_to_git( - f"User: {g.user.username} clicked save for {process_model_identifier}/{file_name}" + f"User: {g.user.username} clicked save for" + f" {process_model_identifier}/{file_name}" ) return Response(json.dumps({"ok": True}), status=200, mimetype="application/json") @@ -248,7 +235,8 @@ def process_model_file_delete( ) from exception _commit_and_push_to_git( - f"User: {g.user.username} deleted process model file {process_model_identifier}/{file_name}" + f"User: {g.user.username} deleted process model file" + f" {process_model_identifier}/{file_name}" ) return Response(json.dumps({"ok": True}), status=200, mimetype="application/json") @@ -274,7 +262,8 @@ def process_model_file_create( file.file_contents = file_contents file.process_model_id = process_model.id _commit_and_push_to_git( - f"User: {g.user.username} added process model file {process_model_identifier}/{file.name}" + f"User: {g.user.username} added process model file" + f" {process_model_identifier}/{file.name}" ) return Response( json.dumps(FileSchema().dump(file)), status=201, mimetype="application/json" @@ -291,8 +280,10 @@ def process_model_file_show( if len(files) == 0: raise ApiError( error_code="unknown file", - message=f"No information exists for file {file_name}" - f" it does not exist in workflow {process_model_identifier}.", + message=( + f"No information exists for file {file_name}" + f" it does not exist in workflow {process_model_identifier}." + ), status_code=404, ) @@ -303,12 +294,14 @@ def process_model_file_show( return FileSchema().dump(file) -# { -# "natural_language_text": "Create a bug tracker process model with a bug-details form that collects summary, description, and priority" -# } +# { +# "natural_language_text": "Create a bug tracker process model \ +# with a bug-details form that collects summary, description, and priority" +# } def process_model_create_with_natural_language( - modified_process_group_id: str, body: Dict[str, Union[str, bool, int]] + modified_process_group_id: str, body: Dict[str, str] ) -> flask.wrappers.Response: + """Process_model_create_with_natural_language.""" # body_include_list = [ # "id", # "display_name", @@ -323,41 +316,66 @@ def process_model_create_with_natural_language( # if include_item in body # } - pattern = re.compile(r"Create a (?P.*?) process model with a (?P.*?) form that collects (?P.*)") + pattern = re.compile( + r"Create a (?P.*?) process model with a (?P.*?) form that" + r" collects (?P.*)" + ) match = pattern.match(body["natural_language_text"]) - process_model_display_name = match.group('pm_name') - process_model_identifier = re.sub(r"[ _]", '-', process_model_display_name) - process_model_identifier = re.sub(r"-{2,}", '-', process_model_identifier).lower() + if match is None: + raise ApiError( + error_code="natural_language_text_not_yet_supported", + message=( + "Natural language text is not yet supported. Please use the form:" + f" {pattern.pattern}" + ), + status_code=400, + ) + process_model_display_name = match.group("pm_name") + process_model_identifier = re.sub(r"[ _]", "-", process_model_display_name) + process_model_identifier = re.sub(r"-{2,}", "-", process_model_identifier).lower() print(f"process_model_identifier: {process_model_identifier}") - - form_name = match.group('form_name') - form_identifier = re.sub(r"[ _]", '-', form_name) - form_identifier = re.sub(r"-{2,}", '-', form_identifier).lower() + + form_name = match.group("form_name") + form_identifier = re.sub(r"[ _]", "-", form_name) + form_identifier = re.sub(r"-{2,}", "-", form_identifier).lower() print(f"form_identifier: {form_identifier}") - column_names = match.group('columns_hey') + column_names = match.group("columns") print(f"column_names: {column_names}") - columns = re.sub(r"(, (and )?)", ',', column_names).split(',') + columns = re.sub(r"(, (and )?)", ",", column_names).split(",") print(f"columns: {columns}") - # - # if modified_process_group_id is None: - # raise ApiError( - # error_code="process_group_id_not_specified", - # message="Process Model could not be created when process_group_id path param is unspecified", - # status_code=400, - # ) - # - # unmodified_process_group_id = _un_modify_modified_process_model_id( - # modified_process_group_id - # ) - # process_group = ProcessModelService.get_process_group(unmodified_process_group_id) - # if process_group is None: - # raise ApiError( - # error_code="process_model_could_not_be_created", - # message=f"Process Model could not be created from given body because Process Group could not be found: {body}", - # status_code=400, - # ) + process_group = _get_process_group_from_modified_identifier( + modified_process_group_id + ) + qualified_process_model_identifier = ( + f"{process_group.id}/{process_model_identifier}" + ) + + process_model_attributes = { + "id": qualified_process_model_identifier, + "display_name": process_model_display_name, + "description": None, + } + + process_model_info = ProcessModelInfo(**process_model_attributes) # type: ignore + if process_model_info is None: + raise ApiError( + error_code="process_model_could_not_be_created", + message=f"Process Model could not be created from given body: {body}", + status_code=400, + ) + + ProcessModelService.add_process_model(process_model_info) + _commit_and_push_to_git( + f"User: {g.user.username} created process model via natural language:" + f" {process_model_info.id}" + ) + return Response( + json.dumps(ProcessModelInfoSchema().dump(process_model_info)), + status=201, + mimetype="application/json", + ) def _get_file_from_request() -> Any: @@ -370,3 +388,33 @@ def _get_file_from_request() -> Any: status_code=400, ) return request_file + + +def _get_process_group_from_modified_identifier( + modified_process_group_id: str, +) -> ProcessGroup: + """_get_process_group_from_modified_identifier.""" + if modified_process_group_id is None: + raise ApiError( + error_code="process_group_id_not_specified", + message=( + "Process Model could not be created when process_group_id path param is" + " unspecified" + ), + status_code=400, + ) + + unmodified_process_group_id = _un_modify_modified_process_model_id( + modified_process_group_id + ) + process_group = ProcessModelService.get_process_group(unmodified_process_group_id) + if process_group is None: + raise ApiError( + error_code="process_model_could_not_be_created", + message=( + "Process Model could not be created from given body because Process" + f" Group could not be found: {unmodified_process_group_id}" + ), + status_code=400, + ) + return process_group 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 6c3d8f27..a7179966 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py @@ -170,6 +170,7 @@ class TestProcessApi(BaseTest): with_db_and_bpmn_file_cleanup: None, with_super_admin_user: UserModel, ) -> None: + """Test_process_model_create_with_natural_language.""" process_group_id = "test_process_group" process_group_description = "Test Process Group" process_model_id = "sample" @@ -178,9 +179,11 @@ class TestProcessApi(BaseTest): client, with_super_admin_user, process_group_id, process_group_description ) - body = { - "natural_language_text": "Create a Bug Tracker process model with a Bug Details form that collects summary, description, and priority" - } + text = "Create a Bug Tracker process model " + text += ( + "with a Bug Details form that collects summary, description, and priority" + ) + body = {"natural_language_text": text} self.create_process_model_with_api( client, process_model_id=process_model_identifier, @@ -193,6 +196,9 @@ class TestProcessApi(BaseTest): headers=self.logged_in_headers(with_super_admin_user), ) assert response.status_code == 201 + assert response.json is not None + assert response.json["id"] == f"{process_group_id}/bug-tracker" + assert response.json["display_name"] == "Bug Tracker" def test_primary_process_id_updates_via_xml( self, @@ -281,7 +287,6 @@ class TestProcessApi(BaseTest): assert response.json is not None assert response.json["ok"] is True - def test_process_model_delete_with_instances( self, app: Flask, From 3c395ca3682894105e71873058ccc6e6a20eefbc Mon Sep 17 00:00:00 2001 From: burnettk Date: Fri, 30 Dec 2022 22:49:35 -0500 Subject: [PATCH 14/27] configure metadata extration paths appropriately --- .../routes/process_models_controller.py | 9 +++++++++ .../integration/test_process_api.py | 5 +++++ 2 files changed, 14 insertions(+) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_models_controller.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_models_controller.py index 6276eda5..a1c750cd 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_models_controller.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_models_controller.py @@ -352,10 +352,15 @@ def process_model_create_with_natural_language( f"{process_group.id}/{process_model_identifier}" ) + metadata_extraction_paths = [] + for column in columns: + metadata_extraction_paths.append({"key": column, "path": column}) + process_model_attributes = { "id": qualified_process_model_identifier, "display_name": process_model_display_name, "description": None, + "metadata_extraction_paths": metadata_extraction_paths, } process_model_info = ProcessModelInfo(**process_model_attributes) # type: ignore @@ -371,6 +376,10 @@ def process_model_create_with_natural_language( f"User: {g.user.username} created process model via natural language:" f" {process_model_info.id}" ) + + # TODO: Create a form json schema and UI schema + # TODO: Add report + return Response( json.dumps(ProcessModelInfoSchema().dump(process_model_info)), status=201, 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 a7179966..809063cb 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py @@ -199,6 +199,11 @@ class TestProcessApi(BaseTest): assert response.json is not None assert response.json["id"] == f"{process_group_id}/bug-tracker" assert response.json["display_name"] == "Bug Tracker" + assert response.json["metadata_extraction_paths"] == [ + {"key": "summary", "path": "summary"}, + {"key": "description", "path": "description"}, + {"key": "priority", "path": "priority"}, + ] def test_primary_process_id_updates_via_xml( self, From 09e012674a32fbc5d8941ae48db3ec3b6bc34143 Mon Sep 17 00:00:00 2001 From: burnettk Date: Fri, 30 Dec 2022 23:08:00 -0500 Subject: [PATCH 15/27] upgrade black, give it the preview flag, and let it rip on long strings --- .pre-commit-config.yaml | 6 ++ poetry.lock | 50 +++++++------ .../bin/import_tickets_for_command_line.py | 1 - .../bin/keycloak_test_server.py | 3 +- .../src/spiffworkflow_backend/__init__.py | 3 +- .../spiffworkflow_backend/config/__init__.py | 24 +++--- .../models/message_instance.py | 3 +- .../models/process_instance.py | 5 +- .../src/spiffworkflow_backend/models/task.py | 8 +- .../routes/messages_controller.py | 12 ++- .../routes/process_api_blueprint.py | 21 ++++-- .../routes/process_groups_controller.py | 3 +- .../routes/process_instances_controller.py | 17 +++-- .../routes/script_unit_tests_controller.py | 5 +- .../routes/tasks_controller.py | 31 ++++++-- .../src/spiffworkflow_backend/routes/user.py | 7 +- .../routes/user_blueprint.py | 1 + .../scripts/fact_service.py | 5 +- .../scripts/get_group_members.py | 3 +- .../scripts/get_process_info.py | 4 +- .../spiffworkflow_backend/scripts/script.py | 10 ++- .../services/acceptance_test_fixtures.py | 1 - .../services/authorization_service.py | 46 +++++++----- .../services/git_service.py | 10 ++- .../services/logging_service.py | 6 +- .../services/message_service.py | 8 +- .../services/process_instance_processor.py | 73 +++++++++++++------ .../process_instance_report_service.py | 6 +- .../services/process_instance_service.py | 8 +- .../services/process_model_service.py | 22 ++++-- .../services/script_unit_test_runner.py | 5 +- .../services/secret_service.py | 19 +++-- .../services/spec_file_service.py | 16 ++-- .../helpers/base_test.py | 5 +- .../integration/test_process_api.py | 12 ++- .../test_save_process_instance_metadata.py | 4 +- .../unit/test_permissions.py | 1 + .../unit/test_process_instance_processor.py | 3 +- .../test_process_instance_report_service.py | 8 +- 39 files changed, 315 insertions(+), 160 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7587ce0d..3f1c6dbb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,6 +11,12 @@ repos: require_serial: true # exclude: ^migrations/ exclude: "/migrations/" + + # otherwise it will not fix long lines if the long lines contain long strings + # https://github.com/psf/black/pull/1132 + # https://github.com/psf/black/pull/1609 + args: [--preview] + - id: check-added-large-files files: ^spiffworkflow-backend/ name: Check for added large files diff --git a/poetry.lock b/poetry.lock index e5c9c4c0..118134c7 100644 --- a/poetry.lock +++ b/poetry.lock @@ -163,7 +163,7 @@ python-versions = "*" [[package]] name = "black" -version = "22.10.0" +version = "23.1a1" description = "The uncompromising code formatter." category = "dev" optional = false @@ -614,7 +614,7 @@ werkzeug = "*" type = "git" url = "https://github.com/sartography/flask-bpmn" reference = "main" -resolved_reference = "860f2387bebdaa9220e9fbf6f8fa7f74e805d0d4" +resolved_reference = "c79c1e0b6d34ec05d82cce888b5e57b33d24403b" [[package]] name = "flask-cors" @@ -1760,7 +1760,7 @@ lxml = "*" type = "git" url = "https://github.com/sartography/SpiffWorkflow" reference = "main" -resolved_reference = "bba7ddf5478af579b891ca63c50babbfccf6b7a4" +resolved_reference = "80640024a8030481645f0c34f34c57e88f7b4f0c" [[package]] name = "sqlalchemy" @@ -2182,27 +2182,18 @@ billiard = [ {file = "billiard-3.6.4.0.tar.gz", hash = "sha256:299de5a8da28a783d51b197d496bef4f1595dd023a93a4f59dde1886ae905547"}, ] black = [ - {file = "black-22.10.0-1fixedarch-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:5cc42ca67989e9c3cf859e84c2bf014f6633db63d1cbdf8fdb666dcd9e77e3fa"}, - {file = "black-22.10.0-1fixedarch-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:5d8f74030e67087b219b032aa33a919fae8806d49c867846bfacde57f43972ef"}, - {file = "black-22.10.0-1fixedarch-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:197df8509263b0b8614e1df1756b1dd41be6738eed2ba9e9769f3880c2b9d7b6"}, - {file = "black-22.10.0-1fixedarch-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:2644b5d63633702bc2c5f3754b1b475378fbbfb481f62319388235d0cd104c2d"}, - {file = "black-22.10.0-1fixedarch-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:e41a86c6c650bcecc6633ee3180d80a025db041a8e2398dcc059b3afa8382cd4"}, - {file = "black-22.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2039230db3c6c639bd84efe3292ec7b06e9214a2992cd9beb293d639c6402edb"}, - {file = "black-22.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14ff67aec0a47c424bc99b71005202045dc09270da44a27848d534600ac64fc7"}, - {file = "black-22.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:819dc789f4498ecc91438a7de64427c73b45035e2e3680c92e18795a839ebb66"}, - {file = "black-22.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5b9b29da4f564ba8787c119f37d174f2b69cdfdf9015b7d8c5c16121ddc054ae"}, - {file = "black-22.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8b49776299fece66bffaafe357d929ca9451450f5466e997a7285ab0fe28e3b"}, - {file = "black-22.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:21199526696b8f09c3997e2b4db8d0b108d801a348414264d2eb8eb2532e540d"}, - {file = "black-22.10.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e464456d24e23d11fced2bc8c47ef66d471f845c7b7a42f3bd77bf3d1789650"}, - {file = "black-22.10.0-cp37-cp37m-win_amd64.whl", hash = "sha256:9311e99228ae10023300ecac05be5a296f60d2fd10fff31cf5c1fa4ca4b1988d"}, - {file = "black-22.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fba8a281e570adafb79f7755ac8721b6cf1bbf691186a287e990c7929c7692ff"}, - {file = "black-22.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:915ace4ff03fdfff953962fa672d44be269deb2eaf88499a0f8805221bc68c87"}, - {file = "black-22.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:444ebfb4e441254e87bad00c661fe32df9969b2bf224373a448d8aca2132b395"}, - {file = "black-22.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:974308c58d057a651d182208a484ce80a26dac0caef2895836a92dd6ebd725e0"}, - {file = "black-22.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72ef3925f30e12a184889aac03d77d031056860ccae8a1e519f6cbb742736383"}, - {file = "black-22.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:432247333090c8c5366e69627ccb363bc58514ae3e63f7fc75c54b1ea80fa7de"}, - {file = "black-22.10.0-py3-none-any.whl", hash = "sha256:c957b2b4ea88587b46cf49d1dc17681c1e672864fd7af32fc1e9664d572b3458"}, - {file = "black-22.10.0.tar.gz", hash = "sha256:f513588da599943e0cde4e32cc9879e825d58720d6557062d1098c5ad80080e1"}, + {file = "black-23.1a1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fb7641d442ede92538bc70fa0201f884753a7d0f62f26c722b7b00301b95902"}, + {file = "black-23.1a1-cp310-cp310-win_amd64.whl", hash = "sha256:88288a645402106b8eb9f50d7340ae741e16240bb01c2eed8466549153daa96e"}, + {file = "black-23.1a1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4db1d8027ce7ae53f0ccf02b0be0b8808fefb291d6cb1543420f4165d96d364c"}, + {file = "black-23.1a1-cp311-cp311-win_amd64.whl", hash = "sha256:88ec25a64063945b4591b6378bead544c5d3260de1c93ad96f3ad2d76ddd76fd"}, + {file = "black-23.1a1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dff6f0157e47fbbeada046fca144b6557d3be2fb2602d668881cd179f04a352"}, + {file = "black-23.1a1-cp37-cp37m-win_amd64.whl", hash = "sha256:ca658b69260a18bf7aa0b0a6562dbbd304a737487d1318998aaca5a75901fd2c"}, + {file = "black-23.1a1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85dede655442f5e246e7abd667fe07e14916897ba52f3640b5489bf11f7dbf67"}, + {file = "black-23.1a1-cp38-cp38-win_amd64.whl", hash = "sha256:ddbf9da228726d46f45c29024263e160d41030a415097254817d65127012d1a2"}, + {file = "black-23.1a1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63330069d8ec909cf4e2c4d43a7f00aeb03335430ef9fec6cd2328e6ebde8a77"}, + {file = "black-23.1a1-cp39-cp39-win_amd64.whl", hash = "sha256:793c9176beb2adf295f6b863d9a4dc953fe2ac359ca3da108d71d14cb2c09e52"}, + {file = "black-23.1a1-py3-none-any.whl", hash = "sha256:e88e4b633d64b9e7adc4a6b922f52bb204af9f90d7b1e3317e6490f2b598b1ea"}, + {file = "black-23.1a1.tar.gz", hash = "sha256:0b945a5a1e5a5321f884de0061d5a8585d947c9b608e37b6d26ceee4dfdf4b62"}, ] blinker = [ {file = "blinker-1.5-py2.py3-none-any.whl", hash = "sha256:1eb563df6fdbc39eeddc177d953203f99f097e9bf0e2b8f9f3cf18b6ca425e36"}, @@ -2857,7 +2848,18 @@ psycopg2 = [ {file = "psycopg2-2.9.5.tar.gz", hash = "sha256:a5246d2e683a972e2187a8714b5c2cf8156c064629f9a9b1a873c1730d9e245a"}, ] pyasn1 = [ + {file = "pyasn1-0.4.8-py2.4.egg", hash = "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"}, + {file = "pyasn1-0.4.8-py2.5.egg", hash = "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf"}, + {file = "pyasn1-0.4.8-py2.6.egg", hash = "sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00"}, + {file = "pyasn1-0.4.8-py2.7.egg", hash = "sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8"}, {file = "pyasn1-0.4.8-py2.py3-none-any.whl", hash = "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d"}, + {file = "pyasn1-0.4.8-py3.1.egg", hash = "sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86"}, + {file = "pyasn1-0.4.8-py3.2.egg", hash = "sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7"}, + {file = "pyasn1-0.4.8-py3.3.egg", hash = "sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576"}, + {file = "pyasn1-0.4.8-py3.4.egg", hash = "sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12"}, + {file = "pyasn1-0.4.8-py3.5.egg", hash = "sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2"}, + {file = "pyasn1-0.4.8-py3.6.egg", hash = "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359"}, + {file = "pyasn1-0.4.8-py3.7.egg", hash = "sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776"}, {file = "pyasn1-0.4.8.tar.gz", hash = "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba"}, ] pycodestyle = [ diff --git a/spiffworkflow-backend/bin/import_tickets_for_command_line.py b/spiffworkflow-backend/bin/import_tickets_for_command_line.py index e193b599..cc94ba54 100644 --- a/spiffworkflow-backend/bin/import_tickets_for_command_line.py +++ b/spiffworkflow-backend/bin/import_tickets_for_command_line.py @@ -27,7 +27,6 @@ def main(): """Main.""" app = get_hacked_up_app_for_script() with app.app_context(): - process_model_identifier_ticket = "ticket" db.session.query(ProcessInstanceModel).filter( ProcessInstanceModel.process_model_identifier diff --git a/spiffworkflow-backend/bin/keycloak_test_server.py b/spiffworkflow-backend/bin/keycloak_test_server.py index 59efd36c..3e933493 100644 --- a/spiffworkflow-backend/bin/keycloak_test_server.py +++ b/spiffworkflow-backend/bin/keycloak_test_server.py @@ -40,7 +40,8 @@ def hello_world(): return ( 'Hello, %s, See private ' 'Log out' - ) % oidc.user_getfield("preferred_username") + % oidc.user_getfield("preferred_username") + ) else: return 'Welcome anonymous, Log in' diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/__init__.py b/spiffworkflow-backend/src/spiffworkflow_backend/__init__.py index c40b3e76..f1de793d 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/__init__.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/__init__.py @@ -93,7 +93,8 @@ def create_app() -> flask.app.Flask: if os.environ.get("FLASK_SESSION_SECRET_KEY") is None: raise KeyError( - "Cannot find the secret_key from the environment. Please set FLASK_SESSION_SECRET_KEY" + "Cannot find the secret_key from the environment. Please set" + " FLASK_SESSION_SECRET_KEY" ) app.secret_key = os.environ.get("FLASK_SESSION_SECRET_KEY") diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/config/__init__.py b/spiffworkflow-backend/src/spiffworkflow_backend/config/__init__.py index f7661bc1..fb5901f0 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/config/__init__.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/config/__init__.py @@ -17,21 +17,21 @@ def setup_database_uri(app: Flask) -> None: if app.config.get("SPIFFWORKFLOW_BACKEND_DATABASE_URI") is None: database_name = f"spiffworkflow_backend_{app.config['ENV_IDENTIFIER']}" if app.config.get("SPIFF_DATABASE_TYPE") == "sqlite": - app.config[ - "SQLALCHEMY_DATABASE_URI" - ] = f"sqlite:///{app.instance_path}/db_{app.config['ENV_IDENTIFIER']}.sqlite3" + app.config["SQLALCHEMY_DATABASE_URI"] = ( + f"sqlite:///{app.instance_path}/db_{app.config['ENV_IDENTIFIER']}.sqlite3" + ) elif app.config.get("SPIFF_DATABASE_TYPE") == "postgres": - app.config[ - "SQLALCHEMY_DATABASE_URI" - ] = f"postgresql://spiffworkflow_backend:spiffworkflow_backend@localhost:5432/{database_name}" + app.config["SQLALCHEMY_DATABASE_URI"] = ( + f"postgresql://spiffworkflow_backend:spiffworkflow_backend@localhost:5432/{database_name}" + ) else: # use pswd to trick flake8 with hardcoded passwords db_pswd = os.environ.get("DB_PASSWORD") if db_pswd is None: db_pswd = "" - app.config[ - "SQLALCHEMY_DATABASE_URI" - ] = f"mysql+mysqlconnector://root:{db_pswd}@localhost/{database_name}" + app.config["SQLALCHEMY_DATABASE_URI"] = ( + f"mysql+mysqlconnector://root:{db_pswd}@localhost/{database_name}" + ) else: app.config["SQLALCHEMY_DATABASE_URI"] = app.config.get( "SPIFFWORKFLOW_BACKEND_DATABASE_URI" @@ -91,10 +91,12 @@ def setup_config(app: Flask) -> None: app.config["SPIFFWORKFLOW_BACKEND_PERMISSIONS_FILE_NAME"], ) print( - f"set permissions file name config: {app.config['SPIFFWORKFLOW_BACKEND_PERMISSIONS_FILE_NAME']}" + "set permissions file name config:" + f" {app.config['SPIFFWORKFLOW_BACKEND_PERMISSIONS_FILE_NAME']}" ) print( - f"set permissions file name full path: {app.config['PERMISSIONS_FILE_FULLPATH']}" + "set permissions file name full path:" + f" {app.config['PERMISSIONS_FILE_FULLPATH']}" ) # unversioned (see .gitignore) config that can override everything and include secrets. diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/models/message_instance.py b/spiffworkflow-backend/src/spiffworkflow_backend/models/message_instance.py index 2559a635..b0cc2aa3 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/models/message_instance.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/models/message_instance.py @@ -86,5 +86,6 @@ def ensure_failure_cause_is_set_if_message_instance_failed( if isinstance(instance, MessageInstanceModel): if instance.status == "failed" and instance.failure_cause is None: raise ValueError( - f"{instance.__class__.__name__}: failure_cause must be set if status is failed" + f"{instance.__class__.__name__}: failure_cause must be set if" + " status is failed" ) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance.py b/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance.py index d1d63510..aa1440b4 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance.py @@ -62,7 +62,10 @@ class ProcessInstanceModel(SpiffworkflowBaseDBModel): active_human_tasks = relationship( "HumanTaskModel", - primaryjoin="and_(HumanTaskModel.process_instance_id==ProcessInstanceModel.id, HumanTaskModel.completed == False)", + primaryjoin=( + "and_(HumanTaskModel.process_instance_id==ProcessInstanceModel.id," + " HumanTaskModel.completed == False)" + ), ) # type: ignore human_tasks = relationship( diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/models/task.py b/spiffworkflow-backend/src/spiffworkflow_backend/models/task.py index 896c79e1..79814c1d 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/models/task.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/models/task.py @@ -43,8 +43,8 @@ class Task: FIELD_TYPE_EMAIL = "email" # email: Email address FIELD_TYPE_URL = "url" # url: Website address - FIELD_PROP_AUTO_COMPLETE_MAX = ( - "autocomplete_num" # Not used directly, passed in from the front end. + FIELD_PROP_AUTO_COMPLETE_MAX = ( # Not used directly, passed in from the front end. + "autocomplete_num" ) # Required field @@ -77,8 +77,8 @@ class Task: # File specific field properties FIELD_PROP_DOC_CODE = "doc_code" # to associate a file upload field with a doc code - FIELD_PROP_FILE_DATA = ( - "file_data" # to associate a bit of data with a specific file upload file. + FIELD_PROP_FILE_DATA = ( # to associate a bit of data with a specific file upload file. + "file_data" ) # Additional properties diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/messages_controller.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/messages_controller.py index c5238bad..51290770 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/messages_controller.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/messages_controller.py @@ -131,8 +131,11 @@ def message_start( raise ( ApiError( error_code="cannot_find_waiting_message", - message=f"Could not find waiting message for identifier {message_identifier} " - f"and process instance {process_instance.id}", + message=( + "Could not find waiting message for identifier" + f" {message_identifier} and process instance" + f" {process_instance.id}" + ), status_code=400, ) ) @@ -151,7 +154,10 @@ def message_start( raise ( ApiError( error_code="cannot_start_message", - message=f"Message with identifier cannot be start with message: {message_identifier}", + message=( + "Message with identifier cannot be start with message:" + f" {message_identifier}" + ), status_code=400, ) ) 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 cff11eb9..6dcd79f7 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py @@ -43,7 +43,9 @@ def permissions_check(body: Dict[str, Dict[str, list[str]]]) -> flask.wrappers.R raise ( ApiError( error_code="could_not_requests_to_check", - message="The key 'requests_to_check' not found at root of request body.", + message=( + "The key 'requests_to_check' not found at root of request body." + ), status_code=400, ) ) @@ -139,7 +141,8 @@ def task_data_update( if process_instance: if process_instance.status != "suspended": raise ProcessInstanceTaskDataCannotBeUpdatedError( - f"The process instance needs to be suspended to udpate the task-data. It is currently: {process_instance.status}" + "The process instance needs to be suspended to udpate the task-data." + f" It is currently: {process_instance.status}" ) process_instance_bpmn_json_dict = json.loads(process_instance.bpmn_json) @@ -163,12 +166,18 @@ def task_data_update( else: raise ApiError( error_code="update_task_data_error", - message=f"Could not find Task: {task_id} in Instance: {process_instance_id}.", + message=( + f"Could not find Task: {task_id} in Instance:" + f" {process_instance_id}." + ), ) else: raise ApiError( error_code="update_task_data_error", - message=f"Could not update task data for Instance: {process_instance_id}, and Task: {task_id}.", + message=( + f"Could not update task data for Instance: {process_instance_id}, and" + f" Task: {task_id}." + ), ) return Response( json.dumps(ProcessInstanceModelSchema().dump(process_instance)), @@ -236,7 +245,9 @@ def manual_complete_task( else: raise ApiError( error_code="complete_task", - message=f"Could not complete Task {task_id} in Instance {process_instance_id}", + message=( + f"Could not complete Task {task_id} in Instance {process_instance_id}" + ), ) return Response( json.dumps(ProcessInstanceModelSchema().dump(process_instance)), diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_groups_controller.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_groups_controller.py index b0d43a4c..228be181 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_groups_controller.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_groups_controller.py @@ -124,6 +124,7 @@ def process_group_move( original_process_group_id, new_location ) _commit_and_push_to_git( - f"User: {g.user.username} moved process group {original_process_group_id} to {new_process_group.id}" + f"User: {g.user.username} moved process group {original_process_group_id} to" + f" {new_process_group.id}" ) return make_response(jsonify(new_process_group), 200) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_instances_controller.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_instances_controller.py index 08ec712e..50f1fc64 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_instances_controller.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_instances_controller.py @@ -94,7 +94,10 @@ def process_instance_run( if process_instance.status != "not_started": raise ApiError( error_code="process_instance_not_runnable", - message=f"Process Instance ({process_instance.id}) is currently running or has already run.", + message=( + f"Process Instance ({process_instance.id}) is currently running or has" + " already run." + ), status_code=400, ) @@ -350,8 +353,8 @@ def process_instance_delete( if not process_instance.has_terminal_status(): raise ProcessInstanceCannotBeDeletedError( - f"Process instance ({process_instance.id}) cannot be deleted since it does not have a terminal status. " - f"Current status is {process_instance.status}." + f"Process instance ({process_instance.id}) cannot be deleted since it does" + f" not have a terminal status. Current status is {process_instance.status}." ) # (Pdb) db.session.delete @@ -620,7 +623,8 @@ def _get_process_instance( ).first() if spec_reference is None: raise SpecReferenceNotFoundError( - f"Could not find given process identifier in the cache: {process_identifier}" + "Could not find given process identifier in the cache:" + f" {process_identifier}" ) process_model_with_diagram = ProcessModelService.get_process_model( @@ -678,7 +682,10 @@ def _find_process_instance_for_me_or_raise( raise ( ApiError( error_code="process_instance_cannot_be_found", - message=f"Process instance with id {process_instance_id} cannot be found that is associated with you.", + message=( + f"Process instance with id {process_instance_id} cannot be found" + " that is associated with you." + ), status_code=400, ) ) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/script_unit_tests_controller.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/script_unit_tests_controller.py index ddd48581..e97b26ae 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/script_unit_tests_controller.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/script_unit_tests_controller.py @@ -40,7 +40,10 @@ def script_unit_test_create( if file is None: raise ApiError( error_code="cannot_find_file", - message=f"Could not find the primary bpmn file for process_model: {process_model.id}", + message=( + "Could not find the primary bpmn file for process_model:" + f" {process_model.id}" + ), status_code=404, ) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/tasks_controller.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/tasks_controller.py index a8f899b5..2c7ceeea 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/tasks_controller.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/tasks_controller.py @@ -157,7 +157,10 @@ def task_show(process_instance_id: int, task_id: str) -> flask.wrappers.Response raise ( ApiError( error_code="no_human_task", - message=f"Cannot find a task to complete for task id '{task_id}' and process instance {process_instance_id}.", + message=( + f"Cannot find a task to complete for task id '{task_id}' and" + f" process instance {process_instance_id}." + ), status_code=500, ) ) @@ -203,7 +206,10 @@ def task_show(process_instance_id: int, task_id: str) -> flask.wrappers.Response raise ( ApiError( error_code="missing_form_file", - message=f"Cannot find a form file for process_instance_id: {process_instance_id}, task_id: {task_id}", + message=( + "Cannot find a form file for process_instance_id:" + f" {process_instance_id}, task_id: {task_id}" + ), status_code=400, ) ) @@ -221,7 +227,10 @@ def task_show(process_instance_id: int, task_id: str) -> flask.wrappers.Response raise ( ApiError( error_code="error_loading_form", - message=f"Could not load form schema from: {form_schema_file_name}. Error was: {str(exception)}", + message=( + f"Could not load form schema from: {form_schema_file_name}." + f" Error was: {str(exception)}" + ), status_code=400, ) ) from exception @@ -285,8 +294,10 @@ def task_submit( if not process_instance.can_submit_task(): raise ApiError( error_code="process_instance_not_runnable", - message=f"Process Instance ({process_instance.id}) has status " - f"{process_instance.status} which does not allow tasks to be submitted.", + message=( + f"Process Instance ({process_instance.id}) has status " + f"{process_instance.status} which does not allow tasks to be submitted." + ), status_code=400, ) @@ -317,7 +328,10 @@ def task_submit( raise ( ApiError( error_code="no_human_task", - message=f"Cannot find a task to complete for task id '{task_id}' and process instance {process_instance_id}.", + message=( + f"Cannot find a task to complete for task id '{task_id}' and" + f" process instance {process_instance_id}." + ), status_code=500, ) ) @@ -511,7 +525,10 @@ def _update_form_schema_with_task_data_as_needed( raise ( ApiError( error_code="missing_task_data_var", - message=f"Task data is missing variable: {task_data_var}", + message=( + "Task data is missing variable:" + f" {task_data_var}" + ), status_code=500, ) ) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/user.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/user.py index 51301c70..1ac6207c 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/user.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/user.py @@ -67,13 +67,16 @@ def verify_token( user_model = get_user_from_decoded_internal_token(decoded_token) except Exception as e: current_app.logger.error( - f"Exception in verify_token getting user from decoded internal token. {e}" + "Exception in verify_token getting user from decoded" + f" internal token. {e}" ) elif "iss" in decoded_token.keys(): try: if AuthenticationService.validate_id_token(token): user_info = decoded_token - except ApiError as ae: # API Error is only thrown in the token is outdated. + except ( + ApiError + ) as ae: # API Error is only thrown in the token is outdated. # Try to refresh the token user = UserService.get_user_by_service_and_service_id( decoded_token["iss"], decoded_token["sub"] diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/user_blueprint.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/user_blueprint.py index 29bbddcd..fd5c1ae9 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/user_blueprint.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/user_blueprint.py @@ -26,6 +26,7 @@ user_blueprint = Blueprint("main", __name__) # user = UserService.create_user('internal', username) # return Response(json.dumps({"id": user.id}), status=201, mimetype=APPLICATION_JSON) + # def _create_user(username): # user = UserModel.query.filter_by(username=username).first() # if user is not None: diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/fact_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/fact_service.py index 6e8a23c2..c739d15a 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/fact_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/fact_service.py @@ -35,7 +35,10 @@ class FactService(Script): if fact == "cat": details = "The cat in the hat" # self.get_cat() elif fact == "norris": - details = "Chuck Norris doesn’t read books. He stares them down until he gets the information he wants." + details = ( + "Chuck Norris doesn’t read books. He stares them down until he gets the" + " information he wants." + ) elif fact == "buzzword": details = "Move the Needle." # self.get_buzzword() else: diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/get_group_members.py b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/get_group_members.py index 8b179a6d..0f20fbb3 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/get_group_members.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/get_group_members.py @@ -32,7 +32,8 @@ class GetGroupMembers(Script): group = GroupModel.query.filter_by(identifier=group_identifier).first() if group is None: raise GroupNotFoundError( - f"Script 'get_group_members' could not find group with identifier '{group_identifier}'." + "Script 'get_group_members' could not find group with identifier" + f" '{group_identifier}'." ) usernames = [u.username for u in group.users] diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/get_process_info.py b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/get_process_info.py index 138a19ac..99eb4ce2 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/get_process_info.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/get_process_info.py @@ -28,5 +28,7 @@ class GetProcessInfo(Script): """Run.""" return { "process_instance_id": script_attributes_context.process_instance_id, - "process_model_identifier": script_attributes_context.process_model_identifier, + "process_model_identifier": ( + script_attributes_context.process_model_identifier + ), } diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/script.py b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/script.py index 9e5836d6..7ca79846 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/script.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/script.py @@ -98,8 +98,9 @@ class Script: ).first() if process_instance is None: raise ProcessInstanceNotFoundError( - f"Could not find a process instance with id '{script_attributes_context.process_instance_id}' " - f"when running script '{script_function_name}'" + "Could not find a process instance with id" + f" '{script_attributes_context.process_instance_id}' when" + f" running script '{script_function_name}'" ) user = process_instance.process_initiator has_permission = AuthorizationService.user_has_permission( @@ -107,7 +108,8 @@ class Script: ) if not has_permission: raise ScriptUnauthorizedForUserError( - f"User {user.username} does not have access to run privileged script '{script_function_name}'" + f"User {user.username} does not have access to run" + f" privileged script '{script_function_name}'" ) def run_script_if_allowed(*ar: Any, **kw: Any) -> Any: @@ -149,7 +151,7 @@ class Script: """_get_all_subclasses.""" # hackish mess to make sure we have all the modules loaded for the scripts pkg_dir = os.path.dirname(__file__) - for (_module_loader, name, _ispkg) in pkgutil.iter_modules([pkg_dir]): + for _module_loader, name, _ispkg in pkgutil.iter_modules([pkg_dir]): importlib.import_module("." + name, __package__) """Returns a list of all classes that extend this class.""" diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/acceptance_test_fixtures.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/acceptance_test_fixtures.py index 81488910..6bbcad33 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/acceptance_test_fixtures.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/acceptance_test_fixtures.py @@ -29,7 +29,6 @@ def load_acceptance_test_fixtures() -> list[ProcessInstanceModel]: # suspended - 6 hours ago process_instances = [] for i in range(len(statuses)): - process_instance = ProcessInstanceService.create_process_instance_from_process_model_identifier( test_process_model_id, user ) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py index 5ee1bf62..69d19cb7 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py @@ -128,7 +128,8 @@ class AuthorizationService: # to check for exact matches as well # see test_user_can_access_base_path_when_given_wildcard_permission unit test text( - f"'{target_uri_normalized}' = replace(replace(permission_target.uri, '/%', ''), ':%', '')" + f"'{target_uri_normalized}' =" + " replace(replace(permission_target.uri, '/%', ''), ':%', '')" ), ) ) @@ -200,7 +201,8 @@ class AuthorizationService: if current_app.config["SPIFFWORKFLOW_BACKEND_PERMISSIONS_FILE_NAME"] is None: raise ( PermissionsFileNotSetError( - "SPIFFWORKFLOW_BACKEND_PERMISSIONS_FILE_NAME needs to be set in order to import permissions" + "SPIFFWORKFLOW_BACKEND_PERMISSIONS_FILE_NAME needs to be set in" + " order to import permissions" ) ) @@ -280,9 +282,9 @@ class AuthorizationService: """Find_or_create_permission_target.""" uri_with_percent = re.sub(r"\*", "%", uri) target_uri_normalized = uri_with_percent.removeprefix(V1_API_PATH_PREFIX) - permission_target: Optional[ - PermissionTargetModel - ] = PermissionTargetModel.query.filter_by(uri=target_uri_normalized).first() + permission_target: Optional[PermissionTargetModel] = ( + PermissionTargetModel.query.filter_by(uri=target_uri_normalized).first() + ) if permission_target is None: permission_target = PermissionTargetModel(uri=target_uri_normalized) db.session.add(permission_target) @@ -297,13 +299,13 @@ class AuthorizationService: permission: str, ) -> PermissionAssignmentModel: """Create_permission_for_principal.""" - permission_assignment: Optional[ - PermissionAssignmentModel - ] = PermissionAssignmentModel.query.filter_by( - principal_id=principal.id, - permission_target_id=permission_target.id, - permission=permission, - ).first() + permission_assignment: Optional[PermissionAssignmentModel] = ( + PermissionAssignmentModel.query.filter_by( + principal_id=principal.id, + permission_target_id=permission_target.id, + permission=permission, + ).first() + ) if permission_assignment is None: permission_assignment = PermissionAssignmentModel( principal_id=principal.id, @@ -403,7 +405,10 @@ class AuthorizationService: raise ApiError( error_code="unauthorized", - message=f"User {g.user.username} is not authorized to perform requested action: {permission_string} - {request.path}", + message=( + f"User {g.user.username} is not authorized to perform requested action:" + f" {permission_string} - {request.path}" + ), status_code=403, ) @@ -482,7 +487,10 @@ class AuthorizationService: except jwt.InvalidTokenError as exception: raise ApiError( "token_invalid", - "The Authentication token you provided is invalid. You need a new token. ", + ( + "The Authentication token you provided is invalid. You need a new" + " token. " + ), ) from exception @staticmethod @@ -504,8 +512,9 @@ class AuthorizationService: if user not in human_task.potential_owners: raise UserDoesNotHaveAccessToTaskError( - f"User {user.username} does not have access to update task'{spiff_task.task_spec.name}'" - f" for process instance '{process_instance_id}'" + f"User {user.username} does not have access to update" + f" task'{spiff_task.task_spec.name}' for process instance" + f" '{process_instance_id}'" ) return True @@ -723,8 +732,9 @@ class AuthorizationService: ) else: raise InvalidPermissionError( - f"Target uri '{target}' with permission set '{permission_set}' is invalid. " - f"The target uri must either be a macro of PG, PM, BASIC, or ALL or an api uri." + f"Target uri '{target}' with permission set '{permission_set}' is" + " invalid. The target uri must either be a macro of PG, PM, BASIC, or" + " ALL or an api uri." ) return permissions_to_assign diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/git_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/git_service.py index 495603cf..43c18edc 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/git_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/git_service.py @@ -173,13 +173,15 @@ class GitService: if "repository" not in webhook or "clone_url" not in webhook["repository"]: raise InvalidGitWebhookBodyError( - f"Cannot find required keys of 'repository:clone_url' from webhook body: {webhook}" + "Cannot find required keys of 'repository:clone_url' from webhook" + f" body: {webhook}" ) clone_url = webhook["repository"]["clone_url"] if clone_url != current_app.config["GIT_CLONE_URL_FOR_PUBLISHING"]: raise GitCloneUrlMismatchError( - f"Configured clone url does not match clone url from webhook: {clone_url}" + "Configured clone url does not match clone url from webhook:" + f" {clone_url}" ) if "ref" not in webhook: @@ -189,8 +191,8 @@ class GitService: if current_app.config["GIT_BRANCH"] is None: raise MissingGitConfigsError( - "Missing config for GIT_BRANCH. " - "This is required for updating the repository as a result of the webhook" + "Missing config for GIT_BRANCH. This is required for updating the" + " repository as a result of the webhook" ) ref = webhook["ref"] diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/logging_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/logging_service.py index dd34cb3f..599d5228 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/logging_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/logging_service.py @@ -122,7 +122,8 @@ def setup_logger(app: Flask) -> None: if upper_log_level_string not in log_levels: raise InvalidLogLevelError( - f"Log level given is invalid: '{upper_log_level_string}'. Valid options are {log_levels}" + f"Log level given is invalid: '{upper_log_level_string}'. Valid options are" + f" {log_levels}" ) log_level = getattr(logging, upper_log_level_string) @@ -176,7 +177,8 @@ def setup_logger(app: Flask) -> None: spiff_logger = logging.getLogger("spiff") spiff_logger.setLevel(spiff_log_level) spiff_formatter = logging.Formatter( - "%(asctime)s | %(levelname)s | %(message)s | %(action)s | %(task_type)s | %(process)s | %(processName)s | %(process_instance_id)s" + "%(asctime)s | %(levelname)s | %(message)s | %(action)s | %(task_type)s |" + " %(process)s | %(processName)s | %(process_instance_id)s" ) # if you add a handler to spiff, it will be used/inherited by spiff.metrics diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/message_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/message_service.py index cfb42c83..b3d1e831 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/message_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/message_service.py @@ -145,8 +145,11 @@ class MessageService: if process_instance_receive is None: raise MessageServiceError( ( - f"Process instance cannot be found for queued message: {message_instance_receive.id}." - f"Tried with id {message_instance_receive.process_instance_id}", + ( + "Process instance cannot be found for queued message:" + f" {message_instance_receive.id}.Tried with id" + f" {message_instance_receive.process_instance_id}" + ), ) ) @@ -182,7 +185,6 @@ class MessageService: ) for message_instance_receive in message_instances_receive: - # sqlalchemy supports select / where statements like active record apparantly # https://docs.sqlalchemy.org/en/14/core/tutorial.html#conjunctions message_correlation_select = ( 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 6b03d4ce..74e4ab24 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py @@ -215,14 +215,14 @@ class CustomBpmnScriptEngine(PythonScriptEngine): # type: ignore except Exception as exception: if task is None: raise ProcessInstanceProcessorError( - "Error evaluating expression: " - "'%s', exception: %s" % (expression, str(exception)), + "Error evaluating expression: '%s', exception: %s" + % (expression, str(exception)), ) from exception else: raise WorkflowTaskExecException( task, - "Error evaluating expression " - "'%s', %s" % (expression, str(exception)), + "Error evaluating expression '%s', %s" + % (expression, str(exception)), ) from exception def execute( @@ -300,9 +300,7 @@ class ProcessInstanceProcessor: tld.spiff_step = process_instance_model.spiff_step # we want this to be the fully qualified path to the process model including all group subcomponents - current_app.config[ - "THREAD_LOCAL_DATA" - ].process_model_identifier = ( + current_app.config["THREAD_LOCAL_DATA"].process_model_identifier = ( f"{process_instance_model.process_model_identifier}" ) @@ -383,8 +381,10 @@ class ProcessInstanceProcessor: except MissingSpecError as ke: raise ApiError( error_code="unexpected_process_instance_structure", - message="Failed to deserialize process_instance" - " '%s' due to a mis-placed or missing task '%s'" + message=( + "Failed to deserialize process_instance" + " '%s' due to a mis-placed or missing task '%s'" + ) % (self.process_model_identifier, str(ke)), ) from ke @@ -400,7 +400,10 @@ class ProcessInstanceProcessor: raise ( ApiError( "process_model_not_found", - f"The given process model was not found: {process_model_identifier}.", + ( + "The given process model was not found:" + f" {process_model_identifier}." + ), ) ) spec_files = SpecFileService.get_files(process_model_info) @@ -530,8 +533,11 @@ class ProcessInstanceProcessor: potential_owner_ids.append(lane_owner_user.id) self.raise_if_no_potential_owners( potential_owner_ids, - f"No users found in task data lane owner list for lane: {task_lane}. " - f"The user list used: {task.data['lane_owners'][task_lane]}", + ( + "No users found in task data lane owner list for lane:" + f" {task_lane}. The user list used:" + f" {task.data['lane_owners'][task_lane]}" + ), ) else: group_model = GroupModel.query.filter_by(identifier=task_lane).first() @@ -722,7 +728,8 @@ class ProcessInstanceProcessor: if payload is not None: event_definition.payload = payload current_app.logger.info( - f"Event of type {event_definition.event_type} sent to process instance {self.process_instance_model.id}" + f"Event of type {event_definition.event_type} sent to process instance" + f" {self.process_instance_model.id}" ) self.bpmn_process_instance.catch(event_definition) self.do_engine_steps(save=True) @@ -732,12 +739,14 @@ class ProcessInstanceProcessor: spiff_task = self.bpmn_process_instance.get_task(UUID(task_id)) if execute: current_app.logger.info( - f"Manually executing Task {spiff_task.task_spec.name} of process instance {self.process_instance_model.id}" + f"Manually executing Task {spiff_task.task_spec.name} of process" + f" instance {self.process_instance_model.id}" ) spiff_task.complete() else: current_app.logger.info( - f"Skipping Task {spiff_task.task_spec.name} of process instance {self.process_instance_model.id}" + f"Skipping Task {spiff_task.task_spec.name} of process instance" + f" {self.process_instance_model.id}" ) spiff_task._set_state(TaskState.COMPLETED) for child in spiff_task.children: @@ -781,7 +790,8 @@ class ProcessInstanceProcessor: """Bpmn_file_full_path_from_bpmn_process_identifier.""" if bpmn_process_identifier is None: raise ValueError( - "bpmn_file_full_path_from_bpmn_process_identifier: bpmn_process_identifier is unexpectedly None" + "bpmn_file_full_path_from_bpmn_process_identifier:" + " bpmn_process_identifier is unexpectedly None" ) spec_reference = SpecReferenceCache.query.filter_by( @@ -803,7 +813,10 @@ class ProcessInstanceProcessor: raise ( ApiError( error_code="could_not_find_bpmn_process_identifier", - message="Could not find the the given bpmn process identifier from any sources: %s" + message=( + "Could not find the the given bpmn process identifier from any" + " sources: %s" + ) % bpmn_process_identifier, ) ) @@ -827,7 +840,6 @@ class ProcessInstanceProcessor: new_bpmn_files = set() for bpmn_process_identifier in processor_dependencies_new: - # ignore identifiers that spiff already knows about if bpmn_process_identifier in bpmn_process_identifiers_in_parser: continue @@ -870,7 +882,10 @@ class ProcessInstanceProcessor: raise ( ApiError( error_code="no_primary_bpmn_error", - message="There is no primary BPMN process id defined for process_model %s" + message=( + "There is no primary BPMN process id defined for" + " process_model %s" + ) % process_model_info.id, ) ) @@ -931,7 +946,10 @@ class ProcessInstanceProcessor: if not bpmn_message.correlations: raise ApiError( "message_correlations_missing", - f"Could not find any message correlations bpmn_message: {bpmn_message.name}", + ( + "Could not find any message correlations bpmn_message:" + f" {bpmn_message.name}" + ), ) message_correlations = [] @@ -951,12 +969,16 @@ class ProcessInstanceProcessor: if message_correlation_property is None: raise ApiError( "message_correlations_missing_from_process", - "Could not find a known message correlation with identifier:" - f"{message_correlation_property_identifier}", + ( + "Could not find a known message correlation with" + f" identifier:{message_correlation_property_identifier}" + ), ) message_correlations.append( { - "message_correlation_property": message_correlation_property, + "message_correlation_property": ( + message_correlation_property + ), "name": message_correlation_key, "value": message_correlation_property_value, } @@ -1013,7 +1035,10 @@ class ProcessInstanceProcessor: if message_model is None: raise ApiError( "invalid_message_name", - f"Invalid message name: {waiting_task.task_spec.event_definition.name}.", + ( + "Invalid message name:" + f" {waiting_task.task_spec.event_definition.name}." + ), ) # Ensure we are only creating one message instance for each waiting message diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py index 18c1bef6..7d725fa2 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py @@ -283,9 +283,9 @@ class ProcessInstanceReportService: process_instance_dict = process_instance["ProcessInstanceModel"].serialized for metadata_column in metadata_columns: if metadata_column["accessor"] not in process_instance_dict: - process_instance_dict[ - metadata_column["accessor"] - ] = process_instance[metadata_column["accessor"]] + process_instance_dict[metadata_column["accessor"]] = ( + process_instance[metadata_column["accessor"]] + ) results.append(process_instance_dict) return results 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 c9722289..c6e3db42 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_service.py @@ -85,7 +85,8 @@ class ProcessInstanceService: db.session.add(process_instance) db.session.commit() error_message = ( - f"Error running waiting task for process_instance {process_instance.id}" + "Error running waiting task for process_instance" + f" {process_instance.id}" + f"({process_instance.process_model_identifier}). {str(e)}" ) current_app.logger.error(error_message) @@ -178,7 +179,10 @@ class ProcessInstanceService: else: raise ApiError.from_task( error_code="task_lane_user_error", - message="Spiff Task %s lane user dict must have a key called 'value' with the user's uid in it." + message=( + "Spiff Task %s lane user dict must have a key called" + " 'value' with the user's uid in it." + ) % spiff_task.task_spec.name, task=spiff_task, ) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_model_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_model_service.py index 714cd799..f9f34631 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_model_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_model_service.py @@ -146,7 +146,10 @@ class ProcessModelService(FileSystemService): if len(instances) > 0: raise ApiError( error_code="existing_instances", - message=f"We cannot delete the model `{process_model_id}`, there are existing instances that depend on it.", + message=( + f"We cannot delete the model `{process_model_id}`, there are" + " existing instances that depend on it." + ), ) process_model = self.get_process_model(process_model_id) path = self.workflow_path(process_model) @@ -339,8 +342,11 @@ class ProcessModelService(FileSystemService): if len(problem_models) > 0: raise ApiError( error_code="existing_instances", - message=f"We cannot delete the group `{process_group_id}`, " - f"there are models with existing instances inside the group. {problem_models}", + message=( + f"We cannot delete the group `{process_group_id}`, there are" + " models with existing instances inside the group." + f" {problem_models}" + ), ) shutil.rmtree(path) self.cleanup_process_group_display_order() @@ -392,7 +398,10 @@ class ProcessModelService(FileSystemService): if process_group is None: raise ApiError( error_code="process_group_could_not_be_loaded_from_disk", - message=f"We could not load the process_group from disk from: {dir_path}", + message=( + "We could not load the process_group from disk from:" + f" {dir_path}" + ), ) else: process_group_id = dir_path.replace(FileSystemService.root_path(), "") @@ -457,7 +466,10 @@ class ProcessModelService(FileSystemService): if process_model_info is None: raise ApiError( error_code="process_model_could_not_be_loaded_from_disk", - message=f"We could not load the process_model from disk with data: {data}", + message=( + "We could not load the process_model from disk with data:" + f" {data}" + ), ) else: if name is None: diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/script_unit_test_runner.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/script_unit_test_runner.py index 9112e20f..ed331672 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/script_unit_test_runner.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/script_unit_test_runner.py @@ -112,7 +112,10 @@ class ScriptUnitTestRunner: except json.decoder.JSONDecodeError as ex: return ScriptUnitTestResult( result=False, - error=f"Failed to parse expectedOutputJson: {unit_test['expectedOutputJson']}: {str(ex)}", + error=( + "Failed to parse expectedOutputJson:" + f" {unit_test['expectedOutputJson']}: {str(ex)}" + ), ) script = task.task_spec.script diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/secret_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/secret_service.py index e4dee491..aa9e6d14 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/secret_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/secret_service.py @@ -44,8 +44,10 @@ class SecretService: except Exception as e: raise ApiError( error_code="create_secret_error", - message=f"There was an error creating a secret with key: {key} and value ending with: {value[:-4]}. " - f"Original error is {e}", + message=( + f"There was an error creating a secret with key: {key} and value" + f" ending with: {value[:-4]}. Original error is {e}" + ), ) from e return secret_model @@ -89,7 +91,9 @@ class SecretService: else: raise ApiError( error_code="update_secret_error", - message=f"Cannot update secret with key: {key}. Resource does not exist.", + message=( + f"Cannot update secret with key: {key}. Resource does not exist." + ), status_code=404, ) @@ -104,11 +108,16 @@ class SecretService: except Exception as e: raise ApiError( error_code="delete_secret_error", - message=f"Could not delete secret with key: {key}. Original error is: {e}", + message=( + f"Could not delete secret with key: {key}. Original error" + f" is: {e}" + ), ) from e else: raise ApiError( error_code="delete_secret_error", - message=f"Cannot delete secret with key: {key}. Resource does not exist.", + message=( + f"Cannot delete secret with key: {key}. Resource does not exist." + ), status_code=404, ) 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 72f59d1f..4fdfbd6d 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/spec_file_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/spec_file_service.py @@ -192,7 +192,8 @@ class SpecFileService(FileSystemService): 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}" + f"No file found with name {file_name} in" + f" {process_model_info.display_name}" ) with open(full_file_path, "rb") as f_handle: spec_file_data = f_handle.read() @@ -314,8 +315,9 @@ class SpecFileService(FileSystemService): ).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}" + "Could not find message model with identifier" + f" '{message_model_identifier}'Required by a Start Event in :" + f" {ref.file_name}" ) message_triggerable_process_model = ( MessageTriggerableProcessModel.query.filter_by( @@ -335,7 +337,8 @@ class SpecFileService(FileSystemService): != ref.process_model_id ): raise ValidationException( - f"Message model is already used to start process model {ref.process_model_id}" + "Message model is already used to start process model" + f" {ref.process_model_id}" ) @staticmethod @@ -353,8 +356,9 @@ class SpecFileService(FileSystemService): ).first() if message_model is None: raise ValidationException( - f"Could not find message model with identifier '{message_model_identifier}'" - f"specified by correlation property: {cpre}" + "Could not find message model with identifier" + f" '{message_model_identifier}'specified by correlation" + f" property: {cpre}" ) # fixme: I think we are currently ignoring the correction properties. message_correlation_property = ( diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/helpers/base_test.py b/spiffworkflow-backend/tests/spiffworkflow_backend/helpers/base_test.py index 8a314f2c..47cf2d87 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/helpers/base_test.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/helpers/base_test.py @@ -133,7 +133,6 @@ class BaseTest: ) -> TestResponse: """Create_process_model.""" if process_model_id is not None: - # make sure we have a group process_group_id, _ = os.path.split(process_model_id) modified_process_group_id = process_group_id.replace("/", ":") @@ -141,7 +140,6 @@ class BaseTest: os.path.join(FileSystemService.root_path(), process_group_id) ) if ProcessModelService.is_group(process_group_path): - if exception_notification_addresses is None: exception_notification_addresses = [] @@ -171,7 +169,8 @@ class BaseTest: raise Exception("You must create the group first") else: raise Exception( - "You must include the process_model_id, which must be a path to the model" + "You must include the process_model_id, which must be a path to the" + " model" ) def get_test_data_file_contents( 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 809063cb..461cdcd4 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py @@ -343,7 +343,8 @@ class TestProcessApi(BaseTest): assert data["error_code"] == "existing_instances" assert ( data["message"] - == f"We cannot delete the model `{process_model_identifier}`, there are existing instances that depend on it." + == f"We cannot delete the model `{process_model_identifier}`, there are" + " existing instances that depend on it." ) def test_process_model_update( @@ -2058,7 +2059,6 @@ class TestProcessApi(BaseTest): mail = app.config["MAIL_APP"] with mail.record_messages() as outbox: - response = client.post( f"/v1.0/process-instances/{self.modify_process_identifier_for_path_param(process_model_identifier)}/{process_instance_id}/run", headers=self.logged_in_headers(with_super_admin_user), @@ -2961,7 +2961,9 @@ class TestProcessApi(BaseTest): ) -> None: """Test_can_get_process_instance_list_with_report_metadata.""" process_model = load_test_spec( - process_model_id="save_process_instance_metadata/save_process_instance_metadata", + process_model_id=( + "save_process_instance_metadata/save_process_instance_metadata" + ), bpmn_file_name="save_process_instance_metadata.bpmn", process_model_source_directory="save_process_instance_metadata", ) @@ -3018,7 +3020,9 @@ class TestProcessApi(BaseTest): ) -> None: """Test_can_get_process_instance_list_with_report_metadata.""" process_model = load_test_spec( - process_model_id="save_process_instance_metadata/save_process_instance_metadata", + process_model_id=( + "save_process_instance_metadata/save_process_instance_metadata" + ), bpmn_file_name="save_process_instance_metadata.bpmn", process_model_source_directory="save_process_instance_metadata", ) diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_save_process_instance_metadata.py b/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_save_process_instance_metadata.py index 5f66fa72..738896cd 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_save_process_instance_metadata.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_save_process_instance_metadata.py @@ -28,7 +28,9 @@ class TestSaveProcessInstanceMetadata(BaseTest): client, with_super_admin_user, "test_group", "test_group" ) process_model = load_test_spec( - process_model_id="save_process_instance_metadata/save_process_instance_metadata", + process_model_id=( + "save_process_instance_metadata/save_process_instance_metadata" + ), bpmn_file_name="save_process_instance_metadata.bpmn", process_model_source_directory="save_process_instance_metadata", ) diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_permissions.py b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_permissions.py index b66f3237..a9698969 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_permissions.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_permissions.py @@ -16,6 +16,7 @@ from spiffworkflow_backend.services.user_service import UserService # we think we can get the list of roles for a user. # spiff needs a way to determine what each role allows. + # user role allows list and read of all process groups/models # super-admin role allows create, update, and delete of all process groups/models # * super-admins users maybe conventionally get the user role as well diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_instance_processor.py b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_instance_processor.py index 4f7db3ad..b4a650dc 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_instance_processor.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_instance_processor.py @@ -52,7 +52,8 @@ class TestProcessInstanceProcessor(BaseTest): result = script_engine._evaluate("fact_service(type='norris')", {}) assert ( result - == "Chuck Norris doesn’t read books. He stares them down until he gets the information he wants." + == "Chuck Norris doesn’t read books. He stares them down until he gets the" + " information he wants." ) app.config["THREAD_LOCAL_DATA"].process_model_identifier = None app.config["THREAD_LOCAL_DATA"].process_instance_id = None diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_instance_report_service.py b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_instance_report_service.py index 75ad3f28..b40412ff 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_instance_report_service.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_instance_report_service.py @@ -880,7 +880,9 @@ class TestProcessInstanceReportService(BaseTest): process_instance_report = ProcessInstanceReportService.report_with_identifier( user=user_one, - report_identifier="system_report_completed_instances_with_tasks_completed_by_me", + report_identifier=( + "system_report_completed_instances_with_tasks_completed_by_me" + ), ) report_filter = ( ProcessInstanceReportService.filter_from_metadata_with_overrides( @@ -983,7 +985,9 @@ class TestProcessInstanceReportService(BaseTest): process_instance_report = ProcessInstanceReportService.report_with_identifier( user=user_one, - report_identifier="system_report_completed_instances_with_tasks_completed_by_my_groups", + report_identifier=( + "system_report_completed_instances_with_tasks_completed_by_my_groups" + ), ) report_filter = ( ProcessInstanceReportService.filter_from_metadata_with_overrides( From df462e0dd09ea94e38f67511d41a118ce04030fd Mon Sep 17 00:00:00 2001 From: jasquat Date: Tue, 3 Jan 2023 16:39:32 -0500 Subject: [PATCH 16/27] added method to create process models based on english string w/ burnettk --- .../routes/process_instances_controller.py | 2 - .../routes/process_models_controller.py | 71 +++++++++++++------ .../services/file_system_service.py | 5 +- .../process_instance_report_service.py | 53 +++++++------- .../basic_with_user_task_template.bpmn | 45 ++++++++++++ .../form-identifier-id-template-schema.json | 6 ++ .../form-identifier-id-template-uischema.json | 3 + .../integration/test_process_api.py | 19 +++++ 8 files changed, 155 insertions(+), 49 deletions(-) create mode 100644 spiffworkflow-backend/src/spiffworkflow_backend/templates/basic_with_user_task_template.bpmn create mode 100644 spiffworkflow-backend/src/spiffworkflow_backend/templates/form-identifier-id-template-schema.json create mode 100644 spiffworkflow-backend/src/spiffworkflow_backend/templates/form-identifier-id-template-uischema.json diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_instances_controller.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_instances_controller.py index 50f1fc64..c1c4c1b0 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_instances_controller.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_instances_controller.py @@ -396,7 +396,6 @@ def process_instance_report_update( report_id: int, body: Dict[str, Any], ) -> flask.wrappers.Response: - """Process_instance_report_create.""" process_instance_report = ProcessInstanceReportModel.query.filter_by( id=report_id, created_by_id=g.user.id, @@ -417,7 +416,6 @@ def process_instance_report_update( def process_instance_report_delete( report_id: int, ) -> flask.wrappers.Response: - """Process_instance_report_create.""" process_instance_report = ProcessInstanceReportModel.query.filter_by( id=report_id, created_by_id=g.user.id, diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_models_controller.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_models_controller.py index a1c750cd..3c3a7382 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_models_controller.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_models_controller.py @@ -1,5 +1,8 @@ """APIs for dealing with process groups, process models, and process instances.""" import json +import os +from spiffworkflow_backend.models.process_instance_report import ProcessInstanceReportModel +from spiffworkflow_backend.services.file_system_service import FileSystemService import re from typing import Any from typing import Dict @@ -26,6 +29,7 @@ from spiffworkflow_backend.routes.process_api_blueprint import ( ) from spiffworkflow_backend.services.git_service import GitService from spiffworkflow_backend.services.git_service import MissingGitConfigsError +from spiffworkflow_backend.services.process_instance_report_service import ProcessInstanceReportService from spiffworkflow_backend.services.process_model_service import ProcessModelService from spiffworkflow_backend.services.spec_file_service import SpecFileService @@ -302,20 +306,6 @@ def process_model_create_with_natural_language( modified_process_group_id: str, body: Dict[str, str] ) -> flask.wrappers.Response: """Process_model_create_with_natural_language.""" - # body_include_list = [ - # "id", - # "display_name", - # "primary_file_name", - # "primary_process_id", - # "description", - # "metadata_extraction_paths", - # ] - # body_filtered = { - # include_item: body[include_item] - # for include_item in body_include_list - # if include_item in body - # } - pattern = re.compile( r"Create a (?P.*?) process model with a (?P.*?) form that" r" collects (?P.*)" @@ -333,17 +323,13 @@ def process_model_create_with_natural_language( process_model_display_name = match.group("pm_name") process_model_identifier = re.sub(r"[ _]", "-", process_model_display_name) process_model_identifier = re.sub(r"-{2,}", "-", process_model_identifier).lower() - print(f"process_model_identifier: {process_model_identifier}") form_name = match.group("form_name") form_identifier = re.sub(r"[ _]", "-", form_name) form_identifier = re.sub(r"-{2,}", "-", form_identifier).lower() - print(f"form_identifier: {form_identifier}") column_names = match.group("columns") - print(f"column_names: {column_names}") columns = re.sub(r"(, (and )?)", ",", column_names).split(",") - print(f"columns: {columns}") process_group = _get_process_group_from_modified_identifier( modified_process_group_id @@ -371,14 +357,59 @@ def process_model_create_with_natural_language( status_code=400, ) + bpmn_template_file = os.path.join(current_app.root_path, 'templates', 'basic_with_user_task_template.bpmn') + if not os.path.exists(bpmn_template_file): + raise ApiError( + error_code="bpmn_template_file_does_not_exist", + message=f"Could not find the bpmn template file to create process model.", + status_code=500, + ) + ProcessModelService.add_process_model(process_model_info) + bpmn_process_identifier = f"{process_model_info.id}_process" + bpmn_template_contents = '' + with open(bpmn_template_file, encoding="utf-8") as f: + bpmn_template_contents = f.read() + + bpmn_template_contents = bpmn_template_contents.replace('natural_language_process_id_template', bpmn_process_identifier) + bpmn_template_contents = bpmn_template_contents.replace('form-identifier-id-template-', form_identifier) + + form_uischema_json: dict = { + "ui:order": [] + } + + form_properties: dict = {} + for column in columns: + form_properties[column] = { + "type": "string", + "title": column, + } + form_schema_json = { + "title": form_identifier, + "description": "", + "properties": form_properties, + "required": [] + } + + SpecFileService.add_file(process_model_info, f"{process_model_identifier}.bpmn", str.encode(bpmn_template_contents)) + SpecFileService.add_file(process_model_info, f"{form_identifier}-schema.json", str.encode(json.dumps(form_schema_json))) + SpecFileService.add_file(process_model_info, f"{form_identifier}-uischema.json", str.encode(json.dumps(form_uischema_json))) + _commit_and_push_to_git( f"User: {g.user.username} created process model via natural language:" f" {process_model_info.id}" ) - # TODO: Create a form json schema and UI schema - # TODO: Add report + default_report_metadata = ProcessInstanceReportService.system_metadata_map('default') + for column in columns: + default_report_metadata['columns'].append({ + "Header": column, "accessor": column + }) + ProcessInstanceReportModel.create_report( + identifier=process_model_identifier, + user=g.user, + report_metadata=default_report_metadata, + ) return Response( json.dumps(ProcessModelInfoSchema().dump(process_model_info)), 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 a2a9181d..06bfd421 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/file_system_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/file_system_service.py @@ -40,10 +40,9 @@ class FileSystemService: @staticmethod def root_path() -> str: """Root_path.""" - # fixme: allow absolute files dir_name = current_app.config["BPMN_SPEC_ABSOLUTE_DIR"] - app_root = current_app.root_path - return os.path.abspath(os.path.join(app_root, "..", dir_name)) + # ensure this is a string - thanks mypy... + return os.path.abspath(os.path.join(dir_name, '')) @staticmethod def id_string_to_relative_path(id_string: str) -> str: diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py index 7d725fa2..2ae12769 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py @@ -1,5 +1,6 @@ """Process_instance_report_service.""" import re +from typing import Any from dataclasses import dataclass from typing import Optional @@ -84,29 +85,7 @@ class ProcessInstanceReportService: """ProcessInstanceReportService.""" @classmethod - def report_with_identifier( - cls, - user: UserModel, - report_id: Optional[int] = None, - report_identifier: Optional[str] = None, - ) -> ProcessInstanceReportModel: - """Report_with_filter.""" - if report_id is not None: - process_instance_report = ProcessInstanceReportModel.query.filter_by( - id=report_id, created_by_id=user.id - ).first() - if process_instance_report is not None: - return process_instance_report # type: ignore - - if report_identifier is None: - report_identifier = "default" - process_instance_report = ProcessInstanceReportModel.query.filter_by( - identifier=report_identifier, created_by_id=user.id - ).first() - - if process_instance_report is not None: - return process_instance_report # type: ignore - + def system_metadata_map(cls, metadata_key: str) -> dict[str, Any]: # TODO replace with system reports that are loaded on launch (or similar) temp_system_metadata_map = { "default": { @@ -151,10 +130,36 @@ class ProcessInstanceReportService: "order_by": ["-start_in_seconds", "-id"], }, } + return temp_system_metadata_map[metadata_key] + + @classmethod + def report_with_identifier( + cls, + user: UserModel, + report_id: Optional[int] = None, + report_identifier: Optional[str] = None, + ) -> ProcessInstanceReportModel: + """Report_with_filter.""" + if report_id is not None: + process_instance_report = ProcessInstanceReportModel.query.filter_by( + id=report_id, created_by_id=user.id + ).first() + if process_instance_report is not None: + return process_instance_report # type: ignore + + if report_identifier is None: + report_identifier = "default" + process_instance_report = ProcessInstanceReportModel.query.filter_by( + identifier=report_identifier, created_by_id=user.id + ).first() + + if process_instance_report is not None: + return process_instance_report # type: ignore + process_instance_report = ProcessInstanceReportModel( identifier=report_identifier, created_by_id=user.id, - report_metadata=temp_system_metadata_map[report_identifier], + report_metadata=cls.system_metadata_map(report_identifier), ) return process_instance_report # type: ignore diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/templates/basic_with_user_task_template.bpmn b/spiffworkflow-backend/src/spiffworkflow_backend/templates/basic_with_user_task_template.bpmn new file mode 100644 index 00000000..2e33d429 --- /dev/null +++ b/spiffworkflow-backend/src/spiffworkflow_backend/templates/basic_with_user_task_template.bpmn @@ -0,0 +1,45 @@ + + + + + Flow_0gixxkm + + + + + + + + + + Flow_0gixxkm + Flow_1oi9nsn + + + Flow_1oi9nsn + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/templates/form-identifier-id-template-schema.json b/spiffworkflow-backend/src/spiffworkflow_backend/templates/form-identifier-id-template-schema.json new file mode 100644 index 00000000..6c2c075d --- /dev/null +++ b/spiffworkflow-backend/src/spiffworkflow_backend/templates/form-identifier-id-template-schema.json @@ -0,0 +1,6 @@ +{ + "title": "{FORM_IDENTIFIER}", + "description": "", + "properties": {}, + "required": [] +} \ No newline at end of file diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/templates/form-identifier-id-template-uischema.json b/spiffworkflow-backend/src/spiffworkflow_backend/templates/form-identifier-id-template-uischema.json new file mode 100644 index 00000000..e8e87ab4 --- /dev/null +++ b/spiffworkflow-backend/src/spiffworkflow_backend/templates/form-identifier-id-template-uischema.json @@ -0,0 +1,3 @@ +{ + "ui:order": [] +} \ No newline at end of file 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 461cdcd4..7096499e 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py @@ -205,6 +205,25 @@ class TestProcessApi(BaseTest): {"key": "priority", "path": "priority"}, ] + process_model = ProcessModelService.get_process_model(response.json['id']) + process_model_path = os.path.join( + FileSystemService.root_path(), + FileSystemService.id_string_to_relative_path(process_model.id) + ) + + process_model_diagram = os.path.join(process_model_path, "bug-tracker.bpmn") + assert os.path.exists(process_model_diagram) + form_schema_json = os.path.join(process_model_path, "bug-details-schema.json") + assert os.path.exists(form_schema_json) + form_uischema_json = os.path.join(process_model_path, "bug-details-uischema.json") + assert os.path.exists(form_uischema_json) + + process_instance_report = ProcessInstanceReportModel.query.filter_by(identifier='bug-tracker').first() + assert process_instance_report is not None + report_column_accessors = [i['accessor'] for i in process_instance_report.report_metadata['columns']] + expected_column_accessors = ['id', 'process_model_display_name', 'start_in_seconds', 'end_in_seconds', 'username', 'status', 'summary', 'description', 'priority'] + assert report_column_accessors == expected_column_accessors + def test_primary_process_id_updates_via_xml( self, app: Flask, From 2c4ae424a4df0cce9badc42fec0f948300cb320f Mon Sep 17 00:00:00 2001 From: jasquat Date: Tue, 3 Jan 2023 17:26:01 -0500 Subject: [PATCH 17/27] added new page to create process models using english text w/ burnettk --- .../routes/process_instances_controller.py | 2 + .../routes/process_models_controller.py | 18 +++-- .../process_instance_report_service.py | 1 + .../integration/test_process_api.py | 3 +- .../src/routes/AdminRoutes.tsx | 5 ++ .../routes/ProcessModelNewExperimental.tsx | 73 +++++++++++++++++++ 6 files changed, 93 insertions(+), 9 deletions(-) create mode 100644 spiffworkflow-frontend/src/routes/ProcessModelNewExperimental.tsx diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_instances_controller.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_instances_controller.py index c1c4c1b0..3f7da50c 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_instances_controller.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_instances_controller.py @@ -396,6 +396,7 @@ def process_instance_report_update( report_id: int, body: Dict[str, Any], ) -> flask.wrappers.Response: + """Process_instance_report_update.""" process_instance_report = ProcessInstanceReportModel.query.filter_by( id=report_id, created_by_id=g.user.id, @@ -416,6 +417,7 @@ def process_instance_report_update( def process_instance_report_delete( report_id: int, ) -> flask.wrappers.Response: + """Process_instance_report_delete.""" process_instance_report = ProcessInstanceReportModel.query.filter_by( id=report_id, created_by_id=g.user.id, diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_models_controller.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_models_controller.py index 3c3a7382..467062be 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_models_controller.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_models_controller.py @@ -2,7 +2,6 @@ import json import os from spiffworkflow_backend.models.process_instance_report import ProcessInstanceReportModel -from spiffworkflow_backend.services.file_system_service import FileSystemService import re from typing import Any from typing import Dict @@ -366,16 +365,17 @@ def process_model_create_with_natural_language( ) ProcessModelService.add_process_model(process_model_info) - bpmn_process_identifier = f"{process_model_info.id}_process" + bpmn_process_identifier = f"{process_model_identifier}_process" bpmn_template_contents = '' with open(bpmn_template_file, encoding="utf-8") as f: bpmn_template_contents = f.read() - bpmn_template_contents = bpmn_template_contents.replace('natural_language_process_id_template', bpmn_process_identifier) - bpmn_template_contents = bpmn_template_contents.replace('form-identifier-id-template-', form_identifier) + bpmn_template_contents = bpmn_template_contents.replace( + 'natural_language_process_id_template', bpmn_process_identifier) + bpmn_template_contents = bpmn_template_contents.replace('form-identifier-id-template', form_identifier) form_uischema_json: dict = { - "ui:order": [] + "ui:order": columns } form_properties: dict = {} @@ -392,8 +392,10 @@ def process_model_create_with_natural_language( } SpecFileService.add_file(process_model_info, f"{process_model_identifier}.bpmn", str.encode(bpmn_template_contents)) - SpecFileService.add_file(process_model_info, f"{form_identifier}-schema.json", str.encode(json.dumps(form_schema_json))) - SpecFileService.add_file(process_model_info, f"{form_identifier}-uischema.json", str.encode(json.dumps(form_uischema_json))) + SpecFileService.add_file(process_model_info, f"{form_identifier}-schema.json", + str.encode(json.dumps(form_schema_json))) + SpecFileService.add_file( + process_model_info, f"{form_identifier}-uischema.json", str.encode(json.dumps(form_uischema_json))) _commit_and_push_to_git( f"User: {g.user.username} created process model via natural language:" @@ -403,7 +405,7 @@ def process_model_create_with_natural_language( default_report_metadata = ProcessInstanceReportService.system_metadata_map('default') for column in columns: default_report_metadata['columns'].append({ - "Header": column, "accessor": column + "Header": column, "accessor": column, "filterable": True }) ProcessInstanceReportModel.create_report( identifier=process_model_identifier, diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py index 2ae12769..cd72ccfa 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py @@ -86,6 +86,7 @@ class ProcessInstanceReportService: @classmethod def system_metadata_map(cls, metadata_key: str) -> dict[str, Any]: + """System_metadata_map.""" # TODO replace with system reports that are loaded on launch (or similar) temp_system_metadata_map = { "default": { 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 7096499e..d058f901 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py @@ -221,7 +221,8 @@ class TestProcessApi(BaseTest): process_instance_report = ProcessInstanceReportModel.query.filter_by(identifier='bug-tracker').first() assert process_instance_report is not None report_column_accessors = [i['accessor'] for i in process_instance_report.report_metadata['columns']] - expected_column_accessors = ['id', 'process_model_display_name', 'start_in_seconds', 'end_in_seconds', 'username', 'status', 'summary', 'description', 'priority'] + expected_column_accessors = ['id', 'process_model_display_name', 'start_in_seconds', + 'end_in_seconds', 'username', 'status', 'summary', 'description', 'priority'] assert report_column_accessors == expected_column_accessors def test_primary_process_id_updates_via_xml( diff --git a/spiffworkflow-frontend/src/routes/AdminRoutes.tsx b/spiffworkflow-frontend/src/routes/AdminRoutes.tsx index cc120c51..2d61439b 100644 --- a/spiffworkflow-frontend/src/routes/AdminRoutes.tsx +++ b/spiffworkflow-frontend/src/routes/AdminRoutes.tsx @@ -22,6 +22,7 @@ import ProcessInstanceLogList from './ProcessInstanceLogList'; import MessageInstanceList from './MessageInstanceList'; import Configuration from './Configuration'; import JsonSchemaFormBuilder from './JsonSchemaFormBuilder'; +import ProcessModelNewExperimental from './ProcessModelNewExperimental'; export default function AdminRoutes() { const location = useLocation(); @@ -50,6 +51,10 @@ export default function AdminRoutes() { path="process-models/:process_group_id/new" element={} /> + } + /> } diff --git a/spiffworkflow-frontend/src/routes/ProcessModelNewExperimental.tsx b/spiffworkflow-frontend/src/routes/ProcessModelNewExperimental.tsx new file mode 100644 index 00000000..af8be822 --- /dev/null +++ b/spiffworkflow-frontend/src/routes/ProcessModelNewExperimental.tsx @@ -0,0 +1,73 @@ +import { useState } from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; +// @ts-ignore +import { TextArea, Button, Form } from '@carbon/react'; +import ProcessBreadcrumb from '../components/ProcessBreadcrumb'; +import { ProcessModel } from '../interfaces'; +import { modifyProcessIdentifierForPathParam } from '../helpers'; +import HttpService from '../services/HttpService'; + +export default function ProcessModelNewExperimental() { + const params = useParams(); + const navigate = useNavigate(); + const [processModelDescriptiveText, setProcessModelDescriptiveText] = + useState(''); + + const helperText = + 'Create a bug tracker process model with a bug-details form that collects summary, description, and priority'; + + const navigateToProcessModel = (result: ProcessModel) => { + if ('id' in result) { + const modifiedProcessModelPathFromResult = + modifyProcessIdentifierForPathParam(result.id); + navigate(`/admin/process-models/${modifiedProcessModelPathFromResult}`); + } + }; + + const handleFormSubmission = (event: any) => { + event.preventDefault(); + HttpService.makeCallToBackend({ + path: `/process-models-natural-language/${params.process_group_id}`, + successCallback: navigateToProcessModel, + httpMethod: 'POST', + postBody: { natural_language_text: processModelDescriptiveText }, + }); + }; + + const ohYeeeeaah = () => { + setProcessModelDescriptiveText(helperText); + }; + + return ( + <> + + {/* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions */} +

+ Add Process Model +

+
+