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 2b53d724..268debab 100644 --- a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx +++ b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx @@ -782,7 +782,7 @@ export default function ProcessInstanceListTable({ undefined, paginationQueryParamPrefix ); - page = 1; // Reset page back to 0 + page = 1; const newReportMetadata = getNewReportMetadataBasedOnPageWidgets(); setReportMetadata(newReportMetadata); @@ -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/routes/ProcessInterstitial.tsx b/spiffworkflow-frontend/src/components/ProcessInterstitial.tsx similarity index 54% rename from spiffworkflow-frontend/src/routes/ProcessInterstitial.tsx rename to spiffworkflow-frontend/src/components/ProcessInterstitial.tsx index c906a0cd..639b5bb9 100644 --- a/spiffworkflow-frontend/src/routes/ProcessInterstitial.tsx +++ b/spiffworkflow-frontend/src/components/ProcessInterstitial.tsx @@ -1,80 +1,103 @@ 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, 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 '../components/InstructionsForEndUser'; -import ProcessBreadcrumb from '../components/ProcessBreadcrumb'; +import InstructionsForEndUser from './InstructionsForEndUser'; import { ProcessInstance, ProcessInstanceTask } from '../interfaces'; import useAPIError from '../hooks/UseApiError'; -export default function ProcessInterstitial() { +type OwnProps = { + processInstanceId: number; + processInstanceShowPageUrl: string; + allowRedirect: boolean; +}; + +export default function ProcessInterstitial({ + processInstanceId, + allowRedirect, + processInstanceShowPageUrl, +}: 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 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() { + 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 && !processInstance && myTask && myTask.can_complete && 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) { @@ -95,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)) { @@ -134,9 +135,12 @@ 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)) { + return `The task ${myTask.title} is ready for you to complete.`; + } if (myTask.error_message) { return
{myTask.error_message}
; } @@ -161,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/interfaces.ts b/spiffworkflow-frontend/src/interfaces.ts index 81a56326..139a0107 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/AdminRoutes.tsx b/spiffworkflow-frontend/src/routes/AdminRoutes.tsx index 0e9df5d9..05b5d295 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 8650cc00..ca7968a2 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; @@ -1109,6 +1110,11 @@ export default function ProcessInstanceShow({ variant }: OwnProps) { {buttonIcons()} +

diff --git a/spiffworkflow-frontend/src/routes/ProcessInterstitialPage.tsx b/spiffworkflow-frontend/src/routes/ProcessInterstitialPage.tsx new file mode 100644 index 00000000..7d633103 --- /dev/null +++ b/spiffworkflow-frontend/src/routes/ProcessInterstitialPage.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import { useParams } from 'react-router-dom'; +// @ts-ignore +import ProcessInterstitial from '../components/ProcessInterstitial'; +import ProcessBreadcrumb from '../components/ProcessBreadcrumb'; + +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}`; + } + + return ( + <> + + + + ); +} diff --git a/spiffworkflow-frontend/src/routes/ProcessRoutes.tsx b/spiffworkflow-frontend/src/routes/ProcessRoutes.tsx deleted file mode 100644 index dc8a1d66..00000000 --- a/spiffworkflow-frontend/src/routes/ProcessRoutes.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { Route, Routes } from 'react-router-dom'; -// @ts-ignore -import ProcessInterstitial from './ProcessInterstitial'; - -export default function ProcessRoutes() { - return ( - - } - /> - - ); -} diff --git a/spiffworkflow-frontend/src/routes/TaskShow.tsx b/spiffworkflow-frontend/src/routes/TaskShow.tsx index af8f9446..29310eef 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` );