Feature/pi short link (#503)

* moved adminroutes to homepage to get rid of /admin from urls

* removed admin prefix from urls

* moved top level base routes to their own routes file w/ burnettk

* added ability to get and use a short link to the process instance show page w/ burnettk

* give the person some feedback

* move about page to baseroutes so it acts like the other pages

* use the normal notificaiton component for the copied link notification

* added 404 page and backend is down page w/ burnettk

---------

Co-authored-by: jasquat <jasquat@users.noreply.github.com>
Co-authored-by: burnettk <burnettk@users.noreply.github.com>
This commit is contained in:
jasquat 2023-09-20 12:02:02 -04:00 committed by GitHub
parent 00158df03d
commit ff83b8c801
51 changed files with 415 additions and 446 deletions

View File

@ -14,7 +14,6 @@ export default function App() {
} }
const ability = defineAbility(() => {}); const ability = defineAbility(() => {});
return ( return (
<div className="cds--white"> <div className="cds--white">
{/* @ts-ignore */} {/* @ts-ignore */}

View File

@ -4,10 +4,6 @@ import React, { useEffect, useState } from 'react';
import { ErrorBoundary } from 'react-error-boundary'; import { ErrorBoundary } from 'react-error-boundary';
import NavigationBar from './components/NavigationBar'; import NavigationBar from './components/NavigationBar';
import HomePageRoutes from './routes/HomePageRoutes';
import About from './routes/About';
import AdminRoutes from './routes/AdminRoutes';
import ScrollToTop from './components/ScrollToTop'; import ScrollToTop from './components/ScrollToTop';
import EditorRoutes from './routes/EditorRoutes'; import EditorRoutes from './routes/EditorRoutes';
import Extension from './routes/Extension'; import Extension from './routes/Extension';
@ -20,8 +16,11 @@ import {
} from './extension_ui_schema_interfaces'; } from './extension_ui_schema_interfaces';
import HttpService from './services/HttpService'; import HttpService from './services/HttpService';
import { ErrorBoundaryFallback } from './ErrorBoundaryFallack'; import { ErrorBoundaryFallback } from './ErrorBoundaryFallack';
import BaseRoutes from './routes/BaseRoutes';
import BackendIsDown from './routes/BackendIsDown';
export default function ContainerForExtensions() { export default function ContainerForExtensions() {
const [backendIsUp, setBackendIsUp] = useState<boolean | null>(null);
const [extensionUxElements, setExtensionNavigationItems] = useState< const [extensionUxElements, setExtensionNavigationItems] = useState<
UiSchemaUxElement[] | null UiSchemaUxElement[] | null
>(null); >(null);
@ -40,10 +39,6 @@ export default function ContainerForExtensions() {
// eslint-disable-next-line sonarjs/cognitive-complexity // eslint-disable-next-line sonarjs/cognitive-complexity
useEffect(() => { useEffect(() => {
if (!permissionsLoaded) {
return;
}
const processExtensionResult = (processModels: ProcessModel[]) => { const processExtensionResult = (processModels: ProcessModel[]) => {
const eni: UiSchemaUxElement[] = processModels const eni: UiSchemaUxElement[] = processModels
.map((processModel: ProcessModel) => { .map((processModel: ProcessModel) => {
@ -72,13 +67,57 @@ export default function ContainerForExtensions() {
} }
}; };
const getExtensions = () => {
if (!permissionsLoaded) {
return;
}
setBackendIsUp(true);
if (ability.can('GET', targetUris.extensionListPath)) { if (ability.can('GET', targetUris.extensionListPath)) {
HttpService.makeCallToBackend({ HttpService.makeCallToBackend({
path: targetUris.extensionListPath, path: targetUris.extensionListPath,
successCallback: processExtensionResult, successCallback: processExtensionResult,
}); });
} }
}, [targetUris.extensionListPath, permissionsLoaded, ability]); };
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="/editor/*" element={<EditorRoutes />} />
<Route path="/extensions/:page_identifier" element={<Extension />} />
</Routes>
);
};
const backendIsDownPage = () => {
return <BackendIsDown />;
};
const innerComponents = () => {
if (backendIsUp === null) {
return null;
}
if (backendIsUp) {
return routeComponents();
}
return backendIsDownPage();
};
return ( return (
<> <>
@ -86,22 +125,7 @@ export default function ContainerForExtensions() {
<Content className={contentClassName}> <Content className={contentClassName}>
<ScrollToTop /> <ScrollToTop />
<ErrorBoundary FallbackComponent={ErrorBoundaryFallback}> <ErrorBoundary FallbackComponent={ErrorBoundaryFallback}>
<Routes> {innerComponents()}
<Route path="/*" element={<HomePageRoutes />} />
<Route path="/about" element={<About />} />
<Route path="/tasks/*" element={<HomePageRoutes />} />
<Route
path="/admin/*"
element={
<AdminRoutes extensionUxElements={extensionUxElements} />
}
/>
<Route path="/editor/*" element={<EditorRoutes />} />
<Route
path="/extensions/:page_identifier"
element={<Extension />}
/>
</Routes>
</ErrorBoundary> </ErrorBoundary>
</Content> </Content>
</> </>

View File

@ -1,24 +1,7 @@
import { Button, Content } from '@carbon/react'; import { Button } from '@carbon/react';
import { Routes, Route } from 'react-router-dom'; import React from 'react';
import React, { useEffect, useState } from 'react'; import { useErrorBoundary } from 'react-error-boundary';
import { ErrorBoundary, useErrorBoundary } from 'react-error-boundary';
import NavigationBar from './components/NavigationBar';
import HomePageRoutes from './routes/HomePageRoutes';
import About from './routes/About';
import AdminRoutes from './routes/AdminRoutes';
import ScrollToTop from './components/ScrollToTop';
import EditorRoutes from './routes/EditorRoutes';
import Extension from './routes/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 { Notification } from './components/Notification'; import { Notification } from './components/Notification';
type ErrorProps = { type ErrorProps = {
@ -44,90 +27,3 @@ export function ErrorBoundaryFallback({ error }: ErrorProps) {
</Notification> </Notification>
); );
} }
export default function ContainerForExtensions() {
const [extensionUxElements, setExtensionNavigationItems] = 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
);
// eslint-disable-next-line sonarjs/cognitive-complexity
useEffect(() => {
if (!permissionsLoaded) {
return;
}
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.ux_elements) {
return extensionUiSchema.ux_elements;
}
} catch (jsonParseError: any) {
console.error(
`Unable to get navigation items for ${processModel.id}`
);
}
}
return [] as UiSchemaUxElement[];
})
.flat();
if (eni) {
setExtensionNavigationItems(eni);
}
};
if (ability.can('GET', targetUris.extensionListPath)) {
HttpService.makeCallToBackend({
path: targetUris.extensionListPath,
successCallback: processExtensionResult,
});
}
}, [targetUris.extensionListPath, permissionsLoaded, ability]);
return (
<>
<NavigationBar extensionUxElements={extensionUxElements} />
<Content className={contentClassName}>
<ScrollToTop />
<ErrorBoundary fallback={<h1>Something went wrong.</h1>}>
<Routes>
<Route path="/*" element={<HomePageRoutes />} />
<Route path="/about" element={<About />} />
<Route path="/tasks/*" element={<HomePageRoutes />} />
<Route
path="/admin/*"
element={
<AdminRoutes extensionUxElements={extensionUxElements} />
}
/>
<Route path="/editor/*" element={<EditorRoutes />} />
<Route
path="/extensions/:page_identifier"
element={<Extension />}
/>
</Routes>
</ErrorBoundary>
</Content>
</>
);
}

View File

@ -100,7 +100,7 @@ export default function MessageInstanceList({ processInstanceId }: OwnProps) {
instanceLink = ( instanceLink = (
<Link <Link
data-qa="process-instance-show-link" data-qa="process-instance-show-link"
to={`/admin/process-instances/${modifyProcessIdentifierForPathParam( to={`/process-instances/${modifyProcessIdentifierForPathParam(
row.process_model_identifier row.process_model_identifier
)}/${row.process_instance_id}`} )}/${row.process_instance_id}`}
> >
@ -167,7 +167,7 @@ export default function MessageInstanceList({ processInstanceId }: OwnProps) {
}, },
[ [
`Process Instance: ${searchParams.get('process_instance_id')}`, `Process Instance: ${searchParams.get('process_instance_id')}`,
`/admin/process-instances/${searchParams.get( `/process-instances/${searchParams.get(
'process_model_id' 'process_model_id'
)}/${searchParams.get('process_instance_id')}`, )}/${searchParams.get('process_instance_id')}`,
], ],

View File

@ -11,7 +11,7 @@ export function FormatProcessModelDisplayName(
} = instanceObject; } = instanceObject;
return ( return (
<Link <Link
to={`/admin/process-models/${modifyProcessIdentifierForPathParam( to={`/process-models/${modifyProcessIdentifierForPathParam(
processModelIdentifier processModelIdentifier
)}`} )}`}
title={processModelIdentifier} title={processModelIdentifier}

View File

@ -76,19 +76,17 @@ export default function NavigationBar({ extensionUxElements }: OwnProps) {
const versionInfo = appVersionInfo(); const versionInfo = appVersionInfo();
useEffect(() => { useEffect(() => {
let newActiveKey = '/admin/process-groups'; let newActiveKey = '/process-groups';
if (location.pathname.match(/^\/admin\/messages\b/)) { if (location.pathname.match(/^\/messages\b/)) {
newActiveKey = '/admin/messages'; newActiveKey = '/messages';
} else if ( } else if (location.pathname.match(/^\/process-instances\/reports\b/)) {
location.pathname.match(/^\/admin\/process-instances\/reports\b/) newActiveKey = '/process-instances/reports';
) { } else if (location.pathname.match(/^\/process-instances\b/)) {
newActiveKey = '/admin/process-instances/reports'; newActiveKey = '/process-instances';
} else if (location.pathname.match(/^\/admin\/process-instances\b/)) { } else if (location.pathname.match(/^\/configuration\b/)) {
newActiveKey = '/admin/process-instances'; newActiveKey = '/configuration';
} else if (location.pathname.match(/^\/admin\/configuration\b/)) { } else if (location.pathname.match(/^\/data-stores\b/)) {
newActiveKey = '/admin/configuration'; newActiveKey = '/data-stores';
} else if (location.pathname.match(/^\/admin\/data-stores\b/)) {
newActiveKey = '/admin/data-stores';
} else if (location.pathname === '/') { } else if (location.pathname === '/') {
newActiveKey = '/'; newActiveKey = '/';
} else if (location.pathname.match(/^\/tasks\b/)) { } else if (location.pathname.match(/^\/tasks\b/)) {
@ -205,8 +203,8 @@ export default function NavigationBar({ extensionUxElements }: OwnProps) {
if (secretAllowed || authenticationAllowed) { if (secretAllowed || authenticationAllowed) {
return ( return (
<HeaderMenuItem <HeaderMenuItem
href="/admin/configuration" href="/configuration"
isCurrentPage={isActivePage('/admin/configuration')} isCurrentPage={isActivePage('/configuration')}
> >
Configuration Configuration
</HeaderMenuItem> </HeaderMenuItem>
@ -249,8 +247,8 @@ export default function NavigationBar({ extensionUxElements }: OwnProps) {
</HeaderMenuItem> </HeaderMenuItem>
<Can I="GET" a={targetUris.processGroupListPath} ability={ability}> <Can I="GET" a={targetUris.processGroupListPath} ability={ability}>
<HeaderMenuItem <HeaderMenuItem
href="/admin/process-groups" href="/process-groups"
isCurrentPage={isActivePage('/admin/process-groups')} isCurrentPage={isActivePage('/process-groups')}
data-qa="header-nav-processes" data-qa="header-nav-processes"
> >
Processes Processes
@ -262,24 +260,24 @@ export default function NavigationBar({ extensionUxElements }: OwnProps) {
ability={ability} ability={ability}
> >
<HeaderMenuItem <HeaderMenuItem
href="/admin/process-instances" href="/process-instances"
isCurrentPage={isActivePage('/admin/process-instances')} isCurrentPage={isActivePage('/process-instances')}
> >
Process Instances Process Instances
</HeaderMenuItem> </HeaderMenuItem>
</Can> </Can>
<Can I="GET" a={targetUris.messageInstanceListPath} ability={ability}> <Can I="GET" a={targetUris.messageInstanceListPath} ability={ability}>
<HeaderMenuItem <HeaderMenuItem
href="/admin/messages" href="/messages"
isCurrentPage={isActivePage('/admin/messages')} isCurrentPage={isActivePage('/messages')}
> >
Messages Messages
</HeaderMenuItem> </HeaderMenuItem>
</Can> </Can>
<Can I="GET" a={targetUris.dataStoreListPath} ability={ability}> <Can I="GET" a={targetUris.dataStoreListPath} ability={ability}>
<HeaderMenuItem <HeaderMenuItem
href="/admin/data-stores" href="/data-stores"
isCurrentPage={isActivePage('/admin/data-stores')} isCurrentPage={isActivePage('/data-stores')}
> >
Data Stores Data Stores
</HeaderMenuItem> </HeaderMenuItem>

View File

@ -15,6 +15,8 @@ type OwnProps = {
type?: string; type?: string;
hideCloseButton?: boolean; hideCloseButton?: boolean;
allowTogglingFullMessage?: boolean; allowTogglingFullMessage?: boolean;
timeout?: number;
withBottomMargin?: boolean;
}; };
export function Notification({ export function Notification({
@ -24,6 +26,8 @@ export function Notification({
type = 'success', type = 'success',
hideCloseButton = false, hideCloseButton = false,
allowTogglingFullMessage = false, allowTogglingFullMessage = false,
timeout,
withBottomMargin = true,
}: OwnProps) { }: OwnProps) {
const [showMessage, setShowMessage] = useState<boolean>( const [showMessage, setShowMessage] = useState<boolean>(
!allowTogglingFullMessage !allowTogglingFullMessage
@ -33,11 +37,19 @@ export function Notification({
iconComponent = <Error className="notification-icon" />; iconComponent = <Error className="notification-icon" />;
} }
if (timeout && onClose) {
setTimeout(() => {
onClose();
}, timeout);
}
let classes = `cds--inline-notification cds--inline-notification--low-contrast cds--inline-notification--${type}`;
if (withBottomMargin) {
classes = `${classes} with-bottom-margin`;
}
return ( return (
<div <div role="status" className={classes}>
role="status"
className={`with-bottom-margin cds--inline-notification cds--inline-notification--low-contrast cds--inline-notification--${type}`}
>
<div className="cds--inline-notification__details"> <div className="cds--inline-notification__details">
<div className="cds--inline-notification__text-wrapper"> <div className="cds--inline-notification__text-wrapper">
{iconComponent} {iconComponent}

View File

@ -58,7 +58,7 @@ export default function ProcessBreadcrumb({ hotCrumbs }: OwnProps) {
) { ) {
const breadcrumbs = processEntity.parent_groups.map( const breadcrumbs = processEntity.parent_groups.map(
(parentGroup: ProcessGroupLite) => { (parentGroup: ProcessGroupLite) => {
const fullUrl = `/admin/process-groups/${modifyProcessIdentifierForPathParam( const fullUrl = `/process-groups/${modifyProcessIdentifierForPathParam(
parentGroup.id parentGroup.id
)}`; )}`;
return ( return (
@ -70,10 +70,10 @@ export default function ProcessBreadcrumb({ hotCrumbs }: OwnProps) {
); );
if (crumb.linkLastItem) { if (crumb.linkLastItem) {
let apiBase = '/admin/process-groups'; let apiBase = '/process-groups';
let dataQaTag = ''; let dataQaTag = '';
if (crumb.entityType.startsWith('process-model')) { if (crumb.entityType.startsWith('process-model')) {
apiBase = '/admin/process-models'; apiBase = '/process-models';
dataQaTag = 'process-model-breadcrumb-link'; dataQaTag = 'process-model-breadcrumb-link';
} }
const fullUrl = `${apiBase}/${modifyProcessIdentifierForPathParam( const fullUrl = `${apiBase}/${modifyProcessIdentifierForPathParam(

View File

@ -27,7 +27,7 @@ export default function ProcessGroupForm({
const navigateToProcessGroup = (_result: any) => { const navigateToProcessGroup = (_result: any) => {
if (newProcessGroupId) { if (newProcessGroupId) {
navigate( navigate(
`/admin/process-groups/${modifyProcessIdentifierForPathParam( `/process-groups/${modifyProcessIdentifierForPathParam(
newProcessGroupId newProcessGroupId
)}` )}`
); );

View File

@ -56,7 +56,7 @@ export default function ProcessGroupListTiles({
<ClickableTile <ClickableTile
id={`process-group-tile-${row.id}`} id={`process-group-tile-${row.id}`}
className="tile-process-group" className="tile-process-group"
href={`/admin/process-groups/${modifyProcessIdentifierForPathParam( href={`/process-groups/${modifyProcessIdentifierForPathParam(
row.id row.id
)}`} )}`}
> >

View File

@ -123,6 +123,7 @@ export default function ProcessInstanceListTable({
headerElement, headerElement,
tableHtmlId, tableHtmlId,
}: OwnProps) { }: OwnProps) {
// eslint-disable-next-line sonarjs/no-duplicate-string
let processInstanceApiSearchPath = '/process-instances/for-me'; let processInstanceApiSearchPath = '/process-instances/for-me';
if (variant === 'all') { if (variant === 'all') {
processInstanceApiSearchPath = '/process-instances'; processInstanceApiSearchPath = '/process-instances';
@ -177,13 +178,9 @@ export default function ProcessInstanceListTable({
}; };
const processInstanceListPathPrefix = const processInstanceListPathPrefix =
variant === 'all' variant === 'all' ? '/process-instances/all' : '/process-instances/for-me';
? '/admin/process-instances/all'
: '/admin/process-instances/for-me';
const processInstanceShowPathPrefix = const processInstanceShowPathPrefix =
variant === 'all' variant === 'all' ? '/process-instances' : '/process-instances/for-me';
? '/admin/process-instances'
: '/admin/process-instances/for-me';
const [processStatusAllOptions, setProcessStatusAllOptions] = useState<any[]>( const [processStatusAllOptions, setProcessStatusAllOptions] = useState<any[]>(
[] []
@ -1834,7 +1831,7 @@ export default function ProcessInstanceListTable({
hasIconOnly hasIconOnly
size="md" size="md"
onClick={() => onClick={() =>
navigate(`/admin/process-instances?report_hash=${reportHash}`) navigate(`/process-instances?report_hash=${reportHash}`)
} }
/> />
</Column> </Column>

View File

@ -32,7 +32,7 @@ export default function ProcessInstanceListTabs({ variant }: OwnProps) {
title="Only show process instances for the current user." title="Only show process instances for the current user."
data-qa="process-instance-list-for-me" data-qa="process-instance-list-for-me"
onClick={() => { onClick={() => {
navigate('/admin/process-instances/for-me'); navigate('/process-instances/for-me');
}} }}
> >
For Me For Me
@ -42,7 +42,7 @@ export default function ProcessInstanceListTabs({ variant }: OwnProps) {
title="Show all process instances for all users." title="Show all process instances for all users."
data-qa="process-instance-list-all" data-qa="process-instance-list-all"
onClick={() => { onClick={() => {
navigate('/admin/process-instances/all'); navigate('/process-instances/all');
}} }}
> >
All All
@ -52,7 +52,7 @@ export default function ProcessInstanceListTabs({ variant }: OwnProps) {
title="Search for a process instance by id." title="Search for a process instance by id."
data-qa="process-instance-list-find-by-id" data-qa="process-instance-list-find-by-id"
onClick={() => { onClick={() => {
navigate('/admin/process-instances/find-by-id'); navigate('/process-instances/find-by-id');
}} }}
> >
Find By Id Find By Id

View File

@ -78,9 +78,9 @@ export default function ProcessInstanceLogList({
shouldDisplayClearButton = true; shouldDisplayClearButton = true;
} }
let processInstanceShowPageBaseUrl = `/admin/process-instances/for-me/${processModelId}`; let processInstanceShowPageBaseUrl = `/process-instances/for-me/${processModelId}`;
if (variant === 'all') { if (variant === 'all') {
processInstanceShowPageBaseUrl = `/admin/process-instances/${processModelId}`; processInstanceShowPageBaseUrl = `/process-instances/${processModelId}`;
} }
const taskNameHeader = isEventsView ? 'Task name' : 'Milestone'; const taskNameHeader = isEventsView ? 'Task name' : 'Milestone';
const tableType = isEventsView ? 'events' : 'milestones'; const tableType = isEventsView ? 'events' : 'milestones';

View File

@ -98,7 +98,7 @@ export default function ProcessInstanceRun({
const onProcessInstanceRun = (processInstance: any) => { const onProcessInstanceRun = (processInstance: any) => {
const processInstanceId = (processInstance as any).id; const processInstanceId = (processInstance as any).id;
navigate( navigate(
`/admin/process-instances/for-me/${modifyProcessIdentifierForPathParam( `/process-instances/for-me/${modifyProcessIdentifierForPathParam(
processModel.id processModel.id
)}/${processInstanceId}/interstitial` )}/${processInstanceId}/interstitial`
); );

View File

@ -42,7 +42,7 @@ export default function ProcessModelForm({
if ('id' in result) { if ('id' in result) {
const modifiedProcessModelPathFromResult = const modifiedProcessModelPathFromResult =
modifyProcessIdentifierForPathParam(result.id); modifyProcessIdentifierForPathParam(result.id);
navigate(`/admin/process-models/${modifiedProcessModelPathFromResult}`); navigate(`/process-models/${modifiedProcessModelPathFromResult}`);
} }
}; };

View File

@ -56,7 +56,7 @@ export default function ProcessModelListTiles({
onClose={() => setProcessInstance(null)} onClose={() => setProcessInstance(null)}
> >
<Link <Link
to={`/admin/process-instances/${modifyProcessIdentifierForPathParam( to={`/process-instances/${modifyProcessIdentifierForPathParam(
processInstance.process_model_identifier processInstance.process_model_identifier
)}/${processInstance.id}`} )}/${processInstance.id}`}
data-qa="process-instance-show-link" data-qa="process-instance-show-link"
@ -83,7 +83,7 @@ export default function ProcessModelListTiles({
<a <a
title={row.id} title={row.id}
data-qa="process-model-show-link" data-qa="process-model-show-link"
href={`/admin/process-models/${modifyProcessIdentifierForPathParam( href={`/process-models/${modifyProcessIdentifierForPathParam(
row.id row.id
)}`} )}`}
> >

View File

@ -632,7 +632,7 @@ export default function ReactDiagramEditor({
<li> <li>
<Link <Link
size="lg" size="lg"
href={`/admin/process-models/${modifyProcessIdentifierForPathParam( href={`/process-models/${modifyProcessIdentifierForPathParam(
ref.process_model_id ref.process_model_id
)}`} )}`}
> >
@ -704,7 +704,7 @@ export default function ReactDiagramEditor({
<Button <Button
onClick={() => { onClick={() => {
navigate( navigate(
`/admin/process-models/${processModelId}/form/${fileName}` `/process-models/${processModelId}/form/${fileName}`
); );
}} }}
> >

View File

@ -1,13 +0,0 @@
import React from 'react';
import UserService from '../services/UserService';
type Props = {
roles: string[];
children: React.ReactNode;
};
function RenderOnRole({ roles, children }: Props) {
return UserService.hasRole(roles) ? children : null;
}
export default RenderOnRole;

View File

@ -214,7 +214,7 @@ export default function TaskListTable({
<td> <td>
<Link <Link
data-qa="process-instance-show-link-id" data-qa="process-instance-show-link-id"
to={`/admin/process-instances/for-me/${modifiedProcessModelIdentifier}/${processInstanceTask.process_instance_id}`} to={`/process-instances/for-me/${modifiedProcessModelIdentifier}/${processInstanceTask.process_instance_id}`}
title={`View process instance ${processInstanceTask.process_instance_id}`} title={`View process instance ${processInstanceTask.process_instance_id}`}
> >
{processInstanceTask.process_instance_id} {processInstanceTask.process_instance_id}
@ -239,7 +239,7 @@ export default function TaskListTable({
<td> <td>
<Link <Link
data-qa="process-model-show-link" data-qa="process-model-show-link"
to={`/admin/process-models/${modifiedProcessModelIdentifier}`} to={`/process-models/${modifiedProcessModelIdentifier}`}
title={processInstanceTask.process_model_identifier} title={processInstanceTask.process_model_identifier}
> >
{processInstanceTask.process_model_display_name} {processInstanceTask.process_model_display_name}

View File

@ -6,6 +6,7 @@ export const useUriListForPermissions = () => {
const targetUris = useMemo(() => { const targetUris = useMemo(() => {
return { return {
authenticationListPath: `/v1.0/authentications`, authenticationListPath: `/v1.0/authentications`,
statusPath: `/v1.0/status`,
messageInstanceListPath: '/v1.0/messages', messageInstanceListPath: '/v1.0/messages',
dataStoreListPath: '/v1.0/data-stores', dataStoreListPath: '/v1.0/data-stores',
extensionListPath: '/v1.0/extensions', extensionListPath: '/v1.0/extensions',

View File

@ -1,7 +1,6 @@
// @ts-ignore // @ts-ignore
import { Table } from '@carbon/react'; import { Table } from '@carbon/react';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import ErrorDisplay from '../components/ErrorDisplay';
import appVersionInfo from '../helpers/appVersionInfo'; import appVersionInfo from '../helpers/appVersionInfo';
import { ObjectWithStringKeysAndValues } from '../interfaces'; import { ObjectWithStringKeysAndValues } from '../interfaces';
import HttpService from '../services/HttpService'; import HttpService from '../services/HttpService';
@ -58,7 +57,6 @@ export default function About() {
return ( return (
<div> <div>
<ErrorDisplay />
<h1>About</h1> <h1>About</h1>
{versionInfoFromDict('Frontend version information', frontendVersionInfo)} {versionInfoFromDict('Frontend version information', frontendVersionInfo)}
{versionInfoFromDict('Backend version information', backendVersionInfo)} {versionInfoFromDict('Backend version information', backendVersionInfo)}

View File

@ -1,151 +0,0 @@
import { Routes, Route, useLocation } from 'react-router-dom';
import React, { useEffect } from 'react';
import ProcessGroupList from './ProcessGroupList';
import ProcessGroupShow from './ProcessGroupShow';
import ProcessGroupNew from './ProcessGroupNew';
import ProcessGroupEdit from './ProcessGroupEdit';
import ProcessModelShow from './ProcessModelShow';
import ProcessInstanceList from './ProcessInstanceList';
import ProcessModelNew from './ProcessModelNew';
import ProcessModelEdit from './ProcessModelEdit';
import ProcessInstanceShow from './ProcessInstanceShow';
import UserService from '../services/UserService';
import ProcessInstanceReportList from './ProcessInstanceReportList';
import ProcessInstanceReportNew from './ProcessInstanceReportNew';
import ProcessInstanceReportEdit from './ProcessInstanceReportEdit';
import ReactFormEditor from './ReactFormEditor';
import Configuration from './Configuration';
import JsonSchemaFormBuilder from './JsonSchemaFormBuilder';
import ProcessModelNewExperimental from './ProcessModelNewExperimental';
import ProcessInstanceFindById from './ProcessInstanceFindById';
import ProcessInterstitialPage from './ProcessInterstitialPage';
import MessageListPage from './MessageListPage';
import DataStorePage from './DataStorePage';
import ErrorDisplay from '../components/ErrorDisplay';
import { UiSchemaUxElement } from '../extension_ui_schema_interfaces';
type OwnProps = {
extensionUxElements?: UiSchemaUxElement[] | null;
};
export default function AdminRoutes({ extensionUxElements }: OwnProps) {
const location = useLocation();
useEffect(() => {}, [location]);
if (UserService.hasRole(['admin'])) {
return (
<div className="fixed-width-container">
<ErrorDisplay />
<Routes>
<Route path="/" element={<ProcessGroupList />} />
<Route path="process-groups" element={<ProcessGroupList />} />
<Route
path="process-groups/:process_group_id"
element={<ProcessGroupShow />}
/>
<Route path="process-groups/new" element={<ProcessGroupNew />} />
<Route
path="process-groups/:process_group_id/edit"
element={<ProcessGroupEdit />}
/>
<Route
path="process-models/:process_group_id/new"
element={<ProcessModelNew />}
/>
<Route
path="process-models/:process_group_id/new-e"
element={<ProcessModelNewExperimental />}
/>
<Route
path="process-models/:process_model_id"
element={<ProcessModelShow />}
/>
<Route
path="process-models/:process_model_id/edit"
element={<ProcessModelEdit />}
/>
<Route
path="process-instances/for-me/:process_model_id/:process_instance_id"
element={<ProcessInstanceShow variant="for-me" />}
/>
<Route
path="process-instances/for-me/:process_model_id/:process_instance_id/:to_task_guid"
element={<ProcessInstanceShow variant="for-me" />}
/>
<Route
path="process-instances/for-me/:process_model_id/:process_instance_id/interstitial"
element={<ProcessInterstitialPage variant="for-me" />}
/>
<Route
path="process-instances/:process_model_id/:process_instance_id/interstitial"
element={<ProcessInterstitialPage variant="all" />}
/>
<Route
path="process-instances/:process_model_id/:process_instance_id"
element={<ProcessInstanceShow variant="all" />}
/>
<Route
path="process-instances/:process_model_id/:process_instance_id/:to_task_guid"
element={<ProcessInstanceShow variant="all" />}
/>
<Route
path="process-instances/reports"
element={<ProcessInstanceReportList />}
/>
<Route
path="process-instances/reports/new"
element={<ProcessInstanceReportNew />}
/>
<Route
path="process-instances/reports/:report_identifier/edit"
element={<ProcessInstanceReportEdit />}
/>
<Route
path="process-models/:process_model_id/form"
element={<ReactFormEditor />}
/>
<Route
path="process-models/:process_model_id/form/:file_name"
element={<ReactFormEditor />}
/>
<Route
path="process-instances"
element={<ProcessInstanceList variant="for-me" />}
/>
<Route
path="process-instances/for-me"
element={<ProcessInstanceList variant="for-me" />}
/>
<Route
path="process-instances/all"
element={<ProcessInstanceList variant="all" />}
/>
<Route
path="configuration/*"
element={
<Configuration extensionUxElements={extensionUxElements} />
}
/>
<Route
path="process-models/:process_model_id/form-builder"
element={<JsonSchemaFormBuilder />}
/>
<Route
path="process-instances/find-by-id"
element={<ProcessInstanceFindById />}
/>
<Route path="messages" element={<MessageListPage />} />
<Route path="data-stores" element={<DataStorePage />} />
</Routes>
</div>
);
}
return (
<main>
<h1>404</h1>
</main>
);
}

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

@ -0,0 +1,44 @@
import { Route, Routes } from 'react-router-dom';
import Configuration from './Configuration';
import MessageListPage from './MessageListPage';
import DataStorePage from './DataStorePage';
import { UiSchemaUxElement } from '../extension_ui_schema_interfaces';
import HomeRoutes from './HomeRoutes';
import ProcessGroupRoutes from './ProcessGroupRoutes';
import ProcessModelRoutes from './ProcessModelRoutes';
import ProcessInstanceRoutes from './ProcessInstanceRoutes';
import ErrorDisplay from '../components/ErrorDisplay';
import ProcessInstanceShortLink from './ProcessInstanceShortLink';
import About from './About';
import Page404 from './Page404';
type OwnProps = {
extensionUxElements?: UiSchemaUxElement[] | null;
};
export default function BaseRoutes({ extensionUxElements }: OwnProps) {
return (
<div className="fixed-width-container">
<ErrorDisplay />
<Routes>
<Route path="/" element={<HomeRoutes />} />
<Route path="tasks/*" element={<HomeRoutes />} />
<Route path="process-groups/*" element={<ProcessGroupRoutes />} />
<Route path="process-models/*" element={<ProcessModelRoutes />} />
<Route path="process-instances/*" element={<ProcessInstanceRoutes />} />
<Route
path="i/:process_instance_id"
element={<ProcessInstanceShortLink />}
/>
<Route
path="configuration/*"
element={<Configuration extensionUxElements={extensionUxElements} />}
/>
<Route path="messages" element={<MessageListPage />} />
<Route path="data-stores" element={<DataStorePage />} />
<Route path="about" element={<About />} />
<Route path="/*" element={<Page404 />} />
</Routes>
</div>
);
}

View File

@ -39,7 +39,7 @@ export default function Configuration({ extensionUxElements }: OwnProps) {
removeError(); removeError();
setPageTitle(['Configuration']); setPageTitle(['Configuration']);
let newSelectedTabIndex = 0; let newSelectedTabIndex = 0;
if (location.pathname.match(/^\/admin\/configuration\/authentications\b/)) { if (location.pathname.match(/^\/configuration\/authentications\b/)) {
newSelectedTabIndex = 1; newSelectedTabIndex = 1;
} }
setSelectedTabIndex(newSelectedTabIndex); setSelectedTabIndex(newSelectedTabIndex);
@ -49,7 +49,7 @@ export default function Configuration({ extensionUxElements }: OwnProps) {
uxElement: UiSchemaUxElement, uxElement: UiSchemaUxElement,
uxElementIndex: number uxElementIndex: number
) => { ) => {
const navItemPage = `/admin/configuration/extension${uxElement.page}`; const navItemPage = `/configuration/extension${uxElement.page}`;
let pagesToCheck = [uxElement.page]; let pagesToCheck = [uxElement.page];
if ( if (
@ -60,7 +60,7 @@ export default function Configuration({ extensionUxElements }: OwnProps) {
} }
pagesToCheck.forEach((pageToCheck: string) => { pagesToCheck.forEach((pageToCheck: string) => {
const pageToCheckNavItem = `/admin/configuration/extension${pageToCheck}`; const pageToCheckNavItem = `/configuration/extension${pageToCheck}`;
if (pageToCheckNavItem === location.pathname) { if (pageToCheckNavItem === location.pathname) {
setSelectedTabIndex(uxElementIndex + 2); setSelectedTabIndex(uxElementIndex + 2);
} }
@ -80,14 +80,12 @@ export default function Configuration({ extensionUxElements }: OwnProps) {
<Tabs selectedIndex={selectedTabIndex}> <Tabs selectedIndex={selectedTabIndex}>
<TabList aria-label="List of tabs"> <TabList aria-label="List of tabs">
<Can I="GET" a={targetUris.secretListPath} ability={ability}> <Can I="GET" a={targetUris.secretListPath} ability={ability}>
<Tab onClick={() => navigate('/admin/configuration/secrets')}> <Tab onClick={() => navigate('/configuration/secrets')}>
Secrets Secrets
</Tab> </Tab>
</Can> </Can>
<Can I="GET" a={targetUris.authenticationListPath} ability={ability}> <Can I="GET" a={targetUris.authenticationListPath} ability={ability}>
<Tab <Tab onClick={() => navigate('/configuration/authentications')}>
onClick={() => navigate('/admin/configuration/authentications')}
>
Authentications Authentications
</Tab> </Tab>
</Can> </Can>

View File

@ -2,7 +2,6 @@ import { Routes, Route, useLocation } from 'react-router-dom';
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import ProcessModelEditDiagram from './ProcessModelEditDiagram'; import ProcessModelEditDiagram from './ProcessModelEditDiagram';
import UserService from '../services/UserService';
import ErrorDisplay from '../components/ErrorDisplay'; import ErrorDisplay from '../components/ErrorDisplay';
export default function EditorRoutes() { export default function EditorRoutes() {
@ -10,7 +9,6 @@ export default function EditorRoutes() {
useEffect(() => {}, [location]); useEffect(() => {}, [location]);
if (UserService.hasRole(['admin'])) {
return ( return (
<div className="full-width-container no-center-stuff"> <div className="full-width-container no-center-stuff">
<ErrorDisplay /> <ErrorDisplay />
@ -27,9 +25,3 @@ export default function EditorRoutes() {
</div> </div>
); );
} }
return (
<main>
<h1>404</h1>
</main>
);
}

View File

@ -7,9 +7,8 @@ import CompletedInstances from './CompletedInstances';
import CreateNewInstance from './CreateNewInstance'; import CreateNewInstance from './CreateNewInstance';
import InProgressInstances from './InProgressInstances'; import InProgressInstances from './InProgressInstances';
import OnboardingView from './OnboardingView'; import OnboardingView from './OnboardingView';
import ErrorDisplay from '../components/ErrorDisplay';
export default function HomePageRoutes() { export default function HomeRoutes() {
const location = useLocation(); const location = useLocation();
const [selectedTabIndex, setSelectedTabIndex] = useState<number>(0); const [selectedTabIndex, setSelectedTabIndex] = useState<number>(0);
const navigate = useNavigate(); const navigate = useNavigate();
@ -49,8 +48,7 @@ export default function HomePageRoutes() {
}; };
return ( return (
<div className="fixed-width-container"> <>
<ErrorDisplay />
<OnboardingView /> <OnboardingView />
{renderTabs()} {renderTabs()}
<Routes> <Routes>
@ -61,6 +59,6 @@ export default function HomePageRoutes() {
<Route path="completed-instances" element={<CompletedInstances />} /> <Route path="completed-instances" element={<CompletedInstances />} />
<Route path="create-new-instance" element={<CreateNewInstance />} /> <Route path="create-new-instance" element={<CreateNewInstance />} />
</Routes> </Routes>
</div> </>
); );
} }

View File

@ -296,7 +296,7 @@ export default function JsonSchemaFormBuilder() {
const httpMethod = 'DELETE'; const httpMethod = 'DELETE';
const navigateToProcessModelShow = (_httpResult: any) => { const navigateToProcessModelShow = (_httpResult: any) => {
navigate(`/admin/process-models/${modifiedProcessModelId}`); navigate(`/process-models/${modifiedProcessModelId}`);
}; };
HttpService.makeCallToBackend({ HttpService.makeCallToBackend({
@ -339,7 +339,7 @@ export default function JsonSchemaFormBuilder() {
<Button <Button
onClick={() => onClick={() =>
navigate( navigate(
`/admin/process-models/${ `/process-models/${
params.process_model_id params.process_model_id
}/form/${searchParams.get('file_name')}` }/form/${searchParams.get('file_name')}`
) )

View File

@ -61,7 +61,7 @@ export default function MyTasks() {
onClose={() => setProcessInstance(null)} onClose={() => setProcessInstance(null)}
> >
<Link <Link
to={`/admin/process-instances/${modifyProcessIdentifierForPathParam( to={`/process-instances/${modifyProcessIdentifierForPathParam(
processInstance.process_model_identifier processInstance.process_model_identifier
)}/${processInstance.id}`} )}/${processInstance.id}`}
data-qa="process-instance-show-link" data-qa="process-instance-show-link"
@ -91,7 +91,7 @@ export default function MyTasks() {
<td> <td>
<Link <Link
data-qa="process-model-show-link" data-qa="process-model-show-link"
to={`/admin/process-models/${modifiedProcessModelIdentifier}`} to={`/process-models/${modifiedProcessModelIdentifier}`}
> >
{rowToUse.process_model_display_name} {rowToUse.process_model_display_name}
</Link> </Link>
@ -99,7 +99,7 @@ export default function MyTasks() {
<td> <td>
<Link <Link
data-qa="process-instance-show-link" data-qa="process-instance-show-link"
to={`/admin/process-instances/${modifiedProcessModelIdentifier}/${rowToUse.process_instance_id}`} to={`/process-instances/${modifiedProcessModelIdentifier}/${rowToUse.process_instance_id}`}
> >
{rowToUse.process_instance_id} {rowToUse.process_instance_id}
</Link> </Link>
@ -158,7 +158,7 @@ export default function MyTasks() {
<td> <td>
<Link <Link
data-qa="process-model-show-link" data-qa="process-model-show-link"
to={`/admin/process-models/${modifiedProcessModelId}`} to={`/process-models/${modifiedProcessModelId}`}
> >
{row.processModelDisplayName} {row.processModelDisplayName}
</Link> </Link>

View File

@ -0,0 +1,8 @@
export default function Page404() {
return (
<div>
<h1>404 not found</h1>
<p>This page does not exist. Please check the url.</p>
</div>
);
}

View File

@ -49,7 +49,7 @@ export default function ProcessGroupList() {
const processModelSearchOnChange = (selection: CarbonComboBoxSelection) => { const processModelSearchOnChange = (selection: CarbonComboBoxSelection) => {
const processModel = selection.selectedItem; const processModel = selection.selectedItem;
navigate( navigate(
`/admin/process-models/${modifyProcessIdentifierForPathParam( `/process-models/${modifyProcessIdentifierForPathParam(
processModel.id processModel.id
)}` )}`
); );
@ -68,7 +68,7 @@ export default function ProcessGroupList() {
<> <>
<ProcessBreadcrumb hotCrumbs={[['Process Groups']]} /> <ProcessBreadcrumb hotCrumbs={[['Process Groups']]} />
<Can I="POST" a={targetUris.processGroupListPath} ability={ability}> <Can I="POST" a={targetUris.processGroupListPath} ability={ability}>
<Button kind="secondary" href="/admin/process-groups/new"> <Button kind="secondary" href="/process-groups/new">
Add a process group Add a process group
</Button> </Button>
<br /> <br />

View File

@ -0,0 +1,16 @@
import { Route, Routes } from 'react-router-dom';
import ProcessGroupList from './ProcessGroupList';
import ProcessGroupShow from './ProcessGroupShow';
import ProcessGroupNew from './ProcessGroupNew';
import ProcessGroupEdit from './ProcessGroupEdit';
export default function ProcessGroupRoutes() {
return (
<Routes>
<Route path="/" element={<ProcessGroupList />} />
<Route path="/:process_group_id" element={<ProcessGroupShow />} />
<Route path="new" element={<ProcessGroupNew />} />
<Route path=":process_group_id/edit" element={<ProcessGroupEdit />} />
</Routes>
);
}

View File

@ -86,7 +86,7 @@ export default function ProcessGroupShow() {
// <tr key={row.id}> // <tr key={row.id}>
// <td> // <td>
// <Link // <Link
// to={`/admin/process-models/${modifiedProcessModelId}`} // to={`/process-models/${modifiedProcessModelId}`}
// data-qa="process-model-show-link" // data-qa="process-model-show-link"
// > // >
// {row.id} // {row.id}
@ -113,7 +113,7 @@ export default function ProcessGroupShow() {
// }; // };
const navigateToProcessGroups = (_result: any) => { const navigateToProcessGroups = (_result: any) => {
navigate(`/admin/process-groups`); navigate(`/process-groups`);
}; };
const deleteProcessGroup = () => { const deleteProcessGroup = () => {
@ -155,7 +155,7 @@ export default function ProcessGroupShow() {
renderIcon={Edit} renderIcon={Edit}
iconDescription="Edit Process Group" iconDescription="Edit Process Group"
hasIconOnly hasIconOnly
href={`/admin/process-groups/${modifiedProcessGroupId}/edit`} href={`/process-groups/${modifiedProcessGroupId}/edit`}
> >
Edit process group Edit process group
</Button> </Button>
@ -178,7 +178,7 @@ export default function ProcessGroupShow() {
<Stack orientation="horizontal" gap={3}> <Stack orientation="horizontal" gap={3}>
<Can I="POST" a={targetUris.processGroupListPath} ability={ability}> <Can I="POST" a={targetUris.processGroupListPath} ability={ability}>
<Button <Button
href={`/admin/process-groups/new?parentGroupId=${processGroup.id}`} href={`/process-groups/new?parentGroupId=${processGroup.id}`}
> >
Add a process group Add a process group
</Button> </Button>
@ -188,9 +188,7 @@ export default function ProcessGroupShow() {
a={targetUris.processModelCreatePath} a={targetUris.processModelCreatePath}
ability={ability} ability={ability}
> >
<Button <Button href={`/process-models/${modifiedProcessGroupId}/new`}>
href={`/admin/process-models/${modifiedProcessGroupId}/new`}
>
Add a process model Add a process model
</Button> </Button>
</Can> </Can>

View File

@ -16,7 +16,7 @@ export default function ProcessInstanceFindById() {
const handleProcessInstanceNavigation = (result: any) => { const handleProcessInstanceNavigation = (result: any) => {
const processInstance: ProcessInstance = result.process_instance; const processInstance: ProcessInstance = result.process_instance;
let path = '/admin/process-instances/'; let path = '/process-instances/';
if (result.uri_type === 'for-me') { if (result.uri_type === 'for-me') {
path += 'for-me/'; path += 'for-me/';
} }

View File

@ -23,11 +23,11 @@ export default function ProcessInstanceReportEdit() {
const [filterBy, setFilterBy] = useState(''); const [filterBy, setFilterBy] = useState('');
const navigateToProcessInstanceReport = (_result: any) => { const navigateToProcessInstanceReport = (_result: any) => {
navigate(`/admin/process-instances/reports/${params.report_identifier}`); navigate(`/process-instances/reports/${params.report_identifier}`);
}; };
const navigateToProcessInstanceReports = (_result: any) => { const navigateToProcessInstanceReports = (_result: any) => {
navigate(`/admin/process-instances/reports`); navigate(`/process-instances/reports`);
}; };
useEffect(() => { useEffect(() => {

View File

@ -31,7 +31,7 @@ export default function ProcessInstanceReportList() {
return ( return (
<tr key={(row as any).id}> <tr key={(row as any).id}>
<td> <td>
<Link to={`/admin/process-instances?report_id=${rowToUse.id}`}> <Link to={`/process-instances?report_id=${rowToUse.id}`}>
{rowToUse.identifier} {rowToUse.identifier}
</Link> </Link>
</td> </td>
@ -54,7 +54,7 @@ export default function ProcessInstanceReportList() {
<> <>
<h1>Process Instance Perspectives</h1> <h1>Process Instance Perspectives</h1>
<Can I="POST" a={targetUris.processInstanceListPath} ability={ability}> <Can I="POST" a={targetUris.processInstanceListPath} ability={ability}>
<Button href="/admin/process-instances/reports/new"> <Button href="/process-instances/reports/new">
Add a process instance perspective Add a process instance perspective
</Button> </Button>
</Can> </Can>

View File

@ -12,7 +12,7 @@ export default function ProcessInstanceReportNew() {
const [filterBy, setFilterBy] = useState(''); const [filterBy, setFilterBy] = useState('');
const navigateToNewProcessInstance = (_result: any) => { const navigateToNewProcessInstance = (_result: any) => {
navigate(`/admin/process-instances/reports/${identifier}`); navigate(`/process-instances/reports/${identifier}`);
}; };
const addProcessInstanceReport = (event: any) => { const addProcessInstanceReport = (event: any) => {

View File

@ -0,0 +1,49 @@
import { Route, Routes } from 'react-router-dom';
import ProcessInstanceList from './ProcessInstanceList';
import ProcessInstanceShow from './ProcessInstanceShow';
import ProcessInstanceReportList from './ProcessInstanceReportList';
import ProcessInstanceReportNew from './ProcessInstanceReportNew';
import ProcessInstanceReportEdit from './ProcessInstanceReportEdit';
import ProcessInstanceFindById from './ProcessInstanceFindById';
import ProcessInterstitialPage from './ProcessInterstitialPage';
export default function ProcessInstanceRoutes() {
return (
<Routes>
<Route path="/" element={<ProcessInstanceList variant="for-me" />} />
<Route path="for-me" element={<ProcessInstanceList variant="for-me" />} />
<Route path="all" element={<ProcessInstanceList variant="all" />} />
<Route
path="for-me/:process_model_id/:process_instance_id"
element={<ProcessInstanceShow variant="for-me" />}
/>
<Route
path="for-me/:process_model_id/:process_instance_id/:to_task_guid"
element={<ProcessInstanceShow variant="for-me" />}
/>
<Route
path="for-me/:process_model_id/:process_instance_id/interstitial"
element={<ProcessInterstitialPage variant="for-me" />}
/>
<Route
path=":process_model_id/:process_instance_id/interstitial"
element={<ProcessInterstitialPage variant="all" />}
/>
<Route
path=":process_model_id/:process_instance_id"
element={<ProcessInstanceShow variant="all" />}
/>
<Route
path=":process_model_id/:process_instance_id/:to_task_guid"
element={<ProcessInstanceShow variant="all" />}
/>
<Route path="reports" element={<ProcessInstanceReportList />} />
<Route path="reports/new" element={<ProcessInstanceReportNew />} />
<Route
path="reports/:report_identifier/edit"
element={<ProcessInstanceReportEdit />}
/>
<Route path="find-by-id" element={<ProcessInstanceFindById />} />
</Routes>
);
}

View File

@ -0,0 +1,31 @@
import { useEffect } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { modifyProcessIdentifierForPathParam } from '../helpers';
import HttpService from '../services/HttpService';
import { ProcessInstance } from '../interfaces';
export default function ProcessInstanceShortLink() {
const params = useParams();
const navigate = useNavigate();
useEffect(() => {
const handleProcessInstanceNavigation = (result: any) => {
const processInstance: ProcessInstance = result.process_instance;
let path = '/process-instances/';
if (result.uri_type === 'for-me') {
path += 'for-me/';
}
path += `${modifyProcessIdentifierForPathParam(
processInstance.process_model_identifier
)}/${processInstance.id}`;
navigate(path);
};
HttpService.makeCallToBackend({
path: `/process-instances/find-by-id/${params.process_instance_id}`,
successCallback: handleProcessInstanceNavigation,
});
}, [params.process_instance_id, navigate]);
return null;
}

View File

@ -21,6 +21,7 @@ import {
StopOutline, StopOutline,
TrashCan, TrashCan,
Warning, Warning,
Link as LinkIcon,
} from '@carbon/icons-react'; } from '@carbon/icons-react';
import { import {
Accordion, Accordion,
@ -117,6 +118,8 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
>(null); >(null);
const [selectedTabIndex, setSelectedTabIndex] = useState<number>(0); const [selectedTabIndex, setSelectedTabIndex] = useState<number>(0);
const [copiedShortLinkToClipboard, setCopiedShortLinkToClipboard] =
useState<boolean>(false);
const { addError, removeError } = useAPIError(); const { addError, removeError } = useAPIError();
const unModifiedProcessModelId = unModifyProcessIdentifierForPathParam( const unModifiedProcessModelId = unModifyProcessIdentifierForPathParam(
@ -151,12 +154,12 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
const navigateToProcessInstances = (_result: any) => { const navigateToProcessInstances = (_result: any) => {
navigate( navigate(
`/admin/process-instances?process_model_identifier=${unModifiedProcessModelId}` `/process-instances?process_model_identifier=${unModifiedProcessModelId}`
); );
}; };
let processInstanceShowPageBaseUrl = `/admin/process-instances/for-me/${params.process_model_id}/${params.process_instance_id}`; let processInstanceShowPageBaseUrl = `/process-instances/for-me/${params.process_model_id}/${params.process_instance_id}`;
const processInstanceShowPageBaseUrlAllVariant = `/admin/process-instances/${params.process_model_id}/${params.process_instance_id}`; const processInstanceShowPageBaseUrlAllVariant = `/process-instances/${params.process_model_id}/${params.process_instance_id}`;
if (variant === 'all') { if (variant === 'all') {
processInstanceShowPageBaseUrl = processInstanceShowPageBaseUrlAllVariant; processInstanceShowPageBaseUrl = processInstanceShowPageBaseUrlAllVariant;
} }
@ -427,7 +430,7 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
<dd> <dd>
<Link <Link
data-qa="go-to-current-diagram-process-model" data-qa="go-to-current-diagram-process-model"
to={`/admin/process-models/${modifyProcessIdentifierForPathParam( to={`/process-models/${modifyProcessIdentifierForPathParam(
processInstance.process_model_with_diagram_identifier || '' processInstance.process_model_with_diagram_identifier || ''
)}`} )}`}
> >
@ -475,6 +478,14 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
); );
}; };
const copyProcessInstanceShortLink = () => {
if (processInstance) {
const piShortLink = `${window.location.origin}/i/${processInstance.id}`;
navigator.clipboard.writeText(piShortLink);
setCopiedShortLinkToClipboard(true);
}
};
const terminateButton = () => { const terminateButton = () => {
if ( if (
processInstance && processInstance &&
@ -518,6 +529,19 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
return <div />; return <div />;
}; };
const copyProcessInstanceShortLinkButton = () => {
return (
<Button
onClick={copyProcessInstanceShortLink}
kind="ghost"
renderIcon={LinkIcon}
iconDescription="Copy short link for sharing"
hasIconOnly
size="lg"
/>
);
};
const resumeButton = () => { const resumeButton = () => {
if (processInstance && processInstance.status === 'suspended') { if (processInstance && processInstance.status === 'suspended') {
return ( return (
@ -1314,6 +1338,7 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
return null; return null;
} }
const elements = []; const elements = [];
elements.push(copyProcessInstanceShortLinkButton());
if (ability.can('POST', `${targetUris.processInstanceTerminatePath}`)) { if (ability.can('POST', `${targetUris.processInstanceTerminatePath}`)) {
elements.push(terminateButton()); elements.push(terminateButton());
} }
@ -1326,6 +1351,20 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
if (ability.can('DELETE', targetUris.processInstanceActionPath)) { if (ability.can('DELETE', targetUris.processInstanceActionPath)) {
elements.push(deleteButton()); elements.push(deleteButton());
} }
let toast = null;
if (copiedShortLinkToClipboard) {
toast = (
<Notification
onClose={() => setCopiedShortLinkToClipboard(false)}
type="success"
title="Copied link to clipboard"
timeout={3000}
hideCloseButton
withBottomMargin={false}
/>
);
elements.push(toast);
}
return elements; return elements;
}; };

View File

@ -10,9 +10,9 @@ type OwnProps = {
export default function ProcessInterstitialPage({ variant }: OwnProps) { export default function ProcessInterstitialPage({ variant }: OwnProps) {
const params = useParams(); const params = useParams();
let processInstanceShowPageUrl = `/admin/process-instances/for-me/${params.process_model_id}/${params.process_instance_id}`; let processInstanceShowPageUrl = `/process-instances/for-me/${params.process_model_id}/${params.process_instance_id}`;
if (variant === 'all') { if (variant === 'all') {
processInstanceShowPageUrl = `/admin/process-instances/${params.process_model_id}/${params.process_instance_id}`; processInstanceShowPageUrl = `/process-instances/${params.process_model_id}/${params.process_instance_id}`;
} }
return ( return (

View File

@ -266,7 +266,7 @@ export default function ProcessModelEditDiagram() {
const httpMethod = 'DELETE'; const httpMethod = 'DELETE';
const navigateToProcessModelShow = (_httpResult: any) => { const navigateToProcessModelShow = (_httpResult: any) => {
navigate(`/admin/process-models/${modifiedProcessModelId}`); navigate(`/process-models/${modifiedProcessModelId}`);
}; };
HttpService.makeCallToBackend({ HttpService.makeCallToBackend({
path: url, path: url,

View File

@ -20,7 +20,7 @@ export default function ProcessModelNewExperimental() {
if ('id' in result) { if ('id' in result) {
const modifiedProcessModelPathFromResult = const modifiedProcessModelPathFromResult =
modifyProcessIdentifierForPathParam(result.id); modifyProcessIdentifierForPathParam(result.id);
navigate(`/admin/process-models/${modifiedProcessModelPathFromResult}`); navigate(`/process-models/${modifiedProcessModelPathFromResult}`);
} }
}; };

View File

@ -0,0 +1,30 @@
import { Route, Routes } from 'react-router-dom';
import JsonSchemaFormBuilder from './JsonSchemaFormBuilder';
import ProcessModelEdit from './ProcessModelEdit';
import ProcessModelNew from './ProcessModelNew';
import ProcessModelNewExperimental from './ProcessModelNewExperimental';
import ProcessModelShow from './ProcessModelShow';
import ReactFormEditor from './ReactFormEditor';
export default function ProcessModelRoutes() {
return (
<Routes>
<Route path=":process_group_id/new" element={<ProcessModelNew />} />
<Route
path=":process_group_id/new-e"
element={<ProcessModelNewExperimental />}
/>
<Route path=":process_model_id" element={<ProcessModelShow />} />
<Route path=":process_model_id/edit" element={<ProcessModelEdit />} />
<Route path=":process_model_id/form" element={<ReactFormEditor />} />
<Route
path=":process_model_id/form/:file_name"
element={<ReactFormEditor />}
/>
<Route
path=":process_model_id/form-builder"
element={<JsonSchemaFormBuilder />}
/>
</Routes>
);
}

View File

@ -128,7 +128,7 @@ export default function ProcessModelShow() {
onClose={() => setProcessInstance(null)} onClose={() => setProcessInstance(null)}
> >
<Link <Link
to={`/admin/process-instances/for-me/${modifiedProcessModelId}/${processInstance.id}`} to={`/process-instances/for-me/${modifiedProcessModelId}/${processInstance.id}`}
data-qa="process-instance-show-link" data-qa="process-instance-show-link"
> >
view view
@ -211,7 +211,7 @@ export default function ProcessModelShow() {
return `/editor/process-models/${modifiedProcessModelId}/files/${processModelFile.name}`; return `/editor/process-models/${modifiedProcessModelId}/files/${processModelFile.name}`;
} }
if (processModelFile.name.match(/\.(json|md)$/)) { if (processModelFile.name.match(/\.(json|md)$/)) {
return `/admin/process-models/${modifiedProcessModelId}/form/${processModelFile.name}`; return `/process-models/${modifiedProcessModelId}/form/${processModelFile.name}`;
} }
} }
return null; return null;
@ -219,9 +219,7 @@ export default function ProcessModelShow() {
const navigateToProcessModels = (_result: any) => { const navigateToProcessModels = (_result: any) => {
navigate( navigate(
`/admin/process-groups/${getGroupFromModifiedModelId( `/process-groups/${getGroupFromModifiedModelId(modifiedProcessModelId)}`
modifiedProcessModelId
)}`
); );
}; };
@ -561,11 +559,11 @@ export default function ProcessModelShow() {
); );
} else if (a.selectedItem.text === 'New JSON File') { } else if (a.selectedItem.text === 'New JSON File') {
navigate( navigate(
`/admin/process-models/${modifiedProcessModelId}/form?file_ext=json` `/process-models/${modifiedProcessModelId}/form?file_ext=json`
); );
} else if (a.selectedItem.text === 'New Markdown File') { } else if (a.selectedItem.text === 'New Markdown File') {
navigate( navigate(
`/admin/process-models/${modifiedProcessModelId}/form?file_ext=md` `/process-models/${modifiedProcessModelId}/form?file_ext=md`
); );
} else { } else {
console.log('a.selectedItem.text', a.selectedItem.text); console.log('a.selectedItem.text', a.selectedItem.text);
@ -760,7 +758,7 @@ export default function ProcessModelShow() {
renderIcon={Edit} renderIcon={Edit}
iconDescription="Edit Process Model" iconDescription="Edit Process Model"
hasIconOnly hasIconOnly
href={`/admin/process-models/${modifiedProcessModelId}/edit`} href={`/process-models/${modifiedProcessModelId}/edit`}
/> />
</Can> </Can>
<Can I="DELETE" a={targetUris.processModelShowPath} ability={ability}> <Can I="DELETE" a={targetUris.processModelShowPath} ability={ability}>

View File

@ -111,7 +111,7 @@ export default function ReactFormEditor() {
const fileNameWithExtension = const fileNameWithExtension =
defaultFileName ?? `${newFileName}.${fileExtension}`; defaultFileName ?? `${newFileName}.${fileExtension}`;
navigate( navigate(
`/admin/process-models/${modifiedProcessModelId}/form/${fileNameWithExtension}` `/process-models/${modifiedProcessModelId}/form/${fileNameWithExtension}`
); );
} }
}; };
@ -160,7 +160,7 @@ export default function ReactFormEditor() {
const httpMethod = 'DELETE'; const httpMethod = 'DELETE';
const navigateToProcessModelShow = (_httpResult: any) => { const navigateToProcessModelShow = (_httpResult: any) => {
navigate(`/admin/process-models/${modifiedProcessModelId}`); navigate(`/process-models/${modifiedProcessModelId}`);
}; };
HttpService.makeCallToBackend({ HttpService.makeCallToBackend({
@ -308,7 +308,7 @@ export default function ReactFormEditor() {
<Button <Button
onClick={() => onClick={() =>
navigate( navigate(
`/admin/process-models/${params.process_model_id}/form-builder${formBuildFileParam}` `/process-models/${params.process_model_id}/form-builder${formBuildFileParam}`
) )
} }
variant="danger" variant="danger"

View File

@ -36,7 +36,7 @@ export default function SecretList() {
!ability.can('GET', targetUris.secretListPath) && !ability.can('GET', targetUris.secretListPath) &&
ability.can('GET', targetUris.authenticationListPath) ability.can('GET', targetUris.authenticationListPath)
) { ) {
navigate('/admin/configuration/authentications'); navigate('/configuration/authentications');
} else { } else {
const { page, perPage } = getPageInfoFromSearchParams(searchParams); const { page, perPage } = getPageInfoFromSearchParams(searchParams);
HttpService.makeCallToBackend({ HttpService.makeCallToBackend({
@ -71,12 +71,12 @@ export default function SecretList() {
return ( return (
<tr key={(row as any).key}> <tr key={(row as any).key}>
<td> <td>
<Link to={`/admin/configuration/secrets/${(row as any).key}`}> <Link to={`/configuration/secrets/${(row as any).key}`}>
{(row as any).id} {(row as any).id}
</Link> </Link>
</td> </td>
<td> <td>
<Link to={`/admin/configuration/secrets/${(row as any).key}`}> <Link to={`/configuration/secrets/${(row as any).key}`}>
{(row as any).key} {(row as any).key}
</Link> </Link>
</td> </td>
@ -125,7 +125,7 @@ export default function SecretList() {
<div> <div>
<h1>Secrets</h1> <h1>Secrets</h1>
{SecretsDisplayArea()} {SecretsDisplayArea()}
<Button href="/admin/configuration/secrets/new">Add a secret</Button> <Button href="/configuration/secrets/new">Add a secret</Button>
</div> </div>
); );
} }

View File

@ -11,11 +11,11 @@ export default function SecretNew() {
const navigate = useNavigate(); const navigate = useNavigate();
const navigateToSecret = (_result: any) => { const navigateToSecret = (_result: any) => {
navigate(`/admin/configuration/secrets/${key}`); navigate(`/configuration/secrets/${key}`);
}; };
const navigateToSecrets = () => { const navigateToSecrets = () => {
navigate(`/admin/configuration/secrets`); navigate(`/configuration/secrets`);
}; };
const addSecret = (event: any) => { const addSecret = (event: any) => {

View File

@ -46,7 +46,7 @@ export default function SecretShow() {
}; };
const navigateToSecrets = (_result: any) => { const navigateToSecrets = (_result: any) => {
navigate(`/admin/configuration/secrets`); navigate(`/configuration/secrets`);
}; };
const deleteSecret = () => { const deleteSecret = () => {

View File

@ -54,7 +54,7 @@ export default function TaskShow() {
setGuestConfirmationText('Thank you!'); setGuestConfirmationText('Thank you!');
} else { } else {
navigate( navigate(
`/admin/process-instances/for-me/${modifyProcessIdentifierForPathParam( `/process-instances/for-me/${modifyProcessIdentifierForPathParam(
myTask.process_model_identifier myTask.process_model_identifier
)}/${myTask.process_instance_id}/interstitial` )}/${myTask.process_instance_id}/interstitial`
); );
@ -378,7 +378,7 @@ export default function TaskShow() {
hotCrumbs={[ hotCrumbs={[
[ [
`Process Instance Id: ${params.process_instance_id}`, `Process Instance Id: ${params.process_instance_id}`,
`/admin/process-instances/for-me/${modifyProcessIdentifierForPathParam( `/process-instances/for-me/${modifyProcessIdentifierForPathParam(
basicTask.process_model_identifier basicTask.process_model_identifier
)}/${params.process_instance_id}`, )}/${params.process_instance_id}`,
], ],

View File

@ -115,10 +115,6 @@ const loginIfNeeded = () => {
} }
}; };
const hasRole = (_roles: string[]): boolean => {
return isLoggedIn();
};
const UserService = { const UserService = {
authenticationDisabled, authenticationDisabled,
doLogin, doLogin,
@ -126,7 +122,6 @@ const UserService = {
getAccessToken, getAccessToken,
getPreferredUsername, getPreferredUsername,
getUserEmail, getUserEmail,
hasRole,
isLoggedIn, isLoggedIn,
loginIfNeeded, loginIfNeeded,
onlyGuestTaskCompletion, onlyGuestTaskCompletion,