Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import {useMemo, type ReactNode} from 'react';

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

import {PercentChange} from 'sentry/components/percentChange';
import {IconArrow, IconCode, IconDownload} from 'sentry/icons';
import {t} from 'sentry/locale';
import {formatBytesBase10} from 'sentry/utils/bytes/formatBytesBase10';
import {MetricCard} from 'sentry/views/preprod/components/metricCard';
import type {
SizeAnalysisComparisonResults,
SizeComparisonApiResponse,
} from 'sentry/views/preprod/types/appSizeTypes';
import {getLabels} from 'sentry/views/preprod/utils/labelUtils';

interface BuildComparisonMetricCardsProps {
comparisonResponse: SizeComparisonApiResponse | undefined;
comparisonResults: SizeAnalysisComparisonResults | undefined;
}

interface ComparisonMetric {
base: number;
diff: number;
head: number;
icon: ReactNode;
key: string;
percentageChange: number;
title: string;
}

export function BuildComparisonMetricCards(props: BuildComparisonMetricCardsProps) {
const {comparisonResults, comparisonResponse} = props;

const metrics = useMemo<ComparisonMetric[]>(() => {
if (!comparisonResults) {
return [];
}

const labels = getLabels(
comparisonResponse?.head_build_details.app_info?.platform ?? undefined
);
const {size_metric_diff_item} = comparisonResults;

return [
{
key: 'install',
title: labels.installSizeLabel,
icon: <IconCode size="sm" />,
head: size_metric_diff_item.head_install_size,
base: size_metric_diff_item.base_install_size,
diff:
size_metric_diff_item.head_install_size -
size_metric_diff_item.base_install_size,
percentageChange:
size_metric_diff_item.base_install_size === 0
? 0
: (size_metric_diff_item.head_install_size -
size_metric_diff_item.base_install_size) /
size_metric_diff_item.base_install_size,
},
{
key: 'download',
title: labels.downloadSizeLabel,
icon: <IconDownload size="sm" />,
head: size_metric_diff_item.head_download_size,
base: size_metric_diff_item.base_download_size,
diff:
size_metric_diff_item.head_download_size -
size_metric_diff_item.base_download_size,
percentageChange:
size_metric_diff_item.base_download_size === 0
? 0
: (size_metric_diff_item.head_download_size -
size_metric_diff_item.base_download_size) /
size_metric_diff_item.base_download_size,
},
];
}, [comparisonResults, comparisonResponse]);

if (!comparisonResults) {
return null;
}

return (
<Flex gap="lg" wrap="wrap">
{metrics.map(metric => {
const {variant, icon} = getTrend(metric.diff);

return (
<MetricCard key={metric.key} icon={metric.icon} label={metric.title}>
<Stack gap="xs">
<Flex align="end" gap="sm" wrap="wrap">
<Heading as="h3">{formatBytesBase10(metric.head)}</Heading>
<Flex align="center" gap="xs">
{icon}
<Text
as="span"
variant={variant}
size="sm"
style={{
display: 'inline-flex',
alignItems: 'center',
flexWrap: 'wrap',
gap: '0.25em',
}}
>
{metric.diff > 0 ? '+' : metric.diff < 0 ? '-' : ''}
{formatBytesBase10(Math.abs(metric.diff))}
{metric.percentageChange !== 0 && (
<Text
as="span"
variant={variant}
style={{
display: 'inline-flex',
alignItems: 'center',
whiteSpace: 'nowrap',
}}
>
{' ('}
<PercentChange
value={metric.percentageChange}
minimumValue={0.001}
preferredPolarity="-"
colorize
/>
{')'}
</Text>
)}
</Text>
</Flex>
</Flex>
<Flex gap="xs" wrap="wrap">
<Text variant="muted" size="sm">
{t('Base Build Size:')}
</Text>
<Text variant="muted" size="sm" bold>
{metric.base === 0 ? t('Not present') : formatBytesBase10(metric.base)}
</Text>
</Flex>
</Stack>
</MetricCard>
);
})}
</Flex>
);
}

