github.com/wfusion/gofusion@v1.1.14/common/infra/asynq/asynqmon/ui/src/views/TaskDetailsView.tsx (about) 1 import React, { useMemo, useEffect } from "react"; 2 import { connect, ConnectedProps } from "react-redux"; 3 import { useHistory } from "react-router-dom"; 4 import { makeStyles } from "@material-ui/core/styles"; 5 import Container from "@material-ui/core/Container"; 6 import Grid from "@material-ui/core/Grid"; 7 import Paper from "@material-ui/core/Paper"; 8 import Typography from "@material-ui/core/Typography"; 9 import Button from "@material-ui/core/Button"; 10 import Alert from "@material-ui/lab/Alert"; 11 import AlertTitle from "@material-ui/lab/AlertTitle"; 12 import ArrowBackIcon from "@material-ui/icons/ArrowBack"; 13 import { useParams } from "react-router-dom"; 14 import QueueBreadCrumb from "../components/QueueBreadcrumb"; 15 import { AppState } from "../store"; 16 import { getTaskInfoAsync } from "../actions/tasksActions"; 17 import { TaskDetailsRouteParams } from "../paths"; 18 import { usePolling } from "../hooks"; 19 import { listQueuesAsync } from "../actions/queuesActions"; 20 import SyntaxHighlighter from "../components/SyntaxHighlighter"; 21 import { durationFromSeconds, stringifyDuration, timeAgo, prettifyPayload } from "../utils"; 22 23 function mapStateToProps(state: AppState) { 24 return { 25 loading: state.tasks.taskInfo.loading, 26 error: state.tasks.taskInfo.error, 27 taskInfo: state.tasks.taskInfo.data, 28 pollInterval: state.settings.pollInterval, 29 queues: state.queues.data.map((q) => q.name), // FIXME: This data may not be available 30 }; 31 } 32 33 const connector = connect(mapStateToProps, { 34 getTaskInfoAsync, 35 listQueuesAsync, 36 }); 37 38 const useStyles = makeStyles((theme) => ({ 39 container: { 40 paddingTop: theme.spacing(2), 41 }, 42 alert: { 43 borderTopLeftRadius: 0, 44 borderTopRightRadius: 0, 45 }, 46 paper: { 47 padding: theme.spacing(2), 48 marginTop: theme.spacing(2), 49 }, 50 breadcrumbs: { 51 marginBottom: theme.spacing(2), 52 }, 53 infoRow: { 54 display: "flex", 55 alignItems: "center", 56 paddingTop: theme.spacing(1), 57 }, 58 infoKeyCell: { 59 width: "140px", 60 }, 61 infoValueCell: { 62 width: "auto", 63 }, 64 footer: { 65 paddingTop: theme.spacing(3), 66 paddingBottom: theme.spacing(3), 67 }, 68 })); 69 70 type Props = ConnectedProps<typeof connector>; 71 72 function TaskDetailsView(props: Props) { 73 const classes = useStyles(); 74 const { qname, taskId } = useParams<TaskDetailsRouteParams>(); 75 const { getTaskInfoAsync, pollInterval, listQueuesAsync, taskInfo } = props; 76 const history = useHistory(); 77 78 const fetchTaskInfo = useMemo(() => { 79 return () => { 80 getTaskInfoAsync(qname, taskId); 81 }; 82 }, [qname, taskId, getTaskInfoAsync]); 83 84 usePolling(fetchTaskInfo, pollInterval); 85 86 // Fetch queues data to populate props.queues 87 useEffect(() => { 88 listQueuesAsync(); 89 }, [listQueuesAsync]); 90 91 return ( 92 <Container maxWidth="lg" className={classes.container}> 93 <Grid container spacing={0}> 94 <Grid item xs={12} className={classes.breadcrumbs}> 95 <QueueBreadCrumb 96 queues={props.queues} 97 queueName={qname} 98 taskId={taskId} 99 /> 100 </Grid> 101 <Grid item xs={12} md={6}> 102 {props.error ? ( 103 <Alert severity="error" className={classes.alert}> 104 <AlertTitle>Error</AlertTitle> 105 {props.error} 106 </Alert> 107 ) : ( 108 <Paper className={classes.paper} variant="outlined"> 109 <Typography variant="h6">Task Info</Typography> 110 <div> 111 <div className={classes.infoRow}> 112 <Typography 113 variant="subtitle2" 114 className={classes.infoKeyCell} 115 > 116 ID:{" "} 117 </Typography> 118 <Typography className={classes.infoValueCell}> 119 {taskInfo?.id} 120 </Typography> 121 </div> 122 <div className={classes.infoRow}> 123 <Typography 124 variant="subtitle2" 125 className={classes.infoKeyCell} 126 > 127 Type:{" "} 128 </Typography> 129 <Typography className={classes.infoValueCell}> 130 {taskInfo?.type} 131 </Typography> 132 </div> 133 <div className={classes.infoRow}> 134 <Typography 135 variant="subtitle2" 136 className={classes.infoKeyCell} 137 > 138 State:{" "} 139 </Typography> 140 <Typography className={classes.infoValueCell}> 141 {taskInfo?.state} 142 </Typography> 143 </div> 144 <div className={classes.infoRow}> 145 <Typography 146 variant="subtitle2" 147 className={classes.infoKeyCell} 148 > 149 Queue:{" "} 150 </Typography> 151 <Typography className={classes.infoValueCell}> 152 {taskInfo?.queue} 153 </Typography> 154 </div> 155 <div className={classes.infoRow}> 156 <Typography 157 variant="subtitle2" 158 className={classes.infoKeyCell} 159 > 160 Retry:{" "} 161 </Typography> 162 <Typography className={classes.infoValueCell}> 163 {taskInfo?.retried}/{taskInfo?.max_retry} 164 </Typography> 165 </div> 166 <div className={classes.infoRow}> 167 <Typography 168 variant="subtitle2" 169 className={classes.infoKeyCell} 170 > 171 Last Failure:{" "} 172 </Typography> 173 <Typography className={classes.infoValueCell}> 174 {taskInfo?.last_failed_at ? ( 175 <Typography> 176 {taskInfo?.error_message} ({taskInfo?.last_failed_at}) 177 </Typography> 178 ) : ( 179 <Typography> - </Typography> 180 )} 181 </Typography> 182 </div> 183 <div className={classes.infoRow}> 184 <Typography 185 variant="subtitle2" 186 className={classes.infoKeyCell} 187 > 188 Next Process Time:{" "} 189 </Typography> 190 {taskInfo?.next_process_at ? ( 191 <Typography>{taskInfo?.next_process_at}</Typography> 192 ) : ( 193 <Typography> - </Typography> 194 )} 195 </div> 196 </div> 197 <div className={classes.infoRow}> 198 <Typography variant="subtitle2" className={classes.infoKeyCell}> 199 Timeout:{" "} 200 </Typography> 201 <Typography className={classes.infoValueCell}> 202 {taskInfo?.timeout_seconds ? ( 203 <Typography>{taskInfo?.timeout_seconds} seconds</Typography> 204 ) : ( 205 <Typography> - </Typography> 206 )} 207 </Typography> 208 </div> 209 <div className={classes.infoRow}> 210 <Typography variant="subtitle2" className={classes.infoKeyCell}> 211 Deadline:{" "} 212 </Typography> 213 <Typography className={classes.infoValueCell}> 214 {taskInfo?.deadline ? ( 215 <Typography>{taskInfo?.deadline}</Typography> 216 ) : ( 217 <Typography> - </Typography> 218 )} 219 </Typography> 220 </div> 221 <div className={classes.infoRow}> 222 <Typography variant="subtitle2" className={classes.infoKeyCell}> 223 Payload:{" "} 224 </Typography> 225 <div className={classes.infoValueCell}> 226 {taskInfo?.payload && ( 227 <SyntaxHighlighter 228 language="json" 229 customStyle={{ margin: 0, maxWidth: 400 }} 230 > 231 {prettifyPayload(taskInfo.payload)} 232 </SyntaxHighlighter> 233 )} 234 </div> 235 </div> 236 { 237 /* Completed Task Only */ taskInfo?.state === "completed" && ( 238 <> 239 <div className={classes.infoRow}> 240 <Typography 241 variant="subtitle2" 242 className={classes.infoKeyCell} 243 > 244 Completed:{" "} 245 </Typography> 246 <div className={classes.infoValueCell}> 247 <Typography> 248 {timeAgo(taskInfo.completed_at)} ( 249 {taskInfo.completed_at}) 250 </Typography> 251 </div> 252 </div> 253 <div className={classes.infoRow}> 254 <Typography 255 variant="subtitle2" 256 className={classes.infoKeyCell} 257 > 258 Result:{" "} 259 </Typography> 260 <div className={classes.infoValueCell}> 261 <SyntaxHighlighter 262 language="json" 263 customStyle={{ margin: 0, maxWidth: 400 }} 264 > 265 {prettifyPayload(taskInfo.result)} 266 </SyntaxHighlighter> 267 </div> 268 </div> 269 <div className={classes.infoRow}> 270 <Typography 271 variant="subtitle2" 272 className={classes.infoKeyCell} 273 > 274 TTL:{" "} 275 </Typography> 276 <Typography className={classes.infoValueCell}> 277 <Typography> 278 {taskInfo.ttl_seconds > 0 279 ? `${stringifyDuration( 280 durationFromSeconds(taskInfo.ttl_seconds) 281 )} left` 282 : "expired"} 283 </Typography> 284 </Typography> 285 </div> 286 </> 287 ) 288 } 289 </Paper> 290 )} 291 <div className={classes.footer}> 292 <Button 293 startIcon={<ArrowBackIcon />} 294 onClick={() => history.goBack()} 295 > 296 Go Back 297 </Button> 298 </div> 299 </Grid> 300 </Grid> 301 </Container> 302 ); 303 } 304 305 export default connector(TaskDetailsView);