From 7e0ae2b4a63d652a894382dd33c70e59ea3b360f Mon Sep 17 00:00:00 2001 From: Ken Hibino Date: Sun, 3 Apr 2022 15:58:49 -0700 Subject: [PATCH] (ui): Use TasksTable component for aggregating tasks --- ui/src/components/AggregatingTasksTable.tsx | 498 +++--------------- .../AggregatingTasksTableContainer.tsx | 100 ++++ ui/src/components/TasksTable.tsx | 38 +- ui/src/components/TasksTableContainer.tsx | 4 +- 4 files changed, 206 insertions(+), 434 deletions(-) create mode 100644 ui/src/components/AggregatingTasksTableContainer.tsx diff --git a/ui/src/components/AggregatingTasksTable.tsx b/ui/src/components/AggregatingTasksTable.tsx index dcd420b..db0e84c 100644 --- a/ui/src/components/AggregatingTasksTable.tsx +++ b/ui/src/components/AggregatingTasksTable.tsx @@ -1,75 +1,36 @@ -import React, { useCallback, useState } from "react"; -import { connect, ConnectedProps } from "react-redux"; -import { makeStyles } from "@material-ui/core/styles"; import Checkbox from "@material-ui/core/Checkbox"; import IconButton from "@material-ui/core/IconButton"; -import Paper from "@material-ui/core/Paper"; -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 TableFooter from "@material-ui/core/TableFooter"; -import TableHead from "@material-ui/core/TableHead"; -import TablePagination from "@material-ui/core/TablePagination"; import TableRow from "@material-ui/core/TableRow"; import Tooltip from "@material-ui/core/Tooltip"; import ArchiveIcon from "@material-ui/icons/Archive"; import DeleteIcon from "@material-ui/icons/Delete"; +import FileCopyOutlinedIcon from "@material-ui/icons/FileCopyOutlined"; import MoreHorizIcon from "@material-ui/icons/MoreHoriz"; import PlayArrowIcon from "@material-ui/icons/PlayArrow"; -import FileCopyOutlinedIcon from "@material-ui/icons/FileCopyOutlined"; -import Alert from "@material-ui/lab/Alert"; -import AlertTitle from "@material-ui/lab/AlertTitle"; +import React from "react"; +import { connect, ConnectedProps } from "react-redux"; import { useHistory } from "react-router-dom"; -import { listGroupsAsync } from "../actions/groupsActions"; -import GroupSelect from "./GroupSelect"; -import TableActions from "./TableActions"; -import SyntaxHighlighter from "./SyntaxHighlighter"; -import { prettifyPayload, uuidPrefix } from "../utils"; -import { usePolling } from "../hooks"; -import { taskDetailsPath } from "../paths"; -import { AppState } from "../store"; -import { TaskInfoExtended } from "../reducers/tasksReducer"; -import { GroupInfo } from "../api"; -import { TableColumn } from "../types/table"; -import TablePaginationActions, { - rowsPerPageOptions, -} from "./TablePaginationActions"; +import { taskRowsPerPageChange } from "../actions/settingsActions"; import { - listAggregatingTasksAsync, - deleteAllAggregatingTasksAsync, + archiveAggregatingTaskAsync, archiveAllAggregatingTasksAsync, - runAllAggregatingTasksAsync, + batchArchiveAggregatingTasksAsync, batchDeleteAggregatingTasksAsync, batchRunAggregatingTasksAsync, - batchArchiveAggregatingTasksAsync, deleteAggregatingTaskAsync, + deleteAllAggregatingTasksAsync, + listAggregatingTasksAsync, runAggregatingTaskAsync, - archiveAggregatingTaskAsync, + runAllAggregatingTasksAsync, } from "../actions/tasksActions"; -import { taskRowsPerPageChange } from "../actions/settingsActions"; - -const useStyles = makeStyles((theme) => ({ - groupSelector: { - paddingTop: theme.spacing(1), - paddingBottom: theme.spacing(1), - paddingLeft: theme.spacing(2), - paddingRight: theme.spacing(2), - }, - table: { - minWidth: 650, - }, - stickyHeaderCell: { - background: theme.palette.background.paper, - }, - alert: { - borderTopLeftRadius: 0, - borderTopRightRadius: 0, - }, - pagination: { - border: "none", - }, -})); +import { PaginationOptions } from "../api"; +import { taskDetailsPath } from "../paths"; +import { AppState } from "../store"; +import { TableColumn } from "../types/table"; +import { prettifyPayload, uuidPrefix } from "../utils"; +import SyntaxHighlighter from "./SyntaxHighlighter"; +import TasksTable, { RowProps, useRowStyles } from "./TasksTable"; function mapStateToProps(state: AppState) { return { @@ -87,7 +48,6 @@ function mapStateToProps(state: AppState) { } const mapDispatchToProps = { - listGroupsAsync, listAggregatingTasksAsync, deleteAllAggregatingTasksAsync, archiveAllAggregatingTasksAsync, @@ -102,9 +62,12 @@ const mapDispatchToProps = { }; const connector = connect(mapStateToProps, mapDispatchToProps); +type ReduxProps = ConnectedProps; interface Props { queue: string; + selectedGroup: string; + totalTaskCount: number; // total number of tasks in the group } const columns: TableColumn[] = [ @@ -115,365 +78,6 @@ const columns: TableColumn[] = [ { key: "actions", label: "Actions", align: "center" }, ]; -function AggregatingTasksTable( - props: Props & ConnectedProps -) { - const [selectedGroup, setSelectedGroup] = useState(null); - const [page, setPage] = useState(0); - const [selectedIds, setSelectedIds] = useState([]); - const [activeTaskId, setActiveTaskId] = useState(""); - const { - pollInterval, - listGroupsAsync, - listAggregatingTasksAsync, - queue, - pageSize, - } = props; - const classes = useStyles(); - - const handlePageChange = ( - event: React.MouseEvent | null, - newPage: number - ) => { - setPage(newPage); - }; - - const handleRowsPerPageChange = ( - event: React.ChangeEvent - ) => { - props.taskRowsPerPageChange(parseInt(event.target.value, 10)); - setPage(0); - }; - - const handleSelectAllClick = (event: React.ChangeEvent) => { - if (event.target.checked) { - const newSelected = props.tasks.map((t) => t.id); - setSelectedIds(newSelected); - } else { - setSelectedIds([]); - } - }; - - const handleDeleteAllClick = () => { - if (selectedGroup === null) { - return; - } - props.deleteAllAggregatingTasksAsync(queue, selectedGroup.group); - }; - - const handleArchiveAllClick = () => { - if (selectedGroup === null) { - return; - } - props.archiveAllAggregatingTasksAsync(queue, selectedGroup.group); - }; - - const handleRunAllClick = () => { - if (selectedGroup === null) { - return; - } - props.runAllAggregatingTasksAsync(queue, selectedGroup.group); - }; - - const handleBatchRunClick = () => { - if (selectedGroup === null) { - return; - } - props - .batchRunAggregatingTasksAsync(queue, selectedGroup.group, selectedIds) - .then(() => setSelectedIds([])); - }; - - const handleBatchDeleteClick = () => { - if (selectedGroup === null) { - return; - } - props - .batchDeleteAggregatingTasksAsync(queue, selectedGroup.group, selectedIds) - .then(() => setSelectedIds([])); - }; - - const handleBatchArchiveClick = () => { - if (selectedGroup === null) { - return; - } - props - .batchArchiveAggregatingTasksAsync( - queue, - selectedGroup.group, - selectedIds - ) - .then(() => setSelectedIds([])); - }; - - const fetchGroups = useCallback(() => { - listGroupsAsync(queue); - }, [listGroupsAsync, queue]); - - const fetchTasks = useCallback(() => { - const pageOpts = { page: page + 1, size: pageSize }; - if (selectedGroup !== null) { - listAggregatingTasksAsync(queue, selectedGroup.group, pageOpts); - } - }, [page, pageSize, queue, selectedGroup, listAggregatingTasksAsync]); - - usePolling(fetchGroups, pollInterval); - usePolling(fetchTasks, pollInterval); - - if (props.error.length > 0) { - return ( - - Error - {props.error} - - ); - } - if (props.groups.length === 0) { - return ( - - Info - No aggregating tasks at this time. - - ); - } - - const rowCount = props.tasks.length; - const numSelected = selectedIds.length; - return ( -
-
- -
- {props.tasks.length > 0 && selectedGroup !== null ? ( - <> - {!window.READ_ONLY && ( - 0} - iconButtonActions={[ - { - tooltip: "Delete", - icon: , - onClick: handleBatchDeleteClick, - disabled: props.batchActionPending, - }, - { - tooltip: "Archive", - icon: , - onClick: handleBatchArchiveClick, - disabled: props.batchActionPending, - }, - { - tooltip: "Run", - icon: , - onClick: handleBatchRunClick, - disabled: props.batchActionPending, - }, - ]} - menuItemActions={[ - { - label: "Delete All", - onClick: handleDeleteAllClick, - disabled: props.allActionPending, - }, - { - label: "Archive All", - onClick: handleArchiveAllClick, - disabled: props.allActionPending, - }, - { - label: "Run All", - onClick: handleRunAllClick, - disabled: props.allActionPending, - }, - ]} - /> - )} - - - - - {!window.READ_ONLY && ( - - - 0 && numSelected < rowCount - } - checked={rowCount > 0 && numSelected === rowCount} - onChange={handleSelectAllClick} - inputProps={{ - "aria-label": "select all tasks shown in the table", - }} - /> - - - )} - {columns - .filter((col) => { - // Filter out actions column in readonly mode. - return !window.READ_ONLY || col.key !== "actions"; - }) - .map((col) => ( - - {col.label} - - ))} - - - - {props.group === selectedGroup.group && - props.tasks.map((task) => ( - { - if (checked) { - setSelectedIds(selectedIds.concat(task.id)); - } else { - setSelectedIds( - selectedIds.filter((id) => id !== task.id) - ); - } - }} - allActionPending={props.allActionPending} - onDeleteClick={() => { - if (selectedGroup === null) return; - props.deleteAggregatingTaskAsync( - queue, - selectedGroup.group, - task.id - ); - }} - onArchiveClick={() => { - if (selectedGroup === null) return; - props.archiveAggregatingTaskAsync( - queue, - selectedGroup.group, - task.id - ); - }} - onRunClick={() => { - if (selectedGroup === null) return; - props.runAggregatingTaskAsync( - queue, - selectedGroup.group, - task.id - ); - }} - onActionCellEnter={() => setActiveTaskId(task.id)} - onActionCellLeave={() => setActiveTaskId("")} - showActions={activeTaskId === task.id} - /> - ))} - - - - - - -
-
- - ) : ( - - Info - {selectedGroup === null ? ( -
Please select group
- ) : ( -
Group {selectedGroup.group} is empty
- )} -
- )} -
- ); -} - -const useRowStyles = makeStyles((theme) => ({ - root: { - cursor: "pointer", - "& #copy-button": { - display: "none", - }, - "&:hover": { - boxShadow: theme.shadows[2], - "& #copy-button": { - display: "inline-block", - }, - }, - "&:hover $copyButton": { - display: "inline-block", - }, - "&:hover .MuiTableCell-root": { - borderBottomColor: theme.palette.background.paper, - }, - }, - - actionCell: { - width: "96px", - }, - actionButton: { - marginLeft: 3, - marginRight: 3, - }, - idCell: { - width: "200px", - }, - copyButton: { - display: "none", - }, - IdGroup: { - display: "flex", - alignItems: "center", - }, -})); - -interface RowProps { - task: TaskInfoExtended; - isSelected: boolean; - onSelectChange: (checked: boolean) => void; - onDeleteClick: () => void; - onArchiveClick: () => void; - onRunClick: () => void; - allActionPending: boolean; - showActions: boolean; - onActionCellEnter: () => void; - onActionCellLeave: () => void; -} - function Row(props: RowProps) { const { task } = props; const classes = useRowStyles(); @@ -576,4 +180,68 @@ function Row(props: RowProps) { ); } +function AggregatingTasksTable(props: Props & ReduxProps) { + const listTasks = (qname: string, pgn?: PaginationOptions) => + props.listAggregatingTasksAsync(qname, props.selectedGroup, pgn); + + const deleteAllTasks = (qname: string) => + props.deleteAllAggregatingTasksAsync(qname, props.selectedGroup); + + const archiveAllTasks = (qname: string) => + props.archiveAllAggregatingTasksAsync(qname, props.selectedGroup); + + const runAllTasks = (qname: string) => + props.runAllAggregatingTasksAsync(qname, props.selectedGroup); + + const batchDeleteTasks = (qname: string, taskIds: string[]) => + props.batchDeleteAggregatingTasksAsync(qname, props.selectedGroup, taskIds); + + const batchArchiveTasks = (qname: string, taskIds: string[]) => + props.batchArchiveAggregatingTasksAsync( + qname, + props.selectedGroup, + taskIds + ); + + const batchRunTasks = (qname: string, taskIds: string[]) => + props.batchRunAggregatingTasksAsync(qname, props.selectedGroup, taskIds); + + const deleteTask = (qname: string, taskId: string) => + props.deleteAggregatingTaskAsync(qname, props.selectedGroup, taskId); + + const archiveTask = (qname: string, taskId: string) => + props.archiveAggregatingTaskAsync(qname, props.selectedGroup, taskId); + + const runTask = (qname: string, taskId: string) => + props.runAggregatingTaskAsync(qname, props.selectedGroup, taskId); + + return ( + } + /> + ); +} + export default connector(AggregatingTasksTable); diff --git a/ui/src/components/AggregatingTasksTableContainer.tsx b/ui/src/components/AggregatingTasksTableContainer.tsx new file mode 100644 index 0000000..05e5f1d --- /dev/null +++ b/ui/src/components/AggregatingTasksTableContainer.tsx @@ -0,0 +1,100 @@ +import { makeStyles } from "@material-ui/core/styles"; +import Alert from "@material-ui/lab/Alert"; +import AlertTitle from "@material-ui/lab/AlertTitle"; +import React, { useCallback, useState } from "react"; +import { connect, ConnectedProps } from "react-redux"; +import { listGroupsAsync } from "../actions/groupsActions"; +import { GroupInfo } from "../api"; +import { usePolling } from "../hooks"; +import { AppState } from "../store"; +import AggregatingTasksTable from "./AggregatingTasksTable"; +import GroupSelect from "./GroupSelect"; + +const useStyles = makeStyles((theme) => ({ + groupSelector: { + paddingTop: theme.spacing(1), + paddingBottom: theme.spacing(1), + paddingLeft: theme.spacing(2), + paddingRight: theme.spacing(2), + }, + alert: { + borderTopLeftRadius: 0, + borderTopRightRadius: 0, + }, +})); + +function mapStateToProps(state: AppState) { + return { + groups: state.groups.data, + groupsError: state.groups.error, + pollInterval: state.settings.pollInterval, + }; +} + +const mapDispatchToProps = { + listGroupsAsync, +}; + +const connector = connect(mapStateToProps, mapDispatchToProps); + +interface Props { + queue: string; +} + +function AggregatingTasksTableContainer( + props: Props & ConnectedProps +) { + const [selectedGroup, setSelectedGroup] = useState(null); + const { pollInterval, listGroupsAsync, queue } = props; + const classes = useStyles(); + + const fetchGroups = useCallback(() => { + listGroupsAsync(queue); + }, [listGroupsAsync, queue]); + + usePolling(fetchGroups, pollInterval); + + if (props.groupsError.length > 0) { + return ( + + Error + {props.groupsError} + + ); + } + if (props.groups.length === 0) { + return ( + + Info + No aggregating tasks at this time. + + ); + } + + return ( +
+
+ +
+ {selectedGroup !== null ? ( + + ) : ( + + Info +
Please select group
+
+ )} +
+ ); +} + +export default connector(AggregatingTasksTableContainer); diff --git a/ui/src/components/TasksTable.tsx b/ui/src/components/TasksTable.tsx index 02ca62f..27e1baf 100644 --- a/ui/src/components/TasksTable.tsx +++ b/ui/src/components/TasksTable.tsx @@ -105,17 +105,17 @@ export default function TasksTable(props: Props) { } }; - function createAllTasksHandler(action: (qname: string) => Promise) { + function createAllActionHandler(action: (qname: string) => Promise) { return () => action(queue); } - function createBatchTasksHandler( + function createBatchActionHandler( action: (qname: string, taskIds: string[]) => Promise ) { return () => action(queue, selectedIds).then(() => setSelectedIds([])); } - function createTaskAction( + function createSingleActionHandler( action: (qname: string, taskId: string) => Promise, taskId: string ) { @@ -126,28 +126,28 @@ export default function TasksTable(props: Props) { if (props.deleteAllTasks) { allActions.push({ label: "Delete All", - onClick: createAllTasksHandler(props.deleteAllTasks), + onClick: createAllActionHandler(props.deleteAllTasks), disabled: props.allActionPending, }); } if (props.archiveAllTasks) { allActions.push({ label: "Archive All", - onClick: createAllTasksHandler(props.archiveAllTasks), + onClick: createAllActionHandler(props.archiveAllTasks), disabled: props.allActionPending, }); } if (props.runAllTasks) { allActions.push({ label: "Run All", - onClick: createAllTasksHandler(props.runAllTasks), + onClick: createAllActionHandler(props.runAllTasks), disabled: props.allActionPending, }); } if (props.cancelAllTasks) { allActions.push({ label: "Cancel All", - onClick: createAllTasksHandler(props.cancelAllTasks), + onClick: createAllActionHandler(props.cancelAllTasks), disabled: props.allActionPending, }); } @@ -158,7 +158,7 @@ export default function TasksTable(props: Props) { tooltip: "Delete", icon: , disabled: props.batchActionPending, - onClick: createBatchTasksHandler(props.batchDeleteTasks), + onClick: createBatchActionHandler(props.batchDeleteTasks), }); } if (props.batchArchiveTasks) { @@ -166,7 +166,7 @@ export default function TasksTable(props: Props) { tooltip: "Archive", icon: , disabled: props.batchActionPending, - onClick: createBatchTasksHandler(props.batchArchiveTasks), + onClick: createBatchActionHandler(props.batchArchiveTasks), }); } if (props.batchRunTasks) { @@ -174,7 +174,7 @@ export default function TasksTable(props: Props) { tooltip: "Run", icon: , disabled: props.batchActionPending, - onClick: createBatchTasksHandler(props.batchRunTasks), + onClick: createBatchActionHandler(props.batchRunTasks), }); } if (props.batchCancelTasks) { @@ -182,7 +182,7 @@ export default function TasksTable(props: Props) { tooltip: "Cancel", icon: , disabled: props.batchActionPending, - onClick: createBatchTasksHandler(props.batchCancelTasks), + onClick: createBatchActionHandler(props.batchCancelTasks), }); } @@ -205,7 +205,11 @@ export default function TasksTable(props: Props) { return ( Info - No {props.taskState} tasks at this time. + {props.taskState === "aggregating" ? ( +
Selected group is empty.
+ ) : ( +
No {props.taskState} tasks at this time.
+ )}
); } @@ -278,16 +282,16 @@ export default function TasksTable(props: Props) { } }, onRunClick: props.runTask - ? createTaskAction(props.runTask, task.id) + ? createSingleActionHandler(props.runTask, task.id) : undefined, onDeleteClick: props.deleteTask - ? createTaskAction(props.deleteTask, task.id) + ? createSingleActionHandler(props.deleteTask, task.id) : undefined, onArchiveClick: props.archiveTask - ? createTaskAction(props.archiveTask, task.id) + ? createSingleActionHandler(props.archiveTask, task.id) : undefined, onCancelClick: props.cancelTask - ? createTaskAction(props.cancelTask, task.id) + ? createSingleActionHandler(props.cancelTask, task.id) : undefined, onActionCellEnter: () => setActiveTaskId(task.id), onActionCellLeave: () => setActiveTaskId(""), @@ -340,7 +344,7 @@ export const useRowStyles = makeStyles((theme) => ({ }, }, actionCell: { - width: "140px", // TODO: This was 96px for pending/archived/completed row + width: "140px", }, actionButton: { marginLeft: 3, diff --git a/ui/src/components/TasksTableContainer.tsx b/ui/src/components/TasksTableContainer.tsx index 19d356c..24d9e27 100644 --- a/ui/src/components/TasksTableContainer.tsx +++ b/ui/src/components/TasksTableContainer.tsx @@ -12,7 +12,7 @@ import ScheduledTasksTable from "./ScheduledTasksTable"; import RetryTasksTable from "./RetryTasksTable"; import ArchivedTasksTable from "./ArchivedTasksTable"; import CompletedTasksTable from "./CompletedTasksTable"; -import AggregatingTasksTable from "./AggregatingTasksTable"; +import AggregatingTasksTableContainer from "./AggregatingTasksTableContainer"; import { useHistory } from "react-router-dom"; import { queueDetailsPath, taskDetailsPath } from "../paths"; import { QueueInfo } from "../reducers/queuesReducer"; @@ -229,7 +229,7 @@ function TasksTableContainer(props: Props & ReduxProps) { /> - +