function getTrend(diff: number): {
variant: 'danger' | 'success' | 'muted';
icon?: React.ReactNode;
} {
if (diff > 0) {
return {
variant: 'danger',
icon: <IconArrow direction="up" size="xs" />,
};
}

if (diff < 0) {
return {
variant: 'success',
icon: <IconArrow direction="down" size="xs" />,
};
}

return {variant: 'muted'};
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {useMemo, useState} from 'react';
import {useTheme} from '@emotion/react';
import styled from '@emotion/styled';

import {Button} from '@sentry/scraps/button';
import {InputGroup} from '@sentry/scraps/input/inputGroup';
Expand All @@ -10,23 +9,15 @@ import {Heading, Text} from '@sentry/scraps/text';

import {addErrorMessage} from 'sentry/actionCreators/indicator';
import LoadingIndicator from 'sentry/components/loadingIndicator';
import {PercentChange} from 'sentry/components/percentChange';
import {
IconArrow,
IconChevron,
IconCode,
IconDownload,
IconRefresh,
IconSearch,
} from 'sentry/icons';
import {IconChevron, IconRefresh, IconSearch} from 'sentry/icons';
import {t} from 'sentry/locale';
import {formatBytesBase10} from 'sentry/utils/bytes/formatBytesBase10';
import {fetchMutation, useApiQuery, useMutation} from 'sentry/utils/queryClient';
import type {UseApiQueryResult} from 'sentry/utils/queryClient';
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 {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';
Expand All @@ -41,7 +32,6 @@ import type {
SizeAnalysisComparisonResults,
SizeComparisonApiResponse,
} from 'sentry/views/preprod/types/appSizeTypes';
import {getLabels} from 'sentry/views/preprod/utils/labelUtils';

function getMainComparison(
response: SizeComparisonApiResponse | undefined
Expand Down Expand Up @@ -121,54 +111,6 @@ export function SizeCompareMainContent() {
},
});

// Process the comparison data for metrics cards
const processedMetrics = useMemo(() => {
if (!comparisonDataQuery.data) {
return [];
}

const {size_metric_diff_item} = comparisonDataQuery.data;

// Calculate summary data
const installSizeDiff =
size_metric_diff_item.head_install_size - size_metric_diff_item.base_install_size;
const downloadSizeDiff =
size_metric_diff_item.head_download_size - size_metric_diff_item.base_download_size;
const installSizePercentage =
installSizeDiff / size_metric_diff_item.base_install_size;
const downloadSizePercentage =
downloadSizeDiff / size_metric_diff_item.base_download_size;

const labels = getLabels(
sizeComparisonQuery.data?.head_build_details.app_info?.platform ?? undefined
);
// Calculate metrics
const metrics = [
{
title: labels.installSizeLabel,
head: size_metric_diff_item.head_install_size,
base: size_metric_diff_item.base_install_size,
diff:
size_metric_diff_item.head_install_size -
size_metric_diff_item.base_install_size,
percentageChange: installSizePercentage,
icon: IconCode,
},
{
title: labels.downloadSizeLabel,
head: size_metric_diff_item.head_download_size,
base: size_metric_diff_item.base_download_size,
diff:
size_metric_diff_item.head_download_size -
size_metric_diff_item.base_download_size,
percentageChange: downloadSizePercentage,
icon: IconDownload,
},
];

return metrics;
}, [comparisonDataQuery.data, sizeComparisonQuery.data]);

// Filter diff items based on the toggle and search query
const filteredDiffItems = useMemo(() => {
if (!comparisonDataQuery.data?.diff_items) {
Expand Down Expand Up @@ -292,75 +234,10 @@ export function SizeCompareMainContent() {
}}
/>

{/* Metrics Grid */}
<Flex gap="lg" wrap="wrap">
{processedMetrics.map((metric, index) => {
let variant: 'danger' | 'success' | 'muted' = 'muted';
let icon: React.ReactNode | undefined;
if (metric.diff > 0) {
variant = 'danger';
icon = <IconArrow direction="up" size="xs" />;
} else if (metric.diff < 0) {
variant = 'success';
icon = <IconArrow direction="down" size="xs" />;
}

return (
<Container
background="primary"
radius="lg"
padding="xl"
border="primary"
key={index}
flex="1"
style={{minWidth: '250px'}}
>
<Flex direction="column" gap="md">
<Flex gap="sm">
<metric.icon size="sm" />
<Text variant="muted" size="sm">
{metric.title}
</Text>
</Flex>
<Flex direction="column" gap="xs">
<Flex align="end" gap="sm" wrap="wrap">
<Heading as="h3">{formatBytesBase10(metric.head)}</Heading>
<Flex align="center" gap="xs">
{icon}
<DiffText variant={variant} size="sm">
{metric.diff > 0 ? '+' : metric.diff < 0 ? '-' : ''}
{formatBytesBase10(Math.abs(metric.diff))}
{metric.percentageChange && (
<Text as="span" variant={variant}>
{' ('}
<PercentChange
value={metric.percentageChange}
minimumValue={0.001}
preferredPolarity="-"
colorize
/>
{')'}
</Text>
)}
</DiffText>
</Flex>
</Flex>
<Flex gap="xs" wrap="wrap">
<Text variant="muted" size="sm">
{t('Comparison:')}
</Text>
<Text variant="muted" size="sm" bold>
{metric.base === 0
? t('Not present')
: formatBytesBase10(metric.base)}
</Text>
</Flex>
</Flex>
</Flex>
</Container>
);
})}
</Flex>
<BuildComparisonMetricCards
comparisonResults={comparisonDataQuery.data}
comparisonResponse={sizeComparisonQuery.data}
/>

{/* Items Changed Section */}
<Container background="primary" radius="lg" padding="0" border="primary">
Expand Down Expand Up @@ -433,16 +310,3 @@ export function SizeCompareMainContent() {
</Flex>
);
}

const DiffText = styled(Text)`
display: inline-flex;
align-items: center;
flex-wrap: wrap;
gap: 0.25em;

span {
display: inline-flex;
align-items: center;
white-space: nowrap;
}
`;
Loading