Squashed 'spiffworkflow-frontend/' changes from 790d267b..bf2d2a22

bf2d2a22 added authentication table w/ burnettk
d9d8f364 added an authentications route and updated error message to show sentry links w/ burnettk

git-subtree-dir: spiffworkflow-frontend
git-subtree-split: bf2d2a22430da355be261d864e3b507866deafdf
This commit is contained in:
jasquat 2022-10-18 16:41:13 -04:00
parent 8d86fe710d
commit 75729ba3df
12 changed files with 120 additions and 40 deletions

View File

@ -10,9 +10,12 @@ import TaskShow from './routes/TaskShow';
import ErrorBoundary from './components/ErrorBoundary';
import AdminRoutes from './routes/AdminRoutes';
import SubNavigation from './components/SubNavigation';
import { ErrorForDisplay } from './interfaces';
export default function App() {
const [errorMessage, setErrorMessage] = useState('');
const [errorMessage, setErrorMessage] = useState<ErrorForDisplay | null>(
null
);
const errorContextValueArray = useMemo(
() => [errorMessage, setErrorMessage],
@ -20,10 +23,24 @@ export default function App() {
);
let errorTag = null;
if (errorMessage !== '') {
if (errorMessage) {
let sentryLinkTag = null;
if (errorMessage.sentry_link) {
sentryLinkTag = (
<span>
{
': Find details about this error here (it may take a moment to become available): '
}
<a href={errorMessage.sentry_link} target="_blank" rel="noreferrer">
{errorMessage.sentry_link}
</a>
</span>
);
}
errorTag = (
<div id="filter-errors" className="mt-4 alert alert-danger" role="alert">
{errorMessage}
{errorMessage.message}
{sentryLinkTag}
</div>
);
}

View File

@ -1,34 +1,10 @@
import { Button, Navbar, Nav, Container } from 'react-bootstrap';
// import { capitalizeFirstLetter } from '../helpers';
// @ts-expect-error TS(2307) FIXME: Cannot find module '../logo.svg' or its correspond... Remove this comment to see the full error message
import logo from '../logo.svg';
import UserService from '../services/UserService';
// for ref: https://react-bootstrap.github.io/components/navbar/
export default function NavigationBar() {
// const navItems: string[] = [];
// if (UserService.hasRole(['admin'])) {
// navItems.push('/admin');
// }
// navItems.push('/tasks');
//
// const navElements = navItems.map((navItem) => {
// let className = '';
// if (window.location.pathname.startsWith(navItem)) {
// className = 'active';
// }
// const navItemWithoutSlash = navItem.replace(/\/*/, '');
// const title = capitalizeFirstLetter(navItemWithoutSlash);
// return (
// <Nav.Link
// href={navItem}
// className={className}
// data-qa={`nav-item-${navItemWithoutSlash}`}
// >
// {title}
// </Nav.Link>
// );
// });
const navElements = null;
const handleLogout = () => {

View File

@ -14,6 +14,8 @@ export default function SubNavigation() {
newActiveKey = '/admin/process-instances';
} else if (location.pathname.match(/^\/admin\/secrets\b/)) {
newActiveKey = '/admin/secrets';
} else if (location.pathname.match(/^\/admin\/authentications\b/)) {
newActiveKey = '/admin/authentications';
} else if (location.pathname === '/') {
newActiveKey = '/';
} else if (location.pathname.match(/^\/tasks\b/)) {
@ -40,6 +42,9 @@ export default function SubNavigation() {
<Nav.Item>
<Nav.Link href="/admin/secrets">Secrets</Nav.Link>
</Nav.Item>
<Nav.Item>
<Nav.Link href="/admin/authentications">Authentications</Nav.Link>
</Nav.Item>
</Nav>
);
}

View File

@ -25,3 +25,19 @@ export interface ProcessModel {
// tuple of display value and URL
export type BreadcrumbItem = [displayValue: string, url?: string];
export interface ErrorForDisplay {
message: string;
sentry_link?: string;
}
export interface AuthenticationParam {
id: string;
type: string;
required: boolean;
}
export interface AuthenticationItem {
id: string;
parameters: AuthenticationParam[];
}

View File

@ -23,13 +23,14 @@ import MessageInstanceList from './MessageInstanceList';
import SecretList from './SecretList';
import SecretNew from './SecretNew';
import SecretShow from './SecretShow';
import AuthenticationList from './AuthenticationList';
export default function AdminRoutes() {
const location = useLocation();
const setErrorMessage = (useContext as any)(ErrorContext)[1];
useEffect(() => {
setErrorMessage('');
setErrorMessage(null);
}, [location, setErrorMessage]);
if (UserService.hasRole(['admin'])) {
@ -108,6 +109,7 @@ export default function AdminRoutes() {
<Route path="secrets" element={<SecretList />} />
<Route path="secrets/new" element={<SecretNew />} />
<Route path="secrets/:key" element={<SecretShow />} />
<Route path="authentications" element={<AuthenticationList />} />
</Routes>
);
}

View File

@ -0,0 +1,64 @@
import { useContext, useEffect, useState } from 'react';
import { Table } from 'react-bootstrap';
import ErrorContext from '../contexts/ErrorContext';
import { AuthenticationItem } from '../interfaces';
import HttpService from '../services/HttpService';
export default function AuthenticationList() {
const setErrorMessage = (useContext as any)(ErrorContext)[1];
const [authenticationList, setAuthenticationList] = useState<
AuthenticationItem[] | null
>(null);
const [connectProxyBaseUrl, setConnectProxyBaseUrl] = useState<string | null>(
null
);
useEffect(() => {
const processResult = (result: any) => {
setAuthenticationList(result.results);
setConnectProxyBaseUrl(result.connector_proxy_base_url);
};
HttpService.makeCallToBackend({
path: `/authentications`,
successCallback: processResult,
failureCallback: setErrorMessage,
});
}, [setErrorMessage]);
const buildTable = () => {
if (authenticationList) {
const rows = authenticationList.map((row) => {
return (
<tr key={row.id}>
<td>
<a
data-qa="authentication-create-link"
href={`${connectProxyBaseUrl}/v1/auths/${row.id}`}
>
{row.id}
</a>
</td>
</tr>
);
});
return (
<Table striped bordered>
<thead>
<tr>
<th>ID</th>
</tr>
</thead>
<tbody>{rows}</tbody>
</Table>
);
}
return null;
};
if (authenticationList) {
return <>{buildTable()}</>;
}
return <main />;
}

View File

@ -176,19 +176,19 @@ export default function ProcessInstanceList() {
let queryParamString = `per_page=${perPage}&page=${page}`;
if (isTrueComparison(startFrom, '>', startTill)) {
setErrorMessage('startFrom cannot be after startTill');
setErrorMessage({ message: 'startFrom cannot be after startTill' });
return;
}
if (isTrueComparison(endFrom, '>', endTill)) {
setErrorMessage('endFrom cannot be after endTill');
setErrorMessage({ message: 'endFrom cannot be after endTill' });
return;
}
if (isTrueComparison(startFrom, '>', endFrom)) {
setErrorMessage('startFrom cannot be after endFrom');
setErrorMessage({ message: 'startFrom cannot be after endFrom' });
return;
}
if (isTrueComparison(startTill, '>', endTill)) {
setErrorMessage('startTill cannot be after endTill');
setErrorMessage({ message: 'startTill cannot be after endTill' });
return;
}
@ -217,7 +217,7 @@ export default function ProcessInstanceList() {
queryParamString += `&process_group_identifier=${currentProcessModel.process_group_id}&process_model_identifier=${currentProcessModel.id}`;
}
setErrorMessage('');
setErrorMessage(null);
navigate(`/admin/process-instances?${queryParamString}`);
};

View File

@ -49,7 +49,7 @@ export default function ProcessModelEdit() {
};
const deleteProcessModel = () => {
setErrorMessage('');
setErrorMessage(null);
const processModelToUse = processModel as any;
const processModelShowPath = `/process-models/${processModelToUse.process_group_id}/${processModelToUse.id}`;
HttpService.makeCallToBackend({

View File

@ -112,7 +112,7 @@ export default function ProcessModelEditDiagram() {
};
const saveDiagram = (bpmnXML: any, fileName = params.file_name) => {
setErrorMessage('');
setErrorMessage(null);
setBpmnXmlForDiagramRendering(bpmnXML);
let url = `/process-models/${params.process_group_id}/${params.process_model_id}/files`;

View File

@ -82,7 +82,7 @@ export default function ProcessModelShow() {
}, [params, reloadModel]);
const processModelRun = (processInstance: any) => {
setErrorMessage('');
setErrorMessage(null);
HttpService.makeCallToBackend({
path: `/process-models/${params.process_group_id}/${params.process_model_id}/process-instances/${processInstance.id}/run`,
successCallback: setProcessInstanceResult,

View File

@ -29,7 +29,7 @@ export default function TaskShow() {
}, [params, setErrorMessage]);
const processSubmitResult = (result: any) => {
setErrorMessage('');
setErrorMessage(null);
if (result.ok) {
navigate(`/tasks`);
} else if (result.process_instance_id) {
@ -40,7 +40,7 @@ export default function TaskShow() {
};
const handleFormSubmit = (event: any) => {
setErrorMessage('');
setErrorMessage(null);
const dataToSubmit = event.formData;
delete dataToSubmit.isManualTask;
HttpService.makeCallToBackend({

View File

@ -94,7 +94,7 @@ backendCallProps) => {
message = result.message;
}
if (failureCallback) {
failureCallback(message);
failureCallback(result);
} else {
console.error(message);
// eslint-disable-next-line no-alert
@ -105,7 +105,7 @@ backendCallProps) => {
.catch((error) => {
if (error.name !== 'UnauthenticatedError') {
if (failureCallback) {
failureCallback(error.message);
failureCallback(error);
} else {
console.error(error.message);
}