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 }