Add breadcrumb to task view

This commit is contained in:
Ken Hibino 2021-01-24 13:37:45 -08:00
parent b2c8de61bb
commit 2b2d5f88a5
4 changed files with 126 additions and 8 deletions

View File

@ -0,0 +1,87 @@
import React, { useState } from "react";
import { Link, useHistory } from "react-router-dom";
import { emphasize, withStyles, Theme } from "@material-ui/core/styles";
import Breadcrumbs from "@material-ui/core/Breadcrumbs";
import Chip from "@material-ui/core/Chip";
import Menu from "@material-ui/core/Menu";
import MenuItem from "@material-ui/core/MenuItem";
import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
import { paths, queueDetailsPath } from "../paths";
const StyledBreadcrumb = withStyles((theme: Theme) => ({
root: {
backgroundColor:
theme.palette.type === "dark"
? "#303030"
: theme.palette.background.default,
height: theme.spacing(3),
color: theme.palette.text.secondary,
fontWeight: theme.typography.fontWeightRegular,
"&:hover, &:focus": {
backgroundColor: theme.palette.action.hover,
},
"&:active": {
boxShadow: theme.shadows[1],
backgroundColor: emphasize(theme.palette.action.hover, 0.12),
},
},
}))(Chip) as typeof Chip; // Note: need a type cast here because https://github.com/Microsoft/TypeScript/issues/26591
interface Props {
// All queue names.
queues: string[];
// Name of the queue currently selected.
selectedQueue: string;
}
export default function QueueBreadcrumbs(props: Props) {
const history = useHistory();
const [anchorEl, setAnchorEl] = useState<null | Element>(null);
const handleClick = (event: React.MouseEvent<Element, MouseEvent>) => {
event.preventDefault();
setAnchorEl(event.currentTarget);
};
const closeMenu = () => {
setAnchorEl(null);
};
return (
<>
<Breadcrumbs aria-label="breadcrumb">
<StyledBreadcrumb
component={Link}
to={paths.HOME}
label="Queues"
onClick={() => history.push(paths.HOME)}
/>
<StyledBreadcrumb
label={props.selectedQueue}
deleteIcon={<ExpandMoreIcon />}
onClick={handleClick}
onDelete={handleClick}
/>
</Breadcrumbs>
<Menu
id="queue-breadcrumb-menu"
anchorEl={anchorEl}
keepMounted
open={Boolean(anchorEl)}
onClose={closeMenu}
>
{props.queues.sort().map((qname) => (
<MenuItem
key={qname}
onClick={() => {
history.push(queueDetailsPath(qname));
closeMenu();
}}
>
{qname}
</MenuItem>
))}
</Menu>
</>
);
}

View File

@ -3,6 +3,7 @@ import { connect, ConnectedProps } from "react-redux";
import { makeStyles } from "@material-ui/core/styles"; import { makeStyles } from "@material-ui/core/styles";
import Typography from "@material-ui/core/Typography"; import Typography from "@material-ui/core/Typography";
import { AppState } from "../store"; import { AppState } from "../store";
import { percentage } from "../utils";
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
banner: { banner: {
@ -92,7 +93,9 @@ function QueueInfoBanner(props: Props & ReduxProps) {
<Typography variant="subtitle2" color="textPrimary" gutterBottom> <Typography variant="subtitle2" color="textPrimary" gutterBottom>
Error rate Error rate
</Typography> </Typography>
<Typography color="textSecondary">0.3 %</Typography> <Typography color="textSecondary">
{queue ? percentage(queue.failed, queue.processed) : "-"}
</Typography>
</div> </div>
</div> </div>
); );

View File

@ -79,3 +79,8 @@ export function uuidPrefix(uuid: string): string {
} }
return uuid.substr(0, idx); return uuid.substr(0, idx);
} }
export function percentage(numerator: number, denominator: number): string {
const perc = ((numerator / denominator) * 100).toFixed(2);
return `${perc} %`;
}

View File

@ -1,19 +1,34 @@
import React from "react"; import React, { useEffect } from "react";
import { connect, ConnectedProps } from "react-redux";
import { makeStyles } from "@material-ui/core/styles"; import { makeStyles } from "@material-ui/core/styles";
import Container from "@material-ui/core/Container"; import Container from "@material-ui/core/Container";
import Grid from "@material-ui/core/Grid"; import Grid from "@material-ui/core/Grid";
import TasksTable from "../components/TasksTable"; import TasksTable from "../components/TasksTable";
import QueueInfoBanner from "../components/QueueInfoBanner"; import QueueInfoBanner from "../components/QueueInfoBanner";
import QueueBreadCrumb from "../components/QueueBreadcrumb";
import { useParams, useLocation } from "react-router-dom"; import { useParams, useLocation } from "react-router-dom";
import { listQueuesAsync } from "../actions/queuesActions";
import { AppState } from "../store";
function mapStateToProps(state: AppState) {
return {
queues: state.queues.data.map((q) => q.name),
};
}
const connector = connect(mapStateToProps, { listQueuesAsync });
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
container: { container: {
paddingTop: theme.spacing(2), paddingTop: theme.spacing(2),
}, },
bannerContainer: { breadcrumbs: {
marginBottom: theme.spacing(2), marginBottom: theme.spacing(2),
}, },
taskTableContainer: { banner: {
marginBottom: theme.spacing(2),
},
tasksTable: {
marginBottom: theme.spacing(4), marginBottom: theme.spacing(4),
}, },
})); }));
@ -29,7 +44,7 @@ interface RouteParams {
const validStatus = ["active", "pending", "scheduled", "retry", "archived"]; const validStatus = ["active", "pending", "scheduled", "retry", "archived"];
const defaultStatus = "active"; const defaultStatus = "active";
function TasksView() { function TasksView(props: ConnectedProps<typeof connector>) {
const classes = useStyles(); const classes = useStyles();
const { qname } = useParams<RouteParams>(); const { qname } = useParams<RouteParams>();
const query = useQuery(); const query = useQuery();
@ -37,14 +52,22 @@ function TasksView() {
if (!selected || !validStatus.includes(selected)) { if (!selected || !validStatus.includes(selected)) {
selected = defaultStatus; selected = defaultStatus;
} }
const { listQueuesAsync } = props;
useEffect(() => {
listQueuesAsync();
}, [listQueuesAsync]);
return ( return (
<Container maxWidth="lg"> <Container maxWidth="lg">
<Grid container spacing={0} className={classes.container}> <Grid container spacing={0} className={classes.container}>
<Grid item xs={12} className={classes.bannerContainer}> <Grid xs={12} className={classes.breadcrumbs}>
<QueueBreadCrumb queues={props.queues} selectedQueue={qname} />
</Grid>
<Grid item xs={12} className={classes.banner}>
<QueueInfoBanner qname={qname} /> <QueueInfoBanner qname={qname} />
</Grid> </Grid>
<Grid item xs={12} className={classes.taskTableContainer}> <Grid item xs={12} className={classes.tasksTable}>
<TasksTable queue={qname} selected={selected} /> <TasksTable queue={qname} selected={selected} />
</Grid> </Grid>
</Grid> </Grid>
@ -52,4 +75,4 @@ function TasksView() {
); );
} }
export default TasksView; export default connector(TasksView);