From 6b38b62ccf68cd8673cee5d73e545c4a49012496 Mon Sep 17 00:00:00 2001 From: jasquat Date: Fri, 16 Dec 2022 13:23:58 -0500 Subject: [PATCH] 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 --- .gitignore | 3 + cypress/e2e/process_models.cy.js | 24 +++-- cypress/support/commands.js | 11 +- cypress/support/helpers.js | 1 + package-lock.json | 16 +-- package.json | 2 +- src/App.tsx | 6 ++ src/components/NavigationBar.tsx | 9 ++ src/components/ProcessInstanceListTable.tsx | 15 ++- src/components/ReactDiagramEditor.tsx | 87 +++++++++++---- src/components/TasksForMyOpenProcesses.tsx | 2 +- src/components/TasksWaitingForMyGroups.tsx | 2 +- src/helpers.tsx | 26 ++++- src/hooks/UriListForPermissions.tsx | 3 +- src/interfaces.ts | 8 ++ src/routes/ProcessInstanceList.tsx | 9 +- src/routes/ProcessInstanceShow.tsx | 109 ++++++++++++++----- src/routes/ProcessModelEditDiagram.tsx | 20 ++++ src/routes/ProcessModelShow.tsx | 114 ++++++++++++++------ src/routes/ReactFormEditor.tsx | 28 ++++- src/routes/TaskShow.tsx | 4 +- src/services/HttpService.ts | 2 +- src/services/UserService.ts | 4 +- 23 files changed, 380 insertions(+), 125 deletions(-) create mode 100644 cypress/support/helpers.js diff --git a/.gitignore b/.gitignore index 8ff3e35c..c0316f7e 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,9 @@ # testing /coverage +# in case we accidentally run backend tests in frontend. :D +/.coverage.* + # production /build diff --git a/cypress/e2e/process_models.cy.js b/cypress/e2e/process_models.cy.js index 4fd1b481..43fba108 100644 --- a/cypress/e2e/process_models.cy.js +++ b/cypress/e2e/process_models.cy.js @@ -1,4 +1,5 @@ import { modifyProcessIdentifierForPathParam } from '../../src/helpers'; +import { miscDisplayName } from '../support/helpers'; describe('process-models', () => { beforeEach(() => { @@ -16,7 +17,7 @@ describe('process-models', () => { const modelDisplayName = `Test Model 2 ${id}`; const modelId = `test-model-2-${id}`; const newModelDisplayName = `${modelDisplayName} edited`; - cy.contains('99-Shared Resources').click(); + cy.contains(miscDisplayName).click(); cy.wait(500); cy.contains(groupDisplayName).click(); cy.createModel(groupId, modelId, modelDisplayName); @@ -34,7 +35,7 @@ describe('process-models', () => { cy.contains(`Process Model: ${newModelDisplayName}`); // 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.contains('Are you sure'); @@ -46,6 +47,7 @@ describe('process-models', () => { `process-groups/${modifyProcessIdentifierForPathParam(groupId)}` ); cy.contains(modelId).should('not.exist'); + cy.contains(modelDisplayName).should('not.exist'); }); it('can create new bpmn, dmn, and json files', () => { @@ -61,11 +63,11 @@ describe('process-models', () => { const dmnFileName = `dmn_test_file_${id}`; const jsonFileName = `json_test_file_${id}`; - cy.contains('99-Shared Resources').click(); + cy.contains(miscDisplayName).click(); cy.wait(500); cy.contains(groupDisplayName).click(); cy.createModel(groupId, modelId, modelDisplayName); - cy.contains(directParentGroupId).click(); + cy.contains(groupDisplayName).click(); cy.contains(modelDisplayName).click(); cy.url().should( 'include', @@ -90,7 +92,7 @@ describe('process-models', () => { cy.get('input[name=file_name]').type(bpmnFileName); cy.contains('Save Changes').click(); cy.contains(`Process Model File: ${bpmnFileName}`); - cy.contains(modelId).click(); + cy.contains(modelDisplayName).click(); cy.contains(`Process Model: ${modelDisplayName}`); // cy.getBySel('files-accordion').click(); cy.contains(`${bpmnFileName}.bpmn`).should('exist'); @@ -108,7 +110,7 @@ describe('process-models', () => { cy.get('input[name=file_name]').type(dmnFileName); cy.contains('Save Changes').click(); cy.contains(`Process Model File: ${dmnFileName}`); - cy.contains(modelId).click(); + cy.contains(modelDisplayName).click(); cy.contains(`Process Model: ${modelDisplayName}`); // cy.getBySel('files-accordion').click(); cy.contains(`${dmnFileName}.dmn`).should('exist'); @@ -124,7 +126,7 @@ describe('process-models', () => { cy.contains(`Process Model File: ${jsonFileName}`); // wait for json to load before clicking away to avoid network errors cy.wait(500); - cy.contains(modelId).click(); + cy.contains(modelDisplayName).click(); cy.contains(`Process Model: ${modelDisplayName}`); // cy.getBySel('files-accordion').click(); cy.contains(`${jsonFileName}.json`).should('exist'); @@ -151,12 +153,12 @@ describe('process-models', () => { const modelDisplayName = `Test Model 2 ${id}`; const modelId = `test-model-2-${id}`; cy.contains('Add a process group'); - cy.contains('99-Shared Resources').click(); + cy.contains(miscDisplayName).click(); cy.wait(500); cy.contains(groupDisplayName).click(); cy.createModel(groupId, modelId, modelDisplayName); - cy.contains(`${directParentGroupId}`).click(); + cy.contains(`${groupDisplayName}`).click(); cy.contains('Add a process model'); cy.contains(modelDisplayName).click(); cy.url().should( @@ -186,7 +188,7 @@ describe('process-models', () => { .click(); // in breadcrumb - cy.contains(modelId).click(); + cy.contains(modelDisplayName).click(); cy.getBySel('delete-process-model-button').click(); cy.contains('Are you sure'); @@ -203,7 +205,7 @@ describe('process-models', () => { // process models no longer has pagination post-tiles // it.only('can paginate items', () => { - // cy.contains('99-Shared Resources').click(); + // cy.contains(miscDisplayName).click(); // cy.wait(500); // cy.contains('Acceptance Tests Group One').click(); // cy.basicPaginationTest(); diff --git a/cypress/support/commands.js b/cypress/support/commands.js index f0034168..f7c4e846 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -1,5 +1,6 @@ import { string } from 'prop-types'; import { modifyProcessIdentifierForPathParam } from '../../src/helpers'; +import { miscDisplayName } from './helpers'; // *********************************************** // This example commands.js shows you how to @@ -86,15 +87,15 @@ Cypress.Commands.add('createModel', (groupId, modelId, modelDisplayName) => { Cypress.Commands.add( 'runPrimaryBpmnFile', (expectAutoRedirectToHumanTask = false) => { - cy.contains('Run').click(); + cy.contains('Start').click(); if (expectAutoRedirectToHumanTask) { // the url changes immediately, so also make sure we get some content from the next page, "Task:", or else when we try to interact with the page, it'll re-render and we'll get an error with cypress. cy.url().should('include', `/tasks/`); cy.contains('Task: '); } else { - cy.contains(/Process Instance.*kicked off/); + cy.contains(/Process Instance.*[kK]icked [oO]ff/); 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', (groupDisplayName, modelDisplayName, modelIdentifier) => { cy.navigateToAdmin(); - cy.contains('99-Shared Resources').click(); - cy.contains(`Process Group: 99-Shared Resources`, { timeout: 10000 }); + cy.contains(miscDisplayName).click(); + cy.contains(`Process Group: ${miscDisplayName}`, { timeout: 10000 }); cy.contains(groupDisplayName).click(); cy.contains(`Process Group: ${groupDisplayName}`); // https://stackoverflow.com/q/51254946/6090676 diff --git a/cypress/support/helpers.js b/cypress/support/helpers.js new file mode 100644 index 00000000..b3ae449e --- /dev/null +++ b/cypress/support/helpers.js @@ -0,0 +1 @@ +export const miscDisplayName = 'Shared Resources'; diff --git a/package-lock.json b/package-lock.json index ba233998..4ccea192 100644 --- a/package-lock.json +++ b/package-lock.json @@ -68,7 +68,7 @@ "@cypress/grep": "^3.1.0", "@typescript-eslint/eslint-plugin": "^5.30.5", "@typescript-eslint/parser": "^5.30.6", - "cypress": "^10.8.0", + "cypress": "^12", "eslint": "^8.19.0", "eslint_d": "^12.2.0", "eslint-config-airbnb": "^19.0.4", @@ -9850,9 +9850,9 @@ "integrity": "sha512-NJGVKPS81XejHcLhaLJS7plab0fK3slPh11mESeeDq2W4ZI5kUKK/LRRdVDvjJseojbPB7ZwjnyOybg3Igea/A==" }, "node_modules/cypress": { - "version": "10.11.0", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-10.11.0.tgz", - "integrity": "sha512-lsaE7dprw5DoXM00skni6W5ElVVLGAdRUUdZjX2dYsGjbY/QnpzWZ95Zom1mkGg0hAaO/QVTZoFVS7Jgr/GUPA==", + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-12.1.0.tgz", + "integrity": "sha512-7fz8N84uhN1+ePNDsfQvoWEl4P3/VGKKmAg+bJQFY4onhA37Ys+6oBkGbNdwGeC7n2QqibNVPhk8x3YuQLwzfw==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -9903,7 +9903,7 @@ "cypress": "bin/cypress" }, "engines": { - "node": ">=12.0.0" + "node": "^14.0.0 || ^16.0.0 || >=18.0.0" } }, "node_modules/cypress/node_modules/@types/node": { @@ -38586,9 +38586,9 @@ "integrity": "sha512-NJGVKPS81XejHcLhaLJS7plab0fK3slPh11mESeeDq2W4ZI5kUKK/LRRdVDvjJseojbPB7ZwjnyOybg3Igea/A==" }, "cypress": { - "version": "10.11.0", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-10.11.0.tgz", - "integrity": "sha512-lsaE7dprw5DoXM00skni6W5ElVVLGAdRUUdZjX2dYsGjbY/QnpzWZ95Zom1mkGg0hAaO/QVTZoFVS7Jgr/GUPA==", + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-12.1.0.tgz", + "integrity": "sha512-7fz8N84uhN1+ePNDsfQvoWEl4P3/VGKKmAg+bJQFY4onhA37Ys+6oBkGbNdwGeC7n2QqibNVPhk8x3YuQLwzfw==", "dev": true, "requires": { "@cypress/request": "^2.88.10", diff --git a/package.json b/package.json index b896bdce..6a84cea9 100644 --- a/package.json +++ b/package.json @@ -104,7 +104,7 @@ "@cypress/grep": "^3.1.0", "@typescript-eslint/eslint-plugin": "^5.30.5", "@typescript-eslint/parser": "^5.30.6", - "cypress": "^10.8.0", + "cypress": "^12", "eslint": "^8.19.0", "eslint_d": "^12.2.0", "eslint-config-airbnb": "^19.0.4", diff --git a/src/App.tsx b/src/App.tsx index deb38410..6357a713 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -13,6 +13,7 @@ import AdminRoutes from './routes/AdminRoutes'; import { ErrorForDisplay } from './interfaces'; import { AbilityContext } from './contexts/Can'; +import UserService from './services/UserService'; export default function App() { const [errorMessage, setErrorMessage] = useState( @@ -24,6 +25,11 @@ export default function App() { [errorMessage] ); + if (!UserService.isLoggedIn()) { + UserService.doLogin(); + return null; + } + const ability = defineAbility(() => {}); let errorTag = null; diff --git a/src/components/NavigationBar.tsx b/src/components/NavigationBar.tsx index 47e0de99..7a0ffd3e 100644 --- a/src/components/NavigationBar.tsx +++ b/src/components/NavigationBar.tsx @@ -24,6 +24,7 @@ import UserService from '../services/UserService'; import { useUriListForPermissions } from '../hooks/UriListForPermissions'; import { PermissionsToCheck } from '../interfaces'; import { usePermissionFetcher } from '../hooks/PermissionService'; +import { UnauthenticatedError } from '../services/HttpService'; // for ref: https://react-bootstrap.github.io/components/navbar/ export default function NavigationBar() { @@ -39,6 +40,11 @@ export default function NavigationBar() { const [activeKey, setActiveKey] = useState(''); 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 = { [targetUris.authenticationListPath]: ['GET'], [targetUris.messageInstanceListPath]: ['GET'], @@ -135,6 +141,9 @@ export default function NavigationBar() { }; const headerMenuItems = () => { + if (!UserService.isLoggedIn()) { + return null; + } return ( <> diff --git a/src/components/ProcessInstanceListTable.tsx b/src/components/ProcessInstanceListTable.tsx index 98b76df3..a21baec0 100644 --- a/src/components/ProcessInstanceListTable.tsx +++ b/src/components/ProcessInstanceListTable.tsx @@ -300,8 +300,13 @@ export default function ProcessInstanceListTable({ checkFiltersAndRun(); if (autoReload) { - refreshAtInterval(REFRESH_INTERVAL, REFRESH_TIMEOUT, checkFiltersAndRun); + return refreshAtInterval( + REFRESH_INTERVAL, + REFRESH_TIMEOUT, + checkFiltersAndRun + ); } + return undefined; }, [ autoReload, searchParams, @@ -838,8 +843,8 @@ export default function ProcessInstanceListTable({ return null; }} shouldFilterItem={shouldFilterReportColumn} - placeholder="Choose a report column" - titleText="Report Column" + placeholder="Choose a column to show" + titleText="Column" /> ); } @@ -888,7 +893,7 @@ export default function ProcessInstanceListTable({ kind="ghost" size="sm" className={`button-tag-icon ${tagTypeClass}`} - title={`Edit ${reportColumnForEditing.accessor}`} + title={`Edit ${reportColumnForEditing.accessor} column`} onClick={() => { setReportColumnToOperateOn(reportColumnForEditing); setShowReportColumnForm(true); @@ -916,7 +921,7 @@ export default function ProcessInstanceListTable({ + + {canViewXml && ( + + )} + ); } diff --git a/src/components/TasksForMyOpenProcesses.tsx b/src/components/TasksForMyOpenProcesses.tsx index deb2030e..297f2071 100644 --- a/src/components/TasksForMyOpenProcesses.tsx +++ b/src/components/TasksForMyOpenProcesses.tsx @@ -41,7 +41,7 @@ export default function MyOpenProcesses() { }); }; getTasks(); - refreshAtInterval(REFRESH_INTERVAL, REFRESH_TIMEOUT, getTasks); + return refreshAtInterval(REFRESH_INTERVAL, REFRESH_TIMEOUT, getTasks); }, [searchParams]); const buildTable = () => { diff --git a/src/components/TasksWaitingForMyGroups.tsx b/src/components/TasksWaitingForMyGroups.tsx index 565cd4a5..5b05dcd0 100644 --- a/src/components/TasksWaitingForMyGroups.tsx +++ b/src/components/TasksWaitingForMyGroups.tsx @@ -41,7 +41,7 @@ export default function TasksWaitingForMyGroups() { }); }; getTasks(); - refreshAtInterval(REFRESH_INTERVAL, REFRESH_TIMEOUT, getTasks); + return refreshAtInterval(REFRESH_INTERVAL, REFRESH_TIMEOUT, getTasks); }, [searchParams]); const buildTable = () => { diff --git a/src/helpers.tsx b/src/helpers.tsx index 6781ada9..8f625533 100644 --- a/src/helpers.tsx +++ b/src/helpers.tsx @@ -208,5 +208,29 @@ export const refreshAtInterval = ( () => clearInterval(intervalRef), 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; }; diff --git a/src/hooks/UriListForPermissions.tsx b/src/hooks/UriListForPermissions.tsx index f84465c8..4ba04352 100644 --- a/src/hooks/UriListForPermissions.tsx +++ b/src/hooks/UriListForPermissions.tsx @@ -14,7 +14,8 @@ export const useUriListForPermissions = () => { processInstanceListPath: '/v1.0/process-instances', processInstanceLogListPath: `/v1.0/logs/${params.process_model_id}/${params.process_instance_id}`, processInstanceReportListPath: '/v1.0/process-instances/reports', - processInstanceTaskListPath: `/v1.0/task-data/${params.process_model_id}/${params.process_instance_id}`, + 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}`, processModelFileCreatePath: `/v1.0/process-models/${params.process_model_id}/files`, processModelFileShowPath: `/v1.0/process-models/${params.process_model_id}/files/${params.file_name}`, diff --git a/src/interfaces.ts b/src/interfaces.ts index 079e4cdc..6afb1144 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -11,6 +11,13 @@ export interface RecentProcessModel { processModelDisplayName: string; } +export interface ProcessInstanceTask { + id: string; + state: string; + process_identifier: string; + name: string; +} + export interface ProcessReference { name: string; // The process or decision Display name. identifier: string; // The unique id of the process @@ -39,6 +46,7 @@ export interface ProcessInstance { id: number; process_model_identifier: string; process_model_display_name: string; + spiff_step?: number; } export interface MessageCorrelationProperties { diff --git a/src/routes/ProcessInstanceList.tsx b/src/routes/ProcessInstanceList.tsx index b6c08b21..1d75db56 100644 --- a/src/routes/ProcessInstanceList.tsx +++ b/src/routes/ProcessInstanceList.tsx @@ -21,10 +21,11 @@ export default function ProcessInstanceList() { diff --git a/src/routes/ProcessInstanceShow.tsx b/src/routes/ProcessInstanceShow.tsx index 9a0495d1..1adb585b 100644 --- a/src/routes/ProcessInstanceShow.tsx +++ b/src/routes/ProcessInstanceShow.tsx @@ -1,6 +1,11 @@ import { useContext, useEffect, useState } from '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 { TrashCan, StopOutline, @@ -34,15 +39,21 @@ import { import ButtonWithConfirmation from '../components/ButtonWithConfirmation'; import ErrorContext from '../contexts/ErrorContext'; import { useUriListForPermissions } from '../hooks/UriListForPermissions'; -import { PermissionsToCheck } from '../interfaces'; +import { + PermissionsToCheck, + ProcessInstance, + ProcessInstanceTask, +} from '../interfaces'; import { usePermissionFetcher } from '../hooks/PermissionService'; export default function ProcessInstanceShow() { const navigate = useNavigate(); const params = useParams(); + const [searchParams] = useSearchParams(); - const [processInstance, setProcessInstance] = useState(null); - const [tasks, setTasks] = useState | null>(null); + const [processInstance, setProcessInstance] = + useState(null); + const [tasks, setTasks] = useState(null); const [tasksCallHadError, setTasksCallHadError] = useState(false); const [taskToDisplay, setTaskToDisplay] = useState(null); const [taskDataToDisplay, setTaskDataToDisplay] = useState(''); @@ -59,8 +70,10 @@ export default function ProcessInstanceShow() { const permissionRequestData: PermissionsToCheck = { [targetUris.messageInstanceListPath]: ['GET'], [targetUris.processInstanceTaskListPath]: ['GET'], + [targetUris.processInstanceTaskListDataPath]: ['GET', 'PUT'], [targetUris.processInstanceActionPath]: ['DELETE'], [targetUris.processInstanceLogListPath]: ['GET'], + [targetUris.processModelShowPath]: ['PUT'], [`${targetUris.processInstanceActionPath}/suspend`]: ['PUT'], [`${targetUris.processInstanceActionPath}/terminate`]: ['PUT'], [`${targetUris.processInstanceActionPath}/resume`]: ['PUT'], @@ -80,17 +93,28 @@ export default function ProcessInstanceShow() { const processTaskFailure = () => { setTasksCallHadError(true); }; + let queryParams = ''; + const processIdentifier = searchParams.get('process_identifier'); + if (processIdentifier) { + queryParams = `?process_identifier=${processIdentifier}`; + } HttpService.makeCallToBackend({ - path: `/process-instances/${modifiedProcessModelId}/${params.process_instance_id}`, + path: `/process-instances/${modifiedProcessModelId}/${params.process_instance_id}${queryParams}`, successCallback: setProcessInstance, }); let taskParams = '?all_tasks=true'; if (typeof params.spiff_step !== 'undefined') { 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({ - path: `${targetUris.processInstanceTaskListPath}${taskParams}`, + path: taskPath, successCallback: setTasks, failureCallback: processTaskFailure, }); @@ -98,7 +122,14 @@ export default function ProcessInstanceShow() { setTasksCallHadError(true); } } - }, [params, modifiedProcessModelId, permissionsLoaded, ability, targetUris]); + }, [ + params, + modifiedProcessModelId, + permissionsLoaded, + ability, + targetUris, + searchParams, + ]); const deleteProcessInstance = () => { HttpService.makeCallToBackend({ @@ -140,12 +171,12 @@ export default function ProcessInstanceShow() { const getTaskIds = () => { const taskIds = { completed: [], readyOrWaiting: [] }; if (tasks) { - tasks.forEach(function getUserTasksElement(task: any) { + tasks.forEach(function getUserTasksElement(task: ProcessInstanceTask) { if (task.state === 'COMPLETED') { - (taskIds.completed as any).push(task.name); + (taskIds.completed as any).push(task); } 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, distance: number ) => { + const processIdentifier = searchParams.get('process_identifier'); + let queryParams = ''; + if (processIdentifier) { + queryParams = `?process_identifier=${processIdentifier}`; + } return ( {label} @@ -364,10 +398,15 @@ export default function ProcessInstanceShow() { } }; - const handleClickedDiagramTask = (shapeElement: any) => { + const handleClickedDiagramTask = ( + shapeElement: any, + bpmnProcessIdentifiers: any + ) => { if (tasks) { const matchingTask: any = tasks.find( - (task: any) => task.name === shapeElement.id + (task: any) => + task.name === shapeElement.id && + bpmnProcessIdentifiers.includes(task.process_identifier) ); if (matchingTask) { setTaskToDisplay(matchingTask); @@ -411,7 +450,9 @@ export default function ProcessInstanceShow() { const canEditTaskData = (task: any) => { 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 buttons = []; - if (task.type === 'Script Task') { + if ( + task.type === 'Script Task' && + ability.can('PUT', targetUris.processModelShowPath) + ) { buttons.push( ); buttons.push( + ) : null} { setTask(result); - if (ability.can('GET', targetUris.processInstanceTaskListPath)) { + if (ability.can('GET', targetUris.processInstanceTaskListDataPath)) { HttpService.makeCallToBackend({ path: `/task-data/${modifyProcessIdentifierForPathParam( result.process_model_identifier diff --git a/src/services/HttpService.ts b/src/services/HttpService.ts index 119765a7..78a29d07 100644 --- a/src/services/HttpService.ts +++ b/src/services/HttpService.ts @@ -26,7 +26,7 @@ type backendCallProps = { postBody?: any; }; -class UnauthenticatedError extends Error { +export class UnauthenticatedError extends Error { constructor(message: string) { super(message); this.name = 'UnauthenticatedError'; diff --git a/src/services/UserService.ts b/src/services/UserService.ts index 84e84d6f..df0f213e 100644 --- a/src/services/UserService.ts +++ b/src/services/UserService.ts @@ -27,8 +27,8 @@ const doLogout = () => { const idToken = getIdToken(); localStorage.removeItem('jwtAccessToken'); localStorage.removeItem('jwtIdToken'); - const redirctUrl = `${window.location.origin}/`; - const url = `${BACKEND_BASE_URL}/logout?redirect_url=${redirctUrl}&id_token=${idToken}`; + const redirectUrl = `${window.location.origin}`; + const url = `${BACKEND_BASE_URL}/logout?redirect_url=${redirectUrl}&id_token=${idToken}`; window.location.href = url; };