import React , { useState } from "react" ; import clsx from "clsx" ; import { Link } from "react-router-dom" ; 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 TableSortLabel from "@material-ui/core/TableSortLabel" ; import IconButton from "@material-ui/core/IconButton" ; import Tooltip from "@material-ui/core/Tooltip" ; import PauseCircleFilledIcon from "@material-ui/icons/PauseCircleFilled" ; import PlayCircleFilledIcon from "@material-ui/icons/PlayCircleFilled" ; import DeleteIcon from "@material-ui/icons/Delete" ; import MoreHorizIcon from "@material-ui/icons/MoreHoriz" ; import DeleteQueueConfirmationDialog from "./DeleteQueueConfirmationDialog" ; import { Queue } from "../api" ; import { queueDetailsPath } from "../paths" ; import { SortDirection , SortableTableColumn } from "../types/table" ; import prettyBytes from "pretty-bytes" ; import { percentage } from "../utils" ; const useStyles = makeStyles ( ( theme ) => ( { table : { minWidth : 650 , } , fixedCell : { position : "sticky" , zIndex : 1 , left : 0 , background : theme . palette . background . paper , } , } ) ) ; interface QueueWithMetadata extends Queue { requestPending : boolean ; } interface Props { queues : QueueWithMetadata [ ] ; onPauseClick : ( qname : string ) => Promise < void > ; onResumeClick : ( qname : string ) => Promise < void > ; onDeleteClick : ( qname : string ) => Promise < void > ; } enum SortBy { Queue , State , Size , MemoryUsage , Processed , Failed , ErrorRate , None } const colConfigs = [ { label : "Queue" , key : "queue" , sortBy : SortBy . Queue , align : "left" } , { label : "State" , key : "state" , sortBy : SortBy . State , align : "left" } , { label : "Size" , key : "size" , sortBy : SortBy . Size , align : "right" , } , { label : "Memory usage" , key : "memory_usage" , sortBy : SortBy . MemoryUsage , align : "right" , } , { label : "Processed" , key : "processed" , sortBy : SortBy . Processed , align : "right" , } , { label : "Failed" , key : "failed" , sortBy : SortBy . Failed , align : "right" } , { label : "Error rate" , key : "error_rate" , sortBy : SortBy . ErrorRate , align : "right" , } , { label : "Actions" , key : "actions" , sortBy : SortBy . None , align : "center" } , ] ; function sortQueues ( queues , cmpFn ) { let copy = [ ... queues ] ; copy . sort ( cmpFn ) ; return copy ; } export default function QueuesOverviewTable ( props ) { const classes = useStyles ( ) ; const [ sortBy , setSortBy ] = useState < SortBy > ( SortBy . Queue ) ; const [ sortDir , setSortDir ] = useState < SortDirection > ( SortDirection . Asc ) ; const [ queueToDelete , setQueueToDelete ] = useState < QueueWithMetadata | null > ( null ) ; const createSortClickHandler = ( sortKey ) => ( e ) => { if ( sortKey === sortBy ) { const nextSortDir = sortDir === SortDirection . Asc ? SortDirection . Desc : SortDirection . Asc ; setSortDir ( nextSortDir ) ; } else { setSortBy ( sortKey ) ; } } ; const cmpFunc = ( q1 , q2 ) : number => { let isQ1Smaller ; switch ( sortBy ) { case SortBy . Queue : if ( q1 . queue === q2 . queue ) return 0 ; isQ1Smaller = q1 . queue < q2 . queue ; break ; case SortBy . State : if ( q1 . paused === q2 . paused ) return 0 ; isQ1Smaller = ! q1 . paused ; break ; case SortBy . Size : if ( q1 . size === q2 . size ) return 0 ; isQ1Smaller = q1 . size < q2 . size ; break ; case SortBy . MemoryUsage : if ( q1 . memory_usage_bytes === q2 . memory_usage_bytes ) return 0 ; isQ1Smaller = q1 . memory_usage_bytes < q2 . memory_usage_bytes ; break ; case SortBy . Processed : if ( q1 . processed === q2 . processed ) return 0 ; isQ1Smaller = q1 . processed < q2 . processed ; break ; case SortBy . Failed : if ( q1 . failed === q2 . failed ) return 0 ; isQ1Smaller = q1 . failed < q2 . failed ; break ; case SortBy . ErrorRate : const q1ErrorRate = q1 . failed / q1 . processed ; const q2ErrorRate = q2 . failed / q2 . processed ; if ( q1ErrorRate === q2ErrorRate ) return 0 ; isQ1Smaller = q1ErrorRate < q2ErrorRate ; break ; default : throw `Unexpected order by value: ${ sortBy }` ; } if ( sortDir === SortDirection . Asc ) { return isQ1Smaller ? - 1 : 1 ; } else { return isQ1Smaller ? 1 : - 1 ; } } ; const handleDialogClose = ( ) => { setQueueToDelete ( null ) ; } ; return ( < React . Fragment > < TableContainer > < Table className = { classes . table } aria - label = "queues overview table" > < TableHead > < TableRow > { colConfigs . map ( ( cfg , i ) => ( < TableCell key = { cfg . key } align = { cfg . align } className = { clsx ( i === 0 && classes . fixedCell ) } > { cfg . sortBy !== SortBy . None ? ( < TableSortLabel active = { sortBy === cfg . sortBy } direction = { sortDir } onClick = { createSortClickHandler ( cfg . sortBy ) } > { cfg . label } < /TableSortLabel>) : (<div>{cfg.label}</div > ) } < /TableCell>))} < /TableRow> < /TableHead> < TableBody > { sortQueues ( props . queues , cmpFunc ) . map ( ( q ) => ( < Row key = { q . queue } queue = { q } onPauseClick = { ( ) => props . onPauseClick ( q . queue ) } onResumeClick = { ( ) => props . onResumeClick ( q . queue ) } onDeleteClick = { ( ) => setQueueToDelete ( q ) } />))} < /TableBody> < /Table> < /TableContainer> < DeleteQueueConfirmationDialog onClose = { handleDialogClose } queue = { queueToDelete } / > < /React.Fragment>); } const useRowStyles = makeStyles ( ( theme ) => ( { row : { "&:last-child td" : { borderBottomWidth : 0 , } , "&:last-child th" : { borderBottomWidth : 0 , } , } , linkText : { textDecoration : "none" , color : theme . palette . text . primary , "&:hover" : { textDecoration : "underline" , } , } , textGreen : { color : theme . palette . success . dark , } , textRed : { color : theme . palette . error . dark , } , boldCell : { fontWeight : 600 , } , fixedCell : { position : "sticky" , zIndex : 1 , left : 0 , background : theme . palette . background . paper , } , actionIconsContainer : { display : "flex" , justifyContent : "center" , minWidth : "100px" , } , } ) ) ; interface RowProps { queue : QueueWithMetadata ; onPauseClick : ( ) => void ; onResumeClick : ( ) => void ; onDeleteClick : ( ) => void ; } function Row ( props ) { const classes = useRowStyles ( ) ; const { queue : q } = props ; const [ showIcons , setShowIcons ] = useState < boolean > ( false ) ; return ( < TableRow key = { q . queue } className = { classes . row } > < TableCell component = "th" scope = "row" className = { clsx ( classes . boldCell , classes . fixedCell ) } > < Link to = { queueDetailsPath ( q . queue ) } className = { classes . linkText } > { q . queue } < /Link> < /TableCell> < TableCell > { q . paused ? ( < span className = { classes . textRed } > paused < /span>) : (<span className={classes.textGreen}>run</span > ) } < /TableCell> < TableCell align = "right" > { q . size } < /TableCell> < TableCell align = "right" > { prettyBytes ( q . memory_usage_bytes ) } < /TableCell> < TableCell align = "right" > { q . processed } < /TableCell> < TableCell align = "right" > { q . failed } < /TableCell> < TableCell align = "right" > { percentage ( q . failed , q . processed ) } < /TableCell> < TableCell align = "center" onMouseEnter = { ( ) => setShowIcons ( true ) } onMouseLeave = { ( ) => setShowIcons ( false ) } > < div className = { classes . actionIconsContainer } > { showIcons ? ( < React . Fragment > { q . paused ? ( < Tooltip title = "Resume" > < IconButton color = "secondary" onClick = { props . onResumeClick } disabled = { q . requestPending } size = "small" > < PlayCircleFilledIcon fontSize = "small" /> < /IconButton> < /Tooltip>) : (<Tooltip title="Pause"> < IconButton color = "primary" onClick = { props . onPauseClick } disabled = { q . requestPending } size = "small" > < PauseCircleFilledIcon fontSize = "small" /> < /IconButton> < /Tooltip>)} < Tooltip title = "Delete" > < IconButton onClick = { props . onDeleteClick } size = "small" > < DeleteIcon fontSize = "small" / > < /IconButton> < /Tooltip> < /React.Fragment>) : (<IconButton size="small"> < MoreHorizIcon fontSize = "small" / > < /IconButton>)} < /div> < /TableCell> < /TableRow>); }