Merge remote-tracking branch 'origin/main' into bug/improve_reset_to_previous_task

This commit is contained in:
danfunk 2023-05-10 15:36:48 -04:00
commit dcbf351180
8 changed files with 136 additions and 117 deletions

View File

@ -1781,8 +1781,17 @@ class ProcessInstanceProcessor:
return None return None
def terminate(self) -> None: def terminate(self) -> None:
"""Terminate.""" start_time = time.time()
self.bpmn_process_instance.cancel() 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.save()
self.process_instance_model.status = "terminated" self.process_instance_model.status = "terminated"
db.session.add(self.process_instance_model) db.session.add(self.process_instance_model)

View File

@ -1607,6 +1607,10 @@ class TestProcessApi(BaseTest):
assert response.status_code == 200 assert response.status_code == 200
assert response.json is not None 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( response = client.post(
f"/v1.0/process-instance-terminate/{self.modify_process_identifier_for_path_param(process_model_identifier)}/{process_instance_id}", 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), 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() process_instance = ProcessInstanceModel.query.filter_by(id=process_instance_id).first()
assert process_instance assert process_instance
assert process_instance.status == "terminated" 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( def test_process_instance_delete(
self, self,

View File

@ -45,7 +45,7 @@ for attempt in $(seq 1 "$ATTEMPTS" ); do
start_time=$(date +%s) start_time=$(date +%s)
success="false" 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}" spec_pattern="cypress/pilot/**/*.cy.{js,jsx,ts,tsx}"
if ./node_modules/.bin/cypress "$command" -c specPattern="${spec_pattern}" --e2e --browser chrome "$@"; then if ./node_modules/.bin/cypress "$command" -c specPattern="${spec_pattern}" --e2e --browser chrome "$@"; then
success="true" success="true"
fi fi

View File

@ -3,16 +3,18 @@ import { DATE_FORMAT, PROCESS_STATUSES } from '../../src/config';
import { titleizeString } from '../../src/helpers'; import { titleizeString } from '../../src/helpers';
const filterByDate = (fromDate) => { 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.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.contains('End date to').click();
cy.getBySel('filter-button').click(); cy.getBySel('filter-button').click();
}; };
const updateDmnText = (oldText, newText, elementId = 'wonderful_process') => { const updateDmnText = (oldText, newText, elementId = 'wonderful_process') => {
// this will break if there are more elements added to the drd // 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.get('.dmn-icon-decision-table').click();
cy.contains(oldText).clear().type(`"${newText}"`); cy.contains(oldText).clear().type(`"${newText}"`);
@ -23,46 +25,44 @@ const updateDmnText = (oldText, newText, elementId = 'wonderful_process') => {
}; };
const updateBpmnPythonScript = (pythonScript, elementId = 'process_script') => { 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.contains(/^Script$/).click();
cy.get('textarea[name="pythonScript_bpmn:script"]') cy.get('textarea[name="pythonScript_bpmn:script"]').clear();
.clear() cy.get('textarea[name="pythonScript_bpmn:script"]').type(pythonScript);
.type(pythonScript);
// wait for a little bit for the xml to get set before saving // wait for a little bit for the xml to get set before saving
cy.wait(500); cy.wait(500);
cy.contains('Save').click(); cy.contains('Save').click();
}; };
const updateBpmnPythonScriptWithMonaco = ( // const updateBpmnPythonScriptWithMonaco = (
pythonScript, // pythonScript,
elementId = 'process_script' // elementId = 'process_script'
) => { // ) => {
cy.get(`g[data-element-id=${elementId}]`).click().should('exist'); // 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. // // 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(/^Script$/).click();
cy.contains('Launch Editor').click(); // cy.contains('Launch Editor').click();
// sometimes, Loading... appears for more than 4 seconds. not sure why. // // sometimes, Loading... appears for more than 4 seconds. not sure why.
cy.contains('Loading...').should('not.exist'); // cy.contains('Loading...').should('not.exist');
//
// the delay 30 is because, at some point, monaco started automatically // // 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 // // 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 // // 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 // // 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 // // than a human being could, so we artificially slow it down to make it more
// human. // // human.
cy.get('.monaco-editor textarea:first') // cy.get('.monaco-editor textarea:first').click();
.click() // cy.get('.monaco-editor textarea:first').focused(); // change subject to currently focused element
.focused() // change subject to currently focused element // cy.get('.monaco-editor textarea:first').clear();
.clear() // // long delay to ensure cypress isn't competing with monaco auto complete stuff
// long delay to ensure cypress isn't competing with monaco auto complete stuff // cy.get('.monaco-editor textarea:first').type(pythonScript, { delay: 120 });
.type(pythonScript, { delay: 120 }); //
// cy.contains('Close').click();
cy.contains('Close').click(); // // wait for a little bit for the xml to get set before saving
// wait for a little bit for the xml to get set before saving // cy.wait(500);
cy.wait(500); // cy.contains('Save').click();
cy.contains('Save').click(); // };
};
describe('process-instances', () => { describe('process-instances', () => {
beforeEach(() => { beforeEach(() => {
@ -79,7 +79,6 @@ describe('process-instances', () => {
it('can create a new instance and can modify', () => { it('can create a new instance and can modify', () => {
const originalDmnOutputForKevin = 'Very wonderful'; const originalDmnOutputForKevin = 'Very wonderful';
const newDmnOutputForKevin = 'The new wonderful'; const newDmnOutputForKevin = 'The new wonderful';
const dmnOutputForDan = 'pretty wonderful';
const acceptanceTestOneDisplayName = 'Acceptance Tests Model 1'; const acceptanceTestOneDisplayName = 'Acceptance Tests Model 1';
const originalPythonScript = 'person = "Kevin"'; const originalPythonScript = 'person = "Kevin"';

View File

@ -132,61 +132,63 @@ describe('process-models', () => {
cy.get('.tile-process-group-content-container').should('exist'); cy.get('.tile-process-group-content-container').should('exist');
}); });
it('can upload and run a bpmn file', () => { // FIXME: we currently do not know how to upload files since the new Add File
const uuid = () => Cypress._.random(0, 1e6); // component does not support the selectFile method
const id = uuid(); // it('can upload and run a bpmn file', () => {
const directParentGroupId = 'acceptance-tests-group-one'; // const uuid = () => Cypress._.random(0, 1e6);
const groupId = `misc/${directParentGroupId}`; // const id = uuid();
const modelDisplayName = `Test Model 2 ${id}`; // const directParentGroupId = 'acceptance-tests-group-one';
const modelId = `test-model-2-${id}`; // const groupId = `misc/${directParentGroupId}`;
cy.contains('Add a process group'); // const modelDisplayName = `Test Model 2 ${id}`;
cy.contains(miscDisplayName).click(); // const modelId = `test-model-2-${id}`;
cy.contains(groupDisplayName).click(); // cy.contains('Add a process group');
cy.createModel(groupId, modelId, modelDisplayName); // cy.contains(miscDisplayName).click();
// cy.contains(groupDisplayName).click();
cy.contains(`${groupDisplayName}`).click(); // cy.createModel(groupId, modelId, modelDisplayName);
cy.contains('Add a process model'); //
cy.contains(modelDisplayName).click(); // cy.contains(`${groupDisplayName}`).click();
cy.url().should( // cy.contains('Add a process model');
'include', // cy.contains(modelDisplayName).click();
`process-models/${modifyProcessIdentifierForPathParam( // cy.url().should(
groupId // 'include',
)}:${modelId}` // `process-models/${modifyProcessIdentifierForPathParam(
); // groupId
cy.contains(`Process Model: ${modelDisplayName}`); // )}:${modelId}`
// );
cy.getBySel('upload-file-button').click(); // cy.contains(`Process Model: ${modelDisplayName}`);
cy.contains('Add file').selectFile( //
'cypress/fixtures/test_bpmn_file_upload.bpmn' // cy.getBySel('upload-file-button').click();
); // cy.contains('Add file').selectFile(
cy.getBySel('modal-upload-file-dialog') // 'cypress/fixtures/test_bpmn_file_upload.bpmn'
.find('.cds--btn--primary') // );
.contains('Upload') // cy.getBySel('modal-upload-file-dialog')
.click(); // .find('.cds--btn--primary')
cy.runPrimaryBpmnFile(); // .contains('Upload')
// .click();
cy.getBySel('process-instance-show-link-id').click(); // cy.runPrimaryBpmnFile();
cy.getBySel('process-instance-delete').click(); //
cy.contains('Are you sure'); // cy.getBySel('process-instance-show-link-id').click();
cy.getBySel('process-instance-delete-modal-confirmation-dialog') // cy.getBySel('process-instance-delete').click();
.find('.cds--btn--danger') // cy.contains('Are you sure');
.click(); // cy.getBySel('process-instance-delete-modal-confirmation-dialog')
// .find('.cds--btn--danger')
// in breadcrumb // .click();
cy.contains(modelDisplayName).click(); //
// // in breadcrumb
cy.getBySel(deleteProcessModelButtonId).click(); // cy.contains(modelDisplayName).click();
cy.contains('Are you sure'); //
cy.getBySel('delete-process-model-button-modal-confirmation-dialog') // cy.getBySel(deleteProcessModelButtonId).click();
.find('.cds--btn--danger') // cy.contains('Are you sure');
.click(); // cy.getBySel('delete-process-model-button-modal-confirmation-dialog')
cy.url().should( // .find('.cds--btn--danger')
'include', // .click();
`process-groups/${modifyProcessIdentifierForPathParam(groupId)}` // cy.url().should(
); // 'include',
cy.contains(modelId).should('not.exist'); // `process-groups/${modifyProcessIdentifierForPathParam(groupId)}`
cy.contains(modelDisplayName).should('not.exist'); // );
}); // cy.contains(modelId).should('not.exist');
// cy.contains(modelDisplayName).should('not.exist');
// });
it('can allow searching for model', () => { it('can allow searching for model', () => {
cy.getBySel('process-model-selection').click(); cy.getBySel('process-model-selection').click();

View File

@ -81,7 +81,7 @@ describe('tasks', () => {
cy.navigateToHome(); cy.navigateToHome();
// look for somethig to make sure the homepage has loaded // 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 // FIXME: this will probably need a better way to link to the proper form that we want
cy.contains('Go').click(); cy.contains('Go').click();

View File

@ -44,13 +44,16 @@ Cypress.Commands.add('navigateToAdmin', () => {
Cypress.Commands.add('login', (username, password) => { Cypress.Commands.add('login', (username, password) => {
cy.visit('/admin'); cy.visit('/admin');
console.log('username', username); let usernameToUse = username;
if (!username) { let passwordToUse = password;
username = Cypress.env('SPIFFWORKFLOW_FRONTEND_USERNAME') || 'ciadmin1'; if (!usernameToUse) {
password = Cypress.env('SPIFFWORKFLOW_FRONTEND_PASSWORD') || 'ciadmin1'; usernameToUse =
Cypress.env('SPIFFWORKFLOW_FRONTEND_USERNAME') || 'ciadmin1';
passwordToUse =
Cypress.env('SPIFFWORKFLOW_FRONTEND_PASSWORD') || 'ciadmin1';
} }
cy.get('#username').type(username); cy.get('#username').type(usernameToUse);
cy.get('#password').type(password); cy.get('#password').type(passwordToUse);
if (Cypress.env('SPIFFWORKFLOW_FRONTEND_AUTH_WITH_KEYCLOAK') === true) { if (Cypress.env('SPIFFWORKFLOW_FRONTEND_AUTH_WITH_KEYCLOAK') === true) {
cy.get('#kc-login').click(); cy.get('#kc-login').click();
} else { } else {
@ -58,14 +61,13 @@ Cypress.Commands.add('login', (username, password) => {
} }
}); });
Cypress.Commands.add('logout', (selector, ...args) => { Cypress.Commands.add('logout', (_selector, ..._args) => {
cy.wait(2000); cy.get('#user-profile-toggletip').click();
//cy.getBySel('logout-button').click(); cy.getBySel('logout-button').click();
cy.get('#root > div > header > div.cds--header__global > span:nth-child(3) > button > svg').click();
if (Cypress.env('SPIFFWORKFLOW_FRONTEND_AUTH_WITH_KEYCLOAK') === true) { if (Cypress.env('SPIFFWORKFLOW_FRONTEND_AUTH_WITH_KEYCLOAK') === true) {
// otherwise we can click logout, quickly load the next page, and the javascript // otherwise we can click logout, quickly load the next page, and the javascript
// doesn't have time to actually sign you out // doesn't have time to actually sign you out
//cy.wait(4000); // cy.wait(4000);
cy.contains('Sign in to your account'); cy.contains('Sign in to your account');
} else { } else {
cy.get('#spiff-login-button').should('exist'); cy.get('#spiff-login-button').should('exist');
@ -104,16 +106,10 @@ Cypress.Commands.add('createModel', (groupId, modelId, modelDisplayName) => {
Cypress.Commands.add( Cypress.Commands.add(
'runPrimaryBpmnFile', 'runPrimaryBpmnFile',
(expectAutoRedirectToHumanTask = false, returnToProcessModelShow = true) => { (expectAutoRedirectToHumanTask = false, returnToProcessModelShow = true) => {
// cy.getBySel('start-process-instance').click(); 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();
if (expectAutoRedirectToHumanTask) { 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.url().should('include', `/tasks/`);
cy.contains('Task: ', { timeout: 30000 }); cy.contains('Task: ', { timeout: 30000 });
} else { } else {

View File

@ -99,7 +99,11 @@ export default function NavigationBar() {
</p> </p>
<p>{UserService.getUserEmail()}</p> <p>{UserService.getUserEmail()}</p>
<hr /> <hr />
<Button className="button-link" onClick={handleLogout}> <Button
data-qa="logout-button"
className="button-link"
onClick={handleLogout}
>
<Logout /> <Logout />
&nbsp;&nbsp;Sign out &nbsp;&nbsp;Sign out
</Button> </Button>