go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/milo/ui/src/monitoring/components/luci_bisection_result/heuristic_result.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 HelpOutline from '@mui/icons-material/HelpOutline';
    16  import IconButton from '@mui/material/IconButton';
    17  import Link from '@mui/material/Link';
    18  import Table from '@mui/material/Table';
    19  import TableBody from '@mui/material/TableBody';
    20  import TableCell from '@mui/material/TableCell';
    21  import TableContainer from '@mui/material/TableContainer';
    22  import TableHead from '@mui/material/TableHead';
    23  import TableRow from '@mui/material/TableRow';
    24  
    25  import { HeuristicAnalysis } from '@/monitoring/util/server_json';
    26  
    27  interface HeuristicResultProps {
    28    heuristicResult: HeuristicAnalysis;
    29  }
    30  
    31  export const HeuristicResult = ({ heuristicResult }: HeuristicResultProps) => {
    32    const suspects = heuristicResult?.suspects ?? [];
    33    if (suspects.length === 0) {
    34      return (
    35        <>
    36          LUCI Bisection couldn&apos;t find any heuristic suspect for this
    37          failure.
    38        </>
    39      );
    40    }
    41    const heuristicTooltipTitle =
    42      'The heuristic suspects were based on best-guess effort, so they may not be 100% accurate.';
    43    return (
    44      <TableContainer>
    45        <Table sx={{ maxWidth: '1000px', tableLayout: 'fixed' }}>
    46          <TableHead>
    47            <TableRow>
    48              <TableCell align="left" sx={{ width: '350px' }}>
    49                Heuristic result
    50                {/* We dont use Tooltip from MUI here as the MUI tooltip is attached in <body> tag
    51                    so style cannot be applied. */}
    52                <span title={heuristicTooltipTitle}>
    53                  <IconButton>
    54                    <HelpOutline></HelpOutline>
    55                  </IconButton>
    56                </span>
    57              </TableCell>
    58              <TableCell align="left" sx={{ width: '60px' }}>
    59                Confidence
    60              </TableCell>
    61              <TableCell align="left">Justification</TableCell>
    62            </TableRow>
    63          </TableHead>
    64          <TableBody>
    65            {suspects.map((s) => (
    66              <TableRow key={s.reviewUrl}>
    67                <TableCell align="left">
    68                  <Link
    69                    href={s.reviewUrl}
    70                    target="_blank"
    71                    rel="noopener"
    72                    onClick={() => {
    73                      ga('send', {
    74                        hitType: 'event',
    75                        eventCategory: 'LuciBisection',
    76                        eventAction: 'ClickSuspectLink',
    77                        eventLabel: s.reviewUrl,
    78                        transport: 'beacon',
    79                      });
    80                    }}
    81                  >
    82                    {s.reviewUrl}
    83                  </Link>
    84                </TableCell>
    85                <TableCell align="left">
    86                  {confidenceText(s.confidence_level)}
    87                </TableCell>
    88                <TableCell align="left">
    89                  <pre style={{ whiteSpace: 'pre-wrap' }}>
    90                    {shortenJustification(s.justification)}
    91                  </pre>
    92                </TableCell>
    93              </TableRow>
    94            ))}
    95          </TableBody>
    96        </Table>
    97      </TableContainer>
    98    );
    99  };
   100  
   101  // Sometimes justification is too long if a CL touches many files.
   102  // In such case we should shorten the justification to at most 3 lines
   103  // and link to the detailed analysis if the sheriff wants to see the details
   104  // (when the detail analysis page is ready).
   105  function shortenJustification(justification: string) {
   106    const lines = justification.split('\n');
   107    if (lines.length < 4) {
   108      return justification;
   109    }
   110    return lines.slice(0, 3).join('\n') + '\n...';
   111  }
   112  
   113  function confidenceText(confidenceLevel: number) {
   114    switch (confidenceLevel) {
   115      case 1:
   116        return 'Low';
   117      case 2:
   118        return 'Medium';
   119      case 3:
   120        return 'High';
   121      default:
   122        return 'N/A';
   123    }
   124  }