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);