process instance list page is mostly set up now w/ burnettk

This commit is contained in:
jasquat 2022-11-01 16:26:24 -04:00
parent 9bbf40a985
commit 3f8da5a568
11 changed files with 263 additions and 275 deletions

View File

@ -12,6 +12,7 @@
"@babel/plugin-transform-react-jsx": "^7.18.6", "@babel/plugin-transform-react-jsx": "^7.18.6",
"@babel/preset-react": "^7.18.6", "@babel/preset-react": "^7.18.6",
"@carbon/react": "^1.16.0", "@carbon/react": "^1.16.0",
"@carbon/styles": "^1.16.0",
"@ginkgo-bioworks/react-json-schema-form-builder": "^2.9.0", "@ginkgo-bioworks/react-json-schema-form-builder": "^2.9.0",
"@monaco-editor/react": "^4.4.5", "@monaco-editor/react": "^4.4.5",
"@rjsf/core": "^4.2.0", "@rjsf/core": "^4.2.0",

View File

@ -7,6 +7,7 @@
"@babel/plugin-transform-react-jsx": "^7.18.6", "@babel/plugin-transform-react-jsx": "^7.18.6",
"@babel/preset-react": "^7.18.6", "@babel/preset-react": "^7.18.6",
"@carbon/react": "^1.16.0", "@carbon/react": "^1.16.0",
"@carbon/styles": "^1.16.0",
"@ginkgo-bioworks/react-json-schema-form-builder": "^2.9.0", "@ginkgo-bioworks/react-json-schema-form-builder": "^2.9.0",
"@monaco-editor/react": "^4.4.5", "@monaco-editor/react": "^4.4.5",
"@rjsf/core": "^4.2.0", "@rjsf/core": "^4.2.0",

View File

