TaskShow had a useEffect that depended on params, that dependency caused an infinite request cycle when an error occured.

The same issue was happening on the ProcessInstanceListTable, and there it was being managed by a "SafelySetErrorMessage" function in one case,
but would not be addressed in all possible cases.

Reworked error handling into a context provider (APIErrorProvider) and hook (UseApiError) and removed the "(useContext as any)(ErrorContext)[1];" that felt a little off but that never was an actual problem.
This commit is contained in:
Dan 2023-01-25 10:46:56 -05:00
parent 809c4b055a
commit c35ba85605
22 changed files with 1927 additions and 208 deletions

View File

@ -20,8 +20,10 @@ services:
retries: 10
spiffworkflow-backend:
container_name: spiffworkflow-backend
image: ghcr.io/sartography/spiffworkflow-backend:latest
# container_name: spiffworkflow-backend
build: ./spiffworkflow-backend/.
# dockerfile: Dockerfile
# image: ghcr.io/sartography/spiffworkflow-backend:latest
depends_on:
spiffworkflow-db:
condition: service_healthy

File diff suppressed because it is too large Load Diff

View File

@ -49,6 +49,7 @@
"react-bootstrap": "^2.5.0",
"react-bootstrap-typeahead": "^6.0.0",
"react-datepicker": "^4.8.0",
"react-devtools": "^4.27.1",
"react-dom": "^18.2.0",
"react-icons": "^4.4.0",
"react-jsonschema-form": "^1.8.1",

View File

