Skip to content
Draft
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
4 changes: 3 additions & 1 deletion static/app/components/modals/insightChartModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export type InsightChartModalOptions = {
children: React.ReactNode;
title: React.ReactNode;
footer?: React.ReactNode;
height?: number;
};
type Props = ModalRenderProps & InsightChartModalOptions;

Expand All @@ -20,6 +21,7 @@ export default function InsightChartModal({
children,
Footer,
footer,
height = 300,
}: Props) {
return (
<Fragment>
Expand All @@ -28,7 +30,7 @@ export default function InsightChartModal({
<h3>{title}</h3>
</Header>

<ChartRenderingContext value={{height: 300, isFullscreen: true}}>
<ChartRenderingContext value={{height, isFullscreen: true}}>
{children}
</ChartRenderingContext>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,8 @@ export function BuildDetailsMainContent(props: BuildDetailsMainContentProps) {
<AppSizeTreemap
root={filteredTreemapData.root}
searchQuery={searchQuery || ''}
unfilteredRoot={appSizeData.treemap.root}
onSearchChange={value => setSearchQuery(value || undefined)}
/>
) : (
<Alert type="info">No files found matching "{searchQuery}"</Alert>
Expand All @@ -229,7 +231,12 @@ export function BuildDetailsMainContent(props: BuildDetailsMainContentProps) {
);
} else {
visualizationContent = filteredTreemapData ? (
<AppSizeTreemap root={filteredTreemapData.root} searchQuery={searchQuery || ''} />
<AppSizeTreemap
root={filteredTreemapData.root}
searchQuery={searchQuery || ''}
unfilteredRoot={appSizeData.treemap.root}
onSearchChange={value => setSearchQuery(value || undefined)}
/>
) : (
<Alert type="info">No files found matching "{searchQuery}"</Alert>
);
Expand Down
131 changes: 112 additions & 19 deletions static/app/views/preprod/components/visualizations/appSizeTreemap.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,86 @@
import {useContext, useState} from 'react';
import {useTheme} from '@emotion/react';
import styled from '@emotion/styled';
import type {TreemapSeriesOption, VisualMapComponentOption} from 'echarts';

import {openInsightChartModal} from 'sentry/actionCreators/modal';
import BaseChart, {type TooltipOption} from 'sentry/components/charts/baseChart';
import {Button} from 'sentry/components/core/button';
import {InputGroup} from 'sentry/components/core/input/inputGroup';
import {Container, Flex} from 'sentry/components/core/layout';
import {Heading} from 'sentry/components/core/text';
import {IconClose, IconExpand, IconSearch} from 'sentry/icons';
import {t} from 'sentry/locale';
import {formatBytesBase10} from 'sentry/utils/bytes/formatBytesBase10';
import {ChartRenderingContext} from 'sentry/views/insights/common/components/chart';
import {getAppSizeCategoryInfo} from 'sentry/views/preprod/components/visualizations/appSizeTheme';
import {TreemapType, type TreemapElement} from 'sentry/views/preprod/types/appSizeTypes';
import {filterTreemapElement} from 'sentry/views/preprod/utils/treemapFiltering';

interface AppSizeTreemapProps {
root: TreemapElement | null;
searchQuery: string;
onSearchChange?: (query: string) => void;
unfilteredRoot?: TreemapElement;
}

function FullscreenModalContent({
unfilteredRoot,
initialSearch,
onSearchChange,
}: {
initialSearch: string;
unfilteredRoot: TreemapElement;
onSearchChange?: (query: string) => void;
}) {
const [localSearch, setLocalSearch] = useState(initialSearch);
const filteredRoot = filterTreemapElement(unfilteredRoot, localSearch, '');

const handleSearchChange = (value: string) => {
setLocalSearch(value);
onSearchChange?.(value);
};

return (
<Container height="100%" width="100%">
<Flex direction="column" gap="md" height="100%">
<InputGroup>
<InputGroup.LeadingItems>
<IconSearch />
</InputGroup.LeadingItems>
<InputGroup.Input
placeholder="Search files"
value={localSearch}
onChange={e => handleSearchChange(e.target.value)}
/>
{localSearch && (
<InputGroup.TrailingItems>
<Button
onClick={() => handleSearchChange('')}
aria-label="Clear search"
borderless
size="zero"
>
<IconClose size="sm" />
</Button>
</InputGroup.TrailingItems>
)}
</InputGroup>
<Container height="100%" width="100%">
<AppSizeTreemap root={filteredRoot} searchQuery={localSearch} />
</Container>
</Flex>
</Container>
);
}

export function AppSizeTreemap(props: AppSizeTreemapProps) {
const theme = useTheme();
const {root} = props;
const {root, searchQuery, unfilteredRoot, onSearchChange} = props;
const appSizeCategoryInfo = getAppSizeCategoryInfo(theme);
const renderingContext = useContext(ChartRenderingContext);
const isFullscreen = renderingContext?.isFullscreen ?? false;
const contextHeight = renderingContext?.height;

function convertToEChartsData(element: TreemapElement): any {
const categoryInfo =
Expand Down Expand Up @@ -82,7 +146,7 @@ export function AppSizeTreemap(props: AppSizeTreemapProps) {
// Empty state
if (root === null) {
return (
<EmptyContainer>
<Flex align="center" justify="center" height="100%">
<Heading as="h4">
No files match your search:{' '}
<span
Expand All @@ -96,7 +160,7 @@ export function AppSizeTreemap(props: AppSizeTreemapProps) {
{props.searchQuery}
</span>
</Heading>
</EmptyContainer>
</Flex>
);
}

Expand All @@ -109,8 +173,8 @@ export function AppSizeTreemap(props: AppSizeTreemapProps) {
type: 'treemap',
animationEasing: 'quarticOut',
animationDuration: 300,
height: `calc(100% - 22px)`,
width: `100%`,
height: isFullscreen ? '100%' : `calc(100% - 22px)`,
width: '100%',
top: '22px',
breadcrumb: {
show: true,
Expand Down Expand Up @@ -224,21 +288,50 @@ export function AppSizeTreemap(props: AppSizeTreemapProps) {
};

return (
<BaseChart
autoHeightResize
renderer="canvas"
xAxis={null}
yAxis={null}
series={series}
visualMap={visualMap}
tooltip={tooltip}
/>
<Container height="100%" width="100%" position="relative">
<BaseChart
autoHeightResize={!isFullscreen}
height={contextHeight}
renderer="canvas"
xAxis={null}
yAxis={null}
series={series}
visualMap={visualMap}
tooltip={tooltip}
/>
{!isFullscreen && (
<ButtonContainer direction="column" gap="sm" padding="sm" position="absolute">
<Button
size="xs"
aria-label={t('Open Full-Screen View')}
borderless
icon={<IconExpand />}
onClick={() => {
openInsightChartModal({
title: t('Size Analysis'),
height: 500,
children: unfilteredRoot ? (
<FullscreenModalContent
unfilteredRoot={unfilteredRoot}
initialSearch={searchQuery}
onSearchChange={onSearchChange}
/>
) : (
<Container height="100%" width="100%">
<AppSizeTreemap root={root} searchQuery={searchQuery} />
</Container>
),
});
}}
/>
</ButtonContainer>
)}
</Container>
);
}

const EmptyContainer = styled('div')`
display: flex;
align-items: center;
justify-content: center;
height: 100%;
const ButtonContainer = styled(Flex)`
top: -10px;
right: 0;
z-index: 10;
`;
Loading