Squashed 'spiffworkflow-frontend/' changes from cdae31a57..2149f03f5

2149f03f5 fix breadcrumb
4106c5aac Merge pull request #28 from sartography/bug/browser_lock_on_dmn_selection
d9f04b932 one instance test left for cypress w/ burnettk
db4ff9019 kill a few consoles
8fd0cbafc fixing up routes for launching editor. Also some fixes in the bpmn-js-spiffworkflow to avoid locking up the browser if no files are available.
063fc1e0c process model cypress tests are passing
a1de9eca1 camelcase
67116e6ac fix process model create and a couple tests, docker build refactor
6dbdbffad fixed lint issue w/ burnettk cullerton
441e300c6 merged in main and resolved conflicts w/ burnettk cullerton
8c29bc3f6 fixed some acceptance tests w/ burnettk cullerton
cf9af691e updated breadcrumbs to work with new process models ids w/ burnettk cullerton
23ff4e816 made a process model form w/ burnettk
dbe0b9c71 Merge branch 'main' into feature/nested-groups
099ce24bb lint
da1bd979d return next task when running an instance w/ burnettk
ee76c5c81 More frontend changes
ff0e4c762 process model show page lists files as accordion with action icons w/ burnettk
a8cf19162 Merge remote-tracking branch 'origin/main' into feature/carbon_process_model_show
0b334f08d some minor chnages to prepare for chnaging actions dropdown to buttons
4e7d4733f First pass at custom report/perspective for Process Instance List (#23)
381cd4578 change action dropdown direction based on if it is the last one or not
ec72afceb add back run and edit and add actions menu
331c079e1 added a table for files w/ burnettk
65874023b updated process modal show page to use accordion component w/ burnettk
d50d23f14 First stab at fixing routes and urls
d26b67865 Merge remote-tracking branch 'origin/main' into feature/carbon_process_model_show
6d8fee5eb Merge commit '44e49e6ae6a1f644162489a27618c39194f4628d' into main
01218b3ca update gitignore.
ee11c1c2f updated the breadcrumb component and added a test for buttons in file dropdown

git-subtree-dir: spiffworkflow-frontend
git-subtree-split: 2149f03f5d352ba2f40b4dc41e9435cfb396d0e5
This commit is contained in:
jasquat 2022-11-09 15:02:22 -05:00
parent 44e49e6ae6
commit 0a0bb2281a
35 changed files with 1207 additions and 599 deletions

3
.gitignore vendored
View File

@ -27,3 +27,6 @@ cypress/screenshots
# i keep accidentally committing these
/test*.json
# Editors
.idea

View File

@ -1,19 +1,23 @@
### STAGE 1: Build ###
FROM quay.io/sartography/node:latest
RUN mkdir /app
WORKDIR /app
ADD package.json /app/
ADD package-lock.json /app/
COPY . /app/
# this matches total memory on spiffworkflow-demo
ENV NODE_OPTIONS=--max_old_space_size=2048
ADD package.json /app/
ADD package-lock.json /app/
# npm ci because it respects the lock file.
# --ignore-scripts because authors can do bad things in postinstall scripts.
# https://cheatsheetseries.owasp.org/cheatsheets/NPM_Security_Cheat_Sheet.html
# npx can-i-ignore-scripts can check that it's safe to ignore scripts.
RUN npm ci --ignore-scripts
COPY . /app/
RUN npm run build
ENTRYPOINT ["/app/bin/boot_server_in_docker"]

View File

@ -3,10 +3,10 @@ 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 Range').click();
cy.contains('Start date from').click();
cy.get('#date-picker-end-from').clear().type(format(fromDate, DATE_FORMAT));
cy.contains('Start Range').click();
cy.contains('Filter').click();
cy.contains('End date from').click();
cy.getBySel('filter-button').click();
};
const updateDmnText = (oldText, newText, elementId = 'wonderful_process') => {
@ -68,6 +68,7 @@ describe('process-instances', () => {
cy.login();
cy.navigateToProcessModel(
'Acceptance Tests Group One',
'Acceptance Tests Model 1',
'acceptance-tests-model-1'
);
});
@ -90,28 +91,29 @@ describe('process-instances', () => {
cy.runPrimaryBpmnFile();
// Change dmn
cy.contains(dmnFile).click();
cy.contains(`Process Model File: ${dmnFile}`);
cy.getBySel('files-accordion').click();
cy.getBySel(`edit-file-${dmnFile.replace('.', '-')}`).click();
updateDmnText(originalDmnOutputForKevin, newDmnOutputForKevin);
cy.contains('acceptance-tests-model-1').click();
cy.runPrimaryBpmnFile();
cy.contains(dmnFile).click();
cy.contains(`Process Model File: ${dmnFile}`);
cy.getBySel('files-accordion').click();
cy.getBySel(`edit-file-${dmnFile.replace('.', '-')}`).click();
updateDmnText(newDmnOutputForKevin, originalDmnOutputForKevin);
cy.contains('acceptance-tests-model-1').click();
cy.runPrimaryBpmnFile();
// Change bpmn
cy.contains(bpmnFile).click();
cy.getBySel('files-accordion').click();
cy.getBySel(`edit-file-${bpmnFile.replace('.', '-')}`).click();
cy.contains(`Process Model File: ${bpmnFile}`);
updateBpmnPythonScript(newPythonScript);
cy.contains('acceptance-tests-model-1').click();
cy.runPrimaryBpmnFile();
cy.contains(bpmnFile).click();
cy.contains(`Process Model File: ${bpmnFile}`);
cy.getBySel('files-accordion').click();
cy.getBySel(`edit-file-${bpmnFile.replace('.', '-')}`).click();
updateBpmnPythonScript(originalPythonScript);
cy.contains('acceptance-tests-model-1').click();
cy.runPrimaryBpmnFile();
@ -125,13 +127,15 @@ describe('process-instances', () => {
const bpmnFile = 'process_model_one.bpmn';
// Change bpmn
cy.contains(bpmnFile).click();
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.contains(bpmnFile).click();
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();
@ -161,25 +165,27 @@ describe('process-instances', () => {
cy.basicPaginationTest();
});
it('can filter', () => {
it.only('can filter', () => {
cy.getBySel('process-instance-list-link').click();
cy.assertAtLeastOneItemInPaginatedResults();
PROCESS_STATUSES.forEach((processStatus) => {
if (!['all', 'waiting'].includes(processStatus)) {
cy.get('[name=process-status-selection]').click();
cy.get('[name=process-status-selection]').type(processStatus);
cy.get(`[aria-label=${processStatus}]`).click();
cy.contains('Process Status').click();
cy.contains('Filter').click();
cy.assertAtLeastOneItemInPaginatedResults();
cy.getBySel(`process-instance-status-${processStatus}`).contains(
processStatus
);
// there should really only be one, but in CI there are sometimes more
cy.get('button[aria-label=Remove]:first').click();
}
});
// PROCESS_STATUSES.forEach((processStatus) => {
// if (!['all', 'waiting'].includes(processStatus)) {
// cy.get('#process-instance-status-select').click();
// cy.get('#process-instance-status-select')
// .contains(processStatus)
// .click();
// // close the dropdown again
// cy.get('#process-instance-status-select').click();
// cy.getBySel('filter-button').click();
// cy.assertAtLeastOneItemInPaginatedResults();
// cy.getBySel(`process-instance-status-${processStatus}`).contains(
// 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();
// }
// });
const date = new Date();
date.setHours(date.getHours() - 1);

View File

@ -16,19 +16,14 @@ describe('process-models', () => {
const modelId = `test-model-2-${id}`;
cy.contains(groupDisplayName).click();
cy.createModel(groupId, modelId, modelDisplayName);
cy.contains(`Process Group: ${groupId}`).click();
cy.contains(modelId);
cy.contains(modelId).click();
cy.url().should('include', `process-models/${groupId}/${modelId}`);
cy.contains(`Process Model: ${modelId}`);
cy.url().should('include', `process-models/${groupId}:${modelId}`);
cy.contains(`Process Model: ${modelDisplayName}`);
cy.contains('Edit process model').click();
cy.get('input[name=display_name]').clear().type(newModelDisplayName);
cy.contains('Submit').click();
cy.contains(`Process Model: ${modelId}`);
cy.contains('Edit process model').click();
cy.contains(`Process Model: ${groupId}/${modelId}`);
cy.contains('Submit').click();
cy.get('input[name=display_name]').should(
'have.value',
newModelDisplayName
@ -36,7 +31,7 @@ describe('process-models', () => {
cy.contains('Delete').click();
cy.contains('Are you sure');
cy.contains('OK').click();
cy.getBySel('modal-confirmation-dialog').find('.cds--btn--danger').click();
cy.url().should('include', `process-groups/${groupId}`);
cy.contains(modelId).should('not.exist');
});
@ -55,18 +50,17 @@ describe('process-models', () => {
cy.contains(groupDisplayName).click();
cy.createModel(groupId, modelId, modelDisplayName);
cy.contains(`Process Group: ${groupId}`).click();
cy.contains(modelId);
cy.contains(groupId).click();
cy.contains(modelId).click();
cy.url().should('include', `process-models/${groupId}/${modelId}`);
cy.contains(`Process Model: ${modelId}`);
cy.url().should('include', `process-models/${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');
// add new bpmn file
cy.contains('Add New BPMN File').click();
cy.contains('New BPMN File').click();
cy.contains(/^Process Model File$/);
cy.get('g[data-element-id=StartEvent_1]').click().should('exist');
cy.contains('General').click();
@ -78,11 +72,12 @@ describe('process-models', () => {
cy.contains('Save Changes').click();
cy.contains(`Process Model File: ${bpmnFileName}`);
cy.contains(modelId).click();
cy.contains(`Process Model: ${modelId}`);
cy.contains(`Process Model: ${modelDisplayName}`);
cy.getBySel('files-accordion').click();
cy.contains(`${bpmnFileName}.bpmn`).should('exist');
// add new dmn file
cy.contains('Add New DMN File').click();
cy.contains('New DMN File').click();
cy.contains(/^Process Model File$/);
cy.get('g[data-element-id=decision_1]').click().should('exist');
cy.contains('General').click();
@ -91,11 +86,12 @@ describe('process-models', () => {
cy.contains('Save Changes').click();
cy.contains(`Process Model File: ${dmnFileName}`);
cy.contains(modelId).click();
cy.contains(`Process Model: ${modelId}`);
cy.contains(`Process Model: ${modelDisplayName}`);
cy.getBySel('files-accordion').click();
cy.contains(`${dmnFileName}.dmn`).should('exist');
// add new json file
cy.contains('Add New JSON File').click();
cy.contains('New JSON File').click();
cy.contains(/^Process Model File$/);
// Some reason, cypress evals json strings so we have to escape it it with '{{}'
cy.get('.view-line').type('{{} "test_key": "test_value" }');
@ -106,13 +102,14 @@ describe('process-models', () => {
// wait for json to load before clicking away to avoid network errors
cy.wait(500);
cy.contains(modelId).click();
cy.contains(`Process Model: ${modelId}`);
cy.contains(`Process Model: ${modelDisplayName}`);
cy.getBySel('files-accordion').click();
cy.contains(`${jsonFileName}.json`).should('exist');
cy.contains('Edit process model').click();
cy.contains('Delete').click();
cy.contains('Are you sure');
cy.contains('OK').click();
cy.getBySel('modal-confirmation-dialog').find('.cds--btn--danger').click();
cy.url().should('include', `process-groups/${groupId}`);
cy.contains(modelId).should('not.exist');
});
@ -128,36 +125,35 @@ describe('process-models', () => {
cy.contains(groupDisplayName).click();
cy.createModel(groupId, modelId, modelDisplayName);
// seeing if getBySel works better, because we are seeing tests fail in CI
// when looking for the "Add a process model" link, so it seems like the
// click on the breadcrumb element must have failed.
cy.getBySel('process-group-breadcrumb-link').click();
// cy.contains(`Process Group: ${groupId}`).click();
cy.contains(`${groupId}`).click();
cy.contains('Add a process model');
cy.contains(modelId).click();
cy.url().should('include', `process-models/${groupId}/${modelId}`);
cy.contains(`Process Model: ${modelId}`);
cy.url().should('include', `process-models/${groupId}:${modelId}`);
cy.contains(`Process Model: ${modelDisplayName}`);
cy.get('input[type=file]').selectFile(
cy.getBySel('files-accordion').click();
cy.getBySel('upload-file-button').click();
cy.contains('Add file').selectFile(
'cypress/fixtures/test_bpmn_file_upload.bpmn'
);
cy.contains('Submit').click();
cy.getBySel('modal-upload-file-dialog')
.find('.cds--btn--primary')
.contains('Upload')
.click();
cy.runPrimaryBpmnFile();
cy.getBySel('process-instance-list-link').click();
cy.getBySel('process-instance-show-link').click();
cy.contains('Delete').click();
cy.contains('Are you sure');
cy.contains('OK').click();
cy.getBySel('modal-confirmation-dialog').find('.cds--btn--danger').click();
cy.contains(`Process Instances for: ${groupId}/${modelId}`);
cy.contains(modelId).click();
cy.contains('Edit process model').click();
cy.contains('Delete').click();
cy.contains('Are you sure');
cy.contains('OK').click();
cy.getBySel('modal-confirmation-dialog').find('.cds--btn--danger').click();
cy.url().should('include', `process-groups/${groupId}`);
cy.contains(modelId).should('not.exist');
});

View File

@ -25,10 +25,11 @@ describe('tasks', () => {
it('can complete and navigate a form', () => {
const groupDisplayName = 'Acceptance Tests Group One';
const modelId = `acceptance-tests-model-2`;
const modelDisplayName = `Acceptance Tests Model 2`;
const completedTaskClassName = 'completed-task-highlight';
const activeTaskClassName = 'active-task-highlight';
cy.navigateToProcessModel(groupDisplayName, modelId);
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);
@ -67,7 +68,7 @@ describe('tasks', () => {
);
cy.contains('Task: get_user_generated_number_four');
cy.navigateToProcessModel(groupDisplayName, modelId);
cy.navigateToProcessModel(groupDisplayName, modelDisplayName, modelId);
cy.getBySel('process-instance-list-link').click();
cy.assertAtLeastOneItemInPaginatedResults();
@ -84,7 +85,7 @@ describe('tasks', () => {
checkTaskHasClass('form2', completedTaskClassName);
checkTaskHasClass('form3', completedTaskClassName);
checkTaskHasClass('form4', activeTaskClassName);
cy.get('.modal .btn-close').click();
cy.get('.is-visible .cds--modal-close').click();
cy.navigateToHome();
cy.contains('Tasks').should('exist');
@ -99,7 +100,7 @@ describe('tasks', () => {
);
cy.url().should('include', '/tasks');
cy.navigateToProcessModel(groupDisplayName, modelId);
cy.navigateToProcessModel(groupDisplayName, modelDisplayName, modelId);
cy.getBySel('process-instance-list-link').click();
cy.assertAtLeastOneItemInPaginatedResults();
@ -112,6 +113,7 @@ describe('tasks', () => {
it('can paginate items', () => {
cy.navigateToProcessModel(
'Acceptance Tests Group One',
'Acceptance Tests Model 2',
'acceptance-tests-model-2'
);

View File

@ -76,8 +76,8 @@ 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.contains(`Process Model: ${modelId}`);
cy.url().should('include', `process-models/${groupId}:${modelId}`);
cy.contains(`Process Model: ${modelDisplayName}`);
});
Cypress.Commands.add('runPrimaryBpmnFile', (reload = true) => {
@ -91,13 +91,12 @@ Cypress.Commands.add('runPrimaryBpmnFile', (reload = true) => {
Cypress.Commands.add(
'navigateToProcessModel',
(groupDisplayName, modelDisplayName) => {
(groupDisplayName, modelDisplayName, modelIdentifier) => {
cy.navigateToAdmin();
cy.contains(groupDisplayName).click();
cy.contains(`Process Group: ${groupDisplayName}`);
// https://stackoverflow.com/q/51254946/6090676
cy.getBySel('process-model-show-link').contains(modelDisplayName).click();
// cy.url().should('include', `process-models/${groupDisplayName}/${modelDisplayName}`);
cy.getBySel('process-model-show-link').contains(modelIdentifier).click();
cy.contains(`Process Model: ${modelDisplayName}`);
}
);
@ -115,12 +114,14 @@ Cypress.Commands.add('basicPaginationTest', () => {
});
Cypress.Commands.add('assertAtLeastOneItemInPaginatedResults', () => {
cy.getBySel('total-paginated-items')
.invoke('text')
.then(parseFloat)
.should('be.gt', 0);
cy.contains(/\b[1-9]\d*[1-9]\d* of [1-9]\d*/);
});
Cypress.Commands.add('assertNoItemInPaginatedResults', () => {
cy.getBySel('total-paginated-items').contains('0');
});
Cypress.Commands.add('modifyProcessModelPath', (path) => {
path.replace('/', ':');
return path;
});

20
package-lock.json generated
View File

@ -31,7 +31,7 @@
"bootstrap": "^5.2.0",
"bpmn-js": "^9.3.2",
"bpmn-js-properties-panel": "^1.10.0",
"bpmn-js-spiffworkflow": "sartography/bpmn-js-spiffworkflow#feature/more_launch_buttons_and_dropdowns",
"bpmn-js-spiffworkflow": "sartography/bpmn-js-spiffworkflow#main",
"craco": "^0.0.3",
"date-fns": "^2.28.0",
"diagram-js": "^8.5.0",
@ -7485,7 +7485,7 @@
},
"node_modules/bpmn-js-spiffworkflow": {
"version": "0.0.8",
"resolved": "git+ssh://git@github.com/sartography/bpmn-js-spiffworkflow.git#84593aee1ead7328efdc7da03ab3c9cd34364496",
"resolved": "git+ssh://git@github.com/sartography/bpmn-js-spiffworkflow.git#24c2cc36067adf8fed75990c6bf4a1a67bc9122b",
"license": "MIT",
"dependencies": {
"inherits": "^2.0.4",
@ -7890,9 +7890,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001418",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001418.tgz",
"integrity": "sha512-oIs7+JL3K9JRQ3jPZjlH6qyYDp+nBTCais7hjh0s+fuBwufc7uZ7hPYMXrDOJhV360KGMTcczMRObk0/iMqZRg==",
"version": "1.0.30001431",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001431.tgz",
"integrity": "sha512-zBUoFU0ZcxpvSt9IU66dXVT/3ctO1cy4y9cscs1szkPlcWb6pasYM144GqrUygUbT+k7cmUCW61cvskjcv0enQ==",
"funding": [
{
"type": "opencollective",
@ -35755,8 +35755,8 @@
}
},
"bpmn-js-spiffworkflow": {
"version": "git+ssh://git@github.com/sartography/bpmn-js-spiffworkflow.git#84593aee1ead7328efdc7da03ab3c9cd34364496",
"from": "bpmn-js-spiffworkflow@sartography/bpmn-js-spiffworkflow#feature/more_launch_buttons_and_dropdowns",
"version": "git+ssh://git@github.com/sartography/bpmn-js-spiffworkflow.git#24c2cc36067adf8fed75990c6bf4a1a67bc9122b",
"from": "bpmn-js-spiffworkflow@sartography/bpmn-js-spiffworkflow#main",
"requires": {
"inherits": "^2.0.4",
"inherits-browser": "^0.0.1",
@ -36070,9 +36070,9 @@
}
},
"caniuse-lite": {
"version": "1.0.30001418",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001418.tgz",
"integrity": "sha512-oIs7+JL3K9JRQ3jPZjlH6qyYDp+nBTCais7hjh0s+fuBwufc7uZ7hPYMXrDOJhV360KGMTcczMRObk0/iMqZRg=="
"version": "1.0.30001431",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001431.tgz",
"integrity": "sha512-zBUoFU0ZcxpvSt9IU66dXVT/3ctO1cy4y9cscs1szkPlcWb6pasYM144GqrUygUbT+k7cmUCW61cvskjcv0enQ=="
},
"case-sensitive-paths-webpack-plugin": {
"version": "2.4.0",

View File

@ -26,7 +26,7 @@
"bootstrap": "^5.2.0",
"bpmn-js": "^9.3.2",
"bpmn-js-properties-panel": "^1.10.0",
"bpmn-js-spiffworkflow": "sartography/bpmn-js-spiffworkflow#feature/more_launch_buttons_and_dropdowns",
"bpmn-js-spiffworkflow": "sartography/bpmn-js-spiffworkflow#main",
"craco": "^0.0.3",
"date-fns": "^2.28.0",
"diagram-js": "^8.5.0",

View File

@ -4,10 +4,14 @@ import { Button, Modal } from '@carbon/react';
type OwnProps = {
description?: string;
buttonLabel: string;
buttonLabel?: string;
onConfirmation: (..._args: any[]) => any;
title?: string;
confirmButtonLabel?: string;
kind?: string;
renderIcon?: boolean;
iconDescription?: string | null;
hasIconOnly?: boolean;
};
export default function ButtonWithConfirmation({
@ -16,6 +20,10 @@ export default function ButtonWithConfirmation({
onConfirmation,
title = 'Are you sure?',
confirmButtonLabel = 'OK',
kind = 'danger',
renderIcon = false,
iconDescription = null,
hasIconOnly = false,
}: OwnProps) {
const [showConfirmationPrompt, setShowConfirmationPrompt] = useState(false);
@ -49,7 +57,13 @@ export default function ButtonWithConfirmation({
return (
<>
<Button onClick={handleShowConfirmationPrompt} kind="danger">
<Button
onClick={handleShowConfirmationPrompt}
kind={kind}
renderIcon={renderIcon}
iconDescription={iconDescription}
hasIconOnly={hasIconOnly}
>
{buttonLabel}
</Button>
{confirmationDialog()}

View File

@ -1,5 +1,6 @@
import React from 'react';
import HttpService from '../services/HttpService';
import { modifyProcessModelPath } from '../helpers';
type Props = {
processGroupId: string;
@ -27,7 +28,10 @@ export default class FileInput extends React.Component<Props> {
handleSubmit(event: any) {
event.preventDefault();
const url = `/process-models/${this.processGroupId}/${this.processModelId}/files`;
const modifiedProcessModelId = modifyProcessModelPath(
`${this.processGroupId}/${this.processModelId}`
);
const url = `/process-models/${modifiedProcessModelId}/files`;
const formData = new FormData();
formData.append('file', this.fileInput.current.files[0]);
formData.append('fileName', this.fileInput.current.files[0].name);

View File

@ -1,12 +1,46 @@
import { Link } from 'react-router-dom';
import Breadcrumb from 'react-bootstrap/Breadcrumb';
import { BreadcrumbItem } from '../interfaces';
// @ts-ignore
import { Breadcrumb, BreadcrumbItem } from '@carbon/react';
import { splitProcessModelId } from '../helpers';
import { HotCrumbItem } from '../interfaces';
type OwnProps = {
processModelId?: string;
processGroupId?: string;
linkProcessModel?: boolean;
hotCrumbs?: BreadcrumbItem[];
hotCrumbs?: HotCrumbItem[];
};
const explodeCrumb = (crumb: HotCrumbItem) => {
const url: string = crumb[1] || '';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [_unused, 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') {
const lastUrl = `/admin/process-models/${paths.join(':')}:${lastPathItem}`;
breadcrumbItems.push(
<BreadcrumbItem key={lastPathItem} href={lastUrl}>
{lastPathItem}
</BreadcrumbItem>
);
} else {
breadcrumbItems.push(
<BreadcrumbItem isCurrentPage>{lastPathItem}</BreadcrumbItem>
);
}
return breadcrumbItems;
};
export default function ProcessBreadcrumb({
@ -18,66 +52,58 @@ export default function ProcessBreadcrumb({
let processGroupBreadcrumb = null;
let processModelBreadcrumb = null;
if (hotCrumbs) {
const lastItem = hotCrumbs.pop();
if (lastItem === undefined) {
return null;
}
const lastCrumb = <Breadcrumb.Item active>{lastItem[0]}</Breadcrumb.Item>;
const leadingCrumbLinks = hotCrumbs.map((crumb) => {
const leadingCrumbLinks = hotCrumbs.map((crumb: any) => {
const valueLabel = crumb[0];
const url = crumb[1];
if (!url) {
return <BreadcrumbItem isCurrentPage>{valueLabel}</BreadcrumbItem>;
}
if (url && url.startsWith('process_model:')) {
return explodeCrumb(crumb);
}
return (
<Breadcrumb.Item key={valueLabel} linkAs={Link} linkProps={{ to: url }}>
<BreadcrumbItem key={valueLabel} href={url}>
{valueLabel}
</Breadcrumb.Item>
</BreadcrumbItem>
);
});
return (
<Breadcrumb>
{leadingCrumbLinks}
{lastCrumb}
</Breadcrumb>
);
return <Breadcrumb noTrailingSlash>{leadingCrumbLinks}</Breadcrumb>;
}
if (processModelId) {
if (linkProcessModel) {
processModelBreadcrumb = (
<Breadcrumb.Item
linkAs={Link}
linkProps={{
to: `/admin/process-models/${processGroupId}/${processModelId}`,
}}
<BreadcrumbItem
href={`/admin/process-models/${processGroupId}/${processModelId}`}
>
Process Model: {processModelId}
</Breadcrumb.Item>
{`Process Model: ${processModelId}`}
</BreadcrumbItem>
);
} else {
processModelBreadcrumb = (
<Breadcrumb.Item active>
Process Model: {processModelId}
</Breadcrumb.Item>
<BreadcrumbItem isCurrentPage>
{`Process Model: ${processModelId}`}
</BreadcrumbItem>
);
}
processGroupBreadcrumb = (
<Breadcrumb.Item
linkAs={Link}
<BreadcrumbItem
data-qa="process-group-breadcrumb-link"
linkProps={{ to: `/admin/process-groups/${processGroupId}` }}
href={`/admin/process-groups/${processGroupId}`}
>
Process Group: {processGroupId}
</Breadcrumb.Item>
{`Process Group: ${processGroupId}`}
</BreadcrumbItem>
);
} else if (processGroupId) {
processGroupBreadcrumb = (
<Breadcrumb.Item active>Process Group: {processGroupId}</Breadcrumb.Item>
<BreadcrumbItem isCurrentPage>
{`Process Group: ${processGroupId}`}
</BreadcrumbItem>
);
}
return (
<Breadcrumb>
<Breadcrumb.Item linkAs={Link} linkProps={{ to: '/admin' }}>
Process Groups
</Breadcrumb.Item>
<Breadcrumb noTrailingSlash>
<BreadcrumbItem href="/admin">Process Groups</BreadcrumbItem>
{processGroupBreadcrumb}
{processModelBreadcrumb}
</Breadcrumb>

View File

@ -95,7 +95,7 @@ export default function ProcessGroupForm({
const onDisplayNameChanged = (newDisplayName: any) => {
setDisplayNameInvalid(false);
const updateDict = { display_name: newDisplayName };
if (!idHasBeenUpdatedByUser) {
if (!idHasBeenUpdatedByUser && mode === 'new') {
Object.assign(updateDict, { id: slugifyString(newDisplayName) });
}
updateProcessGroup(updateDict);

View File

@ -0,0 +1,198 @@
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
// @ts-ignore
import { Button, ButtonSet, Form, Stack, TextInput } from '@carbon/react';
import {
getGroupFromModifiedModelId,
modifyProcessModelPath,
slugifyString,
} from '../helpers';
import HttpService from '../services/HttpService';
import { ProcessModel } from '../interfaces';
import ButtonWithConfirmation from './ButtonWithConfirmation';
type OwnProps = {
mode: string;
processModel: ProcessModel;
processGroupId?: string;
setProcessModel: (..._args: any[]) => any;
};
export default function ProcessModelForm({
mode,
processModel,
processGroupId,
setProcessModel,
}: OwnProps) {
const [identifierInvalid, setIdentifierInvalid] = useState<boolean>(false);
const [idHasBeenUpdatedByUser, setIdHasBeenUpdatedByUser] =
useState<boolean>(false);
const [displayNameInvalid, setDisplayNameInvalid] = useState<boolean>(false);
const navigate = useNavigate();
const modifiedProcessModelPath = modifyProcessModelPath(processModel.id);
const navigateToProcessModel = (result: ProcessModel) => {
if ('id' in result) {
const modifiedProcessModelPathFromResult = modifyProcessModelPath(
result.id
);
navigate(`/admin/process-models/${modifiedProcessModelPathFromResult}`);
}
};
const navigateToProcessModels = (_result: any) => {
navigate(
`/admin/process-groups/${getGroupFromModifiedModelId(
modifiedProcessModelPath
)}`
);
};
const hasValidIdentifier = (identifierToCheck: string) => {
return identifierToCheck.match(/^[a-z0-9][0-9a-z-]+[a-z0-9]$/);
};
const deleteProcessModel = () => {
HttpService.makeCallToBackend({
path: `/process-models/${modifiedProcessModelPath}`,
successCallback: navigateToProcessModels,
httpMethod: 'DELETE',
});
};
const handleFormSubmission = (event: any) => {
event.preventDefault();
let hasErrors = false;
if (!hasValidIdentifier(processModel.id)) {
setIdentifierInvalid(true);
hasErrors = true;
}
if (processModel.display_name === '') {
setDisplayNameInvalid(true);
hasErrors = true;
}
if (hasErrors) {
return;
}
let path = `/process-models`;
if (mode === 'edit') {
path = `/process-models/${modifiedProcessModelPath}`;
}
let httpMethod = 'POST';
if (mode === 'edit') {
httpMethod = 'PUT';
}
const postBody = {
display_name: processModel.display_name,
description: processModel.description,
};
if (mode === 'new') {
Object.assign(postBody, {
id: `${processGroupId}/${processModel.id}`,
});
}
HttpService.makeCallToBackend({
path,
successCallback: navigateToProcessModel,
httpMethod,
postBody,
});
};
const updateProcessModel = (newValues: any) => {
const processModelToCopy = {
...processModel,
};
Object.assign(processModelToCopy, newValues);
setProcessModel(processModelToCopy);
};
const onDisplayNameChanged = (newDisplayName: any) => {
setDisplayNameInvalid(false);
const updateDict = { display_name: newDisplayName };
if (!idHasBeenUpdatedByUser && mode === 'new') {
Object.assign(updateDict, { id: slugifyString(newDisplayName) });
}
updateProcessModel(updateDict);
};
const formElements = () => {
const textInputs = [
<TextInput
id="process-model-display-name"
name="display_name"
invalidText="Display Name is required."
invalid={displayNameInvalid}
labelText="Display Name*"
value={processModel.display_name}
onChange={(event: any) => {
onDisplayNameChanged(event.target.value);
}}
onBlur={(event: any) => console.log('event', event)}
/>,
];
if (mode === 'new') {
textInputs.push(
<TextInput
id="process-model-identifier"
name="id"
invalidText="Identifier is required and must be all lowercase characters and hyphens."
invalid={identifierInvalid}
labelText="Identifier*"
value={processModel.id}
onChange={(event: any) => {
updateProcessModel({ id: event.target.value });
// was invalid, and now valid
if (identifierInvalid && hasValidIdentifier(event.target.value)) {
setIdentifierInvalid(false);
}
setIdHasBeenUpdatedByUser(true);
}}
/>
);
}
textInputs.push(
<TextInput
id="process-model-description"
name="description"
labelText="Description"
value={processModel.description}
onChange={(event: any) =>
updateProcessModel({ description: event.target.value })
}
/>
);
return textInputs;
};
const formButtons = () => {
const buttons = [
<Button kind="secondary" type="submit">
Submit
</Button>,
];
if (mode === 'edit') {
buttons.push(
<ButtonWithConfirmation
description={`Delete Process Model ${processModel.id}?`}
onConfirmation={deleteProcessModel}
buttonLabel="Delete"
confirmButtonLabel="Delete"
/>
);
}
return <ButtonSet>{buttons}</ButtonSet>;
};
return (
<Form onSubmit={handleFormSubmission}>
<Stack gap={5}>
{formElements()}
{formButtons()}
</Stack>
</Form>
);
}

View File

@ -21,7 +21,7 @@ export default function ProcessModelSearch({
const shouldFilterProcessModel = (options: any) => {
const processModel: ProcessModel = options.item;
const { inputValue } = options;
return `${processModel.process_group_id}/${processModel.id} (${processModel.display_name})`.includes(
return `${processModel.id} (${processModel.display_name})`.includes(
inputValue
);
};
@ -33,9 +33,10 @@ export default function ProcessModelSearch({
items={processModels}
itemToString={(processModel: ProcessModel) => {
if (processModel) {
return `${processModel.process_group_id}/${
processModel.id
} (${truncateString(processModel.display_name, 20)})`;
return `${processModel.id} (${truncateString(
processModel.display_name,
20
)})`;
}
return null;
}}

View File

@ -59,7 +59,6 @@ import { makeid } from '../helpers';
type OwnProps = {
processModelId: string;
processGroupId: string;
diagramType: string;
readyOrWaitingBpmnTaskIds?: string[] | null;
completedTasksBpmnIds?: string[] | null;
@ -83,7 +82,6 @@ type OwnProps = {
// https://codesandbox.io/s/quizzical-lake-szfyo?file=/src/App.js was a handy reference
export default function ReactDiagramEditor({
processModelId,
processGroupId,
diagramType,
readyOrWaitingBpmnTaskIds,
completedTasksBpmnIds,
@ -408,6 +406,7 @@ export default function ReactDiagramEditor({
}
function fetchDiagramFromURL(urlToUse: any) {
console.log(`urlToUse: ${urlToUse}`);
fetch(urlToUse)
.then((response) => response.text())
.then((text) => {
@ -424,7 +423,7 @@ export default function ReactDiagramEditor({
function fetchDiagramFromJsonAPI() {
HttpService.makeCallToBackend({
path: `/process-models/${processGroupId}/${processModelId}/files/${fileName}`,
path: `/process-models/${processModelId}/files/${fileName}`,
successCallback: setDiagramXMLStringFromResponseJson,
});
}
@ -470,7 +469,6 @@ export default function ReactDiagramEditor({
completedTasksBpmnIds,
fileName,
performingXmlUpdates,
processGroupId,
processModelId,
url,
]);

View File

@ -95,13 +95,10 @@ export const getProcessModelFullIdentifierFromSearchParams = (
searchParams: any
) => {
let processModelFullIdentifier = null;
if (
searchParams.get('process_model_identifier') &&
searchParams.get('process_group_identifier')
) {
if (searchParams.get('process_model_identifier')) {
processModelFullIdentifier = `${searchParams.get(
'process_group_identifier'
)}/${searchParams.get('process_model_identifier')}`;
'process_model_identifier'
)}`;
}
return processModelFullIdentifier;
};
@ -113,3 +110,22 @@ export const truncateString = (text: string, len: number) => {
}
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) => {
return path.replace('/', ':') || '';
};
export const unModifyProcessModelPath = (path: string) => {
return path.replace(':', '/') || '';
};
export const getGroupFromModifiedModelId = (modifiedId: string) => {
const finalSplitIndex = modifiedId.lastIndexOf(':');
return modifiedId.slice(0, finalSplitIndex);
};
export const splitProcessModelId = (processModelId: string) => {
return processModelId.split('/');
};

View File

@ -40,6 +40,18 @@ span.bjs-crumb {
opacity: .4;
}
.accordion-item-label {
vertical-align: middle;
}
.cds--breadcrumb {
margin-bottom: 2em;
}
.process-description {
margin-bottom: 2em;
}
.diagram-editor-canvas {
border:1px solid #000000;
height:70vh;

View File

@ -1,10 +1,22 @@
// @use '@carbon/react/scss/themes';
// @use '@carbon/react/scss/theme' with ($theme: themes.$g100);
// @use '@carbon/react/scss/theme' with
// (
// $theme: (
// cds-link-primary: #525252
// )
// );
@use '@carbon/react';
@use '@carbon/styles';
// @include grid.flex-grid();
@use '@carbon/colors';
// @use '@carbon/react/scss/colors';
@use '@carbon/react/scss/themes';
// var(--cds-link-text-color, var(--cds-link-primary, #0f62fe))
// site is mainly using white theme.
// header is mainly using g100
@ -13,3 +25,75 @@
// background-color: colors.$gray-100;
color: white;
}
h1{
height: 36px;
font-family: 'IBM Plex Sans';
font-style: normal;
font-weight: 400;
font-size: 28px;
line-height: 36px;
color: #161616;
flex: none;
order: 0;
align-self: stretch;
flex-grow: 0;
margin-bottom: 1em
}
.cds--breadcrumb-item a.cds--link:hover {
color: #525252;
}
.cds--breadcrumb-item a.cds--link:visited {
color: #525252;
}
.cds--breadcrumb-item a.cds--link:visited:hover {
color: #525252;
}
.cds--breadcrumb-item a.cds--link {
color: #525252;
}
.cds--btn--ghost {
color: black;
}
.cds--btn--ghost:visited {
color: black;
}
.cds--btn--ghost:hover {
color: black;
}
.cds--btn--ghost:visited:hover {
color: black;
}
$slightly-lighter-gray: #474747;
$spiff-header-background-color: #161616;
.cds--header__global .cds--btn--primary {
background-color: $spiff-header-background-color;
}
.cds--btn--primary {
background-color: #393939;
}
.cds--btn--primary:hover {
background-color: $slightly-lighter-gray;
}
// .cds--btn--ghost:visited {
// color: black;
// }
// .cds--btn--ghost:hover {
// color: black;
// }
// .cds--btn--ghost:visited:hover {
// color: black;
// }
// :root {
// --cds-link-primary: #525252;
// }
// .card {
// background: var(--orange);
// --orange: hsl(255, 72%, var(--lightness));
// }

View File

@ -27,23 +27,23 @@ export interface ProcessFile {
content_type: string;
last_modified: string;
name: string;
process_group_id: string;
process_model_id: string;
references: ProcessFileReference[];
size: number;
type: string;
file_contents?: string;
}
export interface ProcessModel {
id: string;
process_group_id: string;
description: string;
display_name: string;
primary_file_name: string;
files: ProcessFile[];
}
// tuple of display value and URL
export type BreadcrumbItem = [displayValue: string, url?: string];
export type HotCrumbItem = [displayValue: string, url?: string];
export interface ErrorForDisplay {
message: string;

View File

@ -53,35 +53,35 @@ export default function AdminRoutes() {
element={<ProcessModelNew />}
/>
<Route
path="process-models/:process_group_id/:process_model_id"
path="process-models/:process_model_id"
element={<ProcessModelShow />}
/>
<Route
path="process-models/:process_group_id/:process_model_id/files"
path="process-models/:process_model_id/files"
element={<ProcessModelEditDiagram />}
/>
<Route
path="process-models/:process_group_id/:process_model_id/files/:file_name"
path="process-models/:process_model_id/files/:file_name"
element={<ProcessModelEditDiagram />}
/>
<Route
path="process-models/:process_group_id/:process_model_id/process-instances"
path="process-models/:process_model_id/process-instances"
element={<ProcessInstanceList />}
/>
<Route
path="process-models/:process_group_id/:process_model_id/edit"
path="process-models/:process_model_id/edit"
element={<ProcessModelEdit />}
/>
<Route
path="process-models/:process_group_id/:process_model_id/process-instances/:process_instance_id"
path="process-models/:process_model_id/process-instances/:process_instance_id"
element={<ProcessInstanceShow />}
/>
<Route
path="process-models/:process_group_id/:process_model_id/process-instances/:process_instance_id/:spiff_step"
path="process-models/:process_model_id/process-instances/:process_instance_id/:spiff_step"
element={<ProcessInstanceShow />}
/>
<Route
path="process-models/:process_group_id/:process_model_id/process-instances/reports"
path="process-models/:process_model_id/process-instances/reports"
element={<ProcessInstanceReportList />}
/>
<Route
@ -97,15 +97,15 @@ export default function AdminRoutes() {
element={<ProcessInstanceReportEdit />}
/>
<Route
path="process-models/:process_group_id/:process_model_id/form"
path="process-models/:process_model_id/form"
element={<ReactFormEditor />}
/>
<Route
path="process-models/:process_group_id/:process_model_id/form/:file_name"
path="process-models/:process_model_id/form/:file_name"
element={<ReactFormEditor />}
/>
<Route
path="process-models/:process_group_id/:process_model_id/process-instances/:process_instance_id/logs"
path="process-models/:process_model_id/process-instances/:process_instance_id/logs"
element={<ProcessInstanceLogList />}
/>
<Route path="process-instances" element={<ProcessInstanceList />} />

View File

@ -3,7 +3,10 @@ import { useEffect, useState } from 'react';
import { Button, Table } from '@carbon/react';
import { Link, useSearchParams } from 'react-router-dom';
import PaginationForTable from '../components/PaginationForTable';
import { getPageInfoFromSearchParams } from '../helpers';
import {
getPageInfoFromSearchParams,
modifyProcessModelPath,
} from '../helpers';
import HttpService from '../services/HttpService';
import { PaginationObject, RecentProcessModel } from '../interfaces';
@ -39,12 +42,15 @@ export default function HomePage() {
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
);
return (
<tr key={rowToUse.id}>
<td>
<Link
data-qa="process-model-show-link"
to={`/admin/process-models/${rowToUse.process_group_identifier}/${rowToUse.process_model_identifier}`}
to={`/admin/process-models/${modifiedProcessModelIdentifier}`}
>
{rowToUse.process_model_display_name}
</Link>
@ -52,7 +58,7 @@ export default function HomePage() {
<td>
<Link
data-qa="process-instance-show-link"
to={`/admin/process-models/${rowToUse.process_group_identifier}/${rowToUse.process_model_identifier}/process-instances/${rowToUse.process_instance_id}`}
to={`/admin/process-models/${modifiedProcessModelIdentifier}/process-instances/${rowToUse.process_instance_id}`}
>
View {rowToUse.process_instance_id}
</Link>
@ -96,6 +102,9 @@ export default function HomePage() {
const buildRecentProcessModelSection = () => {
const rows = recentProcessModels.map((row) => {
const rowToUse = row as any;
const modifiedProcessModelId = modifyProcessModelPath(
rowToUse.processModelIdentifier
);
return (
<tr
key={`${rowToUse.processGroupIdentifier}/${rowToUse.processModelIdentifier}`}
@ -103,7 +112,7 @@ export default function HomePage() {
<td>
<Link
data-qa="process-model-show-link"
to={`/admin/process-models/${rowToUse.processGroupIdentifier}/${rowToUse.processModelIdentifier}`}
to={`/admin/process-models/${modifiedProcessModelId}`}
>
{rowToUse.processModelDisplayName}
</Link>

View File

@ -13,7 +13,10 @@ import {
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
import PaginationForTable from '../components/PaginationForTable';
import HttpService from '../services/HttpService';
import { getPageInfoFromSearchParams } from '../helpers';
import {
getPageInfoFromSearchParams,
modifyProcessModelPath,
} from '../helpers';
import { CarbonComboBoxSelection, ProcessGroup } from '../interfaces';
import ProcessModelSearch from '../components/ProcessModelSearch';
@ -36,7 +39,7 @@ export default function ProcessGroupList() {
};
const processResultForProcessModels = (result: any) => {
const selectionArray = result.results.map((item: any) => {
const label = `${item.process_group_id}/${item.id}`;
const label = `${item.id}`;
Object.assign(item, { label });
return item;
});
@ -120,7 +123,7 @@ export default function ProcessGroupList() {
const processModelSearchOnChange = (selection: CarbonComboBoxSelection) => {
const processModel = selection.selectedItem;
navigate(
`/admin/process-models/${processModel.process_group_id}/${processModel.id}`
`/admin/process-models/${modifyProcessModelPath(processModel.id)}`
);
};
return (

View File

@ -24,6 +24,7 @@ export default function ProcessGroupShow() {
setPagination(result.pagination);
};
const processResult = (result: any) => {
console.log(result);
setProcessGroup(result);
HttpService.makeCallToBackend({
path: `/process-models?process_group_identifier=${params.process_group_id}&per_page=${perPage}&page=${page}`,
@ -41,11 +42,12 @@ export default function ProcessGroupShow() {
return null;
}
const rows = processModels.map((row) => {
const modifiedProcessModelId: String = (row as any).id.replace('/', ':');
return (
<tr key={(row as any).id}>
<td>
<Link
to={`/admin/process-models/${processGroup.id}/${(row as any).id}`}
to={`/admin/process-models/${modifiedProcessModelId}`}
data-qa="process-model-show-link"
>
{(row as any).id}

View File

@ -17,11 +17,9 @@ import {
Grid,
Column,
MultiSelect,
// TableHeader,
// TableHead,
// TableRow,
// TableBody,
// TableCell,
TableHeader,
TableHead,
TableRow,
// @ts-ignore
} from '@carbon/react';
import { PROCESS_STATUSES, DATE_FORMAT, DATE_FORMAT_CARBON } from '../config';
@ -30,6 +28,7 @@ import {
convertSecondsToFormattedDate,
getPageInfoFromSearchParams,
getProcessModelFullIdentifierFromSearchParams,
modifyProcessModelPath,
} from '../helpers';
import PaginationForTable from '../components/PaginationForTable';
@ -49,6 +48,7 @@ export default function ProcessInstanceList() {
const navigate = useNavigate();
const [processInstances, setProcessInstances] = useState([]);
const [reportMetadata, setReportMetadata] = useState({});
const [pagination, setPagination] = useState<PaginationObject | null>(null);
const oneHourInSeconds = 3600;
@ -84,7 +84,6 @@ export default function ProcessInstanceList() {
const parametersToGetFromSearchParams = useMemo(() => {
return {
process_group_identifier: null,
process_model_identifier: null,
process_status: null,
};
@ -95,6 +94,7 @@ export default function ProcessInstanceList() {
function setProcessInstancesFromResult(result: any) {
const processInstancesFromApi = result.results;
setProcessInstances(processInstancesFromApi);
setReportMetadata(result.report_metadata);
setPagination(result.pagination);
}
function getProcessInstances() {
@ -111,6 +111,7 @@ export default function ProcessInstanceList() {
searchParamValue as any
);
functionToCall(dateString);
setShowFilterOptions(true);
}
});
@ -123,6 +124,7 @@ export default function ProcessInstanceList() {
if (functionToCall !== null) {
functionToCall(searchParams.get(paramName) || '');
}
setShowFilterOptions(true);
}
}
);
@ -135,7 +137,7 @@ export default function ProcessInstanceList() {
const processModelFullIdentifier =
getProcessModelFullIdentifierFromSearchParams(searchParams);
const selectionArray = result.results.map((item: any) => {
const label = `${item.process_group_id}/${item.id}`;
const label = `${item.id}`;
Object.assign(item, { label });
if (label === processModelFullIdentifier) {
setProcessModelSelection(item);
@ -160,6 +162,7 @@ export default function ProcessInstanceList() {
getProcessInstances();
}
// populate process model selection
HttpService.makeCallToBackend({
path: `/process-models?per_page=1000`,
successCallback: processResultForProcessModels,
@ -241,7 +244,7 @@ export default function ProcessInstanceList() {
}
if (processModelSelection) {
queryParamString += `&process_group_identifier=${processModelSelection.process_group_id}&process_model_identifier=${processModelSelection.id}`;
queryParamString += `&process_model_identifier=${processModelSelection.id}`;
}
setErrorMessage(null);
@ -296,6 +299,7 @@ export default function ProcessInstanceList() {
return (
<MultiSelect
label="Choose Status"
className="our-class"
id="process-instance-status-select"
titleText="Status"
items={processStatusAllOptions}
@ -367,7 +371,11 @@ export default function ProcessInstanceList() {
>
Clear
</Button>
<Button kind="secondary" onClick={applyFilter}>
<Button
kind="secondary"
onClick={applyFilter}
data-qa="filter-button"
>
Filter
</Button>
</ButtonSet>
@ -378,54 +386,86 @@ export default function ProcessInstanceList() {
};
const buildTable = () => {
const rows = processInstances.map((row: any) => {
const formattedStartDate =
convertSecondsToFormattedDate(row.start_in_seconds) || '-';
const formattedEndDate =
convertSecondsToFormattedDate(row.end_in_seconds) || '-';
return (
<tr key={row.id}>
<td>
<Link
data-qa="process-instance-show-link"
to={`/admin/process-models/${row.process_group_identifier}/${row.process_model_identifier}/process-instances/${row.id}`}
>
{row.id}
</Link>
</td>
<td>
<Link to={`/admin/process-groups/${row.process_group_identifier}`}>
{row.process_group_identifier}
</Link>
</td>
<td>
<Link
to={`/admin/process-models/${row.process_group_identifier}/${row.process_model_identifier}`}
>
{row.process_model_identifier}
</Link>
</td>
<td>{formattedStartDate}</td>
<td>{formattedEndDate}</td>
<td data-qa={`process-instance-status-${row.status}`}>
{row.status}
</td>
</tr>
);
const headerLabels: Record<string, string> = {
id: 'Process Instance Id',
process_model_identifier: 'Process Model',
start_in_seconds: 'Start Time',
end_in_seconds: 'End Time',
status: 'Status',
spiff_step: 'SpiffWorkflow Step',
};
const getHeaderLabel = (header: string) => {
return headerLabels[header] ?? header;
};
const headers = (reportMetadata as any).columns.map((column: any) => {
// return <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
);
return (
<Link
data-qa="process-instance-show-link"
to={`/admin/process-models/${modifiedProcessModelId}/process-instances/${row.id}`}
>
{id}
</Link>
);
};
const formatProcessModelIdentifier = (_row: any, identifier: any) => {
return (
<Link
to={`/admin/process-models/${modifyProcessModelPath(identifier)}`}
>
{identifier}
</Link>
);
};
const formatSecondsForDisplay = (_row: any, seconds: any) => {
return convertSecondsToFormattedDate(seconds) || '-';
};
const defaultFormatter = (_row: any, value: any) => {
return value;
};
const columnFormatters: Record<string, any> = {
id: formatProcessInstanceId,
process_model_identifier: formatProcessModelIdentifier,
start_in_seconds: formatSecondsForDisplay,
end_in_seconds: formatSecondsForDisplay,
};
const formattedColumn = (row: any, column: any) => {
const formatter = columnFormatters[column.accessor] ?? defaultFormatter;
const value = row[column.accessor];
if (column.accessor === 'status') {
return (
<td data-qa={`process-instance-status-${value}`}>
{formatter(row, value)}
</td>
);
}
return <td>{formatter(row, value)}</td>;
};
const rows = processInstances.map((row: any) => {
const currentRow = (reportMetadata as any).columns.map((column: any) => {
return formattedColumn(row, column);
});
return <tr key={row.id}>{currentRow}</tr>;
});
return (
<Table size="lg">
<thead>
<tr>
<th>Process Instance Id</th>
<th>Process Group</th>
<th>Process Model</th>
<th>Start Time</th>
<th>End Time</th>
<th>Status</th>
</tr>
</thead>
<TableHead>
<TableRow>
{headers.map((header: any) => (
<TableHeader key={header}>{header}</TableHeader>
))}
</TableRow>
</TableHead>
<tbody>{rows}</tbody>
</Table>
);
@ -440,7 +480,11 @@ export default function ProcessInstanceList() {
return (
<h2>
Process Instances for:{' '}
<Link to={`/admin/process-models/${processModelFullIdentifier}`}>
<Link
to={`/admin/process-models/${modifyProcessModelPath(
processModelFullIdentifier
)}`}
>
{processModelFullIdentifier}
</Link>
</h2>
@ -457,9 +501,13 @@ export default function ProcessInstanceList() {
<>
{processInstanceTitleElement()}
<Grid fullWidth>
<Column lg={15} />
<Column lg={1}>
<Column
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"
@ -471,18 +519,14 @@ export default function ProcessInstanceList() {
</Grid>
{filterOptions()}
<br />
<Grid fullWidth>
<Column lg={16}>
<PaginationForTable
page={page}
perPage={perPage}
pagination={pagination}
tableToDisplay={buildTable()}
queryParamString={getSearchParamsAsQueryString()}
path="/admin/process-instances"
/>
</Column>
</Grid>
<PaginationForTable
page={page}
perPage={perPage}
pagination={pagination}
tableToDisplay={buildTable()}
queryParamString={getSearchParamsAsQueryString()}
path="/admin/process-instances"
/>
</>
);
}

View File

@ -7,6 +7,7 @@ import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
import {
getPageInfoFromSearchParams,
convertSecondsToFormattedDate,
modifyProcessModelPath,
} from '../helpers';
import HttpService from '../services/HttpService';
@ -15,6 +16,9 @@ export default function ProcessInstanceLogList() {
const [searchParams] = useSearchParams();
const [processInstanceLogs, setProcessInstanceLogs] = useState([]);
const [pagination, setPagination] = useState(null);
const modifiedProcessModelId = modifyProcessModelPath(
`${params.process_model_id}`
);
useEffect(() => {
const setProcessInstanceLogListFromResult = (result: any) => {
@ -23,7 +27,7 @@ export default function ProcessInstanceLogList() {
};
const { page, perPage } = getPageInfoFromSearchParams(searchParams);
HttpService.makeCallToBackend({
path: `/process-models/${params.process_group_id}/${params.process_model_id}/process-instances/${params.process_instance_id}/logs?per_page=${perPage}&page=${page}`,
path: `/process-instances/${params.process_instance_id}/logs?per_page=${perPage}&page=${page}`,
successCallback: setProcessInstanceLogListFromResult,
});
}, [searchParams, params]);
@ -32,6 +36,7 @@ export default function ProcessInstanceLogList() {
// return null;
const rows = processInstanceLogs.map((row) => {
const rowToUse = row as any;
console.log(`rowToUse: ${rowToUse}`);
return (
<tr key={rowToUse.id}>
<td>{rowToUse.bpmn_process_identifier}</td>
@ -43,7 +48,7 @@ export default function ProcessInstanceLogList() {
<td>
<Link
data-qa="process-instance-show-link"
to={`/admin/process-models/${params.process_group_id}/${params.process_model_id}/process-instances/${rowToUse.process_instance_id}/${rowToUse.spiff_step}`}
to={`/admin/process-models/${modifiedProcessModelId}/process-instances/${rowToUse.process_instance_id}/${rowToUse.spiff_step}`}
>
{convertSecondsToFormattedDate(rowToUse.timestamp)}
</Link>
@ -83,7 +88,7 @@ export default function ProcessInstanceLogList() {
perPage={perPage}
pagination={pagination}
tableToDisplay={buildTable()}
path={`/admin/process-models/${params.process_group_id}/${params.process_model_id}/process-instances/${params.process_instance_id}/logs`}
path={`/admin/process-models/${modifiedProcessModelId}/process-instances/${params.process_instance_id}/logs`}
/>
</main>
);

View File

@ -56,7 +56,7 @@ export default function ProcessInstanceReportEdit() {
};
function getProcessInstanceReport() {
HttpService.makeCallToBackend({
path: `/process-models/${params.process_group_id}/${params.process_model_id}/process-instances/reports/${params.report_identifier}?per_page=1`,
path: `/process-instances/reports/${params.report_identifier}?per_page=1`,
successCallback: processResult,
});
}
@ -88,7 +88,7 @@ export default function ProcessInstanceReportEdit() {
.filter((n) => n);
HttpService.makeCallToBackend({
path: `/process-models/${params.process_group_id}/${params.process_model_id}/process-instances/reports/${params.report_identifier}`,
path: `/process-instances/reports/${params.report_identifier}`,
successCallback: navigateToProcessInstanceReport,
httpMethod: 'PUT',
postBody: {
@ -103,7 +103,7 @@ export default function ProcessInstanceReportEdit() {
const deleteProcessInstanceReport = () => {
HttpService.makeCallToBackend({
path: `/process-models/${params.process_group_id}/${params.process_model_id}/process-instances/reports/${params.report_identifier}`,
path: `/process-instances/reports/${params.report_identifier}`,
successCallback: navigateToProcessInstanceReports,
httpMethod: 'DELETE',
});

View File

@ -4,14 +4,18 @@ import { Button, Table } from '@carbon/react';
import { useParams, Link } from 'react-router-dom';
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
import HttpService from '../services/HttpService';
import { modifyProcessModelPath } from '../helpers';
export default function ProcessInstanceReportList() {
const params = useParams();
const [processInstanceReports, setProcessInstanceReports] = useState([]);
const modifiedProcessModelId = modifyProcessModelPath(
params.process_model_id || ''
);
useEffect(() => {
HttpService.makeCallToBackend({
path: `/process-models/${params.process_group_id}/${params.process_model_id}/process-instances/reports`,
path: `/process-instances/reports`,
successCallback: setProcessInstanceReports,
});
}, [params]);
@ -23,7 +27,7 @@ export default function ProcessInstanceReportList() {
<tr key={(row as any).id}>
<td>
<Link
to={`/admin/process-models/${params.process_group_id}/${params.process_model_id}/process-instances/reports/${rowToUse.identifier}`}
to={`/admin/process-models/${modifiedProcessModelId}/process-instances/reports/${rowToUse.identifier}`}
>
{rowToUse.identifier}
</Link>
@ -52,7 +56,7 @@ export default function ProcessInstanceReportList() {
/>
<h2>Reports for Process Model: {params.process_model_id}</h2>
<Button
href={`/admin/process-models/${params.process_group_id}/${params.process_model_id}/process-instances/reports/new`}
href={`/admin/process-models/${modifiedProcessModelId}/process-instances/reports/new`}
>
Add a process instance report
</Button>

View File

@ -42,7 +42,7 @@ export default function ProcessInstanceReportNew() {
.filter((n) => n);
HttpService.makeCallToBackend({
path: `/process-models/${params.process_group_id}/${params.process_model_id}/process-instances/reports`,
path: `/process-instances/reports`,
successCallback: navigateToNewProcessInstance,
httpMethod: 'POST',
postBody: {

View File

@ -39,7 +39,7 @@ export default function ProcessInstanceReport() {
}
});
HttpService.makeCallToBackend({
path: `/process-models/${params.process_group_id}/${params.process_model_id}/process-instances/reports/${params.report_identifier}?${query}`,
path: `/process-instances/reports/${params.report_identifier}${query}`,
successCallback: processResult,
});
}

View File

@ -6,7 +6,10 @@ import { Button, Modal, Stack } from '@carbon/react';
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
import HttpService from '../services/HttpService';
import ReactDiagramEditor from '../components/ReactDiagramEditor';
import { convertSecondsToFormattedDate } from '../helpers';
import {
convertSecondsToFormattedDate,
unModifyProcessModelPath,
} from '../helpers';
import ButtonWithConfirmation from '../components/ButtonWithConfirmation';
import ErrorContext from '../contexts/ErrorContext';
@ -22,15 +25,20 @@ export default function ProcessInstanceShow() {
const setErrorMessage = (useContext as any)(ErrorContext)[1];
const unModifiedProcessModelId = unModifyProcessModelPath(
`${params.process_model_id}`
);
const modifiedProcessModelId = params.process_model_id;
const navigateToProcessInstances = (_result: any) => {
navigate(
`/admin/process-instances?process_group_identifier=${params.process_group_id}&process_model_identifier=${params.process_model_id}`
`/admin/process-instances?process_model_identifier=${unModifiedProcessModelId}`
);
};
useEffect(() => {
HttpService.makeCallToBackend({
path: `/process-models/${params.process_group_id}/${params.process_model_id}/process-instances/${params.process_instance_id}`,
path: `/process-models/${modifiedProcessModelId}/process-instances/${params.process_instance_id}`,
successCallback: setProcessInstance,
});
if (typeof params.spiff_step === 'undefined')
@ -43,11 +51,11 @@ export default function ProcessInstanceShow() {
path: `/process-instance/${params.process_instance_id}/tasks?all_tasks=true&spiff_step=${params.spiff_step}`,
successCallback: setTasks,
});
}, [params]);
}, [params, modifiedProcessModelId]);
const deleteProcessInstance = () => {
HttpService.makeCallToBackend({
path: `/process-models/${params.process_group_id}/${params.process_model_id}/process-instances/${params.process_instance_id}`,
path: `/process-instances/${params.process_instance_id}`,
successCallback: navigateToProcessInstances,
httpMethod: 'DELETE',
});
@ -60,7 +68,7 @@ export default function ProcessInstanceShow() {
const terminateProcessInstance = () => {
HttpService.makeCallToBackend({
path: `/process-models/${params.process_group_id}/${params.process_model_id}/process-instances/${params.process_instance_id}/terminate`,
path: `/process-instances/${params.process_instance_id}/terminate`,
successCallback: refreshPage,
httpMethod: 'POST',
});
@ -68,7 +76,7 @@ export default function ProcessInstanceShow() {
const suspendProcessInstance = () => {
HttpService.makeCallToBackend({
path: `/process-models/${params.process_group_id}/${params.process_model_id}/process-instances/${params.process_instance_id}/suspend`,
path: `/process-instances/${params.process_instance_id}/suspend`,
successCallback: refreshPage,
httpMethod: 'POST',
});
@ -76,7 +84,7 @@ export default function ProcessInstanceShow() {
const resumeProcessInstance = () => {
HttpService.makeCallToBackend({
path: `/process-models/${params.process_group_id}/${params.process_model_id}/process-instances/${params.process_instance_id}/resume`,
path: `/process-instances/${params.process_instance_id}/resume`,
successCallback: refreshPage,
httpMethod: 'POST',
});
@ -125,7 +133,7 @@ export default function ProcessInstanceShow() {
<Link
reloadDocument
data-qa="process-instance-step-link"
to={`/admin/process-models/${params.process_group_id}/${
to={`/admin/process-models/${
params.process_model_id
}/process-instances/${params.process_instance_id}/${
currentSpiffStep(processInstanceToUse) + distance
@ -179,7 +187,7 @@ export default function ProcessInstanceShow() {
<li>
<Link
data-qa="process-instance-log-list-link"
to={`/admin/process-models/${params.process_group_id}/${params.process_model_id}/process-instances/${params.process_instance_id}/logs`}
to={`/admin/process-models/${modifiedProcessModelId}/process-instances/${params.process_instance_id}/logs`}
>
Logs
</Link>
@ -187,7 +195,7 @@ export default function ProcessInstanceShow() {
<li>
<Link
data-qa="process-instance-message-instance-list-link"
to={`/admin/messages?process_group_id=${params.process_group_id}&process_model_id=${params.process_model_id}&process_instance_id=${params.process_instance_id}`}
to={`/admin/messages?process_model_id=${params.process_model_id}&process_instance_id=${params.process_instance_id}`}
>
Messages
</Link>
@ -284,7 +292,7 @@ export default function ProcessInstanceShow() {
const taskToUse: any = taskToDisplay;
const previousTask: any = getTaskById(taskToUse.parent);
HttpService.makeCallToBackend({
path: `/process-models/${params.process_group_id}/${params.process_model_id}/script-unit-tests`,
path: `/process-models/${modifiedProcessModelId}/script-unit-tests`,
httpMethod: 'POST',
successCallback: processScriptUnitTestCreateResult,
postBody: {
@ -428,13 +436,21 @@ export default function ProcessInstanceShow() {
if (processInstance && tasks) {
const processInstanceToUse = processInstance as any;
const taskIds = getTaskIds();
const processModelId = unModifyProcessModelPath(
params.process_model_id ? params.process_model_id : ''
);
return (
<>
<ProcessBreadcrumb
processModelId={params.process_model_id}
processGroupId={params.process_group_id}
linkProcessModel
hotCrumbs={[
['Process Groups', '/admin'],
[
`Process Model: ${processModelId}`,
`process_model:${processModelId}:link`,
],
[`Process Instance: ${params.process_instance_id}`],
]}
/>
<Stack orientation="horizontal" gap={3}>
<h2>Process Instance Id: {processInstanceToUse.id}</h2>
@ -450,8 +466,7 @@ export default function ProcessInstanceShow() {
{getInfoTag(processInstanceToUse)}
{taskDataDisplayArea()}
<ReactDiagramEditor
processModelId={params.process_model_id || ''}
processGroupId={params.process_group_id || ''}
processModelId={processModelId || ''}
diagramXML={processInstanceToUse.bpmn_xml_file_contents || ''}
fileName={processInstanceToUse.bpmn_xml_file_contents || ''}
readyOrWaitingBpmnTaskIds={taskIds.readyOrWaiting}

View File

@ -1,97 +1,33 @@
import { useState, useEffect, useContext } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { useState, useEffect } from 'react';
import { useParams } from 'react-router-dom';
// @ts-ignore
import { Button, Stack } from '@carbon/react';
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
import HttpService from '../services/HttpService';
import ButtonWithConfirmation from '../components/ButtonWithConfirmation';
import ErrorContext from '../contexts/ErrorContext';
import ProcessModelForm from '../components/ProcessModelForm';
export default function ProcessModelEdit() {
const [displayName, setDisplayName] = useState('');
const params = useParams();
const navigate = useNavigate();
const [processModel, setProcessModel] = useState(null);
const setErrorMessage = (useContext as any)(ErrorContext)[1];
const processModelPath = `process-models/${params.process_group_id}/${params.process_model_id}`;
const processModelPath = `process-models/${params.process_model_id}`;
useEffect(() => {
const processResult = (result: any) => {
setProcessModel(result);
setDisplayName(result.display_name);
};
HttpService.makeCallToBackend({
path: `/${processModelPath}`,
successCallback: processResult,
successCallback: setProcessModel,
});
}, [processModelPath]);
const navigateToProcessModel = (_result: any) => {
navigate(`/admin/${processModelPath}`);
};
const navigateToProcessModels = (_result: any) => {
navigate(`/admin/process-groups/${params.process_group_id}`);
};
const updateProcessModel = (event: any) => {
const processModelToUse = processModel as any;
event.preventDefault();
const processModelToPass = Object.assign(processModelToUse, {
display_name: displayName,
});
HttpService.makeCallToBackend({
path: `/${processModelPath}`,
successCallback: navigateToProcessModel,
httpMethod: 'PUT',
postBody: processModelToPass,
});
};
const deleteProcessModel = () => {
setErrorMessage(null);
const processModelToUse = processModel as any;
const processModelShowPath = `/process-models/${processModelToUse.process_group_id}/${processModelToUse.id}`;
HttpService.makeCallToBackend({
path: `${processModelShowPath}`,
successCallback: navigateToProcessModels,
httpMethod: 'DELETE',
failureCallback: setErrorMessage,
});
};
const onDisplayNameChanged = (newDisplayName: any) => {
setDisplayName(newDisplayName);
};
if (processModel) {
return (
<>
<ProcessBreadcrumb processGroupId={(processModel as any).id} />
<h2>Edit Process Group: {(processModel as any).id}</h2>
<form onSubmit={updateProcessModel}>
<label>Display Name:</label>
<input
name="display_name"
type="text"
value={displayName}
onChange={(e) => onDisplayNameChanged(e.target.value)}
/>
<br />
<br />
<Stack orientation="horizontal" gap={3}>
<Button type="submit">Submit</Button>
<Button variant="secondary" href={`/admin/${processModelPath}`}>
Cancel
</Button>
<ButtonWithConfirmation
description={`Delete Process Model ${(processModel as any).id}?`}
onConfirmation={deleteProcessModel}
buttonLabel="Delete"
/>
</Stack>
</form>
<ProcessBreadcrumb processGroupId={params.process_group_id} />
<h2>Edit Process Model: {(processModel as any).id}</h2>
<ProcessModelForm
mode="edit"
processGroupId={params.process_group_id}
processModel={processModel}
setProcessModel={setProcessModel}
/>
</>
);
}

View File

@ -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 } from '../helpers';
import { makeid, modifyProcessModelPath } from '../helpers';
import { ProcessFile, ProcessModel } from '../interfaces';
export default function ProcessModelEditDiagram() {
@ -77,12 +77,18 @@ export default function ProcessModelEditDiagram() {
const [searchParams] = useSearchParams();
const setErrorMessage = (useContext as any)(ErrorContext)[1];
const [processModelFile, setProcessModelFile] = useState(null);
const [processModelFile, setProcessModelFile] = useState<ProcessFile | null>(
null
);
const [newFileName, setNewFileName] = useState('');
const [bpmnXmlForDiagramRendering, setBpmnXmlForDiagramRendering] =
useState(null);
const processModelPath = `process-models/${params.process_group_id}/${params.process_model_id}`;
const modifiedProcessModelId = modifyProcessModelPath(
(params as any).process_model_id
);
const processModelPath = `process-models/${modifiedProcessModelId}`;
useEffect(() => {
const processResult = (result: ProcessModel) => {
@ -119,7 +125,7 @@ export default function ProcessModelEditDiagram() {
'file_type'
)}`;
navigate(
`/admin/process-models/${params.process_group_id}/${params.process_model_id}/files/${fileNameWithExtension}`
`/admin/process-models/${modifiedProcessModelId}/files/${fileNameWithExtension}`
);
}
};
@ -128,7 +134,7 @@ export default function ProcessModelEditDiagram() {
setErrorMessage(null);
setBpmnXmlForDiagramRendering(bpmnXML);
let url = `/process-models/${params.process_group_id}/${params.process_model_id}/files`;
let url = `/process-models/${modifiedProcessModelId}/files`;
let httpMethod = 'PUT';
let fileNameWithExtension = fileName;
@ -162,13 +168,11 @@ export default function ProcessModelEditDiagram() {
};
const onDeleteFile = (fileName = params.file_name) => {
const url = `/process-models/${params.process_group_id}/${params.process_model_id}/files/${fileName}`;
const url = `/process-models/${modifiedProcessModelId}/files/${fileName}`;
const httpMethod = 'DELETE';
const navigateToProcessModelShow = (_httpResult: any) => {
navigate(
`/admin/process-models/${params.process_group_id}/${params.process_model_id}`
);
navigate(`/admin/process-models/${modifiedProcessModelId}`);
};
HttpService.makeCallToBackend({
path: url,
@ -178,7 +182,7 @@ export default function ProcessModelEditDiagram() {
};
const onSetPrimaryFile = (fileName = params.file_name) => {
const url = `/process-models/${params.process_group_id}/${params.process_model_id}`;
const url = `/process-models/${modifiedProcessModelId}`;
const httpMethod = 'PUT';
const navigateToProcessModelShow = (_httpResult: any) => {
@ -428,7 +432,7 @@ export default function ProcessModelEditDiagram() {
if (currentScriptUnitTest && scriptElement) {
resetUnitTextResult();
HttpService.makeCallToBackend({
path: `/process-models/${params.process_group_id}/${params.process_model_id}/script-unit-tests/run`,
path: `/process-models/${modifiedProcessModelId}/script-unit-tests/run`,
httpMethod: 'POST',
successCallback: processScriptUnitTestRunResult,
postBody: {
@ -680,42 +684,34 @@ export default function ProcessModelEditDiagram() {
* fixme: Not currently in use. This would only work for bpmn files within the process model. Which is right for DMN and json, but not right here. Need to merge in work on the nested process groups before tackling this.
* @param processId
*/
const fileNameTemplatePath =
'/admin/process-models/:process_model_id/files/:file_name';
const onLaunchBpmnEditor = (processId: string) => {
const file = findFileNameForReferenceId(processId, 'bpmn');
if (file) {
const path = generatePath(
'/admin/process-models/:process_group_id/:process_model_id/files/:file_name',
{
process_group_id: params.process_group_id,
process_model_id: params.process_model_id,
file_name: file.name,
}
);
const path = generatePath(fileNameTemplatePath, {
process_model_id: params.process_model_id,
file_name: file.name,
});
window.open(path);
}
};
const onLaunchJsonEditor = (fileName: string) => {
const path = generatePath(
'/admin/process-models/:process_group_id/:process_model_id/form/:file_name',
{
process_group_id: params.process_group_id,
process_model_id: params.process_model_id,
file_name: fileName,
}
);
const path = generatePath(fileNameTemplatePath, {
process_model_id: params.process_model_id,
file_name: fileName,
});
window.open(path);
};
const onLaunchDmnEditor = (processId: string) => {
const file = findFileNameForReferenceId(processId, 'dmn');
if (file) {
const path = generatePath(
'/admin/process-models/:process_group_id/:process_model_id/files/:file_name',
{
process_group_id: params.process_group_id,
process_model_id: params.process_model_id,
file_name: file.name,
}
);
const path = generatePath(fileNameTemplatePath, {
process_model_id: params.process_model_id,
file_name: file.name,
});
window.open(path);
}
};
@ -730,7 +726,6 @@ export default function ProcessModelEditDiagram() {
return (
<ReactDiagramEditor
processModelId={params.process_model_id || ''}
processGroupId={params.process_group_id || ''}
saveDiagram={saveDiagram}
onDeleteFile={onDeleteFile}
diagramXML={bpmnXmlForDiagramRendering}
@ -752,7 +747,6 @@ export default function ProcessModelEditDiagram() {
return (
<ReactDiagramEditor
processModelId={params.process_model_id || ''}
processGroupId={params.process_group_id || ''}
saveDiagram={saveDiagram}
onDeleteFile={onDeleteFile}
onSetPrimaryFile={onSetPrimaryFileCallback}
@ -773,16 +767,22 @@ export default function ProcessModelEditDiagram() {
// if a file name is not given then this is a new model and the ReactDiagramEditor component will handle it
if ((bpmnXmlForDiagramRendering || !params.file_name) && processModel) {
const processModelFileName = processModelFile ? processModelFile.name : '';
return (
<>
<ProcessBreadcrumb
processGroupId={params.process_group_id}
processModelId={params.process_model_id}
linkProcessModel
hotCrumbs={[
['Process Groups', '/admin'],
[
`Process Model: ${processModel.id}`,
`process_model:${processModel.id}:link`,
],
[processModelFileName],
]}
/>
<h2>
Process Model File
{processModelFile ? `: ${(processModelFile as any).name}` : ''}
Process Model File{processModelFile ? ': ' : ''}
{processModelFileName}
</h2>
{appropriateEditor()}
{newFileNameBox()}

View File

@ -1,75 +1,29 @@
import { useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import Button from 'react-bootstrap/Button';
import Form from 'react-bootstrap/Form';
import { useParams } from 'react-router-dom';
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
import { slugifyString } from '../helpers';
import HttpService from '../services/HttpService';
import { ProcessModel } from '../interfaces';
import ProcessModelForm from '../components/ProcessModelForm';
export default function ProcessModelNew() {
const params = useParams();
const [identifier, setIdentifier] = useState('');
const [idHasBeenUpdatedByUser, setIdHasBeenUpdatedByUser] = useState(false);
const [displayName, setDisplayName] = useState('');
const navigate = useNavigate();
const navigateToNewProcessModel = (_result: any) => {
navigate(`/admin/process-models/${params.process_group_id}/${identifier}`);
};
const addProcessModel = (event: any) => {
event.preventDefault();
HttpService.makeCallToBackend({
path: `/process-models`,
successCallback: navigateToNewProcessModel,
httpMethod: 'POST',
postBody: {
id: identifier,
display_name: displayName,
description: displayName,
process_group_id: params.process_group_id,
},
});
};
const onDisplayNameChanged = (newDisplayName: any) => {
setDisplayName(newDisplayName);
if (!idHasBeenUpdatedByUser) {
setIdentifier(slugifyString(newDisplayName));
}
};
const [processModel, setProcessModel] = useState<ProcessModel>({
id: '',
display_name: '',
description: '',
primary_file_name: '',
files: [],
});
return (
<>
<ProcessBreadcrumb />
<h2>Add Process Model</h2>
<Form onSubmit={addProcessModel}>
<Form.Group className="mb-3" controlId="display_name">
<Form.Label>Display Name:</Form.Label>
<Form.Control
name="display_name"
type="text"
value={displayName}
onChange={(e) => onDisplayNameChanged(e.target.value)}
/>
</Form.Group>
<Form.Group className="mb-3" controlId="identifier">
<Form.Label>ID:</Form.Label>
<Form.Control
name="id"
type="text"
value={identifier}
onChange={(e) => {
setIdentifier(e.target.value);
setIdHasBeenUpdatedByUser(true);
}}
/>
</Form.Group>
<Button variant="primary" type="submit">
Submit
</Button>
</Form>
<ProcessModelForm
mode="new"
processGroupId={params.process_group_id}
processModel={processModel}
setProcessModel={setProcessModel}
/>
</>
);
}

View File

@ -1,12 +1,36 @@
import { useContext, useEffect, useState } from 'react';
import { Link, useParams } from 'react-router-dom';
// @ts-ignore
import { Button, Stack } from '@carbon/react';
import { Link, useNavigate, useParams } from 'react-router-dom';
import {
Add,
Upload,
Download,
TrashCan,
Favorite,
Edit,
// @ts-ignore
} from '@carbon/icons-react';
import {
Accordion,
AccordionItem,
Button,
Stack,
ButtonSet,
Modal,
FileUploader,
Table,
TableHead,
TableHeader,
TableRow,
TableCell,
TableBody,
// @ts-ignore
} from '@carbon/react';
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
import FileInput from '../components/FileInput';
import HttpService from '../services/HttpService';
import ErrorContext from '../contexts/ErrorContext';
import { RecentProcessModel } from '../interfaces';
import { modifyProcessModelPath, unModifyProcessModelPath } from '../helpers';
import { ProcessFile, ProcessModel, RecentProcessModel } from '../interfaces';
import ButtonWithConfirmation from '../components/ButtonWithConfirmation';
const storeRecentProcessModelInLocalStorage = (
processModelForStorage: any,
@ -66,26 +90,34 @@ export default function ProcessModelShow() {
const params = useParams();
const setErrorMessage = (useContext as any)(ErrorContext)[1];
const [processModel, setProcessModel] = useState({});
const [processModel, setProcessModel] = useState<ProcessModel | null>(null);
const [processInstanceResult, setProcessInstanceResult] = useState(null);
const [reloadModel, setReloadModel] = useState(false);
const [reloadModel, setReloadModel] = useState<boolean>(false);
const [filesToUpload, setFilesToUpload] = useState<any>(null);
const [showFileUploadModal, setShowFileUploadModal] =
useState<boolean>(false);
const navigate = useNavigate();
const modifiedProcessModelId = modifyProcessModelPath(
`${params.process_model_id}`
);
useEffect(() => {
const processResult = (result: object) => {
const processResult = (result: ProcessModel) => {
setProcessModel(result);
setReloadModel(false);
storeRecentProcessModelInLocalStorage(result, params);
};
HttpService.makeCallToBackend({
path: `/process-models/${params.process_group_id}/${params.process_model_id}`,
path: `/process-models/${modifiedProcessModelId}`,
successCallback: processResult,
});
}, [params, reloadModel]);
}, [params, reloadModel, modifiedProcessModelId]);
const processModelRun = (processInstance: any) => {
setErrorMessage(null);
HttpService.makeCallToBackend({
path: `/process-models/${params.process_group_id}/${params.process_model_id}/process-instances/${processInstance.id}/run`,
path: `/process-instances/${processInstance.id}/run`,
successCallback: setProcessInstanceResult,
failureCallback: setErrorMessage,
httpMethod: 'POST',
@ -94,102 +126,246 @@ export default function ProcessModelShow() {
const processInstanceCreateAndRun = () => {
HttpService.makeCallToBackend({
path: `/process-models/${params.process_group_id}/${params.process_model_id}/process-instances`,
path: `/process-models/${modifiedProcessModelId}/process-instances`,
successCallback: processModelRun,
httpMethod: 'POST',
});
};
let processInstanceResultTag = null;
if (processInstanceResult) {
let takeMeToMyTaskBlurb = null;
// FIXME: ensure that the task is actually for the current user as well
const processInstanceId = (processInstanceResult as any).id;
const nextTask = (processInstanceResult as any).next_task;
if (nextTask && nextTask.state === 'READY') {
takeMeToMyTaskBlurb = (
<span>
You have a task to complete. Go to{' '}
<Link to={`/tasks/${processInstanceId}/${nextTask.id}`}>my task</Link>
.
</span>
const processInstanceResultTag = () => {
if (processModel && processInstanceResult) {
let takeMeToMyTaskBlurb = null;
// FIXME: ensure that the task is actually for the current user as well
const processInstanceId = (processInstanceResult as any).id;
const nextTask = (processInstanceResult as any).next_task;
if (nextTask && nextTask.state === 'READY') {
takeMeToMyTaskBlurb = (
<span>
You have a task to complete. Go to{' '}
<Link to={`/tasks/${processInstanceId}/${nextTask.id}`}>
my task
</Link>
.
</span>
);
}
return (
<div className="alert alert-success" role="alert">
<p>
Process Instance {processInstanceId} kicked off (
<Link
to={`/admin/process-models/${modifiedProcessModelId}/process-instances/${processInstanceId}`}
data-qa="process-instance-show-link"
>
view
</Link>
). {takeMeToMyTaskBlurb}
</p>
</div>
);
}
processInstanceResultTag = (
<div className="alert alert-success" role="alert">
<p>
Process Instance {processInstanceId} kicked off (
<Link
to={`/admin/process-models/${
(processModel as any).process_group_id
}/${
(processModel as any).id
}/process-instances/${processInstanceId}`}
data-qa="process-instance-show-link"
>
view
</Link>
). {takeMeToMyTaskBlurb}
</p>
</div>
);
}
return null;
};
const onUploadedCallback = () => {
setReloadModel(true);
};
const reloadModelOhYeah = (_httpResult: any) => {
setReloadModel(!reloadModel);
};
const processModelFileList = () => {
let constructedTag;
const tags = (processModel as any).files.map((processModelFile: any) => {
// Remove this code from
const onDeleteFile = (fileName: string) => {
const url = `/process-models/${modifiedProcessModelId}/files/${fileName}`;
const httpMethod = 'DELETE';
HttpService.makeCallToBackend({
path: url,
successCallback: reloadModelOhYeah,
httpMethod,
});
};
const onSetPrimaryFile = (fileName: string) => {
const url = `/process-models/${modifiedProcessModelId}`;
const httpMethod = 'PUT';
const processModelToPass = {
primary_file_name: fileName,
};
HttpService.makeCallToBackend({
path: url,
successCallback: onUploadedCallback,
httpMethod,
postBody: processModelToPass,
});
};
const handleProcessModelFileResult = (processModelFile: ProcessFile) => {
if (
!('file_contents' in processModelFile) ||
processModelFile.file_contents === undefined
) {
setErrorMessage({
message: `Could not file file contents for file: ${processModelFile.name}`,
});
return;
}
let contentType = 'application/xml';
if (processModelFile.type === 'json') {
contentType = 'application/json';
}
const element = document.createElement('a');
const file = new Blob([processModelFile.file_contents], {
type: contentType,
});
const downloadFileName = processModelFile.name;
element.href = URL.createObjectURL(file);
element.download = downloadFileName;
document.body.appendChild(element);
element.click();
};
const downloadFile = (fileName: string) => {
setErrorMessage(null);
const processModelPath = `process-models/${modifiedProcessModelId}`;
HttpService.makeCallToBackend({
path: `/${processModelPath}/files/${fileName}`,
successCallback: handleProcessModelFileResult,
});
};
const navigateToFileEdit = (processModelFile: ProcessFile) => {
if (processModel) {
if (processModelFile.name.match(/\.(dmn|bpmn)$/)) {
let primarySuffix = '';
if (processModelFile.name === (processModel as any).primary_file_name) {
primarySuffix = '- Primary File';
}
constructedTag = (
<li key={processModelFile.name}>
<Link
to={`/admin/process-models/${
(processModel as any).process_group_id
}/${(processModel as any).id}/files/${processModelFile.name}`}
>
{processModelFile.name}
</Link>
{primarySuffix}
</li>
navigate(
`/admin/process-models/${modifiedProcessModelId}/files/${processModelFile.name}`
);
} else if (processModelFile.name.match(/\.(json|md)$/)) {
constructedTag = (
<li key={processModelFile.name}>
<Link
to={`/admin/process-models/${
(processModel as any).process_group_id
}/${(processModel as any).id}/form/${processModelFile.name}`}
>
{processModelFile.name}
</Link>
</li>
);
} else {
constructedTag = (
<li key={processModelFile.name}>{processModelFile.name}</li>
navigate(
`/admin/process-models/${modifiedProcessModelId}/form/${processModelFile.name}`
);
}
}
};
const renderButtonElements = (
processModelFile: ProcessFile,
isPrimaryBpmnFile: boolean
) => {
const elements = [];
elements.push(
<Button
kind="ghost"
renderIcon={Edit}
iconDescription="Edit File"
hasIconOnly
size="lg"
data-qa={`edit-file-${processModelFile.name.replace('.', '-')}`}
onClick={() => navigateToFileEdit(processModelFile)}
/>
);
elements.push(
<Button
kind="ghost"
renderIcon={Download}
iconDescription="Download File"
hasIconOnly
size="lg"
onClick={() => downloadFile(processModelFile.name)}
/>
);
elements.push(
<ButtonWithConfirmation
kind="ghost"
renderIcon={TrashCan}
iconDescription="Delete File"
hasIconOnly
description={`Delete file: ${processModelFile.name}`}
onConfirmation={() => {
onDeleteFile(processModelFile.name);
}}
confirmButtonLabel="Delete"
/>
);
if (processModelFile.name.match(/\.bpmn$/) && !isPrimaryBpmnFile) {
elements.push(
<Button
kind="ghost"
renderIcon={Favorite}
iconDescription="Set As Primary File"
hasIconOnly
size="lg"
onClick={() => onSetPrimaryFile(processModelFile.name)}
/>
);
}
return elements;
};
const processModelFileList = () => {
if (!processModel) {
return null;
}
let constructedTag;
const tags = processModel.files.map((processModelFile: ProcessFile) => {
const isPrimaryBpmnFile =
processModelFile.name === processModel.primary_file_name;
let actionsTableCell = null;
if (processModelFile.name.match(/\.(dmn|bpmn|json|md)$/)) {
actionsTableCell = (
<TableCell key={`${processModelFile.name}-cell`}>
{renderButtonElements(processModelFile, isPrimaryBpmnFile)}
</TableCell>
);
}
let primarySuffix = '';
if (isPrimaryBpmnFile) {
primarySuffix = '- Primary File';
}
constructedTag = (
<TableRow key={processModelFile.name}>
<TableCell key={`${processModelFile.name}-cell`}>
{processModelFile.name}
{primarySuffix}
</TableCell>
{actionsTableCell}
</TableRow>
);
return constructedTag;
});
return <ul>{tags}</ul>;
// return <ul>{tags}</ul>;
const headers = ['Name', 'Actions'];
return (
<Table size="lg" useZebraStyles={false}>
<TableHead>
<TableRow>
{headers.map((header) => (
<TableHeader id={header} key={header}>
{header}
</TableHeader>
))}
</TableRow>
</TableHead>
<TableBody>{tags}</TableBody>
</Table>
);
};
const processInstancesUl = () => {
const unmodifiedProcessModelId: String = unModifyProcessModelPath(
`${params.process_model_id}`
);
if (!processModel) {
return null;
}
return (
<ul>
<li>
<Link
to={`/admin/process-instances?process_group_identifier=${
(processModel as any).process_group_id
}&process_model_identifier=${(processModel as any).id}`}
to={`/admin/process-instances?process_model_identifier=${unmodifiedProcessModelId}`}
data-qa="process-instance-list-link"
>
List
@ -197,9 +373,7 @@ export default function ProcessModelShow() {
</li>
<li>
<Link
to={`/admin/process-models/${
(processModel as any).process_group_id
}/${(processModel as any).id}/process-instances/reports`}
to={`/admin/process-models/${modifiedProcessModelId}/process-instances/reports`}
data-qa="process-instance-reports-link"
>
Reports
@ -209,79 +383,157 @@ export default function ProcessModelShow() {
);
};
const processModelButtons = () => {
const handleFileUploadCancel = () => {
setShowFileUploadModal(false);
};
const handleFileUpload = (event: any) => {
if (processModel) {
event.preventDefault();
const url = `/process-models/${modifiedProcessModelId}/files`;
const formData = new FormData();
formData.append('file', filesToUpload[0]);
formData.append('fileName', filesToUpload[0].name);
HttpService.makeCallToBackend({
path: url,
successCallback: onUploadedCallback,
httpMethod: 'POST',
postBody: formData,
});
}
setShowFileUploadModal(false);
};
const fileUploadModal = () => {
return (
<Stack orientation="horizontal" gap={3}>
<Button onClick={processInstanceCreateAndRun} variant="primary">
Run
</Button>
<Button
href={`/admin/process-models/${
(processModel as any).process_group_id
}/${(processModel as any).id}/edit`}
variant="secondary"
>
Edit process model
</Button>
<Button
href={`/admin/process-models/${
(processModel as any).process_group_id
}/${(processModel as any).id}/files?file_type=bpmn`}
variant="warning"
>
Add New BPMN File
</Button>
<Button
href={`/admin/process-models/${
(processModel as any).process_group_id
}/${(processModel as any).id}/files?file_type=dmn`}
variant="success"
>
Add New DMN File
</Button>
<Button
href={`/admin/process-models/${
(processModel as any).process_group_id
}/${(processModel as any).id}/form?file_ext=json`}
variant="info"
>
Add New JSON File
</Button>
<Button
href={`/admin/process-models/${
(processModel as any).process_group_id
}/${(processModel as any).id}/form?file_ext=md`}
variant="info"
>
Add New Markdown File
</Button>
</Stack>
<Modal
data-qa="modal-upload-file-dialog"
open={showFileUploadModal}
modalHeading="Upload File"
primaryButtonText="Upload"
secondaryButtonText="Cancel"
onSecondarySubmit={handleFileUploadCancel}
onRequestClose={handleFileUploadCancel}
onRequestSubmit={handleFileUpload}
>
<FileUploader
labelTitle="Upload files"
labelDescription="Max file size is 500mb. Only .bpmn, .dmn, and .json files are supported."
buttonLabel="Add file"
buttonKind="primary"
size="md"
filenameStatus="edit"
role="button"
accept={['.bpmn', '.dmn', '.json']}
disabled={false}
iconDescription="Delete file"
name=""
multiple={false}
onChange={(event: any) => setFilesToUpload(event.target.files)}
/>
</Modal>
);
};
if (Object.keys(processModel).length > 1) {
const processModelButtons = () => {
if (!processModel) {
return null;
}
return (
<Accordion>
<AccordionItem
data-qa="files-accordion"
title={
<Stack orientation="horizontal">
<span>
<Button size="sm" kind="ghost">
Files
</Button>
</span>
</Stack>
}
>
<ButtonSet>
<Button
renderIcon={Upload}
data-qa="upload-file-button"
onClick={() => setShowFileUploadModal(true)}
size="sm"
kind=""
className="button-white-background"
>
Upload File
</Button>
<Button
renderIcon={Add}
href={`/admin/process-models/${modifiedProcessModelId}/files?file_type=bpmn`}
size="sm"
>
New BPMN File
</Button>
<Button
renderIcon={Add}
href={`/admin/process-models/${modifiedProcessModelId}/files?file_type=dmn`}
size="sm"
>
New DMN File
</Button>
<Button
renderIcon={Add}
href={`/admin/process-models/${modifiedProcessModelId}/form?file_ext=json`}
size="sm"
>
New JSON File
</Button>
<Button
renderIcon={Add}
href={`/admin/process-models/${modifiedProcessModelId}/form?file_ext=md`}
size="sm"
>
New Markdown File
</Button>
</ButtonSet>
<br />
{processModelFileList()}
</AccordionItem>
</Accordion>
);
};
if (processModel) {
return (
<>
{fileUploadModal()}
<ProcessBreadcrumb
processGroupId={(processModel as any).process_group_id}
processModelId={(processModel as any).id}
/>
{processInstanceResultTag}
<FileInput
processModelId={(processModel as any).id}
processGroupId={(processModel as any).process_group_id}
onUploadedCallback={onUploadedCallback}
hotCrumbs={[
['Process Groups', '/admin'],
[
`Process Model: ${processModel.id}`,
`process_model:${processModel.id}`,
],
]}
/>
<h1>Process Model: {processModel.display_name}</h1>
<p className="process-description">{processModel.description}</p>
<Stack orientation="horizontal" gap={3}>
<Button onClick={processInstanceCreateAndRun} variant="primary">
Run
</Button>
<Button
href={`/admin/process-models/${modifiedProcessModelId}/edit`}
variant="secondary"
>
Edit process model
</Button>
</Stack>
<br />
<br />
{processInstanceResultTag()}
{processModelButtons()}
<br />
<br />
<h3>Process Instances</h3>
{processInstancesUl()}
<br />
<br />
<h3>Files</h3>
{processModelFileList()}
</>
);
}

View File

@ -6,6 +6,8 @@ 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 { ProcessFile } from '../interfaces';
// NOTE: This is mostly the same as ProcessModelEditDiagram and if we go this route could
// possibly be merged into it. I'm leaving as a separate file now in case it does
@ -18,7 +20,9 @@ export default function ReactFormEditor() {
const handleShowFileNameEditor = () => setShowFileNameEditor(true);
const navigate = useNavigate();
const [processModelFile, setProcessModelFile] = useState(null);
const [processModelFile, setProcessModelFile] = useState<ProcessFile | null>(
null
);
const [processModelFileContents, setProcessModelFileContents] = useState('');
const fileExtension = (() => {
@ -34,6 +38,10 @@ export default function ReactFormEditor() {
const editorDefaultLanguage = fileExtension === 'md' ? 'markdown' : 'json';
const modifiedProcessModelId = modifyProcessModelPath(
`${params.process_model_id}`
);
useEffect(() => {
const processResult = (result: any) => {
setProcessModelFile(result);
@ -42,23 +50,23 @@ export default function ReactFormEditor() {
if (params.file_name) {
HttpService.makeCallToBackend({
path: `/process-models/${params.process_group_id}/${params.process_model_id}/files/${params.file_name}`,
path: `/process-models/${modifiedProcessModelId}/files/${params.file_name}`,
successCallback: processResult,
});
}
}, [params]);
}, [params, modifiedProcessModelId]);
const navigateToProcessModelFile = (_result: any) => {
if (!params.file_name) {
const fileNameWithExtension = `${newFileName}.${fileExtension}`;
navigate(
`/admin/process-models/${params.process_group_id}/${params.process_model_id}/form/${fileNameWithExtension}`
`/admin/process-models/${modifiedProcessModelId}/form/${fileNameWithExtension}`
);
}
};
const saveFile = () => {
let url = `/process-models/${params.process_group_id}/${params.process_model_id}/files`;
let url = `/process-models/${modifiedProcessModelId}/files`;
let httpMethod = 'PUT';
let fileNameWithExtension = params.file_name;
@ -90,13 +98,11 @@ export default function ReactFormEditor() {
};
const deleteFile = () => {
const url = `/process-models/${params.process_group_id}/${params.process_model_id}/files/${params.file_name}`;
const url = `/process-models/${modifiedProcessModelId}/files/${params.file_name}`;
const httpMethod = 'DELETE';
const navigateToProcessModelShow = (_httpResult: any) => {
navigate(
`/admin/process-models/${params.process_group_id}/${params.process_model_id}`
);
navigate(`/admin/process-models/${modifiedProcessModelId}`);
};
HttpService.makeCallToBackend({
@ -143,16 +149,29 @@ export default function ReactFormEditor() {
};
if (processModelFile || !params.file_name) {
const processModelFileName = processModelFile ? processModelFile.name : '';
return (
<main>
<ProcessBreadcrumb
processGroupId={params.process_group_id}
processModelId={params.process_model_id}
linkProcessModel
hotCrumbs={[
['Process Groups', '/admin'],
[
`Process Model: ${unModifyProcessModelPath(
params.process_model_id || ''
)}`,
`process_model:${unModifyProcessModelPath(
params.process_model_id || ''
)}:link`,
],
[processModelFileName],
]}
/>
<h2>
Process Model File
{processModelFile ? `: ${(processModelFile as any).name}` : ''}
Process Model File{processModelFile ? ': ' : ''}
{processModelFileName}
</h2>
{newFileNameBox()}
<Button onClick={saveFile} variant="danger" data-qa="file-save-button">