import React , { useState , useCallback } from "react" ; import { useHistory } from "react-router-dom" ; 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 TableFooter from "@material-ui/core/TableFooter" ; import TablePagination from "@material-ui/core/TablePagination" ; import Paper from "@material-ui/core/Paper" ; import Tooltip from "@material-ui/core/Tooltip" ; 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 MoreHorizIcon from "@material-ui/icons/MoreHoriz" ; import Alert from "@material-ui/lab/Alert" ; import AlertTitle from "@material-ui/lab/AlertTitle" ; import SyntaxHighlighter from "./SyntaxHighlighter" ; import { batchDeleteScheduledTasksAsync , batchRunScheduledTasksAsync , batchArchiveScheduledTasksAsync , deleteAllScheduledTasksAsync , runAllScheduledTasksAsync , archiveAllScheduledTasksAsync , listScheduledTasksAsync , deleteScheduledTaskAsync , runScheduledTaskAsync , archiveScheduledTaskAsync , } from "../actions/tasksActions" ; import { taskRowsPerPageChange } from "../actions/settingsActions" ; import { AppState } from "../store" ; import TablePaginationActions , { rowsPerPageOptions , } from "./TablePaginationActions" ; import TableActions from "./TableActions" ; import { durationBefore , uuidPrefix , prettifyPayload } from "../utils" ; import { usePolling } from "../hooks" ; import { TaskInfoExtended } from "../reducers/tasksReducer" ; import { TableColumn } from "../types/table" ; import { taskDetailsPath } from "../paths" ; import FileCopyOutlinedIcon from '@material-ui/icons/FileCopyOutlined' ; const useStyles = makeStyles ( ( theme ) => ( { table : { minWidth : 650 , } , stickyHeaderCell : { background : theme . palette . background . paper , } , alert : { borderTopLeftRadius : 0 , borderTopRightRadius : 0 , } , pagination : { border : "none" , } , } ) ) ; function mapStateToProps ( state ) { return { loading : state . tasks . scheduledTasks . loading , error : state . tasks . scheduledTasks . error , tasks : state . tasks . scheduledTasks . data , batchActionPending : state . tasks . scheduledTasks . batchActionPending , allActionPending : state . tasks . scheduledTasks . allActionPending , pollInterval : state . settings . pollInterval , pageSize : state . settings . taskRowsPerPage , } ; } const mapDispatchToProps = { listScheduledTasksAsync , batchDeleteScheduledTasksAsync , batchRunScheduledTasksAsync , batchArchiveScheduledTasksAsync , deleteAllScheduledTasksAsync , runAllScheduledTasksAsync , archiveAllScheduledTasksAsync , deleteScheduledTaskAsync , runScheduledTaskAsync , archiveScheduledTaskAsync , taskRowsPerPageChange , } ; const connector = connect ( mapStateToProps , mapDispatchToProps ) ; type ReduxProps = ConnectedProps < typeof connector > ; interface Props { queue : string ; totalTaskCount : number ; } function ScheduledTasksTable ( props ) { const { pollInterval , listScheduledTasksAsync , queue , pageSize } = props ; const classes = useStyles ( ) ; const [ page , setPage ] = useState ( 0 ) ; const [ selectedIds , setSelectedIds ] = useState < string [ ] > ( [ ] ) ; const [ activeTaskId , setActiveTaskId ] = useState < string > ( "" ) ; const handlePageChange = ( event , newPage ) => { setPage ( newPage ) ; } ; const handleRowsPerPageChange = ( event ) => { props . taskRowsPerPageChange ( parseInt ( event . target . value , 10 ) ) ; setPage ( 0 ) ; } ; const handleSelectAllClick = ( event ) => { if ( event . target . checked ) { const newSelected = props . tasks . map ( ( t ) => t . id ) ; setSelectedIds ( newSelected ) ; } else { setSelectedIds ( [ ] ) ; } } ; const handleRunAllClick = ( ) => { props . runAllScheduledTasksAsync ( queue ) ; } ; const handleDeleteAllClick = ( ) => { props . deleteAllScheduledTasksAsync ( queue ) ; } ; const handleArchiveAllClick = ( ) => { props . archiveAllScheduledTasksAsync ( queue ) ; } ; const handleBatchRunClick = ( ) => { props . batchRunScheduledTasksAsync ( queue , selectedIds ) . then ( ( ) => setSelectedIds ( [ ] ) ) ; } ; const handleBatchDeleteClick = ( ) => { props . batchDeleteScheduledTasksAsync ( queue , selectedIds ) . then ( ( ) => setSelectedIds ( [ ] ) ) ; } ; const handleBatchArchiveClick = ( ) => { props . batchArchiveScheduledTasksAsync ( queue , selectedIds ) . then ( ( ) => setSelectedIds ( [ ] ) ) ; } ; const fetchData = useCallback ( ( ) => { const pageOpts = { page : page + 1 , size : pageSize } ; listScheduledTasksAsync ( queue , pageOpts ) ; } , [ page , pageSize , queue , listScheduledTasksAsync ] ) ; usePolling ( fetchData , pollInterval ) ; if ( props . error . length > 0 ) { return ( < Alert severity = "error" className = { classes . alert } > < AlertTitle > Error < /AlertTitle> { props . error } < /Alert>); } if ( props . tasks . length === 0 ) { return ( < Alert severity = "info" className = { classes . alert } > < AlertTitle > Info < /AlertTitle> No scheduled tasks at this time . < /Alert>); } const columns = [ { key : "id" , label : "ID" , align : "left" } , { key : "type" , label : "Type" , align : "left" } , { key : "payload" , label : "Payload" , align : "left" } , { key : "process_in" , label : "Process In" , align : "left" } , { key : "actions" , label : "Actions" , align : "center" } , ] ; const rowCount = props . tasks . length ; const numSelected = selectedIds . length ; return ( < div > < TableActions showIconButtons = { numSelected > 0 } iconButtonActions = { [ { tooltip : "Delete" , icon : < DeleteIcon />, onClick : handleBatchDeleteClick , disabled : props . batchActionPending , } , { tooltip : "Archive" , icon : < ArchiveIcon />, onClick : handleBatchArchiveClick , disabled : props . batchActionPending , } , { tooltip : "Run" , icon : < PlayArrowIcon />, 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 , } , ] } / > < TableContainer component = { Paper } > < Table stickyHeader = { true } className = { classes . table } aria - label = "scheduled tasks table" size = "small" > < TableHead > < TableRow > < TableCell padding = "checkbox" classes = { { stickyHeader : classes . stickyHeaderCell } } > < IconButton > < Checkbox indeterminate = { numSelected > 0 && numSelected < rowCount } checked = { rowCount > 0 && numSelected === rowCount } onChange = { handleSelectAllClick } inputProps = { { "aria-label" : "select all tasks shown in the table" , } } /> < /IconButton> < /TableCell> { columns . map ( ( col ) => ( < TableCell key = { col . label } align = { col . align } classes = { { stickyHeader : classes . stickyHeaderCell } } > { col . label } < /TableCell>))} < /TableRow> < /TableHead> < TableBody > { props . tasks . map ( ( task ) => ( < Row key = { task . id } task = { task } allActionPending = { props . allActionPending } isSelected = { selectedIds . includes ( task . id ) } onSelectChange = { ( checked ) => { if ( checked ) { setSelectedIds ( selectedIds . concat ( task . id ) ) ; } else { setSelectedIds ( selectedIds . filter ( ( id ) => id !== task . id ) ) ; } } } onRunClick = { ( ) => { props . runScheduledTaskAsync ( queue , task . id ) ; } } onDeleteClick = { ( ) => { props . deleteScheduledTaskAsync ( queue , task . id ) ; } } onArchiveClick = { ( ) => { props . archiveScheduledTaskAsync ( queue , task . id ) ; } } onActionCellEnter = { ( ) => setActiveTaskId ( task . id ) } onActionCellLeave = { ( ) => setActiveTaskId ( "" ) } showActions = { activeTaskId === task . id } />))} < /TableBody> < TableFooter > < TableRow > < TablePagination rowsPerPageOptions = { rowsPerPageOptions } colSpan = { columns . length + 1 } count = { props . totalTaskCount } rowsPerPage = { pageSize } page = { page } SelectProps = { { inputProps : { "aria-label" : "rows per page" } , native : true , } } onPageChange = { handlePageChange } onRowsPerPageChange = { handleRowsPerPageChange } ActionsComponent = { TablePaginationActions } className = { classes . pagination } / > < /TableRow> < /TableFooter> < /Table> < /TableContainer> < /div>); } const useRowStyles = makeStyles ( ( theme ) => ( { root : { cursor : "pointer" , "&:hover" : { boxShadow : theme . shadows [ 2 ] , } , "&: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" , } } ) ) ; interface RowProps { task : TaskInfoExtended ; isSelected : boolean ; onSelectChange : ( checked : boolean ) => void ; onRunClick : ( ) => void ; onDeleteClick : ( ) => void ; onArchiveClick : ( ) => void ; allActionPending : boolean ; showActions : boolean ; onActionCellEnter : ( ) => void ; onActionCellLeave : ( ) => void ; } function Row ( props ) { const { task } = props ; const classes = useRowStyles ( ) ; const history = useHistory ( ) ; return ( < TableRow key = { task . id } className = { classes . root } selected = { props . isSelected } onClick = { ( ) => history . push ( taskDetailsPath ( task . queue , task . id ) ) } > < TableCell padding = "checkbox" onClick = { ( e ) => e . stopPropagation ( ) } > < IconButton > < Checkbox onChange = { ( event ) => props . onSelectChange ( event . target . checked ) } checked = { props . isSelected } / > < /IconButton> < /TableCell> < TableCell component = "th" scope = "row" className = { classes . idCell } > < div className = { classes . IdGroup } > { uuidPrefix ( task . id ) } < Tooltip title = "Copy full ID to clipboard" > < IconButton onClick = { ( e ) => { e . stopPropagation ( ) ; navigator . clipboard . writeText ( task . id ) ; } } size = "small" className = { classes . copyButton } > < FileCopyOutlinedIcon fontSize = "small" / > < /IconButton> < /Tooltip> < /div> < /TableCell> < TableCell > { task . type } < /TableCell> < TableCell > < SyntaxHighlighter language = "json" customStyle = { { margin : 0 , maxWidth : 400 } } > { prettifyPayload ( task . payload ) } < /SyntaxHighlighter> < /TableCell> < TableCell > { durationBefore ( task . next_process_at ) } < /TableCell> < TableCell align = "center" className = { classes . actionCell } onMouseEnter = { props . onActionCellEnter } onMouseLeave = { props . onActionCellLeave } onClick = { ( e ) => e . stopPropagation ( ) } > { props . showActions ? ( < React . Fragment > < Tooltip title = "Delete" > < IconButton onClick = { props . onDeleteClick } disabled = { task . requestPending || props . allActionPending } size = "small" className = { classes . actionButton } > < DeleteIcon fontSize = "small" / > < /IconButton> < /Tooltip> < Tooltip title = "Archive" > < IconButton onClick = { props . onArchiveClick } disabled = { task . requestPending || props . allActionPending } size = "small" className = { classes . actionButton } > < ArchiveIcon fontSize = "small" / > < /IconButton> < /Tooltip> < Tooltip title = "Run" > < IconButton onClick = { props . onRunClick } disabled = { task . requestPending || props . allActionPending } size = "small" className = { classes . actionButton } > < PlayArrowIcon fontSize = "small" / > < /IconButton> < /Tooltip> < /React.Fragment>) : (<IconButton size="small" onClick={props.onActionCellEnter}> < MoreHorizIcon fontSize = "small" / > < /IconButton>)} < /TableCell> < /TableRow>); } export default connector ( ScheduledTasksTable ) ;