Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
210 changes: 119 additions & 91 deletions static/app/views/issueList/pages/dynamicGrouping.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {Fragment, useCallback, useMemo, useState} from 'react';
import styled from '@emotion/styled';

import {Tag} from '@sentry/scraps/badge';
import {Container, Flex} from '@sentry/scraps/layout';
import {Heading, Text} from '@sentry/scraps/text';

Expand Down Expand Up @@ -243,6 +244,13 @@ function ClusterCard({
)}
</Fragment>
)}
{cluster.tags && cluster.tags.length > 0 && (
<Flex wrap="wrap" gap="xs">
{cluster.tags.map(tag => (
<Tag key={tag}>{tag}</Tag>
))}
</Flex>
)}
</Flex>
<IssueCountBadge>
<IssueCountNumber>{issueCount}</IssueCountNumber>
Expand Down Expand Up @@ -346,6 +354,7 @@ function DynamicGrouping() {
null
);
const [jsonError, setJsonError] = useState<string | null>(null);
const [disableFilters, setDisableFilters] = useState(false);

// Fetch cluster data from API
const {data: topIssuesResponse, isPending} = useApiQuery<TopIssuesResponse>(
Expand Down Expand Up @@ -377,6 +386,7 @@ function DynamicGrouping() {
setCustomClusterData(null);
setJsonInputValue('');
setJsonError(null);
setDisableFilters(false);
}, []);

const clusterData = customClusterData ?? topIssuesResponse?.data ?? [];
Comment on lines 386 to 392
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: removedClusterIds state is not reset when custom data is cleared.
Severity: CRITICAL | Confidence: High

🔍 Detailed Analysis

When handleClearCustomData is called, the removedClusterIds state is not reset. This allows previously 'resolved' cluster IDs to persist, causing clusters with those IDs to be unexpectedly hidden if they appear in newly loaded custom JSON data, leading to incorrect filtering.

💡 Suggested Fix

Add setRemovedClusterIds(new Set()) to the handleClearCustomData function to ensure removedClusterIds is cleared upon custom data removal.

🤖 Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: static/app/views/issueList/pages/dynamicGrouping.tsx#L386-L392

Potential issue: When `handleClearCustomData` is called, the `removedClusterIds` state
is not reset. This allows previously 'resolved' cluster IDs to persist, causing clusters
with those IDs to be unexpectedly hidden if they appear in newly loaded custom JSON
data, leading to incorrect filtering.

Did we get this right? 👍 / 👎 to inform future reviews.
Reference ID: 3642060

Expand Down Expand Up @@ -419,32 +429,36 @@ function DynamicGrouping() {
setRemovedClusterIds(prev => new Set([...prev, clusterId]));
};

const filteredAndSortedClusters = clusterData
.filter(cluster => {
if (removedClusterIds.has(cluster.cluster_id)) return false;

const fixabilityScore = (cluster.fixability_score ?? 0) * 100;
if (fixabilityScore < minFixabilityScore) return false;

if (filterByAssignedToMe) {
if (!cluster.assignedTo?.length) return false;
return cluster.assignedTo.some(
entity =>
(entity.type === 'user' && entity.id === user.id) ||
(entity.type === 'team' && userTeams.some(team => team.id === entity.id))
);
}

if (isTeamFilterActive) {
if (!cluster.assignedTo?.length) return false;
return cluster.assignedTo.some(
entity => entity.type === 'team' && selectedTeamIds.has(entity.id)
);
}

return true;
})
.sort((a, b) => (b.fixability_score ?? 0) - (a.fixability_score ?? 0));
// When using custom JSON data with filters disabled, skip all filtering and sorting
const shouldSkipFilters = isUsingCustomData && disableFilters;
const filteredAndSortedClusters = shouldSkipFilters
? clusterData.filter(cluster => !removedClusterIds.has(cluster.cluster_id))
: clusterData
.filter(cluster => {
if (removedClusterIds.has(cluster.cluster_id)) return false;

const fixabilityScore = (cluster.fixability_score ?? 0) * 100;
if (fixabilityScore < minFixabilityScore) return false;

if (filterByAssignedToMe) {
if (!cluster.assignedTo?.length) return false;
return cluster.assignedTo.some(
entity =>
(entity.type === 'user' && entity.id === user.id) ||
(entity.type === 'team' && userTeams.some(team => team.id === entity.id))
);
}

if (isTeamFilterActive) {
if (!cluster.assignedTo?.length) return false;
return cluster.assignedTo.some(
entity => entity.type === 'team' && selectedTeamIds.has(entity.id)
);
}

return true;
})
.sort((a, b) => (b.fixability_score ?? 0) - (a.fixability_score ?? 0));

const totalIssues = filteredAndSortedClusters.flatMap(c => c.group_ids).length;

Expand Down Expand Up @@ -506,6 +520,17 @@ function DynamicGrouping() {
{jsonError}
</Text>
)}
<Flex gap="sm" align="center" style={{marginTop: space(1.5)}}>
<Checkbox
checked={disableFilters}
onChange={e => setDisableFilters(e.target.checked)}
aria-label={t('Disable filters and sorting')}
size="sm"
/>
<Text size="sm" variant="muted">
{t('Disable filters and sorting')}
</Text>
</Flex>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Disable filters checkbox inaccessible after loading data

The disableFilters checkbox is only visible when showJsonInput is true, but clicking "Parse and Load" hides the JSON input section while keeping disableFilters in its current state. Users who enable this option before loading data cannot re-enable filters without clearing all custom data or reopening the JSON input section, creating a hidden state that affects functionality without visible controls.

