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/preset-react": "^7.18.6",
"@carbon/react": "^1.16.0",
"@carbon/styles": "^1.16.0",
"@ginkgo-bioworks/react-json-schema-form-builder": "^2.9.0",
"@monaco-editor/react": "^4.4.5",
"@rjsf/core": "^4.2.0",

View File

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

View File

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

View File

@ -2,14 +2,12 @@ import {
Header,
Theme,
HeaderName,
HeaderContainer,
HeaderNavigation,
HeaderMenuItem,
HeaderMenu,
// @ts-ignore
} from '@carbon/react';
import { useEffect, useState } from 'react';
import { Navbar, Nav } from 'react-bootstrap';
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
import logo from '../logo.svg';
@ -89,44 +87,9 @@ export default function NavigationBar() {
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) {
return (
<HeaderContainer>
<div className="spiffworkflow-header-container">
<Theme theme="g100">
<Header aria-label="Spiffworkflow">
<HeaderName href="/" prefix="">
@ -151,7 +114,7 @@ export default function NavigationBar() {
</HeaderNavigation>
</Header>
</Theme>
</HeaderContainer>
</div>
);
}
return null;

View File

@ -1,8 +1,9 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { Link, useNavigate } from 'react-router-dom';
// @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_PAGE = 1;
@ -11,9 +12,7 @@ type OwnProps = {
page: number;
perPage: number;
perPageOptions?: number[];
pagination: {
[key: string]: number;
};
pagination: PaginationObject | null;
tableToDisplay: any;
queryParamString?: string;
path: string;
@ -29,141 +28,28 @@ export default function PaginationForTable({
path,
}: OwnProps) {
const PER_PAGE_OPTIONS = [2, 10, 50, 100];
const navigate = useNavigate();
// const buildPerPageDropdown = () => {
// const perPageDropdownRows = (perPageOptions || PER_PAGE_OPTIONS).map(
// (perPageOption) => {
// if (perPageOption === perPage) {
// return (
// <Dropdown.Item
// key={perPageOption}
// href={`${path}?page=1&per_page=${perPageOption}`}
// active
// >
// {perPageOption}
// </Dropdown.Item>
// );
// }
// return (
// <Dropdown.Item
// key={perPageOption}
// href={`${path}?page=1&per_page=${perPageOption}`}
// >
// {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;
const updateRows = (args: any) => {
const newPage = args.page;
const { pageSize } = args;
navigate(`${path}?page=${newPage}&per_page=${pageSize}${queryParamString}`);
};
return (
<>
{tableToDisplay}
<Pagination
backwardText="Previous page"
forwardText="Next page"
itemsPerPageText="Items per page:"
page={page}
pageNumberText="Page Number"
pageSize={perPage}
pageSizes={perPageOptions || PER_PAGE_OPTIONS}
totalItems={(pagination as any).total}
onChange={updateRows}
/>
</>
);
}

View File

@ -90,3 +90,11 @@ export const getProcessModelFullIdentifierFromSearchParams = (
}
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;
}
.spiffworkflow-header-container {
margin-bottom: 2em;
}
.active-task-highlight:not(.djs-connection) .djs-visual > :nth-child(1) {
fill: yellow !important;
@ -44,6 +48,17 @@ span.bjs-crumb {
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 {
border:1px solid #000000;
height:70vh;

View File

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

View File

@ -41,3 +41,9 @@ export interface AuthenticationItem {
id: string;
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"
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>
</td>
<td

View File

@ -6,8 +6,20 @@ import {
useSearchParams,
} from 'react-router-dom';
import {
Button,
ButtonSet,
Table,
Stack,
Form,
ComboBox,
Grid,
Column,
FlexGrid,
Row,
MultiSelect,
// @ts-ignore
import { Button, Table, Stack, Form } from '@carbon/react';
} from '@carbon/react';
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
import DatePicker from 'react-datepicker';
@ -19,6 +31,7 @@ import {
convertSecondsToFormattedDate,
getPageInfoFromSearchParams,
getProcessModelFullIdentifierFromSearchParams,
truncateString,
} from '../helpers';
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.bs5.css';
import { PaginationObject, ProcessModel } from '../interfaces';
export default function ProcessInstanceList() {
const params = useParams();
@ -36,7 +50,7 @@ export default function ProcessInstanceList() {
const navigate = useNavigate();
const [processInstances, setProcessInstances] = useState([]);
const [pagination, setPagination] = useState(null);
const [pagination, setPagination] = useState<PaginationObject | null>(null);
const oneHourInSeconds = 3600;
const oneMonthInSeconds = oneHourInSeconds * 24 * 30;
@ -47,13 +61,17 @@ export default function ProcessInstanceList() {
const setErrorMessage = (useContext as any)(ErrorContext)[1];
const [processStatuseSelectionOptions, setProcessStatusSelectionOptions] =
const [processStatusSelectionOptions, setProcessStatusSelectionOptions] =
useState<any[]>([]);
const [processStatusSelection, setProcessStatusSelection] = useState<
Option[]
>([]);
const [processModeleSelectionOptions, setProcessModelSelectionOptions] =
useState([]);
const [processModelAvailableItems, setProcessModelAvailableItems] = useState<
ProcessModel[]
>([]);
const [processModelFilteredItems, setProcessModelFilteredItems] = useState<
ProcessModel[]
>([]);
const [processModelSelection, setProcessModelSelection] = useState<Option[]>(
[]
);
@ -124,7 +142,8 @@ export default function ProcessInstanceList() {
}
return item;
});
setProcessModelSelectionOptions(selectionArray);
setProcessModelAvailableItems(selectionArray);
setProcessModelFilteredItems(selectionArray);
const processStatusSelectedArray: Option[] = [];
const processStatusSelectionArray = PROCESS_STATUSES.map(
@ -287,7 +306,7 @@ export default function ProcessInstanceList() {
id="process-model-selection"
labelKey="label"
onChange={setProcessModelSelection}
options={processModeleSelectionOptions}
options={processModelAvailableItems}
placeholder="Choose a process model..."
selected={processModelSelection}
/>
@ -297,74 +316,172 @@ export default function ProcessInstanceList() {
};
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 (
<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"
<MultiSelect
label="Process Instance Status"
id="process-instance-status-select"
titleText="Process Instance Status Seleect"
items={processStatusSelectionOptions}
onChange={setProcessStatusSelection}
options={processStatuseSelectionOptions}
placeholder="Choose process statuses..."
selected={processStatusSelection}
itemToString={(item: any) => {
return item.label || '';
}}
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 { 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 (
<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
<>
<Grid fullWidth className="with-bottom-margin">
<Column md={8}>
<ComboBox
onChange={setProcessModelSelection}
id="process-model-select"
items={processModelFilteredItems}
itemToString={(processModel: ProcessModel) => {
if (processModel) {
return `${processModel.process_group_id}/${
processModel.id
} (${truncateString(processModel.display_name, 20)})`;
}
return null;
}}
shouldFilterItem={shouldFilterProcessModel}
placeholder="Process Model"
titleText="Process model"
/>
</Column>
<Column md={8}>{processStatusSearch()}</Column>
</Grid>
<Grid fullWidth className="with-bottom-margin">
<Column md={4}>
<ButtonSet>
<Button kind="" className="button-white-background">
Clear
</Button>
</Stack>
</form>
</div>
<div className="col" />
</div>
</div>
<Button kind="secondary">Filter</Button>
</ButtonSet>
</Column>
</Grid>
<Grid fullWidth>
<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()}
{filterOptions()}
<PaginationForTable
page={page}
perPage={perPage}
pagination={pagination}
tableToDisplay={buildTable()}
queryParamString={getSearchParamsAsQueryString()}
path="/admin/process-instances"
/>
</>
);
}