import React, { useState, useCallback } from "react"; 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 TableFooter from "@material-ui/core/TableFooter"; import TablePagination from "@material-ui/core/TablePagination"; import Paper from "@material-ui/core/Paper"; import Checkbox from "@material-ui/core/Checkbox"; import IconButton from "@material-ui/core/IconButton"; import PlayArrowIcon from "@material-ui/icons/PlayArrow"; import DeleteIcon from "@material-ui/icons/Delete"; import ArchiveIcon from "@material-ui/icons/Archive"; import CancelIcon from "@material-ui/icons/Cancel"; import Alert from "@material-ui/lab/Alert"; import AlertTitle from "@material-ui/lab/AlertTitle"; import TablePaginationActions, { rowsPerPageOptions, } from "./TablePaginationActions"; import TableActions from "./TableActions"; import { usePolling } from "../hooks"; import { TaskInfoExtended } from "../reducers/tasksReducer"; import { TableColumn } from "../types/table"; import { PaginationOptions } from "../api"; import { TaskState } from "../types/taskState"; const useStyles = makeStyles((theme) => ({ table: { minWidth: 650, }, stickyHeaderCell: { background: theme.palette.background.paper, }, alert: { borderTopLeftRadius: 0, borderTopRightRadius: 0, }, pagination: { border: "none", }, })); interface Props { queue: string; // name of the queue. totalTaskCount: number; // totoal number of tasks in the given state. taskState: TaskState; loading: boolean; error: string; tasks: TaskInfoExtended[]; batchActionPending: boolean; allActionPending: boolean; pollInterval: number; pageSize: number; columns: TableColumn[]; // actions listTasks: (qname: string, pgn: PaginationOptions) => void; batchDeleteTasks?: (qname: string, taskIds: string[]) => Promise; batchRunTasks?: (qname: string, taskIds: string[]) => Promise; batchArchiveTasks?: (qname: string, taskIds: string[]) => Promise; batchCancelTasks?: (qname: string, taskIds: string[]) => Promise; deleteAllTasks?: (qname: string) => Promise; runAllTasks?: (qname: string) => Promise; archiveAllTasks?: (qname: string) => Promise; cancelAllTasks?: (qname: string) => Promise; deleteTask?: (qname: string, taskId: string) => Promise; runTask?: (qname: string, taskId: string) => Promise; archiveTask?: (qname: string, taskId: string) => Promise; cancelTask?: (qname: string, taskId: string) => Promise; taskRowsPerPageChange: (n: number) => void; renderRow: (rowProps: RowProps) => JSX.Element; } export default function TasksTable(props: Props) { const { pollInterval, listTasks, queue, pageSize } = props; const classes = useStyles(); const [page, setPage] = useState(0); const [selectedIds, setSelectedIds] = useState([]); const [activeTaskId, setActiveTaskId] = useState(""); 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([]); } }; function createAllActionHandler(action: (qname: string) => Promise) { return () => action(queue); } function createBatchActionHandler( action: (qname: string, taskIds: string[]) => Promise ) { return () => action(queue, selectedIds).then(() => setSelectedIds([])); } function createSingleActionHandler( action: (qname: string, taskId: string) => Promise, taskId: string ) { return () => action(queue, taskId); } let allActions = []; if (props.deleteAllTasks) { allActions.push({ label: "Delete All", onClick: createAllActionHandler(props.deleteAllTasks), disabled: props.allActionPending, }); } if (props.archiveAllTasks) { allActions.push({ label: "Archive All", onClick: createAllActionHandler(props.archiveAllTasks), disabled: props.allActionPending, }); } if (props.runAllTasks) { allActions.push({ label: "Run All", onClick: createAllActionHandler(props.runAllTasks), disabled: props.allActionPending, }); } if (props.cancelAllTasks) { allActions.push({ label: "Cancel All", onClick: createAllActionHandler(props.cancelAllTasks), disabled: props.allActionPending, }); } let batchActions = []; if (props.batchDeleteTasks) { batchActions.push({ tooltip: "Delete", icon: , disabled: props.batchActionPending, onClick: createBatchActionHandler(props.batchDeleteTasks), }); } if (props.batchArchiveTasks) { batchActions.push({ tooltip: "Archive", icon: , disabled: props.batchActionPending, onClick: createBatchActionHandler(props.batchArchiveTasks), }); } if (props.batchRunTasks) { batchActions.push({ tooltip: "Run", icon: , disabled: props.batchActionPending, onClick: createBatchActionHandler(props.batchRunTasks), }); } if (props.batchCancelTasks) { batchActions.push({ tooltip: "Cancel", icon: , disabled: props.batchActionPending, onClick: createBatchActionHandler(props.batchCancelTasks), }); } const fetchData = useCallback(() => { const pageOpts = { page: page + 1, size: pageSize }; listTasks(queue, pageOpts); }, [page, pageSize, queue, listTasks]); usePolling(fetchData, pollInterval); if (props.error.length > 0) { return ( Error {props.error} ); } if (props.tasks.length === 0) { return ( Info {props.taskState === "aggregating" ? (
Selected group is empty.
) : (
No {props.taskState} tasks at this time.
)}
); } const rowCount = props.tasks.length; const numSelected = selectedIds.length; return (
{!window.READ_ONLY && ( 0} iconButtonActions={batchActions} menuItemActions={allActions} /> )} {!window.READ_ONLY && ( 0 && numSelected < rowCount} checked={rowCount > 0 && numSelected === rowCount} onChange={handleSelectAllClick} inputProps={{ "aria-label": "select all tasks shown in the table", }} /> )} {props.columns .filter((col) => { // Filter out actions column in readonly mode. return !window.READ_ONLY || col.key !== "actions"; }) .map((col) => ( {col.label} ))} {props.tasks.map((task) => { return props.renderRow({ key: task.id, task: task, allActionPending: props.allActionPending, isSelected: selectedIds.includes(task.id), onSelectChange: (checked: boolean) => { if (checked) { setSelectedIds(selectedIds.concat(task.id)); } else { setSelectedIds(selectedIds.filter((id) => id !== task.id)); } }, onRunClick: props.runTask ? createSingleActionHandler(props.runTask, task.id) : undefined, onDeleteClick: props.deleteTask ? createSingleActionHandler(props.deleteTask, task.id) : undefined, onArchiveClick: props.archiveTask ? createSingleActionHandler(props.archiveTask, task.id) : undefined, onCancelClick: props.cancelTask ? createSingleActionHandler(props.cancelTask, task.id) : undefined, onActionCellEnter: () => setActiveTaskId(task.id), onActionCellLeave: () => setActiveTaskId(""), showActions: activeTaskId === task.id, }); })}
); } export 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: "140px", }, actionButton: { marginLeft: 3, marginRight: 3, }, idCell: { width: "200px", }, copyButton: { display: "none", }, IdGroup: { display: "flex", alignItems: "center", }, })); export interface RowProps { key: string; task: TaskInfoExtended; isSelected: boolean; onSelectChange: (checked: boolean) => void; onRunClick?: () => void; onDeleteClick?: () => void; onArchiveClick?: () => void; onCancelClick?: () => void; allActionPending: boolean; showActions: boolean; onActionCellEnter: () => void; onActionCellLeave: () => void; }