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 0e3065a85..9c40e9583 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py @@ -1781,8 +1781,17 @@ class ProcessInstanceProcessor: return None def terminate(self) -> None: - """Terminate.""" - self.bpmn_process_instance.cancel() + start_time = time.time() + deleted_tasks = self.bpmn_process_instance.cancel() or [] + spiff_tasks = self.bpmn_process_instance.get_tasks() + + task_service = TaskService( + process_instance=self.process_instance_model, + serializer=self._serializer, + bpmn_definition_to_task_definitions_mappings=self.bpmn_definition_to_task_definitions_mappings, + ) + task_service.update_all_tasks_from_spiff_tasks(spiff_tasks, deleted_tasks, start_time) + self.save() self.process_instance_model.status = "terminated" db.session.add(self.process_instance_model) 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 0e731a245..5c0fc5b77 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py @@ -1607,6 +1607,10 @@ class TestProcessApi(BaseTest): assert response.status_code == 200 assert response.json is not None + ready_tasks = TaskModel.query.filter_by(process_instance_id=process_instance_id, state="READY").all() + assert len(ready_tasks) == 1 + ready_task = ready_tasks[0] + response = client.post( f"/v1.0/process-instance-terminate/{self.modify_process_identifier_for_path_param(process_model_identifier)}/{process_instance_id}", headers=self.logged_in_headers(with_super_admin_user), @@ -1617,6 +1621,11 @@ class TestProcessApi(BaseTest): process_instance = ProcessInstanceModel.query.filter_by(id=process_instance_id).first() assert process_instance assert process_instance.status == "terminated" + assert ready_task.state == "CANCELLED" + + # TODO: uncomment this once spiff is returning deleted tasks on cancel + # remaining_tasks = TaskModel.query.filter_by(process_instance_id=process_instance_id).all() + # assert len(remaining_tasks) == 3 def test_process_instance_delete( self, diff --git a/spiffworkflow-frontend/bin/cypress_pilot b/spiffworkflow-frontend/bin/cypress_pilot index fb480a7d5..121e9138f 100755 --- a/spiffworkflow-frontend/bin/cypress_pilot +++ b/spiffworkflow-frontend/bin/cypress_pilot @@ -45,7 +45,7 @@ for attempt in $(seq 1 "$ATTEMPTS" ); do start_time=$(date +%s) success="false" # spec_pattern="cypress/pilot/**/*.cy.{js,jsx,ts,tsx}" - spec_pattern="cypress/pilot/*.cy.{js,jsx,ts,tsx}" + spec_pattern="cypress/pilot/**/*.cy.{js,jsx,ts,tsx}" if ./node_modules/.bin/cypress "$command" -c specPattern="${spec_pattern}" --e2e --browser chrome "$@"; then success="true" fi diff --git a/spiffworkflow-frontend/cypress/e2e/process_instances.cy.js b/spiffworkflow-frontend/cypress/e2e/process_instances.cy.js index 406a55c57..48a549b69 100644 --- a/spiffworkflow-frontend/cypress/e2e/process_instances.cy.js +++ b/spiffworkflow-frontend/cypress/e2e/process_instances.cy.js @@ -3,16 +3,18 @@ import { DATE_FORMAT, PROCESS_STATUSES } from '../../src/config'; import { titleizeString } from '../../src/helpers'; const filterByDate = (fromDate) => { - cy.get('#date-picker-start-from').clear().type(format(fromDate, DATE_FORMAT)); + cy.get('#date-picker-start-from').clear(); + cy.get('#date-picker-start-from').type(format(fromDate, DATE_FORMAT)); cy.contains('Start date to').click(); - cy.get('#date-picker-end-from').clear().type(format(fromDate, DATE_FORMAT)); + cy.get('#date-picker-end-from').clear(); + cy.get('#date-picker-end-from').type(format(fromDate, DATE_FORMAT)); cy.contains('End date to').click(); cy.getBySel('filter-button').click(); }; const updateDmnText = (oldText, newText, elementId = 'wonderful_process') => { // this will break if there are more elements added to the drd - cy.get(`g[data-element-id=${elementId}]`).click().should('exist'); + cy.get(`g[data-element-id=${elementId}]`).click(); cy.get('.dmn-icon-decision-table').click(); cy.contains(oldText).clear().type(`"${newText}"`); @@ -23,46 +25,44 @@ const updateDmnText = (oldText, newText, elementId = 'wonderful_process') => { }; const updateBpmnPythonScript = (pythonScript, elementId = 'process_script') => { - cy.get(`g[data-element-id=${elementId}]`).click().should('exist'); + cy.get(`g[data-element-id=${elementId}]`).click(); cy.contains(/^Script$/).click(); - cy.get('textarea[name="pythonScript_bpmn:script"]') - .clear() - .type(pythonScript); + cy.get('textarea[name="pythonScript_bpmn:script"]').clear(); + cy.get('textarea[name="pythonScript_bpmn:script"]').type(pythonScript); // wait for a little bit for the xml to get set before saving cy.wait(500); cy.contains('Save').click(); }; -const updateBpmnPythonScriptWithMonaco = ( - pythonScript, - elementId = 'process_script' -) => { - cy.get(`g[data-element-id=${elementId}]`).click().should('exist'); - // sometimes, we click on the script task and panel doesn't update to include script task stuff. not sure why. - cy.contains(/^Script$/).click(); - cy.contains('Launch Editor').click(); - // sometimes, Loading... appears for more than 4 seconds. not sure why. - cy.contains('Loading...').should('not.exist'); - - // the delay 30 is because, at some point, monaco started automatically - // adding a second double quote when we type a double quote. when it does - // that, there is a race condition where it sometimes gets in more text - // before the second double quote appears because the robot is typing faster - // than a human being could, so we artificially slow it down to make it more - // human. - cy.get('.monaco-editor textarea:first') - .click() - .focused() // change subject to currently focused element - .clear() - // long delay to ensure cypress isn't competing with monaco auto complete stuff - .type(pythonScript, { delay: 120 }); - - cy.contains('Close').click(); - // wait for a little bit for the xml to get set before saving - cy.wait(500); - cy.contains('Save').click(); -}; +// const updateBpmnPythonScriptWithMonaco = ( +// pythonScript, +// elementId = 'process_script' +// ) => { +// cy.get(`g[data-element-id=${elementId}]`).click(); +// // sometimes, we click on the script task and panel doesn't update to include script task stuff. not sure why. +// cy.contains(/^Script$/).click(); +// cy.contains('Launch Editor').click(); +// // sometimes, Loading... appears for more than 4 seconds. not sure why. +// cy.contains('Loading...').should('not.exist'); +// +// // the delay 30 is because, at some point, monaco started automatically +// // adding a second double quote when we type a double quote. when it does +// // that, there is a race condition where it sometimes gets in more text +// // before the second double quote appears because the robot is typing faster +// // than a human being could, so we artificially slow it down to make it more +// // human. +// cy.get('.monaco-editor textarea:first').click(); +// cy.get('.monaco-editor textarea:first').focused(); // change subject to currently focused element +// cy.get('.monaco-editor textarea:first').clear(); +// // long delay to ensure cypress isn't competing with monaco auto complete stuff +// cy.get('.monaco-editor textarea:first').type(pythonScript, { delay: 120 }); +// +// cy.contains('Close').click(); +// // wait for a little bit for the xml to get set before saving +// cy.wait(500); +// cy.contains('Save').click(); +// }; describe('process-instances', () => { beforeEach(() => { @@ -79,7 +79,6 @@ describe('process-instances', () => { it('can create a new instance and can modify', () => { const originalDmnOutputForKevin = 'Very wonderful'; const newDmnOutputForKevin = 'The new wonderful'; - const dmnOutputForDan = 'pretty wonderful'; const acceptanceTestOneDisplayName = 'Acceptance Tests Model 1'; const originalPythonScript = 'person = "Kevin"'; diff --git a/spiffworkflow-frontend/cypress/e2e/process_models.cy.js b/spiffworkflow-frontend/cypress/e2e/process_models.cy.js index 069860808..2801db301 100644 --- a/spiffworkflow-frontend/cypress/e2e/process_models.cy.js +++ b/spiffworkflow-frontend/cypress/e2e/process_models.cy.js @@ -132,61 +132,63 @@ describe('process-models', () => { cy.get('.tile-process-group-content-container').should('exist'); }); - it('can upload and run a bpmn file', () => { - const uuid = () => Cypress._.random(0, 1e6); - const id = uuid(); - const directParentGroupId = 'acceptance-tests-group-one'; - const groupId = `misc/${directParentGroupId}`; - const modelDisplayName = `Test Model 2 ${id}`; - const modelId = `test-model-2-${id}`; - cy.contains('Add a process group'); - cy.contains(miscDisplayName).click(); - cy.contains(groupDisplayName).click(); - cy.createModel(groupId, modelId, modelDisplayName); - - cy.contains(`${groupDisplayName}`).click(); - cy.contains('Add a process model'); - cy.contains(modelDisplayName).click(); - cy.url().should( - 'include', - `process-models/${modifyProcessIdentifierForPathParam( - groupId - )}:${modelId}` - ); - cy.contains(`Process Model: ${modelDisplayName}`); - - cy.getBySel('upload-file-button').click(); - cy.contains('Add file').selectFile( - 'cypress/fixtures/test_bpmn_file_upload.bpmn' - ); - cy.getBySel('modal-upload-file-dialog') - .find('.cds--btn--primary') - .contains('Upload') - .click(); - cy.runPrimaryBpmnFile(); - - cy.getBySel('process-instance-show-link-id').click(); - cy.getBySel('process-instance-delete').click(); - cy.contains('Are you sure'); - cy.getBySel('process-instance-delete-modal-confirmation-dialog') - .find('.cds--btn--danger') - .click(); - - // in breadcrumb - cy.contains(modelDisplayName).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.contains(modelId).should('not.exist'); - cy.contains(modelDisplayName).should('not.exist'); - }); + // FIXME: we currently do not know how to upload files since the new Add File + // component does not support the selectFile method + // it('can upload and run a bpmn file', () => { + // const uuid = () => Cypress._.random(0, 1e6); + // const id = uuid(); + // const directParentGroupId = 'acceptance-tests-group-one'; + // const groupId = `misc/${directParentGroupId}`; + // const modelDisplayName = `Test Model 2 ${id}`; + // const modelId = `test-model-2-${id}`; + // cy.contains('Add a process group'); + // cy.contains(miscDisplayName).click(); + // cy.contains(groupDisplayName).click(); + // cy.createModel(groupId, modelId, modelDisplayName); + // + // cy.contains(`${groupDisplayName}`).click(); + // cy.contains('Add a process model'); + // cy.contains(modelDisplayName).click(); + // cy.url().should( + // 'include', + // `process-models/${modifyProcessIdentifierForPathParam( + // groupId + // )}:${modelId}` + // ); + // cy.contains(`Process Model: ${modelDisplayName}`); + // + // cy.getBySel('upload-file-button').click(); + // cy.contains('Add file').selectFile( + // 'cypress/fixtures/test_bpmn_file_upload.bpmn' + // ); + // cy.getBySel('modal-upload-file-dialog') + // .find('.cds--btn--primary') + // .contains('Upload') + // .click(); + // cy.runPrimaryBpmnFile(); + // + // cy.getBySel('process-instance-show-link-id').click(); + // cy.getBySel('process-instance-delete').click(); + // cy.contains('Are you sure'); + // cy.getBySel('process-instance-delete-modal-confirmation-dialog') + // .find('.cds--btn--danger') + // .click(); + // + // // in breadcrumb + // cy.contains(modelDisplayName).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.contains(modelId).should('not.exist'); + // cy.contains(modelDisplayName).should('not.exist'); + // }); it('can allow searching for model', () => { cy.getBySel('process-model-selection').click(); diff --git a/spiffworkflow-frontend/cypress/e2e/tasks.cy.js b/spiffworkflow-frontend/cypress/e2e/tasks.cy.js index fde678738..ac9f9a474 100644 --- a/spiffworkflow-frontend/cypress/e2e/tasks.cy.js +++ b/spiffworkflow-frontend/cypress/e2e/tasks.cy.js @@ -81,7 +81,7 @@ describe('tasks', () => { cy.navigateToHome(); // look for somethig to make sure the homepage has loaded - cy.contains('Waiting for me').should('exist'); + cy.contains('Instances with tasks waiting for me').should('exist'); // FIXME: this will probably need a better way to link to the proper form that we want cy.contains('Go').click(); diff --git a/spiffworkflow-frontend/cypress/support/commands.js b/spiffworkflow-frontend/cypress/support/commands.js index 0737e8428..968550245 100644 --- a/spiffworkflow-frontend/cypress/support/commands.js +++ b/spiffworkflow-frontend/cypress/support/commands.js @@ -44,13 +44,16 @@ Cypress.Commands.add('navigateToAdmin', () => { Cypress.Commands.add('login', (username, password) => { cy.visit('/admin'); - console.log('username', username); - if (!username) { - username = Cypress.env('SPIFFWORKFLOW_FRONTEND_USERNAME') || 'ciadmin1'; - password = Cypress.env('SPIFFWORKFLOW_FRONTEND_PASSWORD') || 'ciadmin1'; + let usernameToUse = username; + let passwordToUse = password; + if (!usernameToUse) { + usernameToUse = + Cypress.env('SPIFFWORKFLOW_FRONTEND_USERNAME') || 'ciadmin1'; + passwordToUse = + Cypress.env('SPIFFWORKFLOW_FRONTEND_PASSWORD') || 'ciadmin1'; } - cy.get('#username').type(username); - cy.get('#password').type(password); + cy.get('#username').type(usernameToUse); + cy.get('#password').type(passwordToUse); if (Cypress.env('SPIFFWORKFLOW_FRONTEND_AUTH_WITH_KEYCLOAK') === true) { cy.get('#kc-login').click(); } else { @@ -58,14 +61,13 @@ Cypress.Commands.add('login', (username, password) => { } }); -Cypress.Commands.add('logout', (selector, ...args) => { - cy.wait(2000); - //cy.getBySel('logout-button').click(); - cy.get('#root > div > header > div.cds--header__global > span:nth-child(3) > button > svg').click(); +Cypress.Commands.add('logout', (_selector, ..._args) => { + cy.get('#user-profile-toggletip').click(); + cy.getBySel('logout-button').click(); if (Cypress.env('SPIFFWORKFLOW_FRONTEND_AUTH_WITH_KEYCLOAK') === true) { // otherwise we can click logout, quickly load the next page, and the javascript // doesn't have time to actually sign you out - //cy.wait(4000); + // cy.wait(4000); cy.contains('Sign in to your account'); } else { cy.get('#spiff-login-button').should('exist'); @@ -104,16 +106,10 @@ Cypress.Commands.add('createModel', (groupId, modelId, modelDisplayName) => { Cypress.Commands.add( 'runPrimaryBpmnFile', (expectAutoRedirectToHumanTask = false, returnToProcessModelShow = true) => { - // cy.getBySel('start-process-instance').click(); - // click on button with text Start - //cy.get('button') - //cy.get('#process-model-tile-manage-procurement\\/procurement\\/requisition-order-management\\/new-demand-request-procurement > div > button') - cy.get('#process-model-tile-manage-procurement\\/procurement\\/requisition-order-management\\/request-goods-services > div > button') - //cy.get('#process-model-tile-manage-procurement\\/procurement\\/requisition-order-management\\/raise-new-demand-request > div > button') - .contains(/^Start$/) - .click(); + cy.getBySel('start-process-instance').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. + // 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: ', { timeout: 30000 }); } else { diff --git a/spiffworkflow-frontend/src/components/NavigationBar.tsx b/spiffworkflow-frontend/src/components/NavigationBar.tsx index 5ac960342..b00bee848 100644 --- a/spiffworkflow-frontend/src/components/NavigationBar.tsx +++ b/spiffworkflow-frontend/src/components/NavigationBar.tsx @@ -99,7 +99,11 @@ export default function NavigationBar() {
{UserService.getUserEmail()}