Skip to content
Merged
Show file tree
Hide file tree
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
103 changes: 103 additions & 0 deletions static/app/components/events/eventXrayDiff.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import EmptyStateWarning from 'sentry/components/emptyStateWarning';
import ErrorBoundary from 'sentry/components/errorBoundary';
import LoadingError from 'sentry/components/loadingError';
import LoadingIndicator from 'sentry/components/loadingIndicator';
import {t} from 'sentry/locale';
import type {Event} from 'sentry/types/event';
import type {Project} from 'sentry/types/project';
import {useApiQuery} from 'sentry/utils/queryClient';
import useOrganization from 'sentry/utils/useOrganization';
import {SectionKey} from 'sentry/views/issueDetails/streamline/context';
import {InterimSection} from 'sentry/views/issueDetails/streamline/interimSection';
import {TreemapDiffSection} from 'sentry/views/preprod/buildComparison/main/treemapDiffSection';
import type {SizeAnalysisComparisonResults} from 'sentry/views/preprod/types/appSizeTypes';

// Currently Content and Section props are identical.
type ContentProps = SectionProps;

function EventXrayDiffContent({baseMetricId, headMetricId, project}: ContentProps) {
const organization = useOrganization();

const query = useApiQuery<SizeAnalysisComparisonResults>(
[
`/projects/${organization.slug}/${project.id}/preprodartifacts/size-analysis/compare/${headMetricId}/${baseMetricId}/download/`,
],
{
staleTime: 0,
}
);

if (query.isLoading) {
return <LoadingIndicator />;
}

if (query.isError) {
return (
<LoadingError
message={t('Failed to load X-Ray diff data.')}
onRetry={query.refetch}
/>
);
}

const diffItems = query.data?.diff_items;

if (!diffItems || diffItems.length === 0) {
return <EmptyStateWarning small>{t('No diff found.')}</EmptyStateWarning>;
}

return <TreemapDiffSection diffItems={diffItems} />;
Comment thread
cursor[bot] marked this conversation as resolved.
Resolved
Comment on lines +30 to +49

This comment was marked as outdated.

}

type SectionProps = {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

what's the difference between SectionProps and ContentProps?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Nothing currently, I've made it so one aliases the other

baseMetricId: string;
headMetricId: string;
project: Project;
};

function EventXrayDiffSection({baseMetricId, headMetricId, project}: SectionProps) {
return (
<InterimSection title={t('X-Ray diff')} type={SectionKey.XRAY_DIFF}>
<ErrorBoundary mini>
<EventXrayDiffContent
project={project}
headMetricId={headMetricId}
baseMetricId={baseMetricId}
/>
</ErrorBoundary>
</InterimSection>
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Duplicate section headers when rendering treemap diff

The EventXrayDiffSection wraps its content in InterimSection with title={t('X-Ray diff')}, which renders a section header. However, TreemapDiffSection (rendered inside via EventXrayDiffContent) already includes its own <Heading as="h2">X-Ray Diff</Heading> and a <Separator>. This results in duplicate "X-Ray diff" headings appearing in the UI when the treemap successfully renders.

Additional Locations (1)

Fix in Cursor Fix in Web

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Will solve this later

}

interface MetricIds {
baseMetricId: string;
headMetricId: string;
}

function getMetricIds(event: Event): MetricIds | undefined {
const headMetricId = event.occurrence?.evidenceData?.headSizeMetricId;
const baseMetricId = event.occurrence?.evidenceData?.baseSizeMetricId;

if (baseMetricId && headMetricId) {
return {
baseMetricId,
headMetricId,
};
}
return undefined;
}
Comment on lines +78 to +88

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Bug: The getMetricIds function expects headSizeMetricId and baseSizeMetricId in event.occurrence.evidenceData, but these fields are never populated, causing the X-ray diff UI to never render.
Severity: CRITICAL | Confidence: High

🔍 Detailed Analysis

The getMetricIds function in the new EventXrayDiff component attempts to extract headSizeMetricId and baseSizeMetricId from event.occurrence.evidenceData. However, these camelCase fields are not defined in the type system and are not populated by the backend for size-related issues. As a result, getMetricIds will always return undefined. This causes the EventXrayDiff component to consistently return null, preventing the new X-ray diff UI from ever rendering for users. The feature will fail silently without any error indication.

💡 Suggested Fix

Ensure the backend populates event.occurrence.evidenceData.headSizeMetricId and event.occurrence.evidenceData.baseSizeMetricId. Alternatively, if the data exists elsewhere or with different naming (e.g., snake_case), update the frontend to source the metric IDs from the correct location and handle any naming discrepancies.

🤖 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/components/events/eventXrayDiff.tsx#L77-L88

Potential issue: The `getMetricIds` function in the new `EventXrayDiff` component
attempts to extract `headSizeMetricId` and `baseSizeMetricId` from
`event.occurrence.evidenceData`. However, these camelCase fields are not defined in the
type system and are not populated by the backend for size-related issues. As a result,
`getMetricIds` will always return `undefined`. This causes the `EventXrayDiff` component
to consistently return `null`, preventing the new X-ray diff UI from ever rendering for
users. The feature will fail silently without any error indication.

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


type Props = {
event: Event;
project: Project;
};

function EventXrayDiff(props: Props) {
const ids = getMetricIds(props.event);
if (ids) {
return <EventXrayDiffSection {...props} {...ids} />;
}
return null;
}

export {EventXrayDiff};
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {EventTagsAndScreenshot} from 'sentry/components/events/eventTagsAndScree
import {ScreenshotDataSection} from 'sentry/components/events/eventTagsAndScreenshot/screenshot/screenshotDataSection';
import {EventTagsDataSection} from 'sentry/components/events/eventTagsAndScreenshot/tags';
import {EventViewHierarchy} from 'sentry/components/events/eventViewHierarchy';
import {EventXrayDiff} from 'sentry/components/events/eventXrayDiff';
import {EventFeatureFlagSection} from 'sentry/components/events/featureFlags/eventFeatureFlagSection';
import {EventGroupingInfoSection} from 'sentry/components/events/groupingInfo/groupingInfoSection';
import HighlightsDataSection from 'sentry/components/events/highlights/highlightsDataSection';
Expand Down Expand Up @@ -414,6 +415,7 @@ export function EventDetailsContent({
</ErrorBoundary>
<EventExtraData event={event} />
<EventViewHierarchy event={event} project={project} />
<EventXrayDiff event={event} project={project} />
<EventPackageData event={event} />
<EventDevice event={event} />
<EventAttachments event={event} project={project} group={group} />
Expand Down
2 changes: 2 additions & 0 deletions static/app/views/issueDetails/streamline/context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ export const enum SectionKey {
MCP_OUTPUT = 'mcp-output',

SPAN_LINKS = 'span-links',

XRAY_DIFF = 'xray-diff',
}

/**
Expand Down
Loading