diff --git a/ui/src/components/QueueBreadcrumb.tsx b/ui/src/components/QueueBreadcrumb.tsx new file mode 100644 index 0000000..912795d --- /dev/null +++ b/ui/src/components/QueueBreadcrumb.tsx @@ -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); + + const handleClick = (event: React.MouseEvent) => { + event.preventDefault(); + setAnchorEl(event.currentTarget); + }; + + const closeMenu = () => { + setAnchorEl(null); + }; + + return ( + <> + + history.push(paths.HOME)} + /> + } + onClick={handleClick} + onDelete={handleClick} + /> + + + {props.queues.sort().map((qname) => ( + { + history.push(queueDetailsPath(qname)); + closeMenu(); + }} + > + {qname} + + ))} + + + ); +} diff --git a/ui/src/components/QueueInfoBanner.tsx b/ui/src/components/QueueInfoBanner.tsx index fef48a7..a7d0686 100644 --- a/ui/src/components/QueueInfoBanner.tsx +++ b/ui/src/components/QueueInfoBanner.tsx @@ -3,6 +3,7 @@ import { connect, ConnectedProps } from "react-redux"; import { makeStyles } from "@material-ui/core/styles"; import Typography from "@material-ui/core/Typography"; import { AppState } from "../store"; +import { percentage } from "../utils"; const useStyles = makeStyles((theme) => ({ banner: { @@ -92,7 +93,9 @@ function QueueInfoBanner(props: Props & ReduxProps) { Error rate - 0.3 % + + {queue ? percentage(queue.failed, queue.processed) : "-"} + ); diff --git a/ui/src/utils.ts b/ui/src/utils.ts index 1a17e2e..f78ac25 100644 --- a/ui/src/utils.ts +++ b/ui/src/utils.ts @@ -79,3 +79,8 @@ export function uuidPrefix(uuid: string): string { } return uuid.substr(0, idx); } + +export function percentage(numerator: number, denominator: number): string { + const perc = ((numerator / denominator) * 100).toFixed(2); + return `${perc} %`; +} diff --git a/ui/src/views/TasksView.tsx b/ui/src/views/TasksView.tsx index f423d2e..0dddc81 100644 --- a/ui/src/views/TasksView.tsx +++ b/ui/src/views/TasksView.tsx @@ -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 Container from "@material-ui/core/Container"; import Grid from "@material-ui/core/Grid"; import TasksTable from "../components/TasksTable"; import QueueInfoBanner from "../components/QueueInfoBanner"; +import QueueBreadCrumb from "../components/QueueBreadcrumb"; 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) => ({ container: { paddingTop: theme.spacing(2), }, - bannerContainer: { + breadcrumbs: { marginBottom: theme.spacing(2), }, - taskTableContainer: { + banner: { + marginBottom: theme.spacing(2), + }, + tasksTable: { marginBottom: theme.spacing(4), }, })); @@ -29,7 +44,7 @@ interface RouteParams { const validStatus = ["active", "pending", "scheduled", "retry", "archived"]; const defaultStatus = "active"; -function TasksView() { +function TasksView(props: ConnectedProps) { const classes = useStyles(); const { qname } = useParams(); const query = useQuery(); @@ -37,14 +52,22 @@ function TasksView() { if (!selected || !validStatus.includes(selected)) { selected = defaultStatus; } + const { listQueuesAsync } = props; + + useEffect(() => { + listQueuesAsync(); + }, [listQueuesAsync]); return ( - + + + + - + @@ -52,4 +75,4 @@ function TasksView() { ); } -export default TasksView; +export default connector(TasksView);