diff --git a/static/app/views/explore/components/attributeBreakdowns/attributeDistributionContent.tsx b/static/app/views/explore/components/attributeBreakdowns/attributeDistributionContent.tsx index b0922c7d440774..6b2d3d010bf540 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,13 +138,11 @@ export function AttributeDistribution() { setPage(0); }, [filteredAttributeDistribution]); - if (isAttributeBreakdownsError || isCohortCountError) { - return ; - } + const error = attributeBreakdownsError ?? cohortCountError; return ( - + {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..4eb6d45d84a665 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,13 +127,9 @@ export function CohortComparison({ }; }, [selection, yAxis]); - if (isError) { - return ; - } - return ( - + {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..a2800d6c3cd021 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 {IconSearch, IconTimer, IconWarning} 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() { @@ -106,7 +108,7 @@ function LoadingCharts() { }, []); return ( - + {showMessage && ( {t( @@ -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 StyledIconWarning = styled(IconWarning)` + 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, };