From 1bcb819219f963a2833b15e9df4aab6383e56f1e Mon Sep 17 00:00:00 2001 From: Max Topolsky Date: Mon, 17 Nov 2025 14:26:51 -0500 Subject: [PATCH 1/9] add cards to build details --- .../main/BuildDetailsMetricCards.tsx | 211 ++++++++++++++++++ .../main/buildDetailsMainContent.tsx | 98 ++++---- .../sidebar/buildDetailsSidebarAppInfo.tsx | 111 +-------- .../sidebar/buildDetailsSidebarContent.tsx | 13 -- 4 files changed, 270 insertions(+), 163 deletions(-) create mode 100644 static/app/views/preprod/buildDetails/main/BuildDetailsMetricCards.tsx diff --git a/static/app/views/preprod/buildDetails/main/BuildDetailsMetricCards.tsx b/static/app/views/preprod/buildDetails/main/BuildDetailsMetricCards.tsx new file mode 100644 index 00000000000000..f5b54ba661592e --- /dev/null +++ b/static/app/views/preprod/buildDetails/main/BuildDetailsMetricCards.tsx @@ -0,0 +1,211 @@ +import type {ReactNode} from 'react'; +import styled from '@emotion/styled'; + +import {Flex, Stack} from '@sentry/scraps/layout'; +import {Heading, Text} from '@sentry/scraps/text'; +import {Tooltip} from '@sentry/scraps/tooltip'; + +import {IconCode, IconDownload, IconLightning, IconSettings} from 'sentry/icons'; +import {t} from 'sentry/locale'; +import {formatBytesBase10} from 'sentry/utils/bytes/formatBytesBase10'; +import {formatPercentage} from 'sentry/utils/number/formatPercentage'; +import {MetricCard} from 'sentry/views/preprod/components/metricCard'; +import {MetricsArtifactType} from 'sentry/views/preprod/types/appSizeTypes'; +import { + getMainArtifactSizeMetric, + isSizeInfoCompleted, +} from 'sentry/views/preprod/types/buildDetailsTypes'; +import type {BuildDetailsSizeInfo} from 'sentry/views/preprod/types/buildDetailsTypes'; +import type {Platform} from 'sentry/views/preprod/types/sharedTypes'; +import type {ProcessedInsight} from 'sentry/views/preprod/utils/insightProcessing'; +import { + formattedPrimaryMetricDownloadSize, + formattedPrimaryMetricInstallSize, + getLabels, +} from 'sentry/views/preprod/utils/labelUtils'; + +interface BuildDetailsMetricCardsProps { + onOpenInsightsSidebar: () => void; + processedInsights: ProcessedInsight[]; + sizeInfo: BuildDetailsSizeInfo | undefined; + totalSize: number; + platform?: Platform | null; +} + +interface MetricCardConfig { + icon: ReactNode; + key: string; + title: string; + value: string; + labelTooltip?: string; + percentageText?: string; + showInsightsButton?: boolean; + watchBreakdown?: WatchBreakdown; +} + +interface WatchBreakdown { + appValue: string; + watchValue: string; +} + +export function BuildDetailsMetricCards(props: BuildDetailsMetricCardsProps) { + const {sizeInfo, processedInsights, totalSize, platform, onOpenInsightsSidebar} = props; + + const labels = getLabels(platform ?? undefined); + const primarySizeMetric = + sizeInfo && isSizeInfoCompleted(sizeInfo) + ? getMainArtifactSizeMetric(sizeInfo) + : undefined; + const watchAppMetrics = + sizeInfo && isSizeInfoCompleted(sizeInfo) + ? (sizeInfo.size_metrics.find( + metric => metric.metrics_artifact_type === MetricsArtifactType.WATCH_ARTIFACT + ) ?? null) + : null; + const installMetricValue = formattedPrimaryMetricInstallSize(sizeInfo); + const downloadMetricValue = formattedPrimaryMetricDownloadSize(sizeInfo); + const totalPotentialSavings = processedInsights.reduce( + (sum, insight) => sum + (insight.totalSavings ?? 0), + 0 + ); + const potentialSavingsPercentage = + totalSize > 0 ? totalPotentialSavings / totalSize : null; + const potentialSavingsPercentageText = + potentialSavingsPercentage !== null && potentialSavingsPercentage !== undefined + ? ` (${formatPercentage(potentialSavingsPercentage, 1, { + minimumValue: 0.001, + })})` + : undefined; + const hasInsights = processedInsights.length > 0; + + const metricsCards: MetricCardConfig[] = [ + { + key: 'install', + title: labels.installSizeLabel, + icon: , + labelTooltip: labels.installSizeDescription, + value: installMetricValue, + watchBreakdown: getWatchBreakdown( + primarySizeMetric, + watchAppMetrics, + 'install_size_bytes' + ), + }, + { + key: 'download', + title: labels.downloadSizeLabel, + icon: , + labelTooltip: labels.downloadSizeDescription, + value: downloadMetricValue, + watchBreakdown: getWatchBreakdown( + primarySizeMetric, + watchAppMetrics, + 'download_size_bytes' + ), + }, + { + key: 'savings', + title: t('Potential savings'), + icon: , + value: formatBytesBase10(totalPotentialSavings), + percentageText: potentialSavingsPercentageText, + showInsightsButton: hasInsights, + }, + ]; + + return ( + + {metricsCards.map(card => { + const valueContent = ( + + + {card.value} + + {card.percentageText ?? ''} + + ); + + return ( + , + tooltip: t('View insight details'), + ariaLabel: t('View insight details'), + onClick: onOpenInsightsSidebar, + } + : undefined + } + > + {card.watchBreakdown ? ( + + } + position="left" + > + {valueContent} + + ) : ( + valueContent + )} + + ); + })} + + ); +} + +function WatchBreakdownTooltip(props: {appValue: string; watchValue: string}) { + const {appValue, watchValue} = props; + + return ( + + + + {t('App')}: + + {appValue} + + + + {t('Watch')}: + + {watchValue} + + + ); +} + +function getWatchBreakdown( + primaryMetric: ReturnType, + watchMetric: ReturnType | null, + field: 'install_size_bytes' | 'download_size_bytes' +): WatchBreakdown | undefined { + if (!primaryMetric || !watchMetric) { + return undefined; + } + + return { + appValue: formatBytesBase10(primaryMetric[field]), + watchValue: formatBytesBase10(watchMetric[field]), + }; +} + +const MetricValue = styled('span')<{$interactive?: boolean}>` + ${p => + p.$interactive + ? ` + text-decoration: underline dotted; + cursor: help; + ` + : ''} +`; diff --git a/static/app/views/preprod/buildDetails/main/buildDetailsMainContent.tsx b/static/app/views/preprod/buildDetails/main/buildDetailsMainContent.tsx index e8c8146fbcb171..771dd188cde29e 100644 --- a/static/app/views/preprod/buildDetails/main/buildDetailsMainContent.tsx +++ b/static/app/views/preprod/buildDetails/main/buildDetailsMainContent.tsx @@ -1,3 +1,5 @@ +import {useCallback} from 'react'; +import {useSearchParams} from 'react-router-dom'; import styled from '@emotion/styled'; import {Alert} from '@sentry/scraps/alert'; @@ -13,6 +15,7 @@ import {t} from 'sentry/locale'; import type {UseApiQueryResult} from 'sentry/utils/queryClient'; import type RequestError from 'sentry/utils/requestError/requestError'; import {useQueryParamState} from 'sentry/utils/url/useQueryParamState'; +import {BuildDetailsMetricCards} from 'sentry/views/preprod/buildDetails/main/BuildDetailsMetricCards'; 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'; @@ -52,6 +55,12 @@ export function BuildDetailsMainContent(props: BuildDetailsMainContentProps) { isError: isAppSizeError, error: appSizeError, } = appSizeQuery; + const [searchParams, setSearchParams] = useSearchParams(); + const openInsightsSidebar = useCallback(() => { + const next = new URLSearchParams(searchParams); + next.set('insights', 'open'); + setSearchParams(next); + }, [searchParams, setSearchParams]); // If the main data fetch fails, this component will not be rendered // so we don't handle 'isBuildDetailsError'. @@ -204,7 +213,6 @@ export function BuildDetailsMainContent(props: BuildDetailsMainContentProps) { appSizeData.insights && totalSize > 0 ? processInsights(appSizeData.insights, totalSize) : []; - const categoriesEnabled = appSizeData.treemap.category_breakdown && Object.keys(appSizeData.treemap.category_breakdown).length > 0; @@ -283,42 +291,49 @@ export function BuildDetailsMainContent(props: BuildDetailsMainContentProps) { } return ( - - - {categoriesEnabled && ( - - } /> - } /> - - )} - {selectedContent === 'treemap' && ( - - - - - setSearchQuery(e.target.value || undefined)} - /> - {searchQuery && ( - - - - )} - - )} - - {visualizationContent} + + - + + + {categoriesEnabled && ( + + } /> + } /> + + )} + {selectedContent === 'treemap' && ( + + + + + setSearchQuery(e.target.value || undefined)} + /> + {searchQuery && ( + + + + )} + + )} + + {visualizationContent} {selectedContent === 'treemap' && appSizeData && ( )} - - + + + ); } diff --git a/static/app/views/preprod/buildDetails/sidebar/buildDetailsSidebarAppInfo.tsx b/static/app/views/preprod/buildDetails/sidebar/buildDetailsSidebarAppInfo.tsx index aa3ff6e65e9c8d..1a89fbf5bc551d 100644 --- a/static/app/views/preprod/buildDetails/sidebar/buildDetailsSidebarAppInfo.tsx +++ b/static/app/views/preprod/buildDetails/sidebar/buildDetailsSidebarAppInfo.tsx @@ -2,26 +2,17 @@ import styled from '@emotion/styled'; import {PlatformIcon} from 'platformicons'; import {CodeBlock} from '@sentry/scraps/code'; -import {Flex, Stack} from '@sentry/scraps/layout'; +import {Flex} from '@sentry/scraps/layout'; import {Heading, Text} from '@sentry/scraps/text'; import {Tooltip} from '@sentry/scraps/tooltip'; import Feature from 'sentry/components/acl/feature'; import {IconClock, IconFile, IconJson, IconLink, IconMobile} from 'sentry/icons'; import {t} from 'sentry/locale'; -import {formatBytesBase10} from 'sentry/utils/bytes/formatBytesBase10'; import {getFormat, getFormattedDate, getUtcToSystem} from 'sentry/utils/dates'; import {openInstallModal} from 'sentry/views/preprod/components/installModal'; -import {MetricsArtifactType} from 'sentry/views/preprod/types/appSizeTypes'; +import {type BuildDetailsAppInfo} from 'sentry/views/preprod/types/buildDetailsTypes'; import { - BuildDetailsSizeAnalysisState, - getMainArtifactSizeMetric, - type BuildDetailsAppInfo, - type BuildDetailsSizeInfo, -} from 'sentry/views/preprod/types/buildDetailsTypes'; -import { - formattedPrimaryMetricDownloadSize, - formattedPrimaryMetricInstallSize, getLabels, getPlatformIconFromPlatform, getReadableArtifactTypeLabel, @@ -33,7 +24,6 @@ interface BuildDetailsSidebarAppInfoProps { appInfo: BuildDetailsAppInfo; artifactId: string; projectId: string | null; - sizeInfo?: BuildDetailsSizeInfo; } export function BuildDetailsSidebarAppInfo(props: BuildDetailsSidebarAppInfoProps) { @@ -44,101 +34,6 @@ export function BuildDetailsSidebarAppInfo(props: BuildDetailsSidebarAppInfoProp timeZone: true, }); - let sizeInfoGroup = null; - if ( - props.sizeInfo && - props.sizeInfo.state === BuildDetailsSizeAnalysisState.COMPLETED - ) { - const primarySizeMetric = getMainArtifactSizeMetric(props.sizeInfo); - const watchAppMetrics = props.sizeInfo.size_metrics.find( - metric => metric.metrics_artifact_type === MetricsArtifactType.WATCH_ARTIFACT - ); - - let installSizeContent = ( - {formattedPrimaryMetricInstallSize(props.sizeInfo)} - ); - let downloadSizeContent = ( - {formattedPrimaryMetricDownloadSize(props.sizeInfo)} - ); - if (watchAppMetrics) { - installSizeContent = ( - - - - {t('App')}: - - - {formatBytesBase10(primarySizeMetric?.install_size_bytes ?? 0)} - - - - - {t('Watch')}: - - - {formatBytesBase10(watchAppMetrics.install_size_bytes)} - - - - } - position="left" - > - - {formattedPrimaryMetricInstallSize(props.sizeInfo)} - - - ); - downloadSizeContent = ( - - - - {t('App')}: - - - {formatBytesBase10(watchAppMetrics.download_size_bytes)} - - - - - {t('Watch')}: - - - {formatBytesBase10(watchAppMetrics.download_size_bytes)} - - - - } - position="left" - > - - {formattedPrimaryMetricDownloadSize(props.sizeInfo)} - - - ); - } - - sizeInfoGroup = ( - - - - {labels.installSizeLabel} - - {installSizeContent} - - - - {labels.downloadSizeLabel} - - {downloadSizeContent} - - - ); - } - return ( @@ -148,8 +43,6 @@ export function BuildDetailsSidebarAppInfo(props: BuildDetailsSidebarAppInfoProp {props.appInfo.name && {props.appInfo.name}} - {sizeInfoGroup} - diff --git a/static/app/views/preprod/buildDetails/sidebar/buildDetailsSidebarContent.tsx b/static/app/views/preprod/buildDetails/sidebar/buildDetailsSidebarContent.tsx index bc6fd9a452362c..567b85efb3f91e 100644 --- a/static/app/views/preprod/buildDetails/sidebar/buildDetailsSidebarContent.tsx +++ b/static/app/views/preprod/buildDetails/sidebar/buildDetailsSidebarContent.tsx @@ -126,7 +126,6 @@ export function BuildDetailsSidebarContent(props: BuildDetailsSidebarContentProp {buildDetailsData.state === BuildDetailsState.PROCESSED && ( @@ -149,18 +148,6 @@ function SidebarLoadingSkeleton(props: {['data-testid']: string}) { - {/* Size info section */} - - - - - - - - - - - {/* Additional info */} From 2fff5ac8d051b6489e694b2a1ba340af6d64f755 Mon Sep 17 00:00:00 2001 From: Max Topolsky Date: Mon, 17 Nov 2025 15:46:41 -0500 Subject: [PATCH 2/9] ryan comments --- .../main/BuildDetailsMetricCards.tsx | 44 +++++++++++-------- .../views/preprod/types/buildDetailsTypes.ts | 2 +- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/static/app/views/preprod/buildDetails/main/BuildDetailsMetricCards.tsx b/static/app/views/preprod/buildDetails/main/BuildDetailsMetricCards.tsx index f5b54ba661592e..43fcabec11a80e 100644 --- a/static/app/views/preprod/buildDetails/main/BuildDetailsMetricCards.tsx +++ b/static/app/views/preprod/buildDetails/main/BuildDetailsMetricCards.tsx @@ -15,7 +15,10 @@ import { getMainArtifactSizeMetric, isSizeInfoCompleted, } from 'sentry/views/preprod/types/buildDetailsTypes'; -import type {BuildDetailsSizeInfo} from 'sentry/views/preprod/types/buildDetailsTypes'; +import type { + BuildDetailsSizeInfo, + BuildDetailsSizeInfoSizeMetric, +} from 'sentry/views/preprod/types/buildDetailsTypes'; import type {Platform} from 'sentry/views/preprod/types/sharedTypes'; import type {ProcessedInsight} from 'sentry/views/preprod/utils/insightProcessing'; import { @@ -49,19 +52,23 @@ interface WatchBreakdown { } export function BuildDetailsMetricCards(props: BuildDetailsMetricCardsProps) { - const {sizeInfo, processedInsights, totalSize, platform, onOpenInsightsSidebar} = props; + const { + sizeInfo, + processedInsights, + totalSize, + platform: platformProp, + onOpenInsightsSidebar, + } = props; - const labels = getLabels(platform ?? undefined); - const primarySizeMetric = - sizeInfo && isSizeInfoCompleted(sizeInfo) - ? getMainArtifactSizeMetric(sizeInfo) - : undefined; - const watchAppMetrics = - sizeInfo && isSizeInfoCompleted(sizeInfo) - ? (sizeInfo.size_metrics.find( - metric => metric.metrics_artifact_type === MetricsArtifactType.WATCH_ARTIFACT - ) ?? null) - : null; + if (!isSizeInfoCompleted(sizeInfo)) { + return null; + } + + const labels = getLabels(platformProp ?? undefined); + const primarySizeMetric = getMainArtifactSizeMetric(sizeInfo); + const watchArtifactMetric = sizeInfo.size_metrics.find( + metric => metric.metrics_artifact_type === MetricsArtifactType.WATCH_ARTIFACT + ); const installMetricValue = formattedPrimaryMetricInstallSize(sizeInfo); const downloadMetricValue = formattedPrimaryMetricDownloadSize(sizeInfo); const totalPotentialSavings = processedInsights.reduce( @@ -76,7 +83,6 @@ export function BuildDetailsMetricCards(props: BuildDetailsMetricCardsProps) { minimumValue: 0.001, })})` : undefined; - const hasInsights = processedInsights.length > 0; const metricsCards: MetricCardConfig[] = [ { @@ -87,7 +93,7 @@ export function BuildDetailsMetricCards(props: BuildDetailsMetricCardsProps) { value: installMetricValue, watchBreakdown: getWatchBreakdown( primarySizeMetric, - watchAppMetrics, + watchArtifactMetric, 'install_size_bytes' ), }, @@ -99,7 +105,7 @@ export function BuildDetailsMetricCards(props: BuildDetailsMetricCardsProps) { value: downloadMetricValue, watchBreakdown: getWatchBreakdown( primarySizeMetric, - watchAppMetrics, + watchArtifactMetric, 'download_size_bytes' ), }, @@ -109,7 +115,7 @@ export function BuildDetailsMetricCards(props: BuildDetailsMetricCardsProps) { icon: , value: formatBytesBase10(totalPotentialSavings), percentageText: potentialSavingsPercentageText, - showInsightsButton: hasInsights, + showInsightsButton: totalPotentialSavings > 0, }, ]; @@ -186,8 +192,8 @@ function WatchBreakdownTooltip(props: {appValue: string; watchValue: string}) { } function getWatchBreakdown( - primaryMetric: ReturnType, - watchMetric: ReturnType | null, + primaryMetric: BuildDetailsSizeInfoSizeMetric | undefined, + watchMetric: BuildDetailsSizeInfoSizeMetric | undefined, field: 'install_size_bytes' | 'download_size_bytes' ): WatchBreakdown | undefined { if (!primaryMetric || !watchMetric) { diff --git a/static/app/views/preprod/types/buildDetailsTypes.ts b/static/app/views/preprod/types/buildDetailsTypes.ts index 9ded2c20700f2e..d2fe800b91e7b7 100644 --- a/static/app/views/preprod/types/buildDetailsTypes.ts +++ b/static/app/views/preprod/types/buildDetailsTypes.ts @@ -45,7 +45,7 @@ export interface BuildDetailsVcsInfo { provider?: string | null; } -interface BuildDetailsSizeInfoSizeMetric { +export interface BuildDetailsSizeInfoSizeMetric { metrics_artifact_type: MetricsArtifactType; install_size_bytes: number; download_size_bytes: number; From ca73803846220e275c2df26288fdc3b5d7cb3f9a Mon Sep 17 00:00:00 2001 From: Max Topolsky Date: Mon, 17 Nov 2025 15:49:35 -0500 Subject: [PATCH 3/9] file rename (convention) --- .../views/preprod/buildDetails/main/buildDetailsMainContent.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/app/views/preprod/buildDetails/main/buildDetailsMainContent.tsx b/static/app/views/preprod/buildDetails/main/buildDetailsMainContent.tsx index 771dd188cde29e..800f08b56225b3 100644 --- a/static/app/views/preprod/buildDetails/main/buildDetailsMainContent.tsx +++ b/static/app/views/preprod/buildDetails/main/buildDetailsMainContent.tsx @@ -15,7 +15,7 @@ import {t} from 'sentry/locale'; import type {UseApiQueryResult} from 'sentry/utils/queryClient'; import type RequestError from 'sentry/utils/requestError/requestError'; import {useQueryParamState} from 'sentry/utils/url/useQueryParamState'; -import {BuildDetailsMetricCards} from 'sentry/views/preprod/buildDetails/main/BuildDetailsMetricCards'; +import {BuildDetailsMetricCards} from 'sentry/views/preprod/buildDetails/main/buildDetailsMetricCards'; 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'; From 181b61a32369904e5f7d4ee7dd9d0a33f797e459 Mon Sep 17 00:00:00 2001 From: Max Topolsky Date: Mon, 17 Nov 2025 16:18:06 -0500 Subject: [PATCH 4/9] temp rename --- .../main/{BuildDetailsMetricCards.tsx => temp-name.tsx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename static/app/views/preprod/buildDetails/main/{BuildDetailsMetricCards.tsx => temp-name.tsx} (100%) diff --git a/static/app/views/preprod/buildDetails/main/BuildDetailsMetricCards.tsx b/static/app/views/preprod/buildDetails/main/temp-name.tsx similarity index 100% rename from static/app/views/preprod/buildDetails/main/BuildDetailsMetricCards.tsx rename to static/app/views/preprod/buildDetails/main/temp-name.tsx From 0b9f9676f49f08c58cf704f05cccd32ded18ea5e Mon Sep 17 00:00:00 2001 From: Max Topolsky Date: Mon, 17 Nov 2025 16:18:29 -0500 Subject: [PATCH 5/9] proper naming --- .../main/{temp-name.tsx => buildDetailsMetricCards.tsx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename static/app/views/preprod/buildDetails/main/{temp-name.tsx => buildDetailsMetricCards.tsx} (100%) diff --git a/static/app/views/preprod/buildDetails/main/temp-name.tsx b/static/app/views/preprod/buildDetails/main/buildDetailsMetricCards.tsx similarity index 100% rename from static/app/views/preprod/buildDetails/main/temp-name.tsx rename to static/app/views/preprod/buildDetails/main/buildDetailsMetricCards.tsx From 9a7ccfb582a10551b89ce1aa6c24885d4d2ff879 Mon Sep 17 00:00:00 2001 From: Max Topolsky Date: Mon, 17 Nov 2025 16:44:32 -0500 Subject: [PATCH 6/9] require tooltip --- .../main/BuildComparisonMetricCards.tsx | 10 +++++++++- .../main/buildDetailsMainContent.tsx | 20 +++++++++++++------ .../main/buildDetailsMetricCards.tsx | 1 + .../views/preprod/components/metricCard.tsx | 2 +- 4 files changed, 25 insertions(+), 8 deletions(-) diff --git a/static/app/views/preprod/buildComparison/main/BuildComparisonMetricCards.tsx b/static/app/views/preprod/buildComparison/main/BuildComparisonMetricCards.tsx index f473a49bf43987..b40f3d589b9fb8 100644 --- a/static/app/views/preprod/buildComparison/main/BuildComparisonMetricCards.tsx +++ b/static/app/views/preprod/buildComparison/main/BuildComparisonMetricCards.tsx @@ -25,6 +25,7 @@ interface ComparisonMetric { head: number; icon: ReactNode; key: string; + labelTooltip: string; percentageChange: number; title: string; } @@ -47,6 +48,7 @@ export function BuildComparisonMetricCards(props: BuildComparisonMetricCardsProp key: 'install', title: labels.installSizeLabel, icon: , + labelTooltip: labels.installSizeDescription, head: size_metric_diff_item.head_install_size, base: size_metric_diff_item.base_install_size, diff: @@ -63,6 +65,7 @@ export function BuildComparisonMetricCards(props: BuildComparisonMetricCardsProp key: 'download', title: labels.downloadSizeLabel, icon: , + labelTooltip: labels.downloadSizeDescription, head: size_metric_diff_item.head_download_size, base: size_metric_diff_item.base_download_size, diff: @@ -88,7 +91,12 @@ export function BuildComparisonMetricCards(props: BuildComparisonMetricCardsProp const {variant, icon} = getTrend(metric.diff); return ( - + {formatBytesBase10(metric.head)} diff --git a/static/app/views/preprod/buildDetails/main/buildDetailsMainContent.tsx b/static/app/views/preprod/buildDetails/main/buildDetailsMainContent.tsx index 800f08b56225b3..cc710b727689e4 100644 --- a/static/app/views/preprod/buildDetails/main/buildDetailsMainContent.tsx +++ b/static/app/views/preprod/buildDetails/main/buildDetailsMainContent.tsx @@ -116,13 +116,21 @@ export function BuildDetailsMainContent(props: BuildDetailsMainContentProps) { if (isLoadingRequests) { return ( - - - - + + + + + - - + + + + + + + + + ); } diff --git a/static/app/views/preprod/buildDetails/main/buildDetailsMetricCards.tsx b/static/app/views/preprod/buildDetails/main/buildDetailsMetricCards.tsx index 43fcabec11a80e..13140929212351 100644 --- a/static/app/views/preprod/buildDetails/main/buildDetailsMetricCards.tsx +++ b/static/app/views/preprod/buildDetails/main/buildDetailsMetricCards.tsx @@ -113,6 +113,7 @@ export function BuildDetailsMetricCards(props: BuildDetailsMetricCardsProps) { key: 'savings', title: t('Potential savings'), icon: , + labelTooltip: t('Total savings from insights'), value: formatBytesBase10(totalPotentialSavings), percentageText: potentialSavingsPercentageText, showInsightsButton: totalPotentialSavings > 0, diff --git a/static/app/views/preprod/components/metricCard.tsx b/static/app/views/preprod/components/metricCard.tsx index 535ac9f24b0b27..15e7d8cad7037d 100644 --- a/static/app/views/preprod/components/metricCard.tsx +++ b/static/app/views/preprod/components/metricCard.tsx @@ -16,8 +16,8 @@ interface MetricCardProps { children: ReactNode; icon: ReactNode; label: string; + labelTooltip: ReactNode; action?: MetricCardAction; - labelTooltip?: ReactNode; style?: CSSProperties; } From b628247c02c99d78fd5718d7b45f41c5aa44e93d Mon Sep 17 00:00:00 2001 From: Max Topolsky Date: Mon, 17 Nov 2025 16:45:34 -0500 Subject: [PATCH 7/9] temp rename --- .../main/{BuildComparisonMetricCards.tsx => temp.tsx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename static/app/views/preprod/buildComparison/main/{BuildComparisonMetricCards.tsx => temp.tsx} (100%) diff --git a/static/app/views/preprod/buildComparison/main/BuildComparisonMetricCards.tsx b/static/app/views/preprod/buildComparison/main/temp.tsx similarity index 100% rename from static/app/views/preprod/buildComparison/main/BuildComparisonMetricCards.tsx rename to static/app/views/preprod/buildComparison/main/temp.tsx From 53c33bd029dbe88df6fe7da797d7ca7d2aef88e9 Mon Sep 17 00:00:00 2001 From: Max Topolsky Date: Mon, 17 Nov 2025 16:45:56 -0500 Subject: [PATCH 8/9] force rename --- .../main/{temp.tsx => buildComparisonMetricCards.tsx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename static/app/views/preprod/buildComparison/main/{temp.tsx => buildComparisonMetricCards.tsx} (100%) diff --git a/static/app/views/preprod/buildComparison/main/temp.tsx b/static/app/views/preprod/buildComparison/main/buildComparisonMetricCards.tsx similarity index 100% rename from static/app/views/preprod/buildComparison/main/temp.tsx rename to static/app/views/preprod/buildComparison/main/buildComparisonMetricCards.tsx From 20a55eb75f16762c5b6f02c8d89ec2a561c3927b Mon Sep 17 00:00:00 2001 From: Max Topolsky Date: Mon, 17 Nov 2025 16:49:06 -0500 Subject: [PATCH 9/9] missed casing --- .../preprod/buildComparison/main/sizeCompareMainContent.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/app/views/preprod/buildComparison/main/sizeCompareMainContent.tsx b/static/app/views/preprod/buildComparison/main/sizeCompareMainContent.tsx index 0382e6ca58edbb..9f16aa5c45dc57 100644 --- a/static/app/views/preprod/buildComparison/main/sizeCompareMainContent.tsx +++ b/static/app/views/preprod/buildComparison/main/sizeCompareMainContent.tsx @@ -17,7 +17,7 @@ import type RequestError from 'sentry/utils/requestError/requestError'; import {useNavigate} from 'sentry/utils/useNavigate'; import useOrganization from 'sentry/utils/useOrganization'; import {useParams} from 'sentry/utils/useParams'; -import {BuildComparisonMetricCards} from 'sentry/views/preprod/buildComparison/main/BuildComparisonMetricCards'; +import {BuildComparisonMetricCards} from 'sentry/views/preprod/buildComparison/main/buildComparisonMetricCards'; import {SizeCompareItemDiffTable} from 'sentry/views/preprod/buildComparison/main/sizeCompareItemDiffTable'; import {SizeCompareSelectedBuilds} from 'sentry/views/preprod/buildComparison/main/sizeCompareSelectedBuilds'; import {BuildError} from 'sentry/views/preprod/components/buildError';