Squashed 'spiffworkflow-frontend/' changes from 55607af9..16dc9a7c
16dc9a7c Merge pull request #75 from sartography/bug/replace-file-warning c9d376a3 Merge pull request #74 from sartography/bug/delete-primary-warning dcf5b5f2 Merge pull request #73 from sartography/bug/save-file-message d4ae393b added new api endpoint to get task-info so users with access to process instances can see the tasks but not the data 3d6c0acb get all of the process identifiers that the diagram knows about so we can display the correct task info 818fdbbd Allow viewing/editing xml of bpmn and dmn files (#76) e8a86a5a words 04ee4be6 process model cypress tests are passing w/ burnettk 23754677 some fixes for ci w/ burnettk 4ceb9364 throw error if not logged in w/ burnettk 7272eec0 force login if not logged when navigating to frontend w/ burnettk e285d9a4 Merge pull request #72 from sartography/feature/view_call_activity_diagram db8c9ec8 pyl and fix test w/ burnettk 7f0416cb use forEach 54881ae8 gitignore things f09d3aab Add a message when file is saved. 3bb56589 some fixes to ensure we display the correct task data for the diagram elements w/ burnettk 29533216 Don't show delete button for primary file 4fbeba49 allow viewing the diagram for a specific process identifier 43d7bfed Confirm before overwriting file when uploading file with same name git-subtree-dir: spiffworkflow-frontend git-subtree-split: 16dc9a7c4535eea9f15374fad8ecff77d6027a66
This commit is contained in:
parent
e4e0056581
commit
6b38b62ccf
|
@ -8,6 +8,9 @@
|
||||||
# testing
|
# testing
|
||||||
/coverage
|
/coverage
|
||||||
|
|
||||||
|
# in case we accidentally run backend tests in frontend. :D
|
||||||
|
/.coverage.*
|
||||||
|
|
||||||
# production
|
# production
|
||||||
/build
|
/build
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { modifyProcessIdentifierForPathParam } from '../../src/helpers';
|
import { modifyProcessIdentifierForPathParam } from '../../src/helpers';
|
||||||
|
import { miscDisplayName } from '../support/helpers';
|
||||||
|
|
||||||
describe('process-models', () => {
|
describe('process-models', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
@ -16,7 +17,7 @@ describe('process-models', () => {
|
||||||
const modelDisplayName = `Test Model 2 ${id}`;
|
const modelDisplayName = `Test Model 2 ${id}`;
|
||||||
const modelId = `test-model-2-${id}`;
|
const modelId = `test-model-2-${id}`;
|
||||||
const newModelDisplayName = `${modelDisplayName} edited`;
|
const newModelDisplayName = `${modelDisplayName} edited`;
|
||||||
cy.contains('99-Shared Resources').click();
|
cy.contains(miscDisplayName).click();
|
||||||
cy.wait(500);
|
cy.wait(500);
|
||||||
cy.contains(groupDisplayName).click();
|
cy.contains(groupDisplayName).click();
|
||||||
cy.createModel(groupId, modelId, modelDisplayName);
|
cy.createModel(groupId, modelId, modelDisplayName);
|
||||||
|
@ -34,7 +35,7 @@ describe('process-models', () => {
|
||||||
cy.contains(`Process Model: ${newModelDisplayName}`);
|
cy.contains(`Process Model: ${newModelDisplayName}`);
|
||||||
|
|
||||||
// go back to process model show by clicking on the breadcrumb
|
// go back to process model show by clicking on the breadcrumb
|
||||||
cy.contains(modelId).click();
|
cy.contains(modelDisplayName).click();
|
||||||
|
|
||||||
cy.getBySel('delete-process-model-button').click();
|
cy.getBySel('delete-process-model-button').click();
|
||||||
cy.contains('Are you sure');
|
cy.contains('Are you sure');
|
||||||
|
@ -46,6 +47,7 @@ describe('process-models', () => {
|
||||||
`process-groups/${modifyProcessIdentifierForPathParam(groupId)}`
|
`process-groups/${modifyProcessIdentifierForPathParam(groupId)}`
|
||||||
);
|
);
|
||||||
cy.contains(modelId).should('not.exist');
|
cy.contains(modelId).should('not.exist');
|
||||||
|
cy.contains(modelDisplayName).should('not.exist');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can create new bpmn, dmn, and json files', () => {
|
it('can create new bpmn, dmn, and json files', () => {
|
||||||
|
@ -61,11 +63,11 @@ describe('process-models', () => {
|
||||||
const dmnFileName = `dmn_test_file_${id}`;
|
const dmnFileName = `dmn_test_file_${id}`;
|
||||||
const jsonFileName = `json_test_file_${id}`;
|
const jsonFileName = `json_test_file_${id}`;
|
||||||
|
|
||||||
cy.contains('99-Shared Resources').click();
|
cy.contains(miscDisplayName).click();
|
||||||
cy.wait(500);
|
cy.wait(500);
|
||||||
cy.contains(groupDisplayName).click();
|
cy.contains(groupDisplayName).click();
|
||||||
cy.createModel(groupId, modelId, modelDisplayName);
|
cy.createModel(groupId, modelId, modelDisplayName);
|
||||||
cy.contains(directParentGroupId).click();
|
cy.contains(groupDisplayName).click();
|
||||||
cy.contains(modelDisplayName).click();
|
cy.contains(modelDisplayName).click();
|
||||||
cy.url().should(
|
cy.url().should(
|
||||||
'include',
|
'include',
|
||||||
|
@ -90,7 +92,7 @@ describe('process-models', () => {
|
||||||
cy.get('input[name=file_name]').type(bpmnFileName);
|
cy.get('input[name=file_name]').type(bpmnFileName);
|
||||||
cy.contains('Save Changes').click();
|
cy.contains('Save Changes').click();
|
||||||
cy.contains(`Process Model File: ${bpmnFileName}`);
|
cy.contains(`Process Model File: ${bpmnFileName}`);
|
||||||
cy.contains(modelId).click();
|
cy.contains(modelDisplayName).click();
|
||||||
cy.contains(`Process Model: ${modelDisplayName}`);
|
cy.contains(`Process Model: ${modelDisplayName}`);
|
||||||
// cy.getBySel('files-accordion').click();
|
// cy.getBySel('files-accordion').click();
|
||||||
cy.contains(`${bpmnFileName}.bpmn`).should('exist');
|
cy.contains(`${bpmnFileName}.bpmn`).should('exist');
|
||||||
|
@ -108,7 +110,7 @@ describe('process-models', () => {
|
||||||
cy.get('input[name=file_name]').type(dmnFileName);
|
cy.get('input[name=file_name]').type(dmnFileName);
|
||||||
cy.contains('Save Changes').click();
|
cy.contains('Save Changes').click();
|
||||||
cy.contains(`Process Model File: ${dmnFileName}`);
|
cy.contains(`Process Model File: ${dmnFileName}`);
|
||||||
cy.contains(modelId).click();
|
cy.contains(modelDisplayName).click();
|
||||||
cy.contains(`Process Model: ${modelDisplayName}`);
|
cy.contains(`Process Model: ${modelDisplayName}`);
|
||||||
// cy.getBySel('files-accordion').click();
|
// cy.getBySel('files-accordion').click();
|
||||||
cy.contains(`${dmnFileName}.dmn`).should('exist');
|
cy.contains(`${dmnFileName}.dmn`).should('exist');
|
||||||
|
@ -124,7 +126,7 @@ describe('process-models', () => {
|
||||||
cy.contains(`Process Model File: ${jsonFileName}`);
|
cy.contains(`Process Model File: ${jsonFileName}`);
|
||||||
// wait for json to load before clicking away to avoid network errors
|
// wait for json to load before clicking away to avoid network errors
|
||||||
cy.wait(500);
|
cy.wait(500);
|
||||||
cy.contains(modelId).click();
|
cy.contains(modelDisplayName).click();
|
||||||
cy.contains(`Process Model: ${modelDisplayName}`);
|
cy.contains(`Process Model: ${modelDisplayName}`);
|
||||||
// cy.getBySel('files-accordion').click();
|
// cy.getBySel('files-accordion').click();
|
||||||
cy.contains(`${jsonFileName}.json`).should('exist');
|
cy.contains(`${jsonFileName}.json`).should('exist');
|
||||||
|
@ -151,12 +153,12 @@ describe('process-models', () => {
|
||||||
const modelDisplayName = `Test Model 2 ${id}`;
|
const modelDisplayName = `Test Model 2 ${id}`;
|
||||||
const modelId = `test-model-2-${id}`;
|
const modelId = `test-model-2-${id}`;
|
||||||
cy.contains('Add a process group');
|
cy.contains('Add a process group');
|
||||||
cy.contains('99-Shared Resources').click();
|
cy.contains(miscDisplayName).click();
|
||||||
cy.wait(500);
|
cy.wait(500);
|
||||||
cy.contains(groupDisplayName).click();
|
cy.contains(groupDisplayName).click();
|
||||||
cy.createModel(groupId, modelId, modelDisplayName);
|
cy.createModel(groupId, modelId, modelDisplayName);
|
||||||
|
|
||||||
cy.contains(`${directParentGroupId}`).click();
|
cy.contains(`${groupDisplayName}`).click();
|
||||||
cy.contains('Add a process model');
|
cy.contains('Add a process model');
|
||||||
cy.contains(modelDisplayName).click();
|
cy.contains(modelDisplayName).click();
|
||||||
cy.url().should(
|
cy.url().should(
|
||||||
|
@ -186,7 +188,7 @@ describe('process-models', () => {
|
||||||
.click();
|
.click();
|
||||||
|
|
||||||
// in breadcrumb
|
// in breadcrumb
|
||||||
cy.contains(modelId).click();
|
cy.contains(modelDisplayName).click();
|
||||||
|
|
||||||
cy.getBySel('delete-process-model-button').click();
|
cy.getBySel('delete-process-model-button').click();
|
||||||
cy.contains('Are you sure');
|
cy.contains('Are you sure');
|
||||||
|
@ -203,7 +205,7 @@ describe('process-models', () => {
|
||||||
|
|
||||||
// process models no longer has pagination post-tiles
|
// process models no longer has pagination post-tiles
|
||||||
// it.only('can paginate items', () => {
|
// it.only('can paginate items', () => {
|
||||||
// cy.contains('99-Shared Resources').click();
|
// cy.contains(miscDisplayName).click();
|
||||||
// cy.wait(500);
|
// cy.wait(500);
|
||||||
// cy.contains('Acceptance Tests Group One').click();
|
// cy.contains('Acceptance Tests Group One').click();
|
||||||
// cy.basicPaginationTest();
|
// cy.basicPaginationTest();
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { string } from 'prop-types';
|
import { string } from 'prop-types';
|
||||||
import { modifyProcessIdentifierForPathParam } from '../../src/helpers';
|
import { modifyProcessIdentifierForPathParam } from '../../src/helpers';
|
||||||
|
import { miscDisplayName } from './helpers';
|
||||||
|
|
||||||
// ***********************************************
|
// ***********************************************
|
||||||
// This example commands.js shows you how to
|
// This example commands.js shows you how to
|
||||||
|
@ -86,15 +87,15 @@ Cypress.Commands.add('createModel', (groupId, modelId, modelDisplayName) => {
|
||||||
Cypress.Commands.add(
|
Cypress.Commands.add(
|
||||||
'runPrimaryBpmnFile',
|
'runPrimaryBpmnFile',
|
||||||
(expectAutoRedirectToHumanTask = false) => {
|
(expectAutoRedirectToHumanTask = false) => {
|
||||||
cy.contains('Run').click();
|
cy.contains('Start').click();
|
||||||
if (expectAutoRedirectToHumanTask) {
|
if (expectAutoRedirectToHumanTask) {
|
||||||
// the url changes immediately, so also make sure we get some content from the next page, "Task:", or else when we try to interact with the page, it'll re-render and we'll get an error with cypress.
|
// the url changes immediately, so also make sure we get some content from the next page, "Task:", or else when we try to interact with the page, it'll re-render and we'll get an error with cypress.
|
||||||
cy.url().should('include', `/tasks/`);
|
cy.url().should('include', `/tasks/`);
|
||||||
cy.contains('Task: ');
|
cy.contains('Task: ');
|
||||||
} else {
|
} else {
|
||||||
cy.contains(/Process Instance.*kicked off/);
|
cy.contains(/Process Instance.*[kK]icked [oO]ff/);
|
||||||
cy.reload(true);
|
cy.reload(true);
|
||||||
cy.contains(/Process Instance.*kicked off/).should('not.exist');
|
cy.contains(/Process Instance.*[kK]icked [oO]ff/).should('not.exist');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -103,8 +104,8 @@ Cypress.Commands.add(
|
||||||
'navigateToProcessModel',
|
'navigateToProcessModel',
|
||||||
(groupDisplayName, modelDisplayName, modelIdentifier) => {
|
(groupDisplayName, modelDisplayName, modelIdentifier) => {
|
||||||
cy.navigateToAdmin();
|
cy.navigateToAdmin();
|
||||||
cy.contains('99-Shared Resources').click();
|
cy.contains(miscDisplayName).click();
|
||||||
cy.contains(`Process Group: 99-Shared Resources`, { timeout: 10000 });
|
cy.contains(`Process Group: ${miscDisplayName}`, { timeout: 10000 });
|
||||||
cy.contains(groupDisplayName).click();
|
cy.contains(groupDisplayName).click();
|
||||||
cy.contains(`Process Group: ${groupDisplayName}`);
|
cy.contains(`Process Group: ${groupDisplayName}`);
|
||||||
// https://stackoverflow.com/q/51254946/6090676
|
// https://stackoverflow.com/q/51254946/6090676
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
export const miscDisplayName = 'Shared Resources';
|
|
@ -68,7 +68,7 @@
|
||||||
"@cypress/grep": "^3.1.0",
|
"@cypress/grep": "^3.1.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.30.5",
|
"@typescript-eslint/eslint-plugin": "^5.30.5",
|
||||||
"@typescript-eslint/parser": "^5.30.6",
|
"@typescript-eslint/parser": "^5.30.6",
|
||||||
"cypress": "^10.8.0",
|
"cypress": "^12",
|
||||||
"eslint": "^8.19.0",
|
"eslint": "^8.19.0",
|
||||||
"eslint_d": "^12.2.0",
|
"eslint_d": "^12.2.0",
|
||||||
"eslint-config-airbnb": "^19.0.4",
|
"eslint-config-airbnb": "^19.0.4",
|
||||||
|
@ -9850,9 +9850,9 @@
|
||||||
"integrity": "sha512-NJGVKPS81XejHcLhaLJS7plab0fK3slPh11mESeeDq2W4ZI5kUKK/LRRdVDvjJseojbPB7ZwjnyOybg3Igea/A=="
|
"integrity": "sha512-NJGVKPS81XejHcLhaLJS7plab0fK3slPh11mESeeDq2W4ZI5kUKK/LRRdVDvjJseojbPB7ZwjnyOybg3Igea/A=="
|
||||||
},
|
},
|
||||||
"node_modules/cypress": {
|
"node_modules/cypress": {
|
||||||
"version": "10.11.0",
|
"version": "12.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/cypress/-/cypress-10.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/cypress/-/cypress-12.1.0.tgz",
|
||||||
"integrity": "sha512-lsaE7dprw5DoXM00skni6W5ElVVLGAdRUUdZjX2dYsGjbY/QnpzWZ95Zom1mkGg0hAaO/QVTZoFVS7Jgr/GUPA==",
|
"integrity": "sha512-7fz8N84uhN1+ePNDsfQvoWEl4P3/VGKKmAg+bJQFY4onhA37Ys+6oBkGbNdwGeC7n2QqibNVPhk8x3YuQLwzfw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -9903,7 +9903,7 @@
|
||||||
"cypress": "bin/cypress"
|
"cypress": "bin/cypress"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12.0.0"
|
"node": "^14.0.0 || ^16.0.0 || >=18.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/cypress/node_modules/@types/node": {
|
"node_modules/cypress/node_modules/@types/node": {
|
||||||
|
@ -38586,9 +38586,9 @@
|
||||||
"integrity": "sha512-NJGVKPS81XejHcLhaLJS7plab0fK3slPh11mESeeDq2W4ZI5kUKK/LRRdVDvjJseojbPB7ZwjnyOybg3Igea/A=="
|
"integrity": "sha512-NJGVKPS81XejHcLhaLJS7plab0fK3slPh11mESeeDq2W4ZI5kUKK/LRRdVDvjJseojbPB7ZwjnyOybg3Igea/A=="
|
||||||
},
|
},
|
||||||
"cypress": {
|
"cypress": {
|
||||||
"version": "10.11.0",
|
"version": "12.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/cypress/-/cypress-10.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/cypress/-/cypress-12.1.0.tgz",
|
||||||
"integrity": "sha512-lsaE7dprw5DoXM00skni6W5ElVVLGAdRUUdZjX2dYsGjbY/QnpzWZ95Zom1mkGg0hAaO/QVTZoFVS7Jgr/GUPA==",
|
"integrity": "sha512-7fz8N84uhN1+ePNDsfQvoWEl4P3/VGKKmAg+bJQFY4onhA37Ys+6oBkGbNdwGeC7n2QqibNVPhk8x3YuQLwzfw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@cypress/request": "^2.88.10",
|
"@cypress/request": "^2.88.10",
|
||||||
|
|
|
@ -104,7 +104,7 @@
|
||||||
"@cypress/grep": "^3.1.0",
|
"@cypress/grep": "^3.1.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.30.5",
|
"@typescript-eslint/eslint-plugin": "^5.30.5",
|
||||||
"@typescript-eslint/parser": "^5.30.6",
|
"@typescript-eslint/parser": "^5.30.6",
|
||||||
"cypress": "^10.8.0",
|
"cypress": "^12",
|
||||||
"eslint": "^8.19.0",
|
"eslint": "^8.19.0",
|
||||||
"eslint_d": "^12.2.0",
|
"eslint_d": "^12.2.0",
|
||||||
"eslint-config-airbnb": "^19.0.4",
|
"eslint-config-airbnb": "^19.0.4",
|
||||||
|
|
|
@ -13,6 +13,7 @@ import AdminRoutes from './routes/AdminRoutes';
|
||||||
import { ErrorForDisplay } from './interfaces';
|
import { ErrorForDisplay } from './interfaces';
|
||||||
|
|
||||||
import { AbilityContext } from './contexts/Can';
|
import { AbilityContext } from './contexts/Can';
|
||||||
|
import UserService from './services/UserService';
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const [errorMessage, setErrorMessage] = useState<ErrorForDisplay | null>(
|
const [errorMessage, setErrorMessage] = useState<ErrorForDisplay | null>(
|
||||||
|
@ -24,6 +25,11 @@ export default function App() {
|
||||||
[errorMessage]
|
[errorMessage]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!UserService.isLoggedIn()) {
|
||||||
|
UserService.doLogin();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const ability = defineAbility(() => {});
|
const ability = defineAbility(() => {});
|
||||||
|
|
||||||
let errorTag = null;
|
let errorTag = null;
|
||||||
|
|
|
@ -24,6 +24,7 @@ import UserService from '../services/UserService';
|
||||||
import { useUriListForPermissions } from '../hooks/UriListForPermissions';
|
import { useUriListForPermissions } from '../hooks/UriListForPermissions';
|
||||||
import { PermissionsToCheck } from '../interfaces';
|
import { PermissionsToCheck } from '../interfaces';
|
||||||
import { usePermissionFetcher } from '../hooks/PermissionService';
|
import { usePermissionFetcher } from '../hooks/PermissionService';
|
||||||
|
import { UnauthenticatedError } from '../services/HttpService';
|
||||||
|
|
||||||
// for ref: https://react-bootstrap.github.io/components/navbar/
|
// for ref: https://react-bootstrap.github.io/components/navbar/
|
||||||
export default function NavigationBar() {
|
export default function NavigationBar() {
|
||||||
|
@ -39,6 +40,11 @@ export default function NavigationBar() {
|
||||||
const [activeKey, setActiveKey] = useState<string>('');
|
const [activeKey, setActiveKey] = useState<string>('');
|
||||||
|
|
||||||
const { targetUris } = useUriListForPermissions();
|
const { targetUris } = useUriListForPermissions();
|
||||||
|
|
||||||
|
// App.jsx forces login (which redirects to keycloak) so we should never get here if we're not logged in.
|
||||||
|
if (!UserService.isLoggedIn()) {
|
||||||
|
throw new UnauthenticatedError('You must be authenticated to do this.');
|
||||||
|
}
|
||||||
const permissionRequestData: PermissionsToCheck = {
|
const permissionRequestData: PermissionsToCheck = {
|
||||||
[targetUris.authenticationListPath]: ['GET'],
|
[targetUris.authenticationListPath]: ['GET'],
|
||||||
[targetUris.messageInstanceListPath]: ['GET'],
|
[targetUris.messageInstanceListPath]: ['GET'],
|
||||||
|
@ -135,6 +141,9 @@ export default function NavigationBar() {
|
||||||
};
|
};
|
||||||
|
|
||||||
const headerMenuItems = () => {
|
const headerMenuItems = () => {
|
||||||
|
if (!UserService.isLoggedIn()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<HeaderMenuItem href="/" isCurrentPage={isActivePage('/')}>
|
<HeaderMenuItem href="/" isCurrentPage={isActivePage('/')}>
|
||||||
|
|
|
@ -300,8 +300,13 @@ export default function ProcessInstanceListTable({
|
||||||
|
|
||||||
checkFiltersAndRun();
|
checkFiltersAndRun();
|
||||||
if (autoReload) {
|
if (autoReload) {
|
||||||
refreshAtInterval(REFRESH_INTERVAL, REFRESH_TIMEOUT, checkFiltersAndRun);
|
return refreshAtInterval(
|
||||||
|
REFRESH_INTERVAL,
|
||||||
|
REFRESH_TIMEOUT,
|
||||||
|
checkFiltersAndRun
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
return undefined;
|
||||||
}, [
|
}, [
|
||||||
autoReload,
|
autoReload,
|
||||||
searchParams,
|
searchParams,
|
||||||
|
@ -838,8 +843,8 @@ export default function ProcessInstanceListTable({
|
||||||
return null;
|
return null;
|
||||||
}}
|
}}
|
||||||
shouldFilterItem={shouldFilterReportColumn}
|
shouldFilterItem={shouldFilterReportColumn}
|
||||||
placeholder="Choose a report column"
|
placeholder="Choose a column to show"
|
||||||
titleText="Report Column"
|
titleText="Column"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -888,7 +893,7 @@ export default function ProcessInstanceListTable({
|
||||||
kind="ghost"
|
kind="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
className={`button-tag-icon ${tagTypeClass}`}
|
className={`button-tag-icon ${tagTypeClass}`}
|
||||||
title={`Edit ${reportColumnForEditing.accessor}`}
|
title={`Edit ${reportColumnForEditing.accessor} column`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setReportColumnToOperateOn(reportColumnForEditing);
|
setReportColumnToOperateOn(reportColumnForEditing);
|
||||||
setShowReportColumnForm(true);
|
setShowReportColumnForm(true);
|
||||||
|
@ -916,7 +921,7 @@ export default function ProcessInstanceListTable({
|
||||||
<Button
|
<Button
|
||||||
data-qa="add-column-button"
|
data-qa="add-column-button"
|
||||||
renderIcon={AddAlt}
|
renderIcon={AddAlt}
|
||||||
iconDescription="Filter Options"
|
iconDescription="Column options"
|
||||||
className="with-tiny-top-margin"
|
className="with-tiny-top-margin"
|
||||||
kind="ghost"
|
kind="ghost"
|
||||||
hasIconOnly
|
hasIconOnly
|
||||||
|
|
|
@ -52,22 +52,25 @@ import TouchModule from 'diagram-js/lib/navigation/touch';
|
||||||
// @ts-expect-error TS(7016) FIXME
|
// @ts-expect-error TS(7016) FIXME
|
||||||
import ZoomScrollModule from 'diagram-js/lib/navigation/zoomscroll';
|
import ZoomScrollModule from 'diagram-js/lib/navigation/zoomscroll';
|
||||||
|
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
import { Can } from '@casl/react';
|
import { Can } from '@casl/react';
|
||||||
import HttpService from '../services/HttpService';
|
import HttpService from '../services/HttpService';
|
||||||
|
|
||||||
import ButtonWithConfirmation from './ButtonWithConfirmation';
|
import ButtonWithConfirmation from './ButtonWithConfirmation';
|
||||||
import { makeid } from '../helpers';
|
import { getBpmnProcessIdentifiers, makeid } from '../helpers';
|
||||||
import { useUriListForPermissions } from '../hooks/UriListForPermissions';
|
import { useUriListForPermissions } from '../hooks/UriListForPermissions';
|
||||||
import { PermissionsToCheck } from '../interfaces';
|
import { PermissionsToCheck, ProcessInstanceTask } from '../interfaces';
|
||||||
import { usePermissionFetcher } from '../hooks/PermissionService';
|
import { usePermissionFetcher } from '../hooks/PermissionService';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
processModelId: string;
|
processModelId: string;
|
||||||
diagramType: string;
|
diagramType: string;
|
||||||
readyOrWaitingBpmnTaskIds?: string[] | null;
|
readyOrWaitingProcessInstanceTasks?: ProcessInstanceTask[] | null;
|
||||||
completedTasksBpmnIds?: string[] | null;
|
completedProcessInstanceTasks?: ProcessInstanceTask[] | null;
|
||||||
saveDiagram?: (..._args: any[]) => any;
|
saveDiagram?: (..._args: any[]) => any;
|
||||||
onDeleteFile?: (..._args: any[]) => any;
|
onDeleteFile?: (..._args: any[]) => any;
|
||||||
|
isPrimaryFile?: boolean;
|
||||||
onSetPrimaryFile?: (..._args: any[]) => any;
|
onSetPrimaryFile?: (..._args: any[]) => any;
|
||||||
diagramXML?: string | null;
|
diagramXML?: string | null;
|
||||||
fileName?: string;
|
fileName?: string;
|
||||||
|
@ -88,10 +91,11 @@ type OwnProps = {
|
||||||
export default function ReactDiagramEditor({
|
export default function ReactDiagramEditor({
|
||||||
processModelId,
|
processModelId,
|
||||||
diagramType,
|
diagramType,
|
||||||
readyOrWaitingBpmnTaskIds,
|
readyOrWaitingProcessInstanceTasks,
|
||||||
completedTasksBpmnIds,
|
completedProcessInstanceTasks,
|
||||||
saveDiagram,
|
saveDiagram,
|
||||||
onDeleteFile,
|
onDeleteFile,
|
||||||
|
isPrimaryFile,
|
||||||
onSetPrimaryFile,
|
onSetPrimaryFile,
|
||||||
diagramXML,
|
diagramXML,
|
||||||
fileName,
|
fileName,
|
||||||
|
@ -119,6 +123,7 @@ export default function ReactDiagramEditor({
|
||||||
[targetUris.processModelFileShowPath]: ['POST', 'GET', 'PUT', 'DELETE'],
|
[targetUris.processModelFileShowPath]: ['POST', 'GET', 'PUT', 'DELETE'],
|
||||||
};
|
};
|
||||||
const { ability } = usePermissionFetcher(permissionRequestData);
|
const { ability } = usePermissionFetcher(permissionRequestData);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (diagramModelerState) {
|
if (diagramModelerState) {
|
||||||
|
@ -227,7 +232,11 @@ export default function ReactDiagramEditor({
|
||||||
|
|
||||||
function handleElementClick(event: any) {
|
function handleElementClick(event: any) {
|
||||||
if (onElementClick) {
|
if (onElementClick) {
|
||||||
onElementClick(event.element);
|
const canvas = diagramModeler.get('canvas');
|
||||||
|
const bpmnProcessIdentifiers = getBpmnProcessIdentifiers(
|
||||||
|
canvas.getRootElement()
|
||||||
|
);
|
||||||
|
onElementClick(event.element, bpmnProcessIdentifiers);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -350,12 +359,19 @@ export default function ReactDiagramEditor({
|
||||||
|
|
||||||
function highlightBpmnIoElement(
|
function highlightBpmnIoElement(
|
||||||
canvas: any,
|
canvas: any,
|
||||||
taskBpmnId: string,
|
processInstanceTask: ProcessInstanceTask,
|
||||||
bpmnIoClassName: string
|
bpmnIoClassName: string,
|
||||||
|
bpmnProcessIdentifiers: string[]
|
||||||
) {
|
) {
|
||||||
if (checkTaskCanBeHighlighted(taskBpmnId)) {
|
if (checkTaskCanBeHighlighted(processInstanceTask.name)) {
|
||||||
try {
|
try {
|
||||||
canvas.addMarker(taskBpmnId, bpmnIoClassName);
|
if (
|
||||||
|
bpmnProcessIdentifiers.includes(
|
||||||
|
processInstanceTask.process_identifier
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
canvas.addMarker(processInstanceTask.name, bpmnIoClassName);
|
||||||
|
}
|
||||||
} catch (bpmnIoError: any) {
|
} catch (bpmnIoError: any) {
|
||||||
// the task list also contains task for processes called from call activities which will
|
// the task list also contains task for processes called from call activities which will
|
||||||
// not exist in this diagram so just ignore them for now.
|
// not exist in this diagram so just ignore them for now.
|
||||||
|
@ -394,21 +410,29 @@ export default function ReactDiagramEditor({
|
||||||
// highlighting a field
|
// highlighting a field
|
||||||
// Option 3 at:
|
// Option 3 at:
|
||||||
// https://github.com/bpmn-io/bpmn-js-examples/tree/master/colors
|
// https://github.com/bpmn-io/bpmn-js-examples/tree/master/colors
|
||||||
if (readyOrWaitingBpmnTaskIds) {
|
if (readyOrWaitingProcessInstanceTasks) {
|
||||||
readyOrWaitingBpmnTaskIds.forEach((readyOrWaitingBpmnTaskId) => {
|
const bpmnProcessIdentifiers = getBpmnProcessIdentifiers(
|
||||||
|
canvas.getRootElement()
|
||||||
|
);
|
||||||
|
readyOrWaitingProcessInstanceTasks.forEach((readyOrWaitingBpmnTask) => {
|
||||||
highlightBpmnIoElement(
|
highlightBpmnIoElement(
|
||||||
canvas,
|
canvas,
|
||||||
readyOrWaitingBpmnTaskId,
|
readyOrWaitingBpmnTask,
|
||||||
'active-task-highlight'
|
'active-task-highlight',
|
||||||
|
bpmnProcessIdentifiers
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (completedTasksBpmnIds) {
|
if (completedProcessInstanceTasks) {
|
||||||
completedTasksBpmnIds.forEach((completedTaskBpmnId) => {
|
const bpmnProcessIdentifiers = getBpmnProcessIdentifiers(
|
||||||
|
canvas.getRootElement()
|
||||||
|
);
|
||||||
|
completedProcessInstanceTasks.forEach((completedTask) => {
|
||||||
highlightBpmnIoElement(
|
highlightBpmnIoElement(
|
||||||
canvas,
|
canvas,
|
||||||
completedTaskBpmnId,
|
completedTask,
|
||||||
'completed-task-highlight'
|
'completed-task-highlight',
|
||||||
|
bpmnProcessIdentifiers
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -484,8 +508,8 @@ export default function ReactDiagramEditor({
|
||||||
diagramType,
|
diagramType,
|
||||||
diagramXML,
|
diagramXML,
|
||||||
diagramXMLString,
|
diagramXMLString,
|
||||||
readyOrWaitingBpmnTaskIds,
|
readyOrWaitingProcessInstanceTasks,
|
||||||
completedTasksBpmnIds,
|
completedProcessInstanceTasks,
|
||||||
fileName,
|
fileName,
|
||||||
performingXmlUpdates,
|
performingXmlUpdates,
|
||||||
processModelId,
|
processModelId,
|
||||||
|
@ -533,6 +557,8 @@ export default function ReactDiagramEditor({
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const canViewXml = fileName !== undefined;
|
||||||
|
|
||||||
const userActionOptions = () => {
|
const userActionOptions = () => {
|
||||||
if (diagramType !== 'readonly') {
|
if (diagramType !== 'readonly') {
|
||||||
return (
|
return (
|
||||||
|
@ -549,7 +575,7 @@ export default function ReactDiagramEditor({
|
||||||
a={targetUris.processModelFileShowPath}
|
a={targetUris.processModelFileShowPath}
|
||||||
ability={ability}
|
ability={ability}
|
||||||
>
|
>
|
||||||
{fileName && (
|
{fileName && !isPrimaryFile && (
|
||||||
<ButtonWithConfirmation
|
<ButtonWithConfirmation
|
||||||
description={`Delete file ${fileName}?`}
|
description={`Delete file ${fileName}?`}
|
||||||
onConfirmation={handleDelete}
|
onConfirmation={handleDelete}
|
||||||
|
@ -571,6 +597,23 @@ export default function ReactDiagramEditor({
|
||||||
>
|
>
|
||||||
<Button onClick={downloadXmlFile}>Download</Button>
|
<Button onClick={downloadXmlFile}>Download</Button>
|
||||||
</Can>
|
</Can>
|
||||||
|
<Can
|
||||||
|
I="GET"
|
||||||
|
a={targetUris.processModelFileShowPath}
|
||||||
|
ability={ability}
|
||||||
|
>
|
||||||
|
{canViewXml && (
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
navigate(
|
||||||
|
`/admin/process-models/${processModelId}/form/${fileName}`
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
View XML
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Can>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,7 @@ export default function MyOpenProcesses() {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
getTasks();
|
getTasks();
|
||||||
refreshAtInterval(REFRESH_INTERVAL, REFRESH_TIMEOUT, getTasks);
|
return refreshAtInterval(REFRESH_INTERVAL, REFRESH_TIMEOUT, getTasks);
|
||||||
}, [searchParams]);
|
}, [searchParams]);
|
||||||
|
|
||||||
const buildTable = () => {
|
const buildTable = () => {
|
||||||
|
|
|
@ -41,7 +41,7 @@ export default function TasksWaitingForMyGroups() {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
getTasks();
|
getTasks();
|
||||||
refreshAtInterval(REFRESH_INTERVAL, REFRESH_TIMEOUT, getTasks);
|
return refreshAtInterval(REFRESH_INTERVAL, REFRESH_TIMEOUT, getTasks);
|
||||||
}, [searchParams]);
|
}, [searchParams]);
|
||||||
|
|
||||||
const buildTable = () => {
|
const buildTable = () => {
|
||||||
|
|
|
@ -208,5 +208,29 @@ export const refreshAtInterval = (
|
||||||
() => clearInterval(intervalRef),
|
() => clearInterval(intervalRef),
|
||||||
timeout * 1000
|
timeout * 1000
|
||||||
);
|
);
|
||||||
return [intervalRef, timeoutRef];
|
return () => {
|
||||||
|
clearInterval(intervalRef);
|
||||||
|
clearTimeout(timeoutRef);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getChildProcesses = (bpmnElement: any) => {
|
||||||
|
let elements: string[] = [];
|
||||||
|
bpmnElement.children.forEach((c: any) => {
|
||||||
|
if (c.type === 'bpmn:Participant') {
|
||||||
|
if (c.businessObject.processRef) {
|
||||||
|
elements.push(c.businessObject.processRef.id);
|
||||||
|
}
|
||||||
|
elements = [...elements, ...getChildProcesses(c)];
|
||||||
|
} else if (c.type === 'bpmn:SubProcess') {
|
||||||
|
elements.push(c.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return elements;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getBpmnProcessIdentifiers = (rootBpmnElement: any) => {
|
||||||
|
const childProcesses = getChildProcesses(rootBpmnElement);
|
||||||
|
childProcesses.push(rootBpmnElement.businessObject.id);
|
||||||
|
return childProcesses;
|
||||||
};
|
};
|
||||||
|
|
|
@ -14,7 +14,8 @@ export const useUriListForPermissions = () => {
|
||||||
processInstanceListPath: '/v1.0/process-instances',
|
processInstanceListPath: '/v1.0/process-instances',
|
||||||
processInstanceLogListPath: `/v1.0/logs/${params.process_model_id}/${params.process_instance_id}`,
|
processInstanceLogListPath: `/v1.0/logs/${params.process_model_id}/${params.process_instance_id}`,
|
||||||
processInstanceReportListPath: '/v1.0/process-instances/reports',
|
processInstanceReportListPath: '/v1.0/process-instances/reports',
|
||||||
processInstanceTaskListPath: `/v1.0/task-data/${params.process_model_id}/${params.process_instance_id}`,
|
processInstanceTaskListPath: `/v1.0/process-instances/${params.process_model_id}/${params.process_instance_id}/task-info`,
|
||||||
|
processInstanceTaskListDataPath: `/v1.0/task-data/${params.process_model_id}/${params.process_instance_id}`,
|
||||||
processModelCreatePath: `/v1.0/process-models/${params.process_group_id}`,
|
processModelCreatePath: `/v1.0/process-models/${params.process_group_id}`,
|
||||||
processModelFileCreatePath: `/v1.0/process-models/${params.process_model_id}/files`,
|
processModelFileCreatePath: `/v1.0/process-models/${params.process_model_id}/files`,
|
||||||
processModelFileShowPath: `/v1.0/process-models/${params.process_model_id}/files/${params.file_name}`,
|
processModelFileShowPath: `/v1.0/process-models/${params.process_model_id}/files/${params.file_name}`,
|
||||||
|
|
|
@ -11,6 +11,13 @@ export interface RecentProcessModel {
|
||||||
processModelDisplayName: string;
|
processModelDisplayName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ProcessInstanceTask {
|
||||||
|
id: string;
|
||||||
|
state: string;
|
||||||
|
process_identifier: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ProcessReference {
|
export interface ProcessReference {
|
||||||
name: string; // The process or decision Display name.
|
name: string; // The process or decision Display name.
|
||||||
identifier: string; // The unique id of the process
|
identifier: string; // The unique id of the process
|
||||||
|
@ -39,6 +46,7 @@ export interface ProcessInstance {
|
||||||
id: number;
|
id: number;
|
||||||
process_model_identifier: string;
|
process_model_identifier: string;
|
||||||
process_model_display_name: string;
|
process_model_display_name: string;
|
||||||
|
spiff_step?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MessageCorrelationProperties {
|
export interface MessageCorrelationProperties {
|
||||||
|
|
|
@ -21,10 +21,11 @@ export default function ProcessInstanceList() {
|
||||||
<ProcessBreadcrumb
|
<ProcessBreadcrumb
|
||||||
hotCrumbs={[
|
hotCrumbs={[
|
||||||
['Process Groups', '/admin'],
|
['Process Groups', '/admin'],
|
||||||
[
|
{
|
||||||
`Process Model: ${processModelFullIdentifier}`,
|
entityToExplode: processModelFullIdentifier,
|
||||||
`process_model:${processModelFullIdentifier}:link`,
|
entityType: 'process-model-id',
|
||||||
],
|
linkLastItem: true,
|
||||||
|
},
|
||||||
['Process Instances'],
|
['Process Instances'],
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
import { useContext, useEffect, useState } from 'react';
|
import { useContext, useEffect, useState } from 'react';
|
||||||
import Editor from '@monaco-editor/react';
|
import Editor from '@monaco-editor/react';
|
||||||
import { useParams, useNavigate, Link } from 'react-router-dom';
|
import {
|
||||||
|
useParams,
|
||||||
|
useNavigate,
|
||||||
|
Link,
|
||||||
|
useSearchParams,
|
||||||
|
} from 'react-router-dom';
|
||||||
import {
|
import {
|
||||||
TrashCan,
|
TrashCan,
|
||||||
StopOutline,
|
StopOutline,
|
||||||
|
@ -34,15 +39,21 @@ import {
|
||||||
import ButtonWithConfirmation from '../components/ButtonWithConfirmation';
|
import ButtonWithConfirmation from '../components/ButtonWithConfirmation';
|
||||||
import ErrorContext from '../contexts/ErrorContext';
|
import ErrorContext from '../contexts/ErrorContext';
|
||||||
import { useUriListForPermissions } from '../hooks/UriListForPermissions';
|
import { useUriListForPermissions } from '../hooks/UriListForPermissions';
|
||||||
import { PermissionsToCheck } from '../interfaces';
|
import {
|
||||||
|
PermissionsToCheck,
|
||||||
|
ProcessInstance,
|
||||||
|
ProcessInstanceTask,
|
||||||
|
} from '../interfaces';
|
||||||
import { usePermissionFetcher } from '../hooks/PermissionService';
|
import { usePermissionFetcher } from '../hooks/PermissionService';
|
||||||
|
|
||||||
export default function ProcessInstanceShow() {
|
export default function ProcessInstanceShow() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
|
||||||
const [processInstance, setProcessInstance] = useState(null);
|
const [processInstance, setProcessInstance] =
|
||||||
const [tasks, setTasks] = useState<Array<object> | null>(null);
|
useState<ProcessInstance | null>(null);
|
||||||
|
const [tasks, setTasks] = useState<ProcessInstanceTask[] | null>(null);
|
||||||
const [tasksCallHadError, setTasksCallHadError] = useState<boolean>(false);
|
const [tasksCallHadError, setTasksCallHadError] = useState<boolean>(false);
|
||||||
const [taskToDisplay, setTaskToDisplay] = useState<object | null>(null);
|
const [taskToDisplay, setTaskToDisplay] = useState<object | null>(null);
|
||||||
const [taskDataToDisplay, setTaskDataToDisplay] = useState<string>('');
|
const [taskDataToDisplay, setTaskDataToDisplay] = useState<string>('');
|
||||||
|
@ -59,8 +70,10 @@ export default function ProcessInstanceShow() {
|
||||||
const permissionRequestData: PermissionsToCheck = {
|
const permissionRequestData: PermissionsToCheck = {
|
||||||
[targetUris.messageInstanceListPath]: ['GET'],
|
[targetUris.messageInstanceListPath]: ['GET'],
|
||||||
[targetUris.processInstanceTaskListPath]: ['GET'],
|
[targetUris.processInstanceTaskListPath]: ['GET'],
|
||||||
|
[targetUris.processInstanceTaskListDataPath]: ['GET', 'PUT'],
|
||||||
[targetUris.processInstanceActionPath]: ['DELETE'],
|
[targetUris.processInstanceActionPath]: ['DELETE'],
|
||||||
[targetUris.processInstanceLogListPath]: ['GET'],
|
[targetUris.processInstanceLogListPath]: ['GET'],
|
||||||
|
[targetUris.processModelShowPath]: ['PUT'],
|
||||||
[`${targetUris.processInstanceActionPath}/suspend`]: ['PUT'],
|
[`${targetUris.processInstanceActionPath}/suspend`]: ['PUT'],
|
||||||
[`${targetUris.processInstanceActionPath}/terminate`]: ['PUT'],
|
[`${targetUris.processInstanceActionPath}/terminate`]: ['PUT'],
|
||||||
[`${targetUris.processInstanceActionPath}/resume`]: ['PUT'],
|
[`${targetUris.processInstanceActionPath}/resume`]: ['PUT'],
|
||||||
|
@ -80,17 +93,28 @@ export default function ProcessInstanceShow() {
|
||||||
const processTaskFailure = () => {
|
const processTaskFailure = () => {
|
||||||
setTasksCallHadError(true);
|
setTasksCallHadError(true);
|
||||||
};
|
};
|
||||||
|
let queryParams = '';
|
||||||
|
const processIdentifier = searchParams.get('process_identifier');
|
||||||
|
if (processIdentifier) {
|
||||||
|
queryParams = `?process_identifier=${processIdentifier}`;
|
||||||
|
}
|
||||||
HttpService.makeCallToBackend({
|
HttpService.makeCallToBackend({
|
||||||
path: `/process-instances/${modifiedProcessModelId}/${params.process_instance_id}`,
|
path: `/process-instances/${modifiedProcessModelId}/${params.process_instance_id}${queryParams}`,
|
||||||
successCallback: setProcessInstance,
|
successCallback: setProcessInstance,
|
||||||
});
|
});
|
||||||
let taskParams = '?all_tasks=true';
|
let taskParams = '?all_tasks=true';
|
||||||
if (typeof params.spiff_step !== 'undefined') {
|
if (typeof params.spiff_step !== 'undefined') {
|
||||||
taskParams = `${taskParams}&spiff_step=${params.spiff_step}`;
|
taskParams = `${taskParams}&spiff_step=${params.spiff_step}`;
|
||||||
}
|
}
|
||||||
if (ability.can('GET', targetUris.processInstanceTaskListPath)) {
|
let taskPath = '';
|
||||||
|
if (ability.can('GET', targetUris.processInstanceTaskListDataPath)) {
|
||||||
|
taskPath = `${targetUris.processInstanceTaskListDataPath}${taskParams}`;
|
||||||
|
} else if (ability.can('GET', targetUris.processInstanceTaskListPath)) {
|
||||||
|
taskPath = `${targetUris.processInstanceTaskListPath}${taskParams}`;
|
||||||
|
}
|
||||||
|
if (taskPath) {
|
||||||
HttpService.makeCallToBackend({
|
HttpService.makeCallToBackend({
|
||||||
path: `${targetUris.processInstanceTaskListPath}${taskParams}`,
|
path: taskPath,
|
||||||
successCallback: setTasks,
|
successCallback: setTasks,
|
||||||
failureCallback: processTaskFailure,
|
failureCallback: processTaskFailure,
|
||||||
});
|
});
|
||||||
|
@ -98,7 +122,14 @@ export default function ProcessInstanceShow() {
|
||||||
setTasksCallHadError(true);
|
setTasksCallHadError(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [params, modifiedProcessModelId, permissionsLoaded, ability, targetUris]);
|
}, [
|
||||||
|
params,
|
||||||
|
modifiedProcessModelId,
|
||||||
|
permissionsLoaded,
|
||||||
|
ability,
|
||||||
|
targetUris,
|
||||||
|
searchParams,
|
||||||
|
]);
|
||||||
|
|
||||||
const deleteProcessInstance = () => {
|
const deleteProcessInstance = () => {
|
||||||
HttpService.makeCallToBackend({
|
HttpService.makeCallToBackend({
|
||||||
|
@ -140,12 +171,12 @@ export default function ProcessInstanceShow() {
|
||||||
const getTaskIds = () => {
|
const getTaskIds = () => {
|
||||||
const taskIds = { completed: [], readyOrWaiting: [] };
|
const taskIds = { completed: [], readyOrWaiting: [] };
|
||||||
if (tasks) {
|
if (tasks) {
|
||||||
tasks.forEach(function getUserTasksElement(task: any) {
|
tasks.forEach(function getUserTasksElement(task: ProcessInstanceTask) {
|
||||||
if (task.state === 'COMPLETED') {
|
if (task.state === 'COMPLETED') {
|
||||||
(taskIds.completed as any).push(task.name);
|
(taskIds.completed as any).push(task);
|
||||||
}
|
}
|
||||||
if (task.state === 'READY' || task.state === 'WAITING') {
|
if (task.state === 'READY' || task.state === 'WAITING') {
|
||||||
(taskIds.readyOrWaiting as any).push(task.name);
|
(taskIds.readyOrWaiting as any).push(task);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -175,15 +206,18 @@ export default function ProcessInstanceShow() {
|
||||||
label: any,
|
label: any,
|
||||||
distance: number
|
distance: number
|
||||||
) => {
|
) => {
|
||||||
|
const processIdentifier = searchParams.get('process_identifier');
|
||||||
|
let queryParams = '';
|
||||||
|
if (processIdentifier) {
|
||||||
|
queryParams = `?process_identifier=${processIdentifier}`;
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
reloadDocument
|
reloadDocument
|
||||||
data-qa="process-instance-step-link"
|
data-qa="process-instance-step-link"
|
||||||
to={`/admin/process-instances/${
|
to={`/admin/process-instances/${params.process_model_id}/${
|
||||||
params.process_model_id
|
params.process_instance_id
|
||||||
}/process-instances/${params.process_instance_id}/${
|
}/${currentSpiffStep(processInstanceToUse) + distance}${queryParams}`}
|
||||||
currentSpiffStep(processInstanceToUse) + distance
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
</Link>
|
</Link>
|
||||||
|
@ -364,10 +398,15 @@ export default function ProcessInstanceShow() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClickedDiagramTask = (shapeElement: any) => {
|
const handleClickedDiagramTask = (
|
||||||
|
shapeElement: any,
|
||||||
|
bpmnProcessIdentifiers: any
|
||||||
|
) => {
|
||||||
if (tasks) {
|
if (tasks) {
|
||||||
const matchingTask: any = tasks.find(
|
const matchingTask: any = tasks.find(
|
||||||
(task: any) => task.name === shapeElement.id
|
(task: any) =>
|
||||||
|
task.name === shapeElement.id &&
|
||||||
|
bpmnProcessIdentifiers.includes(task.process_identifier)
|
||||||
);
|
);
|
||||||
if (matchingTask) {
|
if (matchingTask) {
|
||||||
setTaskToDisplay(matchingTask);
|
setTaskToDisplay(matchingTask);
|
||||||
|
@ -411,7 +450,9 @@ export default function ProcessInstanceShow() {
|
||||||
|
|
||||||
const canEditTaskData = (task: any) => {
|
const canEditTaskData = (task: any) => {
|
||||||
return (
|
return (
|
||||||
task.state === 'READY' && showingLastSpiffStep(processInstance as any)
|
ability.can('PUT', targetUris.processInstanceTaskListDataPath) &&
|
||||||
|
task.state === 'READY' &&
|
||||||
|
showingLastSpiffStep(processInstance as any)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -460,7 +501,10 @@ export default function ProcessInstanceShow() {
|
||||||
const taskDataButtons = (task: any) => {
|
const taskDataButtons = (task: any) => {
|
||||||
const buttons = [];
|
const buttons = [];
|
||||||
|
|
||||||
if (task.type === 'Script Task') {
|
if (
|
||||||
|
task.type === 'Script Task' &&
|
||||||
|
ability.can('PUT', targetUris.processModelShowPath)
|
||||||
|
) {
|
||||||
buttons.push(
|
buttons.push(
|
||||||
<Button
|
<Button
|
||||||
data-qa="create-script-unit-test-button"
|
data-qa="create-script-unit-test-button"
|
||||||
|
@ -471,19 +515,28 @@ export default function ProcessInstanceShow() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (task.type === 'Call Activity') {
|
||||||
|
buttons.push(
|
||||||
|
<Link
|
||||||
|
data-qa="go-to-call-activity-result"
|
||||||
|
to={`${window.location.pathname}?process_identifier=${task.call_activity_process_identifier}`}
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
View Call Activity Diagram
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (canEditTaskData(task)) {
|
if (canEditTaskData(task)) {
|
||||||
if (editingTaskData) {
|
if (editingTaskData) {
|
||||||
buttons.push(
|
buttons.push(
|
||||||
<Button
|
<Button data-qa="save-task-data-button" onClick={saveTaskData}>
|
||||||
data-qa="create-script-unit-test-button"
|
|
||||||
onClick={saveTaskData}
|
|
||||||
>
|
|
||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
buttons.push(
|
buttons.push(
|
||||||
<Button
|
<Button
|
||||||
data-qa="create-script-unit-test-button"
|
data-qa="cancel-task-data-edit-button"
|
||||||
onClick={cancelEditingTaskData}
|
onClick={cancelEditingTaskData}
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
|
@ -492,7 +545,7 @@ export default function ProcessInstanceShow() {
|
||||||
} else {
|
} else {
|
||||||
buttons.push(
|
buttons.push(
|
||||||
<Button
|
<Button
|
||||||
data-qa="create-script-unit-test-button"
|
data-qa="edit-task-data-button"
|
||||||
onClick={() => setEditingTaskData(true)}
|
onClick={() => setEditingTaskData(true)}
|
||||||
>
|
>
|
||||||
Edit
|
Edit
|
||||||
|
@ -622,8 +675,8 @@ export default function ProcessInstanceShow() {
|
||||||
processModelId={processModelId || ''}
|
processModelId={processModelId || ''}
|
||||||
diagramXML={processInstanceToUse.bpmn_xml_file_contents || ''}
|
diagramXML={processInstanceToUse.bpmn_xml_file_contents || ''}
|
||||||
fileName={processInstanceToUse.bpmn_xml_file_contents || ''}
|
fileName={processInstanceToUse.bpmn_xml_file_contents || ''}
|
||||||
readyOrWaitingBpmnTaskIds={taskIds.readyOrWaiting}
|
readyOrWaitingProcessInstanceTasks={taskIds.readyOrWaiting}
|
||||||
completedTasksBpmnIds={taskIds.completed}
|
completedProcessInstanceTasks={taskIds.completed}
|
||||||
diagramType="readonly"
|
diagramType="readonly"
|
||||||
onElementClick={handleClickedDiagramTask}
|
onElementClick={handleClickedDiagramTask}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -25,6 +25,7 @@ import {
|
||||||
ProcessReference,
|
ProcessReference,
|
||||||
} from '../interfaces';
|
} from '../interfaces';
|
||||||
import ProcessSearch from '../components/ProcessSearch';
|
import ProcessSearch from '../components/ProcessSearch';
|
||||||
|
import { Notification } from '../components/Notification';
|
||||||
|
|
||||||
export default function ProcessModelEditDiagram() {
|
export default function ProcessModelEditDiagram() {
|
||||||
const [showFileNameEditor, setShowFileNameEditor] = useState(false);
|
const [showFileNameEditor, setShowFileNameEditor] = useState(false);
|
||||||
|
@ -157,6 +158,8 @@ export default function ProcessModelEditDiagram() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const [displaySaveFileMessage, setDisplaySaveFileMessage] =
|
||||||
|
useState<boolean>(false);
|
||||||
const saveDiagram = (bpmnXML: any, fileName = params.file_name) => {
|
const saveDiagram = (bpmnXML: any, fileName = params.file_name) => {
|
||||||
setErrorMessage(null);
|
setErrorMessage(null);
|
||||||
setBpmnXmlForDiagramRendering(bpmnXML);
|
setBpmnXmlForDiagramRendering(bpmnXML);
|
||||||
|
@ -192,6 +195,7 @@ export default function ProcessModelEditDiagram() {
|
||||||
// after saving the file, make sure we null out newFileName
|
// after saving the file, make sure we null out newFileName
|
||||||
// so it does not get used over the params
|
// so it does not get used over the params
|
||||||
setNewFileName('');
|
setNewFileName('');
|
||||||
|
setDisplaySaveFileMessage(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onDeleteFile = (fileName = params.file_name) => {
|
const onDeleteFile = (fileName = params.file_name) => {
|
||||||
|
@ -819,6 +823,7 @@ export default function ProcessModelEditDiagram() {
|
||||||
processModelId={params.process_model_id || ''}
|
processModelId={params.process_model_id || ''}
|
||||||
saveDiagram={saveDiagram}
|
saveDiagram={saveDiagram}
|
||||||
onDeleteFile={onDeleteFile}
|
onDeleteFile={onDeleteFile}
|
||||||
|
isPrimaryFile={params.file_name === processModel?.primary_file_name}
|
||||||
onSetPrimaryFile={onSetPrimaryFileCallback}
|
onSetPrimaryFile={onSetPrimaryFileCallback}
|
||||||
diagramXML={bpmnXmlForDiagramRendering}
|
diagramXML={bpmnXmlForDiagramRendering}
|
||||||
fileName={params.file_name}
|
fileName={params.file_name}
|
||||||
|
@ -836,6 +841,20 @@ export default function ProcessModelEditDiagram() {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const saveFileMessage = () => {
|
||||||
|
if (displaySaveFileMessage) {
|
||||||
|
return (
|
||||||
|
<Notification
|
||||||
|
title="File Saved: "
|
||||||
|
onClose={() => setDisplaySaveFileMessage(false)}
|
||||||
|
>
|
||||||
|
Changes to the file were saved.
|
||||||
|
</Notification>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
// if a file name is not given then this is a new model and the ReactDiagramEditor component will handle it
|
// 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) {
|
if ((bpmnXmlForDiagramRendering || !params.file_name) && processModel) {
|
||||||
const processModelFileName = processModelFile ? processModelFile.name : '';
|
const processModelFileName = processModelFile ? processModelFile.name : '';
|
||||||
|
@ -856,6 +875,7 @@ export default function ProcessModelEditDiagram() {
|
||||||
Process Model File{processModelFile ? ': ' : ''}
|
Process Model File{processModelFile ? ': ' : ''}
|
||||||
{processModelFileName}
|
{processModelFileName}
|
||||||
</h1>
|
</h1>
|
||||||
|
{saveFileMessage()}
|
||||||
{appropriateEditor()}
|
{appropriateEditor()}
|
||||||
{newFileNameBox()}
|
{newFileNameBox()}
|
||||||
{scriptEditor()}
|
{scriptEditor()}
|
||||||
|
|
|
@ -264,25 +264,27 @@ export default function ProcessModelShow() {
|
||||||
</Can>
|
</Can>
|
||||||
);
|
);
|
||||||
|
|
||||||
elements.push(
|
if (!isPrimaryBpmnFile) {
|
||||||
<Can
|
elements.push(
|
||||||
I="DELETE"
|
<Can
|
||||||
a={targetUris.processModelFileCreatePath}
|
I="DELETE"
|
||||||
ability={ability}
|
a={targetUris.processModelFileCreatePath}
|
||||||
>
|
ability={ability}
|
||||||
<ButtonWithConfirmation
|
>
|
||||||
kind="ghost"
|
<ButtonWithConfirmation
|
||||||
renderIcon={TrashCan}
|
kind="ghost"
|
||||||
iconDescription="Delete File"
|
renderIcon={TrashCan}
|
||||||
hasIconOnly
|
iconDescription="Delete File"
|
||||||
description={`Delete file: ${processModelFile.name}`}
|
hasIconOnly
|
||||||
onConfirmation={() => {
|
description={`Delete file: ${processModelFile.name}`}
|
||||||
onDeleteFile(processModelFile.name);
|
onConfirmation={() => {
|
||||||
}}
|
onDeleteFile(processModelFile.name);
|
||||||
confirmButtonLabel="Delete"
|
}}
|
||||||
/>
|
confirmButtonLabel="Delete"
|
||||||
</Can>
|
/>
|
||||||
);
|
</Can>
|
||||||
|
);
|
||||||
|
}
|
||||||
if (processModelFile.name.match(/\.bpmn$/) && !isPrimaryBpmnFile) {
|
if (processModelFile.name.match(/\.bpmn$/) && !isPrimaryBpmnFile) {
|
||||||
elements.push(
|
elements.push(
|
||||||
<Can I="PUT" a={targetUris.processModelShowPath} ability={ability}>
|
<Can I="PUT" a={targetUris.processModelShowPath} ability={ability}>
|
||||||
|
@ -360,27 +362,76 @@ export default function ProcessModelShow() {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const [fileUploadEvent, setFileUploadEvent] = useState(null);
|
||||||
|
const [duplicateFilename, setDuplicateFilename] = useState<String>('');
|
||||||
|
const [showOverwriteConfirmationPrompt, setShowOverwriteConfirmationPrompt] =
|
||||||
|
useState(false);
|
||||||
|
|
||||||
|
const doFileUpload = (event: any) => {
|
||||||
|
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,
|
||||||
|
});
|
||||||
|
setFilesToUpload(null);
|
||||||
|
};
|
||||||
|
|
||||||
const handleFileUploadCancel = () => {
|
const handleFileUploadCancel = () => {
|
||||||
setShowFileUploadModal(false);
|
setShowFileUploadModal(false);
|
||||||
setFilesToUpload(null);
|
setFilesToUpload(null);
|
||||||
};
|
};
|
||||||
|
const handleOverwriteFileConfirm = () => {
|
||||||
|
setShowOverwriteConfirmationPrompt(false);
|
||||||
|
doFileUpload(fileUploadEvent);
|
||||||
|
};
|
||||||
|
const handleOverwriteFileCancel = () => {
|
||||||
|
setShowOverwriteConfirmationPrompt(false);
|
||||||
|
setFilesToUpload(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const confirmOverwriteFileDialog = () => {
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
danger
|
||||||
|
open={showOverwriteConfirmationPrompt}
|
||||||
|
data-qa="file-overwrite-modal-confirmation-dialog"
|
||||||
|
modalHeading={`Overwrite the file: ${duplicateFilename}`}
|
||||||
|
modalLabel="Overwrite file?"
|
||||||
|
primaryButtonText="Yes"
|
||||||
|
secondaryButtonText="Cancel"
|
||||||
|
onSecondarySubmit={handleOverwriteFileCancel}
|
||||||
|
onRequestSubmit={handleOverwriteFileConfirm}
|
||||||
|
onRequestClose={handleOverwriteFileCancel}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const displayOverwriteConfirmation = (filename: String) => {
|
||||||
|
setDuplicateFilename(filename);
|
||||||
|
setShowOverwriteConfirmationPrompt(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkDuplicateFile = (event: any) => {
|
||||||
|
if (processModel && processModel.files.length > 0) {
|
||||||
|
processModel.files.forEach((file) => {
|
||||||
|
if (file.name === filesToUpload[0].name) {
|
||||||
|
displayOverwriteConfirmation(file.name);
|
||||||
|
setFileUploadEvent(event);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleFileUpload = (event: any) => {
|
const handleFileUpload = (event: any) => {
|
||||||
if (processModel) {
|
if (processModel) {
|
||||||
event.preventDefault();
|
checkDuplicateFile(event);
|
||||||
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);
|
setShowFileUploadModal(false);
|
||||||
setFilesToUpload(null);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const fileUploadModal = () => {
|
const fileUploadModal = () => {
|
||||||
|
@ -548,6 +599,7 @@ export default function ProcessModelShow() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{fileUploadModal()}
|
{fileUploadModal()}
|
||||||
|
{confirmOverwriteFileDialog()}
|
||||||
<ProcessBreadcrumb
|
<ProcessBreadcrumb
|
||||||
hotCrumbs={[
|
hotCrumbs={[
|
||||||
['Process Groups', '/admin'],
|
['Process Groups', '/admin'],
|
||||||
|
|
|
@ -36,7 +36,20 @@ export default function ReactFormEditor() {
|
||||||
return searchParams.get('file_ext') ?? 'json';
|
return searchParams.get('file_ext') ?? 'json';
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const editorDefaultLanguage = fileExtension === 'md' ? 'markdown' : 'json';
|
const hasDiagram = fileExtension === 'bpmn' || fileExtension === 'dmn';
|
||||||
|
|
||||||
|
const editorDefaultLanguage = (() => {
|
||||||
|
if (fileExtension === 'json') {
|
||||||
|
return 'json';
|
||||||
|
}
|
||||||
|
if (hasDiagram) {
|
||||||
|
return 'xml';
|
||||||
|
}
|
||||||
|
if (fileExtension === 'md') {
|
||||||
|
return 'markdown';
|
||||||
|
}
|
||||||
|
return 'text';
|
||||||
|
})();
|
||||||
|
|
||||||
const modifiedProcessModelId = modifyProcessIdentifierForPathParam(
|
const modifiedProcessModelId = modifyProcessIdentifierForPathParam(
|
||||||
`${params.process_model_id}`
|
`${params.process_model_id}`
|
||||||
|
@ -193,6 +206,19 @@ export default function ReactFormEditor() {
|
||||||
buttonLabel="Delete"
|
buttonLabel="Delete"
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
{hasDiagram ? (
|
||||||
|
<Button
|
||||||
|
onClick={() =>
|
||||||
|
navigate(
|
||||||
|
`/admin/process-models/${modifiedProcessModelId}/files/${params.file_name}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
variant="danger"
|
||||||
|
data-qa="view-diagram-button"
|
||||||
|
>
|
||||||
|
View Diagram
|
||||||
|
</Button>
|
||||||
|
) : null}
|
||||||
<Editor
|
<Editor
|
||||||
height={600}
|
height={600}
|
||||||
width="auto"
|
width="auto"
|
||||||
|
|
|
@ -40,7 +40,7 @@ export default function TaskShow() {
|
||||||
|
|
||||||
const { targetUris } = useUriListForPermissions();
|
const { targetUris } = useUriListForPermissions();
|
||||||
const permissionRequestData: PermissionsToCheck = {
|
const permissionRequestData: PermissionsToCheck = {
|
||||||
[targetUris.processInstanceTaskListPath]: ['GET'],
|
[targetUris.processInstanceTaskListDataPath]: ['GET'],
|
||||||
};
|
};
|
||||||
const { ability, permissionsLoaded } = usePermissionFetcher(
|
const { ability, permissionsLoaded } = usePermissionFetcher(
|
||||||
permissionRequestData
|
permissionRequestData
|
||||||
|
@ -50,7 +50,7 @@ export default function TaskShow() {
|
||||||
if (permissionsLoaded) {
|
if (permissionsLoaded) {
|
||||||
const processResult = (result: any) => {
|
const processResult = (result: any) => {
|
||||||
setTask(result);
|
setTask(result);
|
||||||
if (ability.can('GET', targetUris.processInstanceTaskListPath)) {
|
if (ability.can('GET', targetUris.processInstanceTaskListDataPath)) {
|
||||||
HttpService.makeCallToBackend({
|
HttpService.makeCallToBackend({
|
||||||
path: `/task-data/${modifyProcessIdentifierForPathParam(
|
path: `/task-data/${modifyProcessIdentifierForPathParam(
|
||||||
result.process_model_identifier
|
result.process_model_identifier
|
||||||
|
|
|
@ -26,7 +26,7 @@ type backendCallProps = {
|
||||||
postBody?: any;
|
postBody?: any;
|
||||||
};
|
};
|
||||||
|
|
||||||
class UnauthenticatedError extends Error {
|
export class UnauthenticatedError extends Error {
|
||||||
constructor(message: string) {
|
constructor(message: string) {
|
||||||
super(message);
|
super(message);
|
||||||
this.name = 'UnauthenticatedError';
|
this.name = 'UnauthenticatedError';
|
||||||
|
|
|
@ -27,8 +27,8 @@ const doLogout = () => {
|
||||||
const idToken = getIdToken();
|
const idToken = getIdToken();
|
||||||
localStorage.removeItem('jwtAccessToken');
|
localStorage.removeItem('jwtAccessToken');
|
||||||
localStorage.removeItem('jwtIdToken');
|
localStorage.removeItem('jwtIdToken');
|
||||||
const redirctUrl = `${window.location.origin}/`;
|
const redirectUrl = `${window.location.origin}`;
|
||||||
const url = `${BACKEND_BASE_URL}/logout?redirect_url=${redirctUrl}&id_token=${idToken}`;
|
const url = `${BACKEND_BASE_URL}/logout?redirect_url=${redirectUrl}&id_token=${idToken}`;
|
||||||
window.location.href = url;
|
window.location.href = url;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue