some support for extensions in v3 ui w/ burnettk

This commit is contained in:
jasquat 2025-02-12 10:56:53 -05:00
parent 12bf88de88
commit 298e50bbba
No known key found for this signature in database
8 changed files with 220 additions and 12 deletions

View File

@ -6,8 +6,8 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { AbilityContext } from './contexts/Can';
import APIErrorProvider from './contexts/APIErrorContext';
import ContainerForExtensions from './ContainerForExtensions';
import ContainerForExtensionsV3 from './a-spiffui-v3/ContainerForExtensions';
import PublicRoutes from './a-spiffui-v3/views/PublicRoutes';
import SpiffUIV3 from './a-spiffui-v3/views/SpiffUIV3';
const queryClient = new QueryClient();
@ -18,7 +18,7 @@ export default function App() {
{ path: 'public/*', element: <PublicRoutes /> },
{
path: 'newui/*',
element: <SpiffUIV3 />,
element: <ContainerForExtensionsV3 />,
},
{
path: '*',

View File

@ -0,0 +1,150 @@
import { Container } from '@mui/material';
import { Routes, Route, useLocation } from 'react-router-dom';
import React, { useEffect, useState } from 'react';
import { ErrorBoundary } from 'react-error-boundary';
import ScrollToTop from './components/ScrollToTop';
import Extension from './views/Extension';
import { useUriListForPermissions } from './hooks/UriListForPermissions';
import { PermissionsToCheck, ProcessFile, ProcessModel } from './interfaces';
import { usePermissionFetcher } from './hooks/PermissionService';
import {
ExtensionUiSchema,
UiSchemaUxElement,
} from './extension_ui_schema_interfaces';
import HttpService from './services/HttpService';
import { ErrorBoundaryFallback } from './ErrorBoundaryFallack';
import BaseRoutes from './views/BaseRoutes';
import BackendIsDown from './views/BackendIsDown';
import Login from './views/Login';
import useAPIError from './hooks/UseApiError';
export default function ContainerForExtensions() {
const [backendIsUp, setBackendIsUp] = useState<boolean | null>(null);
const [extensionUxElements, setExtensionUxElements] = useState<
UiSchemaUxElement[] | null
>(null);
let contentClassName = 'main-site-body-centered';
if (window.location.pathname.startsWith('/editor/')) {
contentClassName = 'no-center-stuff';
}
const { targetUris } = useUriListForPermissions();
const permissionRequestData: PermissionsToCheck = {
[targetUris.extensionListPath]: ['GET'],
};
const { ability, permissionsLoaded } = usePermissionFetcher(
permissionRequestData,
);
const { removeError } = useAPIError();
const location = useLocation();
// never carry an error message across to a different path
useEffect(() => {
removeError();
// if we include the removeError function to the dependency array of this useEffect, it causes
// an infinite loop where the page with the error adds the error,
// then this runs and it removes the error, etc. it is ok not to include it here, i think, because it never changes.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [location.pathname]);
// eslint-disable-next-line sonarjs/cognitive-complexity
useEffect(() => {
const processExtensionResult = (processModels: ProcessModel[]) => {
const eni: UiSchemaUxElement[] = processModels
.map((processModel: ProcessModel) => {
const extensionUiSchemaFile = processModel.files.find(
(file: ProcessFile) => file.name === 'extension_uischema.json',
);
if (extensionUiSchemaFile && extensionUiSchemaFile.file_contents) {
try {
const extensionUiSchema: ExtensionUiSchema = JSON.parse(
extensionUiSchemaFile.file_contents,
);
if (
extensionUiSchema &&
extensionUiSchema.ux_elements &&
!extensionUiSchema.disabled
) {
return extensionUiSchema.ux_elements;
}
} catch (jsonParseError: any) {
console.error(
`Unable to get navigation items for ${processModel.id}`,
);
}
}
return [] as UiSchemaUxElement[];
})
.flat();
if (eni) {
setExtensionUxElements(eni);
}
};
const getExtensions = () => {
setBackendIsUp(true);
if (!permissionsLoaded) {
return;
}
if (ability.can('GET', targetUris.extensionListPath)) {
HttpService.makeCallToBackend({
path: targetUris.extensionListPath,
successCallback: processExtensionResult,
});
} else {
// set to an empty array so we know that it loaded
setExtensionUxElements([]);
}
};
HttpService.makeCallToBackend({
path: targetUris.statusPath,
successCallback: getExtensions,
failureCallback: () => setBackendIsUp(false),
});
}, [
targetUris.extensionListPath,
targetUris.statusPath,
permissionsLoaded,
ability,
]);
const routeComponents = () => {
return (
<Routes>
<Route
path="*"
element={<BaseRoutes extensionUxElements={extensionUxElements} />}
/>
<Route path="extensions/:page_identifier" element={<Extension />} />
<Route path="login" element={<Login />} />
</Routes>
);
};
const backendIsDownPage = () => {
return [<BackendIsDown key="backendIsDownPage" />];
};
const innerComponents = () => {
if (backendIsUp === null) {
return [];
}
if (backendIsUp) {
return routeComponents();
}
return backendIsDownPage();
};
return (
<Container className={contentClassName}>
<ScrollToTop />
<ErrorBoundary FallbackComponent={ErrorBoundaryFallback}>
{innerComponents()}
</ErrorBoundary>
</Container>
);
}

View File

@ -0,0 +1,35 @@
import React from 'react';
import { useErrorBoundary } from 'react-error-boundary';
import Button from '@mui/material/Button';
import Alert from '@mui/material/Alert';
import AlertTitle from '@mui/material/AlertTitle';
type ErrorProps = {
error: Error;
};
export function ErrorBoundaryFallback({ error }: ErrorProps) {
// This is displayed if the ErrorBoundary catches an error when rendering the form.
const { resetBoundary } = useErrorBoundary();
// print the error to the console so we can debug issues
console.error(error);
return (
<Alert
severity="error"
action={
<Button color="inherit" size="small" onClick={resetBoundary}>
Try again
</Button>
}
>
<AlertTitle>Something Went Wrong.</AlertTitle>
<p>
We encountered an unexpected error. Please try again. If the problem
persists, please contact your administrator.
</p>
<p>{error.message}</p>
</Alert>
);
}

View File

@ -0,0 +1,12 @@
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
export default function ScrollToTop() {
const { pathname } = useLocation();
useEffect(() => {
window.scrollTo(0, 0);
}, [pathname]);
return null;
}

View File

@ -42,10 +42,9 @@ export enum UiSchemaPersistenceLevel {
* The arguments that can be passed in will generally match the "OwnProps" type defined within each file.
*/
export enum UiSchemaPageComponentList {
// CreateNewInstance = 'CreateNewInstance',
CustomForm = 'CustomForm',
MarkdownRenderer = 'MarkdownRenderer',
// ProcessInstanceListTable = 'ProcessInstanceListTable',
ProcessInstanceListTable = 'ProcessInstanceListTable',
ProcessInstanceRun = 'ProcessInstanceRun',
SpiffTabs = 'SpiffTabs',
}

View File

@ -0,0 +1,12 @@
export default function BackendIsDown() {
return (
<div>
<h1>Server error</h1>
<p>
We are sorry, but our service is temporarily unavailable due to
technical difficulties. Please bear with us while we work to resolve the
issue. If the problem persists, please contact the site administrator.
</p>
</div>
);
}

View File

@ -20,7 +20,6 @@ import Processes from './StartProcess/Processes';
import StartProcessInstance from './StartProcess/StartProcessInstance';
import SideNav from '../components/SideNav';
import LoginHandler from '../components/LoginHandler';
import Login from './Login';
import InstancesStartedByMe from './InstancesStartedByMe';
import TaskShow from './TaskShow/TaskShow';
import ProcessInterstitialPage from './TaskShow/ProcessInterstitialPage';
@ -46,12 +45,16 @@ import ReactFormEditor from './ReactFormEditor'; // Import the new component
import ProcessInstanceRoutes from './ProcessInstanceRoutes';
import ProcessInstanceShortLink from './ProcessInstanceShortLink';
import ProcessInstanceList from './ProcessInstanceList'; // Import the new component
// Import the new component
import { UiSchemaUxElement } from '../extension_ui_schema_interfaces';
const fadeIn = 'fadeIn';
const fadeOutImmediate = 'fadeOutImmediate';
export default function SpiffUIV3() {
type OwnProps = {
extensionUxElements?: UiSchemaUxElement[] | null;
};
export default function BaseRoutes({ extensionUxElements }: OwnProps) {
const storedTheme: PaletteMode = (localStorage.getItem('theme') ||
'light') as PaletteMode;
const [globalTheme, setGlobalTheme] = useState(
@ -260,7 +263,6 @@ export default function SpiffUIV3() {
path="/:modifiedProcessModelId/start"
element={<StartProcessInstance />}
/>
<Route path="login" element={<Login />} />
<Route path="/create-custom-tab" element={<ComingSoon />} />
<Route
path="/tasks/:process_instance_id/:task_guid"

View File

@ -22,8 +22,7 @@ import ProcessInstanceRun from '../components/ProcessInstanceRun';
import MarkdownRenderer from '../components/MarkdownRenderer';
import LoginHandler from '../components/LoginHandler';
import SpiffTabs from '../components/SpiffTabs';
// import ProcessInstanceListTable from '../components/ProcessInstanceListTable';
// import CreateNewInstance from './CreateNewInstance';
import ProcessInstanceListTable from '../components/ProcessInstanceListTable';
type OwnProps = {
pageIdentifier?: string;
@ -63,10 +62,9 @@ export default function Extension({
const { addError, removeError } = useAPIError();
const supportedComponents: SupportedComponentList = {
// CreateNewInstance,
CustomForm,
MarkdownRenderer,
// ProcessInstanceListTable,
ProcessInstanceListTable,
ProcessInstanceRun,
SpiffTabs,
};