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 8ff3e35ce..c0316f7ea 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 4fd1b4810..43fba108e 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 f0034168c..f7c4e8467 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 000000000..b3ae449ed --- /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 ba2339983..4ccea1922 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 b896bdcec..6a84cea9e 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 deb38410d..6357a713f 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 47e0de998..7a0ffd3ea 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 98b76df39..a21baec0d 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 deb2030ea..297f20716 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 565cd4a55..5b05dcd0a 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 6781ada97..8f6255335 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 f84465c82..4ba04352b 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 079e4cdc7..6afb11441 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 b6c08b213..1d75db565 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 9a0495d1d..1adb585bf 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 119765a7b..78a29d07e 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 84e84d6f4..df0f213e5 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; };