@ -54,7 +54,6 @@ export default function App() {
<Content> <Content>
{errorTag} {errorTag}
<ErrorBoundary> <ErrorBoundary>
<main style={{ padding: '1rem 0' }}>
<Routes> <Routes>
<Route path="/" element={<HomePage />} /> <Route path="/" element={<HomePage />} />
<Route path="/tasks" element={<HomePage />} /> <Route path="/tasks" element={<HomePage />} />
@ -68,7 +67,6 @@ export default function App() {
element={<TaskShow />} element={<TaskShow />}
/> />
</Routes> </Routes>
</main>
</ErrorBoundary> </ErrorBoundary>
</Content> </Content>
</BrowserRouter> </BrowserRouter>

View File

@ -2,14 +2,12 @@ import {
Header, Header,
Theme, Theme,
HeaderName, HeaderName,
HeaderContainer,
HeaderNavigation, HeaderNavigation,
HeaderMenuItem, HeaderMenuItem,
HeaderMenu, HeaderMenu,
// @ts-ignore // @ts-ignore
} from '@carbon/react'; } from '@carbon/react';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { Navbar, Nav } from 'react-bootstrap';
import { useLocation } from 'react-router-dom'; import { useLocation } from 'react-router-dom';
// @ts-expect-error TS(2307) FIXME: Cannot find module '../logo.svg' or its correspond... Remove this comment to see the full error message // @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 logo from '../logo.svg';
@ -89,44 +87,9 @@ export default function NavigationBar() {
return activeKey === menuItemPath; return activeKey === menuItemPath;
}; };
// return (
// <Theme theme="g100">
// <Navbar bg="dark" expand="lg" variant="dark">
// <Content>
// <Navbar.Brand data-qa="spiffworkflow-logo" href="/admin">
// <img src={logo} className="app-logo" alt="logo" />
// </Navbar.Brand>
// <Navbar.Toggle aria-controls="basic-navbar-nav" />
// <Navbar.Collapse id="basic-navbar-nav">
// <Nav className="me-auto">{navElements}</Nav>
// </Navbar.Collapse>
// {loginLink()}
// {logoutLink()}
// </Content>
// </Navbar>
// </Theme>
// );
// <Theme theme="g100">
// <Header aria-label="IBM Platform Name">
// <HeaderName href="#" prefix="IBM">
// [Platform]
// </HeaderName>
// <HeaderNavigation aria-label="IBM [Platform]">
// <HeaderMenuItem href="#">Link 1</HeaderMenuItem>
// <HeaderMenuItem href="#">Link 2</HeaderMenuItem>
// <HeaderMenuItem href="#">Link 3</HeaderMenuItem>
// <HeaderMenu aria-label="Link 4" menuLinkName="Link 4">
// <HeaderMenuItem href="#">Sub-link 1</HeaderMenuItem>
// <HeaderMenuItem href="#">Sub-link 2</HeaderMenuItem>
// <HeaderMenuItem href="#">Sub-link 3</HeaderMenuItem>
// </HeaderMenu>
// </HeaderNavigation>
// </Header>
// </Theme>
if (activeKey) { if (activeKey) {
return ( return (
<HeaderContainer> <div className="spiffworkflow-header-container">
<Theme theme="g100"> <Theme theme="g100">
<Header aria-label="Spiffworkflow"> <Header aria-label="Spiffworkflow">
<HeaderName href="/" prefix=""> <HeaderName href="/" prefix="">
@ -151,7 +114,7 @@ export default function NavigationBar() {
</HeaderNavigation> </HeaderNavigation>
</Header> </Header>
</Theme> </Theme>
</HeaderContainer> </div>
); );
} }
return null; return null;

View File

@ -1,8 +1,9 @@
import React from 'react'; import React from 'react';
import { Link } from 'react-router-dom'; import { Link, useNavigate } from 'react-router-dom';
// @ts-ignore // @ts-ignore
import { Dropdown, Stack } from '@carbon/react'; import { Dropdown, Stack, Pagination } from '@carbon/react';
import { PaginationObject } from '../interfaces';
export const DEFAULT_PER_PAGE = 50; export const DEFAULT_PER_PAGE = 50;
export const DEFAULT_PAGE = 1; export const DEFAULT_PAGE = 1;
@ -11,9 +12,7 @@ type OwnProps = {
page: number; page: number;
perPage: number; perPage: number;
perPageOptions?: number[]; perPageOptions?: number[];
pagination: { pagination: PaginationObject | null;
[key: string]: number;
};
tableToDisplay: any; tableToDisplay: any;
queryParamString?: string; queryParamString?: string;
path: string; path: string;
@ -29,141 +28,28 @@ export default function PaginationForTable({
path, path,
}: OwnProps) { }: OwnProps) {
const PER_PAGE_OPTIONS = [2, 10, 50, 100]; const PER_PAGE_OPTIONS = [2, 10, 50, 100];
const navigate = useNavigate();
// const buildPerPageDropdown = () => { const updateRows = (args: any) => {
// const perPageDropdownRows = (perPageOptions || PER_PAGE_OPTIONS).map( const newPage = args.page;
// (perPageOption) => { const { pageSize } = args;
// if (perPageOption === perPage) { navigate(`${path}?page=${newPage}&per_page=${pageSize}${queryParamString}`);
// return ( };
// <Dropdown.Item
// key={perPageOption} return (
// href={`${path}?page=1&per_page=${perPageOption}`} <>
// active {tableToDisplay}
// > <Pagination
// {perPageOption} backwardText="Previous page"
// </Dropdown.Item> forwardText="Next page"
// ); itemsPerPageText="Items per page:"
// } page={page}
// return ( pageNumberText="Page Number"
// <Dropdown.Item pageSize={perPage}
// key={perPageOption} pageSizes={perPageOptions || PER_PAGE_OPTIONS}
// href={`${path}?page=1&per_page=${perPageOption}`} totalItems={(pagination as any).total}
// > onChange={updateRows}
// {perPageOption} />
// </Dropdown.Item> </>
// ); );
// }
// );
// return (
// <Stack direction="horizontal" gap={3}>
// <Dropdown className="ms-auto" id="pagination-page-dropdown">
// <Dropdown.Toggle
// id="process-instances-per-page"
// variant="light border"
// >
// Number to show: {perPage}
// </Dropdown.Toggle>
//
// <Dropdown.Menu variant="light">{perPageDropdownRows}</Dropdown.Menu>
// </Dropdown>
// </Stack>
// );
// };
//
// const buildPaginationNav = () => {
// let previousPageTag = null;
// if (page === 1) {
// previousPageTag = (
// <li
// data-qa="pagination-previous-button-inactive"
// className="page-item disabled"
// key="previous"
// >
// <span style={{ fontSize: '1.5em' }} className="page-link">
// &laquo;
// </span>
// </li>
// );
// } else {
// previousPageTag = (
// <li className="page-item" key="previous">
// <Link
// data-qa="pagination-previous-button"
// className="page-link"
// style={{ fontSize: '1.5em' }}
// to={`${path}?page=${
// page - 1
// }&per_page=${perPage}${queryParamString}`}
// >
// &laquo;
// </Link>
// </li>
// );
// }
//
// let nextPageTag = null;
// if (page >= pagination.pages) {
// nextPageTag = (
// <li
// data-qa="pagination-next-button-inactive"
// className="page-item disabled"
// key="next"
// >
// <span style={{ fontSize: '1.5em' }} className="page-link">
// &raquo;
// </span>
// </li>
// );
// } else {
// nextPageTag = (
// <li className="page-item" key="next">
// <Link
// data-qa="pagination-next-button"
// className="page-link"
// style={{ fontSize: '1.5em' }}
// to={`${path}?page=${
// page + 1
// }&per_page=${perPage}${queryParamString}`}
// >
// &raquo;
// </Link>
// </li>
// );
// }
//
// let startingNumber = (page - 1) * perPage + 1;
// let endingNumber = page * perPage;
// if (endingNumber > pagination.total) {
// endingNumber = pagination.total;
// }
// if (startingNumber > pagination.total) {
// startingNumber = pagination.total;
// }
//
// return (
// <Stack direction="horizontal" gap={3}>
// <p className="ms-auto">
// {startingNumber}-{endingNumber} of{' '}
// <span data-qa="total-paginated-items">{pagination.total}</span>
// </p>
// <nav aria-label="Page navigation">
// <div>
// <ul className="pagination">
// {previousPageTag}
// {nextPageTag}
// </ul>
// </div>
// </nav>
// </Stack>
// );
// };
//
// return (
// <main>
// {buildPaginationNav()}
// {tableToDisplay}
// {buildPerPageDropdown()}
// </main>
// );
return null;
} }

