Skip to content

Commit 075a96b

Browse files
authored
fix(issues): resolve column styling problems in top issues (#103946)
The description of a cluster used to be hidden on a Disclosure on the title of the cluster, but that was a ton of trouble — change it to a "view summary" button. Also fix some issues causing us to sometimes only have 1 column
1 parent 607e93f commit 075a96b

File tree

1 file changed

+161
-129
lines changed

1 file changed

+161
-129
lines changed

static/app/views/issueList/pages/dynamicGrouping.tsx

Lines changed: 161 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,6 @@ function CompactIssuePreview({group}: {group: Group}) {
102102

103103
function ClusterIssues({groupIds}: {groupIds: number[]}) {
104104
const organization = useOrganization();
105-
106105
const previewGroupIds = groupIds.slice(0, 3);
107106

108107
const {data: groups, isPending} = useApiQuery<Group[]>(
@@ -120,11 +119,7 @@ function ClusterIssues({groupIds}: {groupIds: number[]}) {
120119
}
121120
);
122121

123-
if (isPending) {
124-
return <LoadingIndicator size={24} />;
125-
}
126-
127-
if (!groups || groups.length === 0) {
122+
if (isPending || !groups || groups.length === 0) {
128123
return null;
129124
}
130125

@@ -151,33 +146,27 @@ function ClusterCard({
151146
}) {
152147
const organization = useOrganization();
153148
const issueCount = cluster.group_ids.length;
149+
const [showDescription, setShowDescription] = useState(false);
154150

155151
return (
156152
<CardContainer>
157-
<Flex
158-
justify="between"
159-
align="start"
160-
gap="md"
161-
paddingBottom="md"
162-
borderBottom="primary"
163-
>
164-
<Disclosure>
165-
<Disclosure.Title>
166-
<Heading as="h3" size="md" style={{wordBreak: 'break-word'}}>
167-
{cluster.title}
168-
</Heading>
169-
</Disclosure.Title>
170-
<Disclosure.Content>
171-
<Text as="p" variant="muted" style={{marginBottom: space(1.5)}}>
172-
{cluster.description}
173-
</Text>
174-
{cluster.fixability_score !== null && (
175-
<Text size="sm" variant="muted">
176-
{t('%s%% confidence', Math.round(cluster.fixability_score * 100))}
177-
</Text>
178-
)}
179-
</Disclosure.Content>
180-
</Disclosure>
153+
<Flex justify="between" align="start" gap="md" paddingBottom="md">
154+
<Flex direction="column" gap="xs" style={{flex: 1, minWidth: 0}}>
155+
<Heading as="h3" size="md" style={{wordBreak: 'break-word'}}>
156+
{cluster.title}
157+
</Heading>
158+
{cluster.description && (
159+
<Fragment>
160+
{showDescription ? (
161+
<DescriptionText>{cluster.description}</DescriptionText>
162+
) : (
163+
<ReadMoreButton onClick={() => setShowDescription(true)}>
164+
{t('View summary')}
165+
</ReadMoreButton>
166+
)}
167+
</Fragment>
168+
)}
169+
</Flex>
181170
<IssueCountBadge>
182171
<IssueCountNumber>{issueCount}</IssueCountNumber>
183172
<Text size="xs" variant="muted" uppercase>
@@ -264,119 +253,142 @@ function DynamicGrouping() {
264253
}
265254

266255
return (
267-
<Container padding="lg" maxWidth="1600px" margin="0 auto">
268-
<Breadcrumbs
269-
crumbs={[
270-
{
271-
label: t('Issues'),
272-
to: `/organizations/${organization.slug}/issues/`,
273-
},
274-
{
275-
label: t('Top Issues'),
276-
},
277-
]}
278-
/>
279-
280-
<Heading as="h1" style={{marginBottom: space(2)}}>
281-
{t('Top Issues')}
282-
</Heading>
283-
284-
{isPending ? (
285-
<LoadingIndicator />
286-
) : (
287-
<Fragment>
288-
<Text size="sm" variant="muted">
289-
{tn(
290-
'Viewing %s issue in %s cluster',
291-
'Viewing %s issues across %s clusters',
292-
totalIssues,
293-
filteredAndSortedClusters.length
294-
)}
295-
</Text>
256+
<PageWrapper>
257+
<HeaderSection>
258+
<Breadcrumbs
259+
crumbs={[
260+
{
261+
label: t('Issues'),
262+
to: `/organizations/${organization.slug}/issues/`,
263+
},
264+
{
265+
label: t('Top Issues'),
266+
},
267+
]}
268+
/>
269+
270+
<Heading as="h1" style={{marginBottom: space(2)}}>
271+
{t('Top Issues')}
272+
</Heading>
273+
274+
{isPending ? null : (
275+
<Fragment>
276+
<Text size="sm" variant="muted">
277+
{tn(
278+
'Viewing %s issue in %s cluster',
279+
'Viewing %s issues across %s clusters',
280+
totalIssues,
281+
filteredAndSortedClusters.length
282+
)}
283+
</Text>
296284

297-
<Container
298-
padding="sm"
299-
border="primary"
300-
radius="md"
301-
background="primary"
302-
marginTop="md"
303-
marginBottom="lg"
304-
>
305-
<Disclosure>
306-
<Disclosure.Title>
307-
<Text size="sm" bold>
308-
{t('More Filters')}
309-
</Text>
310-
</Disclosure.Title>
311-
<Disclosure.Content>
312-
<Flex direction="column" gap="md" paddingTop="md">
313-
<Flex gap="sm" align="center">
314-
<Checkbox
315-
checked={filterByAssignedToMe}
316-
onChange={e => setFilterByAssignedToMe(e.target.checked)}
317-
aria-label={t('Show only issues assigned to me')}
318-
size="sm"
319-
/>
320-
<Text size="sm" variant="muted">
321-
{t('Only show issues assigned to me')}
322-
</Text>
323-
</Flex>
324-
<Flex gap="sm" align="center">
325-
<Text size="sm" variant="muted">
326-
{t('Minimum fixability score (%)')}
327-
</Text>
328-
<NumberInput
329-
min={0}
330-
max={100}
331-
value={minFixabilityScore}
332-
onChange={value => setMinFixabilityScore(value ?? 0)}
333-
aria-label={t('Minimum fixability score')}
334-
size="sm"
335-
/>
285+
<Container
286+
padding="sm"
287+
border="primary"
288+
radius="md"
289+
background="primary"
290+
marginTop="md"
291+
>
292+
<Disclosure>
293+
<Disclosure.Title>
294+
<Text size="sm" bold>
295+
{t('More Filters')}
296+
</Text>
297+
</Disclosure.Title>
298+
<Disclosure.Content>
299+
<Flex direction="column" gap="md" paddingTop="md">
300+
<Flex gap="sm" align="center">
301+
<Checkbox
302+
checked={filterByAssignedToMe}
303+
onChange={e => setFilterByAssignedToMe(e.target.checked)}
304+
aria-label={t('Show only issues assigned to me')}
305+
size="sm"
306+
/>
307+
<Text size="sm" variant="muted">
308+
{t('Only show issues assigned to me')}
309+
</Text>
310+
</Flex>
311+
<Flex gap="sm" align="center">
312+
<Text size="sm" variant="muted">
313+
{t('Minimum fixability score (%)')}
314+
</Text>
315+
<NumberInput
316+
min={0}
317+
max={100}
318+
value={minFixabilityScore}
319+
onChange={value => setMinFixabilityScore(value ?? 0)}
320+
aria-label={t('Minimum fixability score')}
321+
size="sm"
322+
/>
323+
</Flex>
336324
</Flex>
337-
</Flex>
338-
</Disclosure.Content>
339-
</Disclosure>
340-
</Container>
341-
342-
{filteredAndSortedClusters.length === 0 ? (
343-
<Container padding="lg" border="primary" radius="md" background="primary">
344-
<Text variant="muted" align="center" as="div">
345-
{t('No clusters match the current filters')}
346-
</Text>
325+
</Disclosure.Content>
326+
</Disclosure>
347327
</Container>
348-
) : (
349-
<CardsGrid>
350-
{filteredAndSortedClusters.map(cluster => (
351-
<ClusterCard
352-
key={cluster.cluster_id}
353-
cluster={cluster}
354-
onRemove={handleRemoveCluster}
355-
/>
356-
))}
357-
</CardsGrid>
358-
)}
359-
</Fragment>
360-
)}
361-
</Container>
328+
</Fragment>
329+
)}
330+
</HeaderSection>
331+
332+
<CardsSection>
333+
{isPending ? (
334+
<LoadingIndicator />
335+
) : filteredAndSortedClusters.length === 0 ? (
336+
<Container padding="lg" border="primary" radius="md" background="primary">
337+
<Text variant="muted" align="center" as="div">
338+
{t('No clusters match the current filters')}
339+
</Text>
340+
</Container>
341+
) : (
342+
<CardsGrid>
343+
{filteredAndSortedClusters.map(cluster => (
344+
<ClusterCard
345+
key={cluster.cluster_id}
346+
cluster={cluster}
347+
onRemove={handleRemoveCluster}
348+
/>
349+
))}
350+
</CardsGrid>
351+
)}
352+
</CardsSection>
353+
</PageWrapper>
362354
);
363355
}
364356

365-
// Grid layout for cards - needs CSS grid
357+
const PageWrapper = styled('div')`
358+
display: flex;
359+
flex-direction: column;
360+
min-height: 100%;
361+
`;
362+
363+
const HeaderSection = styled('div')`
364+
padding: ${space(4)} ${space(4)} ${space(3)};
365+
`;
366+
367+
const CardsSection = styled('div')`
368+
flex: 1;
369+
padding: ${space(2)} ${space(4)} ${space(4)};
370+
`;
371+
366372
const CardsGrid = styled('div')`
367373
display: grid;
368-
grid-template-columns: repeat(auto-fill, minmax(500px, 1fr));
374+
grid-template-columns: repeat(2, 1fr);
369375
gap: ${space(3)};
376+
align-items: start;
377+
378+
@media (max-width: ${p => p.theme.breakpoints.lg}) {
379+
grid-template-columns: 1fr;
380+
}
370381
`;
371382

372-
// Card with hover effect - needs custom transition/hover styles
383+
// Card with hover effect
373384
const CardContainer = styled('div')`
374385
background: ${p => p.theme.background};
375386
border: 1px solid ${p => p.theme.border};
376387
border-radius: ${p => p.theme.borderRadius};
377388
padding: ${space(3)};
378389
display: flex;
379390
flex-direction: column;
391+
min-width: 0;
380392
transition:
381393
border-color 0.2s ease,
382394
box-shadow 0.2s ease;
@@ -405,7 +417,7 @@ const IssueCountNumber = styled('div')`
405417
line-height: 1;
406418
`;
407419

408-
// Issue preview link with hover transform effect
420+
// Issue preview link with hover effect
409421
const IssuePreviewLink = styled(Link)`
410422
display: block;
411423
padding: ${space(1.5)} ${space(2)};
@@ -414,13 +426,11 @@ const IssuePreviewLink = styled(Link)`
414426
border-radius: ${p => p.theme.borderRadius};
415427
transition:
416428
border-color 0.15s ease,
417-
background 0.15s ease,
418-
transform 0.15s ease;
429+
background 0.15s ease;
419430
420431
&:hover {
421432
border-color: ${p => p.theme.purple300};
422433
background: ${p => p.theme.backgroundElevated};
423-
transform: translateX(2px);
424434
}
425435
`;
426436

@@ -454,4 +464,26 @@ const MetaSeparator = styled('div')`
454464
background-color: ${p => p.theme.innerBorder};
455465
`;
456466

467+
const ReadMoreButton = styled('button')`
468+
background: none;
469+
border: none;
470+
padding: 0;
471+
font-size: ${p => p.theme.fontSize.sm};
472+
color: ${p => p.theme.subText};
473+
cursor: pointer;
474+
text-align: left;
475+
476+
&:hover {
477+
color: ${p => p.theme.textColor};
478+
text-decoration: underline;
479+
}
480+
`;
481+
482+
const DescriptionText = styled('p')`
483+
margin: 0;
484+
font-size: ${p => p.theme.fontSize.sm};
485+
color: ${p => p.theme.subText};
486+
line-height: 1.5;
487+
`;
488+
457489
export default DynamicGrouping;

0 commit comments

Comments
 (0)