From 03c2827c252432f097d0f1cb224b17afee27c444 Mon Sep 17 00:00:00 2001 From: Ken Hibino Date: Sat, 26 Dec 2020 13:43:51 -0800 Subject: [PATCH] Add SchedulerEnqueueEventsTable --- ui/package.json | 2 + .../SchedulerEnqueueEventsTable.tsx | 75 ++++++ ui/src/components/SchedulerEntriesTable.tsx | 227 +++++++++--------- ui/src/reducers/schedulerEntriesReducer.ts | 15 +- ui/yarn.lock | 17 ++ 5 files changed, 215 insertions(+), 121 deletions(-) create mode 100644 ui/src/components/SchedulerEnqueueEventsTable.tsx diff --git a/ui/package.json b/ui/package.json index 16b490b..f8b38f7 100644 --- a/ui/package.json +++ b/ui/package.json @@ -11,6 +11,7 @@ "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^7.1.2", "@types/jest": "^24.0.0", + "@types/lodash.uniqby": "4.7.6", "@types/node": "^12.0.0", "@types/react": "^16.9.0", "@types/react-dom": "^16.9.0", @@ -21,6 +22,7 @@ "@types/styled-components": "5.1.4", "axios": "0.20.0", "clsx": "1.1.1", + "lodash.uniqby": "4.7.0", "query-string": "6.13.7", "react": "^16.13.1", "react-dom": "^16.13.1", diff --git a/ui/src/components/SchedulerEnqueueEventsTable.tsx b/ui/src/components/SchedulerEnqueueEventsTable.tsx new file mode 100644 index 0000000..b68cad5 --- /dev/null +++ b/ui/src/components/SchedulerEnqueueEventsTable.tsx @@ -0,0 +1,75 @@ +import React, { useEffect } from "react"; +import { connect, ConnectedProps } from "react-redux"; +import { makeStyles } from "@material-ui/core/styles"; +import Table from "@material-ui/core/Table"; +import TableBody from "@material-ui/core/TableBody"; +import TableCell from "@material-ui/core/TableCell"; +import TableContainer from "@material-ui/core/TableContainer"; +import TableHead from "@material-ui/core/TableHead"; +import TableRow from "@material-ui/core/TableRow"; +import { AppState } from "../store"; +import { getEnqueueEventsEntry } from "../reducers/schedulerEntriesReducer"; +import { listSchedulerEnqueueEventsAsync } from "../actions/schedulerEntriesActions"; +import { timeAgo } from "../utils"; + +const useStyles = makeStyles((theme) => ({ + table: { + height: "80vh", + }, +})); + +function mapStateToProps(state: AppState, ownProps: Props) { + return { + events: getEnqueueEventsEntry(state.schedulerEntries, ownProps.entryId), + }; +} + +const connector = connect(mapStateToProps, { listSchedulerEnqueueEventsAsync }); + +type ReduxProps = ConnectedProps; + +interface Props { + entryId: string; // Scheduler Entry ID +} + +function SchedulerEnqueueEventsTable(props: Props & ReduxProps) { + const classes = useStyles(); + const { listSchedulerEnqueueEventsAsync, entryId, events } = props; + + useEffect(() => { + listSchedulerEnqueueEventsAsync(entryId); + }, [entryId, listSchedulerEnqueueEventsAsync]); + + // TODO: loading state UI + + // TODO: "Load More" button OR infinite scroll + + return ( + + + + + Enqueued + Task ID + + + + {events.data.map((e) => ( + + + {timeAgo(e.enqueued_at)} + + {e.task_id} + + ))} + +
+
+ ); +} + +export default connector(SchedulerEnqueueEventsTable); diff --git a/ui/src/components/SchedulerEntriesTable.tsx b/ui/src/components/SchedulerEntriesTable.tsx index d10018f..e09814d 100644 --- a/ui/src/components/SchedulerEntriesTable.tsx +++ b/ui/src/components/SchedulerEntriesTable.tsx @@ -1,8 +1,6 @@ import React, { useState } from "react"; import clsx from "clsx"; import { makeStyles } from "@material-ui/core/styles"; -import Collapse from "@material-ui/core/Collapse"; -import Box from "@material-ui/core/Box"; import IconButton from "@material-ui/core/IconButton"; import Table from "@material-ui/core/Table"; import TableBody from "@material-ui/core/TableBody"; @@ -10,10 +8,10 @@ import TableCell from "@material-ui/core/TableCell"; import TableContainer from "@material-ui/core/TableContainer"; import TableHead from "@material-ui/core/TableHead"; import TableRow from "@material-ui/core/TableRow"; +import Modal from "@material-ui/core/Modal"; import Typography from "@material-ui/core/Typography"; import Tooltip from "@material-ui/core/Tooltip"; -import KeyboardArrowDownIcon from "@material-ui/icons/KeyboardArrowDown"; -import KeyboardArrowUpIcon from "@material-ui/icons/KeyboardArrowUp"; +import HistoryIcon from "@material-ui/icons/History"; import Alert from "@material-ui/lab/Alert"; import AlertTitle from "@material-ui/lab/AlertTitle"; import SyntaxHighlighter from "react-syntax-highlighter"; @@ -22,6 +20,7 @@ import { SortDirection, SortableTableColumn } from "../types/table"; import TableSortLabel from "@material-ui/core/TableSortLabel"; import { SchedulerEntry } from "../api"; import { timeAgo, durationBefore } from "../utils"; +import SchedulerEnqueueEventsTable from "./SchedulerEnqueueEventsTable"; const useStyles = makeStyles((theme) => ({ table: { @@ -36,6 +35,21 @@ const useStyles = makeStyles((theme) => ({ left: 0, background: theme.palette.common.white, }, + modal: { + display: "flex", + alignItems: "center", + justifyContent: "center", + }, + modalContent: { + background: theme.palette.common.white, + padding: theme.spacing(2), + width: "540px", + outline: "none", + borderRadius: theme.shape.borderRadius, + }, + eventsTable: { + maxHeight: "80vh", + }, })); enum SortBy { @@ -120,6 +134,7 @@ export default function SchedulerEntriesTable(props: Props) { const classes = useStyles(); const [sortBy, setSortBy] = useState(SortBy.EntryId); const [sortDir, setSortDir] = useState(SortDirection.Asc); + const [activeEntryId, setActiveEntryId] = useState(""); const createSortClickHandler = (sortKey: SortBy) => (e: React.MouseEvent) => { if (sortKey === sortBy) { @@ -187,144 +202,118 @@ export default function SchedulerEntriesTable(props: Props) { } return ( - - - - - {colConfigs.map((cfg, i) => ( - - + +
+ + + {colConfigs.map((cfg, i) => ( + - {cfg.label} - - + + {cfg.label} + + + ))} + + + + {sortEntries(props.entries, cmpFunc).map((entry, idx) => ( + setActiveEntryId(entry.id)} + /> ))} - - - - {sortEntries(props.entries, cmpFunc).map((entry, idx) => ( - - ))} - -
-
+ + + setActiveEntryId("")} + className={classes.modal} + > +
+ + Recent History + + +
+
+ + ); } interface RowProps { entry: SchedulerEntry; isLastRow: boolean; + onShowHistoryClick: () => void; } const useRowStyles = makeStyles((theme) => ({ - root: { + rowRoot: { "& > *": { borderBottom: "unset", }, }, - historyBox: { - maxWidth: 540, - }, noBorder: { border: "none", }, })); -// TODO: replace with real data -const history = [ - { enqueuedAt: "3m ago", taskId: "abc123" }, - { enqueuedAt: "10m ago", taskId: "xyz456" }, - { enqueuedAt: "30m ago", taskId: "dyz45f" }, -]; - function Row(props: RowProps) { const { entry, isLastRow } = props; const classes = useRowStyles(); - const [open, setOpen] = useState(false); return ( - - - - {entry.id} - - - {entry.spec} - - - {entry.task_type} - - - - {JSON.stringify(entry.task_payload)} - - - - - {entry.options.length > 0 ? entry.options.join(", ") : "No options"} - - - - {durationBefore(entry.next_enqueue_at)} - - - {entry.prev_enqueue_at ? timeAgo(entry.prev_enqueue_at) : "N/A"} - - - - setOpen(!open)} - > - {open ? : } - - - - - - - - - - History - - - - - Enqueued - Task ID - - - - {history.map((historyRow) => ( - - - {historyRow.enqueuedAt} - - {historyRow.taskId} - - ))} - -
-
-
-
-
-
+ + + {entry.id} + + + {entry.spec} + + + {entry.task_type} + + + + {JSON.stringify(entry.task_payload)} + + + + + {entry.options.length > 0 ? entry.options.join(", ") : "No options"} + + + + {durationBefore(entry.next_enqueue_at)} + + + {entry.prev_enqueue_at ? timeAgo(entry.prev_enqueue_at) : "N/A"} + + + + + + + + + ); } diff --git a/ui/src/reducers/schedulerEntriesReducer.ts b/ui/src/reducers/schedulerEntriesReducer.ts index 7d8d299..0eaf39d 100644 --- a/ui/src/reducers/schedulerEntriesReducer.ts +++ b/ui/src/reducers/schedulerEntriesReducer.ts @@ -1,3 +1,4 @@ +import uniqBy from "lodash.uniqby"; import { LIST_SCHEDULER_ENQUEUE_EVENTS_BEGIN, LIST_SCHEDULER_ENQUEUE_EVENTS_ERROR, @@ -18,7 +19,7 @@ interface SchedulerEntriesState { }; } -function getEnqueueEventsEntry( +export function getEnqueueEventsEntry( state: SchedulerEntriesState, entryId: string ): { data: SchedulerEnqueueEvent[]; loading: boolean } { @@ -70,14 +71,24 @@ function schedulerEntriesReducer( }; } case LIST_SCHEDULER_ENQUEUE_EVENTS_SUCCESS: { + const sortByEnqueuedAt = ( + e1: SchedulerEnqueueEvent, + e2: SchedulerEnqueueEvent + ): number => { + return Date.parse(e2.enqueued_at) - Date.parse(e1.enqueued_at); + }; const entry = getEnqueueEventsEntry(state, action.entryId); + const newData = uniqBy( + [...entry.data, ...action.payload.events], + "task_id" + ).sort(sortByEnqueuedAt); return { ...state, enqueueEventsByEntryId: { ...state.enqueueEventsByEntryId, [action.entryId]: { loading: false, - data: [...entry.data, ...action.payload.events], + data: newData, }, }, }; diff --git a/ui/yarn.lock b/ui/yarn.lock index 2a450ff..f9f9f2a 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -1795,6 +1795,18 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.6.tgz#f4c7ec43e81b319a9815115031709f26987891f0" integrity sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw== +"@types/lodash.uniqby@4.7.6": + version "4.7.6" + resolved "https://registry.yarnpkg.com/@types/lodash.uniqby/-/lodash.uniqby-4.7.6.tgz#672827a701403f07904fe37f0721ae92abfa80e8" + integrity sha512-9wBhrm1y6asW50Joj6tsySCNUgzK2tCqL7vtKIej0E9RyeBFdcte7fxUosmFuMoOU0eHqOMK76kCCrK99jxHgg== + dependencies: + "@types/lodash" "*" + +"@types/lodash@*": + version "4.14.166" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.166.tgz#07e7f2699a149219dbc3c35574f126ec8737688f" + integrity sha512-A3YT/c1oTlyvvW/GQqG86EyqWNrT/tisOIh2mW3YCgcx71TNjiTZA3zYZWA5BCmtsOTXjhliy4c4yEkErw6njA== + "@types/minimatch@*": version "3.0.3" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" @@ -7206,6 +7218,11 @@ lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= +lodash.uniqby@4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz#d99c07a669e9e6d24e1362dfe266c67616af1302" + integrity sha1-2ZwHpmnp5tJOE2Lf4mbGdhavEwI= + "lodash@>=3.5 <5", lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.5: version "4.17.15" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"