diff --git a/static/app/views/preprod/buildDetails/main/buildDetailsMainContent.tsx b/static/app/views/preprod/buildDetails/main/buildDetailsMainContent.tsx
index 15e7dfcec47b4e..6db3d71b962e48 100644
--- a/static/app/views/preprod/buildDetails/main/buildDetailsMainContent.tsx
+++ b/static/app/views/preprod/buildDetails/main/buildDetailsMainContent.tsx
@@ -19,6 +19,7 @@ import {BuildDetailsMetricCards} from 'sentry/views/preprod/buildDetails/main/bu
import {AppSizeInsights} from 'sentry/views/preprod/buildDetails/main/insights/appSizeInsights';
import {BuildError} from 'sentry/views/preprod/components/buildError';
import {BuildProcessing} from 'sentry/views/preprod/components/buildProcessing';
+import {openMissingDsymModal} from 'sentry/views/preprod/components/missingDsymModal';
import {AppSizeCategories} from 'sentry/views/preprod/components/visualizations/appSizeCategories';
import {AppSizeLegend} from 'sentry/views/preprod/components/visualizations/appSizeLegend';
import {AppSizeTreemap} from 'sentry/views/preprod/components/visualizations/appSizeTreemap';
@@ -234,13 +235,14 @@ export function BuildDetailsMainContent(props: BuildDetailsMainContentProps) {
if (missingDsymBinaries && missingDsymBinaries.length > 0) {
if (missingDsymBinaries?.length === 1) {
return t(
- 'Missing debug symbols for some binaries (%s). Those binaries will not have a detailed breakdown.',
+ 'Missing debug symbols for %s. This binary will not have a detailed breakdown.',
missingDsymBinaries[0]
);
}
return t(
- 'Missing debug symbols for some binaries (%s and others). Those binaries will not have a detailed breakdown.',
- missingDsymBinaries[0]
+ 'Missing debug symbols for some binaries (%s and %s others). Those binaries will not have a detailed breakdown. Click to view details.',
+ missingDsymBinaries[0],
+ missingDsymBinaries.length - 1
);
}
@@ -251,6 +253,12 @@ export function BuildDetailsMainContent(props: BuildDetailsMainContentProps) {
return undefined;
};
+ const handleAlertClick = () => {
+ if (missingDsymBinaries && missingDsymBinaries.length > 0) {
+ openMissingDsymModal(missingDsymBinaries);
+ }
+ };
+
// Filter data based on search query and categories
const filteredRoot = filterTreemapElement(
appSizeData.treemap.root,
@@ -275,6 +283,11 @@ export function BuildDetailsMainContent(props: BuildDetailsMainContentProps) {
searchQuery={searchQuery || ''}
unfilteredRoot={appSizeData.treemap.root}
alertMessage={getAlertMessage()}
+ onAlertClick={
+ missingDsymBinaries && missingDsymBinaries.length > 1
+ ? handleAlertClick
+ : undefined
+ }
onSearchChange={value => setSearchQuery(value || undefined)}
/>
) : (
@@ -290,6 +303,11 @@ export function BuildDetailsMainContent(props: BuildDetailsMainContentProps) {
searchQuery={searchQuery || ''}
unfilteredRoot={appSizeData.treemap.root}
alertMessage={getAlertMessage()}
+ onAlertClick={
+ missingDsymBinaries && missingDsymBinaries.length > 1
+ ? handleAlertClick
+ : undefined
+ }
onSearchChange={value => setSearchQuery(value || undefined)}
/>
) : (
diff --git a/static/app/views/preprod/components/missingDsymModal.tsx b/static/app/views/preprod/components/missingDsymModal.tsx
new file mode 100644
index 00000000000000..9e965ea25cd8c5
--- /dev/null
+++ b/static/app/views/preprod/components/missingDsymModal.tsx
@@ -0,0 +1,85 @@
+import {css} from '@emotion/react';
+import styled from '@emotion/styled';
+
+import {Button} from '@sentry/scraps/button';
+import {Container, Flex, Stack} from '@sentry/scraps/layout';
+import {Heading, Text} from '@sentry/scraps/text';
+
+import {openModal} from 'sentry/actionCreators/modal';
+import {IconClose} from 'sentry/icons/iconClose';
+import {t, tn} from 'sentry/locale';
+import {space} from 'sentry/styles/space';
+
+interface MissingDsymModalProps {
+ binaries: string[];
+ closeModal: () => void;
+}
+
+function MissingDsymModal({binaries, closeModal}: MissingDsymModalProps) {
+ return (
+
+
+
+ {tn('Missing Debug Symbol', 'Missing Debug Symbols', binaries.length)}
+
+
+ }
+ size="sm"
+ aria-label={t('Close')}
+ />
+
+
+
+
+ {t(
+ 'The following binaries are missing debug symbols. Those binaries will not have a detailed breakdown in the size analysis.'
+ )}
+
+
+
+ {binaries.map(binary => (
+
+ {binary}
+
+ ))}
+
+
+ );
+}
+
+const BinaryList = styled(Stack)`
+ max-height: 400px;
+ overflow-y: auto;
+ padding: ${space(2)};
+ background: ${p => p.theme.backgroundSecondary};
+ border-radius: ${p => p.theme.borderRadius};
+ border: 1px solid ${p => p.theme.border};
+`;
+
+const BinaryItem = styled('div')`
+ padding: ${space(1)} ${space(1.5)};
+ background: ${p => p.theme.background};
+ border: 1px solid ${p => p.theme.border};
+
+ code {
+ font-family: ${p => p.theme.text.familyMono};
+ word-break: break-all;
+ }
+`;
+
+export function openMissingDsymModal(binaries: string[]) {
+ openModal(
+ ({closeModal}) => ,
+ {
+ modalCss: css`
+ max-width: 600px;
+ `,
+ }
+ );
+}
diff --git a/static/app/views/preprod/components/visualizations/appSizeTreemap.tsx b/static/app/views/preprod/components/visualizations/appSizeTreemap.tsx
index 88110c2d165e0d..ae87bb3cbeb35b 100644
--- a/static/app/views/preprod/components/visualizations/appSizeTreemap.tsx
+++ b/static/app/views/preprod/components/visualizations/appSizeTreemap.tsx
@@ -24,6 +24,7 @@ interface AppSizeTreemapProps {
root: TreemapElement | null;
searchQuery: string;
alertMessage?: string;
+ onAlertClick?: () => void;
onSearchChange?: (query: string) => void;
unfilteredRoot?: TreemapElement;
}
@@ -32,11 +33,13 @@ function FullscreenModalContent({
unfilteredRoot,
initialSearch,
alertMessage,
+ onAlertClick,
onSearchChange,
}: {
initialSearch: string;
unfilteredRoot: TreemapElement;
alertMessage?: string;
+ onAlertClick?: () => void;
onSearchChange?: (query: string) => void;
}) {
const [localSearch, setLocalSearch] = useState(initialSearch);
@@ -76,6 +79,7 @@ function FullscreenModalContent({
root={filteredRoot}
searchQuery={localSearch}
alertMessage={alertMessage}
+ onAlertClick={onAlertClick}
/>
@@ -84,7 +88,8 @@ function FullscreenModalContent({
export function AppSizeTreemap(props: AppSizeTreemapProps) {
const theme = useTheme();
- const {root, searchQuery, unfilteredRoot, alertMessage, onSearchChange} = props;
+ const {root, searchQuery, unfilteredRoot, alertMessage, onAlertClick, onSearchChange} =
+ props;
const appSizeCategoryInfo = getAppSizeCategoryInfo(theme);
const renderingContext = useContext(ChartRenderingContext);
const isFullscreen = renderingContext?.isFullscreen ?? false;
@@ -323,7 +328,15 @@ export function AppSizeTreemap(props: AppSizeTreemapProps) {
return (
- {alertMessage && {alertMessage}}
+ {alertMessage && (
+
+ {alertMessage}
+
+ )}
) : (
@@ -380,6 +394,7 @@ export function AppSizeTreemap(props: AppSizeTreemapProps) {
root={root}
searchQuery={searchQuery}
alertMessage={alertMessage}
+ onAlertClick={onAlertClick}
/>
),
@@ -393,6 +408,12 @@ export function AppSizeTreemap(props: AppSizeTreemapProps) {
);
}
+const ClickableAlert = styled(Alert)`
+ &:hover {
+ opacity: ${p => (p.onClick ? 0.9 : 1)};
+ }
+`;
+
const ButtonContainer = styled(Flex)`
top: 0px;
right: 0;