Skip to content

Commit 71764ad

Browse files
authored
ref(issues): use top issues endpoint (#103899)
Switch to using the getsentry endpoint rather than having to copy paste data.
1 parent 7b7895b commit 71764ad

File tree

1 file changed

+57
-137
lines changed

1 file changed

+57
-137
lines changed

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

Lines changed: 57 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
import {Fragment, useEffect, useMemo, useState} from 'react';
1+
import {Fragment, useMemo, useState} from 'react';
22
import styled from '@emotion/styled';
33

44
import {Container, Flex} from '@sentry/scraps/layout';
55
import {Heading, Text} from '@sentry/scraps/text';
66

77
import {Breadcrumbs} from 'sentry/components/breadcrumbs';
8-
import {Alert} from 'sentry/components/core/alert';
98
import {Button} from 'sentry/components/core/button';
109
import {Checkbox} from 'sentry/components/core/checkbox';
1110
import {Disclosure} from 'sentry/components/core/disclosure';
@@ -32,8 +31,6 @@ import useOrganization from 'sentry/utils/useOrganization';
3231
import {useUser} from 'sentry/utils/useUser';
3332
import {useUserTeams} from 'sentry/utils/useUserTeams';
3433

35-
const STORAGE_KEY = 'dynamic-grouping-cluster-data';
36-
3734
interface AssignedEntity {
3835
email: string | null;
3936
id: string;
@@ -52,11 +49,14 @@ interface ClusterSummary {
5249
group_ids: number[];
5350
issue_titles: string[];
5451
project_ids: number[];
55-
summary: string;
5652
tags: string[];
5753
title: string;
5854
}
5955

56+
interface TopIssuesResponse {
57+
data: ClusterSummary[];
58+
}
59+
6060
// Compact issue preview for dynamic grouping - no short ID or quick fix icon
6161
function CompactIssuePreview({group}: {group: Group}) {
6262
const organization = useOrganization();
@@ -215,7 +215,7 @@ function ClusterCard({
215215
</TitleHeading>
216216
</Disclosure.Title>
217217
<Disclosure.Content>
218-
<SummaryText variant="muted">{cluster.summary}</SummaryText>
218+
<SummaryText variant="muted">{cluster.description}</SummaryText>
219219
{cluster.fixability_score !== null && (
220220
<ConfidenceText size="sm" variant="muted">
221221
{t('%s%% confidence', Math.round(cluster.fixability_score * 100))}
@@ -267,72 +267,34 @@ function DynamicGrouping() {
267267
const organization = useOrganization();
268268
const user = useUser();
269269
const {teams} = useUserTeams();
270-
const [jsonInput, setJsonInput] = useState('');
271-
const [clusterData, setClusterData] = useState<ClusterSummary[]>([]);
272-
const [parseError, setParseError] = useState<string | null>(null);
273-
const [showInput, setShowInput] = useState(true);
274270
const [filterByAssignedToMe, setFilterByAssignedToMe] = useState(true);
275271
const [minFixabilityScore, setMinFixabilityScore] = useState(50);
276272
const [removingClusterId, setRemovingClusterId] = useState<number | null>(null);
273+
const [removedClusterIds, setRemovedClusterIds] = useState<Set<number>>(new Set());
277274

278-
// Load from localStorage on mount
279-
useEffect(() => {
280-
const stored = localStorage.getItem(STORAGE_KEY);
281-
if (stored) {
282-
try {
283-
const parsed = JSON.parse(stored);
284-
setClusterData(parsed);
285-
setJsonInput(stored);
286-
setShowInput(false);
287-
} catch {
288-
// If stored data is invalid, clear it
289-
localStorage.removeItem(STORAGE_KEY);
290-
}
291-
}
292-
}, []);
293-
294-
const handleJsonSubmit = () => {
295-
try {
296-
const parsed = JSON.parse(jsonInput);
297-
if (!Array.isArray(parsed)) {
298-
setParseError(t('JSON must be an array of cluster summaries'));
299-
return;
300-
}
301-
setClusterData(parsed);
302-
setParseError(null);
303-
setShowInput(false);
304-
localStorage.setItem(STORAGE_KEY, jsonInput);
305-
} catch (error) {
306-
setParseError(error instanceof Error ? error.message : t('Invalid JSON format'));
275+
// Fetch cluster data from API
276+
const {data: topIssuesResponse, isPending} = useApiQuery<TopIssuesResponse>(
277+
[`/organizations/${organization.slug}/top-issues/`],
278+
{
279+
staleTime: 60000, // Cache for 1 minute
307280
}
308-
};
281+
);
309282

310-
const handleClear = () => {
311-
setClusterData([]);
312-
setJsonInput('');
313-
setParseError(null);
314-
setShowInput(true);
315-
localStorage.removeItem(STORAGE_KEY);
316-
};
283+
const clusterData = useMemo(
284+
() => topIssuesResponse?.data ?? [],
285+
[topIssuesResponse?.data]
286+
);
317287

318288
const handleRemoveCluster = (clusterId: number) => {
319289
// Start animation
320290
setRemovingClusterId(clusterId);
321291

322292
// Wait for animation to complete before removing
323293
setTimeout(() => {
324-
const updatedClusters = clusterData.filter(
325-
cluster => cluster.cluster_id !== clusterId
326-
);
327-
setClusterData(updatedClusters);
294+
const updatedRemovedIds = new Set(removedClusterIds);
295+
updatedRemovedIds.add(clusterId);
296+
setRemovedClusterIds(updatedRemovedIds);
328297
setRemovingClusterId(null);
329-
330-
// Update localStorage with the new data
331-
if (updatedClusters.length > 0) {
332-
localStorage.setItem(STORAGE_KEY, JSON.stringify(updatedClusters));
333-
} else {
334-
localStorage.removeItem(STORAGE_KEY);
335-
}
336298
}, 300); // Match the animation duration
337299
};
338300

@@ -364,6 +326,11 @@ function DynamicGrouping() {
364326
const filteredAndSortedClusters = useMemo(() => {
365327
return [...clusterData]
366328
.filter((cluster: ClusterSummary) => {
329+
// Filter out removed clusters
330+
if (removedClusterIds.has(cluster.cluster_id)) {
331+
return false;
332+
}
333+
367334
// Filter by fixability score - hide clusters below threshold
368335
const fixabilityScore = (cluster.fixability_score ?? 0) * 100;
369336
if (fixabilityScore < minFixabilityScore) {
@@ -380,7 +347,13 @@ function DynamicGrouping() {
380347
(a: ClusterSummary, b: ClusterSummary) =>
381348
(b.fixability_score ?? 0) - (a.fixability_score ?? 0)
382349
);
383-
}, [clusterData, filterByAssignedToMe, minFixabilityScore, isClusterAssignedToMe]);
350+
}, [
351+
clusterData,
352+
removedClusterIds,
353+
filterByAssignedToMe,
354+
minFixabilityScore,
355+
isClusterAssignedToMe,
356+
]);
384357

385358
const totalIssues = useMemo(() => {
386359
return filteredAndSortedClusters.reduce(
@@ -409,52 +382,15 @@ function DynamicGrouping() {
409382
/>
410383

411384
<PageHeader>
412-
<Flex justify="between" align="start" gap="sm">
413-
<div style={{flex: 1}}>
414-
<Heading as="h1">{t('Top Issues')}</Heading>
415-
</div>
416-
{clusterData.length > 0 && !showInput && (
417-
<Flex gap="sm" style={{flexShrink: 0}}>
418-
<Button size="sm" onClick={() => setShowInput(true)}>
419-
{t('Update Data')}
420-
</Button>
421-
<Button size="sm" priority="danger" onClick={handleClear}>
422-
{t('Clear')}
423-
</Button>
424-
</Flex>
425-
)}
426-
</Flex>
385+
<Heading as="h1">{t('Top Issues')}</Heading>
427386
</PageHeader>
428387

429-
{showInput || clusterData.length === 0 ? (
430-
<Container
431-
padding="lg"
432-
border="primary"
433-
radius="md"
434-
marginBottom="lg"
435-
background="primary"
436-
>
437-
<Flex direction="column" gap="md">
438-
<Text size="sm" variant="muted">
439-
{t('Paste cluster summaries JSON data below:')}
440-
</Text>
441-
<JsonTextarea
442-
value={jsonInput}
443-
onChange={e => setJsonInput(e.target.value)}
444-
placeholder={t('Paste JSON array here...')}
445-
rows={10}
446-
/>
447-
{parseError && <Alert type="error">{parseError}</Alert>}
448-
<Flex gap="sm">
449-
<Button priority="primary" onClick={handleJsonSubmit}>
450-
{t('Load Data')}
451-
</Button>
452-
{clusterData.length > 0 && (
453-
<Button onClick={() => setShowInput(false)}>{t('Cancel')}</Button>
454-
)}
455-
</Flex>
456-
</Flex>
457-
</Container>
388+
{isPending ? (
389+
<Flex direction="column" gap="md" marginTop="lg">
390+
{[0, 1, 2].map(i => (
391+
<Placeholder key={i} height="200px" />
392+
))}
393+
</Flex>
458394
) : (
459395
<Fragment>
460396
<Flex marginBottom="lg">
@@ -512,16 +448,24 @@ function DynamicGrouping() {
512448
</Disclosure>
513449
</Container>
514450

515-
<CardsGrid>
516-
{filteredAndSortedClusters.map((cluster: ClusterSummary) => (
517-
<ClusterCard
518-
key={cluster.cluster_id}
519-
cluster={cluster}
520-
onRemove={handleRemoveCluster}
521-
isRemoving={removingClusterId === cluster.cluster_id}
522-
/>
523-
))}
524-
</CardsGrid>
451+
{filteredAndSortedClusters.length === 0 ? (
452+
<Container padding="lg" border="primary" radius="md" background="primary">
453+
<Text variant="muted" style={{textAlign: 'center'}}>
454+
{t('No clusters match the current filters')}
455+
</Text>
456+
</Container>
457+
) : (
458+
<CardsGrid>
459+
{filteredAndSortedClusters.map((cluster: ClusterSummary) => (
460+
<ClusterCard
461+
key={cluster.cluster_id}
462+
cluster={cluster}
463+
onRemove={handleRemoveCluster}
464+
isRemoving={removingClusterId === cluster.cluster_id}
465+
/>
466+
))}
467+
</CardsGrid>
468+
)}
525469
</Fragment>
526470
)}
527471
</PageContainer>
@@ -742,30 +686,6 @@ const EventCount = styled('span')`
742686
font-weight: ${p => p.theme.fontWeight.bold};
743687
`;
744688

745-
const JsonTextarea = styled('textarea')`
746-
width: 100%;
747-
min-height: 300px;
748-
padding: ${space(1.5)};
749-
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace;
750-
font-size: 13px;
751-
line-height: 1.5;
752-
background: ${p => p.theme.backgroundSecondary};
753-
border: 1px solid ${p => p.theme.border};
754-
border-radius: ${p => p.theme.borderRadius};
755-
color: ${p => p.theme.textColor};
756-
resize: vertical;
757-
758-
&:focus {
759-
outline: none;
760-
border-color: ${p => p.theme.purple300};
761-
box-shadow: 0 0 0 1px ${p => p.theme.purple300};
762-
}
763-
764-
&::placeholder {
765-
color: ${p => p.theme.subText};
766-
}
767-
`;
768-
769689
const AdvancedFilterContent = styled('div')`
770690
padding: ${space(2)} 0;
771691
display: flex;

0 commit comments

Comments
 (0)