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