@ -1,29 +1,20 @@
import { useMemo, useState } from 'react';
// @ts-ignore
import { Content } from '@carbon/react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { defineAbility } from '@casl/ability';
import ErrorContext from './contexts/ErrorContext';
import NavigationBar from './components/NavigationBar';
import HomePageRoutes from './routes/HomePageRoutes';
import ErrorBoundary from './components/ErrorBoundary';
import AdminRoutes from './routes/AdminRoutes';
import { ErrorForDisplay } from './interfaces';
import { AbilityContext } from './contexts/Can';
import UserService from './services/UserService';
import ErrorDisplay from './components/ErrorDisplay';
import APIErrorProvider from './contexts/APIErrorContext';
export default function App() {
const [errorObject, setErrorObject] = useState<ErrorForDisplay | null>(null);
const errorContextValueArray = useMemo(
() => [errorObject, setErrorObject],
[errorObject]
);
if (!UserService.isLoggedIn()) {
UserService.doLogin();
return null;
@ -35,7 +26,7 @@ export default function App() {
<div className="cds--white">
{/* @ts-ignore */}
<AbilityContext.Provider value={ability}>
<ErrorContext.Provider value={errorContextValueArray}>
<APIErrorProvider>
<BrowserRouter>
<NavigationBar />
<Content>
@ -49,7 +40,7 @@ export default function App() {
</ErrorBoundary>
</Content>
</BrowserRouter>
</ErrorContext.Provider>
</APIErrorProvider>
</AbilityContext.Provider>
</div>
);

View File

@ -1,6 +1,5 @@
import { useContext } from 'react';
import ErrorContext from '../contexts/ErrorContext';
import { Notification } from './Notification';
import useAPIError from '../hooks/UseApiError';
function errorDetailDisplay(
errorObject: any,
@ -20,8 +19,8 @@ function errorDetailDisplay(
}
export default function ErrorDisplay() {
const [errorObject, setErrorObject] = (useContext as any)(ErrorContext);
const errorObject = useAPIError().error;
const { removeError } = useAPIError()
let errorTag = null;
if (errorObject) {
let sentryLinkTag = null;
@ -50,7 +49,7 @@ export default function ErrorDisplay() {
);
const errorLine = errorDetailDisplay(errorObject, 'error_line', 'Context');
let taskTrace = null;
if ('task_trace' in errorObject && errorObject.task_trace.length > 1) {
if (errorObject.task_trace && errorObject.task_trace.length > 1) {
taskTrace = (
<div className="error_info">
<span className="error_title">Call Activity Trace:</span>
@ -62,7 +61,7 @@ export default function ErrorDisplay() {
errorTag = (
<Notification
title={title}
onClose={() => setErrorObject(null)}
onClose={() => (removeError())}
type="error"
>
{message}

View File

@ -1,4 +1,4 @@
import { useContext, useEffect, useMemo, useRef, useState } from 'react';
import { useEffect, useMemo, useRef, useState } from 'react';
import {
Link,
useNavigate,
@ -40,13 +40,11 @@ import {
getProcessModelFullIdentifierFromSearchParams,
modifyProcessIdentifierForPathParam,
refreshAtInterval,
setErrorMessageSafely,
} from '../helpers';
import PaginationForTable from './PaginationForTable';
import 'react-datepicker/dist/react-datepicker.css';
import ErrorContext from '../contexts/ErrorContext';
import HttpService from '../services/HttpService';
import 'react-bootstrap-typeahead/css/Typeahead.css';
@ -61,6 +59,7 @@ import {
ReportMetadata,
ReportFilter,
User,
ErrorForDisplay,
} from '../interfaces';
import ProcessModelSearch from './ProcessModelSearch';
import ProcessInstanceReportSearch from './ProcessInstanceReportSearch';
@ -68,6 +67,7 @@ import ProcessInstanceListDeleteReport from './ProcessInstanceListDeleteReport';
import ProcessInstanceListSaveAsReport from './ProcessInstanceListSaveAsReport';
import { FormatProcessModelDisplayName } from './MiniComponents';
import { Notification } from './Notification';
import useAPIError from '../hooks/UseApiError';
const REFRESH_INTERVAL = 5;
const REFRESH_TIMEOUT = 600;
@ -110,6 +110,7 @@ export default function ProcessInstanceListTable({
const params = useParams();
const [searchParams] = useSearchParams();
const navigate = useNavigate();
const { addError, removeError } = useAPIError();
const [processInstances, setProcessInstances] = useState([]);
const [reportMetadata, setReportMetadata] = useState<ReportMetadata | null>();
@ -133,8 +134,6 @@ export default function ProcessInstanceListTable({
const [endFromTimeInvalid, setEndFromTimeInvalid] = useState<boolean>(false);
const [endToTimeInvalid, setEndToTimeInvalid] = useState<boolean>(false);
const [errorObject, setErrorObject] = (useContext as any)(ErrorContext);
const processInstanceListPathPrefix =
variant === 'all'
? '/admin/process-instances/all'
@ -517,7 +516,7 @@ export default function ProcessInstanceListTable({
}
if (message !== '') {
valid = false;
setErrorMessageSafely(message, errorObject, setErrorObject);
addError({ message } as ErrorForDisplay);
}
}
@ -579,7 +578,7 @@ export default function ProcessInstanceListTable({
queryParamString += `&process_initiator_username=${processInitiatorSelection.username}`;
}
setErrorObject(null);
removeError();
setProcessInstanceReportJustSaved(null);
setProcessInstanceFilters({});
navigate(`${processInstanceListPathPrefix}?${queryParamString}`);
@ -679,7 +678,7 @@ export default function ProcessInstanceListTable({
queryParamString = `?report_id=${selectedReport.id}`;
}
setErrorObject(null);
removeError();
setProcessInstanceReportJustSaved(mode || null);
navigate(`${processInstanceListPathPrefix}${queryParamString}`);
};

View File

@ -1,4 +1,3 @@
import { useContext } from 'react';
import { useNavigate } from 'react-router-dom';
import {
Button,
@ -11,9 +10,9 @@ import {
RecentProcessModel,
} from '../interfaces';
import HttpService from '../services/HttpService';
import ErrorContext from '../contexts/ErrorContext';
import { modifyProcessIdentifierForPathParam } from '../helpers';
import { usePermissionFetcher } from '../hooks/PermissionService';
import useAPIError from '../hooks/UseApiError';
const storeRecentProcessModelInLocalStorage = (
processModelForStorage: ProcessModel
@ -78,7 +77,7 @@ export default function ProcessInstanceRun({
checkPermissions = true,
}: OwnProps) {
const navigate = useNavigate();
const setErrorObject = (useContext as any)(ErrorContext)[1];
const { addError, removeError } = useAPIError();
const modifiedProcessModelId = modifyProcessIdentifierForPathParam(
processModel.id
);
@ -105,12 +104,12 @@ export default function ProcessInstanceRun({
};
const processModelRun = (processInstance: any) => {
setErrorObject(null);
removeError();
storeRecentProcessModelInLocalStorage(processModel);
HttpService.makeCallToBackend({
path: `/process-instances/${modifiedProcessModelId}/${processInstance.id}/run`,
successCallback: onProcessInstanceRun,
failureCallback: setErrorObject,
failureCallback: addError,
httpMethod: 'POST',
});
};

View File

@ -6,7 +6,7 @@ import BpmnViewer from 'bpmn-js/lib/Viewer';
import {
BpmnPropertiesPanelModule,
BpmnPropertiesProviderModule,
// @ts-expect-error TS(7016) FIXME: Could not find a declaration file for module 'bpmn... Remove this comment to see the full error message
// @ts-expect-error TS(7016) FIXME: Could not find a declaration file for module 'bpmn... RemoFve this comment to see the full error message
} from 'bpmn-js-properties-panel';
// @ts-expect-error TS(7016) FIXME: Could not find a declaration file for module 'dmn-... Remove this comment to see the full error message

View File

@ -0,0 +1,40 @@
import React, { createContext, useState, useCallback } from 'react';
import { ErrorForDisplay } from '../interfaces';
type ErrorContextType = {
error: null | ErrorForDisplay;
addError: Function;
removeError: Function;
};
export const APIErrorContext = createContext<ErrorContextType>({
error: null,
// eslint-disable-next-line no-unused-vars
addError: () => {},
removeError: () => {},
});
// @ts-ignore
// eslint-disable-next-line react/prop-types
export default function APIErrorProvider({ children }) {
const [error, setError] = useState<ErrorForDisplay | null>(null);
const addError = (errorForDisplay: ErrorForDisplay | null) => {
setError(errorForDisplay);
console.log('Adding an error.', errorForDisplay);
}
const removeError = () => setError(null);
const contextValue = {
error,
addError: useCallback(
(newError: ErrorForDisplay | null) => addError(newError),
[]
),
removeError: useCallback(() => removeError(), []),
};
return (
<APIErrorContext.Provider value={contextValue}>
{children}
</APIErrorContext.Provider>
);
}

View File

@ -1,5 +0,0 @@
import { createContext } from 'react';
// @ts-expect-error TS(2554) FIXME: Expected 1 arguments, but got 0.
const ErrorContext = createContext();
export default ErrorContext;

View File

@ -258,20 +258,6 @@ export const getBpmnProcessIdentifiers = (rootBpmnElement: any) => {
return childProcesses;
};
// Setting the error message state to the same string is still considered a change
// and re-renders the page so check the message first to avoid that.
export const setErrorMessageSafely = (
newErrorMessageString: string,
oldErrorMessage: ErrorForDisplay,
errorMessageSetter: any
) => {
if (oldErrorMessage && oldErrorMessage.message === newErrorMessageString) {
return null;
}
errorMessageSetter({ message: newErrorMessageString });
return null;
};
export const isInteger = (str: string | number) => {
return /^\d+$/.test(str.toString());
};

View File

@ -0,0 +1,10 @@
// src/common/hooks/useAPIError/index.js
import { useContext } from 'react';
import { APIErrorContext } from '../contexts/APIErrorContext';
function useAPIError() {
const { error, addError, removeError } = useContext(APIErrorContext);
return { error, addError, removeError };
}
export default useAPIError;

View File

@ -182,6 +182,7 @@ export interface ErrorForDisplay {
task_id?: string;
line_number?: number;
file_name?: string;
task_trace?: [string];
}
export interface AuthenticationParam {

View File

@ -1,6 +1,6 @@
import { Routes, Route, useLocation } from 'react-router-dom';
import { useContext, useEffect } from 'react';
import { useEffect } from 'react';
import ProcessGroupList from './ProcessGroupList';
import ProcessGroupShow from './ProcessGroupShow';
import ProcessGroupNew from './ProcessGroupNew';
@ -17,21 +17,21 @@ import ProcessInstanceReportList from './ProcessInstanceReportList';
import ProcessInstanceReportNew from './ProcessInstanceReportNew';
import ProcessInstanceReportEdit from './ProcessInstanceReportEdit';
import ReactFormEditor from './ReactFormEditor';
import ErrorContext from '../contexts/ErrorContext';
import ProcessInstanceLogList from './ProcessInstanceLogList';
import MessageInstanceList from './MessageInstanceList';
import Configuration from './Configuration';
import JsonSchemaFormBuilder from './JsonSchemaFormBuilder';
import ProcessModelNewExperimental from './ProcessModelNewExperimental';
import ProcessInstanceFindById from './ProcessInstanceFindById';
import useAPIError from '../hooks/UseApiError';
export default function AdminRoutes() {
const location = useLocation();
const setErrorObject = (useContext as any)(ErrorContext)[1];
const { removeError } = useAPIError();
useEffect(() => {
setErrorObject(null);
}, [location, setErrorObject]);
removeError();
}, [location, removeError]);
if (UserService.hasRole(['admin'])) {
return (

View File

@ -1,14 +1,13 @@
import { useContext, useEffect, useState } from 'react';
import { useEffect, useState } from 'react';
// @ts-ignore
import { Table } from '@carbon/react';
import ErrorContext from '../contexts/ErrorContext';
import useAPIError from '../hooks/UseApiError';
import { AuthenticationItem } from '../interfaces';
import HttpService from '../services/HttpService';
import UserService from '../services/UserService';
export default function AuthenticationList() {
const setErrorObject = (useContext as any)(ErrorContext)[1];
const { addError } = useAPIError();
const [authenticationList, setAuthenticationList] = useState<
AuthenticationItem[] | null
>(null);
@ -26,9 +25,9 @@ export default function AuthenticationList() {
HttpService.makeCallToBackend({
path: `/authentications`,
successCallback: processResult,
failureCallback: setErrorObject,
failureCallback: addError,
});
}, [setErrorObject]);
}, [addError]);
const buildTable = () => {
if (authenticationList) {

View File

@ -1,9 +1,9 @@
import { useContext, useEffect, useState } from 'react';
import { useEffect, useState } from 'react';
import { Route, Routes, useLocation, useNavigate } from 'react-router-dom';
// @ts-ignore
import { Tabs, TabList, Tab } from '@carbon/react';
import { Can } from '@casl/react';
import ErrorContext from '../contexts/ErrorContext';
import useAPIError from '../hooks/UseApiError';
import SecretList from './SecretList';
import SecretNew from './SecretNew';
import SecretShow from './SecretShow';
@ -14,7 +14,7 @@ import { usePermissionFetcher } from '../hooks/PermissionService';
export default function Configuration() {
const location = useLocation();
const setErrorObject = (useContext as any)(ErrorContext)[1];
const { removeError } = useAPIError();
const [selectedTabIndex, setSelectedTabIndex] = useState<number>(0);
const navigate = useNavigate();
@ -26,13 +26,13 @@ export default function Configuration() {
const { ability } = usePermissionFetcher(permissionRequestData);
useEffect(() => {
setErrorObject(null);
removeError();
let newSelectedTabIndex = 0;
if (location.pathname.match(/^\/admin\/configuration\/authentications\b/)) {
newSelectedTabIndex = 1;
}
setSelectedTabIndex(newSelectedTabIndex);
}, [location, setErrorObject]);
}, [location, removeError]);
return (
<>

View File

@ -1,9 +1,9 @@
import { useContext, useEffect, useState } from 'react';
import { useEffect, useState } from 'react';
import { Route, Routes, useLocation, useNavigate } from 'react-router-dom';
// @ts-ignore
import { Tabs, TabList, Tab } from '@carbon/react';
import TaskShow from './TaskShow';
import ErrorContext from '../contexts/ErrorContext';
import useAPIError from '../hooks/UseApiError';
import MyTasks from './MyTasks';
import GroupedTasks from './GroupedTasks';
import CompletedInstances from './CompletedInstances';
@ -11,12 +11,12 @@ import CreateNewInstance from './CreateNewInstance';
export default function HomePageRoutes() {
const location = useLocation();
const setErrorObject = (useContext as any)(ErrorContext)[1];
const { removeError } = useAPIError();
const [selectedTabIndex, setSelectedTabIndex] = useState<number>(0);
const navigate = useNavigate();
useEffect(() => {
setErrorObject(null);
removeError();
let newSelectedTabIndex = 0;
if (location.pathname.match(/^\/tasks\/completed-instances\b/)) {
newSelectedTabIndex = 1;
@ -24,7 +24,7 @@ export default function HomePageRoutes() {
newSelectedTabIndex = 2;
}
setSelectedTabIndex(newSelectedTabIndex);
}, [location, setErrorObject]);
}, [location, removeError]);
const renderTabs = () => {
if (location.pathname.match(/^\/tasks\/\d+\/\b/)) {

View File

@ -1,4 +1,4 @@
import { useContext, useEffect, useState } from 'react';
import { useEffect, useState } from 'react';
import Editor from '@monaco-editor/react';
import {
useParams,
@ -38,7 +38,6 @@ import {
unModifyProcessIdentifierForPathParam,
} from '../helpers';
import ButtonWithConfirmation from '../components/ButtonWithConfirmation';
import ErrorContext from '../contexts/ErrorContext';
import { useUriListForPermissions } from '../hooks/UriListForPermissions';
import {
PermissionsToCheck,
@ -49,6 +48,7 @@ import {
import { usePermissionFetcher } from '../hooks/PermissionService';
import ProcessInstanceClass from '../classes/ProcessInstanceClass';
import TaskListTable from '../components/TaskListTable';
import useAPIError from '../hooks/UseApiError';
type OwnProps = {
variant: string;
@ -75,8 +75,7 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
useState<boolean>(false);
const [displayDetails, setDisplayDetails] = useState<boolean>(false);
const setErrorObject = (useContext as any)(ErrorContext)[1];
const { addError, removeError } = useAPIError();
const unModifiedProcessModelId = unModifyProcessIdentifierForPathParam(
`${params.process_model_id}`
);
@ -151,11 +150,11 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
}
}
}, [
params,
// targetUris,
// params,
modifiedProcessModelId,
permissionsLoaded,
ability,
targetUris,
searchParams,
taskListPath,
variant,
@ -684,7 +683,7 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
setSelectingEvent(false);
initializeTaskDataToDisplay(taskToDisplay);
setEventPayload('{}');
setErrorObject(null);
removeError();
};
const taskDataStringToObject = (dataString: string) => {
@ -699,16 +698,12 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
refreshPage();
};
const saveTaskDataFailure = (result: any) => {
setErrorObject({ message: result.message });
};
const saveTaskData = () => {
if (!taskToDisplay) {
return;
}
setErrorObject(null);
removeError();
// taskToUse is copy of taskToDisplay, with taskDataToDisplay in data attribute
const taskToUse: any = { ...taskToDisplay, data: taskDataToDisplay };
@ -716,7 +711,7 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
path: `${targetUris.processInstanceTaskListDataPath}/${taskToUse.id}`,
httpMethod: 'PUT',
successCallback: saveTaskDataResult,
failureCallback: saveTaskDataFailure,
failureCallback: addError,
postBody: {
new_task_data: taskToUse.data,
},
@ -730,7 +725,7 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
path: `/send-event/${modifiedProcessModelId}/${params.process_instance_id}`,
httpMethod: 'POST',
successCallback: saveTaskDataResult,
failureCallback: saveTaskDataFailure,
failureCallback: addError,
postBody: eventToSend,
});
};

View File

@ -1,4 +1,4 @@
import { useContext, useEffect, useRef, useState } from 'react';
import { useEffect, useRef, useState } from 'react';
import {
generatePath,
useNavigate,
@ -25,7 +25,7 @@ import MDEditor from '@uiw/react-md-editor';
import ReactDiagramEditor from '../components/ReactDiagramEditor';
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
import HttpService from '../services/HttpService';
import ErrorContext from '../contexts/ErrorContext';
import useAPIError from '../hooks/UseApiError';
import { makeid, modifyProcessIdentifierForPathParam } from '../helpers';
import {
CarbonComboBoxProcessSelection,
@ -100,7 +100,7 @@ export default function ProcessModelEditDiagram() {
const navigate = useNavigate();
const [searchParams] = useSearchParams();
const setErrorObject = (useContext as any)(ErrorContext)[1];
const { addError, removeError } = useAPIError();
const [processModelFile, setProcessModelFile] = useState<ProcessFile | null>(
null
);
@ -176,7 +176,7 @@ export default function ProcessModelEditDiagram() {
const saveDiagram = (bpmnXML: any, fileName = params.file_name) => {
setDisplaySaveFileMessage(false);
setErrorObject(null);
removeError();
setBpmnXmlForDiagramRendering(bpmnXML);
let url = `/process-models/${modifiedProcessModelId}/files`;
@ -202,7 +202,7 @@ export default function ProcessModelEditDiagram() {
HttpService.makeCallToBackend({
path: url,
successCallback: navigateToProcessModelFile,
failureCallback: setErrorObject,
failureCallback: addError,
httpMethod,
postBody: formData,
});

View File

@ -1,4 +1,4 @@
import { useContext, useEffect, useState } from 'react';
import { useEffect, useState } from 'react';
import { Link, useNavigate, useParams } from 'react-router-dom';
import {
Add,
@ -32,7 +32,8 @@ import {
import { Can } from '@casl/react';
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
import HttpService from '../services/HttpService';
import ErrorContext from '../contexts/ErrorContext';
import useAPIError from '../hooks/UseApiError';
import {
getGroupFromModifiedModelId,
modifyProcessIdentifierForPathParam,
@ -52,7 +53,7 @@ import { Notification } from '../components/Notification';
export default function ProcessModelShow() {
const params = useParams();
const setErrorObject = (useContext as any)(ErrorContext)[1];
const { addError, removeError } = useAPIError();
const [processModel, setProcessModel] = useState<ProcessModel | null>(null);
const [processInstance, setProcessInstance] =
@ -148,7 +149,7 @@ export default function ProcessModelShow() {
!('file_contents' in processModelFile) ||
processModelFile.file_contents === undefined
) {
setErrorObject({
addError({
message: `Could not file file contents for file: ${processModelFile.name}`,
});
return;
@ -169,7 +170,7 @@ export default function ProcessModelShow() {
};
const downloadFile = (fileName: string) => {
setErrorObject(null);
removeError();
const processModelPath = `process-models/${modifiedProcessModelId}`;
HttpService.makeCallToBackend({
path: `/${processModelPath}/files/${fileName}`,
@ -374,7 +375,7 @@ export default function ProcessModelShow() {
const doFileUpload = (event: any) => {
event.preventDefault();
setErrorObject(null);
removeError();
const url = `/process-models/${modifiedProcessModelId}/files`;
const formData = new FormData();
formData.append('file', filesToUpload[0]);
@ -384,7 +385,7 @@ export default function ProcessModelShow() {
successCallback: onUploadedCallback,
httpMethod: 'POST',
postBody: formData,
failureCallback: setErrorObject,
failureCallback: addError,
});
setFilesToUpload(null);
};

View File

@ -1,4 +1,4 @@
import { useContext, useEffect, useState } from 'react';
import { useEffect, useState } from 'react';
import Editor from '@monaco-editor/react';
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
// @ts-ignore
@ -8,16 +8,14 @@ import HttpService from '../services/HttpService';
import ButtonWithConfirmation from '../components/ButtonWithConfirmation';
import { modifyProcessIdentifierForPathParam } from '../helpers';
import { ProcessFile } from '../interfaces';
import ErrorContext from '../contexts/ErrorContext';
import { Notification } from '../components/Notification';
import useAPIError from '../hooks/UseApiError';
// NOTE: This is mostly the same as ProcessModelEditDiagram and if we go this route could
// possibly be merged into it. I'm leaving as a separate file now in case it does
// end up diverging greatly
export default function ReactFormEditor() {
const params = useParams();
const setErrorObject = (useContext as any)(ErrorContext)[1];
const { addError, removeError } = useAPIError();
const [showFileNameEditor, setShowFileNameEditor] = useState(false);
const [newFileName, setNewFileName] = useState('');
const searchParams = useSearchParams()[0];
@ -87,7 +85,7 @@ export default function ReactFormEditor() {
};
const saveFile = () => {
setErrorObject(null);
removeError();
setDisplaySaveFileMessage(false);
let url = `/process-models/${modifiedProcessModelId}/files`;
@ -116,7 +114,7 @@ export default function ReactFormEditor() {
HttpService.makeCallToBackend({
path: url,
successCallback: navigateToProcessModelFile,
failureCallback: setErrorObject,
failureCallback: addError,
httpMethod,
postBody: formData,
});

View File

@ -1,4 +1,4 @@
import { useContext, useEffect, useState } from 'react';
import { useEffect, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import validator from '@rjsf/validator-ajv8';
@ -17,7 +17,7 @@ import remarkGfm from 'remark-gfm';
// eslint-disable-next-line import/no-named-as-default
import Form from '../themes/carbon';
import HttpService from '../services/HttpService';
import ErrorContext from '../contexts/ErrorContext';
import useAPIError from '../hooks/UseApiError';
import { modifyProcessIdentifierForPathParam } from '../helpers';
import { useUriListForPermissions } from '../hooks/UriListForPermissions';
import { PermissionsToCheck } from '../interfaces';
@ -29,7 +29,7 @@ export default function TaskShow() {
const params = useParams();
const navigate = useNavigate();
const setErrorObject = (useContext as any)(ErrorContext)[1];
const { addError, removeError } = useAPIError();
const { targetUris } = useUriListForPermissions();
const permissionRequestData: PermissionsToCheck = {
@ -39,48 +39,55 @@ export default function TaskShow() {
permissionRequestData
);
useEffect(() => {
if (permissionsLoaded) {
const processResult = (result: any) => {
setTask(result);
if (ability.can('GET', targetUris.processInstanceTaskListDataPath)) {
HttpService.makeCallToBackend({
path: `/task-data/${modifyProcessIdentifierForPathParam(
const url = `/task-data/${modifyProcessIdentifierForPathParam(
result.process_model_identifier
)}/${params.process_instance_id}`,
)}/${params.process_instance_id}`;
if (
result.process_model_identifier &&
ability.can('GET', url)
// Assure we get a valid process model identifier back
) {
HttpService.makeCallToBackend({
path: url,
successCallback: setUserTasks,
failureCallback: (error: any) => {
addError(error);
},
});
}
};
useEffect(() => {
if (permissionsLoaded) {
HttpService.makeCallToBackend({
path: `/tasks/${params.process_instance_id}/${params.task_id}`,
successCallback: processResult,
// This causes the page to continuously reload
// failureCallback: setErrorObject,
failureCallback: addError,
});
}
}, [params, permissionsLoaded, ability, targetUris]);
}, [permissionsLoaded, ability]); // params and targetUris (which deps on params) cause this to re-fire in an infinite loop on error.
const processSubmitResult = (result: any) => {
setErrorObject(null);
removeError();
if (result.ok) {
navigate(`/tasks`);
} else if (result.process_instance_id) {
navigate(`/tasks/${result.process_instance_id}/${result.id}`);
} else {
setErrorObject(`Received unexpected error: ${result.message}`);
addError(result);
}
};
const handleFormSubmit = (event: any) => {
setErrorObject(null);
removeError();
const dataToSubmit = event.formData;
delete dataToSubmit.isManualTask;
HttpService.makeCallToBackend({
path: `/tasks/${params.process_instance_id}/${params.task_id}`,
successCallback: processSubmitResult,
failureCallback: setErrorObject,
failureCallback: addError,
httpMethod: 'PUT',
postBody: dataToSubmit,
});