github.com/wfusion/gofusion@v1.1.14/common/infra/asynq/asynqmon/ui/src/views/DashboardView.tsx (about) 1 import React, { useEffect } from "react"; 2 import { connect, ConnectedProps } from "react-redux"; 3 import Container from "@material-ui/core/Container"; 4 import { makeStyles } from "@material-ui/core/styles"; 5 import Grid from "@material-ui/core/Grid"; 6 import Paper from "@material-ui/core/Paper"; 7 import Typography from "@material-ui/core/Typography"; 8 import InfoIcon from "@material-ui/icons/Info"; 9 import Alert from "@material-ui/lab/Alert"; 10 import AlertTitle from "@material-ui/lab/AlertTitle"; 11 import { 12 listQueuesAsync, 13 pauseQueueAsync, 14 resumeQueueAsync, 15 deleteQueueAsync, 16 } from "../actions/queuesActions"; 17 import { listQueueStatsAsync } from "../actions/queueStatsActions"; 18 import { dailyStatsKeyChange } from "../actions/settingsActions"; 19 import { AppState } from "../store"; 20 import QueueSizeChart from "../components/QueueSizeChart"; 21 import ProcessedTasksChart from "../components/ProcessedTasksChart"; 22 import QueuesOverviewTable from "../components/QueuesOverviewTable"; 23 import Tooltip from "../components/Tooltip"; 24 import SplitButton from "../components/SplitButton"; 25 import { usePolling } from "../hooks"; 26 import DailyStatsChart from "../components/DailyStatsChart"; 27 28 const useStyles = makeStyles((theme) => ({ 29 container: { 30 paddingTop: theme.spacing(4), 31 paddingBottom: theme.spacing(4), 32 }, 33 paper: { 34 padding: theme.spacing(2), 35 display: "flex", 36 overflow: "auto", 37 flexDirection: "column", 38 }, 39 chartHeader: { 40 display: "flex", 41 alignItems: "center", 42 justifyContent: "space-between", 43 marginBottom: theme.spacing(2), 44 }, 45 chartHeaderTitle: { 46 display: "flex", 47 alignItems: "center", 48 }, 49 chartContainer: { 50 width: "100%", 51 height: "300px", 52 }, 53 infoIcon: { 54 marginLeft: theme.spacing(1), 55 color: theme.palette.grey[500], 56 cursor: "pointer", 57 }, 58 tooltipSection: { 59 marginBottom: "4px", 60 }, 61 tableContainer: { 62 marginBottom: theme.spacing(2), 63 }, 64 })); 65 66 function mapStateToProps(state: AppState) { 67 return { 68 loading: state.queues.loading, 69 queues: state.queues.data.map((q) => ({ 70 ...q.currentStats, 71 requestPending: q.requestPending, 72 })), 73 error: state.queues.error, 74 pollInterval: state.settings.pollInterval, 75 queueStats: state.queueStats.data, 76 dailyStatsKey: state.settings.dailyStatsChartType, 77 }; 78 } 79 80 const mapDispatchToProps = { 81 listQueuesAsync, 82 pauseQueueAsync, 83 resumeQueueAsync, 84 deleteQueueAsync, 85 listQueueStatsAsync, 86 dailyStatsKeyChange, 87 }; 88 89 const connector = connect(mapStateToProps, mapDispatchToProps); 90 91 type Props = ConnectedProps<typeof connector>; 92 93 export type DailyStatsKey = "today" | "last-7d" | "last-30d" | "last-90d"; 94 export const defaultDailyStatsKey = "last-7d"; 95 96 function DashboardView(props: Props) { 97 const { 98 pollInterval, 99 listQueuesAsync, 100 queues, 101 listQueueStatsAsync, 102 dailyStatsKey, 103 } = props; 104 const classes = useStyles(); 105 106 usePolling(listQueuesAsync, pollInterval); 107 108 // Refetch queue stats if a queue is added or deleted. 109 const qnames = queues 110 .map((q) => q.queue) 111 .sort() 112 .join(","); 113 114 useEffect(() => { 115 listQueueStatsAsync(); 116 }, [listQueueStatsAsync, qnames]); 117 118 const processedStats = queues.map((q) => ({ 119 queue: q.queue, 120 succeeded: q.processed - q.failed, 121 failed: q.failed, 122 })); 123 124 return ( 125 <Container maxWidth="lg" className={classes.container}> 126 <Grid container spacing={3}> 127 {props.error.length > 0 && ( 128 <Grid item xs={12}> 129 <Alert severity="error"> 130 <AlertTitle>Error</AlertTitle> 131 Could not retrieve queues live data —{" "} 132 <strong>See the logs for details</strong> 133 </Alert> 134 </Grid> 135 )} 136 <Grid item xs={6}> 137 <Paper className={classes.paper} variant="outlined"> 138 <div className={classes.chartHeader}> 139 <div className={classes.chartHeaderTitle}> 140 <Typography variant="h6">Queue Size</Typography> 141 <Tooltip 142 title={ 143 <div> 144 <div className={classes.tooltipSection}> 145 Total number of tasks in the queue 146 </div> 147 <div className={classes.tooltipSection}> 148 <strong>Active</strong>: number of tasks currently being 149 processed 150 </div> 151 <div className={classes.tooltipSection}> 152 <strong>Pending</strong>: number of tasks ready to be 153 processed 154 </div> 155 <div className={classes.tooltipSection}> 156 <strong>Scheduled</strong>: number of tasks scheduled to 157 be processed in the future 158 </div> 159 <div className={classes.tooltipSection}> 160 <strong>Retry</strong>: number of tasks scheduled to be 161 retried in the future 162 </div> 163 <div> 164 <strong>Archived</strong>: number of tasks exhausted 165 their retries 166 </div> 167 </div> 168 } 169 > 170 <InfoIcon fontSize="small" className={classes.infoIcon} /> 171 </Tooltip> 172 </div> 173 </div> 174 <div className={classes.chartContainer}> 175 <QueueSizeChart data={queues} /> 176 </div> 177 </Paper> 178 </Grid> 179 180 <Grid item xs={6}> 181 <Paper className={classes.paper} variant="outlined"> 182 <div className={classes.chartHeader}> 183 <div className={classes.chartHeaderTitle}> 184 <Typography variant="h6">Tasks Processed</Typography> 185 <Tooltip 186 title={ 187 <div> 188 <div className={classes.tooltipSection}> 189 Total number of tasks processed in a given day (UTC) 190 </div> 191 <div className={classes.tooltipSection}> 192 <strong>Succeeded</strong>: number of tasks successfully 193 processed 194 </div> 195 <div> 196 <strong>Failed</strong>: number of tasks failed to be 197 processed 198 </div> 199 </div> 200 } 201 > 202 <InfoIcon fontSize="small" className={classes.infoIcon} /> 203 </Tooltip> 204 </div> 205 <div> 206 <SplitButton 207 options={[ 208 { label: "Today", key: "today" }, 209 { label: "Last 7d", key: "last-7d" }, 210 { label: "Last 30d", key: "last-30d" }, 211 { label: "Last 90d", key: "last-90d" }, 212 ]} 213 initialSelectedKey={dailyStatsKey} 214 onSelect={(key) => 215 props.dailyStatsKeyChange(key as DailyStatsKey) 216 } 217 /> 218 </div> 219 </div> 220 <div className={classes.chartContainer}> 221 {dailyStatsKey === "today" && ( 222 <ProcessedTasksChart data={processedStats} /> 223 )} 224 {dailyStatsKey === "last-7d" && ( 225 <DailyStatsChart data={props.queueStats} numDays={7} /> 226 )} 227 {dailyStatsKey === "last-30d" && ( 228 <DailyStatsChart data={props.queueStats} numDays={30} /> 229 )} 230 {dailyStatsKey === "last-90d" && ( 231 <DailyStatsChart data={props.queueStats} numDays={90} /> 232 )} 233 </div> 234 </Paper> 235 </Grid> 236 237 <Grid item xs={12} className={classes.tableContainer}> 238 <Paper className={classes.paper} variant="outlined"> 239 {/* TODO: Add loading indicator */} 240 <QueuesOverviewTable 241 queues={queues} 242 onPauseClick={props.pauseQueueAsync} 243 onResumeClick={props.resumeQueueAsync} 244 onDeleteClick={props.deleteQueueAsync} 245 /> 246 </Paper> 247 </Grid> 248 </Grid> 249 </Container> 250 ); 251 } 252 253 export default connector(DashboardView);