From 77ad3d175eaf4c393c79877586585b0ebcfbefc3 Mon Sep 17 00:00:00 2001 From: danfunk Date: Mon, 15 May 2023 13:53:39 -0400 Subject: [PATCH 1/4] wip. --- .../components/ProcessInstanceListTable.tsx | 2 +- .../ProcessInterstitial.tsx | 69 +++++++++++-------- spiffworkflow-frontend/src/interfaces.ts | 1 + .../src/routes/ProcessInstanceShow.tsx | 21 ++---- .../src/routes/ProcessInterstitialPage.tsx | 18 +++++ .../src/routes/ProcessRoutes.tsx | 4 +- 6 files changed, 65 insertions(+), 50 deletions(-) rename spiffworkflow-frontend/src/{routes => components}/ProcessInterstitial.tsx (82%) create mode 100644 spiffworkflow-frontend/src/routes/ProcessInterstitialPage.tsx diff --git a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx index 0ee6db3e..1e17e32c 100644 --- a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx +++ b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx @@ -744,7 +744,7 @@ export default function ProcessInstanceListTable({ undefined, paginationQueryParamPrefix ); - page = 1; // Reset page back to 0 + page = 1; const newReportMetadata = getNewReportMetadataBasedOnPageWidgets(); setListHasBeenFiltered(true); diff --git a/spiffworkflow-frontend/src/routes/ProcessInterstitial.tsx b/spiffworkflow-frontend/src/components/ProcessInterstitial.tsx similarity index 82% rename from spiffworkflow-frontend/src/routes/ProcessInterstitial.tsx rename to spiffworkflow-frontend/src/components/ProcessInterstitial.tsx index 368c3285..7dcc3326 100644 --- a/spiffworkflow-frontend/src/routes/ProcessInterstitial.tsx +++ b/spiffworkflow-frontend/src/components/ProcessInterstitial.tsx @@ -1,5 +1,5 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import { useNavigate, useParams } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import { fetchEventSource } from '@microsoft/fetch-event-source'; // @ts-ignore import { Loading, Grid, Column, Button } from '@carbon/react'; @@ -7,53 +7,59 @@ import { BACKEND_BASE_URL } from '../config'; import { getBasicHeaders } from '../services/HttpService'; // @ts-ignore -import InstructionsForEndUser from '../components/InstructionsForEndUser'; -import ProcessBreadcrumb from '../components/ProcessBreadcrumb'; +import InstructionsForEndUser from './InstructionsForEndUser'; +import ProcessBreadcrumb from './ProcessBreadcrumb'; import { ProcessInstance, ProcessInstanceTask } from '../interfaces'; import useAPIError from '../hooks/UseApiError'; -export default function ProcessInterstitial() { +type OwnProps = { + processInstanceId: number; + modifiedProcessModelIdentifier: string; + allowRedirect: boolean; +}; + +export default function ProcessInterstitial({ + processInstanceId, + modifiedProcessModelIdentifier, + allowRedirect, +}: OwnProps) { const [data, setData] = useState([]); const [lastTask, setLastTask] = useState(null); + const [state, setState] = useState('RUNNING'); const [processInstance, setProcessInstance] = useState(null); - const [state, setState] = useState('RUNNING'); - const params = useParams(); + const processInstanceShowPageBaseUrl = `/admin/process-instances/for-me/${modifiedProcessModelIdentifier}`; const navigate = useNavigate(); const userTasks = useMemo(() => { return ['User Task', 'Manual Task']; }, []); const { addError } = useAPIError(); - const processInstanceShowPageBaseUrl = `/admin/process-instances/for-me/${params.modified_process_model_identifier}`; - useEffect(() => { - fetchEventSource( - `${BACKEND_BASE_URL}/tasks/${params.process_instance_id}`, - { - headers: getBasicHeaders(), - onmessage(ev) { - const retValue = JSON.parse(ev.data); - if (retValue.type === 'error') { - addError(retValue.error); - } else if (retValue.type === 'task') { - setData((prevData) => [retValue.task, ...prevData]); - setLastTask(retValue.task); - } else if (retValue.type === 'unrunnable_instance') { - setProcessInstance(retValue.unrunnable_instance); - } - }, - onclose() { - setState('CLOSED'); - }, - } - ); + fetchEventSource(`${BACKEND_BASE_URL}/tasks/${processInstanceId}`, { + headers: getBasicHeaders(), + onmessage(ev) { + const retValue = JSON.parse(ev.data); + if (retValue.type === 'error') { + addError(retValue.error); + } else if (retValue.type === 'task') { + setData((prevData) => [retValue.task, ...prevData]); + setLastTask(retValue.task); + } else if (retValue.type === 'unrunnable_instance') { + setProcessInstance(retValue.unrunnable_instance); + } + }, + onclose() { + setState('CLOSED'); + }, + }); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); // it is critical to only run this once. const shouldRedirect = useCallback( (myTask: ProcessInstanceTask): boolean => { return ( + allowRedirect && !processInstance && myTask && myTask.can_complete && @@ -166,6 +172,9 @@ export default function ProcessInterstitial() { if (shouldRedirect(myTask)) { return
Redirecting you to the next task now ...
; } + if (myTask && myTask.can_complete && userTasks.includes(myTask.type)) { + return `The task ${myTask.title} is ready for you to complete.`; + } if (myTask.error_message) { return
{myTask.error_message}
; } @@ -201,8 +210,8 @@ export default function ProcessInterstitial() { linkLastItem: true, }, [ - `Process Instance: ${params.process_instance_id}`, - `${processInstanceShowPageBaseUrl}/${params.process_instance_id}`, + `Process Instance: ${processInstanceId}`, + `${processInstanceShowPageBaseUrl}/${processInstanceId}`, ], ]} /> diff --git a/spiffworkflow-frontend/src/interfaces.ts b/spiffworkflow-frontend/src/interfaces.ts index ac0980a2..e9c5f0df 100644 --- a/spiffworkflow-frontend/src/interfaces.ts +++ b/spiffworkflow-frontend/src/interfaces.ts @@ -23,6 +23,7 @@ export interface RecentProcessModel { export interface TaskPropertiesJson { parent: string; + last_state_change: number; } export interface TaskDefinitionPropertiesJson { diff --git a/spiffworkflow-frontend/src/routes/ProcessInstanceShow.tsx b/spiffworkflow-frontend/src/routes/ProcessInstanceShow.tsx index 8650cc00..cfb9a33a 100644 --- a/spiffworkflow-frontend/src/routes/ProcessInstanceShow.tsx +++ b/spiffworkflow-frontend/src/routes/ProcessInstanceShow.tsx @@ -53,6 +53,7 @@ import { usePermissionFetcher } from '../hooks/PermissionService'; import ProcessInstanceClass from '../classes/ProcessInstanceClass'; import TaskListTable from '../components/TaskListTable'; import useAPIError from '../hooks/UseApiError'; +import ProcessInterstitial from "../components/ProcessInterstitial"; type OwnProps = { variant: string; @@ -1092,23 +1093,9 @@ export default function ProcessInstanceShow({ variant }: OwnProps) { return ( <> - - -

- Process Instance Id: {processInstance.id} -

- {buttonIcons()} -
+ + {buttonIcons()} +


diff --git a/spiffworkflow-frontend/src/routes/ProcessInterstitialPage.tsx b/spiffworkflow-frontend/src/routes/ProcessInterstitialPage.tsx new file mode 100644 index 00000000..31b59337 --- /dev/null +++ b/spiffworkflow-frontend/src/routes/ProcessInterstitialPage.tsx @@ -0,0 +1,18 @@ +import React from 'react'; +import { useParams } from 'react-router-dom'; +// @ts-ignore +import ProcessInterstitial from '../components/ProcessInterstitial'; + +export default function ProcessInterstitialPage() { + const params = useParams(); + // @ts-ignore + return ( + + ); +} diff --git a/spiffworkflow-frontend/src/routes/ProcessRoutes.tsx b/spiffworkflow-frontend/src/routes/ProcessRoutes.tsx index dc8a1d66..001a8ec2 100644 --- a/spiffworkflow-frontend/src/routes/ProcessRoutes.tsx +++ b/spiffworkflow-frontend/src/routes/ProcessRoutes.tsx @@ -1,13 +1,13 @@ import { Route, Routes } from 'react-router-dom'; // @ts-ignore -import ProcessInterstitial from './ProcessInterstitial'; +import ProcessInterstitialPage from './ProcessInterstitialPage'; export default function ProcessRoutes() { return ( } + element={} /> ); From 247578c9684faa517fd536a342631207e5a9ed4d Mon Sep 17 00:00:00 2001 From: danfunk Date: Wed, 24 May 2023 15:24:33 -0400 Subject: [PATCH 2/4] ProcessInterstitial is now a component, and a page. The component is included into both the Interstitial PAge and the Process Instance Show page. Fixed routes for interstitial to align with those of the TaskShow page (variants are now accepted and passed through) Removed the View button completely. --- spiffworkflow-frontend/src/App.tsx | 2 - .../components/ProcessInstanceListTable.tsx | 26 ++--- .../src/components/ProcessInstanceRun.tsx | 2 +- .../src/components/ProcessInterstitial.tsx | 105 +++++++----------- .../src/routes/AdminRoutes.tsx | 9 ++ .../src/routes/ProcessInstanceShow.tsx | 27 ++++- .../src/routes/ProcessInterstitialPage.tsx | 41 +++++-- .../src/routes/ProcessRoutes.tsx | 14 --- .../src/routes/TaskShow.tsx | 2 +- 9 files changed, 120 insertions(+), 108 deletions(-) delete mode 100644 spiffworkflow-frontend/src/routes/ProcessRoutes.tsx diff --git a/spiffworkflow-frontend/src/App.tsx b/spiffworkflow-frontend/src/App.tsx index cddc3061..97d14fcc 100644 --- a/spiffworkflow-frontend/src/App.tsx +++ b/spiffworkflow-frontend/src/App.tsx @@ -10,7 +10,6 @@ import HomePageRoutes from './routes/HomePageRoutes'; import About from './routes/About'; import ErrorBoundary from './components/ErrorBoundary'; import AdminRoutes from './routes/AdminRoutes'; -import ProcessRoutes from './routes/ProcessRoutes'; import { AbilityContext } from './contexts/Can'; import UserService from './services/UserService'; @@ -41,7 +40,6 @@ export default function App() { } /> } /> } /> - } /> } /> diff --git a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx index f54f7856..268debab 100644 --- a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx +++ b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx @@ -1590,9 +1590,7 @@ export default function ProcessInstanceListTable({ }); if (showActionsColumn) { let buttonElement = null; - const interstitialUrl = `/process/${modifyProcessIdentifierForPathParam( - processInstance.process_model_identifier - )}/${processInstance.id}/interstitial`; + const taskShowUrl = `/tasks/${processInstance.id}/${processInstance.task_id}`; const regex = new RegExp(`\\b(${preferredUsername}|${userEmail})\\b`); let hasAccessToCompleteTask = false; if ( @@ -1601,21 +1599,19 @@ export default function ProcessInstanceListTable({ ) { hasAccessToCompleteTask = true; } - let buttonText = 'View'; + buttonElement = null; if (hasAccessToCompleteTask && processInstance.task_id) { - buttonText = 'Go'; + buttonElement = ( + + ); } - buttonElement = ( - - ); - if ( processInstance.status === 'not_started' || processInstance.status === 'user_input_required' || diff --git a/spiffworkflow-frontend/src/components/ProcessInstanceRun.tsx b/spiffworkflow-frontend/src/components/ProcessInstanceRun.tsx index 22cd3ac3..0f61e128 100644 --- a/spiffworkflow-frontend/src/components/ProcessInstanceRun.tsx +++ b/spiffworkflow-frontend/src/components/ProcessInstanceRun.tsx @@ -96,7 +96,7 @@ export default function ProcessInstanceRun({ const onProcessInstanceRun = (processInstance: any) => { const processInstanceId = (processInstance as any).id; navigate( - `/process/${modifyProcessIdentifierForPathParam( + `/admin/process-instances/${modifyProcessIdentifierForPathParam( processModel.id )}/${processInstanceId}/interstitial` ); diff --git a/spiffworkflow-frontend/src/components/ProcessInterstitial.tsx b/spiffworkflow-frontend/src/components/ProcessInterstitial.tsx index 9b26122d..639b5bb9 100644 --- a/spiffworkflow-frontend/src/components/ProcessInterstitial.tsx +++ b/spiffworkflow-frontend/src/components/ProcessInterstitial.tsx @@ -2,33 +2,32 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { fetchEventSource } from '@microsoft/fetch-event-source'; // @ts-ignore -import { Loading, Button } from '@carbon/react'; +import { Loading } from '@carbon/react'; import { BACKEND_BASE_URL } from '../config'; import { getBasicHeaders } from '../services/HttpService'; // @ts-ignore import InstructionsForEndUser from './InstructionsForEndUser'; -import ProcessBreadcrumb from './ProcessBreadcrumb'; import { ProcessInstance, ProcessInstanceTask } from '../interfaces'; import useAPIError from '../hooks/UseApiError'; type OwnProps = { processInstanceId: number; - modifiedProcessModelIdentifier: string; + processInstanceShowPageUrl: string; allowRedirect: boolean; }; export default function ProcessInterstitial({ processInstanceId, - modifiedProcessModelIdentifier, allowRedirect, + processInstanceShowPageUrl, }: OwnProps) { const [data, setData] = useState([]); const [lastTask, setLastTask] = useState(null); const [state, setState] = useState('RUNNING'); const [processInstance, setProcessInstance] = useState(null); - const processInstanceShowPageBaseUrl = `/admin/process-instances/for-me/${modifiedProcessModelIdentifier}`; + const navigate = useNavigate(); const userTasks = useMemo(() => { return ['User Task', 'Manual Task']; @@ -50,13 +49,14 @@ export default function ProcessInterstitial({ } }, onclose() { + console.log('The state is closed.'); setState('CLOSED'); }, }); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); // it is critical to only run this once. - const shouldRedirect = useCallback( + const shouldRedirectToTask = useCallback( (myTask: ProcessInstanceTask): boolean => { return ( allowRedirect && @@ -66,21 +66,38 @@ export default function ProcessInterstitial({ userTasks.includes(myTask.type) ); }, - [userTasks, processInstance] + [allowRedirect, processInstance, userTasks] ); + const shouldRedirectToProcessInstance = useCallback((): boolean => { + return allowRedirect && state === 'CLOSED'; + }, [allowRedirect, state]); + useEffect(() => { // Added this seperate use effect so that the timer interval will be cleared if // we end up redirecting back to the TaskShow page. - if (shouldRedirect(lastTask)) { + if (shouldRedirectToTask(lastTask)) { lastTask.properties.instructionsForEndUser = ''; const timerId = setInterval(() => { navigate(`/tasks/${lastTask.process_instance_id}/${lastTask.id}`); }, 2000); return () => clearInterval(timerId); } + if (shouldRedirectToProcessInstance()) { + // Navigate without pause as we will be showing the same information. + navigate(processInstanceShowPageUrl); + } return undefined; - }, [lastTask, navigate, userTasks, shouldRedirect]); + }, [ + lastTask, + navigate, + userTasks, + shouldRedirectToTask, + processInstanceId, + processInstanceShowPageUrl, + state, + shouldRedirectToProcessInstance, + ]); const getStatus = (): string => { if (processInstance) { @@ -101,35 +118,13 @@ export default function ProcessInterstitial({ ); } return null; }; - const getReturnHomeButton = (index: number) => { - if ( - index === 0 && - !shouldRedirect(lastTask) && - ['WAITING', 'ERROR', 'LOCKED', 'COMPLETED', 'READY'].includes(getStatus()) - ) { - return ( -
- -
- ); - } - return ''; - }; - const userMessage = (myTask: ProcessInstanceTask) => { if (!processInstance || processInstance.status === 'completed') { if (!myTask.can_complete && userTasks.includes(myTask.type)) { @@ -140,7 +135,7 @@ export default function ProcessInterstitial({

); } - if (shouldRedirect(myTask)) { + if (shouldRedirectToTask(myTask)) { return
Redirecting you to the next task now ...
; } if (myTask && myTask.can_complete && userTasks.includes(myTask.type)) { @@ -170,40 +165,24 @@ export default function ProcessInterstitial({ navigate(`/tasks`); } + let displayableData = data; + if (state === 'CLOSED') { + displayableData = [data[0]]; + } + if (lastTask) { return ( <> - {getLoadingIcon()} -
- {data.map((d, index) => ( - <> -
- {userMessage(d)} -
- {getReturnHomeButton(index)} - - ))} -
+ {displayableData.map((d, index) => ( +
+ {userMessage(d)} +
+ ))} ); } diff --git a/spiffworkflow-frontend/src/routes/AdminRoutes.tsx b/spiffworkflow-frontend/src/routes/AdminRoutes.tsx index 0e9df5d9..12fdac25 100644 --- a/spiffworkflow-frontend/src/routes/AdminRoutes.tsx +++ b/spiffworkflow-frontend/src/routes/AdminRoutes.tsx @@ -22,6 +22,7 @@ import Configuration from './Configuration'; import JsonSchemaFormBuilder from './JsonSchemaFormBuilder'; import ProcessModelNewExperimental from './ProcessModelNewExperimental'; import ProcessInstanceFindById from './ProcessInstanceFindById'; +import ProcessInterstitialPage from "./ProcessInterstitialPage"; export default function AdminRoutes() { const location = useLocation(); @@ -75,6 +76,14 @@ export default function AdminRoutes() { path="process-instances/for-me/:process_model_id/:process_instance_id/:to_task_guid" element={} /> + } + /> + } + /> } diff --git a/spiffworkflow-frontend/src/routes/ProcessInstanceShow.tsx b/spiffworkflow-frontend/src/routes/ProcessInstanceShow.tsx index cfb9a33a..ca7968a2 100644 --- a/spiffworkflow-frontend/src/routes/ProcessInstanceShow.tsx +++ b/spiffworkflow-frontend/src/routes/ProcessInstanceShow.tsx @@ -53,7 +53,7 @@ import { usePermissionFetcher } from '../hooks/PermissionService'; import ProcessInstanceClass from '../classes/ProcessInstanceClass'; import TaskListTable from '../components/TaskListTable'; import useAPIError from '../hooks/UseApiError'; -import ProcessInterstitial from "../components/ProcessInterstitial"; +import ProcessInterstitial from '../components/ProcessInterstitial'; type OwnProps = { variant: string; @@ -1093,9 +1093,28 @@ export default function ProcessInstanceShow({ variant }: OwnProps) { return ( <> - - {buttonIcons()} -
+ + +

+ Process Instance Id: {processInstance.id} +

+ {buttonIcons()} +
+

diff --git a/spiffworkflow-frontend/src/routes/ProcessInterstitialPage.tsx b/spiffworkflow-frontend/src/routes/ProcessInterstitialPage.tsx index 31b59337..6911c833 100644 --- a/spiffworkflow-frontend/src/routes/ProcessInterstitialPage.tsx +++ b/spiffworkflow-frontend/src/routes/ProcessInterstitialPage.tsx @@ -2,17 +2,42 @@ import React from 'react'; import { useParams } from 'react-router-dom'; // @ts-ignore import ProcessInterstitial from '../components/ProcessInterstitial'; +import ProcessBreadcrumb from '../components/ProcessBreadcrumb'; -export default function ProcessInterstitialPage() { +type OwnProps = { + variant: string; +}; + +export default function ProcessInterstitialPage({ variant }: OwnProps) { const params = useParams(); + let processInstanceShowPageUrl = `/admin/process-instances/for-me/${params.process_model_id}/${params.process_instance_id}`; + if (variant === 'all') { + processInstanceShowPageUrl = `/admin/process-instances/${params.process_model_id}/${params.process_instance_id}`; + } + // @ts-ignore + console.log('params', params, processInstanceShowPageUrl); return ( - + <> + + + ); } diff --git a/spiffworkflow-frontend/src/routes/ProcessRoutes.tsx b/spiffworkflow-frontend/src/routes/ProcessRoutes.tsx deleted file mode 100644 index 001a8ec2..00000000 --- a/spiffworkflow-frontend/src/routes/ProcessRoutes.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { Route, Routes } from 'react-router-dom'; -// @ts-ignore -import ProcessInterstitialPage from './ProcessInterstitialPage'; - -export default function ProcessRoutes() { - return ( - - } - /> - - ); -} diff --git a/spiffworkflow-frontend/src/routes/TaskShow.tsx b/spiffworkflow-frontend/src/routes/TaskShow.tsx index 6f1407d5..bc06dad1 100644 --- a/spiffworkflow-frontend/src/routes/TaskShow.tsx +++ b/spiffworkflow-frontend/src/routes/TaskShow.tsx @@ -102,7 +102,7 @@ export default function TaskShow() { const navigateToInterstitial = (myTask: Task) => { navigate( - `/process/${modifyProcessIdentifierForPathParam( + `/admin/process-instances/${modifyProcessIdentifierForPathParam( myTask.process_model_identifier )}/${myTask.process_instance_id}/interstitial` ); From 8734005c1ea9cf3c49ddc59e17a8e5a397e7a670 Mon Sep 17 00:00:00 2001 From: danfunk Date: Wed, 24 May 2023 15:31:11 -0400 Subject: [PATCH 3/4] run_pyl --- spiffworkflow-frontend/src/routes/AdminRoutes.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spiffworkflow-frontend/src/routes/AdminRoutes.tsx b/spiffworkflow-frontend/src/routes/AdminRoutes.tsx index 12fdac25..05b5d295 100644 --- a/spiffworkflow-frontend/src/routes/AdminRoutes.tsx +++ b/spiffworkflow-frontend/src/routes/AdminRoutes.tsx @@ -22,7 +22,7 @@ import Configuration from './Configuration'; import JsonSchemaFormBuilder from './JsonSchemaFormBuilder'; import ProcessModelNewExperimental from './ProcessModelNewExperimental'; import ProcessInstanceFindById from './ProcessInstanceFindById'; -import ProcessInterstitialPage from "./ProcessInterstitialPage"; +import ProcessInterstitialPage from './ProcessInterstitialPage'; export default function AdminRoutes() { const location = useLocation(); From 4d058e0fe43efdba7c0b2751b9fde29e0987f12d Mon Sep 17 00:00:00 2001 From: Dan Funk Date: Wed, 24 May 2023 17:31:31 -0400 Subject: [PATCH 4/4] remove console.log --- spiffworkflow-frontend/src/routes/ProcessInterstitialPage.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/spiffworkflow-frontend/src/routes/ProcessInterstitialPage.tsx b/spiffworkflow-frontend/src/routes/ProcessInterstitialPage.tsx index 6911c833..7d633103 100644 --- a/spiffworkflow-frontend/src/routes/ProcessInterstitialPage.tsx +++ b/spiffworkflow-frontend/src/routes/ProcessInterstitialPage.tsx @@ -15,8 +15,6 @@ export default function ProcessInterstitialPage({ variant }: OwnProps) { processInstanceShowPageUrl = `/admin/process-instances/${params.process_model_id}/${params.process_instance_id}`; } - // @ts-ignore - console.log('params', params, processInstanceShowPageUrl); return ( <>