@@ -40,6 +40,119 @@ import { MemoizedSectionError } from '@/components/DetailsPages/SectionError';
4040
4141import { GroupedTestStatus } from '@/components/Status/Status' ;
4242
43+ type IssueItemProps = {
44+ issue : TIssue ;
45+ extraDetails ?: IssueExtraDetailsDict [ string ] ;
46+ extraDetailsLoading ?: boolean ;
47+ detailsId ?: string ;
48+ getIssueLink : ( id : string , version : number ) => LinkProps ;
49+ issueFilterSection : TFilterObjectsKeys ;
50+ diffFilter : TFilter ;
51+ } ;
52+
53+ const IssueItem = ( {
54+ issue,
55+ extraDetails,
56+ extraDetailsLoading,
57+ detailsId,
58+ getIssueLink,
59+ issueFilterSection,
60+ diffFilter,
61+ } : IssueItemProps ) : JSX . Element => {
62+ const { formatMessage } = useIntl ( ) ;
63+
64+ const isFirstIncident =
65+ detailsId === extraDetails ?. first_incident . git_commit_hash ;
66+ const currentVersion = extraDetails ?. versions [ issue . version ] ;
67+ const firstSeen = extraDetails ?. first_incident . first_seen ;
68+ const counts = issue . incidents_info ;
69+
70+ const tagPills = currentVersion ?. tags ?. map ( tag => (
71+ < BranchBadge key = { tag } tag = { tag } />
72+ ) ) ;
73+
74+ if ( isFirstIncident ) {
75+ tagPills ?. unshift (
76+ < Tooltip key = "starburst" >
77+ < TooltipTrigger className = "cursor-default" >
78+ < div className = "starburst bg-red aspect-square w-[24px]" />
79+ </ TooltipTrigger >
80+ < TooltipContent >
81+ < FormattedMessage id = "issue.newIssue" />
82+ </ TooltipContent >
83+ </ Tooltip > ,
84+ ) ;
85+ }
86+
87+ const hasMeta = ! extraDetailsLoading && ! ! ( firstSeen || tagPills ?. length ) ;
88+
89+ return (
90+ < div className = "grid w-full grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-x-3" >
91+ { /* Col 1: status circles — fixed width */ }
92+ < GroupedTestStatus
93+ pass = { counts . PASS }
94+ fail = { counts . FAIL }
95+ nullStatus = { counts . NULL }
96+ error = { counts . ERROR }
97+ done = { counts . DONE }
98+ miss = { counts . MISS }
99+ skip = { counts . SKIP }
100+ />
101+
102+ { /* Col 2: issue text — takes remaining space, truncates */ }
103+ < FilterLink
104+ filterSection = { issueFilterSection }
105+ filterValue = { getIssueFilterLabel ( issue ) }
106+ diffFilter = { diffFilter }
107+ >
108+ < div className = "max-w-5/6 min-w-0 text-sm sm:max-w-3/4" >
109+ < ListingItem
110+ showNumber = { false }
111+ hasBottomBorder
112+ text = { issue . comment ?? formatMessage ( { id : 'issue.uncategorized' } ) }
113+ tooltip = { issue . comment }
114+ />
115+ </ div >
116+ </ FilterLink >
117+
118+ { /* Col 3: action icons — fixed width */ }
119+ < div className = "flex items-center gap-3" >
120+ { extraDetailsLoading && < LoadingCircle /> }
121+ { ! extraDetailsLoading && issue . report_url && (
122+ < LinkWithIcon
123+ link = { issue . report_url }
124+ icon = { < LinkIcon className = "h-4 w-4" /> }
125+ />
126+ ) }
127+ { ! extraDetailsLoading && issue . id !== UNCATEGORIZED_STRING && (
128+ < MemoizedMoreDetailsIconLink
129+ linkProps = { getIssueLink ( issue . id , issue . version ) }
130+ />
131+ ) }
132+ </ div >
133+
134+ { /* Row 2, Col 2: first_seen + tags below text, col 1 and 3 stay empty */ }
135+ { hasMeta && (
136+ < div className = "col-start-2 flex flex-wrap items-center gap-2 pb-1" >
137+ { firstSeen && (
138+ < span className = "text-sm text-nowrap text-gray-600" >
139+ < TooltipDateTime
140+ dateTime = { firstSeen }
141+ lineBreak = { true }
142+ showRelative = { true }
143+ message = { `• ${ formatMessage ( { id : 'issue.firstSeen' } ) } : ` }
144+ />
145+ </ span >
146+ ) }
147+ { tagPills && (
148+ < div className = "flex flex-wrap gap-2" > { ...tagPills } </ div >
149+ ) }
150+ </ div >
151+ ) }
152+ </ div >
153+ ) ;
154+ } ;
155+
43156interface IIssuesList {
44157 issues : TIssue [ ] ;
45158 failedWithUnknownIssues ?: number ;
@@ -135,107 +248,18 @@ const IssuesList = ({
135248 />
136249 ) : (
137250 < DumbListingContent >
138- { sortedIssues . map ( issue => {
139- const currentExtraDetailsId = issueExtraDetails ?. [ issue . id ] ;
140- const currentExtraDetailsVersion =
141- currentExtraDetailsId ?. [ 'versions' ] [ issue . version ] ;
142-
143- const tagPills = currentExtraDetailsVersion ?. tags ?. map ( tag => {
144- return < BranchBadge key = { tag } tag = { tag } /> ;
145- } ) ;
146-
147- if (
148- detailsId === currentExtraDetailsId ?. first_incident . git_commit_hash
149- ) {
150- tagPills ?. unshift (
151- < Tooltip >
152- < TooltipTrigger className = "cursor-default" >
153- < div className = "starburst bg-red aspect-square w-[24px]" > </ div >
154- </ TooltipTrigger >
155- < TooltipContent >
156- < FormattedMessage id = "issue.newIssue" />
157- </ TooltipContent >
158- </ Tooltip > ,
159- ) ;
160- }
161-
162- const first_seen = currentExtraDetailsId ?. first_incident . first_seen ;
163-
164- const counts = issue . incidents_info ;
165- const countElement = (
166- < GroupedTestStatus
167- pass = { counts . PASS }
168- fail = { counts . FAIL }
169- nullStatus = { counts . NULL }
170- error = { counts . ERROR }
171- done = { counts . DONE }
172- miss = { counts . MISS }
173- skip = { counts . SKIP }
174- />
175- ) ;
176-
177- return (
178- < div
179- key = { `${ issue . id } ${ issue . version } ` }
180- className = "flex w-full justify-between gap-4"
181- >
182- < div className = "flex items-center gap-3" >
183- < div className = "overflow-hidden" >
184- < FilterLink
185- filterSection = { issueFilterSection }
186- filterValue = { getIssueFilterLabel ( issue ) }
187- diffFilter = { diffFilter }
188- >
189- < span className = "flex items-center text-sm" >
190- < div className = "flex gap-2" >
191- { countElement }
192- < ListingItem
193- showNumber = { false }
194- hasBottomBorder
195- text = {
196- issue . comment ??
197- formatMessage ( { id : 'issue.uncategorized' } )
198- }
199- tooltip = { issue . comment }
200- />
201- </ div >
202- { extraDetailsLoading ? (
203- < LoadingCircle className = "mx-2" />
204- ) : (
205- first_seen && (
206- < span className = "pb-1 text-nowrap text-gray-600" >
207- < TooltipDateTime
208- dateTime = { first_seen }
209- lineBreak = { true }
210- showRelative = { true }
211- message = { `• ${ formatMessage ( { id : 'issue.firstSeen' } ) } : ` }
212- />
213- </ span >
214- )
215- ) }
216- </ span >
217- </ FilterLink >
218- </ div >
219- { tagPills && ! extraDetailsLoading && (
220- < div className = "flex gap-3" > { ...tagPills } </ div >
221- ) }
222- </ div >
223- < div className = "flex items-center gap-4" >
224- { issue . report_url && (
225- < LinkWithIcon
226- link = { issue . report_url }
227- icon = { < LinkIcon className = "h-4 w-4" /> }
228- />
229- ) }
230- { issue . id !== UNCATEGORIZED_STRING && (
231- < MemoizedMoreDetailsIconLink
232- linkProps = { getIssueLink ( issue . id , issue . version ) }
233- />
234- ) }
235- </ div >
236- </ div >
237- ) ;
238- } ) }
251+ { sortedIssues . map ( issue => (
252+ < IssueItem
253+ key = { `${ issue . id } ${ issue . version } ` }
254+ issue = { issue }
255+ extraDetails = { issueExtraDetails ?. [ issue . id ] }
256+ extraDetailsLoading = { extraDetailsLoading }
257+ detailsId = { detailsId }
258+ getIssueLink = { getIssueLink }
259+ issueFilterSection = { issueFilterSection }
260+ diffFilter = { diffFilter }
261+ />
262+ ) ) }
239263 { failedWithUnknownIssues && (
240264 < FilterLink
241265 filterSection = { issueFilterSection }
0 commit comments