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
Expand Up @@ -6,7 +6,6 @@ import {Button} from '@sentry/scraps/button/button';
import {ButtonBar} from '@sentry/scraps/button/buttonBar';
import {Flex} from '@sentry/scraps/layout';

import LoadingError from 'sentry/components/loadingError';
import Panel from 'sentry/components/panels/panel';
import BaseSearchBar from 'sentry/components/searchBar';
import {IconChevron} from 'sentry/icons/iconChevron';
Expand Down Expand Up @@ -64,13 +63,13 @@ export function AttributeDistribution() {
const {
data: attributeBreakdownsData,
isLoading: isAttributeBreakdownsLoading,
isError: isAttributeBreakdownsError,
error: attributeBreakdownsError,
} = useAttributeBreakdowns();

const {
data: cohortCountResponse,
isLoading: isCohortCountLoading,
isError: isCohortCountError,
error: cohortCountError,
} = useApiQuery<{data: Array<{'count()': number}>}>(
[
`/organizations/${organization.slug}/events/`,
Expand Down Expand Up @@ -139,13 +138,11 @@ export function AttributeDistribution() {
setPage(0);
}, [filteredAttributeDistribution]);

if (isAttributeBreakdownsError || isCohortCountError) {
return <LoadingError message={t('Failed to load attribute breakdowns')} />;
}
const error = attributeBreakdownsError ?? cohortCountError;

return (
<Panel>
<Flex direction="column" gap="xl" padding="xl">
<Flex direction="column" gap="2xl" padding="xl">
<Fragment>
<ControlsContainer>
<StyledBaseSearchBar
Expand All @@ -160,6 +157,8 @@ export function AttributeDistribution() {
</ControlsContainer>
{isAttributeBreakdownsLoading || isCohortCountLoading ? (
<AttributeBreakdownsComponent.LoadingCharts />
) : error ? (
<AttributeBreakdownsComponent.ErrorState error={error} />
) : filteredAttributeDistribution.length > 0 ? (
<Fragment>
<ChartsGrid>
Expand Down Expand Up @@ -204,7 +203,7 @@ export function AttributeDistribution() {
</PaginationContainer>
</Fragment>
) : (
<NoAttributesMessage>{t('No matching attributes found')}</NoAttributesMessage>
<AttributeBreakdownsComponent.EmptySearchState />
)}
</Fragment>
</Flex>
Expand All @@ -222,13 +221,6 @@ const StyledBaseSearchBar = styled(BaseSearchBar)`
flex: 1;
`;

const NoAttributesMessage = styled('div')`
display: flex;
justify-content: center;
align-items: center;
color: ${p => p.theme.subText};
`;

const ChartsGrid = styled('div')`
display: grid;
grid-template-columns: repeat(${CHARTS_COLUMN_COUNT}, 1fr);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {Flex} from '@sentry/scraps/layout';

import type {Selection} from 'sentry/components/charts/useChartXRangeSelection';
import {Text} from 'sentry/components/core/text';
import LoadingError from 'sentry/components/loadingError';
import Panel from 'sentry/components/panels/panel';
import BaseSearchBar from 'sentry/components/searchBar';
import {IconChevron} from 'sentry/icons/iconChevron';
Expand Down Expand Up @@ -42,7 +41,7 @@ export function CohortComparison({

const yAxis = visualizes[chartIndex]?.yAxis ?? '';

const {data, isLoading, isError} = useAttributeBreakdownComparison({
const {data, isLoading, error} = useAttributeBreakdownComparison({
aggregateFunction: yAxis,
range: selection.range,
});
Expand Down Expand Up @@ -128,13 +127,9 @@ export function CohortComparison({
};
}, [selection, yAxis]);

if (isError) {
return <LoadingError message={t('Failed to load attribute breakdowns')} />;
}

return (
<Panel data-explore-chart-selection-region>
<Flex direction="column" gap="xl" padding="xl">
<Flex direction="column" gap="2xl" padding="xl">
<ControlsContainer>
<StyledBaseSearchBar
placeholder={t('Search keys')}
Expand All @@ -148,6 +143,8 @@ export function CohortComparison({
</ControlsContainer>
{isLoading ? (
<AttributeBreakdownsComponent.LoadingCharts />
) : error ? (
<AttributeBreakdownsComponent.ErrorState error={error} />
) : (
<Fragment>
{selectionHint && (
Expand Down Expand Up @@ -200,9 +197,7 @@ export function CohortComparison({
</PaginationContainer>
</Fragment>
) : (
<NoAttributesMessage>
{t('No matching attributes found')}
</NoAttributesMessage>
<AttributeBreakdownsComponent.EmptySearchState />
)}
</Fragment>
)}
Expand All @@ -221,13 +216,6 @@ const StyledBaseSearchBar = styled(BaseSearchBar)`
flex: 1;
`;

const NoAttributesMessage = styled('div')`
display: flex;
justify-content: center;
align-items: center;
color: ${p => p.theme.subText};
`;

const ChartsGrid = styled('div')`
display: grid;
grid-template-columns: repeat(${CHARTS_COLUMN_COUNT}, 1fr);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ import {Flex} from '@sentry/scraps/layout/flex';
import BaseChart from 'sentry/components/charts/baseChart';
import {Text} from 'sentry/components/core/text';
import Placeholder from 'sentry/components/placeholder';
import {IconSad, IconSearch, IconTimer} from 'sentry/icons';
import {IconMegaphone} from 'sentry/icons/iconMegaphone';
import {t} from 'sentry/locale';
import {t, tct} from 'sentry/locale';
import type RequestError from 'sentry/utils/requestError/requestError';
import {useFeedbackForm} from 'sentry/utils/useFeedbackForm';

function FeedbackButton() {
Expand Down Expand Up @@ -106,7 +108,7 @@ function LoadingCharts() {
}, []);

return (
<Flex direction="column" gap="xl">
<Flex direction="column" gap="2xl">
{showMessage && (
<Text size="md" variant="muted">
{t(
Expand All @@ -123,6 +125,96 @@ function LoadingCharts() {
);
}

function FeedbackLink() {
const openForm = useFeedbackForm();

if (!openForm) {
return (
<a href="mailto:[email protected]?subject=Attribute%20breakdowns%20failed%20to%20load">
{t('Send us feedback')}
</a>
);
}

return (
<a href="#" onClick={() => openForm?.()}>
{t('Send us feedback')}
</a>
);
}

const StyledIconSearch = styled(IconSearch)`
color: ${p => p.theme.subText};
`;

const StyledIconSad = styled(IconSad)`
color: ${p => p.theme.subText};
`;

const StyledIconTimer = styled(IconTimer)`
color: ${p => p.theme.subText};
`;

const ERROR_STATE_CONFIG: Record<
number | 'default',
{
icon: React.ReactNode;
subtitle: React.ReactNode;
title: string;
}
> = {
504: {
title: t('Query timed out'),
icon: <StyledIconTimer size="xl" />,
subtitle: tct(
'You can try narrowing the time range. Seeing this often? [feedbackLink]',
{
feedbackLink: <FeedbackLink />,
}
),
},
default: {
title: t('Failed to load attribute breakdowns'),
icon: <StyledIconSad size="xl" />,
subtitle: tct('Seeing this often? [feedbackLink]', {
feedbackLink: <FeedbackLink />,
}),
},
};
Copy link
Contributor

Choose a reason for hiding this comment

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

Bug: React hook called at module scope

The ERROR_STATE_CONFIG object is defined at module scope and includes JSX elements that render FeedbackLink components. Since FeedbackLink calls the useFeedbackForm() hook, this violates React's rules of hooks by executing a hook outside of a component render cycle. This will cause a runtime error when the module loads. The config object needs to be moved inside the ErrorState component or use a function that returns the config dynamically during render.

Fix in Cursor Fix in Web


function ErrorState({error}: {error: RequestError}) {
const config =
ERROR_STATE_CONFIG[error?.status ?? 'default'] ?? ERROR_STATE_CONFIG.default;

return (
<Flex direction="column" gap="2xl" padding="3xl" align="center" justify="center">
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: There is also the <Stack /> for flex column, removing one prop from being added 😄

{config.icon}
<Text size="xl" variant="muted">
{config.title}
</Text>
<Text size="md" variant="muted">
{config.subtitle}
</Text>
</Flex>
);
}

function EmptySearchState() {
return (
<Flex direction="column" gap="2xl" padding="3xl" align="center" justify="center">
<StyledIconSearch size="xl" />
<Text size="xl" variant="muted">
{t('No matching attributes found')}
</Text>
<Text size="md" variant="muted">
{tct('Expecting results? [feedbackLink]', {
feedbackLink: <FeedbackLink />,
})}
</Text>
</Flex>
);
}

const StyledPlaceholder = styled(Placeholder)<{_height: number; _width: number}>`
border-radius: ${p => p.theme.borderRadius};
height: ${p => p._height}px;
Expand Down Expand Up @@ -187,4 +279,6 @@ const StyledFeedbackButton = styled(Button)`
export const AttributeBreakdownsComponent = {
FeedbackButton,
LoadingCharts,
ErrorState,
EmptySearchState,
};
Loading