go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/milo/ui/src/monitoring/components/alert_table/alert_table.tsx (about)

     1  // Copyright 2024 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  import BugReportIcon from '@mui/icons-material/BugReport';
    16  import ChevronRightIcon from '@mui/icons-material/ChevronRight';
    17  import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
    18  import NotificationsPausedIcon from '@mui/icons-material/NotificationsPaused';
    19  import {
    20    IconButton,
    21    Table,
    22    TableBody,
    23    TableCell,
    24    TableHead,
    25    TableRow,
    26    Tooltip,
    27  } from '@mui/material';
    28  import { Fragment, useState } from 'react';
    29  
    30  import { AlertJson, TreeJson, BugId, Bug } from '@/monitoring/util/server_json';
    31  
    32  import { AlertDetailsRow } from './alert_details';
    33  import { BugMenu } from './bug_menu';
    34  import { AlertSummaryRow } from './summary_row';
    35  
    36  interface AlertTableProps {
    37    tree: TreeJson;
    38    alerts: AlertJson[];
    39    bug?: Bug;
    40    bugs: Bug[];
    41    alertBugs: { [alertKey: string]: BugId[] };
    42  }
    43  
    44  // An AlertTable shows a list of alerts.  There are usually several on the page at once.
    45  export const AlertTable = ({
    46    tree,
    47    alerts,
    48    bug,
    49    bugs,
    50    alertBugs,
    51  }: AlertTableProps) => {
    52    const [menuAnchorEl, setMenuAnchorEl] = useState<null | HTMLElement>(null);
    53    const [expanded, setExpanded] = useState({} as { [alert: string]: boolean });
    54    const [expandAll, setExpandAll] = useState(false);
    55    if (!alerts) {
    56      return null;
    57    }
    58    const toggleExpandAll = () => {
    59      setExpanded(Object.fromEntries(alerts.map((a) => [a.key, !expandAll])));
    60      setExpandAll(!expandAll);
    61    };
    62  
    63    return (
    64      <Table size="small">
    65        <TableHead>
    66          <TableRow>
    67            <TableCell>
    68              <IconButton onClick={toggleExpandAll}>
    69                {expandAll ? <ExpandMoreIcon /> : <ChevronRightIcon />}
    70              </IconButton>
    71            </TableCell>
    72            <TableCell>Failed Builder</TableCell>
    73            <TableCell>Failed Step</TableCell>
    74            <TableCell>Failed Builds</TableCell>
    75            <TableCell>Blamelist</TableCell>
    76            <TableCell>
    77              <div style={{ display: 'flex' }}>
    78                <Tooltip title="Link bug to ALL alerts">
    79                  <IconButton onClick={(e) => setMenuAnchorEl(e.currentTarget)}>
    80                    <BugReportIcon />
    81                  </IconButton>
    82                </Tooltip>
    83                <BugMenu
    84                  anchorEl={menuAnchorEl}
    85                  onClose={() => setMenuAnchorEl(null)}
    86                  alerts={alerts}
    87                  tree={tree}
    88                  bugs={bugs}
    89                  alertBugs={alertBugs}
    90                />
    91                <Tooltip title="Snooze ALL alerts for 60 minutes">
    92                  <IconButton>
    93                    <NotificationsPausedIcon />
    94                  </IconButton>
    95                </Tooltip>
    96              </div>
    97            </TableCell>
    98          </TableRow>
    99        </TableHead>
   100        <TableBody>
   101          {alerts.map((alert) => {
   102            // There should only be one builder, but we iterate the builders just in case.
   103            // It will result in some UI weirdness if there are ever more than one builder, but better
   104            // than not showing data.
   105            return (
   106              <Fragment key={alert.key}>
   107                {alert.extension.builders.map((builder) => {
   108                  return (
   109                    <Fragment key={builder.name}>
   110                      <AlertSummaryRow
   111                        alert={alert}
   112                        builder={builder}
   113                        expanded={expanded[alert.key]}
   114                        onExpand={() => {
   115                          const copy = { ...expanded };
   116                          copy[alert.key] = !copy[alert.key];
   117                          setExpanded(copy);
   118                        }}
   119                        tree={tree}
   120                        bugs={bugs}
   121                        alertBugs={alertBugs}
   122                      />
   123                      {expanded[alert.key] && (
   124                        <AlertDetailsRow
   125                          tree={tree}
   126                          alert={alert}
   127                          bug={bug}
   128                          key={alert.key + builder.name}
   129                        />
   130                      )}
   131                    </Fragment>
   132                  );
   133                })}
   134              </Fragment>
   135            );
   136          })}
   137        </TableBody>
   138      </Table>
   139    );
   140  };