Fix in Cursor Fix in Web

<Flex gap="sm" style={{marginTop: space(1)}}>
<Button size="sm" priority="primary" onClick={handleParseJson}>
{t('Parse and Load')}
Expand All @@ -532,77 +557,80 @@ function DynamicGrouping() {
totalIssues,
filteredAndSortedClusters.length
)}
{shouldSkipFilters && ` ${t('(filters disabled)')}`}
</Text>

<Container
padding="sm"
border="primary"
radius="md"
background="primary"
marginTop="md"
>
<Disclosure>
<Disclosure.Title>
<Text size="sm" bold>
{t('More Filters')}
</Text>
</Disclosure.Title>
<Disclosure.Content>
<Flex direction="column" gap="md" paddingTop="md">
<Flex gap="sm" align="center">
<Checkbox
checked={filterByAssignedToMe}
onChange={e => handleAssignedToMeChange(e.target.checked)}
aria-label={t('Show only issues assigned to me')}
size="sm"
disabled={isTeamFilterActive}
/>
<FilterLabel disabled={isTeamFilterActive}>
{t('Only show issues assigned to me')}
</FilterLabel>
</Flex>

{teamsInData.length > 0 && (
<Flex direction="column" gap="sm">
<FilterLabel disabled={filterByAssignedToMe}>
{t('Filter by teams')}
{!shouldSkipFilters && (
<Container
padding="sm"
border="primary"
radius="md"
background="primary"
marginTop="md"
>
<Disclosure>
<Disclosure.Title>
<Text size="sm" bold>
{t('More Filters')}
</Text>
</Disclosure.Title>
<Disclosure.Content>
<Flex direction="column" gap="md" paddingTop="md">
<Flex gap="sm" align="center">
<Checkbox
checked={filterByAssignedToMe}
onChange={e => handleAssignedToMeChange(e.target.checked)}
aria-label={t('Show only issues assigned to me')}
size="sm"
disabled={isTeamFilterActive}
/>
<FilterLabel disabled={isTeamFilterActive}>
{t('Only show issues assigned to me')}
</FilterLabel>
<Flex direction="column" gap="xs" style={{paddingLeft: 8}}>
{teamsInData.map(team => (
<Flex key={team.id} gap="sm" align="center">
<Checkbox
checked={selectedTeamIds.has(team.id)}
onChange={() => handleTeamToggle(team.id)}
aria-label={t('Filter by team %s', team.name)}
size="sm"
disabled={filterByAssignedToMe}
/>
<FilterLabel disabled={filterByAssignedToMe}>
#{team.name}
</FilterLabel>
</Flex>
))}
</Flex>

{teamsInData.length > 0 && (
<Flex direction="column" gap="sm">
<FilterLabel disabled={filterByAssignedToMe}>
{t('Filter by teams')}
</FilterLabel>
<Flex direction="column" gap="xs" style={{paddingLeft: 8}}>
{teamsInData.map(team => (
<Flex key={team.id} gap="sm" align="center">
<Checkbox
checked={selectedTeamIds.has(team.id)}
onChange={() => handleTeamToggle(team.id)}
aria-label={t('Filter by team %s', team.name)}
size="sm"
disabled={filterByAssignedToMe}
/>
<FilterLabel disabled={filterByAssignedToMe}>
#{team.name}
</FilterLabel>
</Flex>
))}
</Flex>
</Flex>
)}

<Flex gap="sm" align="center">
<Text size="sm" variant="muted">
{t('Minimum fixability score (%)')}
</Text>
<NumberInput
min={0}
max={100}
value={minFixabilityScore}
onChange={value => setMinFixabilityScore(value ?? 0)}
aria-label={t('Minimum fixability score')}
size="sm"
/>
</Flex>
)}

<Flex gap="sm" align="center">
<Text size="sm" variant="muted">
{t('Minimum fixability score (%)')}
</Text>
<NumberInput
min={0}
max={100}
value={minFixabilityScore}
onChange={value => setMinFixabilityScore(value ?? 0)}
aria-label={t('Minimum fixability score')}
size="sm"
/>
</Flex>
</Flex>
</Disclosure.Content>
</Disclosure>
</Container>
</Disclosure.Content>
</Disclosure>
</Container>
)}
</Fragment>
)}
</HeaderSection>
Comment on lines +626 to 636
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: teamsInData is not derived from clusterData, preventing custom data team filtering.
Severity: HIGH | Confidence: High

🔍 Detailed Analysis

The teamsInData state, which populates the team filter UI, is derived solely from topIssuesResponse?.data (API data) and not from clusterData (which can be custom JSON). This prevents teams present only in custom JSON data from appearing as filter options in the UI, limiting filtering capabilities for custom datasets.

💡 Suggested Fix

Modify the teamsInData useMemo hook to derive its data from clusterData instead of topIssuesResponse?.data to include teams from custom JSON.

🤖 Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: static/app/views/issueList/pages/dynamicGrouping.tsx#L557-L636

Potential issue: The `teamsInData` state, which populates the team filter UI, is derived
solely from `topIssuesResponse?.data` (API data) and not from `clusterData` (which can
be custom JSON). This prevents teams present only in custom JSON data from appearing as
filter options in the UI, limiting filtering capabilities for custom datasets.

Did we get this right? 👍 / 👎 to inform future reviews.
Reference ID: 3642060

Expand Down
Loading