View File

@ -90,3 +90,11 @@ export const getProcessModelFullIdentifierFromSearchParams = (
} }
return processModelFullIdentifier; return processModelFullIdentifier;
}; };
// https://stackoverflow.com/a/71352046/6090676
export const truncateString = (text: string, len: number) => {
if (text.length > len && text.length > 0) {
return `${text.split(' ').slice(0, len).join(' ')} ...`;
}
return text;
};

View File

@ -26,6 +26,10 @@ span.bjs-crumb {
margin-bottom: 1em; margin-bottom: 1em;
} }
.spiffworkflow-header-container {
margin-bottom: 2em;
}
.active-task-highlight:not(.djs-connection) .djs-visual > :nth-child(1) { .active-task-highlight:not(.djs-connection) .djs-visual > :nth-child(1) {
fill: yellow !important; fill: yellow !important;
@ -44,6 +48,17 @@ span.bjs-crumb {
margin:auto; margin:auto;
} }
.cds--btn.button-white-background {
color: #393939;
background: #FFFFFF;
background-blend-mode: multiply;
border: 1px solid #393939;
}
.with-bottom-margin {
margin-bottom: 1em;
}
.diagram-viewer-canvas { .diagram-viewer-canvas {
border:1px solid #000000; border:1px solid #000000;
height:70vh; height:70vh;

View File

@ -1,2 +1,3 @@
@use '@carbon/react'; @use '@carbon/react';
@use '@carbon/styles';
// @include grid.flex-grid();

View File

@ -41,3 +41,9 @@ export interface AuthenticationItem {
id: string; id: string;
parameters: AuthenticationParam[]; parameters: AuthenticationParam[];
} }
export interface PaginationObject {
count: number;
total: number;
pages: number;
}

View File

@ -54,7 +54,7 @@ export default function HomePage() {
data-qa="process-instance-show-link" data-qa="process-instance-show-link"
to={`/admin/process-models/${rowToUse.process_group_identifier}/${rowToUse.process_model_identifier}/process-instances/${rowToUse.process_instance_id}`} to={`/admin/process-models/${rowToUse.process_group_identifier}/${rowToUse.process_model_identifier}/process-instances/${rowToUse.process_instance_id}`}
> >
View View {rowToUse.process_instance_id}
</Link> </Link>
</td> </td>
<td <td

View File

@ -6,8 +6,20 @@ import {
useSearchParams, useSearchParams,
} from 'react-router-dom'; } from 'react-router-dom';
import {
Button,
ButtonSet,
Table,
Stack,
Form,
ComboBox,
Grid,
Column,
FlexGrid,
Row,
MultiSelect,
// @ts-ignore // @ts-ignore
import { Button, Table, Stack, Form } from '@carbon/react'; } from '@carbon/react';
import { InputGroup } from 'react-bootstrap'; import { InputGroup } from 'react-bootstrap';
// @ts-expect-error TS(7016) FIXME: Could not find a declaration file for module 'reac... Remove this comment to see the full error message // @ts-expect-error TS(7016) FIXME: Could not find a declaration file for module 'reac... Remove this comment to see the full error message
import DatePicker from 'react-datepicker'; import DatePicker from 'react-datepicker';
@ -19,6 +31,7 @@ import {
convertSecondsToFormattedDate, convertSecondsToFormattedDate,
getPageInfoFromSearchParams, getPageInfoFromSearchParams,
getProcessModelFullIdentifierFromSearchParams, getProcessModelFullIdentifierFromSearchParams,
truncateString,
} from '../helpers'; } from '../helpers';
import PaginationForTable from '../components/PaginationForTable'; import PaginationForTable from '../components/PaginationForTable';
@ -29,6 +42,7 @@ import HttpService from '../services/HttpService';
import 'react-bootstrap-typeahead/css/Typeahead.css'; import 'react-bootstrap-typeahead/css/Typeahead.css';
import 'react-bootstrap-typeahead/css/Typeahead.bs5.css'; import 'react-bootstrap-typeahead/css/Typeahead.bs5.css';
import { PaginationObject, ProcessModel } from '../interfaces';
export default function ProcessInstanceList() { export default function ProcessInstanceList() {
const params = useParams(); const params = useParams();
@ -36,7 +50,7 @@ export default function ProcessInstanceList() {
const navigate = useNavigate(); const navigate = useNavigate();
const [processInstances, setProcessInstances] = useState([]); const [processInstances, setProcessInstances] = useState([]);
const [pagination, setPagination] = useState(null); const [pagination, setPagination] = useState<PaginationObject | null>(null);
const oneHourInSeconds = 3600; const oneHourInSeconds = 3600;
const oneMonthInSeconds = oneHourInSeconds * 24 * 30; const oneMonthInSeconds = oneHourInSeconds * 24 * 30;
@ -47,13 +61,17 @@ export default function ProcessInstanceList() {
const setErrorMessage = (useContext as any)(ErrorContext)[1]; const setErrorMessage = (useContext as any)(ErrorContext)[1];
const [processStatuseSelectionOptions, setProcessStatusSelectionOptions] = const [processStatusSelectionOptions, setProcessStatusSelectionOptions] =
useState<any[]>([]); useState<any[]>([]);
const [processStatusSelection, setProcessStatusSelection] = useState< const [processStatusSelection, setProcessStatusSelection] = useState<
Option[] Option[]
>([]); >([]);
const [processModeleSelectionOptions, setProcessModelSelectionOptions] = const [processModelAvailableItems, setProcessModelAvailableItems] = useState<
useState([]); ProcessModel[]
>([]);
const [processModelFilteredItems, setProcessModelFilteredItems] = useState<
ProcessModel[]
>([]);
const [processModelSelection, setProcessModelSelection] = useState<Option[]>( const [processModelSelection, setProcessModelSelection] = useState<Option[]>(
[] []
); );
@ -124,7 +142,8 @@ export default function ProcessInstanceList() {
} }
return item; return item;
}); });
setProcessModelSelectionOptions(selectionArray); setProcessModelAvailableItems(selectionArray);
setProcessModelFilteredItems(selectionArray);
const processStatusSelectedArray: Option[] = []; const processStatusSelectedArray: Option[] = [];
const processStatusSelectionArray = PROCESS_STATUSES.map( const processStatusSelectionArray = PROCESS_STATUSES.map(
@ -287,7 +306,7 @@ export default function ProcessInstanceList() {
id="process-model-selection" id="process-model-selection"
labelKey="label" labelKey="label"
onChange={setProcessModelSelection} onChange={setProcessModelSelection}
options={processModeleSelectionOptions} options={processModelAvailableItems}
placeholder="Choose a process model..." placeholder="Choose a process model..."
selected={processModelSelection} selected={processModelSelection}
/> />
@ -297,74 +316,172 @@ export default function ProcessInstanceList() {
}; };
const processStatusSearch = () => { const processStatusSearch = () => {
// return (
// <Form.Group>
// <InputGroup>
// <InputGroup.Text className="text-nowrap">
// Process Status:{' '}
// </InputGroup.Text>
// <Typeahead
// multiple
// style={{ width: 500 }}
// id="process-status-selection"
// // for cypress tests since data-qa does not work
// inputProps={{
// name: 'process-status-selection',
// }}
// labelKey="label"
// onChange={setProcessStatusSelection}
// options={processStatusSelectionOptions}
// placeholder="Choose process statuses..."
// selected={processStatusSelection}
// />
// </InputGroup>
// </Form.Group>
// );
return ( return (
<Form.Group> <MultiSelect
<InputGroup> label="Process Instance Status"
<InputGroup.Text className="text-nowrap"> id="process-instance-status-select"
Process Status:{' '} titleText="Process Instance Status Seleect"
</InputGroup.Text> items={processStatusSelectionOptions}
<Typeahead
multiple
style={{ width: 500 }}
id="process-status-selection"
// for cypress tests since data-qa does not work
inputProps={{
name: 'process-status-selection',
}}
labelKey="label"
onChange={setProcessStatusSelection} onChange={setProcessStatusSelection}
options={processStatuseSelectionOptions} itemToString={(item: any) => {
placeholder="Choose process statuses..." return item.label || '';
selected={processStatusSelection} }}
selectionFeedback="top-after-reopen"
/> />
</InputGroup> );
</Form.Group> };
const shouldFilterProcessModel = (options: any) => {
const processModel: ProcessModel = options.item;
const { inputValue } = options;
return (
processModel.process_group_id.match(inputValue) ||
processModel.id.match(inputValue) ||
processModel.display_name.match(inputValue)
); );
}; };
const filterOptions = () => { const filterOptions = () => {
const { page, perPage } = getPageInfoFromSearchParams(searchParams);
// return (
// <div className="container">
// <div className="row">
// <div className="col">
// <form onSubmit={handleFilter}>
// <Stack direction="horizontal" gap={3}>
// {processModelSearch()}
// </Stack>
// <br />
// <Stack direction="horizontal" gap={3}>
// {dateComponent(
// 'Start Range: ',
// 'start-from',
// startFrom,
// setStartFrom
// )}
// {dateComponent('-', 'start-till', startTill, setStartTill)}
// </Stack>
// <br />
// <Stack direction="horizontal" gap={3}>
// {dateComponent(
// 'End Range: \u00A0\u00A0',
// 'end-from',
// endFrom,
// setEndFrom
// )}
// {dateComponent('-', 'end-till', endTill, setEndTill)}
// </Stack>
// <br />
// <Stack direction="horizontal" gap={3}>
// {processStatusSearch()}
// </Stack>
// <Stack direction="horizontal" gap={3}>
// <Button className="ms-auto" variant="secondary" type="submit">
// Filter
// </Button>
// </Stack>
// </form>
// </div>
// <div className="col" />
// </div>
// </div>
// );
// return (
// <Grid columns={2} fullWidth>
// <Column md={8} className="blueColumn">
// Hello1
// </Column>
// <Column md={8} className="redColumn">
// Hello2
// </Column>
// </Grid>
// );
// return (
// <FlexGrid>
// <Row>
// <Column>
// <DemoContent>Span 25%</DemoContent>
// </Column>
// <Column>
// <DemoContent>Span 25%</DemoContent>
// </Column>
// <Column>
// <DemoContent>Span 25%</DemoContent>
// </Column>
// <Column>
// <DemoContent>Span 25%</DemoContent>
// </Column>
// </Row>
// </FlexGrid>
// );
return ( return (
<div className="container"> <>
<div className="row"> <Grid fullWidth className="with-bottom-margin">
<div className="col"> <Column md={8}>
<form onSubmit={handleFilter}> <ComboBox
<Stack direction="horizontal" gap={3}> onChange={setProcessModelSelection}
{processModelSearch()} id="process-model-select"
</Stack> items={processModelFilteredItems}
<br /> itemToString={(processModel: ProcessModel) => {
<Stack direction="horizontal" gap={3}> if (processModel) {
{dateComponent( return `${processModel.process_group_id}/${
'Start Range: ', processModel.id
'start-from', } (${truncateString(processModel.display_name, 20)})`;
startFrom, }
setStartFrom return null;
)} }}
{dateComponent('-', 'start-till', startTill, setStartTill)} shouldFilterItem={shouldFilterProcessModel}
</Stack> placeholder="Process Model"
<br /> titleText="Process model"
<Stack direction="horizontal" gap={3}> />
{dateComponent( </Column>
'End Range: \u00A0\u00A0', <Column md={8}>{processStatusSearch()}</Column>
'end-from', </Grid>
endFrom, <Grid fullWidth className="with-bottom-margin">
setEndFrom <Column md={4}>
)} <ButtonSet>
{dateComponent('-', 'end-till', endTill, setEndTill)} <Button kind="" className="button-white-background">
</Stack> Clear
<br />
<Stack direction="horizontal" gap={3}>
{processStatusSearch()}
</Stack>
<Stack direction="horizontal" gap={3}>
<Button className="ms-auto" variant="secondary" type="submit">
Filter
</Button> </Button>
</Stack> <Button kind="secondary">Filter</Button>
</form> </ButtonSet>
</div> </Column>
<div className="col" /> </Grid>
</div> <Grid fullWidth>
</div> <Column lg={16}>
<PaginationForTable
page={page}
perPage={perPage}
pagination={pagination}
tableToDisplay={buildTable()}
queryParamString={getSearchParamsAsQueryString()}
path="/admin/process-instances"
/>
</Column>
</Grid>
</>
); );
}; };
@ -444,14 +561,6 @@ export default function ProcessInstanceList() {
<> <>
{processInstanceTitleElement()} {processInstanceTitleElement()}
{filterOptions()} {filterOptions()}
<PaginationForTable
page={page}
perPage={perPage}
pagination={pagination}
tableToDisplay={buildTable()}
queryParamString={getSearchParamsAsQueryString()}
path="/admin/process-instances"
/>
</> </>
); );
} }