Squashed 'spiffworkflow-frontend/' changes from 326040b3c..55607af93
55607af93 fixed broken test w/ burnettk 4ea766eb4 mypy w/ burnettk cullerton fd2239b0e added git creds for pushing on publish w/ burnettk cullerton 0281bec01 added new notification component that allows links based on carbons w/ burnettk cullerton 49190128c display URL to open PR *** Need to figure out how to turn this into a link *** 72b15c52c Return message to use on successful publish 4997375c8 Merge branch 'main' into feature/git-integration 39deda4d4 Merge branch 'main' into feature/git-integration 027dae1c6 First pass at git integration db0c8dc29 break process instance log list page into two tabs, simple and detailed d9df1104c get the columsn for the instance list table anytime filter options are displayed if empty 3792dafdb make the frontend uris match the api calls better w/ burnettk 7095e4723 more api cleanup w/ burnettk c514ac656 cleaned up more api routes for permissions w/ burnettk c758216ed updated tasks endpoint to task-data for easier permission setting w/ burnettk 7504e1857 pyl w/ burnettk b7edc501a Merge remote-tracking branch 'origin/main' into new_report 112eed7f3 some updates to fix up saving perspectives w/ burnettk 6da6ebe2d Use the identifier, not the id when locating a process model or dmn table. 51515ea21 using an array for metadata extraction paths now instead of dictionaries w/ burnettk f0b8e7185 added some support to add process model metadata. need to fix frontend w/ burnettk 0777bda31 filtering by metadata works w/ burnettk d82a00018 favor report id over identifier but support both and ui updates to allow setting a condition value on a metadata field, changing the display name, and fixes for saving and updating a report de218ba8e updated column form var w/ burnettk de38dc436 added ability to update the display name for perspective columns w/ burnettk 555360eb6 some updates for process instance reports and metadata w/ burnettk f0f4dcd89 better display for failure causes on message list w/ burnettk c4faf5d55 added correlations to message list table w/ burnettk 65feaeecf Merge remote-tracking branch 'origin/main' into new_report fe9dddc03 Choose new report f20d6ee75 Save dates b55a24a1c Save first status c47c62a6d added script to save process instance metadata and fixed permissions issue w/ burnettk cullerton a0098ebd9 Save selected process model 55ecbe565 Use current columns 6de52904e WIP bacf11bdc Save as report component 472578b99 adding the username to the report tables bef4add43 allow disabling the permission check for the Create New Instance page to improve performance. ab929fcaa Merge branch 'main' of github.com:sartography/spiff-arena into main 603db83cb "Continue" rather than "Submit" when displaying manual tasks. 5db42f0e0 Processes you can start is now: Processes I can start c5c6c0fac lint 6f0c58da8 Auto Reload the Process Lists on the home pages' in-progress, and complete tabs 54e8a4717 update bpmn-js-spiffworkflow with better data-object handling a72daa441 Clean up css for the filter icon c755889ae update wording per harmeet: Tasks for my open processes is now My open instances bda4a6ee3 heading for instances on model show page, move instances below files, add margins 21a3eea47 display name instead of id, margin under table sections, Download xml to Download 1d83e3ac1 do not mislead user about being able to edit and clean up time in words 07380eec7 auto refresh tasks waiting for my groups on homepage 710d2340a time ago in words for in progress tab per harmeet feedback 88c4be1bd put id before process like completed tab and add title text to explain what is happening fb4136892 use process model display name rather than id for completed instances tab 8031fda3a left align files section with Start button per harmeet feedback c339d7dec add fin1, lead1, and Tasks actioned by me to Tasks completed by me 951c21f39 improve wording ed38b57e8 consistency is key e09373027 remove View label next to process instance id 38d20ceab ui feedback 3c0284633 some ui changes w/ burnettk e3711f4fd updated copmleted table text w/ burnettk 0688f5ec1 updated instances table descriptions w/ burnettk e9e9b8e2e added descriptions to task tables w/ burnettk 9b1d61866 updated breadcrumb to use display name w/ burnettk a9895f472 Hide perspectives link in nav bar (#59) 77390519b rename process_groups_list to process_group_list and fix lint 31bb0facd some updates to ui homepage to align more with notion doc 12e719146 fixed cypress tests 476c19f72 fix typo b266273e4 some more perm updates for core user w/ burnettk 05161fbcb Start of system report filters (#57) f0e0732ab fixed editing a process model w/ burnettk b02b5a2e4 filter process models based on user permissions on the backend if specified w/ burnettk 29093932f use tiles for process models w/ burnettk cullerton ab24c28d9 updated recently viewed table to be recently run and added run button w/ burnettk cullerton 9f894a8a9 added link to process model tile w/ burnettk cullerton b7a0743a5 moved delete and edit model and group buttons to icons on show pages w/ burnettk cullerton 21f7fc917 created new users for keycloak and fixed some permissions for core user w/ burnettk cullerton bd5a55c04 renamed modifyProcessModelPath to modifyProcessIdentifierForPathParam w/ burnettk 55a59b5ed modify process group id before submitting w/ burnettk 58bf7e38d Allow switching between user defined reports (#56) ec29be773 added recursive option to process model list to recurse or not and fix some ui components 56ae0afe3 fixed task frontend test 5f8a8dd64 the misc group is now 99-Misc 467e9643c allow longer username 83f6185f1 fix tests and add frontend tests f3b5cb7ca upgrade apscheduler and fix mispelling cfe7172de added a script to add a user to a group w/ burnettk 976ca7320 task cypress tests are passing w/ burnettk cullerton aa1a62505 process model cypress tests are passing w/ burnettk cullerton e36012401 make sure to pass the correct form of process group id when creating a process model w/ burnettk cullerton 97a840d04 process instance cypress tests pass now w/ burnettk cullerton 86fdb302a allow getting all process models, process instances should not save when they are initialized, and fixed some cypress tests w/ burnettk 2b15e66d2 iterating on cypress 1aa72f420 fix cypress tests git-subtree-dir: spiffworkflow-frontend git-subtree-split: 55607af9318775fb3524cc5bb4f6a3c6188efe38
This commit is contained in:
parent
b198341b00
commit
e4e0056581
|
@ -29,4 +29,4 @@ cypress/screenshots
|
|||
/test*.json
|
||||
|
||||
# Editors
|
||||
.idea
|
||||
.idea
|
||||
|
|
|
@ -19,25 +19,22 @@ describe('process-groups', () => {
|
|||
cy.url().should('include', `process-groups/${groupId}`);
|
||||
cy.contains(`Process Group: ${groupDisplayName}`);
|
||||
|
||||
cy.contains('Edit process group').click();
|
||||
cy.getBySel('edit-process-group-button').click();
|
||||
cy.get('input[name=display_name]').clear().type(newGroupDisplayName);
|
||||
cy.contains('Submit').click();
|
||||
cy.contains(`Process Group: ${newGroupDisplayName}`);
|
||||
|
||||
cy.contains('Edit process group').click();
|
||||
cy.get('input[name=display_name]').should(
|
||||
'have.value',
|
||||
newGroupDisplayName
|
||||
);
|
||||
|
||||
cy.contains('Delete').click();
|
||||
cy.getBySel('delete-process-group-button').click();
|
||||
cy.contains('Are you sure');
|
||||
cy.getBySel('modal-confirmation-dialog').find('.cds--btn--danger').click();
|
||||
cy.getBySel('delete-process-group-button-modal-confirmation-dialog')
|
||||
.find('.cds--btn--danger')
|
||||
.click();
|
||||
cy.url().should('include', `process-groups`);
|
||||
cy.contains(groupId).should('not.exist');
|
||||
});
|
||||
|
||||
it('can paginate items', () => {
|
||||
cy.basicPaginationTest();
|
||||
});
|
||||
// process groups no longer has pagination post-tiles
|
||||
// it('can paginate items', () => {
|
||||
// cy.basicPaginationTest();
|
||||
// });
|
||||
});
|
||||
|
|
|
@ -3,9 +3,9 @@ import { DATE_FORMAT, PROCESS_STATUSES } from '../../src/config';
|
|||
|
||||
const filterByDate = (fromDate) => {
|
||||
cy.get('#date-picker-start-from').clear().type(format(fromDate, DATE_FORMAT));
|
||||
cy.contains('Start date from').click();
|
||||
cy.contains('Start date to').click();
|
||||
cy.get('#date-picker-end-from').clear().type(format(fromDate, DATE_FORMAT));
|
||||
cy.contains('End date from').click();
|
||||
cy.contains('End date to').click();
|
||||
cy.getBySel('filter-button').click();
|
||||
};
|
||||
|
||||
|
@ -53,9 +53,9 @@ const updateBpmnPythonScriptWithMonaco = (
|
|||
cy.get('.monaco-editor textarea:first')
|
||||
.click()
|
||||
.focused() // change subject to currently focused element
|
||||
// .type('{ctrl}a') // had been doing it this way, but it turns out to be flaky relative to clear()
|
||||
.clear()
|
||||
.type(pythonScript, { delay: 30 });
|
||||
// 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
|
||||
|
@ -119,28 +119,28 @@ describe('process-instances', () => {
|
|||
cy.runPrimaryBpmnFile();
|
||||
});
|
||||
|
||||
it('can create a new instance and can modify with monaco text editor', () => {
|
||||
// leave off the ending double quote since manco adds it
|
||||
const originalPythonScript = 'person = "Kevin';
|
||||
const newPythonScript = 'person = "Mike';
|
||||
|
||||
const bpmnFile = 'process_model_one.bpmn';
|
||||
|
||||
// Change bpmn
|
||||
cy.getBySel('files-accordion').click();
|
||||
cy.getBySel(`edit-file-${bpmnFile.replace('.', '-')}`).click();
|
||||
cy.contains(`Process Model File: ${bpmnFile}`);
|
||||
updateBpmnPythonScriptWithMonaco(newPythonScript);
|
||||
cy.contains('acceptance-tests-model-1').click();
|
||||
cy.runPrimaryBpmnFile();
|
||||
|
||||
cy.getBySel('files-accordion').click();
|
||||
cy.getBySel(`edit-file-${bpmnFile.replace('.', '-')}`).click();
|
||||
cy.contains(`Process Model File: ${bpmnFile}`);
|
||||
updateBpmnPythonScriptWithMonaco(originalPythonScript);
|
||||
cy.contains('acceptance-tests-model-1').click();
|
||||
cy.runPrimaryBpmnFile();
|
||||
});
|
||||
// it('can create a new instance and can modify with monaco text editor', () => {
|
||||
// // leave off the ending double quote since manco adds it
|
||||
// const originalPythonScript = 'person = "Kevin';
|
||||
// const newPythonScript = 'person = "Mike';
|
||||
//
|
||||
// const bpmnFile = 'process_model_one.bpmn';
|
||||
//
|
||||
// // Change bpmn
|
||||
// cy.getBySel('files-accordion').click();
|
||||
// cy.getBySel(`edit-file-${bpmnFile.replace('.', '-')}`).click();
|
||||
// cy.contains(`Process Model File: ${bpmnFile}`);
|
||||
// updateBpmnPythonScriptWithMonaco(newPythonScript);
|
||||
// cy.contains('acceptance-tests-model-1').click();
|
||||
// cy.runPrimaryBpmnFile();
|
||||
//
|
||||
// cy.getBySel('files-accordion').click();
|
||||
// cy.getBySel(`edit-file-${bpmnFile.replace('.', '-')}`).click();
|
||||
// cy.contains(`Process Model File: ${bpmnFile}`);
|
||||
// updateBpmnPythonScriptWithMonaco(originalPythonScript);
|
||||
// cy.contains('acceptance-tests-model-1').click();
|
||||
// cy.runPrimaryBpmnFile();
|
||||
// });
|
||||
|
||||
it('can paginate items', () => {
|
||||
// make sure we have some process instances
|
||||
|
@ -174,13 +174,12 @@ describe('process-instances', () => {
|
|||
if (!['all', 'waiting'].includes(processStatus)) {
|
||||
cy.get(statusSelect).click();
|
||||
cy.get(statusSelect).contains(processStatus).click();
|
||||
// close the dropdown again
|
||||
cy.get(statusSelect).click();
|
||||
cy.getBySel('filter-button').click();
|
||||
// FIXME: wait a little bit for the useEffects to be able to fully set processInstanceFilters
|
||||
cy.wait(1000);
|
||||
cy.url().should('include', `status=${processStatus}`);
|
||||
cy.assertAtLeastOneItemInPaginatedResults();
|
||||
cy.getBySel(`process-instance-status-${processStatus}`).contains(
|
||||
processStatus
|
||||
);
|
||||
cy.getBySel(`process-instance-status-${processStatus}`);
|
||||
// there should really only be one, but in CI there are sometimes more
|
||||
cy.get('div[aria-label="Clear all selected items"]:first').click();
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { modifyProcessIdentifierForPathParam } from '../../src/helpers';
|
||||
|
||||
describe('process-models', () => {
|
||||
beforeEach(() => {
|
||||
cy.login();
|
||||
|
@ -9,37 +11,48 @@ describe('process-models', () => {
|
|||
it('can perform crud operations', () => {
|
||||
const uuid = () => Cypress._.random(0, 1e6);
|
||||
const id = uuid();
|
||||
const groupId = 'acceptance-tests-group-one';
|
||||
const groupId = 'misc/acceptance-tests-group-one';
|
||||
const groupDisplayName = 'Acceptance Tests Group One';
|
||||
const modelDisplayName = `Test Model 2 ${id}`;
|
||||
const newModelDisplayName = `${modelDisplayName} edited`;
|
||||
const modelId = `test-model-2-${id}`;
|
||||
const newModelDisplayName = `${modelDisplayName} edited`;
|
||||
cy.contains('99-Shared Resources').click();
|
||||
cy.wait(500);
|
||||
cy.contains(groupDisplayName).click();
|
||||
cy.createModel(groupId, modelId, modelDisplayName);
|
||||
cy.url().should('include', `process-models/${groupId}:${modelId}`);
|
||||
cy.url().should(
|
||||
'include',
|
||||
`process-models/${modifyProcessIdentifierForPathParam(
|
||||
groupId
|
||||
)}:${modelId}`
|
||||
);
|
||||
cy.contains(`Process Model: ${modelDisplayName}`);
|
||||
|
||||
cy.contains('Edit process model').click();
|
||||
cy.getBySel('edit-process-model-button').click();
|
||||
cy.get('input[name=display_name]').clear().type(newModelDisplayName);
|
||||
cy.contains('Submit').click();
|
||||
cy.contains(`Process Model: ${groupId}/${modelId}`);
|
||||
cy.contains('Submit').click();
|
||||
cy.get('input[name=display_name]').should(
|
||||
'have.value',
|
||||
newModelDisplayName
|
||||
);
|
||||
cy.contains(`Process Model: ${newModelDisplayName}`);
|
||||
|
||||
cy.contains('Delete').click();
|
||||
// go back to process model show by clicking on the breadcrumb
|
||||
cy.contains(modelId).click();
|
||||
|
||||
cy.getBySel('delete-process-model-button').click();
|
||||
cy.contains('Are you sure');
|
||||
cy.getBySel('modal-confirmation-dialog').find('.cds--btn--danger').click();
|
||||
cy.url().should('include', `process-groups/${groupId}`);
|
||||
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');
|
||||
});
|
||||
|
||||
it('can create new bpmn, dmn, and json files', () => {
|
||||
const uuid = () => Cypress._.random(0, 1e6);
|
||||
const id = uuid();
|
||||
const groupId = 'acceptance-tests-group-one';
|
||||
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}`;
|
||||
|
@ -48,13 +61,19 @@ describe('process-models', () => {
|
|||
const dmnFileName = `dmn_test_file_${id}`;
|
||||
const jsonFileName = `json_test_file_${id}`;
|
||||
|
||||
cy.contains('99-Shared Resources').click();
|
||||
cy.wait(500);
|
||||
cy.contains(groupDisplayName).click();
|
||||
cy.createModel(groupId, modelId, modelDisplayName);
|
||||
cy.contains(groupId).click();
|
||||
cy.contains(modelId).click();
|
||||
cy.url().should('include', `process-models/${groupId}:${modelId}`);
|
||||
cy.contains(directParentGroupId).click();
|
||||
cy.contains(modelDisplayName).click();
|
||||
cy.url().should(
|
||||
'include',
|
||||
`process-models/${modifyProcessIdentifierForPathParam(
|
||||
groupId
|
||||
)}:${modelId}`
|
||||
);
|
||||
cy.contains(`Process Model: ${modelDisplayName}`);
|
||||
cy.getBySel('files-accordion').click();
|
||||
cy.contains(`${bpmnFileName}.bpmn`).should('not.exist');
|
||||
cy.contains(`${dmnFileName}.dmn`).should('not.exist');
|
||||
cy.contains(`${jsonFileName}.json`).should('not.exist');
|
||||
|
@ -73,7 +92,7 @@ describe('process-models', () => {
|
|||
cy.contains(`Process Model File: ${bpmnFileName}`);
|
||||
cy.contains(modelId).click();
|
||||
cy.contains(`Process Model: ${modelDisplayName}`);
|
||||
cy.getBySel('files-accordion').click();
|
||||
// cy.getBySel('files-accordion').click();
|
||||
cy.contains(`${bpmnFileName}.bpmn`).should('exist');
|
||||
|
||||
// add new dmn file
|
||||
|
@ -81,13 +100,17 @@ describe('process-models', () => {
|
|||
cy.contains(/^Process Model File$/);
|
||||
cy.get('g[data-element-id=decision_1]').click().should('exist');
|
||||
cy.contains('General').click();
|
||||
cy.get('#bio-properties-panel-id')
|
||||
.clear()
|
||||
.type('decision_acceptance_test_1');
|
||||
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(`Process Model: ${modelDisplayName}`);
|
||||
cy.getBySel('files-accordion').click();
|
||||
// cy.getBySel('files-accordion').click();
|
||||
cy.contains(`${dmnFileName}.dmn`).should('exist');
|
||||
|
||||
// add new json file
|
||||
|
@ -103,35 +126,47 @@ describe('process-models', () => {
|
|||
cy.wait(500);
|
||||
cy.contains(modelId).click();
|
||||
cy.contains(`Process Model: ${modelDisplayName}`);
|
||||
cy.getBySel('files-accordion').click();
|
||||
// cy.getBySel('files-accordion').click();
|
||||
cy.contains(`${jsonFileName}.json`).should('exist');
|
||||
|
||||
cy.contains('Edit process model').click();
|
||||
cy.contains('Delete').click();
|
||||
cy.getBySel('delete-process-model-button').click();
|
||||
cy.contains('Are you sure');
|
||||
cy.getBySel('modal-confirmation-dialog').find('.cds--btn--danger').click();
|
||||
cy.url().should('include', `process-groups/${groupId}`);
|
||||
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 upload and run a bpmn file', () => {
|
||||
const uuid = () => Cypress._.random(0, 1e6);
|
||||
const id = uuid();
|
||||
const groupId = 'acceptance-tests-group-one';
|
||||
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.wait(500);
|
||||
cy.contains(groupDisplayName).click();
|
||||
cy.createModel(groupId, modelId, modelDisplayName);
|
||||
|
||||
cy.contains(`${groupId}`).click();
|
||||
cy.contains(`${directParentGroupId}`).click();
|
||||
cy.contains('Add a process model');
|
||||
cy.contains(modelId).click();
|
||||
cy.url().should('include', `process-models/${groupId}:${modelId}`);
|
||||
cy.contains(modelDisplayName).click();
|
||||
cy.url().should(
|
||||
'include',
|
||||
`process-models/${modifyProcessIdentifierForPathParam(
|
||||
groupId
|
||||
)}:${modelId}`
|
||||
);
|
||||
cy.contains(`Process Model: ${modelDisplayName}`);
|
||||
|
||||
cy.getBySel('files-accordion').click();
|
||||
cy.getBySel('upload-file-button').click();
|
||||
cy.contains('Add file').selectFile(
|
||||
'cypress/fixtures/test_bpmn_file_upload.bpmn'
|
||||
|
@ -142,31 +177,41 @@ describe('process-models', () => {
|
|||
.click();
|
||||
cy.runPrimaryBpmnFile();
|
||||
|
||||
cy.getBySel('process-instance-list-link').click();
|
||||
// cy.getBySel('process-instance-list-link').click();
|
||||
cy.getBySel('process-instance-show-link').click();
|
||||
cy.getBySel('process-instance-delete').click();
|
||||
cy.contains('Are you sure');
|
||||
cy.getBySel('modal-confirmation-dialog').find('.cds--btn--danger').click();
|
||||
cy.getBySel('process-instance-delete-modal-confirmation-dialog')
|
||||
.find('.cds--btn--danger')
|
||||
.click();
|
||||
|
||||
// in breadcrumb
|
||||
cy.contains(modelId).click();
|
||||
|
||||
cy.contains('Edit process model').click();
|
||||
cy.contains('Delete').click();
|
||||
cy.getBySel('delete-process-model-button').click();
|
||||
cy.contains('Are you sure');
|
||||
cy.getBySel('modal-confirmation-dialog').find('.cds--btn--danger').click();
|
||||
cy.url().should('include', `process-groups/${groupId}`);
|
||||
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 paginate items', () => {
|
||||
cy.contains('Acceptance Tests Group One').click();
|
||||
cy.basicPaginationTest();
|
||||
});
|
||||
// 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();
|
||||
cy.contains('List').click();
|
||||
cy.contains('Acceptance Tests Model 3');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,18 +1,27 @@
|
|||
const submitInputIntoFormField = (taskName, fieldKey, fieldValue) => {
|
||||
cy.contains(`Task: ${taskName}`);
|
||||
cy.contains(`Task: ${taskName}`, { timeout: 10000 });
|
||||
cy.get(fieldKey).clear().type(fieldValue);
|
||||
cy.contains('Submit').click();
|
||||
};
|
||||
|
||||
const checkFormFieldIsReadOnly = (formName, fieldKey) => {
|
||||
cy.contains(`Task: ${formName}`);
|
||||
cy.get(fieldKey).invoke('attr', 'readonly').should('exist');
|
||||
cy.get(fieldKey).invoke('attr', 'disabled').should('exist');
|
||||
};
|
||||
|
||||
const checkTaskHasClass = (taskName, className) => {
|
||||
cy.get(`g[data-element-id=${taskName}]`).should('have.class', className);
|
||||
};
|
||||
|
||||
const kickOffModelWithForm = (modelId, formName) => {
|
||||
cy.navigateToProcessModel(
|
||||
'Acceptance Tests Group One',
|
||||
'Acceptance Tests Model 2',
|
||||
'acceptance-tests-model-2'
|
||||
);
|
||||
cy.runPrimaryBpmnFile(true);
|
||||
};
|
||||
|
||||
describe('tasks', () => {
|
||||
beforeEach(() => {
|
||||
cy.login();
|
||||
|
@ -21,7 +30,6 @@ describe('tasks', () => {
|
|||
cy.logout();
|
||||
});
|
||||
|
||||
// TODO: need to fix the next_task thing to make this pass
|
||||
it('can complete and navigate a form', () => {
|
||||
const groupDisplayName = 'Acceptance Tests Group One';
|
||||
const modelId = `acceptance-tests-model-2`;
|
||||
|
@ -30,11 +38,7 @@ describe('tasks', () => {
|
|||
const activeTaskClassName = 'active-task-highlight';
|
||||
|
||||
cy.navigateToProcessModel(groupDisplayName, modelDisplayName, modelId);
|
||||
|
||||
// avoid reloading so we can click on the task link that appears on running the process instance
|
||||
cy.runPrimaryBpmnFile(false);
|
||||
|
||||
cy.contains('my task').click();
|
||||
cy.runPrimaryBpmnFile(true);
|
||||
|
||||
submitInputIntoFormField(
|
||||
'get_user_generated_number_one',
|
||||
|
@ -59,7 +63,6 @@ describe('tasks', () => {
|
|||
'#root_user_generated_number_1'
|
||||
);
|
||||
|
||||
cy.getBySel('form-nav-form3').should('have.text', 'form3 - Current');
|
||||
cy.getBySel('form-nav-form3').click();
|
||||
submitInputIntoFormField(
|
||||
'get_user_generated_number_three',
|
||||
|
@ -111,18 +114,12 @@ describe('tasks', () => {
|
|||
});
|
||||
|
||||
it('can paginate items', () => {
|
||||
cy.navigateToProcessModel(
|
||||
'Acceptance Tests Group One',
|
||||
'Acceptance Tests Model 2',
|
||||
'acceptance-tests-model-2'
|
||||
);
|
||||
|
||||
// make sure we have some tasks
|
||||
cy.runPrimaryBpmnFile();
|
||||
cy.runPrimaryBpmnFile();
|
||||
cy.runPrimaryBpmnFile();
|
||||
cy.runPrimaryBpmnFile();
|
||||
cy.runPrimaryBpmnFile();
|
||||
kickOffModelWithForm();
|
||||
kickOffModelWithForm();
|
||||
kickOffModelWithForm();
|
||||
kickOffModelWithForm();
|
||||
kickOffModelWithForm();
|
||||
|
||||
cy.navigateToHome();
|
||||
cy.basicPaginationTest();
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { string } from 'prop-types';
|
||||
import { modifyProcessIdentifierForPathParam } from '../../src/helpers';
|
||||
|
||||
// ***********************************************
|
||||
// This example commands.js shows you how to
|
||||
|
@ -31,9 +32,8 @@ Cypress.Commands.add('getBySel', (selector, ...args) => {
|
|||
});
|
||||
|
||||
Cypress.Commands.add('navigateToHome', () => {
|
||||
cy.get('button[aria-label="Open menu"]').click();
|
||||
cy.getBySel('header-menu-expand-button').click();
|
||||
cy.getBySel('side-nav-items').contains('Home').click();
|
||||
// cy.getBySel('nav-home').click();
|
||||
});
|
||||
|
||||
Cypress.Commands.add('navigateToAdmin', () => {
|
||||
|
@ -76,27 +76,39 @@ Cypress.Commands.add('createModel', (groupId, modelId, modelDisplayName) => {
|
|||
cy.get('input[name=id]').should('have.value', modelId);
|
||||
cy.contains('Submit').click();
|
||||
|
||||
cy.url().should('include', `process-models/${groupId}:${modelId}`);
|
||||
cy.url().should(
|
||||
'include',
|
||||
`process-models/${modifyProcessIdentifierForPathParam(groupId)}:${modelId}`
|
||||
);
|
||||
cy.contains(`Process Model: ${modelDisplayName}`);
|
||||
});
|
||||
|
||||
Cypress.Commands.add('runPrimaryBpmnFile', (reload = true) => {
|
||||
cy.contains('Run').click();
|
||||
cy.contains(/Process Instance.*kicked off/);
|
||||
if (reload) {
|
||||
cy.reload(true);
|
||||
cy.contains(/Process Instance.*kicked off/).should('not.exist');
|
||||
Cypress.Commands.add(
|
||||
'runPrimaryBpmnFile',
|
||||
(expectAutoRedirectToHumanTask = false) => {
|
||||
cy.contains('Run').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.reload(true);
|
||||
cy.contains(/Process Instance.*kicked off/).should('not.exist');
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
Cypress.Commands.add(
|
||||
'navigateToProcessModel',
|
||||
(groupDisplayName, modelDisplayName, modelIdentifier) => {
|
||||
cy.navigateToAdmin();
|
||||
cy.contains('99-Shared Resources').click();
|
||||
cy.contains(`Process Group: 99-Shared Resources`, { timeout: 10000 });
|
||||
cy.contains(groupDisplayName).click();
|
||||
cy.contains(`Process Group: ${groupDisplayName}`);
|
||||
// https://stackoverflow.com/q/51254946/6090676
|
||||
cy.getBySel('process-model-show-link').contains(modelIdentifier).click();
|
||||
cy.getBySel('process-model-show-link').contains(modelDisplayName).click();
|
||||
cy.contains(`Process Model: ${modelDisplayName}`);
|
||||
}
|
||||
);
|
||||
|
@ -120,13 +132,3 @@ Cypress.Commands.add('assertAtLeastOneItemInPaginatedResults', () => {
|
|||
Cypress.Commands.add('assertNoItemInPaginatedResults', () => {
|
||||
cy.contains(/\b0–0 of 0 items/);
|
||||
});
|
||||
|
||||
Cypress.Commands.add('modifyProcessModelPath', (path) => {
|
||||
path.replace('/', ':');
|
||||
return path;
|
||||
});
|
||||
|
||||
Cypress.Commands.add('modifyProcessModelPath', (path) => {
|
||||
path.replace('/', ':');
|
||||
return path;
|
||||
});
|
||||
|
|
|
@ -7980,7 +7980,7 @@
|
|||
},
|
||||
"node_modules/bpmn-js-spiffworkflow": {
|
||||
"version": "0.0.8",
|
||||
"resolved": "git+ssh://git@github.com/sartography/bpmn-js-spiffworkflow.git#e92f48da7cb4416310af71bb1699caaca87324cd",
|
||||
"resolved": "git+ssh://git@github.com/sartography/bpmn-js-spiffworkflow.git#aca23dc56e5d37aa1ed0a3cf11acb55f76a36da7",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.4",
|
||||
|
@ -37138,7 +37138,7 @@
|
|||
}
|
||||
},
|
||||
"bpmn-js-spiffworkflow": {
|
||||
"version": "git+ssh://git@github.com/sartography/bpmn-js-spiffworkflow.git#e92f48da7cb4416310af71bb1699caaca87324cd",
|
||||
"version": "git+ssh://git@github.com/sartography/bpmn-js-spiffworkflow.git#aca23dc56e5d37aa1ed0a3cf11acb55f76a36da7",
|
||||
"from": "bpmn-js-spiffworkflow@sartography/bpmn-js-spiffworkflow#main",
|
||||
"requires": {
|
||||
"inherits": "^2.0.4",
|
||||
|
|
|
@ -41,4 +41,3 @@
|
|||
-->
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@ export default function ButtonWithConfirmation({
|
|||
<Modal
|
||||
open={showConfirmationPrompt}
|
||||
danger
|
||||
data-qa="modal-confirmation-dialog"
|
||||
data-qa={`${dataQa}-modal-confirmation-dialog`}
|
||||
modalHeading={description}
|
||||
modalLabel={title}
|
||||
primaryButtonText={confirmButtonLabel}
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
import { Link } from 'react-router-dom';
|
||||
import { modifyProcessIdentifierForPathParam } from '../helpers';
|
||||
import { MessageInstance, ProcessInstance } from '../interfaces';
|
||||
|
||||
export function FormatProcessModelDisplayName(
|
||||
instanceObject: ProcessInstance | MessageInstance
|
||||
) {
|
||||
const {
|
||||
process_model_identifier: processModelIdentifier,
|
||||
process_model_display_name: processModelDisplayName,
|
||||
} = instanceObject;
|
||||
return (
|
||||
<Link
|
||||
to={`/admin/process-models/${modifyProcessIdentifierForPathParam(
|
||||
processModelIdentifier
|
||||
)}`}
|
||||
title={processModelIdentifier}
|
||||
>
|
||||
{processModelDisplayName}
|
||||
</Link>
|
||||
);
|
||||
}
|
|
@ -8,6 +8,8 @@ export default function MyCompletedInstances() {
|
|||
filtersEnabled={false}
|
||||
paginationQueryParamPrefix={paginationQueryParamPrefix}
|
||||
perPageOptions={[2, 5, 25]}
|
||||
reportIdentifier="system_report_instances_initiated_by_me"
|
||||
showReports={false}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -74,7 +74,9 @@ export default function NavigationBar() {
|
|||
if (UserService.isLoggedIn()) {
|
||||
return (
|
||||
<>
|
||||
<HeaderGlobalAction>{UserService.getUsername()}</HeaderGlobalAction>
|
||||
<HeaderGlobalAction className="username-header-text">
|
||||
{UserService.getUsername()}
|
||||
</HeaderGlobalAction>
|
||||
<HeaderGlobalAction
|
||||
aria-label="Logout"
|
||||
onClick={handleLogout}
|
||||
|
@ -161,6 +163,7 @@ export default function NavigationBar() {
|
|||
</Can>
|
||||
{configurationElement()}
|
||||
<HeaderMenuItem
|
||||
hidden
|
||||
href="/admin/process-instances/reports"
|
||||
isCurrentPage={isActivePage('/admin/process-instances/reports')}
|
||||
>
|
||||
|
@ -177,6 +180,7 @@ export default function NavigationBar() {
|
|||
<Header aria-label="IBM Platform Name" className="cds--g100">
|
||||
<SkipToContent />
|
||||
<HeaderMenuButton
|
||||
data-qa="header-menu-expand-button"
|
||||
aria-label="Open menu"
|
||||
onClick={onClickSideNavExpand}
|
||||
isActive={isSideNavExpanded}
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
import React from 'react';
|
||||
// @ts-ignore
|
||||
import { Close, CheckmarkFilled } from '@carbon/icons-react';
|
||||
// @ts-ignore
|
||||
import { Button } from '@carbon/react';
|
||||
|
||||
type OwnProps = {
|
||||
title: string;
|
||||
children: React.ReactNode;
|
||||
onClose: (..._args: any[]) => any;
|
||||
type?: string;
|
||||
};
|
||||
|
||||
export function Notification({
|
||||
title,
|
||||
children,
|
||||
onClose,
|
||||
type = 'success',
|
||||
}: OwnProps) {
|
||||
let iconClassName = 'green-icon';
|
||||
if (type === 'error') {
|
||||
iconClassName = 'red-icon';
|
||||
}
|
||||
return (
|
||||
<div
|
||||
role="status"
|
||||
className={`with-bottom-margin cds--inline-notification cds--inline-notification--low-contrast cds--inline-notification--${type}`}
|
||||
>
|
||||
<div className="cds--inline-notification__details">
|
||||
<div className="cds--inline-notification__text-wrapper">
|
||||
<CheckmarkFilled className={`${iconClassName} notification-icon`} />
|
||||
<div className="cds--inline-notification__title">{title}</div>
|
||||
<div className="cds--inline-notification__subtitle">{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
data-qa="close-publish-notification"
|
||||
renderIcon={Close}
|
||||
iconDescription="Close Notification"
|
||||
className="cds--inline-notification__close-button"
|
||||
hasIconOnly
|
||||
size="sm"
|
||||
kind=""
|
||||
onClick={onClose}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -14,6 +14,7 @@ type OwnProps = {
|
|||
pagination: PaginationObject | null;
|
||||
tableToDisplay: any;
|
||||
paginationQueryParamPrefix?: string;
|
||||
paginationClassName?: string;
|
||||
};
|
||||
|
||||
export default function PaginationForTable({
|
||||
|
@ -23,6 +24,7 @@ export default function PaginationForTable({
|
|||
pagination,
|
||||
tableToDisplay,
|
||||
paginationQueryParamPrefix,
|
||||
paginationClassName,
|
||||
}: OwnProps) {
|
||||
const PER_PAGE_OPTIONS = [2, 10, 50, 100];
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
@ -44,6 +46,7 @@ export default function PaginationForTable({
|
|||
<>
|
||||
{tableToDisplay}
|
||||
<Pagination
|
||||
className={paginationClassName}
|
||||
data-qa="pagination-options"
|
||||
backwardText="Previous page"
|
||||
forwardText="Next page"
|
||||
|
|
|
@ -3,13 +3,13 @@ import { BrowserRouter } from 'react-router-dom';
|
|||
import ProcessBreadcrumb from './ProcessBreadcrumb';
|
||||
|
||||
test('renders home link', () => {
|
||||
render(
|
||||
<BrowserRouter>
|
||||
<ProcessBreadcrumb />
|
||||
</BrowserRouter>
|
||||
);
|
||||
const homeElement = screen.getByText(/Process Groups/);
|
||||
expect(homeElement).toBeInTheDocument();
|
||||
// render(
|
||||
// <BrowserRouter>
|
||||
// <ProcessBreadcrumb />
|
||||
// </BrowserRouter>
|
||||
// );
|
||||
// const homeElement = screen.getByText(/Process Groups/);
|
||||
// expect(homeElement).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('renders hotCrumbs', () => {
|
||||
|
|
|
@ -1,123 +1,118 @@
|
|||
// @ts-ignore
|
||||
import { Breadcrumb, BreadcrumbItem } from '@carbon/react';
|
||||
import { splitProcessModelId } from '../helpers';
|
||||
import { HotCrumbItem } from '../interfaces';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { modifyProcessIdentifierForPathParam } from '../helpers';
|
||||
import {
|
||||
HotCrumbItem,
|
||||
ProcessGroup,
|
||||
ProcessGroupLite,
|
||||
ProcessModel,
|
||||
} from '../interfaces';
|
||||
import HttpService from '../services/HttpService';
|
||||
|
||||
type OwnProps = {
|
||||
processModelId?: string;
|
||||
processGroupId?: string;
|
||||
linkProcessModel?: boolean;
|
||||
hotCrumbs?: HotCrumbItem[];
|
||||
};
|
||||
|
||||
const explodeCrumb = (crumb: HotCrumbItem) => {
|
||||
const url: string = crumb[1] || '';
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const [endingUrlType, processModelId, link] = url.split(':');
|
||||
const processModelIdSegments = splitProcessModelId(processModelId);
|
||||
const paths: string[] = [];
|
||||
const lastPathItem = processModelIdSegments.pop();
|
||||
const breadcrumbItems = processModelIdSegments.map(
|
||||
(processModelIdSegment: string) => {
|
||||
paths.push(processModelIdSegment);
|
||||
const fullUrl = `/admin/process-groups/${paths.join(':')}`;
|
||||
return (
|
||||
<BreadcrumbItem key={processModelIdSegment} href={fullUrl}>
|
||||
{processModelIdSegment}
|
||||
</BreadcrumbItem>
|
||||
);
|
||||
}
|
||||
);
|
||||
if (link === 'link') {
|
||||
if (lastPathItem !== undefined) {
|
||||
paths.push(lastPathItem);
|
||||
}
|
||||
// process_model to process-models
|
||||
const lastUrl = `/admin/${endingUrlType
|
||||
.replace('_', '-')
|
||||
.replace(/s*$/, 's')}/${paths.join(':')}`;
|
||||
breadcrumbItems.push(
|
||||
<BreadcrumbItem key={lastPathItem} href={lastUrl}>
|
||||
{lastPathItem}
|
||||
</BreadcrumbItem>
|
||||
);
|
||||
} else {
|
||||
breadcrumbItems.push(
|
||||
<BreadcrumbItem isCurrentPage key={lastPathItem}>
|
||||
{lastPathItem}
|
||||
</BreadcrumbItem>
|
||||
);
|
||||
}
|
||||
return breadcrumbItems;
|
||||
};
|
||||
export default function ProcessBreadcrumb({ hotCrumbs }: OwnProps) {
|
||||
const [processEntity, setProcessEntity] = useState<
|
||||
ProcessGroup | ProcessModel | null
|
||||
>(null);
|
||||
|
||||
export default function ProcessBreadcrumb({
|
||||
processModelId,
|
||||
processGroupId,
|
||||
hotCrumbs,
|
||||
linkProcessModel = false,
|
||||
}: OwnProps) {
|
||||
let processGroupBreadcrumb = null;
|
||||
let processModelBreadcrumb = null;
|
||||
if (hotCrumbs) {
|
||||
const leadingCrumbLinks = hotCrumbs.map((crumb: any) => {
|
||||
const valueLabel = crumb[0];
|
||||
const url = crumb[1];
|
||||
if (!url) {
|
||||
return (
|
||||
<BreadcrumbItem isCurrentPage key={valueLabel}>
|
||||
{valueLabel}
|
||||
</BreadcrumbItem>
|
||||
);
|
||||
useEffect(() => {
|
||||
const explodeCrumbItemObject = (crumb: HotCrumbItem) => {
|
||||
if ('entityToExplode' in crumb) {
|
||||
const { entityToExplode, entityType } = crumb;
|
||||
if (entityType === 'process-model-id') {
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-models/${modifyProcessIdentifierForPathParam(
|
||||
entityToExplode as string
|
||||
)}`,
|
||||
successCallback: setProcessEntity,
|
||||
});
|
||||
} else if (entityType === 'process-group-id') {
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-groups/${modifyProcessIdentifierForPathParam(
|
||||
entityToExplode as string
|
||||
)}`,
|
||||
successCallback: setProcessEntity,
|
||||
});
|
||||
} else {
|
||||
setProcessEntity(entityToExplode as any);
|
||||
}
|
||||
}
|
||||
if (url && url.match(/^process[_-](model|group)s?:/)) {
|
||||
return explodeCrumb(crumb);
|
||||
}
|
||||
return (
|
||||
<BreadcrumbItem key={valueLabel} href={url}>
|
||||
{valueLabel}
|
||||
</BreadcrumbItem>
|
||||
);
|
||||
});
|
||||
return <Breadcrumb noTrailingSlash>{leadingCrumbLinks}</Breadcrumb>;
|
||||
}
|
||||
if (processModelId) {
|
||||
if (linkProcessModel) {
|
||||
processModelBreadcrumb = (
|
||||
<BreadcrumbItem
|
||||
href={`/admin/process-models/${processGroupId}/${processModelId}`}
|
||||
>
|
||||
{`Process Model: ${processModelId}`}
|
||||
</BreadcrumbItem>
|
||||
);
|
||||
} else {
|
||||
processModelBreadcrumb = (
|
||||
<BreadcrumbItem isCurrentPage>
|
||||
{`Process Model: ${processModelId}`}
|
||||
</BreadcrumbItem>
|
||||
);
|
||||
};
|
||||
if (hotCrumbs) {
|
||||
hotCrumbs.forEach(explodeCrumbItemObject);
|
||||
}
|
||||
processGroupBreadcrumb = (
|
||||
<BreadcrumbItem
|
||||
data-qa="process-group-breadcrumb-link"
|
||||
href={`/admin/process-groups/${processGroupId}`}
|
||||
>
|
||||
{`Process Group: ${processGroupId}`}
|
||||
</BreadcrumbItem>
|
||||
);
|
||||
} else if (processGroupId) {
|
||||
processGroupBreadcrumb = (
|
||||
<BreadcrumbItem isCurrentPage>
|
||||
{`Process Group: ${processGroupId}`}
|
||||
</BreadcrumbItem>
|
||||
);
|
||||
}
|
||||
}, [setProcessEntity, hotCrumbs]);
|
||||
|
||||
return (
|
||||
<Breadcrumb noTrailingSlash>
|
||||
<BreadcrumbItem href="/admin">Process Groups</BreadcrumbItem>
|
||||
{processGroupBreadcrumb}
|
||||
{processModelBreadcrumb}
|
||||
</Breadcrumb>
|
||||
);
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
const hotCrumbElement = () => {
|
||||
if (hotCrumbs) {
|
||||
const leadingCrumbLinks = hotCrumbs.map((crumb: any) => {
|
||||
if (
|
||||
'entityToExplode' in crumb &&
|
||||
processEntity &&
|
||||
processEntity.parent_groups
|
||||
) {
|
||||
const breadcrumbs = processEntity.parent_groups.map(
|
||||
(parentGroup: ProcessGroupLite) => {
|
||||
const fullUrl = `/admin/process-groups/${modifyProcessIdentifierForPathParam(
|
||||
parentGroup.id
|
||||
)}`;
|
||||
return (
|
||||
<BreadcrumbItem key={parentGroup.id} href={fullUrl}>
|
||||
{parentGroup.display_name}
|
||||
</BreadcrumbItem>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
if (crumb.linkLastItem) {
|
||||
let apiBase = '/admin/process-groups';
|
||||
if (crumb.entityType.startsWith('process-model')) {
|
||||
apiBase = '/admin/process-models';
|
||||
}
|
||||
const fullUrl = `${apiBase}/${modifyProcessIdentifierForPathParam(
|
||||
processEntity.id
|
||||
)}`;
|
||||
breadcrumbs.push(
|
||||
<BreadcrumbItem key={processEntity.id} href={fullUrl}>
|
||||
{processEntity.display_name}
|
||||
</BreadcrumbItem>
|
||||
);
|
||||
} else {
|
||||
breadcrumbs.push(
|
||||
<BreadcrumbItem key={processEntity.id} isCurrentPage>
|
||||
{processEntity.display_name}
|
||||
</BreadcrumbItem>
|
||||
);
|
||||
}
|
||||
return breadcrumbs;
|
||||
}
|
||||
const valueLabel = crumb[0];
|
||||
const url = crumb[1];
|
||||
if (!url && valueLabel) {
|
||||
return (
|
||||
<BreadcrumbItem isCurrentPage key={valueLabel}>
|
||||
{valueLabel}
|
||||
</BreadcrumbItem>
|
||||
);
|
||||
}
|
||||
if (url && valueLabel) {
|
||||
return (
|
||||
<BreadcrumbItem key={valueLabel} href={url}>
|
||||
{valueLabel}
|
||||
</BreadcrumbItem>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
return <Breadcrumb noTrailingSlash>{leadingCrumbLinks}</Breadcrumb>;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
return <Breadcrumb noTrailingSlash>{hotCrumbElement()}</Breadcrumb>;
|
||||
}
|
||||
|
|
|
@ -2,10 +2,9 @@ import { useState } from 'react';
|
|||
import { useNavigate } from 'react-router-dom';
|
||||
// @ts-ignore
|
||||
import { Button, ButtonSet, Form, Stack, TextInput } from '@carbon/react';
|
||||
import { modifyProcessModelPath, slugifyString } from '../helpers';
|
||||
import { modifyProcessIdentifierForPathParam, slugifyString } from '../helpers';
|
||||
import HttpService from '../services/HttpService';
|
||||
import { ProcessGroup } from '../interfaces';
|
||||
import ButtonWithConfirmation from './ButtonWithConfirmation';
|
||||
|
||||
type OwnProps = {
|
||||
mode: string;
|
||||
|
@ -28,34 +27,24 @@ export default function ProcessGroupForm({
|
|||
const navigateToProcessGroup = (_result: any) => {
|
||||
if (newProcessGroupId) {
|
||||
navigate(
|
||||
`/admin/process-groups/${modifyProcessModelPath(newProcessGroupId)}`
|
||||
`/admin/process-groups/${modifyProcessIdentifierForPathParam(
|
||||
newProcessGroupId
|
||||
)}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const navigateToProcessGroups = (_result: any) => {
|
||||
navigate(`/admin/process-groups`);
|
||||
};
|
||||
|
||||
const hasValidIdentifier = (identifierToCheck: string) => {
|
||||
return identifierToCheck.match(/^[a-z0-9][0-9a-z-]+[a-z0-9]$/);
|
||||
};
|
||||
|
||||
const deleteProcessGroup = () => {
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-groups/${modifyProcessModelPath(processGroup.id)}`,
|
||||
successCallback: navigateToProcessGroups,
|
||||
httpMethod: 'DELETE',
|
||||
});
|
||||
};
|
||||
|
||||
const handleFormSubmission = (event: any) => {
|
||||
const searchParams = new URLSearchParams(document.location.search);
|
||||
const parentGroupId = searchParams.get('parentGroupId');
|
||||
|
||||
event.preventDefault();
|
||||
let hasErrors = false;
|
||||
if (!hasValidIdentifier(processGroup.id)) {
|
||||
if (mode === 'new' && !hasValidIdentifier(processGroup.id)) {
|
||||
setIdentifierInvalid(true);
|
||||
hasErrors = true;
|
||||
}
|
||||
|
@ -68,7 +57,9 @@ export default function ProcessGroupForm({
|
|||
}
|
||||
let path = '/process-groups';
|
||||
if (mode === 'edit') {
|
||||
path = `/process-groups/${processGroup.id}`;
|
||||
path = `/process-groups/${modifyProcessIdentifierForPathParam(
|
||||
processGroup.id
|
||||
)}`;
|
||||
}
|
||||
let httpMethod = 'POST';
|
||||
if (mode === 'edit') {
|
||||
|
@ -124,7 +115,6 @@ export default function ProcessGroupForm({
|
|||
labelText="Display Name*"
|
||||
value={processGroup.display_name}
|
||||
onChange={(event: any) => onDisplayNameChanged(event.target.value)}
|
||||
onBlur={(event: any) => console.log('event', event)}
|
||||
/>,
|
||||
];
|
||||
|
||||
|
@ -166,16 +156,6 @@ export default function ProcessGroupForm({
|
|||
|
||||
const formButtons = () => {
|
||||
const buttons = [<Button type="submit">Submit</Button>];
|
||||
if (mode === 'edit') {
|
||||
buttons.push(
|
||||
<ButtonWithConfirmation
|
||||
description={`Delete Process Group ${processGroup.id}?`}
|
||||
onConfirmation={deleteProcessGroup}
|
||||
buttonLabel="Delete"
|
||||
confirmButtonLabel="Delete"
|
||||
/>
|
||||
);
|
||||
}
|
||||
return <ButtonSet>{buttons}</ButtonSet>;
|
||||
};
|
||||
|
||||
|
|
|
@ -10,7 +10,10 @@ import {
|
|||
} from '@carbon/react';
|
||||
import HttpService from '../services/HttpService';
|
||||
import { ProcessGroup } from '../interfaces';
|
||||
import { modifyProcessModelPath, truncateString } from '../helpers';
|
||||
import {
|
||||
modifyProcessIdentifierForPathParam,
|
||||
truncateString,
|
||||
} from '../helpers';
|
||||
|
||||
type OwnProps = {
|
||||
processGroup?: ProcessGroup;
|
||||
|
@ -51,9 +54,11 @@ export default function ProcessGroupListTiles({
|
|||
displayText = (processGroups || []).map((row: ProcessGroup) => {
|
||||
return (
|
||||
<ClickableTile
|
||||
id="tile-1"
|
||||
id={`process-group-tile-${row.id}`}
|
||||
className="tile-process-group"
|
||||
href={`/admin/process-groups/${modifyProcessModelPath(row.id)}`}
|
||||
href={`/admin/process-groups/${modifyProcessIdentifierForPathParam(
|
||||
row.id
|
||||
)}`}
|
||||
>
|
||||
<div className="tile-process-group-content-container">
|
||||
<ArrowRight />
|
||||
|
@ -61,7 +66,7 @@ export default function ProcessGroupListTiles({
|
|||
{row.display_name}
|
||||
</div>
|
||||
<p className="tile-description">
|
||||
{truncateString(row.description || '', 25)}
|
||||
{truncateString(row.description || '', 100)}
|
||||
</p>
|
||||
<p className="tile-process-group-children-count tile-pin-bottom">
|
||||
Total Sub Items: {processGroupDirectChildrenCount(row)}
|
||||
|
|
|
@ -0,0 +1,205 @@
|
|||
import { useState } from 'react';
|
||||
import {
|
||||
Button,
|
||||
TextInput,
|
||||
Stack,
|
||||
Modal,
|
||||
// @ts-ignore
|
||||
} from '@carbon/react';
|
||||
import {
|
||||
ReportFilter,
|
||||
ProcessInstanceReport,
|
||||
ProcessModel,
|
||||
ReportColumn,
|
||||
ReportMetadata,
|
||||
} from '../interfaces';
|
||||
import HttpService from '../services/HttpService';
|
||||
|
||||
type OwnProps = {
|
||||
onSuccess: (..._args: any[]) => any;
|
||||
columnArray: ReportColumn[];
|
||||
orderBy: string;
|
||||
processModelSelection: ProcessModel | null;
|
||||
processStatusSelection: string[];
|
||||
startFromSeconds: string | null;
|
||||
startToSeconds: string | null;
|
||||
endFromSeconds: string | null;
|
||||
endToSeconds: string | null;
|
||||
buttonText?: string;
|
||||
buttonClassName?: string;
|
||||
processInstanceReportSelection?: ProcessInstanceReport | null;
|
||||
reportMetadata: ReportMetadata;
|
||||
};
|
||||
|
||||
export default function ProcessInstanceListSaveAsReport({
|
||||
onSuccess,
|
||||
columnArray,
|
||||
orderBy,
|
||||
processModelSelection,
|
||||
processInstanceReportSelection,
|
||||
processStatusSelection,
|
||||
startFromSeconds,
|
||||
startToSeconds,
|
||||
endFromSeconds,
|
||||
endToSeconds,
|
||||
buttonClassName,
|
||||
buttonText = 'Save as Perspective',
|
||||
reportMetadata,
|
||||
}: OwnProps) {
|
||||
const [identifier, setIdentifier] = useState<string>(
|
||||
processInstanceReportSelection?.identifier || ''
|
||||
);
|
||||
const [showSaveForm, setShowSaveForm] = useState<boolean>(false);
|
||||
|
||||
const isEditMode = () => {
|
||||
return (
|
||||
processInstanceReportSelection &&
|
||||
processInstanceReportSelection.identifier === identifier
|
||||
);
|
||||
};
|
||||
|
||||
const responseHandler = (result: any) => {
|
||||
if (result) {
|
||||
onSuccess(result, isEditMode() ? 'edit' : 'new');
|
||||
}
|
||||
};
|
||||
|
||||
const handleSaveFormClose = () => {
|
||||
setIdentifier(processInstanceReportSelection?.identifier || '');
|
||||
setShowSaveForm(false);
|
||||
};
|
||||
|
||||
const addProcessInstanceReport = (event: any) => {
|
||||
event.preventDefault();
|
||||
|
||||
// TODO: make a field to set this
|
||||
let orderByArray = ['-start_in_seconds', '-id'];
|
||||
if (orderBy) {
|
||||
orderByArray = orderBy.split(',').filter((n) => n);
|
||||
}
|
||||
const filterByArray: any = [];
|
||||
|
||||
if (processModelSelection) {
|
||||
filterByArray.push({
|
||||
field_name: 'process_model_identifier',
|
||||
field_value: processModelSelection.id,
|
||||
});
|
||||
}
|
||||
|
||||
if (processStatusSelection.length > 0) {
|
||||
filterByArray.push({
|
||||
field_name: 'process_status',
|
||||
field_value: processStatusSelection.join(','),
|
||||
operator: 'in',
|
||||
});
|
||||
}
|
||||
|
||||
if (startFromSeconds) {
|
||||
filterByArray.push({
|
||||
field_name: 'start_from',
|
||||
field_value: startFromSeconds,
|
||||
});
|
||||
}
|
||||
|
||||
if (startToSeconds) {
|
||||
filterByArray.push({
|
||||
field_name: 'start_to',
|
||||
field_value: startToSeconds,
|
||||
});
|
||||
}
|
||||
|
||||
if (endFromSeconds) {
|
||||
filterByArray.push({
|
||||
field_name: 'end_from',
|
||||
field_value: endFromSeconds,
|
||||
});
|
||||
}
|
||||
|
||||
if (endToSeconds) {
|
||||
filterByArray.push({
|
||||
field_name: 'end_to',
|
||||
field_value: endToSeconds,
|
||||
});
|
||||
}
|
||||
|
||||
reportMetadata.filter_by.forEach((reportFilter: ReportFilter) => {
|
||||
columnArray.forEach((reportColumn: ReportColumn) => {
|
||||
if (
|
||||
reportColumn.accessor === reportFilter.field_name &&
|
||||
reportColumn.filterable
|
||||
) {
|
||||
filterByArray.push(reportFilter);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
let path = `/process-instances/reports`;
|
||||
let httpMethod = 'POST';
|
||||
if (isEditMode() && processInstanceReportSelection) {
|
||||
httpMethod = 'PUT';
|
||||
path = `${path}/${processInstanceReportSelection.id}`;
|
||||
}
|
||||
|
||||
HttpService.makeCallToBackend({
|
||||
path,
|
||||
successCallback: responseHandler,
|
||||
httpMethod,
|
||||
postBody: {
|
||||
identifier,
|
||||
report_metadata: {
|
||||
columns: columnArray,
|
||||
order_by: orderByArray,
|
||||
filter_by: filterByArray,
|
||||
},
|
||||
},
|
||||
});
|
||||
handleSaveFormClose();
|
||||
};
|
||||
|
||||
let textInputComponent = null;
|
||||
textInputComponent = (
|
||||
<TextInput
|
||||
id="identifier"
|
||||
name="identifier"
|
||||
labelText="Identifier"
|
||||
className="no-wrap"
|
||||
inline
|
||||
value={identifier}
|
||||
onChange={(e: any) => setIdentifier(e.target.value)}
|
||||
/>
|
||||
);
|
||||
|
||||
let descriptionText =
|
||||
'Save the current columns and filters as a perspective so you can come back to this view in the future.';
|
||||
if (processInstanceReportSelection) {
|
||||
descriptionText =
|
||||
'Keep the identifier the same and click Save to update the current perspective. Change the identifier if you want to save the current view with a new name.';
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack gap={5} orientation="horizontal">
|
||||
<Modal
|
||||
open={showSaveForm}
|
||||
modalHeading="Save Perspective"
|
||||
primaryButtonText="Save"
|
||||
primaryButtonDisabled={!identifier}
|
||||
onRequestSubmit={addProcessInstanceReport}
|
||||
onRequestClose={handleSaveFormClose}
|
||||
hasScrollingContent
|
||||
>
|
||||
<p className="data-table-description">{descriptionText}</p>
|
||||
{textInputComponent}
|
||||
</Modal>
|
||||
<Button
|
||||
kind=""
|
||||
className={buttonClassName}
|
||||
onClick={() => {
|
||||
setIdentifier(processInstanceReportSelection?.identifier || '');
|
||||
setShowSaveForm(true);
|
||||
}}
|
||||
>
|
||||
{buttonText}
|
||||
</Button>
|
||||
</Stack>
|
||||
);
|
||||
}
|
|
@ -7,7 +7,7 @@ import {
|
|||
} from 'react-router-dom';
|
||||
|
||||
// @ts-ignore
|
||||
import { Filter } from '@carbon/icons-react';
|
||||
import { Filter, Close, AddAlt } from '@carbon/icons-react';
|
||||
import {
|
||||
Button,
|
||||
ButtonSet,
|
||||
|
@ -21,6 +21,12 @@ import {
|
|||
TableHead,
|
||||
TableRow,
|
||||
TimePicker,
|
||||
Tag,
|
||||
Stack,
|
||||
Modal,
|
||||
ComboBox,
|
||||
TextInput,
|
||||
FormLabel,
|
||||
// @ts-ignore
|
||||
} from '@carbon/react';
|
||||
import { PROCESS_STATUSES, DATE_FORMAT, DATE_FORMAT_CARBON } from '../config';
|
||||
|
@ -32,7 +38,8 @@ import {
|
|||
convertSecondsToFormattedTimeHoursMinutes,
|
||||
getPageInfoFromSearchParams,
|
||||
getProcessModelFullIdentifierFromSearchParams,
|
||||
modifyProcessModelPath,
|
||||
modifyProcessIdentifierForPathParam,
|
||||
refreshAtInterval,
|
||||
} from '../helpers';
|
||||
|
||||
import PaginationForTable from './PaginationForTable';
|
||||
|
@ -43,14 +50,35 @@ import HttpService from '../services/HttpService';
|
|||
|
||||
import 'react-bootstrap-typeahead/css/Typeahead.css';
|
||||
import 'react-bootstrap-typeahead/css/Typeahead.bs5.css';
|
||||
import { PaginationObject, ProcessModel } from '../interfaces';
|
||||
import {
|
||||
PaginationObject,
|
||||
ProcessModel,
|
||||
ProcessInstanceReport,
|
||||
ProcessInstance,
|
||||
ReportColumn,
|
||||
ReportColumnForEditing,
|
||||
ReportMetadata,
|
||||
ReportFilter,
|
||||
} from '../interfaces';
|
||||
import ProcessModelSearch from './ProcessModelSearch';
|
||||
import ProcessInstanceReportSearch from './ProcessInstanceReportSearch';
|
||||
import ProcessInstanceListSaveAsReport from './ProcessInstanceListSaveAsReport';
|
||||
import { FormatProcessModelDisplayName } from './MiniComponents';
|
||||
import { Notification } from './Notification';
|
||||
|
||||
const REFRESH_INTERVAL = 5;
|
||||
const REFRESH_TIMEOUT = 600;
|
||||
|
||||
type OwnProps = {
|
||||
filtersEnabled?: boolean;
|
||||
processModelFullIdentifier?: string;
|
||||
paginationQueryParamPrefix?: string;
|
||||
perPageOptions?: number[];
|
||||
showReports?: boolean;
|
||||
reportIdentifier?: string;
|
||||
textToShowIfEmpty?: string;
|
||||
paginationClassName?: string;
|
||||
autoReload?: boolean;
|
||||
};
|
||||
|
||||
interface dateParameters {
|
||||
|
@ -62,13 +90,18 @@ export default function ProcessInstanceListTable({
|
|||
processModelFullIdentifier,
|
||||
paginationQueryParamPrefix,
|
||||
perPageOptions,
|
||||
showReports = true,
|
||||
reportIdentifier,
|
||||
textToShowIfEmpty,
|
||||
paginationClassName,
|
||||
autoReload = false,
|
||||
}: OwnProps) {
|
||||
const params = useParams();
|
||||
const [searchParams] = useSearchParams();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [processInstances, setProcessInstances] = useState([]);
|
||||
const [reportMetadata, setReportMetadata] = useState({});
|
||||
const [reportMetadata, setReportMetadata] = useState<ReportMetadata | null>();
|
||||
const [pagination, setPagination] = useState<PaginationObject | null>(null);
|
||||
const [processInstanceFilters, setProcessInstanceFilters] = useState({});
|
||||
|
||||
|
@ -102,6 +135,19 @@ export default function ProcessInstanceListTable({
|
|||
>([]);
|
||||
const [processModelSelection, setProcessModelSelection] =
|
||||
useState<ProcessModel | null>(null);
|
||||
const [processInstanceReportSelection, setProcessInstanceReportSelection] =
|
||||
useState<ProcessInstanceReport | null>(null);
|
||||
|
||||
const [availableReportColumns, setAvailableReportColumns] = useState<
|
||||
ReportColumn[]
|
||||
>([]);
|
||||
const [processInstanceReportJustSaved, setProcessInstanceReportJustSaved] =
|
||||
useState<string | null>(null);
|
||||
const [showReportColumnForm, setShowReportColumnForm] =
|
||||
useState<boolean>(false);
|
||||
const [reportColumnToOperateOn, setReportColumnToOperateOn] =
|
||||
useState<ReportColumnForEditing | null>(null);
|
||||
const [reportColumnFormMode, setReportColumnFormMode] = useState<string>('');
|
||||
|
||||
const dateParametersToAlwaysFilterBy: dateParameters = useMemo(() => {
|
||||
return {
|
||||
|
@ -133,9 +179,13 @@ export default function ProcessInstanceListTable({
|
|||
function setProcessInstancesFromResult(result: any) {
|
||||
const processInstancesFromApi = result.results;
|
||||
setProcessInstances(processInstancesFromApi);
|
||||
setReportMetadata(result.report_metadata);
|
||||
setPagination(result.pagination);
|
||||
setProcessInstanceFilters(result.filters);
|
||||
|
||||
setReportMetadata(result.report.report_metadata);
|
||||
if (result.report.id) {
|
||||
setProcessInstanceReportSelection(result.report);
|
||||
}
|
||||
}
|
||||
function getProcessInstances() {
|
||||
// eslint-disable-next-line prefer-const
|
||||
|
@ -156,6 +206,12 @@ export default function ProcessInstanceListTable({
|
|||
queryParamString += `&user_filter=${userAppliedFilter}`;
|
||||
}
|
||||
|
||||
if (searchParams.get('report_id')) {
|
||||
queryParamString += `&report_id=${searchParams.get('report_id')}`;
|
||||
} else if (reportIdentifier) {
|
||||
queryParamString += `&report_identifier=${reportIdentifier}`;
|
||||
}
|
||||
|
||||
Object.keys(dateParametersToAlwaysFilterBy).forEach(
|
||||
(paramName: string) => {
|
||||
const dateFunctionToCall =
|
||||
|
@ -230,17 +286,24 @@ export default function ProcessInstanceListTable({
|
|||
|
||||
getProcessInstances();
|
||||
}
|
||||
const checkFiltersAndRun = () => {
|
||||
if (filtersEnabled) {
|
||||
// populate process model selection
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-models?per_page=1000&recursive=true`,
|
||||
successCallback: processResultForProcessModels,
|
||||
});
|
||||
} else {
|
||||
getProcessInstances();
|
||||
}
|
||||
};
|
||||
|
||||
if (filtersEnabled) {
|
||||
// populate process model selection
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-models?per_page=1000`,
|
||||
successCallback: processResultForProcessModels,
|
||||
});
|
||||
} else {
|
||||
getProcessInstances();
|
||||
checkFiltersAndRun();
|
||||
if (autoReload) {
|
||||
refreshAtInterval(REFRESH_INTERVAL, REFRESH_TIMEOUT, checkFiltersAndRun);
|
||||
}
|
||||
}, [
|
||||
autoReload,
|
||||
searchParams,
|
||||
params,
|
||||
oneMonthInSeconds,
|
||||
|
@ -251,6 +314,7 @@ export default function ProcessInstanceListTable({
|
|||
paginationQueryParamPrefix,
|
||||
processModelFullIdentifier,
|
||||
perPageOptions,
|
||||
reportIdentifier,
|
||||
]);
|
||||
|
||||
// This sets the filter data using the saved reports returned from the initial instance_list query.
|
||||
|
@ -301,6 +365,28 @@ export default function ProcessInstanceListTable({
|
|||
processModelAvailableItems,
|
||||
]);
|
||||
|
||||
const processInstanceReportSaveTag = () => {
|
||||
if (processInstanceReportJustSaved) {
|
||||
let titleOperation = 'Updated';
|
||||
if (processInstanceReportJustSaved === 'new') {
|
||||
titleOperation = 'Created';
|
||||
}
|
||||
return (
|
||||
<Notification
|
||||
title={`Perspective: ${titleOperation}`}
|
||||
onClose={() => setProcessInstanceReportJustSaved(null)}
|
||||
>
|
||||
<span>{`'${
|
||||
processInstanceReportSelection
|
||||
? processInstanceReportSelection.identifier
|
||||
: ''
|
||||
}'`}</span>
|
||||
</Notification>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
// does the comparison, but also returns false if either argument
|
||||
// is not truthy and therefore not comparable.
|
||||
const isTrueComparison = (param1: any, operation: any, param2: any) => {
|
||||
|
@ -318,16 +404,8 @@ export default function ProcessInstanceListTable({
|
|||
}
|
||||
};
|
||||
|
||||
const applyFilter = (event: any) => {
|
||||
event.preventDefault();
|
||||
const { page, perPage } = getPageInfoFromSearchParams(
|
||||
searchParams,
|
||||
undefined,
|
||||
undefined,
|
||||
paginationQueryParamPrefix
|
||||
);
|
||||
let queryParamString = `per_page=${perPage}&page=${page}&user_filter=true`;
|
||||
|
||||
// TODO: after factoring this out page hangs when invalid date ranges and applying the filter
|
||||
const calculateStartAndEndSeconds = () => {
|
||||
const startFromSeconds = convertDateAndTimeStringsToSeconds(
|
||||
startFromDate,
|
||||
startFromTime || '00:00:00'
|
||||
|
@ -344,28 +422,59 @@ export default function ProcessInstanceListTable({
|
|||
endToDate,
|
||||
endToTime || '00:00:00'
|
||||
);
|
||||
let valid = true;
|
||||
if (isTrueComparison(startFromSeconds, '>', startToSeconds)) {
|
||||
setErrorMessage({
|
||||
message: '"Start date from" cannot be after "start date to"',
|
||||
});
|
||||
return;
|
||||
valid = false;
|
||||
}
|
||||
if (isTrueComparison(endFromSeconds, '>', endToSeconds)) {
|
||||
setErrorMessage({
|
||||
message: '"End date from" cannot be after "end date to"',
|
||||
});
|
||||
return;
|
||||
valid = false;
|
||||
}
|
||||
if (isTrueComparison(startFromSeconds, '>', endFromSeconds)) {
|
||||
setErrorMessage({
|
||||
message: '"Start date from" cannot be after "end date from"',
|
||||
});
|
||||
return;
|
||||
valid = false;
|
||||
}
|
||||
if (isTrueComparison(startToSeconds, '>', endToSeconds)) {
|
||||
setErrorMessage({
|
||||
message: '"Start date to" cannot be after "end date to"',
|
||||
});
|
||||
valid = false;
|
||||
}
|
||||
|
||||
return {
|
||||
valid,
|
||||
startFromSeconds,
|
||||
startToSeconds,
|
||||
endFromSeconds,
|
||||
endToSeconds,
|
||||
};
|
||||
};
|
||||
|
||||
const applyFilter = (event: any) => {
|
||||
event.preventDefault();
|
||||
const { page, perPage } = getPageInfoFromSearchParams(
|
||||
searchParams,
|
||||
undefined,
|
||||
undefined,
|
||||
paginationQueryParamPrefix
|
||||
);
|
||||
let queryParamString = `per_page=${perPage}&page=${page}&user_filter=true`;
|
||||
const {
|
||||
valid,
|
||||
startFromSeconds,
|
||||
startToSeconds,
|
||||
endFromSeconds,
|
||||
endToSeconds,
|
||||
} = calculateStartAndEndSeconds();
|
||||
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -389,7 +498,12 @@ export default function ProcessInstanceListTable({
|
|||
queryParamString += `&process_model_identifier=${processModelSelection.id}`;
|
||||
}
|
||||
|
||||
if (processInstanceReportSelection) {
|
||||
queryParamString += `&report_id=${processInstanceReportSelection.id}`;
|
||||
}
|
||||
|
||||
setErrorMessage(null);
|
||||
setProcessInstanceReportJustSaved(null);
|
||||
navigate(`/admin/process-instances?${queryParamString}`);
|
||||
};
|
||||
|
||||
|
@ -477,12 +591,369 @@ export default function ProcessInstanceListTable({
|
|||
setEndToTime('');
|
||||
};
|
||||
|
||||
const processInstanceReportDidChange = (selection: any, mode?: string) => {
|
||||
clearFilters();
|
||||
const selectedReport = selection.selectedItem;
|
||||
setProcessInstanceReportSelection(selectedReport);
|
||||
|
||||
let queryParamString = '';
|
||||
if (selectedReport) {
|
||||
queryParamString = `?report_id=${selectedReport.id}`;
|
||||
}
|
||||
|
||||
setErrorMessage(null);
|
||||
setProcessInstanceReportJustSaved(mode || null);
|
||||
navigate(`/admin/process-instances${queryParamString}`);
|
||||
};
|
||||
|
||||
const reportColumns = () => {
|
||||
return (reportMetadata as any).columns;
|
||||
};
|
||||
|
||||
const reportColumnAccessors = () => {
|
||||
return reportColumns().map((reportColumn: ReportColumn) => {
|
||||
return reportColumn.accessor;
|
||||
});
|
||||
};
|
||||
|
||||
// TODO onSuccess reload/select the new report in the report search
|
||||
const onSaveReportSuccess = (result: any, mode: string) => {
|
||||
processInstanceReportDidChange(
|
||||
{
|
||||
selectedItem: result,
|
||||
},
|
||||
mode
|
||||
);
|
||||
};
|
||||
|
||||
const saveAsReportComponent = () => {
|
||||
const {
|
||||
valid,
|
||||
startFromSeconds,
|
||||
startToSeconds,
|
||||
endFromSeconds,
|
||||
endToSeconds,
|
||||
} = calculateStartAndEndSeconds();
|
||||
|
||||
if (!valid || !reportMetadata) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<ProcessInstanceListSaveAsReport
|
||||
onSuccess={onSaveReportSuccess}
|
||||
buttonClassName="button-white-background narrow-button"
|
||||
columnArray={reportColumns()}
|
||||
orderBy=""
|
||||
buttonText="Save"
|
||||
processModelSelection={processModelSelection}
|
||||
processStatusSelection={processStatusSelection}
|
||||
processInstanceReportSelection={processInstanceReportSelection}
|
||||
reportMetadata={reportMetadata}
|
||||
startFromSeconds={startFromSeconds}
|
||||
startToSeconds={startToSeconds}
|
||||
endFromSeconds={endFromSeconds}
|
||||
endToSeconds={endToSeconds}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const removeColumn = (reportColumn: ReportColumn) => {
|
||||
if (reportMetadata) {
|
||||
const reportMetadataCopy = { ...reportMetadata };
|
||||
const newColumns = reportColumns().filter(
|
||||
(rc: ReportColumn) => rc.accessor !== reportColumn.accessor
|
||||
);
|
||||
Object.assign(reportMetadataCopy, { columns: newColumns });
|
||||
setReportMetadata(reportMetadataCopy);
|
||||
}
|
||||
};
|
||||
|
||||
const handleColumnFormClose = () => {
|
||||
setShowReportColumnForm(false);
|
||||
setReportColumnFormMode('');
|
||||
setReportColumnToOperateOn(null);
|
||||
};
|
||||
|
||||
const getFilterByFromReportMetadata = (reportColumnAccessor: string) => {
|
||||
if (reportMetadata) {
|
||||
return reportMetadata.filter_by.find((reportFilter: ReportFilter) => {
|
||||
return reportColumnAccessor === reportFilter.field_name;
|
||||
});
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const getNewFiltersFromReportForEditing = (
|
||||
reportColumnForEditing: ReportColumnForEditing
|
||||
) => {
|
||||
if (!reportMetadata) {
|
||||
return null;
|
||||
}
|
||||
const reportMetadataCopy = { ...reportMetadata };
|
||||
let newReportFilters = reportMetadataCopy.filter_by;
|
||||
if (reportColumnForEditing.filterable) {
|
||||
const newReportFilter: ReportFilter = {
|
||||
field_name: reportColumnForEditing.accessor,
|
||||
field_value: reportColumnForEditing.filter_field_value,
|
||||
operator: reportColumnForEditing.filter_operator || 'equals',
|
||||
};
|
||||
const existingReportFilter = getFilterByFromReportMetadata(
|
||||
reportColumnForEditing.accessor
|
||||
);
|
||||
if (existingReportFilter) {
|
||||
const existingReportFilterIndex =
|
||||
reportMetadataCopy.filter_by.indexOf(existingReportFilter);
|
||||
if (reportColumnForEditing.filter_field_value) {
|
||||
newReportFilters[existingReportFilterIndex] = newReportFilter;
|
||||
} else {
|
||||
newReportFilters.splice(existingReportFilterIndex, 1);
|
||||
}
|
||||
} else if (reportColumnForEditing.filter_field_value) {
|
||||
newReportFilters = newReportFilters.concat([newReportFilter]);
|
||||
}
|
||||
}
|
||||
return newReportFilters;
|
||||
};
|
||||
|
||||
const handleUpdateReportColumn = () => {
|
||||
if (reportColumnToOperateOn && reportMetadata) {
|
||||
const reportMetadataCopy = { ...reportMetadata };
|
||||
let newReportColumns = null;
|
||||
if (reportColumnFormMode === 'new') {
|
||||
newReportColumns = reportColumns().concat([reportColumnToOperateOn]);
|
||||
} else {
|
||||
newReportColumns = reportColumns().map((rc: ReportColumn) => {
|
||||
if (rc.accessor === reportColumnToOperateOn.accessor) {
|
||||
return reportColumnToOperateOn;
|
||||
}
|
||||
return rc;
|
||||
});
|
||||
}
|
||||
Object.assign(reportMetadataCopy, {
|
||||
columns: newReportColumns,
|
||||
filter_by: getNewFiltersFromReportForEditing(reportColumnToOperateOn),
|
||||
});
|
||||
setReportMetadata(reportMetadataCopy);
|
||||
setReportColumnToOperateOn(null);
|
||||
setShowReportColumnForm(false);
|
||||
setShowReportColumnForm(false);
|
||||
}
|
||||
};
|
||||
|
||||
const reportColumnToReportColumnForEditing = (reportColumn: ReportColumn) => {
|
||||
const reportColumnForEditing: ReportColumnForEditing = Object.assign(
|
||||
reportColumn,
|
||||
{ filter_field_value: '', filter_operator: '' }
|
||||
);
|
||||
const reportFilter = getFilterByFromReportMetadata(
|
||||
reportColumnForEditing.accessor
|
||||
);
|
||||
if (reportFilter) {
|
||||
reportColumnForEditing.filter_field_value = reportFilter.field_value;
|
||||
reportColumnForEditing.filter_operator =
|
||||
reportFilter.operator || 'equals';
|
||||
}
|
||||
return reportColumnForEditing;
|
||||
};
|
||||
|
||||
const updateReportColumn = (event: any) => {
|
||||
const reportColumnForEditing = reportColumnToReportColumnForEditing(
|
||||
event.selectedItem
|
||||
);
|
||||
setReportColumnToOperateOn(reportColumnForEditing);
|
||||
};
|
||||
|
||||
// options includes item and inputValue
|
||||
const shouldFilterReportColumn = (options: any) => {
|
||||
const reportColumn: ReportColumn = options.item;
|
||||
const { inputValue } = options;
|
||||
return (
|
||||
!reportColumnAccessors().includes(reportColumn.accessor) &&
|
||||
(reportColumn.accessor || '')
|
||||
.toLowerCase()
|
||||
.includes((inputValue || '').toLowerCase())
|
||||
);
|
||||
};
|
||||
|
||||
const setReportColumnConditionValue = (event: any) => {
|
||||
if (reportColumnToOperateOn) {
|
||||
const reportColumnToOperateOnCopy = {
|
||||
...reportColumnToOperateOn,
|
||||
};
|
||||
reportColumnToOperateOnCopy.filter_field_value = event.target.value;
|
||||
setReportColumnToOperateOn(reportColumnToOperateOnCopy);
|
||||
}
|
||||
};
|
||||
|
||||
const reportColumnForm = () => {
|
||||
if (reportColumnFormMode === '') {
|
||||
return null;
|
||||
}
|
||||
const formElements = [
|
||||
<TextInput
|
||||
id="report-column-display-name"
|
||||
name="report-column-display-name"
|
||||
labelText="Display Name"
|
||||
disabled={!reportColumnToOperateOn}
|
||||
value={reportColumnToOperateOn ? reportColumnToOperateOn.Header : ''}
|
||||
onChange={(event: any) => {
|
||||
if (reportColumnToOperateOn) {
|
||||
const reportColumnToOperateOnCopy = {
|
||||
...reportColumnToOperateOn,
|
||||
};
|
||||
reportColumnToOperateOnCopy.Header = event.target.value;
|
||||
setReportColumnToOperateOn(reportColumnToOperateOnCopy);
|
||||
}
|
||||
}}
|
||||
/>,
|
||||
];
|
||||
if (reportColumnToOperateOn && reportColumnToOperateOn.filterable) {
|
||||
formElements.push(
|
||||
<TextInput
|
||||
id="report-column-condition-value"
|
||||
name="report-column-condition-value"
|
||||
labelText="Condition Value"
|
||||
value={
|
||||
reportColumnToOperateOn
|
||||
? reportColumnToOperateOn.filter_field_value
|
||||
: ''
|
||||
}
|
||||
onChange={setReportColumnConditionValue}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (reportColumnFormMode === 'new') {
|
||||
formElements.push(
|
||||
<ComboBox
|
||||
onChange={updateReportColumn}
|
||||
className="combo-box-in-modal"
|
||||
id="report-column-selection"
|
||||
data-qa="report-column-selection"
|
||||
data-modal-primary-focus
|
||||
items={availableReportColumns}
|
||||
itemToString={(reportColumn: ReportColumn) => {
|
||||
if (reportColumn) {
|
||||
return reportColumn.accessor;
|
||||
}
|
||||
return null;
|
||||
}}
|
||||
shouldFilterItem={shouldFilterReportColumn}
|
||||
placeholder="Choose a report column"
|
||||
titleText="Report Column"
|
||||
/>
|
||||
);
|
||||
}
|
||||
const modalHeading =
|
||||
reportColumnFormMode === 'new'
|
||||
? 'Add Column'
|
||||
: `Edit ${
|
||||
reportColumnToOperateOn ? reportColumnToOperateOn.accessor : ''
|
||||
} column`;
|
||||
return (
|
||||
<Modal
|
||||
open={showReportColumnForm}
|
||||
modalHeading={modalHeading}
|
||||
primaryButtonText="Save"
|
||||
primaryButtonDisabled={!reportColumnToOperateOn}
|
||||
onRequestSubmit={handleUpdateReportColumn}
|
||||
onRequestClose={handleColumnFormClose}
|
||||
hasScrollingContent
|
||||
>
|
||||
{formElements}
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
const columnSelections = () => {
|
||||
if (reportColumns()) {
|
||||
const tags: any = [];
|
||||
|
||||
(reportColumns() as any).forEach((reportColumn: ReportColumn) => {
|
||||
const reportColumnForEditing =
|
||||
reportColumnToReportColumnForEditing(reportColumn);
|
||||
|
||||
let tagType = 'cool-gray';
|
||||
let tagTypeClass = '';
|
||||
if (reportColumnForEditing.filterable) {
|
||||
tagType = 'green';
|
||||
tagTypeClass = 'tag-type-green';
|
||||
}
|
||||
let reportColumnLabel = reportColumnForEditing.Header;
|
||||
if (reportColumnForEditing.filter_field_value) {
|
||||
reportColumnLabel = `${reportColumnLabel}=${reportColumnForEditing.filter_field_value}`;
|
||||
}
|
||||
tags.push(
|
||||
<Tag type={tagType} size="sm">
|
||||
<Button
|
||||
kind="ghost"
|
||||
size="sm"
|
||||
className={`button-tag-icon ${tagTypeClass}`}
|
||||
title={`Edit ${reportColumnForEditing.accessor}`}
|
||||
onClick={() => {
|
||||
setReportColumnToOperateOn(reportColumnForEditing);
|
||||
setShowReportColumnForm(true);
|
||||
setReportColumnFormMode('edit');
|
||||
}}
|
||||
>
|
||||
{reportColumnLabel}
|
||||
</Button>
|
||||
<Button
|
||||
data-qa="remove-report-column"
|
||||
renderIcon={Close}
|
||||
iconDescription="Remove Column"
|
||||
className={`button-tag-icon ${tagTypeClass}`}
|
||||
hasIconOnly
|
||||
size="sm"
|
||||
kind="ghost"
|
||||
onClick={() => removeColumn(reportColumnForEditing)}
|
||||
/>
|
||||
</Tag>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<Stack orientation="horizontal">
|
||||
{tags}
|
||||
<Button
|
||||
data-qa="add-column-button"
|
||||
renderIcon={AddAlt}
|
||||
iconDescription="Filter Options"
|
||||
className="with-tiny-top-margin"
|
||||
kind="ghost"
|
||||
hasIconOnly
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setShowReportColumnForm(true);
|
||||
setReportColumnFormMode('new');
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const filterOptions = () => {
|
||||
if (!showFilterOptions) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// get the columns anytime we display the filter options if they are empty
|
||||
if (availableReportColumns.length < 1) {
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-instances/reports/columns`,
|
||||
successCallback: setAvailableReportColumns,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Grid fullWidth className="with-bottom-margin">
|
||||
<Column md={8} lg={16} sm={4}>
|
||||
<FormLabel>Columns</FormLabel>
|
||||
<br />
|
||||
{columnSelections()}
|
||||
</Column>
|
||||
</Grid>
|
||||
<Grid fullWidth className="with-bottom-margin">
|
||||
<Column md={8}>
|
||||
<ProcessModelSearch
|
||||
|
@ -546,11 +1017,11 @@ export default function ProcessInstanceListTable({
|
|||
</Column>
|
||||
</Grid>
|
||||
<Grid fullWidth className="with-bottom-margin">
|
||||
<Column md={4}>
|
||||
<Column sm={4} md={4} lg={8}>
|
||||
<ButtonSet>
|
||||
<Button
|
||||
kind=""
|
||||
className="button-white-background"
|
||||
className="button-white-background narrow-button"
|
||||
onClick={clearFilters}
|
||||
>
|
||||
Clear
|
||||
|
@ -559,11 +1030,15 @@ export default function ProcessInstanceListTable({
|
|||
kind="secondary"
|
||||
onClick={applyFilter}
|
||||
data-qa="filter-button"
|
||||
className="narrow-button"
|
||||
>
|
||||
Filter
|
||||
</Button>
|
||||
</ButtonSet>
|
||||
</Column>
|
||||
<Column sm={4} md={4} lg={8}>
|
||||
{saveAsReportComponent()}
|
||||
</Column>
|
||||
</Grid>
|
||||
</>
|
||||
);
|
||||
|
@ -572,28 +1047,30 @@ export default function ProcessInstanceListTable({
|
|||
const buildTable = () => {
|
||||
const headerLabels: Record<string, string> = {
|
||||
id: 'Id',
|
||||
process_model_identifier: 'Process Model',
|
||||
process_model_identifier: 'Process',
|
||||
process_model_display_name: 'Process',
|
||||
start_in_seconds: 'Start Time',
|
||||
end_in_seconds: 'End Time',
|
||||
status: 'Status',
|
||||
username: 'Started By',
|
||||
spiff_step: 'SpiffWorkflow Step',
|
||||
};
|
||||
const getHeaderLabel = (header: string) => {
|
||||
return headerLabels[header] ?? header;
|
||||
};
|
||||
const headers = (reportMetadata as any).columns.map((column: any) => {
|
||||
const headers = reportColumns().map((column: any) => {
|
||||
// return <th>{getHeaderLabel((column as any).Header)}</th>;
|
||||
return getHeaderLabel((column as any).Header);
|
||||
});
|
||||
|
||||
const formatProcessInstanceId = (row: any, id: any) => {
|
||||
const modifiedProcessModelId: String = modifyProcessModelPath(
|
||||
row.process_model_identifier
|
||||
);
|
||||
const formatProcessInstanceId = (row: ProcessInstance, id: number) => {
|
||||
const modifiedProcessModelId: String =
|
||||
modifyProcessIdentifierForPathParam(row.process_model_identifier);
|
||||
return (
|
||||
<Link
|
||||
data-qa="process-instance-show-link"
|
||||
to={`/admin/process-models/${modifiedProcessModelId}/process-instances/${row.id}`}
|
||||
to={`/admin/process-instances/${modifiedProcessModelId}/${id}`}
|
||||
title={`View process instance ${id}`}
|
||||
>
|
||||
{id}
|
||||
</Link>
|
||||
|
@ -602,12 +1079,15 @@ export default function ProcessInstanceListTable({
|
|||
const formatProcessModelIdentifier = (_row: any, identifier: any) => {
|
||||
return (
|
||||
<Link
|
||||
to={`/admin/process-models/${modifyProcessModelPath(identifier)}`}
|
||||
to={`/admin/process-models/${modifyProcessIdentifierForPathParam(
|
||||
identifier
|
||||
)}`}
|
||||
>
|
||||
{identifier}
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
const formatSecondsForDisplay = (_row: any, seconds: any) => {
|
||||
return convertSecondsToFormattedDateTime(seconds) || '-';
|
||||
};
|
||||
|
@ -615,14 +1095,16 @@ export default function ProcessInstanceListTable({
|
|||
return value;
|
||||
};
|
||||
|
||||
const columnFormatters: Record<string, any> = {
|
||||
const reportColumnFormatters: Record<string, any> = {
|
||||
id: formatProcessInstanceId,
|
||||
process_model_identifier: formatProcessModelIdentifier,
|
||||
process_model_display_name: FormatProcessModelDisplayName,
|
||||
start_in_seconds: formatSecondsForDisplay,
|
||||
end_in_seconds: formatSecondsForDisplay,
|
||||
};
|
||||
const formattedColumn = (row: any, column: any) => {
|
||||
const formatter = columnFormatters[column.accessor] ?? defaultFormatter;
|
||||
const formatter =
|
||||
reportColumnFormatters[column.accessor] ?? defaultFormatter;
|
||||
const value = row[column.accessor];
|
||||
if (column.accessor === 'status') {
|
||||
return (
|
||||
|
@ -635,7 +1117,7 @@ export default function ProcessInstanceListTable({
|
|||
};
|
||||
|
||||
const rows = processInstances.map((row: any) => {
|
||||
const currentRow = (reportMetadata as any).columns.map((column: any) => {
|
||||
const currentRow = reportColumns().map((column: any) => {
|
||||
return formattedColumn(row, column);
|
||||
});
|
||||
return <tr key={row.id}>{currentRow}</tr>;
|
||||
|
@ -664,6 +1146,25 @@ export default function ProcessInstanceListTable({
|
|||
setShowFilterOptions(!showFilterOptions);
|
||||
};
|
||||
|
||||
const reportSearchComponent = () => {
|
||||
if (showReports) {
|
||||
const columns = [
|
||||
<Column sm={2} md={4} lg={7}>
|
||||
<ProcessInstanceReportSearch
|
||||
onChange={processInstanceReportDidChange}
|
||||
selectedItem={processInstanceReportSelection}
|
||||
/>
|
||||
</Column>,
|
||||
];
|
||||
return (
|
||||
<Grid className="with-tiny-bottom-margin" fullWidth>
|
||||
{columns}
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const filterComponent = () => {
|
||||
if (!filtersEnabled) {
|
||||
return null;
|
||||
|
@ -671,14 +1172,17 @@ export default function ProcessInstanceListTable({
|
|||
return (
|
||||
<>
|
||||
<Grid fullWidth>
|
||||
<Column sm={2} md={4} lg={7}>
|
||||
{reportSearchComponent()}
|
||||
</Column>
|
||||
<Column
|
||||
className="filterIcon"
|
||||
sm={{ span: 1, offset: 3 }}
|
||||
md={{ span: 1, offset: 7 }}
|
||||
lg={{ span: 1, offset: 15 }}
|
||||
>
|
||||
<Button
|
||||
data-qa="filter-section-expand-toggle"
|
||||
kind="ghost"
|
||||
renderIcon={Filter}
|
||||
iconDescription="Filter Options"
|
||||
hasIconOnly
|
||||
|
@ -692,7 +1196,7 @@ export default function ProcessInstanceListTable({
|
|||
);
|
||||
};
|
||||
|
||||
if (pagination) {
|
||||
if (pagination && (!textToShowIfEmpty || pagination.total > 0)) {
|
||||
// eslint-disable-next-line prefer-const
|
||||
let { page, perPage } = getPageInfoFromSearchParams(
|
||||
searchParams,
|
||||
|
@ -706,6 +1210,8 @@ export default function ProcessInstanceListTable({
|
|||
}
|
||||
return (
|
||||
<>
|
||||
{reportColumnForm()}
|
||||
{processInstanceReportSaveTag()}
|
||||
{filterComponent()}
|
||||
<PaginationForTable
|
||||
page={page}
|
||||
|
@ -714,10 +1220,18 @@ export default function ProcessInstanceListTable({
|
|||
tableToDisplay={buildTable()}
|
||||
paginationQueryParamPrefix={paginationQueryParamPrefix}
|
||||
perPageOptions={perPageOptions}
|
||||
paginationClassName={paginationClassName}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
if (textToShowIfEmpty) {
|
||||
return (
|
||||
<p className="no-results-message with-large-bottom-margin">
|
||||
{textToShowIfEmpty}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
import {
|
||||
ComboBox,
|
||||
Stack,
|
||||
FormLabel,
|
||||
// @ts-ignore
|
||||
} from '@carbon/react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { truncateString } from '../helpers';
|
||||
import { ProcessInstanceReport } from '../interfaces';
|
||||
import HttpService from '../services/HttpService';
|
||||
|
||||
type OwnProps = {
|
||||
onChange: (..._args: any[]) => any;
|
||||
selectedItem?: ProcessInstanceReport | null;
|
||||
titleText?: string;
|
||||
};
|
||||
|
||||
export default function ProcessInstanceReportSearch({
|
||||
selectedItem,
|
||||
onChange,
|
||||
titleText = 'Process instance perspectives',
|
||||
}: OwnProps) {
|
||||
const [processInstanceReports, setProcessInstanceReports] = useState<
|
||||
ProcessInstanceReport[] | null
|
||||
>(null);
|
||||
|
||||
const [searchParams] = useSearchParams();
|
||||
const reportId = searchParams.get('report_id');
|
||||
|
||||
useEffect(() => {
|
||||
function setProcessInstanceReportsFromResult(
|
||||
result: ProcessInstanceReport[]
|
||||
) {
|
||||
setProcessInstanceReports(result);
|
||||
}
|
||||
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-instances/reports`,
|
||||
successCallback: setProcessInstanceReportsFromResult,
|
||||
});
|
||||
}, [reportId]);
|
||||
|
||||
const reportSelectionString = (
|
||||
processInstanceReport: ProcessInstanceReport
|
||||
) => {
|
||||
return `${truncateString(processInstanceReport.identifier, 20)} (Id: ${
|
||||
processInstanceReport.id
|
||||
})`;
|
||||
};
|
||||
|
||||
const shouldFilterProcessInstanceReport = (options: any) => {
|
||||
const processInstanceReport: ProcessInstanceReport = options.item;
|
||||
const { inputValue } = options;
|
||||
return reportSelectionString(processInstanceReport).includes(inputValue);
|
||||
};
|
||||
|
||||
const reportsAvailable = () => {
|
||||
return processInstanceReports && processInstanceReports.length > 0;
|
||||
};
|
||||
|
||||
if (reportsAvailable()) {
|
||||
return (
|
||||
<Stack orientation="horizontal" gap={2}>
|
||||
<FormLabel className="with-top-margin">{titleText}</FormLabel>
|
||||
<ComboBox
|
||||
onChange={onChange}
|
||||
id="process-instance-report-select"
|
||||
data-qa="process-instance-report-selection"
|
||||
items={processInstanceReports}
|
||||
itemToString={(processInstanceReport: ProcessInstanceReport) => {
|
||||
if (processInstanceReport) {
|
||||
return reportSelectionString(processInstanceReport);
|
||||
}
|
||||
return null;
|
||||
}}
|
||||
shouldFilterItem={shouldFilterProcessInstanceReport}
|
||||
placeholder="Choose a process instance perspective"
|
||||
selectedItem={selectedItem}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
|
@ -4,25 +4,95 @@ import {
|
|||
Button,
|
||||
// @ts-ignore
|
||||
} from '@carbon/react';
|
||||
import { ProcessModel } from '../interfaces';
|
||||
import { Can } from '@casl/react';
|
||||
import {
|
||||
PermissionsToCheck,
|
||||
ProcessModel,
|
||||
RecentProcessModel,
|
||||
} from '../interfaces';
|
||||
import HttpService from '../services/HttpService';
|
||||
import ErrorContext from '../contexts/ErrorContext';
|
||||
import { modifyProcessModelPath } from '../helpers';
|
||||
import { modifyProcessIdentifierForPathParam } from '../helpers';
|
||||
import { usePermissionFetcher } from '../hooks/PermissionService';
|
||||
|
||||
const storeRecentProcessModelInLocalStorage = (
|
||||
processModelForStorage: ProcessModel
|
||||
) => {
|
||||
// All values stored in localStorage are strings.
|
||||
// Grab our recentProcessModels string from localStorage.
|
||||
const stringFromLocalStorage = window.localStorage.getItem(
|
||||
'recentProcessModels'
|
||||
);
|
||||
|
||||
// adapted from https://stackoverflow.com/a/59424458/6090676
|
||||
// If that value is null (meaning that we've never saved anything to that spot in localStorage before), use an empty array as our array. Otherwise, use the value we parse out.
|
||||
let array: RecentProcessModel[] = [];
|
||||
if (stringFromLocalStorage !== null) {
|
||||
// Then parse that string into an actual value.
|
||||
array = JSON.parse(stringFromLocalStorage);
|
||||
}
|
||||
|
||||
// Here's the value we want to add
|
||||
const value = {
|
||||
processModelIdentifier: processModelForStorage.id,
|
||||
processModelDisplayName: processModelForStorage.display_name,
|
||||
};
|
||||
|
||||
// anything with a processGroupIdentifier is old and busted. leave it behind.
|
||||
array = array.filter((item) => item.processGroupIdentifier === undefined);
|
||||
|
||||
// If our parsed/empty array doesn't already have this value in it...
|
||||
const matchingItem = array.find(
|
||||
(item) => item.processModelIdentifier === value.processModelIdentifier
|
||||
);
|
||||
if (matchingItem === undefined) {
|
||||
// add the value to the beginning of the array
|
||||
array.unshift(value);
|
||||
|
||||
// Keep the array to 3 items
|
||||
if (array.length > 3) {
|
||||
array.pop();
|
||||
}
|
||||
}
|
||||
|
||||
// once the old and busted serializations are gone, we can put these two statements inside the above if statement
|
||||
|
||||
// turn the array WITH THE NEW VALUE IN IT into a string to prepare it to be stored in localStorage
|
||||
const stringRepresentingArray = JSON.stringify(array);
|
||||
|
||||
// and store it in localStorage as "recentProcessModels"
|
||||
window.localStorage.setItem('recentProcessModels', stringRepresentingArray);
|
||||
};
|
||||
|
||||
type OwnProps = {
|
||||
processModel: ProcessModel;
|
||||
onSuccessCallback: Function;
|
||||
className?: string;
|
||||
checkPermissions?: boolean;
|
||||
};
|
||||
|
||||
export default function ProcessInstanceRun({
|
||||
processModel,
|
||||
onSuccessCallback,
|
||||
className,
|
||||
checkPermissions = true,
|
||||
}: OwnProps) {
|
||||
const navigate = useNavigate();
|
||||
const setErrorMessage = (useContext as any)(ErrorContext)[1];
|
||||
const modifiedProcessModelId = modifyProcessModelPath(processModel.id);
|
||||
const modifiedProcessModelId = modifyProcessIdentifierForPathParam(
|
||||
processModel.id
|
||||
);
|
||||
|
||||
const processInstanceCreatePath = `/v1.0/process-instances/${modifiedProcessModelId}`;
|
||||
let permissionRequestData: PermissionsToCheck = {
|
||||
[processInstanceCreatePath]: ['POST'],
|
||||
};
|
||||
|
||||
if (!checkPermissions) {
|
||||
permissionRequestData = {};
|
||||
}
|
||||
|
||||
const { ability } = usePermissionFetcher(permissionRequestData);
|
||||
|
||||
const onProcessInstanceRun = (processInstance: any) => {
|
||||
// FIXME: ensure that the task is actually for the current user as well
|
||||
|
@ -36,8 +106,9 @@ export default function ProcessInstanceRun({
|
|||
|
||||
const processModelRun = (processInstance: any) => {
|
||||
setErrorMessage(null);
|
||||
storeRecentProcessModelInLocalStorage(processModel);
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-instances/${processInstance.id}/run`,
|
||||
path: `/process-instances/${modifiedProcessModelId}/${processInstance.id}/run`,
|
||||
successCallback: onProcessInstanceRun,
|
||||
failureCallback: setErrorMessage,
|
||||
httpMethod: 'POST',
|
||||
|
@ -46,19 +117,23 @@ export default function ProcessInstanceRun({
|
|||
|
||||
const processInstanceCreateAndRun = () => {
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-models/${modifiedProcessModelId}/process-instances`,
|
||||
path: processInstanceCreatePath,
|
||||
successCallback: processModelRun,
|
||||
httpMethod: 'POST',
|
||||
});
|
||||
};
|
||||
|
||||
if (checkPermissions) {
|
||||
return (
|
||||
<Can I="POST" a={processInstanceCreatePath} ability={ability}>
|
||||
<Button onClick={processInstanceCreateAndRun} className={className}>
|
||||
Start
|
||||
</Button>
|
||||
</Can>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Button
|
||||
onClick={processInstanceCreateAndRun}
|
||||
variant="primary"
|
||||
className={className}
|
||||
>
|
||||
Run
|
||||
<Button onClick={processInstanceCreateAndRun} className={className}>
|
||||
Start
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,10 +1,20 @@
|
|||
import { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
Button,
|
||||
ButtonSet,
|
||||
Form,
|
||||
Stack,
|
||||
TextInput,
|
||||
Grid,
|
||||
Column,
|
||||
// @ts-ignore
|
||||
} from '@carbon/react';
|
||||
// @ts-ignore
|
||||
import { Button, ButtonSet, Form, Stack, TextInput } from '@carbon/react';
|
||||
import { modifyProcessModelPath, slugifyString } from '../helpers';
|
||||
import { AddAlt, TrashCan } from '@carbon/icons-react';
|
||||
import { modifyProcessIdentifierForPathParam, slugifyString } from '../helpers';
|
||||
import HttpService from '../services/HttpService';
|
||||
import { ProcessModel } from '../interfaces';
|
||||
import { MetadataExtractionPath, ProcessModel } from '../interfaces';
|
||||
|
||||
type OwnProps = {
|
||||
mode: string;
|
||||
|
@ -23,13 +33,13 @@ export default function ProcessModelForm({
|
|||
const [idHasBeenUpdatedByUser, setIdHasBeenUpdatedByUser] =
|
||||
useState<boolean>(false);
|
||||
const [displayNameInvalid, setDisplayNameInvalid] = useState<boolean>(false);
|
||||
useState<boolean>(false);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const navigateToProcessModel = (result: ProcessModel) => {
|
||||
if ('id' in result) {
|
||||
const modifiedProcessModelPathFromResult = modifyProcessModelPath(
|
||||
result.id
|
||||
);
|
||||
const modifiedProcessModelPathFromResult =
|
||||
modifyProcessIdentifierForPathParam(result.id);
|
||||
navigate(`/admin/process-models/${modifiedProcessModelPathFromResult}`);
|
||||
}
|
||||
};
|
||||
|
@ -52,14 +62,20 @@ export default function ProcessModelForm({
|
|||
if (hasErrors) {
|
||||
return;
|
||||
}
|
||||
const path = `/process-models/${processGroupId}`;
|
||||
let path = `/process-models/${modifyProcessIdentifierForPathParam(
|
||||
processGroupId || ''
|
||||
)}`;
|
||||
let httpMethod = 'POST';
|
||||
if (mode === 'edit') {
|
||||
httpMethod = 'PUT';
|
||||
path = `/process-models/${modifyProcessIdentifierForPathParam(
|
||||
processModel.id
|
||||
)}`;
|
||||
}
|
||||
const postBody = {
|
||||
display_name: processModel.display_name,
|
||||
description: processModel.description,
|
||||
metadata_extraction_paths: processModel.metadata_extraction_paths,
|
||||
};
|
||||
if (mode === 'new') {
|
||||
Object.assign(postBody, {
|
||||
|
@ -83,6 +99,80 @@ export default function ProcessModelForm({
|
|||
setProcessModel(processModelToCopy);
|
||||
};
|
||||
|
||||
const metadataExtractionPathForm = (
|
||||
index: number,
|
||||
metadataExtractionPath: MetadataExtractionPath
|
||||
) => {
|
||||
return (
|
||||
<Grid>
|
||||
<Column md={3} lg={7} sm={1}>
|
||||
<TextInput
|
||||
id={`process-model-metadata-extraction-path-key-${index}`}
|
||||
labelText="Extraction Key"
|
||||
value={metadataExtractionPath.key}
|
||||
onChange={(event: any) => {
|
||||
const cep: MetadataExtractionPath[] =
|
||||
processModel.metadata_extraction_paths || [];
|
||||
const newMeta = { ...metadataExtractionPath };
|
||||
newMeta.key = event.target.value;
|
||||
cep[index] = newMeta;
|
||||
updateProcessModel({ metadata_extraction_paths: cep });
|
||||
}}
|
||||
/>
|
||||
</Column>
|
||||
<Column md={4} lg={8} sm={2}>
|
||||
<TextInput
|
||||
id={`process-model-metadata-extraction-path-${index}`}
|
||||
labelText="Extraction Path"
|
||||
value={metadataExtractionPath.path}
|
||||
onChange={(event: any) => {
|
||||
const cep: MetadataExtractionPath[] =
|
||||
processModel.metadata_extraction_paths || [];
|
||||
const newMeta = { ...metadataExtractionPath };
|
||||
newMeta.path = event.target.value;
|
||||
cep[index] = newMeta;
|
||||
updateProcessModel({ metadata_extraction_paths: cep });
|
||||
}}
|
||||
/>
|
||||
</Column>
|
||||
<Column md={1} lg={1} sm={1}>
|
||||
<Button
|
||||
kind="ghost"
|
||||
renderIcon={TrashCan}
|
||||
iconDescription="Remove Key"
|
||||
hasIconOnly
|
||||
size="lg"
|
||||
className="with-extra-top-margin"
|
||||
onClick={() => {
|
||||
const cep: MetadataExtractionPath[] =
|
||||
processModel.metadata_extraction_paths || [];
|
||||
cep.splice(index, 1);
|
||||
updateProcessModel({ metadata_extraction_paths: cep });
|
||||
}}
|
||||
/>
|
||||
</Column>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
const metadataExtractionPathFormArea = () => {
|
||||
if (processModel.metadata_extraction_paths) {
|
||||
return processModel.metadata_extraction_paths.map(
|
||||
(metadataExtractionPath: MetadataExtractionPath, index: number) => {
|
||||
return metadataExtractionPathForm(index, metadataExtractionPath);
|
||||
}
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const addBlankMetadataExtractionPath = () => {
|
||||
const cep: MetadataExtractionPath[] =
|
||||
processModel.metadata_extraction_paths || [];
|
||||
cep.push({ key: '', path: '' });
|
||||
updateProcessModel({ metadata_extraction_paths: cep });
|
||||
};
|
||||
|
||||
const onDisplayNameChanged = (newDisplayName: any) => {
|
||||
setDisplayNameInvalid(false);
|
||||
const updateDict = { display_name: newDisplayName };
|
||||
|
@ -104,7 +194,6 @@ export default function ProcessModelForm({
|
|||
onChange={(event: any) => {
|
||||
onDisplayNameChanged(event.target.value);
|
||||
}}
|
||||
onBlur={(event: any) => console.log('event', event)}
|
||||
/>,
|
||||
];
|
||||
|
||||
|
@ -141,6 +230,38 @@ export default function ProcessModelForm({
|
|||
/>
|
||||
);
|
||||
|
||||
textInputs.push(<h2>Metadata Extractions</h2>);
|
||||
textInputs.push(
|
||||
<Grid>
|
||||
<Column md={8} lg={16} sm={4}>
|
||||
<p className="data-table-description">
|
||||
You can provide one or more metadata extractions to pull data from
|
||||
your process instances to provide quick access in searches and
|
||||
perspectives.
|
||||
</p>
|
||||
</Column>
|
||||
</Grid>
|
||||
);
|
||||
textInputs.push(<>{metadataExtractionPathFormArea()}</>);
|
||||
textInputs.push(
|
||||
<Grid>
|
||||
<Column md={4} lg={8} sm={2}>
|
||||
<Button
|
||||
data-qa="add-metadata-extraction-path-button"
|
||||
renderIcon={AddAlt}
|
||||
className="button-white-background"
|
||||
kind=""
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
addBlankMetadataExtractionPath();
|
||||
}}
|
||||
>
|
||||
Add Metadata Extraction Path
|
||||
</Button>
|
||||
</Column>
|
||||
</Grid>
|
||||
);
|
||||
|
||||
return textInputs;
|
||||
};
|
||||
|
||||
|
|
|
@ -5,15 +5,24 @@ import {
|
|||
// @ts-ignore
|
||||
} from '@carbon/react';
|
||||
import HttpService from '../services/HttpService';
|
||||
import { ProcessModel, ProcessInstance } from '../interfaces';
|
||||
import { modifyProcessModelPath, truncateString } from '../helpers';
|
||||
import { ProcessModel, ProcessInstance, ProcessGroup } from '../interfaces';
|
||||
import {
|
||||
modifyProcessIdentifierForPathParam,
|
||||
truncateString,
|
||||
} from '../helpers';
|
||||
import ProcessInstanceRun from './ProcessInstanceRun';
|
||||
|
||||
type OwnProps = {
|
||||
headerElement?: ReactElement;
|
||||
processGroup?: ProcessGroup;
|
||||
checkPermissions?: boolean;
|
||||
};
|
||||
|
||||
export default function ProcessModelListTiles({ headerElement }: OwnProps) {
|
||||
export default function ProcessModelListTiles({
|
||||
headerElement,
|
||||
processGroup,
|
||||
checkPermissions = true,
|
||||
}: OwnProps) {
|
||||
const [searchParams] = useSearchParams();
|
||||
const [processModels, setProcessModels] = useState<ProcessModel[] | null>(
|
||||
null
|
||||
|
@ -25,13 +34,18 @@ export default function ProcessModelListTiles({ headerElement }: OwnProps) {
|
|||
const setProcessModelsFromResult = (result: any) => {
|
||||
setProcessModels(result.results);
|
||||
};
|
||||
// only allow 10 for now until we get the backend only returnin certain models for user execution
|
||||
const queryParams = '?per_page=10';
|
||||
// only allow 10 for now until we get the backend only returning certain models for user execution
|
||||
let queryParams = '?per_page=20';
|
||||
if (processGroup) {
|
||||
queryParams = `${queryParams}&process_group_identifier=${processGroup.id}`;
|
||||
} else {
|
||||
queryParams = `${queryParams}&recursive=true&filter_runnable_by_user=true`;
|
||||
}
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-models${queryParams}`,
|
||||
successCallback: setProcessModelsFromResult,
|
||||
});
|
||||
}, [searchParams]);
|
||||
}, [searchParams, processGroup]);
|
||||
|
||||
const processInstanceRunResultTag = () => {
|
||||
if (processInstance) {
|
||||
|
@ -40,9 +54,9 @@ export default function ProcessModelListTiles({ headerElement }: OwnProps) {
|
|||
<p>
|
||||
Process Instance {processInstance.id} kicked off (
|
||||
<Link
|
||||
to={`/admin/process-models/${modifyProcessModelPath(
|
||||
to={`/admin/process-instances/${modifyProcessIdentifierForPathParam(
|
||||
processInstance.process_model_identifier
|
||||
)}/process-instances/${processInstance.id}`}
|
||||
)}/${processInstance.id}`}
|
||||
data-qa="process-instance-show-link"
|
||||
>
|
||||
view
|
||||
|
@ -61,19 +75,29 @@ export default function ProcessModelListTiles({ headerElement }: OwnProps) {
|
|||
displayText = (processModels || []).map((row: ProcessModel) => {
|
||||
return (
|
||||
<Tile
|
||||
id="tile-1"
|
||||
id={`process-model-tile-${row.id}`}
|
||||
className="tile-process-group"
|
||||
href={`/admin/process-models/${modifyProcessModelPath(row.id)}`}
|
||||
>
|
||||
<div className="tile-process-group-content-container">
|
||||
<div className="tile-title-top">{row.display_name}</div>
|
||||
<div className="tile-title-top">
|
||||
<a
|
||||
title={row.id}
|
||||
data-qa="process-model-show-link"
|
||||
href={`/admin/process-models/${modifyProcessIdentifierForPathParam(
|
||||
row.id
|
||||
)}`}
|
||||
>
|
||||
{row.display_name}
|
||||
</a>
|
||||
</div>
|
||||
<p className="tile-description">
|
||||
{truncateString(row.description || '', 25)}
|
||||
{truncateString(row.description || '', 100)}
|
||||
</p>
|
||||
<ProcessInstanceRun
|
||||
processModel={row}
|
||||
onSuccessCallback={setProcessInstance}
|
||||
className="tile-pin-bottom"
|
||||
checkPermissions={checkPermissions}
|
||||
/>
|
||||
</div>
|
||||
</Tile>
|
||||
|
|
|
@ -35,7 +35,7 @@ export default function ProcessModelSearch({
|
|||
if (processModel) {
|
||||
return `${processModel.id} (${truncateString(
|
||||
processModel.display_name,
|
||||
20
|
||||
75
|
||||
)})`;
|
||||
}
|
||||
return null;
|
||||
|
|
|
@ -41,7 +41,7 @@ export default function ProcessSearch({
|
|||
if (process) {
|
||||
return `${process.display_name} (${truncateString(
|
||||
process.identifier,
|
||||
20
|
||||
75
|
||||
)})`;
|
||||
}
|
||||
return null;
|
||||
|
|
|
@ -429,7 +429,7 @@ export default function ReactDiagramEditor({
|
|||
fetch(urlToUse)
|
||||
.then((response) => response.text())
|
||||
.then((text) => {
|
||||
const processId = `Proccess_${makeid(7)}`;
|
||||
const processId = `Process_${makeid(7)}`;
|
||||
const newText = text.replace('{{PROCESS_ID}}', processId);
|
||||
setDiagramXMLString(newText);
|
||||
})
|
||||
|
@ -569,7 +569,7 @@ export default function ReactDiagramEditor({
|
|||
a={targetUris.processModelFileShowPath}
|
||||
ability={ability}
|
||||
>
|
||||
<Button onClick={downloadXmlFile}>Download xml</Button>
|
||||
<Button onClick={downloadXmlFile}>Download</Button>
|
||||
</Can>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
// @ts-ignore
|
||||
import { TimeAgo } from '../helpers/timeago';
|
||||
import { convertSecondsToFormattedDateTime } from '../helpers';
|
||||
|
||||
type OwnProps = {
|
||||
timeInSeconds: number;
|
||||
};
|
||||
|
||||
export default function TableCellWithTimeAgoInWords({
|
||||
timeInSeconds,
|
||||
}: OwnProps) {
|
||||
return (
|
||||
<td title={convertSecondsToFormattedDateTime(timeInSeconds) || '-'}>
|
||||
{timeInSeconds ? TimeAgo.inWords(timeInSeconds) : '-'}
|
||||
</td>
|
||||
);
|
||||
}
|
|
@ -6,13 +6,17 @@ import PaginationForTable from './PaginationForTable';
|
|||
import {
|
||||
convertSecondsToFormattedDateTime,
|
||||
getPageInfoFromSearchParams,
|
||||
modifyProcessModelPath,
|
||||
modifyProcessIdentifierForPathParam,
|
||||
refreshAtInterval,
|
||||
} from '../helpers';
|
||||
import HttpService from '../services/HttpService';
|
||||
import { PaginationObject } from '../interfaces';
|
||||
import TableCellWithTimeAgoInWords from './TableCellWithTimeAgoInWords';
|
||||
|
||||
const PER_PAGE_FOR_TASKS_ON_HOME_PAGE = 5;
|
||||
const paginationQueryParamPrefix = 'tasks_for_my_open_processes';
|
||||
const REFRESH_INTERVAL = 5;
|
||||
const REFRESH_TIMEOUT = 600;
|
||||
|
||||
export default function MyOpenProcesses() {
|
||||
const [searchParams] = useSearchParams();
|
||||
|
@ -20,45 +24,50 @@ export default function MyOpenProcesses() {
|
|||
const [pagination, setPagination] = useState<PaginationObject | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const { page, perPage } = getPageInfoFromSearchParams(
|
||||
searchParams,
|
||||
PER_PAGE_FOR_TASKS_ON_HOME_PAGE,
|
||||
undefined,
|
||||
paginationQueryParamPrefix
|
||||
);
|
||||
const setTasksFromResult = (result: any) => {
|
||||
setTasks(result.results);
|
||||
setPagination(result.pagination);
|
||||
const getTasks = () => {
|
||||
const { page, perPage } = getPageInfoFromSearchParams(
|
||||
searchParams,
|
||||
PER_PAGE_FOR_TASKS_ON_HOME_PAGE,
|
||||
undefined,
|
||||
paginationQueryParamPrefix
|
||||
);
|
||||
const setTasksFromResult = (result: any) => {
|
||||
setTasks(result.results);
|
||||
setPagination(result.pagination);
|
||||
};
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/tasks/for-my-open-processes?per_page=${perPage}&page=${page}`,
|
||||
successCallback: setTasksFromResult,
|
||||
});
|
||||
};
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/tasks/for-my-open-processes?per_page=${perPage}&page=${page}`,
|
||||
successCallback: setTasksFromResult,
|
||||
});
|
||||
getTasks();
|
||||
refreshAtInterval(REFRESH_INTERVAL, REFRESH_TIMEOUT, getTasks);
|
||||
}, [searchParams]);
|
||||
|
||||
const buildTable = () => {
|
||||
const rows = tasks.map((row) => {
|
||||
const rowToUse = row as any;
|
||||
const taskUrl = `/tasks/${rowToUse.process_instance_id}/${rowToUse.task_id}`;
|
||||
const modifiedProcessModelIdentifier = modifyProcessModelPath(
|
||||
rowToUse.process_model_identifier
|
||||
);
|
||||
const modifiedProcessModelIdentifier =
|
||||
modifyProcessIdentifierForPathParam(rowToUse.process_model_identifier);
|
||||
return (
|
||||
<tr key={rowToUse.id}>
|
||||
<td>
|
||||
<Link
|
||||
data-qa="process-model-show-link"
|
||||
to={`/admin/process-models/${modifiedProcessModelIdentifier}`}
|
||||
data-qa="process-instance-show-link"
|
||||
to={`/admin/process-instances/${modifiedProcessModelIdentifier}/${rowToUse.process_instance_id}`}
|
||||
title={`View process instance ${rowToUse.process_instance_id}`}
|
||||
>
|
||||
{rowToUse.process_model_display_name}
|
||||
{rowToUse.process_instance_id}
|
||||
</Link>
|
||||
</td>
|
||||
<td>
|
||||
<Link
|
||||
data-qa="process-instance-show-link"
|
||||
to={`/admin/process-models/${modifiedProcessModelIdentifier}/process-instances/${rowToUse.process_instance_id}`}
|
||||
data-qa="process-model-show-link"
|
||||
to={`/admin/process-models/${modifiedProcessModelIdentifier}`}
|
||||
title={rowToUse.process_model_identifier}
|
||||
>
|
||||
View {rowToUse.process_instance_id}
|
||||
{rowToUse.process_model_display_name}
|
||||
</Link>
|
||||
</td>
|
||||
<td
|
||||
|
@ -66,18 +75,15 @@ export default function MyOpenProcesses() {
|
|||
>
|
||||
{rowToUse.task_title}
|
||||
</td>
|
||||
<td>{rowToUse.process_instance_status}</td>
|
||||
<td>{rowToUse.group_identifier || '-'}</td>
|
||||
<td>
|
||||
{convertSecondsToFormattedDateTime(
|
||||
rowToUse.created_at_in_seconds
|
||||
) || '-'}
|
||||
</td>
|
||||
<td>
|
||||
{convertSecondsToFormattedDateTime(
|
||||
rowToUse.updated_at_in_seconds
|
||||
) || '-'}
|
||||
</td>
|
||||
<TableCellWithTimeAgoInWords
|
||||
timeInSeconds={rowToUse.updated_at_in_seconds}
|
||||
/>
|
||||
<td>
|
||||
<Button
|
||||
variant="primary"
|
||||
|
@ -95,13 +101,12 @@ export default function MyOpenProcesses() {
|
|||
<Table striped bordered>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Process Model</th>
|
||||
<th>Process Instance</th>
|
||||
<th>Task Name</th>
|
||||
<th>Process Instance Status</th>
|
||||
<th>Assigned Group</th>
|
||||
<th>Process Started</th>
|
||||
<th>Process Updated</th>
|
||||
<th>Id</th>
|
||||
<th>Process</th>
|
||||
<th>Task</th>
|
||||
<th>Waiting For</th>
|
||||
<th>Date Started</th>
|
||||
<th>Last Updated</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -112,7 +117,11 @@ export default function MyOpenProcesses() {
|
|||
|
||||
const tasksComponent = () => {
|
||||
if (pagination && pagination.total < 1) {
|
||||
return null;
|
||||
return (
|
||||
<p className="no-results-message with-large-bottom-margin">
|
||||
There are no tasks for processes you started at this time.
|
||||
</p>
|
||||
);
|
||||
}
|
||||
const { page, perPage } = getPageInfoFromSearchParams(
|
||||
searchParams,
|
||||
|
@ -121,22 +130,27 @@ export default function MyOpenProcesses() {
|
|||
paginationQueryParamPrefix
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<h1>Tasks for my open processes</h1>
|
||||
<PaginationForTable
|
||||
page={page}
|
||||
perPage={perPage}
|
||||
perPageOptions={[2, PER_PAGE_FOR_TASKS_ON_HOME_PAGE, 25]}
|
||||
pagination={pagination}
|
||||
tableToDisplay={buildTable()}
|
||||
paginationQueryParamPrefix={paginationQueryParamPrefix}
|
||||
/>
|
||||
</>
|
||||
<PaginationForTable
|
||||
page={page}
|
||||
perPage={perPage}
|
||||
perPageOptions={[2, PER_PAGE_FOR_TASKS_ON_HOME_PAGE, 25]}
|
||||
pagination={pagination}
|
||||
tableToDisplay={buildTable()}
|
||||
paginationQueryParamPrefix={paginationQueryParamPrefix}
|
||||
paginationClassName="with-large-bottom-margin"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
if (pagination) {
|
||||
return tasksComponent();
|
||||
}
|
||||
return null;
|
||||
return (
|
||||
<>
|
||||
<h2>My open instances</h2>
|
||||
<p className="data-table-description">
|
||||
These tasks are for processes you started which are not complete. You
|
||||
may not have an action to take at this time. See below for tasks waiting
|
||||
on you.
|
||||
</p>
|
||||
{tasksComponent()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -6,10 +6,11 @@ import PaginationForTable from './PaginationForTable';
|
|||
import {
|
||||
convertSecondsToFormattedDateTime,
|
||||
getPageInfoFromSearchParams,
|
||||
modifyProcessModelPath,
|
||||
modifyProcessIdentifierForPathParam,
|
||||
} from '../helpers';
|
||||
import HttpService from '../services/HttpService';
|
||||
import { PaginationObject } from '../interfaces';
|
||||
import TableCellWithTimeAgoInWords from './TableCellWithTimeAgoInWords';
|
||||
|
||||
const PER_PAGE_FOR_TASKS_ON_HOME_PAGE = 5;
|
||||
|
||||
|
@ -39,25 +40,26 @@ export default function TasksWaitingForMe() {
|
|||
const rows = tasks.map((row) => {
|
||||
const rowToUse = row as any;
|
||||
const taskUrl = `/tasks/${rowToUse.process_instance_id}/${rowToUse.task_id}`;
|
||||
const modifiedProcessModelIdentifier = modifyProcessModelPath(
|
||||
rowToUse.process_model_identifier
|
||||
);
|
||||
const modifiedProcessModelIdentifier =
|
||||
modifyProcessIdentifierForPathParam(rowToUse.process_model_identifier);
|
||||
return (
|
||||
<tr key={rowToUse.id}>
|
||||
<td>
|
||||
<Link
|
||||
data-qa="process-model-show-link"
|
||||
to={`/admin/process-models/${modifiedProcessModelIdentifier}`}
|
||||
data-qa="process-instance-show-link"
|
||||
to={`/admin/${modifiedProcessModelIdentifier}/${rowToUse.process_instance_id}`}
|
||||
title={`View process instance ${rowToUse.process_instance_id}`}
|
||||
>
|
||||
{rowToUse.process_model_display_name}
|
||||
{rowToUse.process_instance_id}
|
||||
</Link>
|
||||
</td>
|
||||
<td>
|
||||
<Link
|
||||
data-qa="process-instance-show-link"
|
||||
to={`/admin/process-models/${modifiedProcessModelIdentifier}/process-instances/${rowToUse.process_instance_id}`}
|
||||
data-qa="process-model-show-link"
|
||||
to={`/admin/process-models/${modifiedProcessModelIdentifier}`}
|
||||
title={rowToUse.process_model_identifier}
|
||||
>
|
||||
View {rowToUse.process_instance_id}
|
||||
{rowToUse.process_model_display_name}
|
||||
</Link>
|
||||
</td>
|
||||
<td
|
||||
|
@ -66,18 +68,15 @@ export default function TasksWaitingForMe() {
|
|||
{rowToUse.task_title}
|
||||
</td>
|
||||
<td>{rowToUse.username}</td>
|
||||
<td>{rowToUse.process_instance_status}</td>
|
||||
<td>{rowToUse.group_identifier || '-'}</td>
|
||||
<td>
|
||||
{convertSecondsToFormattedDateTime(
|
||||
rowToUse.created_at_in_seconds
|
||||
) || '-'}
|
||||
</td>
|
||||
<td>
|
||||
{convertSecondsToFormattedDateTime(
|
||||
rowToUse.updated_at_in_seconds
|
||||
) || '-'}
|
||||
</td>
|
||||
<TableCellWithTimeAgoInWords
|
||||
timeInSeconds={rowToUse.updated_at_in_seconds}
|
||||
/>
|
||||
<td>
|
||||
<Button
|
||||
variant="primary"
|
||||
|
@ -95,14 +94,13 @@ export default function TasksWaitingForMe() {
|
|||
<Table striped bordered>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Process Model</th>
|
||||
<th>Process Instance</th>
|
||||
<th>Task Name</th>
|
||||
<th>Process Started By</th>
|
||||
<th>Process Instance Status</th>
|
||||
<th>Assigned Group</th>
|
||||
<th>Process Started</th>
|
||||
<th>Process Updated</th>
|
||||
<th>Id</th>
|
||||
<th>Process</th>
|
||||
<th>Task</th>
|
||||
<th>Started By</th>
|
||||
<th>Waiting For</th>
|
||||
<th>Date Started</th>
|
||||
<th>Last Updated</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -113,7 +111,11 @@ export default function TasksWaitingForMe() {
|
|||
|
||||
const tasksComponent = () => {
|
||||
if (pagination && pagination.total < 1) {
|
||||
return null;
|
||||
return (
|
||||
<p className="no-results-message with-large-bottom-margin">
|
||||
You have no task assignments at this time.
|
||||
</p>
|
||||
);
|
||||
}
|
||||
const { page, perPage } = getPageInfoFromSearchParams(
|
||||
searchParams,
|
||||
|
@ -122,22 +124,26 @@ export default function TasksWaitingForMe() {
|
|||
'tasks_waiting_for_me'
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<h1>Tasks waiting for me</h1>
|
||||
<PaginationForTable
|
||||
page={page}
|
||||
perPage={perPage}
|
||||
perPageOptions={[2, PER_PAGE_FOR_TASKS_ON_HOME_PAGE, 25]}
|
||||
pagination={pagination}
|
||||
tableToDisplay={buildTable()}
|
||||
paginationQueryParamPrefix="tasks_waiting_for_me"
|
||||
/>
|
||||
</>
|
||||
<PaginationForTable
|
||||
page={page}
|
||||
perPage={perPage}
|
||||
perPageOptions={[2, PER_PAGE_FOR_TASKS_ON_HOME_PAGE, 25]}
|
||||
pagination={pagination}
|
||||
tableToDisplay={buildTable()}
|
||||
paginationQueryParamPrefix="tasks_waiting_for_me"
|
||||
paginationClassName="with-large-bottom-margin"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
if (pagination) {
|
||||
return tasksComponent();
|
||||
}
|
||||
return null;
|
||||
return (
|
||||
<>
|
||||
<h2>Tasks waiting for me</h2>
|
||||
<p className="data-table-description">
|
||||
These processes are waiting on you to complete the next task. All are
|
||||
processes created by others that are now actionable by you.
|
||||
</p>
|
||||
{tasksComponent()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -6,59 +6,68 @@ import PaginationForTable from './PaginationForTable';
|
|||
import {
|
||||
convertSecondsToFormattedDateTime,
|
||||
getPageInfoFromSearchParams,
|
||||
modifyProcessModelPath,
|
||||
modifyProcessIdentifierForPathParam,
|
||||
refreshAtInterval,
|
||||
} from '../helpers';
|
||||
import HttpService from '../services/HttpService';
|
||||
import { PaginationObject } from '../interfaces';
|
||||
import TableCellWithTimeAgoInWords from './TableCellWithTimeAgoInWords';
|
||||
|
||||
const PER_PAGE_FOR_TASKS_ON_HOME_PAGE = 5;
|
||||
const paginationQueryParamPrefix = 'tasks_waiting_for_my_groups';
|
||||
const REFRESH_INTERVAL = 5;
|
||||
const REFRESH_TIMEOUT = 600;
|
||||
|
||||
export default function TasksForWaitingForMyGroups() {
|
||||
export default function TasksWaitingForMyGroups() {
|
||||
const [searchParams] = useSearchParams();
|
||||
const [tasks, setTasks] = useState([]);
|
||||
const [pagination, setPagination] = useState<PaginationObject | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const { page, perPage } = getPageInfoFromSearchParams(
|
||||
searchParams,
|
||||
PER_PAGE_FOR_TASKS_ON_HOME_PAGE,
|
||||
undefined,
|
||||
paginationQueryParamPrefix
|
||||
);
|
||||
const setTasksFromResult = (result: any) => {
|
||||
setTasks(result.results);
|
||||
setPagination(result.pagination);
|
||||
const getTasks = () => {
|
||||
const { page, perPage } = getPageInfoFromSearchParams(
|
||||
searchParams,
|
||||
PER_PAGE_FOR_TASKS_ON_HOME_PAGE,
|
||||
undefined,
|
||||
paginationQueryParamPrefix
|
||||
);
|
||||
const setTasksFromResult = (result: any) => {
|
||||
setTasks(result.results);
|
||||
setPagination(result.pagination);
|
||||
};
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/tasks/for-my-groups?per_page=${perPage}&page=${page}`,
|
||||
successCallback: setTasksFromResult,
|
||||
});
|
||||
};
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/tasks/for-my-groups?per_page=${perPage}&page=${page}`,
|
||||
successCallback: setTasksFromResult,
|
||||
});
|
||||
getTasks();
|
||||
refreshAtInterval(REFRESH_INTERVAL, REFRESH_TIMEOUT, getTasks);
|
||||
}, [searchParams]);
|
||||
|
||||
const buildTable = () => {
|
||||
const rows = tasks.map((row) => {
|
||||
const rowToUse = row as any;
|
||||
const taskUrl = `/tasks/${rowToUse.process_instance_id}/${rowToUse.task_id}`;
|
||||
const modifiedProcessModelIdentifier = modifyProcessModelPath(
|
||||
rowToUse.process_model_identifier
|
||||
);
|
||||
const modifiedProcessModelIdentifier =
|
||||
modifyProcessIdentifierForPathParam(rowToUse.process_model_identifier);
|
||||
return (
|
||||
<tr key={rowToUse.id}>
|
||||
<td>
|
||||
<Link
|
||||
data-qa="process-model-show-link"
|
||||
to={`/admin/process-models/${modifiedProcessModelIdentifier}`}
|
||||
data-qa="process-instance-show-link"
|
||||
to={`/admin/process-instances/${modifiedProcessModelIdentifier}/${rowToUse.process_instance_id}`}
|
||||
title={`View process instance ${rowToUse.process_instance_id}`}
|
||||
>
|
||||
{rowToUse.process_model_display_name}
|
||||
{rowToUse.process_instance_id}
|
||||
</Link>
|
||||
</td>
|
||||
<td>
|
||||
<Link
|
||||
data-qa="process-instance-show-link"
|
||||
to={`/admin/process-models/${modifiedProcessModelIdentifier}/process-instances/${rowToUse.process_instance_id}`}
|
||||
data-qa="process-model-show-link"
|
||||
to={`/admin/process-models/${modifiedProcessModelIdentifier}`}
|
||||
title={rowToUse.process_model_identifier}
|
||||
>
|
||||
View {rowToUse.process_instance_id}
|
||||
{rowToUse.process_model_display_name}
|
||||
</Link>
|
||||
</td>
|
||||
<td
|
||||
|
@ -67,18 +76,15 @@ export default function TasksForWaitingForMyGroups() {
|
|||
{rowToUse.task_title}
|
||||
</td>
|
||||
<td>{rowToUse.username}</td>
|
||||
<td>{rowToUse.process_instance_status}</td>
|
||||
<td>{rowToUse.group_identifier || '-'}</td>
|
||||
<td>
|
||||
{convertSecondsToFormattedDateTime(
|
||||
rowToUse.created_at_in_seconds
|
||||
) || '-'}
|
||||
</td>
|
||||
<td>
|
||||
{convertSecondsToFormattedDateTime(
|
||||
rowToUse.updated_at_in_seconds
|
||||
) || '-'}
|
||||
</td>
|
||||
<TableCellWithTimeAgoInWords
|
||||
timeInSeconds={rowToUse.updated_at_in_seconds}
|
||||
/>
|
||||
<td>
|
||||
<Button
|
||||
variant="primary"
|
||||
|
@ -96,14 +102,13 @@ export default function TasksForWaitingForMyGroups() {
|
|||
<Table striped bordered>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Process Model</th>
|
||||
<th>Process Instance</th>
|
||||
<th>Task Name</th>
|
||||
<th>Process Started By</th>
|
||||
<th>Process Instance Status</th>
|
||||
<th>Assigned Group</th>
|
||||
<th>Process Started</th>
|
||||
<th>Process Updated</th>
|
||||
<th>Id</th>
|
||||
<th>Process</th>
|
||||
<th>Task</th>
|
||||
<th>Started By</th>
|
||||
<th>Waiting For</th>
|
||||
<th>Date Started</th>
|
||||
<th>Last Updated</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -114,7 +119,11 @@ export default function TasksForWaitingForMyGroups() {
|
|||
|
||||
const tasksComponent = () => {
|
||||
if (pagination && pagination.total < 1) {
|
||||
return null;
|
||||
return (
|
||||
<p className="no-results-message">
|
||||
Your groups have no task assignments at this time.
|
||||
</p>
|
||||
);
|
||||
}
|
||||
const { page, perPage } = getPageInfoFromSearchParams(
|
||||
searchParams,
|
||||
|
@ -123,22 +132,25 @@ export default function TasksForWaitingForMyGroups() {
|
|||
paginationQueryParamPrefix
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<h1>Tasks waiting for my groups</h1>
|
||||
<PaginationForTable
|
||||
page={page}
|
||||
perPage={perPage}
|
||||
perPageOptions={[2, PER_PAGE_FOR_TASKS_ON_HOME_PAGE, 25]}
|
||||
pagination={pagination}
|
||||
tableToDisplay={buildTable()}
|
||||
paginationQueryParamPrefix={paginationQueryParamPrefix}
|
||||
/>
|
||||
</>
|
||||
<PaginationForTable
|
||||
page={page}
|
||||
perPage={perPage}
|
||||
perPageOptions={[2, PER_PAGE_FOR_TASKS_ON_HOME_PAGE, 25]}
|
||||
pagination={pagination}
|
||||
tableToDisplay={buildTable()}
|
||||
paginationQueryParamPrefix={paginationQueryParamPrefix}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
if (pagination) {
|
||||
return tasksComponent();
|
||||
}
|
||||
return null;
|
||||
return (
|
||||
<>
|
||||
<h2>Tasks waiting for my groups</h2>
|
||||
<p className="data-table-description">
|
||||
This is a list of tasks for groups you belong to that can be completed
|
||||
by any member of the group.
|
||||
</p>
|
||||
{tasksComponent()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ export const PROCESS_STATUSES = [
|
|||
'complete',
|
||||
'error',
|
||||
'suspended',
|
||||
'terminated',
|
||||
];
|
||||
|
||||
// with time: yyyy-MM-dd HH:mm:ss
|
||||
|
|
|
@ -174,18 +174,18 @@ export const getProcessModelFullIdentifierFromSearchParams = (
|
|||
// https://stackoverflow.com/a/71352046/6090676
|
||||
export const truncateString = (text: string, len: number) => {
|
||||
if (text.length > len && text.length > 0) {
|
||||
return `${text.split(' ').slice(0, len).join(' ')} ...`;
|
||||
return `${text.split('').slice(0, len).join('')} ...`;
|
||||
}
|
||||
return text;
|
||||
};
|
||||
|
||||
// Because of limitations in the way openapi defines parameters, we have to modify process models ids
|
||||
// which are basically paths to the models
|
||||
export const modifyProcessModelPath = (path: string) => {
|
||||
export const modifyProcessIdentifierForPathParam = (path: string) => {
|
||||
return path.replace(/\//g, ':') || '';
|
||||
};
|
||||
|
||||
export const unModifyProcessModelPath = (path: string) => {
|
||||
export const unModifyProcessIdentifierForPathParam = (path: string) => {
|
||||
return path.replace(/:/g, '/') || '';
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
/* eslint-disable no-restricted-syntax */
|
||||
// https://gist.github.com/caiotarifa/30ae974f2293c761f3139dd194abd9e5
|
||||
export const TimeAgo = (function awesomeFunc() {
|
||||
const self = {};
|
||||
|
||||
// Public Methods
|
||||
self.locales = {
|
||||
prefix: '',
|
||||
sufix: 'ago',
|
||||
|
||||
seconds: 'less than a minute',
|
||||
minute: 'about a minute',
|
||||
minutes: '%d minutes',
|
||||
hour: 'about an hour',
|
||||
hours: 'about %d hours',
|
||||
day: 'a day',
|
||||
days: '%d days',
|
||||
month: 'about a month',
|
||||
months: '%d months',
|
||||
year: 'about a year',
|
||||
years: '%d years',
|
||||
};
|
||||
|
||||
self.inWords = function inWords(timeAgo) {
|
||||
const milliseconds = timeAgo * 1000;
|
||||
const seconds = Math.floor(
|
||||
(new Date() - parseInt(milliseconds, 10)) / 1000
|
||||
);
|
||||
const separator = this.locales.separator || ' ';
|
||||
let words = this.locales.prefix + separator;
|
||||
let interval = 0;
|
||||
const intervals = {
|
||||
year: seconds / 31536000,
|
||||
month: seconds / 2592000,
|
||||
day: seconds / 86400,
|
||||
hour: seconds / 3600,
|
||||
minute: seconds / 60,
|
||||
};
|
||||
|
||||
let distance = this.locales.seconds;
|
||||
|
||||
// eslint-disable-next-line guard-for-in
|
||||
for (const key in intervals) {
|
||||
interval = Math.floor(intervals[key]);
|
||||
|
||||
if (interval > 1) {
|
||||
distance = this.locales[`${key}s`];
|
||||
break;
|
||||
} else if (interval === 1) {
|
||||
distance = this.locales[key];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
distance = distance.replace(/%d/i, interval);
|
||||
words += distance + separator + this.locales.sufix;
|
||||
|
||||
return words.trim();
|
||||
};
|
||||
|
||||
return self;
|
||||
})();
|
|
@ -1,7 +1,7 @@
|
|||
// We may need to update usage of Ability when we update.
|
||||
// They say they are going to rename PureAbility to Ability and remove the old class.
|
||||
import { AbilityBuilder, Ability } from '@casl/ability';
|
||||
import { useContext, useEffect } from 'react';
|
||||
import { useContext, useEffect, useState } from 'react';
|
||||
import { AbilityContext } from '../contexts/Can';
|
||||
import { PermissionCheckResponseBody, PermissionsToCheck } from '../interfaces';
|
||||
import HttpService from '../services/HttpService';
|
||||
|
@ -10,6 +10,7 @@ export const usePermissionFetcher = (
|
|||
permissionsToCheck: PermissionsToCheck
|
||||
) => {
|
||||
const ability = useContext(AbilityContext);
|
||||
const [permissionsLoaded, setPermissionsLoaded] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
const processPermissionResult = (result: PermissionCheckResponseBody) => {
|
||||
|
@ -34,15 +35,17 @@ export const usePermissionFetcher = (
|
|||
}
|
||||
});
|
||||
ability.update(rules);
|
||||
setPermissionsLoaded(true);
|
||||
};
|
||||
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/permissions-check`,
|
||||
httpMethod: 'POST',
|
||||
successCallback: processPermissionResult,
|
||||
postBody: { requests_to_check: permissionsToCheck },
|
||||
});
|
||||
if (Object.keys(permissionsToCheck).length !== 0) {
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/permissions-check`,
|
||||
httpMethod: 'POST',
|
||||
successCallback: processPermissionResult,
|
||||
postBody: { requests_to_check: permissionsToCheck },
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return { ability };
|
||||
return { ability, permissionsLoaded };
|
||||
};
|
||||
|
|
|
@ -1,20 +1,28 @@
|
|||
import { useMemo } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
export const useUriListForPermissions = () => {
|
||||
const params = useParams();
|
||||
const targetUris = {
|
||||
authenticationListPath: `/v1.0/authentications`,
|
||||
messageInstanceListPath: '/v1.0/messages',
|
||||
processGroupListPath: '/v1.0/process-groups',
|
||||
processGroupShowPath: `/v1.0/process-groups/${params.process_group_id}`,
|
||||
processInstanceActionPath: `/v1.0/process-models/${params.process_model_id}/process-instances`,
|
||||
processInstanceListPath: '/v1.0/process-instances',
|
||||
processModelCreatePath: `/v1.0/process-models/${params.process_group_id}`,
|
||||
processModelFileCreatePath: `/v1.0/process-models/${params.process_model_id}/files`,
|
||||
processModelFileShowPath: `/v1.0/process-models/${params.process_model_id}/files/${params.file_name}`,
|
||||
processModelShowPath: `/v1.0/process-models/${params.process_model_id}`,
|
||||
secretListPath: `/v1.0/secrets`,
|
||||
};
|
||||
const targetUris = useMemo(() => {
|
||||
return {
|
||||
authenticationListPath: `/v1.0/authentications`,
|
||||
messageInstanceListPath: '/v1.0/messages',
|
||||
processGroupListPath: '/v1.0/process-groups',
|
||||
processGroupShowPath: `/v1.0/process-groups/${params.process_group_id}`,
|
||||
processInstanceCreatePath: `/v1.0/process-instances/${params.process_model_id}`,
|
||||
processInstanceActionPath: `/v1.0/process-instances/${params.process_model_id}/${params.process_instance_id}`,
|
||||
processInstanceListPath: '/v1.0/process-instances',
|
||||
processInstanceLogListPath: `/v1.0/logs/${params.process_model_id}/${params.process_instance_id}`,
|
||||
processInstanceReportListPath: '/v1.0/process-instances/reports',
|
||||
processInstanceTaskListPath: `/v1.0/task-data/${params.process_model_id}/${params.process_instance_id}`,
|
||||
processModelCreatePath: `/v1.0/process-models/${params.process_group_id}`,
|
||||
processModelFileCreatePath: `/v1.0/process-models/${params.process_model_id}/files`,
|
||||
processModelFileShowPath: `/v1.0/process-models/${params.process_model_id}/files/${params.file_name}`,
|
||||
processModelPublishPath: `/v1.0/process-models/${params.process_model_id}/publish`,
|
||||
processModelShowPath: `/v1.0/process-models/${params.process_model_id}`,
|
||||
secretListPath: `/v1.0/secrets`,
|
||||
};
|
||||
}, [params]);
|
||||
|
||||
return { targetUris };
|
||||
};
|
||||
|
|
127
src/index.css
127
src/index.css
|
@ -5,6 +5,15 @@
|
|||
color: white;
|
||||
}
|
||||
|
||||
.megacondensed {
|
||||
padding-left: 0px;
|
||||
}
|
||||
|
||||
/* defaults to 3rem, which isn't long sufficient for "elizabeth" */
|
||||
.cds--header__action.username-header-text {
|
||||
width: 5rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-weight: 400;
|
||||
font-size: 28px;
|
||||
|
@ -60,6 +69,24 @@ h2 {
|
|||
color: black;
|
||||
}
|
||||
|
||||
/* match normal link colors */
|
||||
.cds--btn--ghost.button-link {
|
||||
color: #0062fe;
|
||||
padding-left: 0;
|
||||
}
|
||||
.cds--btn--ghost.button-link:visited {
|
||||
color: #0062fe;
|
||||
padding-left: 0;
|
||||
}
|
||||
.cds--btn--ghost.button-link:hover {
|
||||
color: #0062fe;
|
||||
padding-left: 0;
|
||||
}
|
||||
.cds--btn--ghost.button-link:visited:hover {
|
||||
color: #0062fe;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.cds--header__global .cds--btn--primary {
|
||||
background-color: #161616
|
||||
}
|
||||
|
@ -138,6 +165,26 @@ h1.with-icons {
|
|||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.with-top-margin {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.with-extra-top-margin {
|
||||
margin-top: 1.3em;
|
||||
}
|
||||
|
||||
.with-tiny-top-margin {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.with-large-bottom-margin {
|
||||
margin-bottom: 3em;
|
||||
}
|
||||
|
||||
.with-tiny-bottom-margin {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.diagram-viewer-canvas {
|
||||
border:1px solid #000000;
|
||||
height:70vh;
|
||||
|
@ -243,3 +290,83 @@ in on this with the react-jsonschema-form repo. This is just a patch fix to allo
|
|||
position: absolute;
|
||||
bottom: 1em;
|
||||
}
|
||||
|
||||
.cds--tabs .cds--tabs__nav-link {
|
||||
max-width: 20rem;
|
||||
}
|
||||
|
||||
.clear-left {
|
||||
clear: left;
|
||||
}
|
||||
|
||||
td.actions-cell {
|
||||
width: 1em;
|
||||
}
|
||||
|
||||
.no-results-message {
|
||||
font-style: italic;
|
||||
margin-left: 2em;
|
||||
margin-top: 1em;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.data-table-description {
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
letter-spacing: 0.16px;
|
||||
color: #525252;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
/* top and bottom margin since this is sort of the middle of three sections on the process model show page */
|
||||
.process-model-files-section {
|
||||
margin: 2em 0;
|
||||
}
|
||||
|
||||
.filterIcon {
|
||||
text-align: right;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.cds--btn--ghost:not([disabled]) svg.red-icon {
|
||||
fill: red;
|
||||
}
|
||||
|
||||
svg.green-icon {
|
||||
fill: #198038;
|
||||
}
|
||||
|
||||
svg.notification-icon {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.failure-string {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.cds--btn--ghost.cds--btn--sm.button-tag-icon {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
/* .no-wrap cds--label cds--label--inline cds--label--inline--md{ */
|
||||
.no-wrap .cds--label--inline{
|
||||
word-break: normal;
|
||||
}
|
||||
|
||||
.combo-box-in-modal {
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.cds--btn.narrow-button {
|
||||
max-width: 10rem;
|
||||
min-width: 5rem;
|
||||
word-break: normal;
|
||||
|
||||
}
|
||||
|
||||
/* lime green */
|
||||
.tag-type-green:hover {
|
||||
background-color: #80ee90;
|
||||
}
|
||||
|
|
|
@ -12,9 +12,8 @@ export interface RecentProcessModel {
|
|||
}
|
||||
|
||||
export interface ProcessReference {
|
||||
id: string; // The unique id of the process or decision table.
|
||||
name: string; // The process or decision Display name.
|
||||
identifier: string;
|
||||
identifier: string; // The unique id of the process
|
||||
display_name: string;
|
||||
process_group_id: string;
|
||||
process_model_id: string;
|
||||
|
@ -39,6 +38,68 @@ export interface ProcessFile {
|
|||
export interface ProcessInstance {
|
||||
id: number;
|
||||
process_model_identifier: string;
|
||||
process_model_display_name: string;
|
||||
}
|
||||
|
||||
export interface MessageCorrelationProperties {
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
export interface MessageCorrelations {
|
||||
[key: string]: MessageCorrelationProperties;
|
||||
}
|
||||
|
||||
export interface MessageInstance {
|
||||
id: number;
|
||||
process_model_identifier: string;
|
||||
process_model_display_name: string;
|
||||
process_instance_id: number;
|
||||
message_identifier: string;
|
||||
message_type: string;
|
||||
failure_cause: string;
|
||||
status: string;
|
||||
created_at_in_seconds: number;
|
||||
message_correlations?: MessageCorrelations;
|
||||
}
|
||||
|
||||
export interface ReportFilter {
|
||||
field_name: string;
|
||||
field_value: string;
|
||||
operator?: string;
|
||||
}
|
||||
|
||||
export interface ReportColumn {
|
||||
Header: string;
|
||||
accessor: string;
|
||||
filterable: boolean;
|
||||
}
|
||||
|
||||
export interface ReportColumnForEditing extends ReportColumn {
|
||||
filter_field_value: string;
|
||||
filter_operator: string;
|
||||
}
|
||||
|
||||
export interface ReportMetadata {
|
||||
columns: ReportColumn[];
|
||||
filter_by: ReportFilter[];
|
||||
order_by: string[];
|
||||
}
|
||||
|
||||
export interface ProcessInstanceReport {
|
||||
id: number;
|
||||
identifier: string;
|
||||
name: string;
|
||||
report_metadata: ReportMetadata;
|
||||
}
|
||||
|
||||
export interface ProcessGroupLite {
|
||||
id: string;
|
||||
display_name: string;
|
||||
}
|
||||
|
||||
export interface MetadataExtractionPath {
|
||||
key: string;
|
||||
path: string;
|
||||
}
|
||||
|
||||
export interface ProcessModel {
|
||||
|
@ -47,6 +108,8 @@ export interface ProcessModel {
|
|||
display_name: string;
|
||||
primary_file_name: string;
|
||||
files: ProcessFile[];
|
||||
parent_groups?: ProcessGroupLite[];
|
||||
metadata_extraction_paths?: MetadataExtractionPath[];
|
||||
}
|
||||
|
||||
export interface ProcessGroup {
|
||||
|
@ -55,10 +118,19 @@ export interface ProcessGroup {
|
|||
description?: string | null;
|
||||
process_models?: ProcessModel[];
|
||||
process_groups?: ProcessGroup[];
|
||||
parent_groups?: ProcessGroupLite[];
|
||||
}
|
||||
|
||||
export interface HotCrumbItemObject {
|
||||
entityToExplode: ProcessModel | ProcessGroup | string;
|
||||
entityType: string;
|
||||
linkLastItem?: boolean;
|
||||
}
|
||||
|
||||
export type HotCrumbItemArray = [displayValue: string, url?: string];
|
||||
|
||||
// tuple of display value and URL
|
||||
export type HotCrumbItem = [displayValue: string, url?: string];
|
||||
export type HotCrumbItem = HotCrumbItemArray | HotCrumbItemObject;
|
||||
|
||||
export interface ErrorForDisplay {
|
||||
message: string;
|
||||
|
|
|
@ -71,11 +71,11 @@ export default function AdminRoutes() {
|
|||
element={<ProcessModelEdit />}
|
||||
/>
|
||||
<Route
|
||||
path="process-models/:process_model_id/process-instances/:process_instance_id"
|
||||
path="process-instances/:process_model_id/:process_instance_id"
|
||||
element={<ProcessInstanceShow />}
|
||||
/>
|
||||
<Route
|
||||
path="process-models/:process_model_id/process-instances/:process_instance_id/:spiff_step"
|
||||
path="process-instances/:process_model_id/:process_instance_id/:spiff_step"
|
||||
element={<ProcessInstanceShow />}
|
||||
/>
|
||||
<Route
|
||||
|
@ -103,7 +103,7 @@ export default function AdminRoutes() {
|
|||
element={<ReactFormEditor />}
|
||||
/>
|
||||
<Route
|
||||
path="process-models/:process_model_id/process-instances/:process_instance_id/logs"
|
||||
path="logs/:process_model_id/:process_instance_id"
|
||||
element={<ProcessInstanceLogList />}
|
||||
/>
|
||||
<Route path="process-instances" element={<ProcessInstanceList />} />
|
||||
|
|
|
@ -54,7 +54,7 @@ export default function AuthenticationList() {
|
|||
<Table striped bordered>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Id</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>{rows}</tbody>
|
||||
|
|
|
@ -1,5 +1,48 @@
|
|||
import MyCompletedInstances from '../components/MyCompletedInstances';
|
||||
import ProcessInstanceListTable from '../components/ProcessInstanceListTable';
|
||||
|
||||
export default function CompletedInstances() {
|
||||
return <MyCompletedInstances />;
|
||||
return (
|
||||
<>
|
||||
<h2>My completed instances</h2>
|
||||
<p className="data-table-description">
|
||||
This is a list of instances you started that are now complete.
|
||||
</p>
|
||||
<ProcessInstanceListTable
|
||||
filtersEnabled={false}
|
||||
paginationQueryParamPrefix="my_completed_instances"
|
||||
perPageOptions={[2, 5, 25]}
|
||||
reportIdentifier="system_report_instances_initiated_by_me"
|
||||
showReports={false}
|
||||
textToShowIfEmpty="You have no completed instances at this time."
|
||||
paginationClassName="with-large-bottom-margin"
|
||||
autoReload
|
||||
/>
|
||||
<h2>Tasks completed by me</h2>
|
||||
<p className="data-table-description">
|
||||
This is a list of instances where you have completed tasks.
|
||||
</p>
|
||||
<ProcessInstanceListTable
|
||||
filtersEnabled={false}
|
||||
paginationQueryParamPrefix="my_completed_tasks"
|
||||
perPageOptions={[2, 5, 25]}
|
||||
reportIdentifier="system_report_instances_with_tasks_completed_by_me"
|
||||
showReports={false}
|
||||
textToShowIfEmpty="You have no completed tasks at this time."
|
||||
paginationClassName="with-large-bottom-margin"
|
||||
/>
|
||||
<h2>Tasks completed by my groups</h2>
|
||||
<p className="data-table-description">
|
||||
This is a list of instances with tasks that were completed by groups you
|
||||
belong to.
|
||||
</p>
|
||||
<ProcessInstanceListTable
|
||||
filtersEnabled={false}
|
||||
paginationQueryParamPrefix="group_completed_tasks"
|
||||
perPageOptions={[2, 5, 25]}
|
||||
reportIdentifier="system_report_instances_with_tasks_completed_by_my_groups"
|
||||
showReports={false}
|
||||
textToShowIfEmpty="Your group has no completed tasks at this time."
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -3,7 +3,8 @@ import ProcessModelListTiles from '../components/ProcessModelListTiles';
|
|||
export default function CreateNewInstance() {
|
||||
return (
|
||||
<ProcessModelListTiles
|
||||
headerElement={<h1>Process models available to you</h1>}
|
||||
headerElement={<h2>Processes I can start</h2>}
|
||||
checkPermissions={false}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
import TasksForMyOpenProcesses from '../components/TasksForMyOpenProcesses';
|
||||
import TasksWaitingForMe from '../components/TasksWaitingForMe';
|
||||
import TasksForWaitingForMyGroups from '../components/TasksWaitingForMyGroups';
|
||||
import TasksWaitingForMyGroups from '../components/TasksWaitingForMyGroups';
|
||||
|
||||
export default function GroupedTasks() {
|
||||
return (
|
||||
<>
|
||||
{/* be careful moving these around since the first two have with-large-bottom-margin in order to get some space between the three table sections. */}
|
||||
{/* i wish Stack worked to add space just between top-level elements */}
|
||||
<TasksForMyOpenProcesses />
|
||||
<br />
|
||||
<TasksWaitingForMe />
|
||||
<br />
|
||||
<TasksForWaitingForMyGroups />
|
||||
<TasksWaitingForMyGroups />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -18,12 +18,10 @@ export default function HomePageRoutes() {
|
|||
useEffect(() => {
|
||||
setErrorMessage(null);
|
||||
let newSelectedTabIndex = 0;
|
||||
if (location.pathname.match(/^\/tasks\/grouped\b/)) {
|
||||
if (location.pathname.match(/^\/tasks\/completed-instances\b/)) {
|
||||
newSelectedTabIndex = 1;
|
||||
} else if (location.pathname.match(/^\/tasks\/completed-instances\b/)) {
|
||||
newSelectedTabIndex = 2;
|
||||
} else if (location.pathname.match(/^\/tasks\/create-new-instance\b/)) {
|
||||
newSelectedTabIndex = 3;
|
||||
newSelectedTabIndex = 2;
|
||||
}
|
||||
setSelectedTabIndex(newSelectedTabIndex);
|
||||
}, [location, setErrorMessage]);
|
||||
|
@ -36,13 +34,13 @@ export default function HomePageRoutes() {
|
|||
<>
|
||||
<Tabs selectedIndex={selectedTabIndex}>
|
||||
<TabList aria-label="List of tabs">
|
||||
<Tab onClick={() => navigate('/tasks/my-tasks')}>My Tasks</Tab>
|
||||
<Tab onClick={() => navigate('/tasks/grouped')}>Grouped Tasks</Tab>
|
||||
{/* <Tab onClick={() => navigate('/tasks/my-tasks')}>My Tasks</Tab> */}
|
||||
<Tab onClick={() => navigate('/tasks/grouped')}>In Progress</Tab>
|
||||
<Tab onClick={() => navigate('/tasks/completed-instances')}>
|
||||
Completed Instances
|
||||
Completed
|
||||
</Tab>
|
||||
<Tab onClick={() => navigate('/tasks/create-new-instance')}>
|
||||
Create New Instance +
|
||||
Start New +
|
||||
</Tab>
|
||||
</TabList>
|
||||
</Tabs>
|
||||
|
@ -55,7 +53,7 @@ export default function HomePageRoutes() {
|
|||
<>
|
||||
{renderTabs()}
|
||||
<Routes>
|
||||
<Route path="/" element={<MyTasks />} />
|
||||
<Route path="/" element={<GroupedTasks />} />
|
||||
<Route path="my-tasks" element={<MyTasks />} />
|
||||
<Route path=":process_instance_id/:task_id" element={<TaskShow />} />
|
||||
<Route path="grouped" element={<GroupedTasks />} />
|
||||
|
|
|
@ -3,7 +3,7 @@ import { useEffect, useState } from 'react';
|
|||
import { Button, Select, SelectItem, TextInput } from '@carbon/react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { FormField } from '../interfaces';
|
||||
import { modifyProcessModelPath, slugifyString } from '../helpers';
|
||||
import { modifyProcessIdentifierForPathParam, slugifyString } from '../helpers';
|
||||
import HttpService from '../services/HttpService';
|
||||
|
||||
export default function JsonSchemaFormBuilder() {
|
||||
|
@ -28,7 +28,7 @@ export default function JsonSchemaFormBuilder() {
|
|||
const [formFieldTitle, setFormFieldTitle] = useState<string>('');
|
||||
const [formFieldType, setFormFieldType] = useState<string>('');
|
||||
|
||||
const modifiedProcessModelId = modifyProcessModelPath(
|
||||
const modifiedProcessModelId = modifyProcessIdentifierForPathParam(
|
||||
`${params.process_model_id}`
|
||||
);
|
||||
|
||||
|
|
|
@ -1,16 +1,19 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
// @ts-ignore
|
||||
import { Table } from '@carbon/react';
|
||||
import { ErrorOutline } from '@carbon/icons-react';
|
||||
// @ts-ignore
|
||||
import { Table, Modal, Button } from '@carbon/react';
|
||||
import { Link, useParams, useSearchParams } from 'react-router-dom';
|
||||
import PaginationForTable from '../components/PaginationForTable';
|
||||
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
|
||||
import {
|
||||
convertSecondsToFormattedDateString,
|
||||
convertSecondsToFormattedDateTime,
|
||||
getPageInfoFromSearchParams,
|
||||
modifyProcessModelPath,
|
||||
unModifyProcessModelPath,
|
||||
modifyProcessIdentifierForPathParam,
|
||||
} from '../helpers';
|
||||
import HttpService from '../services/HttpService';
|
||||
import { FormatProcessModelDisplayName } from '../components/MiniComponents';
|
||||
import { MessageInstance } from '../interfaces';
|
||||
|
||||
export default function MessageInstanceList() {
|
||||
const params = useParams();
|
||||
|
@ -18,6 +21,9 @@ export default function MessageInstanceList() {
|
|||
const [messageIntances, setMessageInstances] = useState([]);
|
||||
const [pagination, setPagination] = useState(null);
|
||||
|
||||
const [messageInstanceForModal, setMessageInstanceForModal] =
|
||||
useState<MessageInstance | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const setMessageInstanceListFromResult = (result: any) => {
|
||||
setMessageInstances(result.results);
|
||||
|
@ -36,41 +42,89 @@ export default function MessageInstanceList() {
|
|||
});
|
||||
}, [searchParams, params]);
|
||||
|
||||
const buildTable = () => {
|
||||
// return null;
|
||||
const rows = messageIntances.map((row) => {
|
||||
const rowToUse = row as any;
|
||||
const handleCorrelationDisplayClose = () => {
|
||||
setMessageInstanceForModal(null);
|
||||
};
|
||||
|
||||
const correlationsDisplayModal = () => {
|
||||
if (messageInstanceForModal) {
|
||||
let failureCausePre = null;
|
||||
if (messageInstanceForModal.failure_cause) {
|
||||
failureCausePre = (
|
||||
<>
|
||||
<p className="failure-string">
|
||||
{messageInstanceForModal.failure_cause}
|
||||
</p>
|
||||
<br />
|
||||
</>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<tr key={rowToUse.id}>
|
||||
<td>{rowToUse.id}</td>
|
||||
<td>
|
||||
<Link
|
||||
data-qa="process-model-show-link"
|
||||
to={`/admin/process-models/${modifyProcessModelPath(
|
||||
rowToUse.process_model_identifier
|
||||
)}`}
|
||||
>
|
||||
{rowToUse.process_model_identifier}
|
||||
</Link>
|
||||
</td>
|
||||
<Modal
|
||||
open={!!messageInstanceForModal}
|
||||
passiveModal
|
||||
onRequestClose={handleCorrelationDisplayClose}
|
||||
modalHeading={`Message ${messageInstanceForModal.id} (${messageInstanceForModal.message_identifier}) ${messageInstanceForModal.message_type} data:`}
|
||||
modalLabel="Details"
|
||||
>
|
||||
{failureCausePre}
|
||||
<p>Correlations:</p>
|
||||
<pre>
|
||||
{JSON.stringify(
|
||||
messageInstanceForModal.message_correlations,
|
||||
null,
|
||||
2
|
||||
)}
|
||||
</pre>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const buildTable = () => {
|
||||
const rows = messageIntances.map((row: MessageInstance) => {
|
||||
let errorIcon = null;
|
||||
let errorTitle = null;
|
||||
if (row.failure_cause) {
|
||||
errorTitle = 'Instance has an error';
|
||||
errorIcon = (
|
||||
<>
|
||||
|
||||
<ErrorOutline className="red-icon" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<tr key={row.id}>
|
||||
<td>{row.id}</td>
|
||||
<td>{FormatProcessModelDisplayName(row)}</td>
|
||||
<td>
|
||||
<Link
|
||||
data-qa="process-instance-show-link"
|
||||
to={`/admin/process-models/${modifyProcessModelPath(
|
||||
rowToUse.process_model_identifier
|
||||
)}/process-instances/${rowToUse.process_instance_id}`}
|
||||
to={`/admin/process-instances/${modifyProcessIdentifierForPathParam(
|
||||
row.process_model_identifier
|
||||
)}/${row.process_instance_id}`}
|
||||
>
|
||||
{rowToUse.process_instance_id}
|
||||
{row.process_instance_id}
|
||||
</Link>
|
||||
</td>
|
||||
<td>{rowToUse.message_identifier}</td>
|
||||
<td>{rowToUse.message_type}</td>
|
||||
<td>{rowToUse.failure_cause || '-'}</td>
|
||||
<td>{rowToUse.status}</td>
|
||||
<td>{row.message_identifier}</td>
|
||||
<td>{row.message_type}</td>
|
||||
<td>
|
||||
{convertSecondsToFormattedDateString(
|
||||
rowToUse.created_at_in_seconds
|
||||
)}
|
||||
<Button
|
||||
kind="ghost"
|
||||
className="button-link"
|
||||
onClick={() => setMessageInstanceForModal(row)}
|
||||
title={errorTitle}
|
||||
>
|
||||
View
|
||||
{errorIcon}
|
||||
</Button>
|
||||
</td>
|
||||
<td>{row.status}</td>
|
||||
<td>
|
||||
{convertSecondsToFormattedDateTime(row.created_at_in_seconds)}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
|
@ -79,12 +133,12 @@ export default function MessageInstanceList() {
|
|||
<Table striped bordered>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Instance Id</th>
|
||||
<th>Process Model</th>
|
||||
<th>Id</th>
|
||||
<th>Process</th>
|
||||
<th>Process Instance</th>
|
||||
<th>Message Model</th>
|
||||
<th>Name</th>
|
||||
<th>Type</th>
|
||||
<th>Failure Cause</th>
|
||||
<th>Details</th>
|
||||
<th>Status</th>
|
||||
<th>Created At</th>
|
||||
</tr>
|
||||
|
@ -102,17 +156,16 @@ export default function MessageInstanceList() {
|
|||
<ProcessBreadcrumb
|
||||
hotCrumbs={[
|
||||
['Process Groups', '/admin'],
|
||||
[
|
||||
`Process Model: ${params.process_model_id}`,
|
||||
`process_model:${unModifyProcessModelPath(
|
||||
searchParams.get('process_model_id') || ''
|
||||
)}:link`,
|
||||
],
|
||||
{
|
||||
entityToExplode: searchParams.get('process_model_id') || '',
|
||||
entityType: 'process-model-id',
|
||||
linkLastItem: true,
|
||||
},
|
||||
[
|
||||
`Process Instance: ${searchParams.get('process_instance_id')}`,
|
||||
`/admin/process-models/${searchParams.get(
|
||||
`/admin/process-instances/${searchParams.get(
|
||||
'process_model_id'
|
||||
)}/process-instances/${searchParams.get('process_instance_id')}`,
|
||||
)}/${searchParams.get('process_instance_id')}`,
|
||||
],
|
||||
['Messages'],
|
||||
]}
|
||||
|
@ -123,6 +176,7 @@ export default function MessageInstanceList() {
|
|||
<>
|
||||
{breadcrumbElement}
|
||||
<h1>Messages</h1>
|
||||
{correlationsDisplayModal()}
|
||||
<PaginationForTable
|
||||
page={page}
|
||||
perPage={perPage}
|
||||
|
|
|
@ -5,20 +5,28 @@ import { Link, useSearchParams } from 'react-router-dom';
|
|||
import PaginationForTable from '../components/PaginationForTable';
|
||||
import {
|
||||
getPageInfoFromSearchParams,
|
||||
modifyProcessModelPath,
|
||||
modifyProcessIdentifierForPathParam,
|
||||
refreshAtInterval,
|
||||
} from '../helpers';
|
||||
import HttpService from '../services/HttpService';
|
||||
import { PaginationObject, RecentProcessModel } from '../interfaces';
|
||||
import {
|
||||
PaginationObject,
|
||||
ProcessInstance,
|
||||
ProcessModel,
|
||||
RecentProcessModel,
|
||||
} from '../interfaces';
|
||||
import ProcessInstanceRun from '../components/ProcessInstanceRun';
|
||||
|
||||
const PER_PAGE_FOR_TASKS_ON_HOME_PAGE = 5;
|
||||
const REFRESH_INTERVAL = 10;
|
||||
const REFRESH_INTERVAL = 5;
|
||||
const REFRESH_TIMEOUT = 600;
|
||||
|
||||
export default function MyTasks() {
|
||||
const [searchParams] = useSearchParams();
|
||||
const [tasks, setTasks] = useState([]);
|
||||
const [pagination, setPagination] = useState<PaginationObject | null>(null);
|
||||
const [processInstance, setProcessInstance] =
|
||||
useState<ProcessInstance | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const getTasks = () => {
|
||||
|
@ -40,6 +48,28 @@ export default function MyTasks() {
|
|||
refreshAtInterval(REFRESH_INTERVAL, REFRESH_TIMEOUT, getTasks);
|
||||
}, [searchParams]);
|
||||
|
||||
const processInstanceRunResultTag = () => {
|
||||
if (processInstance) {
|
||||
return (
|
||||
<div className="alert alert-success" role="alert">
|
||||
<p>
|
||||
Process Instance {processInstance.id} kicked off (
|
||||
<Link
|
||||
to={`/admin/process-instances/${modifyProcessIdentifierForPathParam(
|
||||
processInstance.process_model_identifier
|
||||
)}/${processInstance.id}`}
|
||||
data-qa="process-instance-show-link"
|
||||
>
|
||||
view
|
||||
</Link>
|
||||
).
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
let recentProcessModels: RecentProcessModel[] = [];
|
||||
const recentProcessModelsString = localStorage.getItem('recentProcessModels');
|
||||
if (recentProcessModelsString !== null) {
|
||||
|
@ -50,9 +80,8 @@ export default function MyTasks() {
|
|||
const rows = tasks.map((row) => {
|
||||
const rowToUse = row as any;
|
||||
const taskUrl = `/tasks/${rowToUse.process_instance_id}/${rowToUse.id}`;
|
||||
const modifiedProcessModelIdentifier = modifyProcessModelPath(
|
||||
rowToUse.process_model_identifier
|
||||
);
|
||||
const modifiedProcessModelIdentifier =
|
||||
modifyProcessIdentifierForPathParam(rowToUse.process_model_identifier);
|
||||
return (
|
||||
<tr key={rowToUse.id}>
|
||||
<td>
|
||||
|
@ -66,9 +95,9 @@ export default function MyTasks() {
|
|||
<td>
|
||||
<Link
|
||||
data-qa="process-instance-show-link"
|
||||
to={`/admin/process-models/${modifiedProcessModelIdentifier}/process-instances/${rowToUse.process_instance_id}`}
|
||||
to={`/admin/process-instances/${modifiedProcessModelIdentifier}/${rowToUse.process_instance_id}`}
|
||||
>
|
||||
View {rowToUse.process_instance_id}
|
||||
{rowToUse.process_instance_id}
|
||||
</Link>
|
||||
</td>
|
||||
<td
|
||||
|
@ -108,33 +137,44 @@ export default function MyTasks() {
|
|||
};
|
||||
|
||||
const buildRecentProcessModelSection = () => {
|
||||
const rows = recentProcessModels.map((row) => {
|
||||
const rowToUse = row as any;
|
||||
const modifiedProcessModelId = modifyProcessModelPath(
|
||||
rowToUse.processModelIdentifier
|
||||
const rows = recentProcessModels.map((row: RecentProcessModel) => {
|
||||
const processModel: ProcessModel = {
|
||||
id: row.processModelIdentifier,
|
||||
description: '',
|
||||
display_name: '',
|
||||
primary_file_name: '',
|
||||
files: [],
|
||||
};
|
||||
const modifiedProcessModelId = modifyProcessIdentifierForPathParam(
|
||||
row.processModelIdentifier
|
||||
);
|
||||
return (
|
||||
<tr
|
||||
key={`${rowToUse.processGroupIdentifier}/${rowToUse.processModelIdentifier}`}
|
||||
>
|
||||
<tr key={`${row.processGroupIdentifier}/${row.processModelIdentifier}`}>
|
||||
<td>
|
||||
<Link
|
||||
data-qa="process-model-show-link"
|
||||
to={`/admin/process-models/${modifiedProcessModelId}`}
|
||||
>
|
||||
{rowToUse.processModelDisplayName}
|
||||
{row.processModelDisplayName}
|
||||
</Link>
|
||||
</td>
|
||||
<td className="actions-cell">
|
||||
<ProcessInstanceRun
|
||||
processModel={processModel}
|
||||
onSuccessCallback={setProcessInstance}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<>
|
||||
<h1>Recently viewed process models</h1>
|
||||
<h1>Recently instantiated process models</h1>
|
||||
<Table striped bordered>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Process Model</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>{rows}</tbody>
|
||||
|
@ -176,6 +216,7 @@ export default function MyTasks() {
|
|||
}
|
||||
return (
|
||||
<>
|
||||
{processInstanceRunResultTag()}
|
||||
{tasksWaitingForMe}
|
||||
<br />
|
||||
{relevantProcessModelSection}
|
||||
|
|
|
@ -27,10 +27,11 @@ export default function ProcessGroupEdit() {
|
|||
<ProcessBreadcrumb
|
||||
hotCrumbs={[
|
||||
['Process Groups', '/admin'],
|
||||
[
|
||||
`Process Group: ${processGroup.id}:link`,
|
||||
`process_group:${processGroup.id}:link`,
|
||||
],
|
||||
{
|
||||
entityToExplode: processGroup,
|
||||
entityType: 'process-group',
|
||||
linkLastItem: true,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<h1>Edit Process Group: {(processGroup as any).id}</h1>
|
||||
|
|
|
@ -7,7 +7,7 @@ import {
|
|||
import { Can } from '@casl/react';
|
||||
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
|
||||
import HttpService from '../services/HttpService';
|
||||
import { modifyProcessModelPath } from '../helpers';
|
||||
import { modifyProcessIdentifierForPathParam } from '../helpers';
|
||||
import { CarbonComboBoxSelection, PermissionsToCheck } from '../interfaces';
|
||||
import { useUriListForPermissions } from '../hooks/UriListForPermissions';
|
||||
import { usePermissionFetcher } from '../hooks/PermissionService';
|
||||
|
@ -39,7 +39,7 @@ export default function ProcessGroupList() {
|
|||
};
|
||||
// for search box
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-models?per_page=1000`,
|
||||
path: `/process-models?per_page=1000&recursive=true`,
|
||||
successCallback: processResultForProcessModels,
|
||||
});
|
||||
}, [searchParams]);
|
||||
|
@ -48,7 +48,9 @@ export default function ProcessGroupList() {
|
|||
const processModelSearchOnChange = (selection: CarbonComboBoxSelection) => {
|
||||
const processModel = selection.selectedItem;
|
||||
navigate(
|
||||
`/admin/process-models/${modifyProcessModelPath(processModel.id)}`
|
||||
`/admin/process-models/${modifyProcessIdentifierForPathParam(
|
||||
processModel.id
|
||||
)}`
|
||||
);
|
||||
};
|
||||
return (
|
||||
|
|
|
@ -14,7 +14,11 @@ export default function ProcessGroupNew() {
|
|||
|
||||
const hotCrumbs: HotCrumbItem[] = [['Process Groups', '/admin']];
|
||||
if (parentGroupId) {
|
||||
hotCrumbs.push(['', `process_group:${parentGroupId}:link`]);
|
||||
hotCrumbs.push({
|
||||
entityToExplode: parentGroupId,
|
||||
entityType: 'process-group-id',
|
||||
linkLastItem: true,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,39 +1,51 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
import { Link, useSearchParams, useParams } from 'react-router-dom';
|
||||
import {
|
||||
// Link,
|
||||
useSearchParams,
|
||||
useParams,
|
||||
useNavigate,
|
||||
} from 'react-router-dom';
|
||||
import {
|
||||
TrashCan,
|
||||
Edit,
|
||||
// @ts-ignore
|
||||
} from '@carbon/icons-react';
|
||||
// @ts-ignore
|
||||
import { Button, Table, Stack } from '@carbon/react';
|
||||
import { Button, Stack } from '@carbon/react';
|
||||
import { Can } from '@casl/react';
|
||||
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
|
||||
import PaginationForTable from '../components/PaginationForTable';
|
||||
import HttpService from '../services/HttpService';
|
||||
import {
|
||||
getPageInfoFromSearchParams,
|
||||
modifyProcessModelPath,
|
||||
unModifyProcessModelPath,
|
||||
modifyProcessIdentifierForPathParam,
|
||||
unModifyProcessIdentifierForPathParam,
|
||||
} from '../helpers';
|
||||
import {
|
||||
PaginationObject,
|
||||
PermissionsToCheck,
|
||||
ProcessGroup,
|
||||
ProcessModel,
|
||||
// ProcessModel,
|
||||
} from '../interfaces';
|
||||
import { useUriListForPermissions } from '../hooks/UriListForPermissions';
|
||||
import { usePermissionFetcher } from '../hooks/PermissionService';
|
||||
import ProcessGroupListTiles from '../components/ProcessGroupListTiles';
|
||||
import ButtonWithConfirmation from '../components/ButtonWithConfirmation';
|
||||
import ProcessModelListTiles from '../components/ProcessModelListTiles';
|
||||
|
||||
export default function ProcessGroupShow() {
|
||||
const params = useParams();
|
||||
const [searchParams] = useSearchParams();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [processGroup, setProcessGroup] = useState<ProcessGroup | null>(null);
|
||||
const [processModels, setProcessModels] = useState([]);
|
||||
// const [processModels, setProcessModels] = useState([]);
|
||||
const [modelPagination, setModelPagination] =
|
||||
useState<PaginationObject | null>(null);
|
||||
|
||||
const { targetUris } = useUriListForPermissions();
|
||||
const permissionRequestData: PermissionsToCheck = {
|
||||
[targetUris.processGroupListPath]: ['POST'],
|
||||
[targetUris.processGroupShowPath]: ['PUT'],
|
||||
[targetUris.processGroupShowPath]: ['PUT', 'DELETE'],
|
||||
[targetUris.processModelCreatePath]: ['POST'],
|
||||
};
|
||||
const { ability } = usePermissionFetcher(permissionRequestData);
|
||||
|
@ -42,12 +54,12 @@ export default function ProcessGroupShow() {
|
|||
const { page, perPage } = getPageInfoFromSearchParams(searchParams);
|
||||
|
||||
const setProcessModelFromResult = (result: any) => {
|
||||
setProcessModels(result.results);
|
||||
// setProcessModels(result.results);
|
||||
setModelPagination(result.pagination);
|
||||
};
|
||||
const processResult = (result: any) => {
|
||||
setProcessGroup(result);
|
||||
const unmodifiedProcessGroupId = unModifyProcessModelPath(
|
||||
const unmodifiedProcessGroupId = unModifyProcessIdentifierForPathParam(
|
||||
(params as any).process_group_id
|
||||
);
|
||||
HttpService.makeCallToBackend({
|
||||
|
@ -61,56 +73,105 @@ export default function ProcessGroupShow() {
|
|||
});
|
||||
}, [params, searchParams]);
|
||||
|
||||
const buildModelTable = () => {
|
||||
if (processGroup === null) {
|
||||
return null;
|
||||
// const buildModelTable = () => {
|
||||
// if (processGroup === null) {
|
||||
// return null;
|
||||
// }
|
||||
// const rows = processModels.map((row: ProcessModel) => {
|
||||
// const modifiedProcessModelId: String =
|
||||
// modifyProcessIdentifierForPathParam((row as any).id);
|
||||
// return (
|
||||
// <tr key={row.id}>
|
||||
// <td>
|
||||
// <Link
|
||||
// to={`/admin/process-models/${modifiedProcessModelId}`}
|
||||
// data-qa="process-model-show-link"
|
||||
// >
|
||||
// {row.id}
|
||||
// </Link>
|
||||
// </td>
|
||||
// <td>{row.display_name}</td>
|
||||
// </tr>
|
||||
// );
|
||||
// });
|
||||
// return (
|
||||
// <div>
|
||||
// <h2>Process Models</h2>
|
||||
// <Table striped bordered>
|
||||
// <thead>
|
||||
// <tr>
|
||||
// <th>Process Model Id</th>
|
||||
// <th>Display Name</th>
|
||||
// </tr>
|
||||
// </thead>
|
||||
// <tbody>{rows}</tbody>
|
||||
// </Table>
|
||||
// </div>
|
||||
// );
|
||||
// };
|
||||
|
||||
const navigateToProcessGroups = (_result: any) => {
|
||||
navigate(`/admin/process-groups`);
|
||||
};
|
||||
|
||||
const deleteProcessGroup = () => {
|
||||
if (processGroup) {
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-groups/${modifyProcessIdentifierForPathParam(
|
||||
processGroup.id
|
||||
)}`,
|
||||
successCallback: navigateToProcessGroups,
|
||||
httpMethod: 'DELETE',
|
||||
});
|
||||
}
|
||||
const rows = processModels.map((row: ProcessModel) => {
|
||||
const modifiedProcessModelId: String = modifyProcessModelPath(
|
||||
(row as any).id
|
||||
);
|
||||
return (
|
||||
<tr key={row.id}>
|
||||
<td>
|
||||
<Link
|
||||
to={`/admin/process-models/${modifiedProcessModelId}`}
|
||||
data-qa="process-model-show-link"
|
||||
>
|
||||
{row.id}
|
||||
</Link>
|
||||
</td>
|
||||
<td>{row.display_name}</td>
|
||||
</tr>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<div>
|
||||
<h2>Process Models</h2>
|
||||
<Table striped bordered>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Process Model Id</th>
|
||||
<th>Display Name</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>{rows}</tbody>
|
||||
</Table>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
if (processGroup && modelPagination) {
|
||||
const { page, perPage } = getPageInfoFromSearchParams(searchParams);
|
||||
const modifiedProcessGroupId = modifyProcessModelPath(processGroup.id);
|
||||
// const { page, perPage } = getPageInfoFromSearchParams(searchParams);
|
||||
const modifiedProcessGroupId = modifyProcessIdentifierForPathParam(
|
||||
processGroup.id
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<ProcessBreadcrumb
|
||||
hotCrumbs={[
|
||||
['Process Groups', '/admin'],
|
||||
['', `process_group:${processGroup.id}`],
|
||||
{
|
||||
entityToExplode: processGroup,
|
||||
entityType: 'process-group',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<h1>Process Group: {processGroup.display_name}</h1>
|
||||
<Stack orientation="horizontal" gap={1}>
|
||||
<h1 className="with-icons">
|
||||
Process Group: {processGroup.display_name}
|
||||
</h1>
|
||||
<Can I="PUT" a={targetUris.processGroupShowPath} ability={ability}>
|
||||
<Button
|
||||
kind="ghost"
|
||||
data-qa="edit-process-group-button"
|
||||
renderIcon={Edit}
|
||||
iconDescription="Edit Process Group"
|
||||
hasIconOnly
|
||||
href={`/admin/process-groups/${modifiedProcessGroupId}/edit`}
|
||||
>
|
||||
Edit process group
|
||||
</Button>
|
||||
</Can>
|
||||
<Can I="DELETE" a={targetUris.processGroupShowPath} ability={ability}>
|
||||
<ButtonWithConfirmation
|
||||
kind="ghost"
|
||||
data-qa="delete-process-group-button"
|
||||
renderIcon={TrashCan}
|
||||
iconDescription="Delete Process Group"
|
||||
hasIconOnly
|
||||
description={`Delete process group: ${processGroup.display_name}`}
|
||||
onConfirmation={deleteProcessGroup}
|
||||
confirmButtonLabel="Delete"
|
||||
/>
|
||||
</Can>
|
||||
</Stack>
|
||||
<p className="process-description">{processGroup.description}</p>
|
||||
<ul>
|
||||
<Stack orientation="horizontal" gap={3}>
|
||||
<Can I="POST" a={targetUris.processGroupListPath} ability={ability}>
|
||||
|
@ -131,30 +192,27 @@ export default function ProcessGroupShow() {
|
|||
Add a process model
|
||||
</Button>
|
||||
</Can>
|
||||
<Can I="PUT" a={targetUris.processGroupShowPath} ability={ability}>
|
||||
<Button
|
||||
href={`/admin/process-groups/${modifiedProcessGroupId}/edit`}
|
||||
>
|
||||
Edit process group
|
||||
</Button>
|
||||
</Can>
|
||||
</Stack>
|
||||
<br />
|
||||
<br />
|
||||
<ProcessModelListTiles
|
||||
headerElement={<h2>Process Models</h2>}
|
||||
processGroup={processGroup}
|
||||
/>
|
||||
{/* eslint-disable-next-line sonarjs/no-gratuitous-expressions */}
|
||||
{modelPagination && modelPagination.total > 0 && (
|
||||
{/* {modelPagination && modelPagination.total > 0 && (
|
||||
<PaginationForTable
|
||||
page={page}
|
||||
perPage={perPage}
|
||||
pagination={modelPagination}
|
||||
tableToDisplay={buildModelTable()}
|
||||
/>
|
||||
)}
|
||||
)} */}
|
||||
<br />
|
||||
<br />
|
||||
<ProcessGroupListTiles
|
||||
processGroup={processGroup}
|
||||
headerElement={<h2>Process Groups</h2>}
|
||||
headerElement={<h2 className="clear-left">Process Groups</h2>}
|
||||
/>
|
||||
</ul>
|
||||
</>
|
||||
|
|
|
@ -1,25 +1,27 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
// @ts-ignore
|
||||
import { Table } from '@carbon/react';
|
||||
import { Table, Tabs, TabList, Tab } from '@carbon/react';
|
||||
import { useParams, useSearchParams, Link } from 'react-router-dom';
|
||||
import PaginationForTable from '../components/PaginationForTable';
|
||||
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
|
||||
import {
|
||||
getPageInfoFromSearchParams,
|
||||
modifyProcessModelPath,
|
||||
unModifyProcessModelPath,
|
||||
modifyProcessIdentifierForPathParam,
|
||||
convertSecondsToFormattedDateTime,
|
||||
} from '../helpers';
|
||||
import HttpService from '../services/HttpService';
|
||||
import { useUriListForPermissions } from '../hooks/UriListForPermissions';
|
||||
|
||||
export default function ProcessInstanceLogList() {
|
||||
const params = useParams();
|
||||
const [searchParams] = useSearchParams();
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const [processInstanceLogs, setProcessInstanceLogs] = useState([]);
|
||||
const [pagination, setPagination] = useState(null);
|
||||
const modifiedProcessModelId = modifyProcessModelPath(
|
||||
const modifiedProcessModelId = modifyProcessIdentifierForPathParam(
|
||||
`${params.process_model_id}`
|
||||
);
|
||||
const { targetUris } = useUriListForPermissions();
|
||||
const isDetailedView = searchParams.get('detailed') === 'true';
|
||||
|
||||
useEffect(() => {
|
||||
const setProcessInstanceLogListFromResult = (result: any) => {
|
||||
|
@ -28,26 +30,36 @@ export default function ProcessInstanceLogList() {
|
|||
};
|
||||
const { page, perPage } = getPageInfoFromSearchParams(searchParams);
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-instances/${params.process_instance_id}/logs?per_page=${perPage}&page=${page}`,
|
||||
path: `${targetUris.processInstanceLogListPath}?per_page=${perPage}&page=${page}&detailed=${isDetailedView}`,
|
||||
successCallback: setProcessInstanceLogListFromResult,
|
||||
});
|
||||
}, [searchParams, params]);
|
||||
}, [
|
||||
searchParams,
|
||||
params,
|
||||
targetUris.processInstanceLogListPath,
|
||||
isDetailedView,
|
||||
]);
|
||||
|
||||
const buildTable = () => {
|
||||
const rows = processInstanceLogs.map((row) => {
|
||||
const rowToUse = row as any;
|
||||
return (
|
||||
<tr key={rowToUse.id}>
|
||||
<td>{rowToUse.bpmn_process_identifier}</td>
|
||||
<td>{rowToUse.id}</td>
|
||||
<td>{rowToUse.message}</td>
|
||||
<td>{rowToUse.bpmn_task_identifier}</td>
|
||||
<td>{rowToUse.bpmn_task_name}</td>
|
||||
<td>{rowToUse.bpmn_task_type}</td>
|
||||
{isDetailedView && (
|
||||
<>
|
||||
<td>{rowToUse.bpmn_task_identifier}</td>
|
||||
<td>{rowToUse.bpmn_task_type}</td>
|
||||
<td>{rowToUse.bpmn_process_identifier}</td>
|
||||
</>
|
||||
)}
|
||||
<td>{rowToUse.username}</td>
|
||||
<td>
|
||||
<Link
|
||||
data-qa="process-instance-show-link"
|
||||
to={`/admin/process-models/${modifiedProcessModelId}/process-instances/${rowToUse.process_instance_id}/${rowToUse.spiff_step}`}
|
||||
to={`/admin/process-instances/${modifiedProcessModelId}/${rowToUse.process_instance_id}/${rowToUse.spiff_step}`}
|
||||
>
|
||||
{convertSecondsToFormattedDateTime(rowToUse.timestamp)}
|
||||
</Link>
|
||||
|
@ -59,11 +71,16 @@ export default function ProcessInstanceLogList() {
|
|||
<Table size="lg">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Bpmn Process Identifier</th>
|
||||
<th>Id</th>
|
||||
<th>Message</th>
|
||||
<th>Task Identifier</th>
|
||||
<th>Task Name</th>
|
||||
<th>Task Type</th>
|
||||
{isDetailedView && (
|
||||
<>
|
||||
<th>Task Identifier</th>
|
||||
<th>Task Type</th>
|
||||
<th>Bpmn Process Identifier</th>
|
||||
</>
|
||||
)}
|
||||
<th>User</th>
|
||||
<th>Timestamp</th>
|
||||
</tr>
|
||||
|
@ -72,34 +89,57 @@ export default function ProcessInstanceLogList() {
|
|||
</Table>
|
||||
);
|
||||
};
|
||||
const selectedTabIndex = isDetailedView ? 1 : 0;
|
||||
|
||||
if (pagination) {
|
||||
const { page, perPage } = getPageInfoFromSearchParams(searchParams);
|
||||
return (
|
||||
<main>
|
||||
<>
|
||||
<ProcessBreadcrumb
|
||||
hotCrumbs={[
|
||||
['Process Groups', '/admin'],
|
||||
[
|
||||
`Process Model: ${params.process_model_id}`,
|
||||
`process_model:${unModifyProcessModelPath(
|
||||
params.process_model_id || ''
|
||||
)}:link`,
|
||||
],
|
||||
{
|
||||
entityToExplode: params.process_model_id || '',
|
||||
entityType: 'process-model-id',
|
||||
linkLastItem: true,
|
||||
},
|
||||
[
|
||||
`Process Instance: ${params.process_instance_id}`,
|
||||
`/admin/process-models/${params.process_model_id}/process-instances/${params.process_instance_id}`,
|
||||
`/admin/process-instances/${params.process_model_id}/${params.process_instance_id}`,
|
||||
],
|
||||
['Logs'],
|
||||
]}
|
||||
/>
|
||||
<Tabs selectedIndex={selectedTabIndex}>
|
||||
<TabList aria-label="List of tabs">
|
||||
<Tab
|
||||
title="Only show a subset of the logs, and show fewer columns"
|
||||
onClick={() => {
|
||||
searchParams.set('detailed', 'false');
|
||||
setSearchParams(searchParams);
|
||||
}}
|
||||
>
|
||||
Simple
|
||||
</Tab>
|
||||
<Tab
|
||||
title="Show all logs for this process instance, and show extra columns that may be useful for debugging"
|
||||
onClick={() => {
|
||||
searchParams.set('detailed', 'true');
|
||||
setSearchParams(searchParams);
|
||||
}}
|
||||
>
|
||||
Detailed
|
||||
</Tab>
|
||||
</TabList>
|
||||
</Tabs>
|
||||
<br />
|
||||
<PaginationForTable
|
||||
page={page}
|
||||
perPage={perPage}
|
||||
pagination={pagination}
|
||||
tableToDisplay={buildTable()}
|
||||
/>
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
|
|
|
@ -2,12 +2,22 @@ import { useEffect, useState } from 'react';
|
|||
// @ts-ignore
|
||||
import { Button, Table } from '@carbon/react';
|
||||
import { useParams, Link } from 'react-router-dom';
|
||||
import { Can } from '@casl/react';
|
||||
import HttpService from '../services/HttpService';
|
||||
import { useUriListForPermissions } from '../hooks/UriListForPermissions';
|
||||
import { PermissionsToCheck } from '../interfaces';
|
||||
import { usePermissionFetcher } from '../hooks/PermissionService';
|
||||
|
||||
export default function ProcessInstanceReportList() {
|
||||
const params = useParams();
|
||||
const [processInstanceReports, setProcessInstanceReports] = useState([]);
|
||||
|
||||
const { targetUris } = useUriListForPermissions();
|
||||
const permissionRequestData: PermissionsToCheck = {
|
||||
[targetUris.processInstanceReportListPath]: ['POST'],
|
||||
};
|
||||
const { ability } = usePermissionFetcher(permissionRequestData);
|
||||
|
||||
useEffect(() => {
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-instances/reports`,
|
||||
|
@ -45,9 +55,11 @@ export default function ProcessInstanceReportList() {
|
|||
const headerStuff = (
|
||||
<>
|
||||
<h1>Process Instance Perspectives</h1>
|
||||
<Button href="/admin/process-instances/reports/new">
|
||||
Add a process instance perspective
|
||||
</Button>
|
||||
<Can I="POST" a={targetUris.processInstanceListPath} ability={ability}>
|
||||
<Button href="/admin/process-instances/reports/new">
|
||||
Add a process instance perspective
|
||||
</Button>
|
||||
</Can>
|
||||
</>
|
||||
);
|
||||
if (processInstanceReports?.length > 0) {
|
||||
|
|
|
@ -76,9 +76,7 @@ export default function ProcessInstanceReport() {
|
|||
return (
|
||||
<main>
|
||||
<ProcessBreadcrumb
|
||||
processModelId={params.process_model_id}
|
||||
processGroupId={params.process_group_id}
|
||||
linkProcessModel
|
||||
hotCrumbs={[['Process Groups', '/admin'], ['Process Instance']]}
|
||||
/>
|
||||
<h1>Process Instance Perspective: {params.report_identifier}</h1>
|
||||
<Button
|
||||
|
|
|
@ -29,7 +29,7 @@ import HttpService from '../services/HttpService';
|
|||
import ReactDiagramEditor from '../components/ReactDiagramEditor';
|
||||
import {
|
||||
convertSecondsToFormattedDateTime,
|
||||
unModifyProcessModelPath,
|
||||
unModifyProcessIdentifierForPathParam,
|
||||
} from '../helpers';
|
||||
import ButtonWithConfirmation from '../components/ButtonWithConfirmation';
|
||||
import ErrorContext from '../contexts/ErrorContext';
|
||||
|
@ -43,13 +43,14 @@ export default function ProcessInstanceShow() {
|
|||
|
||||
const [processInstance, setProcessInstance] = useState(null);
|
||||
const [tasks, setTasks] = useState<Array<object> | null>(null);
|
||||
const [tasksCallHadError, setTasksCallHadError] = useState<boolean>(false);
|
||||
const [taskToDisplay, setTaskToDisplay] = useState<object | null>(null);
|
||||
const [taskDataToDisplay, setTaskDataToDisplay] = useState<string>('');
|
||||
const [editingTaskData, setEditingTaskData] = useState<boolean>(false);
|
||||
|
||||
const setErrorMessage = (useContext as any)(ErrorContext)[1];
|
||||
|
||||
const unModifiedProcessModelId = unModifyProcessModelPath(
|
||||
const unModifiedProcessModelId = unModifyProcessIdentifierForPathParam(
|
||||
`${params.process_model_id}`
|
||||
);
|
||||
const modifiedProcessModelId = params.process_model_id;
|
||||
|
@ -57,8 +58,16 @@ export default function ProcessInstanceShow() {
|
|||
const { targetUris } = useUriListForPermissions();
|
||||
const permissionRequestData: PermissionsToCheck = {
|
||||
[targetUris.messageInstanceListPath]: ['GET'],
|
||||
[targetUris.processInstanceTaskListPath]: ['GET'],
|
||||
[targetUris.processInstanceActionPath]: ['DELETE'],
|
||||
[targetUris.processInstanceLogListPath]: ['GET'],
|
||||
[`${targetUris.processInstanceActionPath}/suspend`]: ['PUT'],
|
||||
[`${targetUris.processInstanceActionPath}/terminate`]: ['PUT'],
|
||||
[`${targetUris.processInstanceActionPath}/resume`]: ['PUT'],
|
||||
};
|
||||
const { ability } = usePermissionFetcher(permissionRequestData);
|
||||
const { ability, permissionsLoaded } = usePermissionFetcher(
|
||||
permissionRequestData
|
||||
);
|
||||
|
||||
const navigateToProcessInstances = (_result: any) => {
|
||||
navigate(
|
||||
|
@ -67,25 +76,33 @@ export default function ProcessInstanceShow() {
|
|||
};
|
||||
|
||||
useEffect(() => {
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-models/${modifiedProcessModelId}/process-instances/${params.process_instance_id}`,
|
||||
successCallback: setProcessInstance,
|
||||
});
|
||||
if (typeof params.spiff_step === 'undefined')
|
||||
if (permissionsLoaded) {
|
||||
const processTaskFailure = () => {
|
||||
setTasksCallHadError(true);
|
||||
};
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-instances/${modifiedProcessModelId}/${params.process_instance_id}/tasks?all_tasks=true`,
|
||||
successCallback: setTasks,
|
||||
path: `/process-instances/${modifiedProcessModelId}/${params.process_instance_id}`,
|
||||
successCallback: setProcessInstance,
|
||||
});
|
||||
else
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-instances/${modifiedProcessModelId}/${params.process_instance_id}/tasks?all_tasks=true&spiff_step=${params.spiff_step}`,
|
||||
successCallback: setTasks,
|
||||
});
|
||||
}, [params, modifiedProcessModelId]);
|
||||
let taskParams = '?all_tasks=true';
|
||||
if (typeof params.spiff_step !== 'undefined') {
|
||||
taskParams = `${taskParams}&spiff_step=${params.spiff_step}`;
|
||||
}
|
||||
if (ability.can('GET', targetUris.processInstanceTaskListPath)) {
|
||||
HttpService.makeCallToBackend({
|
||||
path: `${targetUris.processInstanceTaskListPath}${taskParams}`,
|
||||
successCallback: setTasks,
|
||||
failureCallback: processTaskFailure,
|
||||
});
|
||||
} else {
|
||||
setTasksCallHadError(true);
|
||||
}
|
||||
}
|
||||
}, [params, modifiedProcessModelId, permissionsLoaded, ability, targetUris]);
|
||||
|
||||
const deleteProcessInstance = () => {
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-instances/${params.process_instance_id}`,
|
||||
path: targetUris.processInstanceActionPath,
|
||||
successCallback: navigateToProcessInstances,
|
||||
httpMethod: 'DELETE',
|
||||
});
|
||||
|
@ -98,7 +115,7 @@ export default function ProcessInstanceShow() {
|
|||
|
||||
const terminateProcessInstance = () => {
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-instances/${params.process_instance_id}/terminate`,
|
||||
path: `${targetUris.processInstanceActionPath}/terminate`,
|
||||
successCallback: refreshPage,
|
||||
httpMethod: 'POST',
|
||||
});
|
||||
|
@ -106,7 +123,7 @@ export default function ProcessInstanceShow() {
|
|||
|
||||
const suspendProcessInstance = () => {
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-instances/${params.process_instance_id}/suspend`,
|
||||
path: `${targetUris.processInstanceActionPath}/suspend`,
|
||||
successCallback: refreshPage,
|
||||
httpMethod: 'POST',
|
||||
});
|
||||
|
@ -114,7 +131,7 @@ export default function ProcessInstanceShow() {
|
|||
|
||||
const resumeProcessInstance = () => {
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-instances/${params.process_instance_id}/resume`,
|
||||
path: `${targetUris.processInstanceActionPath}/resume`,
|
||||
successCallback: refreshPage,
|
||||
httpMethod: 'POST',
|
||||
});
|
||||
|
@ -162,7 +179,7 @@ export default function ProcessInstanceShow() {
|
|||
<Link
|
||||
reloadDocument
|
||||
data-qa="process-instance-step-link"
|
||||
to={`/admin/process-models/${
|
||||
to={`/admin/process-instances/${
|
||||
params.process_model_id
|
||||
}/process-instances/${params.process_instance_id}/${
|
||||
currentSpiffStep(processInstanceToUse) + distance
|
||||
|
@ -197,7 +214,7 @@ export default function ProcessInstanceShow() {
|
|||
if (currentEndDate) {
|
||||
currentEndDateTag = (
|
||||
<Grid condensed fullWidth>
|
||||
<Column sm={1} md={1} lg={1} className="grid-list-title">
|
||||
<Column sm={1} md={1} lg={2} className="grid-list-title">
|
||||
Completed:{' '}
|
||||
</Column>
|
||||
<Column sm={3} md={3} lg={3} className="grid-date">
|
||||
|
@ -223,7 +240,7 @@ export default function ProcessInstanceShow() {
|
|||
return (
|
||||
<>
|
||||
<Grid condensed fullWidth>
|
||||
<Column sm={1} md={1} lg={1} className="grid-list-title">
|
||||
<Column sm={1} md={1} lg={2} className="grid-list-title">
|
||||
Started:{' '}
|
||||
</Column>
|
||||
<Column sm={3} md={3} lg={3} className="grid-date">
|
||||
|
@ -234,7 +251,7 @@ export default function ProcessInstanceShow() {
|
|||
</Grid>
|
||||
{currentEndDateTag}
|
||||
<Grid condensed fullWidth>
|
||||
<Column sm={1} md={1} lg={1} className="grid-list-title">
|
||||
<Column sm={1} md={1} lg={2} className="grid-list-title">
|
||||
Status:{' '}
|
||||
</Column>
|
||||
<Column sm={3} md={3} lg={3}>
|
||||
|
@ -247,14 +264,20 @@ export default function ProcessInstanceShow() {
|
|||
<Grid condensed fullWidth>
|
||||
<Column sm={2} md={2} lg={2}>
|
||||
<ButtonSet>
|
||||
<Button
|
||||
size="sm"
|
||||
className="button-white-background"
|
||||
data-qa="process-instance-log-list-link"
|
||||
href={`/admin/process-models/${modifiedProcessModelId}/process-instances/${params.process_instance_id}/logs`}
|
||||
<Can
|
||||
I="GET"
|
||||
a={targetUris.processInstanceLogListPath}
|
||||
ability={ability}
|
||||
>
|
||||
Logs
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
className="button-white-background"
|
||||
data-qa="process-instance-log-list-link"
|
||||
href={`/admin/logs/${modifiedProcessModelId}/${params.process_instance_id}`}
|
||||
>
|
||||
Logs
|
||||
</Button>
|
||||
</Can>
|
||||
<Can
|
||||
I="GET"
|
||||
a={targetUris.messageInstanceListPath}
|
||||
|
@ -424,8 +447,8 @@ export default function ProcessInstanceShow() {
|
|||
// taskToUse is copy of taskToDisplay, with taskDataToDisplay in data attribute
|
||||
const taskToUse: any = { ...taskToDisplay, data: taskDataToDisplay };
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-instances/${params.process_instance_id}/task/${taskToUse.id}/update`,
|
||||
httpMethod: 'POST',
|
||||
path: `/task-data/${modifiedProcessModelId}/${params.process_instance_id}/${taskToUse.id}`,
|
||||
httpMethod: 'PUT',
|
||||
successCallback: saveTaskDataResult,
|
||||
failureCallback: saveTaskDataFailure,
|
||||
postBody: {
|
||||
|
@ -532,28 +555,40 @@ export default function ProcessInstanceShow() {
|
|||
|
||||
const buttonIcons = (processInstanceToUse: any) => {
|
||||
const elements = [];
|
||||
elements.push(terminateButton(processInstanceToUse));
|
||||
elements.push(suspendButton(processInstanceToUse));
|
||||
elements.push(resumeButton(processInstanceToUse));
|
||||
elements.push(
|
||||
<ButtonWithConfirmation
|
||||
data-qa="process-instance-delete"
|
||||
kind="ghost"
|
||||
renderIcon={TrashCan}
|
||||
iconDescription="Delete"
|
||||
hasIconOnly
|
||||
description={`Delete Process Instance: ${processInstanceToUse.id}`}
|
||||
onConfirmation={deleteProcessInstance}
|
||||
confirmButtonLabel="Delete"
|
||||
/>
|
||||
);
|
||||
if (
|
||||
ability.can('POST', `${targetUris.processInstanceActionPath}/terminate`)
|
||||
) {
|
||||
elements.push(terminateButton(processInstanceToUse));
|
||||
}
|
||||
if (
|
||||
ability.can('POST', `${targetUris.processInstanceActionPath}/suspend`)
|
||||
) {
|
||||
elements.push(suspendButton(processInstanceToUse));
|
||||
}
|
||||
if (ability.can('POST', `${targetUris.processInstanceActionPath}/resume`)) {
|
||||
elements.push(resumeButton(processInstanceToUse));
|
||||
}
|
||||
if (ability.can('DELETE', targetUris.processInstanceActionPath)) {
|
||||
elements.push(
|
||||
<ButtonWithConfirmation
|
||||
data-qa="process-instance-delete"
|
||||
kind="ghost"
|
||||
renderIcon={TrashCan}
|
||||
iconDescription="Delete"
|
||||
hasIconOnly
|
||||
description={`Delete Process Instance: ${processInstanceToUse.id}`}
|
||||
onConfirmation={deleteProcessInstance}
|
||||
confirmButtonLabel="Delete"
|
||||
/>
|
||||
);
|
||||
}
|
||||
return elements;
|
||||
};
|
||||
|
||||
if (processInstance && tasks) {
|
||||
if (processInstance && (tasks || tasksCallHadError)) {
|
||||
const processInstanceToUse = processInstance as any;
|
||||
const taskIds = getTaskIds();
|
||||
const processModelId = unModifyProcessModelPath(
|
||||
const processModelId = unModifyProcessIdentifierForPathParam(
|
||||
params.process_model_id ? params.process_model_id : ''
|
||||
);
|
||||
|
||||
|
@ -562,10 +597,11 @@ export default function ProcessInstanceShow() {
|
|||
<ProcessBreadcrumb
|
||||
hotCrumbs={[
|
||||
['Process Groups', '/admin'],
|
||||
[
|
||||
`Process Model: ${processModelId}`,
|
||||
`process_model:${processModelId}:link`,
|
||||
],
|
||||
{
|
||||
entityToExplode: processModelId,
|
||||
entityType: 'process-model-id',
|
||||
linkLastItem: true,
|
||||
},
|
||||
[`Process Instance Id: ${processInstanceToUse.id}`],
|
||||
]}
|
||||
/>
|
||||
|
|
|
@ -24,10 +24,11 @@ export default function ProcessModelEdit() {
|
|||
<ProcessBreadcrumb
|
||||
hotCrumbs={[
|
||||
['Process Groups', '/admin'],
|
||||
[
|
||||
`Process Model: ${processModel.id}`,
|
||||
`process_model:${processModel.id}:link`,
|
||||
],
|
||||
{
|
||||
entityToExplode: processModel,
|
||||
entityType: 'process-model',
|
||||
linkLastItem: true,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<h1>Edit Process Model: {(processModel as any).id}</h1>
|
||||
|
|
|
@ -17,7 +17,7 @@ import ReactDiagramEditor from '../components/ReactDiagramEditor';
|
|||
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
|
||||
import HttpService from '../services/HttpService';
|
||||
import ErrorContext from '../contexts/ErrorContext';
|
||||
import { makeid, modifyProcessModelPath } from '../helpers';
|
||||
import { makeid, modifyProcessIdentifierForPathParam } from '../helpers';
|
||||
import {
|
||||
CarbonComboBoxProcessSelection,
|
||||
ProcessFile,
|
||||
|
@ -94,7 +94,7 @@ export default function ProcessModelEditDiagram() {
|
|||
const [bpmnXmlForDiagramRendering, setBpmnXmlForDiagramRendering] =
|
||||
useState(null);
|
||||
|
||||
const modifiedProcessModelId = modifyProcessModelPath(
|
||||
const modifiedProcessModelId = modifyProcessIdentifierForPathParam(
|
||||
(params as any).process_model_id
|
||||
);
|
||||
|
||||
|
@ -283,7 +283,7 @@ export default function ProcessModelEditDiagram() {
|
|||
|
||||
const onServiceTasksRequested = (event: any) => {
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/service_tasks`,
|
||||
path: `/service-tasks`,
|
||||
successCallback: makeApiHandler(event),
|
||||
});
|
||||
};
|
||||
|
@ -735,7 +735,7 @@ export default function ProcessModelEditDiagram() {
|
|||
if (processModel) {
|
||||
const files = processModel.files.filter((f) => f.type === type);
|
||||
files.some((file) => {
|
||||
if (file.references.some((ref) => ref.id === id)) {
|
||||
if (file.references.some((ref) => ref.identifier === id)) {
|
||||
matchFile = file;
|
||||
return true;
|
||||
}
|
||||
|
@ -753,7 +753,7 @@ export default function ProcessModelEditDiagram() {
|
|||
const path = generatePath(
|
||||
'/admin/process-models/:process_model_path/files/:file_name',
|
||||
{
|
||||
process_model_path: modifyProcessModelPath(
|
||||
process_model_path: modifyProcessIdentifierForPathParam(
|
||||
processRef.process_model_id
|
||||
),
|
||||
file_name: processRef.file_name,
|
||||
|
@ -844,10 +844,11 @@ export default function ProcessModelEditDiagram() {
|
|||
<ProcessBreadcrumb
|
||||
hotCrumbs={[
|
||||
['Process Groups', '/admin'],
|
||||
[
|
||||
`Process Model: ${processModel.id}`,
|
||||
`process_model:${processModel.id}:link`,
|
||||
],
|
||||
{
|
||||
entityToExplode: processModel,
|
||||
entityType: 'process-model',
|
||||
linkLastItem: true,
|
||||
},
|
||||
[processModelFileName],
|
||||
]}
|
||||
/>
|
||||
|
|
|
@ -3,6 +3,7 @@ import { useParams } from 'react-router-dom';
|
|||
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
|
||||
import { ProcessModel } from '../interfaces';
|
||||
import ProcessModelForm from '../components/ProcessModelForm';
|
||||
import { unModifyProcessIdentifierForPathParam } from '../helpers';
|
||||
|
||||
export default function ProcessModelNew() {
|
||||
const params = useParams();
|
||||
|
@ -19,16 +20,19 @@ export default function ProcessModelNew() {
|
|||
<ProcessBreadcrumb
|
||||
hotCrumbs={[
|
||||
['Process Groups', '/admin'],
|
||||
[
|
||||
`Process Group: ${params.process_group_id}`,
|
||||
`process_group:${params.process_group_id}:link`,
|
||||
],
|
||||
{
|
||||
entityToExplode: params.process_group_id || '',
|
||||
entityType: 'process-group-id',
|
||||
linkLastItem: true,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<h1>Add Process Model</h1>
|
||||
<ProcessModelForm
|
||||
mode="new"
|
||||
processGroupId={params.process_group_id}
|
||||
processGroupId={unModifyProcessIdentifierForPathParam(
|
||||
params.process_group_id || ''
|
||||
)}
|
||||
processModel={processModel}
|
||||
setProcessModel={setProcessModel}
|
||||
/>
|
||||
|
|
|
@ -7,6 +7,8 @@ import {
|
|||
TrashCan,
|
||||
Favorite,
|
||||
Edit,
|
||||
View,
|
||||
ArrowRight,
|
||||
// @ts-ignore
|
||||
} from '@carbon/icons-react';
|
||||
import {
|
||||
|
@ -33,69 +35,20 @@ import HttpService from '../services/HttpService';
|
|||
import ErrorContext from '../contexts/ErrorContext';
|
||||
import {
|
||||
getGroupFromModifiedModelId,
|
||||
modifyProcessModelPath,
|
||||
modifyProcessIdentifierForPathParam,
|
||||
} from '../helpers';
|
||||
import {
|
||||
PermissionsToCheck,
|
||||
ProcessFile,
|
||||
ProcessInstance,
|
||||
ProcessModel,
|
||||
RecentProcessModel,
|
||||
} from '../interfaces';
|
||||
import ButtonWithConfirmation from '../components/ButtonWithConfirmation';
|
||||
import ProcessInstanceListTable from '../components/ProcessInstanceListTable';
|
||||
import { usePermissionFetcher } from '../hooks/PermissionService';
|
||||
import { useUriListForPermissions } from '../hooks/UriListForPermissions';
|
||||
import ProcessInstanceRun from '../components/ProcessInstanceRun';
|
||||
|
||||
const storeRecentProcessModelInLocalStorage = (
|
||||
processModelForStorage: ProcessModel
|
||||
) => {
|
||||
// All values stored in localStorage are strings.
|
||||
// Grab our recentProcessModels string from localStorage.
|
||||
const stringFromLocalStorage = window.localStorage.getItem(
|
||||
'recentProcessModels'
|
||||
);
|
||||
|
||||
// adapted from https://stackoverflow.com/a/59424458/6090676
|
||||
// If that value is null (meaning that we've never saved anything to that spot in localStorage before), use an empty array as our array. Otherwise, use the value we parse out.
|
||||
let array: RecentProcessModel[] = [];
|
||||
if (stringFromLocalStorage !== null) {
|
||||
// Then parse that string into an actual value.
|
||||
array = JSON.parse(stringFromLocalStorage);
|
||||
}
|
||||
|
||||
// Here's the value we want to add
|
||||
const value = {
|
||||
processModelIdentifier: processModelForStorage.id,
|
||||
processModelDisplayName: processModelForStorage.display_name,
|
||||
};
|
||||
|
||||
// anything with a processGroupIdentifier is old and busted. leave it behind.
|
||||
array = array.filter((item) => item.processGroupIdentifier === undefined);
|
||||
|
||||
// If our parsed/empty array doesn't already have this value in it...
|
||||
const matchingItem = array.find(
|
||||
(item) => item.processModelIdentifier === value.processModelIdentifier
|
||||
);
|
||||
if (matchingItem === undefined) {
|
||||
// add the value to the beginning of the array
|
||||
array.unshift(value);
|
||||
|
||||
// Keep the array to 3 items
|
||||
if (array.length > 3) {
|
||||
array.pop();
|
||||
}
|
||||
}
|
||||
|
||||
// once the old and busted serializations are gone, we can put these two statements inside the above if statement
|
||||
|
||||
// turn the array WITH THE NEW VALUE IN IT into a string to prepare it to be stored in localStorage
|
||||
const stringRepresentingArray = JSON.stringify(array);
|
||||
|
||||
// and store it in localStorage as "recentProcessModels"
|
||||
window.localStorage.setItem('recentProcessModels', stringRepresentingArray);
|
||||
};
|
||||
import { Notification } from '../components/Notification';
|
||||
|
||||
export default function ProcessModelShow() {
|
||||
const params = useParams();
|
||||
|
@ -108,18 +61,23 @@ export default function ProcessModelShow() {
|
|||
const [filesToUpload, setFilesToUpload] = useState<any>(null);
|
||||
const [showFileUploadModal, setShowFileUploadModal] =
|
||||
useState<boolean>(false);
|
||||
const [processModelPublished, setProcessModelPublished] = useState<any>(null);
|
||||
const [publishDisabled, setPublishDisabled] = useState<boolean>(false);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { targetUris } = useUriListForPermissions();
|
||||
const permissionRequestData: PermissionsToCheck = {
|
||||
[targetUris.processModelShowPath]: ['PUT', 'DELETE'],
|
||||
[targetUris.processModelPublishPath]: ['POST'],
|
||||
[targetUris.processInstanceListPath]: ['GET'],
|
||||
[targetUris.processInstanceActionPath]: ['POST'],
|
||||
[targetUris.processModelFileCreatePath]: ['POST', 'GET', 'DELETE'],
|
||||
[targetUris.processInstanceCreatePath]: ['POST'],
|
||||
[targetUris.processModelFileCreatePath]: ['POST', 'PUT', 'GET', 'DELETE'],
|
||||
};
|
||||
const { ability } = usePermissionFetcher(permissionRequestData);
|
||||
const { ability, permissionsLoaded } = usePermissionFetcher(
|
||||
permissionRequestData
|
||||
);
|
||||
|
||||
const modifiedProcessModelId = modifyProcessModelPath(
|
||||
const modifiedProcessModelId = modifyProcessIdentifierForPathParam(
|
||||
`${params.process_model_id}`
|
||||
);
|
||||
|
||||
|
@ -127,7 +85,6 @@ export default function ProcessModelShow() {
|
|||
const processResult = (result: ProcessModel) => {
|
||||
setProcessModel(result);
|
||||
setReloadModel(false);
|
||||
storeRecentProcessModelInLocalStorage(result);
|
||||
};
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-models/${modifiedProcessModelId}`,
|
||||
|
@ -138,18 +95,17 @@ export default function ProcessModelShow() {
|
|||
const processInstanceRunResultTag = () => {
|
||||
if (processInstance) {
|
||||
return (
|
||||
<div className="alert alert-success" role="alert">
|
||||
<p>
|
||||
Process Instance {processInstance.id} kicked off (
|
||||
<Link
|
||||
to={`/admin/process-models/${modifiedProcessModelId}/process-instances/${processInstance.id}`}
|
||||
data-qa="process-instance-show-link"
|
||||
>
|
||||
view
|
||||
</Link>
|
||||
).
|
||||
</p>
|
||||
</div>
|
||||
<Notification
|
||||
title="Process Instance Kicked Off:"
|
||||
onClose={() => setProcessInstance(null)}
|
||||
>
|
||||
<Link
|
||||
to={`/admin/process-instances/${modifiedProcessModelId}/${processInstance.id}`}
|
||||
data-qa="process-instance-show-link"
|
||||
>
|
||||
view
|
||||
</Link>
|
||||
</Notification>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
|
@ -249,6 +205,21 @@ export default function ProcessModelShow() {
|
|||
});
|
||||
};
|
||||
|
||||
const postPublish = (value: any) => {
|
||||
setPublishDisabled(false);
|
||||
setProcessModelPublished(value);
|
||||
};
|
||||
|
||||
const publishProcessModel = () => {
|
||||
setPublishDisabled(true);
|
||||
setProcessModelPublished(null);
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-models/${modifiedProcessModelId}/publish`,
|
||||
successCallback: postPublish,
|
||||
httpMethod: 'POST',
|
||||
});
|
||||
};
|
||||
|
||||
const navigateToFileEdit = (processModelFile: ProcessFile) => {
|
||||
const url = profileModelFileEditUrl(processModelFile);
|
||||
if (url) {
|
||||
|
@ -261,12 +232,18 @@ export default function ProcessModelShow() {
|
|||
isPrimaryBpmnFile: boolean
|
||||
) => {
|
||||
const elements = [];
|
||||
let icon = View;
|
||||
let actionWord = 'View';
|
||||
if (ability.can('PUT', targetUris.processModelFileCreatePath)) {
|
||||
icon = Edit;
|
||||
actionWord = 'Edit';
|
||||
}
|
||||
elements.push(
|
||||
<Can I="GET" a={targetUris.processModelFileCreatePath} ability={ability}>
|
||||
<Button
|
||||
kind="ghost"
|
||||
renderIcon={Edit}
|
||||
iconDescription="Edit File"
|
||||
renderIcon={icon}
|
||||
iconDescription={`${actionWord} File`}
|
||||
hasIconOnly
|
||||
size="lg"
|
||||
data-qa={`edit-file-${processModelFile.name.replace('.', '-')}`}
|
||||
|
@ -324,7 +301,7 @@ export default function ProcessModelShow() {
|
|||
};
|
||||
|
||||
const processModelFileList = () => {
|
||||
if (!processModel) {
|
||||
if (!processModel || !permissionsLoaded) {
|
||||
return null;
|
||||
}
|
||||
let constructedTag;
|
||||
|
@ -438,12 +415,16 @@ export default function ProcessModelShow() {
|
|||
);
|
||||
};
|
||||
|
||||
const processModelButtons = () => {
|
||||
const processModelFilesSection = () => {
|
||||
if (!processModel) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Grid condensed fullWidth>
|
||||
<Grid
|
||||
condensed
|
||||
fullWidth
|
||||
className="megacondensed process-model-files-section"
|
||||
>
|
||||
<Column md={5} lg={9} sm={3}>
|
||||
<Accordion align="end" open>
|
||||
<AccordionItem
|
||||
|
@ -514,6 +495,55 @@ export default function ProcessModelShow() {
|
|||
);
|
||||
};
|
||||
|
||||
const processInstanceListTableButton = () => {
|
||||
if (processModel) {
|
||||
return (
|
||||
<Grid fullWidth condensed>
|
||||
<Column sm={{ span: 3 }} md={{ span: 4 }} lg={{ span: 3 }}>
|
||||
<h2>Process Instances</h2>
|
||||
</Column>
|
||||
<Column
|
||||
sm={{ span: 1, offset: 3 }}
|
||||
md={{ span: 1, offset: 7 }}
|
||||
lg={{ span: 1, offset: 15 }}
|
||||
>
|
||||
<Button
|
||||
data-qa="process-instance-list-link"
|
||||
kind="ghost"
|
||||
renderIcon={ArrowRight}
|
||||
iconDescription="Go to Filterable List"
|
||||
hasIconOnly
|
||||
size="lg"
|
||||
onClick={() =>
|
||||
navigate(
|
||||
`/admin/process-instances?process_model_identifier=${processModel.id}`
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Column>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const processModelPublishMessage = () => {
|
||||
if (processModelPublished) {
|
||||
const prUrl: string = processModelPublished.pr_url;
|
||||
return (
|
||||
<Notification
|
||||
title="Model Published:"
|
||||
onClose={() => setProcessModelPublished(false)}
|
||||
>
|
||||
<a href={prUrl} target="_void()">
|
||||
View the changes and create a Pull Request
|
||||
</a>
|
||||
</Notification>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
if (processModel) {
|
||||
return (
|
||||
<>
|
||||
|
@ -521,20 +551,34 @@ export default function ProcessModelShow() {
|
|||
<ProcessBreadcrumb
|
||||
hotCrumbs={[
|
||||
['Process Groups', '/admin'],
|
||||
[
|
||||
`Process Model: ${processModel.id}`,
|
||||
`process_model:${processModel.id}`,
|
||||
],
|
||||
{
|
||||
entityToExplode: processModel,
|
||||
entityType: 'process-model',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
{processModelPublishMessage()}
|
||||
{processInstanceRunResultTag()}
|
||||
<Stack orientation="horizontal" gap={1}>
|
||||
<h1 className="with-icons">
|
||||
Process Model: {processModel.display_name}
|
||||
</h1>
|
||||
|
||||
<Can I="PUT" a={targetUris.processModelShowPath} ability={ability}>
|
||||
<Button
|
||||
kind="ghost"
|
||||
data-qa="edit-process-model-button"
|
||||
renderIcon={Edit}
|
||||
iconDescription="Edit Process Model"
|
||||
hasIconOnly
|
||||
href={`/admin/process-models/${modifiedProcessModelId}/edit`}
|
||||
>
|
||||
Edit process model
|
||||
</Button>
|
||||
</Can>
|
||||
<Can I="DELETE" a={targetUris.processModelShowPath} ability={ability}>
|
||||
<ButtonWithConfirmation
|
||||
kind="ghost"
|
||||
data-qa="delete-process-model-button"
|
||||
renderIcon={TrashCan}
|
||||
iconDescription="Delete Process Model"
|
||||
hasIconOnly
|
||||
|
@ -548,36 +592,38 @@ export default function ProcessModelShow() {
|
|||
<Stack orientation="horizontal" gap={3}>
|
||||
<Can
|
||||
I="POST"
|
||||
a={targetUris.processInstanceActionPath}
|
||||
a={targetUris.processInstanceCreatePath}
|
||||
ability={ability}
|
||||
>
|
||||
<ProcessInstanceRun
|
||||
processModel={processModel}
|
||||
onSuccessCallback={setProcessInstance}
|
||||
/>
|
||||
<>
|
||||
<ProcessInstanceRun
|
||||
processModel={processModel}
|
||||
onSuccessCallback={setProcessInstance}
|
||||
/>
|
||||
<br />
|
||||
<br />
|
||||
</>
|
||||
</Can>
|
||||
<Can I="PUT" a={targetUris.processModelShowPath} ability={ability}>
|
||||
<Button
|
||||
href={`/admin/process-models/${modifiedProcessModelId}/edit`}
|
||||
variant="secondary"
|
||||
>
|
||||
Edit process model
|
||||
<Can
|
||||
I="POST"
|
||||
a={targetUris.processModelPublishPath}
|
||||
ability={ability}
|
||||
>
|
||||
<Button disabled={publishDisabled} onClick={publishProcessModel}>
|
||||
Publish Changes
|
||||
</Button>
|
||||
</Can>
|
||||
</Stack>
|
||||
<br />
|
||||
<br />
|
||||
{processInstanceRunResultTag()}
|
||||
<br />
|
||||
{processModelFilesSection()}
|
||||
<Can I="GET" a={targetUris.processInstanceListPath} ability={ability}>
|
||||
{processInstanceListTableButton()}
|
||||
<ProcessInstanceListTable
|
||||
filtersEnabled={false}
|
||||
processModelFullIdentifier={processModel.id}
|
||||
perPageOptions={[2, 5, 25]}
|
||||
showReports={false}
|
||||
/>
|
||||
<br />
|
||||
</Can>
|
||||
{processModelButtons()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import { Button, Modal } from '@carbon/react';
|
|||
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
|
||||
import HttpService from '../services/HttpService';
|
||||
import ButtonWithConfirmation from '../components/ButtonWithConfirmation';
|
||||
import { modifyProcessModelPath, unModifyProcessModelPath } from '../helpers';
|
||||
import { modifyProcessIdentifierForPathParam } from '../helpers';
|
||||
import { ProcessFile } from '../interfaces';
|
||||
|
||||
// NOTE: This is mostly the same as ProcessModelEditDiagram and if we go this route could
|
||||
|
@ -38,7 +38,7 @@ export default function ReactFormEditor() {
|
|||
|
||||
const editorDefaultLanguage = fileExtension === 'md' ? 'markdown' : 'json';
|
||||
|
||||
const modifiedProcessModelId = modifyProcessModelPath(
|
||||
const modifiedProcessModelId = modifyProcessIdentifierForPathParam(
|
||||
`${params.process_model_id}`
|
||||
);
|
||||
|
||||
|
@ -156,14 +156,11 @@ export default function ReactFormEditor() {
|
|||
<ProcessBreadcrumb
|
||||
hotCrumbs={[
|
||||
['Process Groups', '/admin'],
|
||||
[
|
||||
`Process Model: ${unModifyProcessModelPath(
|
||||
params.process_model_id || ''
|
||||
)}`,
|
||||
`process_model:${unModifyProcessModelPath(
|
||||
params.process_model_id || ''
|
||||
)}:link`,
|
||||
],
|
||||
{
|
||||
entityToExplode: params.process_model_id || '',
|
||||
entityType: 'process-model-id',
|
||||
linkLastItem: true,
|
||||
},
|
||||
[processModelFileName],
|
||||
]}
|
||||
/>
|
||||
|
@ -190,6 +187,7 @@ export default function ReactFormEditor() {
|
|||
)}
|
||||
{params.file_name ? (
|
||||
<ButtonWithConfirmation
|
||||
data-qa="delete-process-model-file"
|
||||
description={`Delete file ${params.file_name}?`}
|
||||
onConfirmation={deleteFile}
|
||||
buttonLabel="Delete"
|
||||
|
|
|
@ -62,7 +62,7 @@ export default function SecretList() {
|
|||
<Table striped bordered>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Id</th>
|
||||
<th>Secret Key</th>
|
||||
<th>Creator</th>
|
||||
<th>Delete</th>
|
||||
|
|
|
@ -15,6 +15,7 @@ import {
|
|||
Tabs,
|
||||
Grid,
|
||||
Column,
|
||||
Button,
|
||||
// @ts-ignore
|
||||
} from '@carbon/react';
|
||||
|
||||
|
@ -24,7 +25,10 @@ import remarkGfm from 'remark-gfm';
|
|||
import Form from '../themes/carbon';
|
||||
import HttpService from '../services/HttpService';
|
||||
import ErrorContext from '../contexts/ErrorContext';
|
||||
import { modifyProcessModelPath } from '../helpers';
|
||||
import { modifyProcessIdentifierForPathParam } from '../helpers';
|
||||
import { useUriListForPermissions } from '../hooks/UriListForPermissions';
|
||||
import { PermissionsToCheck } from '../interfaces';
|
||||
import { usePermissionFetcher } from '../hooks/PermissionService';
|
||||
|
||||
export default function TaskShow() {
|
||||
const [task, setTask] = useState(null);
|
||||
|
@ -34,24 +38,36 @@ export default function TaskShow() {
|
|||
|
||||
const setErrorMessage = (useContext as any)(ErrorContext)[1];
|
||||
|
||||
useEffect(() => {
|
||||
const processResult = (result: any) => {
|
||||
setTask(result);
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-instances/${modifyProcessModelPath(
|
||||
result.process_model_identifier
|
||||
)}/${params.process_instance_id}/tasks`,
|
||||
successCallback: setUserTasks,
|
||||
});
|
||||
};
|
||||
const { targetUris } = useUriListForPermissions();
|
||||
const permissionRequestData: PermissionsToCheck = {
|
||||
[targetUris.processInstanceTaskListPath]: ['GET'],
|
||||
};
|
||||
const { ability, permissionsLoaded } = usePermissionFetcher(
|
||||
permissionRequestData
|
||||
);
|
||||
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/tasks/${params.process_instance_id}/${params.task_id}`,
|
||||
successCallback: processResult,
|
||||
// This causes the page to continuously reload
|
||||
// failureCallback: setErrorMessage,
|
||||
});
|
||||
}, [params]);
|
||||
useEffect(() => {
|
||||
if (permissionsLoaded) {
|
||||
const processResult = (result: any) => {
|
||||
setTask(result);
|
||||
if (ability.can('GET', targetUris.processInstanceTaskListPath)) {
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/task-data/${modifyProcessIdentifierForPathParam(
|
||||
result.process_model_identifier
|
||||
)}/${params.process_instance_id}`,
|
||||
successCallback: setUserTasks,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/tasks/${params.process_instance_id}/${params.task_id}`,
|
||||
successCallback: processResult,
|
||||
// This causes the page to continuously reload
|
||||
// failureCallback: setErrorMessage,
|
||||
});
|
||||
}
|
||||
}, [params, permissionsLoaded, ability, targetUris]);
|
||||
|
||||
const processSubmitResult = (result: any) => {
|
||||
setErrorMessage(null);
|
||||
|
@ -115,17 +131,18 @@ export default function TaskShow() {
|
|||
}
|
||||
return null;
|
||||
});
|
||||
return (
|
||||
<Tabs
|
||||
title="Steps in this process instance involving people"
|
||||
selectedIndex={selectedTabIndex}
|
||||
>
|
||||
<TabList aria-label="List of tabs" contained>
|
||||
{userTasksElement}
|
||||
</TabList>
|
||||
</Tabs>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Tabs
|
||||
title="Steps in this process instance involving people"
|
||||
selectedIndex={selectedTabIndex}
|
||||
>
|
||||
<TabList aria-label="List of tabs" contained>
|
||||
{userTasksElement}
|
||||
</TabList>
|
||||
</Tabs>
|
||||
);
|
||||
return null;
|
||||
};
|
||||
|
||||
const formElement = (taskToUse: any) => {
|
||||
|
@ -167,6 +184,14 @@ export default function TaskShow() {
|
|||
reactFragmentToHideSubmitButton = <div />;
|
||||
}
|
||||
|
||||
if (taskToUse.type === 'Manual Task') {
|
||||
reactFragmentToHideSubmitButton = (
|
||||
<div>
|
||||
<Button type="submit">Continue</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Grid fullWidth condensed>
|
||||
<Column md={5} lg={8} sm={4}>
|
||||
|
@ -198,7 +223,7 @@ export default function TaskShow() {
|
|||
);
|
||||
};
|
||||
|
||||
if (task && userTasks) {
|
||||
if (task) {
|
||||
const taskToUse = task as any;
|
||||
let statusString = '';
|
||||
if (taskToUse.state !== 'READY') {
|
||||
|
|
|
@ -66,9 +66,11 @@ backendCallProps) => {
|
|||
method: httpMethod,
|
||||
});
|
||||
|
||||
const updatedPath = path.replace(/^\/v1\.0/, '');
|
||||
|
||||
let isSuccessful = true;
|
||||
let is403 = false;
|
||||
fetch(`${BACKEND_BASE_URL}${path}`, httpArgs)
|
||||
fetch(`${BACKEND_BASE_URL}${updatedPath}`, httpArgs)
|
||||
.then((response) => {
|
||||
if (response.status === 401) {
|
||||
UserService.doLogin();
|
||||
|
|
Loading…
Reference in New Issue