Change values displayed in QueuesOverviewTable

This commit is contained in:
Ken Hibino 2021-01-27 22:40:34 -08:00
parent a0e7b49c3a
commit 4179d00b7b

View File

@ -8,7 +8,6 @@ import TableCell from "@material-ui/core/TableCell";
import TableContainer from "@material-ui/core/TableContainer"; import TableContainer from "@material-ui/core/TableContainer";
import TableHead from "@material-ui/core/TableHead"; import TableHead from "@material-ui/core/TableHead";
import TableRow from "@material-ui/core/TableRow"; import TableRow from "@material-ui/core/TableRow";
import TableFooter from "@material-ui/core/TableFooter";
import TableSortLabel from "@material-ui/core/TableSortLabel"; import TableSortLabel from "@material-ui/core/TableSortLabel";
import IconButton from "@material-ui/core/IconButton"; import IconButton from "@material-ui/core/IconButton";
import Tooltip from "@material-ui/core/Tooltip"; import Tooltip from "@material-ui/core/Tooltip";
@ -20,34 +19,19 @@ import DeleteQueueConfirmationDialog from "./DeleteQueueConfirmationDialog";
import { Queue } from "../api"; import { Queue } from "../api";
import { queueDetailsPath } from "../paths"; import { queueDetailsPath } from "../paths";
import { SortDirection, SortableTableColumn } from "../types/table"; import { SortDirection, SortableTableColumn } from "../types/table";
import prettyBytes from "pretty-bytes";
import { percentage } from "../utils";
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
table: { table: {
minWidth: 650, minWidth: 650,
}, },
linkCell: {
textDecoration: "none",
color: theme.palette.text.primary,
},
footerCell: {
fontWeight: 600,
fontSize: "0.875rem",
borderBottom: "none",
},
boldCell: {
fontWeight: 600,
},
fixedCell: { fixedCell: {
position: "sticky", position: "sticky",
zIndex: 1, zIndex: 1,
left: 0, left: 0,
background: theme.palette.background.paper, background: theme.palette.background.paper,
}, },
actionIconsContainer: {
display: "flex",
justifyContent: "center",
minWidth: "100px",
},
})); }));
interface QueueWithMetadata extends Queue { interface QueueWithMetadata extends Queue {
@ -63,32 +47,42 @@ interface Props {
enum SortBy { enum SortBy {
Queue, Queue,
State,
Size, Size,
Active, MemoryUsage,
Pending, Processed,
Scheduled, Failed,
Retry, ErrorRate,
Archived,
None, // no sort support None, // no sort support
} }
const colConfigs: SortableTableColumn<SortBy>[] = [ const colConfigs: SortableTableColumn<SortBy>[] = [
{ label: "Queue", key: "queue", sortBy: SortBy.Queue, align: "left" }, { label: "Queue", key: "queue", sortBy: SortBy.Queue, align: "left" },
{ label: "Size", key: "size", sortBy: SortBy.Size, align: "right" }, { label: "State", key: "state", sortBy: SortBy.State, align: "left" },
{ label: "Active", key: "active", sortBy: SortBy.Active, align: "right" },
{ label: "Pending", key: "pending", sortBy: SortBy.Pending, align: "right" },
{ {
label: "Scheduled", label: "Size",
key: "scheduled", key: "size",
sortBy: SortBy.Scheduled, sortBy: SortBy.Size,
align: "right", align: "right",
}, },
{ label: "Retry", key: "retry", sortBy: SortBy.Retry, align: "right" },
{ {
label: "Archived", label: "Memory usage",
key: "archived", key: "memory_usage",
sortBy: SortBy.Archived, sortBy: SortBy.MemoryUsage,
align: "right",
},
{
label: "Processed",
key: "processed",
sortBy: SortBy.Processed,
align: "right",
},
{ label: "Failed", key: "failed", sortBy: SortBy.Failed, align: "right" },
{
label: "Error rate",
key: "error_rate",
sortBy: SortBy.ErrorRate,
align: "right", align: "right",
}, },
{ label: "Actions", key: "actions", sortBy: SortBy.None, align: "center" }, { label: "Actions", key: "actions", sortBy: SortBy.None, align: "center" },
@ -109,12 +103,9 @@ export default function QueuesOverviewTable(props: Props) {
const classes = useStyles(); const classes = useStyles();
const [sortBy, setSortBy] = useState<SortBy>(SortBy.Queue); const [sortBy, setSortBy] = useState<SortBy>(SortBy.Queue);
const [sortDir, setSortDir] = useState<SortDirection>(SortDirection.Asc); const [sortDir, setSortDir] = useState<SortDirection>(SortDirection.Asc);
const [activeRowIndex, setActiveRowIndex] = useState<number>(-1);
const [queueToDelete, setQueueToDelete] = useState<QueueWithMetadata | null>( const [queueToDelete, setQueueToDelete] = useState<QueueWithMetadata | null>(
null null
); );
const total = getAggregateCounts(props.queues);
const createSortClickHandler = (sortKey: SortBy) => (e: React.MouseEvent) => { const createSortClickHandler = (sortKey: SortBy) => (e: React.MouseEvent) => {
if (sortKey === sortBy) { if (sortKey === sortBy) {
// Toggle sort direction. // Toggle sort direction.
@ -134,29 +125,31 @@ export default function QueuesOverviewTable(props: Props) {
if (q1.queue === q2.queue) return 0; if (q1.queue === q2.queue) return 0;
isQ1Smaller = q1.queue < q2.queue; isQ1Smaller = q1.queue < q2.queue;
break; break;
case SortBy.State:
if (q1.paused === q2.paused) return 0;
isQ1Smaller = !q1.paused;
break;
case SortBy.Size: case SortBy.Size:
if (q1.size === q2.size) return 0; if (q1.size === q2.size) return 0;
isQ1Smaller = q1.size < q2.size; isQ1Smaller = q1.size < q2.size;
break; break;
case SortBy.Active: case SortBy.MemoryUsage:
if (q1.active === q2.active) return 0; if (q1.memory_usage_bytes === q2.memory_usage_bytes) return 0;
isQ1Smaller = q1.active < q2.active; isQ1Smaller = q1.memory_usage_bytes < q2.memory_usage_bytes;
break; break;
case SortBy.Pending: case SortBy.Processed:
if (q1.pending === q2.pending) return 0; if (q1.processed === q2.processed) return 0;
isQ1Smaller = q1.pending < q2.pending; isQ1Smaller = q1.processed < q2.processed;
break; break;
case SortBy.Scheduled: case SortBy.Failed:
if (q1.scheduled === q2.scheduled) return 0; if (q1.failed === q2.failed) return 0;
isQ1Smaller = q1.scheduled < q2.scheduled; isQ1Smaller = q1.failed < q2.failed;
break; break;
case SortBy.Retry: case SortBy.ErrorRate:
if (q1.retry === q2.retry) return 0; const q1ErrorRate = q1.failed / q1.processed;
isQ1Smaller = q1.retry < q2.retry; const q2ErrorRate = q2.failed / q2.processed;
break; if (q1ErrorRate === q2ErrorRate) return 0;
case SortBy.Archived: isQ1Smaller = q1ErrorRate < q2ErrorRate;
if (q1.archived === q2.archived) return 0;
isQ1Smaller = q1.archived < q2.archived;
break; break;
default: default:
// eslint-disable-next-line no-throw-literal // eslint-disable-next-line no-throw-literal
@ -201,141 +194,15 @@ export default function QueuesOverviewTable(props: Props) {
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
{sortQueues(props.queues, cmpFunc).map((q, i) => ( {sortQueues(props.queues, cmpFunc).map((q) => (
<TableRow <Row
key={q.queue} queue={q}
onMouseEnter={() => setActiveRowIndex(i)} onPauseClick={() => props.onPauseClick(q.queue)}
onMouseLeave={() => setActiveRowIndex(-1)} onResumeClick={() => props.onResumeClick(q.queue)}
> onDeleteClick={() => setQueueToDelete(q)}
<TableCell />
component="th"
scope="row"
className={clsx(classes.boldCell, classes.fixedCell)}
>
<Link
to={queueDetailsPath(q.queue)}
className={classes.linkCell}
>
{q.queue}
{q.paused ? " (paused)" : ""}
</Link>
</TableCell>
<TableCell align="right" className={classes.boldCell}>
{q.size}
</TableCell>
<TableCell align="right">
<Link
to={queueDetailsPath(q.queue, "active")}
className={classes.linkCell}
>
{q.active}
</Link>
</TableCell>
<TableCell align="right">
<Link
to={queueDetailsPath(q.queue, "pending")}
className={classes.linkCell}
>
{q.pending}
</Link>
</TableCell>
<TableCell align="right">
<Link
to={queueDetailsPath(q.queue, "scheduled")}
className={classes.linkCell}
>
{q.scheduled}
</Link>
</TableCell>
<TableCell align="right">
<Link
to={queueDetailsPath(q.queue, "retry")}
className={classes.linkCell}
>
{q.retry}
</Link>
</TableCell>
<TableCell align="right">
<Link
to={queueDetailsPath(q.queue, "archived")}
className={classes.linkCell}
>
{q.archived}
</Link>
</TableCell>
<TableCell align="center">
<div className={classes.actionIconsContainer}>
{activeRowIndex === i ? (
<React.Fragment>
{q.paused ? (
<Tooltip title="Resume">
<IconButton
color="secondary"
onClick={() => props.onResumeClick(q.queue)}
disabled={q.requestPending}
size="small"
>
<PlayCircleFilledIcon fontSize="small" />
</IconButton>
</Tooltip>
) : (
<Tooltip title="Pause">
<IconButton
color="primary"
onClick={() => props.onPauseClick(q.queue)}
disabled={q.requestPending}
size="small"
>
<PauseCircleFilledIcon fontSize="small" />
</IconButton>
</Tooltip>
)}
<Tooltip title="Delete">
<IconButton
onClick={() => setQueueToDelete(q)}
size="small"
>
<DeleteIcon fontSize="small" />
</IconButton>
</Tooltip>
</React.Fragment>
) : (
<IconButton size="small">
<MoreHorizIcon fontSize="small" />
</IconButton>
)}
</div>
</TableCell>
</TableRow>
))} ))}
</TableBody> </TableBody>
<TableFooter>
<TableRow>
<TableCell
className={clsx(classes.fixedCell, classes.footerCell)}
>
Total
</TableCell>
<TableCell className={classes.footerCell} align="right">
{total.size}
</TableCell>
<TableCell className={classes.footerCell} align="right">
{total.active}
</TableCell>
<TableCell className={classes.footerCell} align="right">
{total.pending}
</TableCell>
<TableCell className={classes.footerCell} align="right">
{total.scheduled}
</TableCell>
<TableCell className={classes.footerCell} align="right">
{total.retry}
</TableCell>
<TableCell className={classes.footerCell} align="right">
{total.archived}
</TableCell>
</TableRow>
</TableFooter>
</Table> </Table>
</TableContainer> </TableContainer>
<DeleteQueueConfirmationDialog <DeleteQueueConfirmationDialog
@ -346,31 +213,122 @@ export default function QueuesOverviewTable(props: Props) {
); );
} }
interface AggregateCounts { const useRowStyles = makeStyles((theme) => ({
size: number; row: {
active: number; "&:last-child td": {
pending: number; borderBottomWidth: 0,
scheduled: number; },
retry: number; "&:last-child th": {
archived: number; borderBottomWidth: 0,
},
},
linkText: {
textDecoration: "none",
color: theme.palette.text.primary,
"&:hover": {
textDecoration: "underline",
},
},
textGreen: {
color: theme.palette.success.dark,
},
textRed: {
color: theme.palette.error.dark,
},
boldCell: {
fontWeight: 600,
},
fixedCell: {
position: "sticky",
zIndex: 1,
left: 0,
background: theme.palette.background.paper,
},
actionIconsContainer: {
display: "flex",
justifyContent: "center",
minWidth: "100px",
},
}));
interface RowProps {
queue: QueueWithMetadata;
onPauseClick: () => void;
onResumeClick: () => void;
onDeleteClick: () => void;
} }
function getAggregateCounts(queues: Queue[]): AggregateCounts { function Row(props: RowProps) {
let total = { const classes = useRowStyles();
size: 0, const { queue: q } = props;
active: 0, const [showIcons, setShowIcons] = useState<boolean>(false);
pending: 0, return (
scheduled: 0, <TableRow key={q.queue} className={classes.row}>
retry: 0, <TableCell
archived: 0, component="th"
}; scope="row"
queues.forEach((q) => { className={clsx(classes.boldCell, classes.fixedCell)}
total.size += q.size; >
total.active += q.active; <Link to={queueDetailsPath(q.queue)} className={classes.linkText}>
total.pending += q.pending; {q.queue}
total.scheduled += q.scheduled; </Link>
total.retry += q.retry; </TableCell>
total.archived += q.archived; <TableCell>
}); {q.paused ? (
return total; <span className={classes.textRed}>paused</span>
) : (
<span className={classes.textGreen}>run</span>
)}
</TableCell>
<TableCell align="right">{q.size}</TableCell>
<TableCell align="right">{prettyBytes(q.memory_usage_bytes)}</TableCell>
<TableCell align="right">{q.processed}</TableCell>
<TableCell align="right">{q.failed}</TableCell>
<TableCell align="right">{percentage(q.failed, q.processed)}</TableCell>
<TableCell
align="center"
onMouseEnter={() => setShowIcons(true)}
onMouseLeave={() => setShowIcons(false)}
>
<div className={classes.actionIconsContainer}>
{showIcons ? (
<React.Fragment>
{q.paused ? (
<Tooltip title="Resume">
<IconButton
color="secondary"
onClick={props.onResumeClick}
disabled={q.requestPending}
size="small"
>
<PlayCircleFilledIcon fontSize="small" />
</IconButton>
</Tooltip>
) : (
<Tooltip title="Pause">
<IconButton
color="primary"
onClick={props.onPauseClick}
disabled={q.requestPending}
size="small"
>
<PauseCircleFilledIcon fontSize="small" />
</IconButton>
</Tooltip>
)}
<Tooltip title="Delete">
<IconButton onClick={props.onDeleteClick} size="small">
<DeleteIcon fontSize="small" />
</IconButton>
</Tooltip>
</React.Fragment>
) : (
<IconButton size="small">
<MoreHorizIcon fontSize="small" />
</IconButton>
)}
</div>
</TableCell>
</TableRow>
);
} }