Fetch scheduler entries data periodically

This commit is contained in:
Ken Hibino 2020-12-03 07:09:02 -08:00
parent 3e5b145883
commit c6471e8c04
5 changed files with 78 additions and 99 deletions

View File

@ -14,7 +14,7 @@ import LayersIcon from "@material-ui/icons/Layers";
import SettingsIcon from "@material-ui/icons/Settings"; import SettingsIcon from "@material-ui/icons/Settings";
import { paths } from "./paths"; import { paths } from "./paths";
import ListItemLink from "./components/ListItemLink"; import ListItemLink from "./components/ListItemLink";
import CronView from "./views/CronView"; import SchedulersView from "./views/SchedulersView";
import DashboardView from "./views/DashboardView"; import DashboardView from "./views/DashboardView";
import TasksView from "./views/TasksView"; import TasksView from "./views/TasksView";
import SettingsView from "./views/SettingsView"; import SettingsView from "./views/SettingsView";
@ -147,20 +147,20 @@ function App() {
<List> <List>
<div> <div>
<ListItemLink <ListItemLink
to="/" to={paths.HOME}
primary="Queues" primary="Queues"
icon={<BarChartIcon />} icon={<BarChartIcon />}
/> />
<ListItemLink <ListItemLink
to="/cron" to={paths.SCHEDULERS}
primary="Cron" primary="Schedulers"
icon={<LayersIcon />} icon={<LayersIcon />}
/> />
</div> </div>
</List> </List>
<List> <List>
<ListItemLink <ListItemLink
to="/settings" to={paths.SETTINGS}
primary="Settings" primary="Settings"
icon={<SettingsIcon />} icon={<SettingsIcon />}
/> />
@ -173,8 +173,8 @@ function App() {
<Route exact path={paths.QUEUE_DETAILS}> <Route exact path={paths.QUEUE_DETAILS}>
<TasksView /> <TasksView />
</Route> </Route>
<Route exact path={paths.CRON}> <Route exact path={paths.SCHEDULERS}>
<CronView /> <SchedulersView />
</Route> </Route>
<Route exact path={paths.SETTINGS}> <Route exact path={paths.SETTINGS}>
<SettingsView /> <SettingsView />

View File

@ -7,10 +7,14 @@ 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 Alert from "@material-ui/lab/Alert";
import AlertTitle from "@material-ui/lab/AlertTitle";
import SyntaxHighlighter from "react-syntax-highlighter"; import SyntaxHighlighter from "react-syntax-highlighter";
import syntaxHighlightStyle from "react-syntax-highlighter/dist/esm/styles/hljs/github"; import syntaxHighlightStyle from "react-syntax-highlighter/dist/esm/styles/hljs/github";
import { SortDirection, ColumnConfig } from "../types/table"; import { SortDirection, ColumnConfig } from "../types/table";
import TableSortLabel from "@material-ui/core/TableSortLabel"; import TableSortLabel from "@material-ui/core/TableSortLabel";
import { SchedulerEntry } from "../api";
import { timeAgo, durationBefore } from "../timeutil";
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
table: { table: {
@ -58,7 +62,7 @@ const colConfigs: ColumnConfig<SortBy>[] = [
}, },
{ {
label: "Payload", label: "Payload",
key: "payload", key: "task_payload",
sortBy: SortBy.Payload, sortBy: SortBy.Payload,
align: "left", align: "left",
}, },
@ -82,79 +86,22 @@ const colConfigs: ColumnConfig<SortBy>[] = [
}, },
]; ];
function createData(
id: string,
spec: string,
type: string,
payload: any,
options: string,
nextEnqueue: string,
prevEnqueue: string
) {
return { id, spec, type, payload, options, nextEnqueue, prevEnqueue };
}
const rows = [
createData(
"da0e15bb-3649-45de-9c36-90b9db744b8a",
"*/5 * * * *",
"email:welcome",
{ user_id: 42 },
"[Queue('email')]",
"In 29s",
"4m31s ago"
),
createData(
"fi0e10bb-3649-45de-9c36-90b9db744b8a",
"* 1 * * *",
"email:daily_digest",
{},
"[Queue('email')]",
"In 23h",
"1h ago"
),
createData(
"ca0e17bv-3649-45de-9c36-90b9db744b8a",
"@every 10m",
"search:reindex",
{},
"[Queue('index')]",
"In 2m",
"8m ago"
),
createData(
"we4e15bb-3649-45de-9c36-90b9db744b8a",
"*/5 * * * *",
"janitor",
{ user_id: 42 },
"[Queue('low')]",
"In 29s",
"4m31s ago"
),
];
interface Entry {
id: string;
spec: string;
type: string;
payload: any;
options: string;
nextEnqueue: string;
prevEnqueue: string;
}
// sortEntries takes a array of entries and return a sorted array. // sortEntries takes a array of entries and return a sorted array.
// It returns a new array and leave the original array untouched. // It returns a new array and leave the original array untouched.
function sortEntries( function sortEntries(
entries: Entry[], entries: SchedulerEntry[],
cmpFn: (first: Entry, second: Entry) => number cmpFn: (first: SchedulerEntry, second: SchedulerEntry) => number
): Entry[] { ): SchedulerEntry[] {
let copy = [...entries]; let copy = [...entries];
copy.sort(cmpFn); copy.sort(cmpFn);
return copy; return copy;
} }
export default function CronEntriesTable() { interface Props {
entries: SchedulerEntry[];
}
export default function SchedulerEntriesTable(props: Props) {
const classes = useStyles(); const classes = useStyles();
const [sortBy, setSortBy] = useState<SortBy>(SortBy.EntryId); const [sortBy, setSortBy] = useState<SortBy>(SortBy.EntryId);
const [sortDir, setSortDir] = useState<SortDirection>(SortDirection.Asc); const [sortDir, setSortDir] = useState<SortDirection>(SortDirection.Asc);
@ -171,7 +118,7 @@ export default function CronEntriesTable() {
} }
}; };
const cmpFunc = (e1: Entry, q2: Entry): number => { const cmpFunc = (e1: SchedulerEntry, q2: SchedulerEntry): number => {
let isE1Smaller: boolean; let isE1Smaller: boolean;
switch (sortBy) { switch (sortBy) {
case SortBy.EntryId: case SortBy.EntryId:
@ -183,24 +130,24 @@ export default function CronEntriesTable() {
isE1Smaller = e1.spec < q2.spec; isE1Smaller = e1.spec < q2.spec;
break; break;
case SortBy.Type: case SortBy.Type:
if (e1.type === q2.type) return 0; if (e1.task_type === q2.task_type) return 0;
isE1Smaller = e1.type < q2.type; isE1Smaller = e1.task_type < q2.task_type;
break; break;
case SortBy.Payload: case SortBy.Payload:
if (e1.payload === q2.payload) return 0; if (e1.task_payload === q2.task_payload) return 0;
isE1Smaller = e1.payload < q2.payload; isE1Smaller = e1.task_payload < q2.task_payload;
break; break;
case SortBy.Options: case SortBy.Options:
if (e1.options === q2.options) return 0; if (e1.options === q2.options) return 0;
isE1Smaller = e1.options < q2.options; isE1Smaller = e1.options < q2.options;
break; break;
case SortBy.NextEnqueue: case SortBy.NextEnqueue:
if (e1.nextEnqueue === q2.nextEnqueue) return 0; if (e1.next_enqueue_at === q2.next_enqueue_at) return 0;
isE1Smaller = e1.nextEnqueue < q2.nextEnqueue; isE1Smaller = e1.next_enqueue_at < q2.next_enqueue_at;
break; break;
case SortBy.PrevEnqueue: case SortBy.PrevEnqueue:
if (e1.prevEnqueue === q2.prevEnqueue) return 0; if (e1.prev_enqueue_at === q2.prev_enqueue_at) return 0;
isE1Smaller = e1.prevEnqueue < q2.prevEnqueue; isE1Smaller = e1.prev_enqueue_at < q2.prev_enqueue_at;
break; break;
default: default:
// eslint-disable-next-line no-throw-literal // eslint-disable-next-line no-throw-literal
@ -213,6 +160,15 @@ export default function CronEntriesTable() {
} }
}; };
if (props.entries.length === 0) {
return (
<Alert severity="info">
<AlertTitle>Info</AlertTitle>
No entries found at this time.
</Alert>
);
}
return ( return (
<TableContainer> <TableContainer>
<Table className={classes.table} aria-label="simple table"> <Table className={classes.table} aria-label="simple table">
@ -236,41 +192,43 @@ export default function CronEntriesTable() {
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
{sortEntries(rows, cmpFunc).map((row, idx) => { {sortEntries(props.entries, cmpFunc).map((entry, idx) => {
const isLastRow = idx === rows.length - 1; const isLastRow = idx === props.entries.length - 1;
return ( return (
<TableRow key={row.id}> <TableRow key={entry.id}>
<TableCell <TableCell
component="th" component="th"
scope="row" scope="row"
className={clsx(isLastRow && classes.noBorder)} className={clsx(isLastRow && classes.noBorder)}
> >
{row.id} {entry.id}
</TableCell> </TableCell>
<TableCell className={clsx(isLastRow && classes.noBorder)}> <TableCell className={clsx(isLastRow && classes.noBorder)}>
{row.spec} {entry.spec}
</TableCell> </TableCell>
<TableCell className={clsx(isLastRow && classes.noBorder)}> <TableCell className={clsx(isLastRow && classes.noBorder)}>
{row.type} {entry.task_type}
</TableCell> </TableCell>
<TableCell className={clsx(isLastRow && classes.noBorder)}> <TableCell className={clsx(isLastRow && classes.noBorder)}>
<SyntaxHighlighter <SyntaxHighlighter
language="json" language="json"
style={syntaxHighlightStyle} style={syntaxHighlightStyle}
> >
{JSON.stringify(row.payload)} {JSON.stringify(entry.task_payload)}
</SyntaxHighlighter> </SyntaxHighlighter>
</TableCell> </TableCell>
<TableCell className={clsx(isLastRow && classes.noBorder)}> <TableCell className={clsx(isLastRow && classes.noBorder)}>
<SyntaxHighlighter language="go" style={syntaxHighlightStyle}> <SyntaxHighlighter language="go" style={syntaxHighlightStyle}>
{row.options} {entry.options.length > 0
? entry.options.join(", ")
: "No options"}
</SyntaxHighlighter> </SyntaxHighlighter>
</TableCell> </TableCell>
<TableCell className={clsx(isLastRow && classes.noBorder)}> <TableCell className={clsx(isLastRow && classes.noBorder)}>
{row.nextEnqueue} {durationBefore(entry.next_enqueue_at)}
</TableCell> </TableCell>
<TableCell className={clsx(isLastRow && classes.noBorder)}> <TableCell className={clsx(isLastRow && classes.noBorder)}>
{row.prevEnqueue} {timeAgo(entry.prev_enqueue_at)}
</TableCell> </TableCell>
</TableRow> </TableRow>
); );

View File

@ -1,7 +1,7 @@
export const paths = { export const paths = {
HOME: "/", HOME: "/",
SETTINGS: "/settings", SETTINGS: "/settings",
CRON: "/cron", SCHEDULERS: "/schedulers",
QUEUE_DETAILS: "/queues/:qname", QUEUE_DETAILS: "/queues/:qname",
}; };

View File

@ -29,7 +29,7 @@ export function durationBefore(timestamp: string): string {
if (duration.totalSeconds < 1) { if (duration.totalSeconds < 1) {
return "now"; return "now";
} }
return stringifyDuration(duration); return "in " + stringifyDuration(duration);
} catch { } catch {
return "-"; return "-";
} }

View File

@ -1,10 +1,14 @@
import React from "react"; import React from "react";
import { connect, ConnectedProps } from "react-redux";
import Container from "@material-ui/core/Container"; import Container from "@material-ui/core/Container";
import { makeStyles } from "@material-ui/core/styles"; import { makeStyles } from "@material-ui/core/styles";
import Grid from "@material-ui/core/Grid"; import Grid from "@material-ui/core/Grid";
import Paper from "@material-ui/core/Paper"; import Paper from "@material-ui/core/Paper";
import CronEntriesTable from "../components/CronEntriesTable"; import SchedulerEntriesTable from "../components/SchedulerEntriesTable";
import Typography from "@material-ui/core/Typography"; import Typography from "@material-ui/core/Typography";
import { AppState } from "../store";
import { listSchedulerEntriesAsync } from "../actions/schedulerEntriesActions";
import { usePolling } from "../hooks";
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
container: { container: {
@ -19,20 +23,37 @@ const useStyles = makeStyles((theme) => ({
}, },
heading: { heading: {
paddingLeft: theme.spacing(2), paddingLeft: theme.spacing(2),
marginBottom: theme.spacing(1),
}, },
})); }));
function CronView() { function mapStateToProps(state: AppState) {
return {
loading: state.schedulerEntries.loading,
entries: state.schedulerEntries.data,
pollInterval: state.settings.pollInterval,
};
}
const connector = connect(mapStateToProps, { listSchedulerEntriesAsync });
type Props = ConnectedProps<typeof connector>;
function SchedulersView(props: Props) {
const { pollInterval, listSchedulerEntriesAsync } = props;
const classes = useStyles(); const classes = useStyles();
usePolling(listSchedulerEntriesAsync, pollInterval);
return ( return (
<Container maxWidth="lg" className={classes.container}> <Container maxWidth="lg" className={classes.container}>
<Grid container spacing={3}> <Grid container spacing={3}>
<Grid item xs={12}> <Grid item xs={12}>
<Paper className={classes.paper} variant="outlined"> <Paper className={classes.paper} variant="outlined">
<Typography variant="h6" className={classes.heading}> <Typography variant="h6" className={classes.heading}>
Cron Entries Scheduler Entries
</Typography> </Typography>
<CronEntriesTable /> <SchedulerEntriesTable entries={props.entries} />
</Paper> </Paper>
</Grid> </Grid>
</Grid> </Grid>
@ -40,4 +61,4 @@ function CronView() {
); );
} }
export default CronView; export default connector(SchedulersView);