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,
};