diff --git a/ui/src/components/DeadTasksTable.tsx b/ui/src/components/DeadTasksTable.tsx index e20026f..87e478f 100644 --- a/ui/src/components/DeadTasksTable.tsx +++ b/ui/src/components/DeadTasksTable.tsx @@ -5,7 +5,6 @@ import Table from "@material-ui/core/Table"; import TableBody from "@material-ui/core/TableBody"; import TableCell from "@material-ui/core/TableCell"; import Button from "@material-ui/core/Button"; -import ButtonGroup from "@material-ui/core/ButtonGroup"; import Checkbox from "@material-ui/core/Checkbox"; import TableContainer from "@material-ui/core/TableContainer"; import TableHead from "@material-ui/core/TableHead"; @@ -14,14 +13,11 @@ import Paper from "@material-ui/core/Paper"; import Box from "@material-ui/core/Box"; import Collapse from "@material-ui/core/Collapse"; import IconButton from "@material-ui/core/IconButton"; -import Menu from "@material-ui/core/Menu"; -import MenuItem from "@material-ui/core/MenuItem"; import KeyboardArrowUpIcon from "@material-ui/icons/KeyboardArrowUp"; import KeyboardArrowDownIcon from "@material-ui/icons/KeyboardArrowDown"; import Typography from "@material-ui/core/Typography"; import TableFooter from "@material-ui/core/TableFooter"; import TablePagination from "@material-ui/core/TablePagination"; -import MoreHorizIcon from "@material-ui/icons/MoreHoriz"; import Alert from "@material-ui/lab/Alert"; import AlertTitle from "@material-ui/lab/AlertTitle"; import SyntaxHighlighter from "react-syntax-highlighter"; @@ -40,6 +36,7 @@ import TablePaginationActions, { defaultPageSize, rowsPerPageOptions, } from "./TablePaginationActions"; +import TableActions from "./TableActions"; import { timeAgo } from "../timeutil"; import { usePolling } from "../hooks"; import { DeadTaskExtended } from "../reducers/tasksReducer"; @@ -48,12 +45,6 @@ const useStyles = makeStyles({ table: { minWidth: 650, }, - actionsContainer: { - padding: "4px", - }, - moreIcon: { - marginRight: "8px", - }, }); const useRowStyles = makeStyles({ @@ -99,7 +90,6 @@ function DeadTasksTable(props: Props & ReduxProps) { const [page, setPage] = useState(0); const [pageSize, setPageSize] = useState(defaultPageSize); const [selectedKeys, setSelectedKeys] = useState([]); - const [menuAnchor, setMenuAnchor] = useState(null); const handleChangePage = ( event: React.MouseEvent | null, @@ -124,20 +114,24 @@ function DeadTasksTable(props: Props & ReduxProps) { } }; - const handleMenuClick = (event: React.MouseEvent) => { - setMenuAnchor(event.currentTarget); - }; - - const handleMenuClose = () => setMenuAnchor(null); - const handleRunAllClick = () => { props.runAllDeadTasksAsync(queue); - setMenuAnchor(null); }; const handleDeleteAllClick = () => { props.deleteAllDeadTasksAsync(queue); - setMenuAnchor(null); + }; + + const handleBatchRunClick = () => { + props + .batchDeleteDeadTasksAsync(queue, selectedKeys) + .then(() => setSelectedKeys([])); + }; + + const handleBatchDeleteClick = () => { + props + .batchRunDeadTasksAsync(queue, selectedKeys) + .then(() => setSelectedKeys([])); }; const fetchData = useCallback(() => { @@ -169,64 +163,15 @@ function DeadTasksTable(props: Props & ReduxProps) { const numSelected = selectedKeys.length; return (
-
- - - - - - Run All - - - Delete All - - - {numSelected > 0 && ( - - - - - - )} -
+ 0} + onRunAllClick={handleRunAllClick} + onDeleteAllClick={handleDeleteAllClick} + onBatchRunClick={handleBatchRunClick} + onBatchDeleteClick={handleBatchDeleteClick} + /> -
- - - - 0 && numSelected < rowCount} - checked={rowCount > 0 && numSelected === rowCount} - onChange={handleSelectAllClick} - inputProps={{ - "aria-label": "select all tasks shown in the table", +
+ 0} + onRunAllClick={() => console.log("TODO")} + onDeleteAllClick={() => console.log("TODO")} + onBatchRunClick={() => console.log("TODO")} + onBatchDeleteClick={() => console.log("TODO")} + /> + +
+ + + + 0 && numSelected < rowCount} + checked={rowCount > 0 && numSelected === rowCount} + onChange={handleSelectAllClick} + inputProps={{ + "aria-label": "select all tasks shown in the table", + }} + /> + + {columns.map((col) => ( + {col.label} + ))} + + + + {props.tasks.map((task) => ( + { + if (checked) { + setSelectedKeys(selectedKeys.concat(task.key)); + } else { + setSelectedKeys( + selectedKeys.filter((key) => key !== task.key) + ); + } + }} + onDeleteClick={() => { + props.deleteRetryTaskAsync(task.queue, task.key); }} /> - - {columns.map((col) => ( - {col.label} ))} - - - - {props.tasks.map((task) => ( - { - if (checked) { - setSelectedKeys(selectedKeys.concat(task.key)); - } else { - setSelectedKeys( - selectedKeys.filter((key) => key !== task.key) - ); - } - }} - onDeleteClick={() => { - props.deleteRetryTaskAsync(task.queue, task.key); - }} - /> - ))} - - - - - - -
-
+ + + + + + + + +
); } diff --git a/ui/src/components/ScheduledTasksTable.tsx b/ui/src/components/ScheduledTasksTable.tsx index 0228b4e..1bddcc2 100644 --- a/ui/src/components/ScheduledTasksTable.tsx +++ b/ui/src/components/ScheduledTasksTable.tsx @@ -31,6 +31,7 @@ import TablePaginationActions, { defaultPageSize, rowsPerPageOptions, } from "./TablePaginationActions"; +import TableActions from "./TableActions"; import { durationBefore } from "../timeutil"; import { usePolling } from "../hooks"; import { ScheduledTaskExtended } from "../reducers/tasksReducer"; @@ -45,6 +46,8 @@ function mapStateToProps(state: AppState) { return { loading: state.tasks.scheduledTasks.loading, tasks: state.tasks.scheduledTasks.data, + batchActionPending: state.tasks.scheduledTasks.batchActionPending, + allActionPending: state.tasks.scheduledTasks.allActionPending, pollInterval: state.settings.pollInterval, }; } @@ -120,71 +123,82 @@ function ScheduledTasksTable(props: Props & ReduxProps) { const rowCount = props.tasks.length; const numSelected = selectedKeys.length; return ( - - - - - - 0 && numSelected < rowCount} - checked={rowCount > 0 && numSelected === rowCount} - onChange={handleSelectAllClick} - inputProps={{ - "aria-label": "select all tasks shown in the table", +
+ 0} + onRunAllClick={() => console.log("TODO")} + onDeleteAllClick={() => console.log("TODO")} + onBatchRunClick={() => console.log("TODO")} + onBatchDeleteClick={() => console.log("TODO")} + /> + +
+ + + + 0 && numSelected < rowCount} + checked={rowCount > 0 && numSelected === rowCount} + onChange={handleSelectAllClick} + inputProps={{ + "aria-label": "select all tasks shown in the table", + }} + /> + + {columns.map((col) => ( + {col.label} + ))} + + + + {props.tasks.map((task) => ( + { + if (checked) { + setSelectedKeys(selectedKeys.concat(task.key)); + } else { + setSelectedKeys( + selectedKeys.filter((key) => key !== task.key) + ); + } + }} + onDeleteClick={() => { + props.deleteScheduledTaskAsync(queue, task.key); }} /> - - {columns.map((col) => ( - {col.label} ))} - - - - {props.tasks.map((task) => ( - { - if (checked) { - setSelectedKeys(selectedKeys.concat(task.key)); - } else { - setSelectedKeys( - selectedKeys.filter((key) => key !== task.key) - ); - } - }} - onDeleteClick={() => { - props.deleteScheduledTaskAsync(queue, task.key); - }} - /> - ))} - - - - - - -
-
+ + + + + + + + + ); } diff --git a/ui/src/components/TableActions.tsx b/ui/src/components/TableActions.tsx new file mode 100644 index 0000000..8a547e6 --- /dev/null +++ b/ui/src/components/TableActions.tsx @@ -0,0 +1,97 @@ +import React, { useState } from "react"; +import { makeStyles } from "@material-ui/core/styles"; +import Button from "@material-ui/core/Button"; +import ButtonGroup from "@material-ui/core/ButtonGroup"; +import IconButton from "@material-ui/core/IconButton"; +import Menu from "@material-ui/core/Menu"; +import MenuItem from "@material-ui/core/MenuItem"; +import MoreHorizIcon from "@material-ui/icons/MoreHoriz"; + +const useStyles = makeStyles({ + actionsContainer: { + padding: "4px", + }, + moreIcon: { + marginRight: "8px", + }, +}); + +interface Props { + allActionPending: boolean; + onRunAllClick: () => void; + onDeleteAllClick: () => void; + showBatchActions: boolean; + batchActionPending: boolean; + onBatchRunClick: () => void; + onBatchDeleteClick: () => void; +} + +export default function TableActions(props: Props) { + const classes = useStyles(); + const [menuAnchor, setMenuAnchor] = useState(null); + + const handleMenuClick = (event: React.MouseEvent) => { + setMenuAnchor(event.currentTarget); + }; + + const closeMenu = () => setMenuAnchor(null); + + return ( +
+ + + + + { + props.onRunAllClick(); + closeMenu(); + }} + disabled={props.allActionPending} + > + Run All + + { + props.onDeleteAllClick(); + closeMenu(); + }} + disabled={props.allActionPending} + > + Delete All + + + {props.showBatchActions && ( + + + + + + )} +
+ ); +} diff --git a/ui/src/reducers/tasksReducer.ts b/ui/src/reducers/tasksReducer.ts index ae9453c..94d6180 100644 --- a/ui/src/reducers/tasksReducer.ts +++ b/ui/src/reducers/tasksReducer.ts @@ -92,11 +92,15 @@ interface TasksState { }; scheduledTasks: { loading: boolean; + batchActionPending: boolean; + allActionPending: boolean; error: string; data: ScheduledTaskExtended[]; }; retryTasks: { loading: boolean; + batchActionPending: boolean; + allActionPending: boolean; error: string; data: RetryTaskExtended[]; }; @@ -122,11 +126,15 @@ const initialState: TasksState = { }, scheduledTasks: { loading: false, + batchActionPending: false, + allActionPending: false, error: "", data: [], }, retryTasks: { loading: false, + batchActionPending: false, + allActionPending: false, error: "", data: [], }, @@ -222,6 +230,7 @@ function tasksReducer( return { ...state, scheduledTasks: { + ...state.scheduledTasks, loading: false, error: "", data: action.payload.tasks.map((task) => ({ @@ -255,6 +264,7 @@ function tasksReducer( return { ...state, retryTasks: { + ...state.retryTasks, loading: false, error: "", data: action.payload.tasks.map((task) => ({