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

     1  // Copyright 2023 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 Link from '@mui/material/Link';
    16  import TableBody from '@mui/material/TableBody';
    17  import TableCell from '@mui/material/TableCell';
    18  import TableContainer from '@mui/material/TableContainer';
    19  import TableRow from '@mui/material/TableRow';
    20  import Typography from '@mui/material/Typography';
    21  
    22  import { PlainTable } from '@/bisection/components/plain_table';
    23  import { AnalysisStatusInfo } from '@/bisection/components/status_info';
    24  import { BUILD_FAILURE_TYPE_DISPLAY_MAP } from '@/bisection/constants';
    25  import {
    26    ExternalLink,
    27    linkToBuild,
    28    linkToBuilder,
    29    linkToCommit,
    30  } from '@/bisection/tools/link_constructors';
    31  import { getFormattedTimestamp } from '@/bisection/tools/timestamp_formatters';
    32  import { GenericNthSectionAnalysisResult } from '@/bisection/types';
    33  import {
    34    Analysis,
    35    AnalysisRunStatus,
    36  } from '@/proto/go.chromium.org/luci/bisection/proto/v1/analyses.pb';
    37  import { CulpritActionType } from '@/proto/go.chromium.org/luci/bisection/proto/v1/culprits.pb';
    38  
    39  import { nthSectionSuspectRange } from './common';
    40  
    41  function getSuspectRange(analysis: Analysis): ExternalLink[] {
    42    if (analysis.culprits.length > 0) {
    43      return analysis.culprits.map((culprit) => linkToCommit(culprit.commit!));
    44    }
    45    if (analysis.nthSectionResult) {
    46      const link = nthSectionSuspectRange(
    47        GenericNthSectionAnalysisResult.from(analysis.nthSectionResult),
    48      );
    49      return link ? [link] : [];
    50    }
    51    return [];
    52  }
    53  
    54  function getBugLinks(analysis: Analysis): ExternalLink[] {
    55    const bugLinks: ExternalLink[] = [];
    56  
    57    // Get the bug links from the actions for each culprit
    58    if (analysis.culprits) {
    59      analysis.culprits.forEach((culprit) => {
    60        if (culprit.culpritAction) {
    61          culprit.culpritAction.forEach((action) => {
    62            if (
    63              action.actionType === CulpritActionType.BUG_COMMENTED &&
    64              action.bugUrl
    65            ) {
    66              // TODO: construct short link text for bug
    67              bugLinks.push({
    68                linkText: action.bugUrl,
    69                url: action.bugUrl,
    70              });
    71            }
    72          });
    73        }
    74      });
    75    }
    76  
    77    return bugLinks;
    78  }
    79  
    80  export interface AnalysisOverviewProps {
    81    readonly analysis: Analysis;
    82  }
    83  
    84  export function AnalysisOverview({ analysis }: AnalysisOverviewProps) {
    85    const buildLink = linkToBuild(analysis.firstFailedBbid);
    86    const builderLink = analysis.builder ? linkToBuilder(analysis.builder) : null;
    87  
    88    const suspectRange = getSuspectRange(analysis);
    89    const bugLinks = getBugLinks(analysis);
    90  
    91    return (
    92      <TableContainer>
    93        <PlainTable>
    94          <colgroup>
    95            <col style={{ width: '15%' }} />
    96            <col style={{ width: '35%' }} />
    97            <col style={{ width: '15%' }} />
    98            <col style={{ width: '35%' }} />
    99          </colgroup>
   100          <TableBody data-testid="analysis_overview_table_body">
   101            <TableRow>
   102              <TableCell variant="head">Analysis ID</TableCell>
   103              <TableCell>{analysis.analysisId}</TableCell>
   104              <TableCell variant="head">Buildbucket ID</TableCell>
   105              <TableCell>
   106                <Link
   107                  href={buildLink.url}
   108                  target="_blank"
   109                  rel="noreferrer"
   110                  underline="always"
   111                >
   112                  {buildLink.linkText}
   113                </Link>
   114              </TableCell>
   115            </TableRow>
   116            <TableRow>
   117              <TableCell variant="head">Created time</TableCell>
   118              <TableCell>{getFormattedTimestamp(analysis.createdTime)}</TableCell>
   119              <TableCell variant="head">Builder</TableCell>
   120              <TableCell>
   121                {builderLink && (
   122                  <Link
   123                    href={builderLink.url}
   124                    target="_blank"
   125                    rel="noreferrer"
   126                    underline="always"
   127                  >
   128                    {builderLink.linkText}
   129                  </Link>
   130                )}
   131              </TableCell>
   132            </TableRow>
   133            <TableRow>
   134              <TableCell variant="head">End time</TableCell>
   135              <TableCell>{getFormattedTimestamp(analysis.endTime)}</TableCell>
   136              <TableCell variant="head">Failure type</TableCell>
   137              <TableCell>
   138                {BUILD_FAILURE_TYPE_DISPLAY_MAP[analysis.buildFailureType]}
   139              </TableCell>
   140            </TableRow>
   141            <TableRow>
   142              <TableCell variant="head">Status</TableCell>
   143              <TableCell>
   144                <AnalysisStatusInfo status={analysis.status}></AnalysisStatusInfo>
   145                {/* TODO (nqmtuan): Currently, analyses are only canceled if
   146                    a later build is successful. If analyses are canceled for
   147                    other reasons, we will need to store the cancelation reason
   148                    in the backend and update the UI here to display it.*/}
   149                {analysis.runStatus === AnalysisRunStatus.CANCELED && (
   150                  <Typography color="var(--greyed-out-text-color)">
   151                    (canceled because the builder started passing again)
   152                  </Typography>
   153                )}
   154              </TableCell>
   155            </TableRow>
   156            <TableRow>
   157              <TableCell variant="head">Suspect range</TableCell>
   158              <TableCell>
   159                {suspectRange.map((suspectLink) => (
   160                  <span className="span-link" key={suspectLink.url}>
   161                    <Link
   162                      data-testid="analysis_overview_suspect_range"
   163                      href={suspectLink.url}
   164                      target="_blank"
   165                      rel="noreferrer"
   166                      underline="always"
   167                    >
   168                      {suspectLink.linkText}
   169                    </Link>
   170                  </span>
   171                ))}
   172              </TableCell>
   173            </TableRow>
   174            {bugLinks.length > 0 && (
   175              <>
   176                <TableRow>
   177                  <TableCell>
   178                    <br />
   179                  </TableCell>
   180                </TableRow>
   181                <TableRow>
   182                  <TableCell variant="head">Related bugs</TableCell>
   183                  <TableCell colSpan={3}>
   184                    {bugLinks.map((bugLink) => (
   185                      <span className="span-link" key={bugLink.url}>
   186                        <Link
   187                          data-testid="analysis_overview_bug_link"
   188                          href={bugLink.url}
   189                          target="_blank"
   190                          rel="noreferrer"
   191                          underline="always"
   192                        >
   193                          {bugLink.linkText}
   194                        </Link>
   195                      </span>
   196                    ))}
   197                  </TableCell>
   198                </TableRow>
   199              </>
   200            )}
   201          </TableBody>
   202        </PlainTable>
   203      </TableContainer>
   204    );
   205  }