From e8792b2d319d1520c4338058a39dddad6c4e6852 Mon Sep 17 00:00:00 2001 From: Abdullah Khan Date: Tue, 25 Nov 2025 16:24:49 -0500 Subject: [PATCH 1/3] feat(explore-attr-breakdowns): Adding error and empty search state UI --- .../attributeDistributionContent.tsx | 20 ++-- .../cohortComparisonContent.tsx | 20 +--- .../components/attributeBreakdowns/styles.tsx | 96 ++++++++++++++++++- 3 files changed, 105 insertions(+), 31 deletions(-) diff --git a/static/app/views/explore/components/attributeBreakdowns/attributeDistributionContent.tsx b/static/app/views/explore/components/attributeBreakdowns/attributeDistributionContent.tsx index b0922c7d440774..02ad98936a58a1 100644 --- a/static/app/views/explore/components/attributeBreakdowns/attributeDistributionContent.tsx +++ b/static/app/views/explore/components/attributeBreakdowns/attributeDistributionContent.tsx @@ -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'; @@ -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/`, @@ -139,9 +138,7 @@ export function AttributeDistribution() { setPage(0); }, [filteredAttributeDistribution]); - if (isAttributeBreakdownsError || isCohortCountError) { - return ; - } + const error = attributeBreakdownsError ?? cohortCountError; return ( @@ -160,6 +157,8 @@ export function AttributeDistribution() { {isAttributeBreakdownsLoading || isCohortCountLoading ? ( + ) : error ? ( + ) : filteredAttributeDistribution.length > 0 ? ( @@ -204,7 +203,7 @@ export function AttributeDistribution() { ) : ( - {t('No matching attributes found')} + )} @@ -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); diff --git a/static/app/views/explore/components/attributeBreakdowns/cohortComparisonContent.tsx b/static/app/views/explore/components/attributeBreakdowns/cohortComparisonContent.tsx index 5b55d8e48e73c0..77d56bc6ea68fd 100644 --- a/static/app/views/explore/components/attributeBreakdowns/cohortComparisonContent.tsx +++ b/static/app/views/explore/components/attributeBreakdowns/cohortComparisonContent.tsx @@ -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'; @@ -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, }); @@ -128,10 +127,6 @@ export function CohortComparison({ }; }, [selection, yAxis]); - if (isError) { - return ; - } - return ( @@ -148,6 +143,8 @@ export function CohortComparison({ {isLoading ? ( + ) : error ? ( + ) : ( {selectionHint && ( @@ -200,9 +197,7 @@ export function CohortComparison({ ) : ( - - {t('No matching attributes found')} - + )} )} @@ -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); diff --git a/static/app/views/explore/components/attributeBreakdowns/styles.tsx b/static/app/views/explore/components/attributeBreakdowns/styles.tsx index 67f3437db79b3d..c0068e933e0623 100644 --- a/static/app/views/explore/components/attributeBreakdowns/styles.tsx +++ b/static/app/views/explore/components/attributeBreakdowns/styles.tsx @@ -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() { @@ -123,6 +125,96 @@ function LoadingCharts() { ); } +function FeedbackLink() { + const openForm = useFeedbackForm(); + + if (!openForm) { + return ( + + {t('Send us feedback')} + + ); + } + + return ( + openForm?.()}> + {t('Send us feedback')} + + ); +} + +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: , + subtitle: tct( + 'You can try narrowing the time range. Seeing this often? [feedbackLink]', + { + feedbackLink: , + } + ), + }, + default: { + title: t('Failed to load attribute breakdowns'), + icon: , + subtitle: tct('Seeing this often? [feedbackLink]', { + feedbackLink: , + }), + }, +}; + +function ErrorState({error}: {error: RequestError}) { + const config = + ERROR_STATE_CONFIG[error?.status ?? 'default'] ?? ERROR_STATE_CONFIG.default; + + return ( + + {config.icon} + + {config.title} + + + {config.subtitle} + + + ); +} + +function EmptySearchState() { + return ( + + + + {t('No matching attributes found')} + + + {tct('Expecting results? [feedbackLink]', { + feedbackLink: , + })} + + + ); +} + const StyledPlaceholder = styled(Placeholder)<{_height: number; _width: number}>` border-radius: ${p => p.theme.borderRadius}; height: ${p => p._height}px; @@ -187,4 +279,6 @@ const StyledFeedbackButton = styled(Button)` export const AttributeBreakdownsComponent = { FeedbackButton, LoadingCharts, + ErrorState, + EmptySearchState, }; From 4ec339fe977ee28f22f1d245ec478d14bc65be07 Mon Sep 17 00:00:00 2001 From: Abdullah Khan Date: Tue, 25 Nov 2025 16:38:41 -0500 Subject: [PATCH 2/3] feat(explore-attr-breakdowns): Minor style changes --- .../attributeBreakdowns/attributeDistributionContent.tsx | 2 +- .../components/attributeBreakdowns/cohortComparisonContent.tsx | 2 +- .../app/views/explore/components/attributeBreakdowns/styles.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/static/app/views/explore/components/attributeBreakdowns/attributeDistributionContent.tsx b/static/app/views/explore/components/attributeBreakdowns/attributeDistributionContent.tsx index 02ad98936a58a1..6b2d3d010bf540 100644 --- a/static/app/views/explore/components/attributeBreakdowns/attributeDistributionContent.tsx +++ b/static/app/views/explore/components/attributeBreakdowns/attributeDistributionContent.tsx @@ -142,7 +142,7 @@ export function AttributeDistribution() { return ( - + - + + {showMessage && ( {t( From 4aa7faac6d579f2f6495c2a674dda05697ee4b39 Mon Sep 17 00:00:00 2001 From: Abdullah Khan Date: Wed, 26 Nov 2025 20:27:56 -0500 Subject: [PATCH 3/3] feat(explore-attr-breakdowns): Using warning icon for error state CTA --- .../views/explore/components/attributeBreakdowns/styles.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/static/app/views/explore/components/attributeBreakdowns/styles.tsx b/static/app/views/explore/components/attributeBreakdowns/styles.tsx index 088db0150748aa..a2800d6c3cd021 100644 --- a/static/app/views/explore/components/attributeBreakdowns/styles.tsx +++ b/static/app/views/explore/components/attributeBreakdowns/styles.tsx @@ -8,7 +8,7 @@ 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 {IconSearch, IconTimer, IconWarning} from 'sentry/icons'; import {IconMegaphone} from 'sentry/icons/iconMegaphone'; import {t, tct} from 'sentry/locale'; import type RequestError from 'sentry/utils/requestError/requestError'; @@ -147,7 +147,7 @@ const StyledIconSearch = styled(IconSearch)` color: ${p => p.theme.subText}; `; -const StyledIconSad = styled(IconSad)` +const StyledIconWarning = styled(IconWarning)` color: ${p => p.theme.subText}; `; @@ -175,7 +175,7 @@ const ERROR_STATE_CONFIG: Record< }, default: { title: t('Failed to load attribute breakdowns'), - icon: , + icon: , subtitle: tct('Seeing this often? [feedbackLink]', { feedbackLink: , }),