Skip to content

Commit f7b8318

Browse files
author
André Luiz
authored
refactor(style): extract IssueItem and fix row layout (#1825)
Move per-issue rendering logic from IssuesList map callback into a dedicated IssueItem component, reducing cyclomatic complexity. Fix layout: justify-between now conditional on hasRightContent, add min-w-0 to left group so overflow-hidden truncates correctly, and shrink-0 on right group and tagPills to prevent compression. Closes #1426
1 parent eb8033e commit f7b8318

2 files changed

Lines changed: 126 additions & 102 deletions

File tree

dashboard/src/components/Cards/IssuesList.tsx

Lines changed: 125 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,119 @@ import { MemoizedSectionError } from '@/components/DetailsPages/SectionError';
4040

4141
import { 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+
43156
interface 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}

dashboard/src/components/ListingItem/ListingItem.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ const ListingItem = ({
9898
return (
9999
<Tooltip>
100100
<div className="flex w-full">
101-
<TooltipTrigger className="max-w-[200px] overflow-hidden md:max-w-[100px] lg:max-w-[280px] xl:max-w-[475px] 2xl:max-w-[700px]">
101+
<TooltipTrigger className="max-w-full overflow-hidden">
102102
{children}
103103
</TooltipTrigger>
104104

0 commit comments

Comments
 (0)