diff --git a/static/app/views/explore/hooks/useMetricOptions.spec.tsx b/static/app/views/explore/hooks/useMetricOptions.spec.tsx index 786eb3253819ed..c5ffdfc28e3dfb 100644 --- a/static/app/views/explore/hooks/useMetricOptions.spec.tsx +++ b/static/app/views/explore/hooks/useMetricOptions.spec.tsx @@ -114,6 +114,7 @@ describe('useMetricOptions', () => { dataset: 'tracemetrics', field: ['metric.name', 'metric.type', 'metric.unit', 'count(metric.name)'], referrer: 'api.explore.metric-options', + statsPeriod: '3d', }), }) ); diff --git a/static/app/views/explore/hooks/useMetricOptions.tsx b/static/app/views/explore/hooks/useMetricOptions.tsx index cc7cd85c4c9ded..b104eac8212154 100644 --- a/static/app/views/explore/hooks/useMetricOptions.tsx +++ b/static/app/views/explore/hooks/useMetricOptions.tsx @@ -48,15 +48,12 @@ function metricOptionsQueryKey({ query.project = projectIds.map(String); } - if (search && datetime) { - // If searching we use the full filters in order to not miss the result. + if (datetime) { Object.entries(normalizeDateTimeParams(datetime)).forEach(([key, value]) => { if (value !== undefined) { query[key] = value as string | string[]; } }); - } else { - query.statsPeriod = '24h'; // Default to a much smaller time window if not searching. } return [`/organizations/${orgSlug}/events/`, {query}]; @@ -112,5 +109,13 @@ export function useMetricOptions({ } }, [result.data]); - return result; + const isMetricOptionsEmpty = + !result.isFetching && + !result.isLoading && + (!result.data?.data || result.data.data.length === 0); + + return { + ...result, + isMetricOptionsEmpty, + }; } diff --git a/static/app/views/explore/metrics/metricGraph/index.tsx b/static/app/views/explore/metrics/metricGraph/index.tsx index 5654a544eecf4a..cef2644400ab47 100644 --- a/static/app/views/explore/metrics/metricGraph/index.tsx +++ b/static/app/views/explore/metrics/metricGraph/index.tsx @@ -1,9 +1,11 @@ import {Fragment, useMemo} from 'react'; +import {ExternalLink} from '@sentry/scraps/link'; + import {CompactSelect} from 'sentry/components/core/compactSelect'; import {Tooltip} from 'sentry/components/core/tooltip'; import {IconClock, IconGraph} from 'sentry/icons'; -import {t} from 'sentry/locale'; +import {t, tct} from 'sentry/locale'; import {defined} from 'sentry/utils'; import {determineSeriesSampleCountAndIsSampled} from 'sentry/views/alerts/rules/metric/utils/determineSeriesSampleCount'; import {Widget} from 'sentry/views/dashboards/widgets/widget/widget'; @@ -29,6 +31,7 @@ import { } from 'sentry/views/explore/utils'; import {ChartType} from 'sentry/views/insights/common/components/chart'; import type {useSortedTimeSeries} from 'sentry/views/insights/common/queries/useSortedTimeSeries'; +import {GenericWidgetEmptyStateWarning} from 'sentry/views/performance/landing/widgets/components/selectableList'; import {WidgetWrapper} from './styles'; @@ -41,6 +44,7 @@ interface MetricsGraphProps { timeseriesResult: ReturnType; additionalActions?: React.ReactNode; infoContentHidden?: boolean; + isMetricOptionsEmpty?: boolean; } export function MetricsGraph({ @@ -49,6 +53,7 @@ export function MetricsGraph({ orientation, additionalActions, infoContentHidden, + isMetricOptionsEmpty, }: MetricsGraphProps) { const visualize = useMetricVisualize(); const setVisualize = useSetMetricVisualize(); @@ -66,6 +71,7 @@ export function MetricsGraph({ orientation={orientation} additionalActions={additionalActions} infoContentHidden={infoContentHidden} + isMetricOptionsEmpty={isMetricOptionsEmpty} /> ); } @@ -84,6 +90,7 @@ function Graph({ visualize, infoContentHidden, additionalActions, + isMetricOptionsEmpty, }: GraphProps) { const aggregate = visualize.yAxis; const topEventsLimit = useQueryParamsTopEventsLimit(); @@ -156,14 +163,34 @@ function Graph({ ); + const showEmptyState = isMetricOptionsEmpty && visualize.visible; + const showChart = visualize.visible && !isMetricOptionsEmpty; + return ( } + Visualization={ + showEmptyState ? ( + + {t('learn more')} + + ), + } + )} + /> + ) : showChart ? ( + + ) : undefined + } Footer={ - visualize.visible && ( + showChart && ( (null); const {result, eventView, fields} = useMetricAggregatesTable({ - enabled: Boolean(traceMetric.name), + enabled: Boolean(traceMetric.name) && !isMetricOptionsEmpty, limit: RESULT_LIMIT, traceMetric, }); @@ -151,9 +152,11 @@ export function AggregatesTab({traceMetric}: AggregatesTabProps) { }; }, [result.data, fields.length]); + const isPending = result.isPending && !isMetricOptionsEmpty; + return ( - {result.isPending && } + {isPending && } {fields.map((field, i) => { @@ -221,15 +224,13 @@ export function AggregatesTab({traceMetric}: AggregatesTabProps) { ))} )) - ) : result.isPending ? ( + ) : isPending ? ( ) : ( - -

{t('No aggregates found')}

-
+
)} diff --git a/static/app/views/explore/metrics/metricInfoTabs/index.tsx b/static/app/views/explore/metrics/metricInfoTabs/index.tsx index 4ead63218db8b6..3a644aaaa72cb9 100644 --- a/static/app/views/explore/metrics/metricInfoTabs/index.tsx +++ b/static/app/views/explore/metrics/metricInfoTabs/index.tsx @@ -23,6 +23,7 @@ interface MetricInfoTabsProps { traceMetric: TraceMetric; additionalActions?: React.ReactNode; contentsHidden?: boolean; + isMetricOptionsEmpty?: boolean; } export default function MetricInfoTabs({ @@ -30,6 +31,7 @@ export default function MetricInfoTabs({ additionalActions, contentsHidden, orientation, + isMetricOptionsEmpty, }: MetricInfoTabsProps) { const visualize = useMetricVisualize(); const queryParamsMode = useQueryParamsMode(); @@ -61,10 +63,16 @@ export default function MetricInfoTabs({ - + - + diff --git a/static/app/views/explore/metrics/metricInfoTabs/metricsSamplesTable.tsx b/static/app/views/explore/metrics/metricInfoTabs/metricsSamplesTable.tsx index cc25b45ede22a6..27b5cbc2c8e719 100644 --- a/static/app/views/explore/metrics/metricInfoTabs/metricsSamplesTable.tsx +++ b/static/app/views/explore/metrics/metricInfoTabs/metricsSamplesTable.tsx @@ -1,7 +1,6 @@ import {useMemo} from 'react'; import styled from '@emotion/styled'; -import EmptyStateWarning from 'sentry/components/emptyStateWarning'; import LoadingIndicator from 'sentry/components/loadingIndicator'; import {SimpleTable} from 'sentry/components/tables/simpleTable'; import {IconWarning} from 'sentry/icons'; @@ -22,6 +21,7 @@ import {SampleTableRow} from 'sentry/views/explore/metrics/metricInfoTabs/metric import type {TraceMetric} from 'sentry/views/explore/metrics/metricQuery'; import {TraceMetricKnownFieldKey} from 'sentry/views/explore/metrics/types'; import {getMetricTableColumnType} from 'sentry/views/explore/metrics/utils'; +import {GenericWidgetEmptyStateWarning} from 'sentry/views/performance/landing/widgets/components/selectableList'; const RESULT_LIMIT = 50; const TWO_MINUTE_DELAY = 120; @@ -31,12 +31,14 @@ export const SAMPLES_PANEL_MIN_WIDTH = 350; interface MetricsSamplesTableProps { embedded?: boolean; + isMetricOptionsEmpty?: boolean; traceMetric?: TraceMetric; } export function MetricsSamplesTable({ traceMetric, embedded = false, + isMetricOptionsEmpty, }: MetricsSamplesTableProps) { const columns = embedded ? TraceSamplesTableEmbeddedColumns : TraceSamplesTableColumns; const fields = columns.filter(c => getMetricTableColumnType(c) !== 'stat'); @@ -47,7 +49,7 @@ export function MetricsSamplesTable({ error, isFetching, } = useMetricSamplesTable({ - disabled: embedded ? false : !traceMetric?.name, + disabled: embedded ? false : !traceMetric?.name || isMetricOptionsEmpty, limit: RESULT_LIMIT, traceMetric, fields, @@ -92,9 +94,7 @@ export function MetricsSamplesTable({ ) : ( - -

{t('No samples found')}

-
+
)} diff --git a/static/app/views/explore/metrics/metricInfoTabs/samplesTab.tsx b/static/app/views/explore/metrics/metricInfoTabs/samplesTab.tsx index 55392396db494f..482d34504524a8 100644 --- a/static/app/views/explore/metrics/metricInfoTabs/samplesTab.tsx +++ b/static/app/views/explore/metrics/metricInfoTabs/samplesTab.tsx @@ -3,8 +3,14 @@ import type {TraceMetric} from 'sentry/views/explore/metrics/metricQuery'; interface SamplesTabProps { traceMetric: TraceMetric; + isMetricOptionsEmpty?: boolean; } -export function SamplesTab({traceMetric}: SamplesTabProps) { - return ; +export function SamplesTab({traceMetric, isMetricOptionsEmpty}: SamplesTabProps) { + return ( + + ); } diff --git a/static/app/views/explore/metrics/metricPanel/index.tsx b/static/app/views/explore/metrics/metricPanel/index.tsx index 9d85a60a814ba4..0b53ba48e4e823 100644 --- a/static/app/views/explore/metrics/metricPanel/index.tsx +++ b/static/app/views/explore/metrics/metricPanel/index.tsx @@ -4,6 +4,7 @@ import Panel from 'sentry/components/panels/panel'; import PanelBody from 'sentry/components/panels/panelBody'; import {useMetricsPanelAnalytics} from 'sentry/views/explore/hooks/useAnalytics'; import {useChartInterval} from 'sentry/views/explore/hooks/useChartInterval'; +import {useMetricOptions} from 'sentry/views/explore/hooks/useMetricOptions'; import {useTopEvents} from 'sentry/views/explore/hooks/useTopEvents'; import {TraceSamplesTableColumns} from 'sentry/views/explore/metrics/constants'; import {useMetricAggregatesTable} from 'sentry/views/explore/metrics/hooks/useMetricAggregatesTable'; @@ -35,16 +36,17 @@ export function MetricPanel({traceMetric, queryIndex}: MetricPanelProps) { canChangeOrientation, } = useTableOrientationControl(); const [infoContentHidden, setInfoContentHidden] = useState(false); + const {isMetricOptionsEmpty} = useMetricOptions({enabled: Boolean(traceMetric.name)}); const {result: timeseriesResult} = useMetricTimeseries({ traceMetric, - enabled: Boolean(traceMetric.name), + enabled: Boolean(traceMetric.name) && !isMetricOptionsEmpty, }); const columns = TraceSamplesTableColumns; const fields = columns.filter(c => getMetricTableColumnType(c) !== 'stat'); const metricSamplesTableResult = useMetricSamplesTable({ - disabled: !traceMetric?.name, + disabled: !traceMetric?.name || isMetricOptionsEmpty, limit: RESULT_LIMIT, traceMetric, fields, @@ -52,7 +54,7 @@ export function MetricPanel({traceMetric, queryIndex}: MetricPanelProps) { }); const metricAggregatesTableResult = useMetricAggregatesTable({ - enabled: Boolean(traceMetric.name), + enabled: Boolean(traceMetric.name) && !isMetricOptionsEmpty, limit: RESULT_LIMIT, traceMetric, }); @@ -87,6 +89,7 @@ export function MetricPanel({traceMetric, queryIndex}: MetricPanelProps) { orientation={orientation} infoContentHidden={infoContentHidden} setInfoContentHidden={setInfoContentHidden} + isMetricOptionsEmpty={isMetricOptionsEmpty} /> ) : ( )} diff --git a/static/app/views/explore/metrics/metricPanel/sideBySideOrientation.tsx b/static/app/views/explore/metrics/metricPanel/sideBySideOrientation.tsx index 9dadb23a15be84..e00c0928b1c980 100644 --- a/static/app/views/explore/metrics/metricPanel/sideBySideOrientation.tsx +++ b/static/app/views/explore/metrics/metricPanel/sideBySideOrientation.tsx @@ -27,8 +27,10 @@ export function SideBySideOrientation({ setOrientation, infoContentHidden, setInfoContentHidden, + isMetricOptionsEmpty, }: { infoContentHidden: boolean; + isMetricOptionsEmpty: boolean; orientation: TableOrientation; queryIndex: number; setInfoContentHidden: (hidden: boolean) => void; @@ -71,6 +73,7 @@ export function SideBySideOrientation({ orientation={orientation} additionalActions={additionalActions} infoContentHidden={infoContentHidden} + isMetricOptionsEmpty={isMetricOptionsEmpty} /> ); @@ -87,6 +90,7 @@ export function SideBySideOrientation({ timeseriesResult={timeseriesResult} queryIndex={queryIndex} orientation={orientation} + isMetricOptionsEmpty={isMetricOptionsEmpty} /> ), default: defaultSplit, @@ -98,6 +102,7 @@ export function SideBySideOrientation({ traceMetric={traceMetric} additionalActions={additionalActions} orientation={orientation} + isMetricOptionsEmpty={isMetricOptionsEmpty} /> } /> diff --git a/static/app/views/explore/metrics/metricPanel/stackedOrientation.tsx b/static/app/views/explore/metrics/metricPanel/stackedOrientation.tsx index dcd430dc520479..3b1981659129e1 100644 --- a/static/app/views/explore/metrics/metricPanel/stackedOrientation.tsx +++ b/static/app/views/explore/metrics/metricPanel/stackedOrientation.tsx @@ -19,9 +19,11 @@ export function StackedOrientation({ canChangeOrientation, infoContentHidden, setInfoContentHidden, + isMetricOptionsEmpty, }: { canChangeOrientation: boolean; infoContentHidden: boolean; + isMetricOptionsEmpty: boolean; orientation: TableOrientation; queryIndex: number; setInfoContentHidden: (hidden: boolean) => void; @@ -50,6 +52,7 @@ export function StackedOrientation({ timeseriesResult={timeseriesResult} queryIndex={queryIndex} orientation={orientation} + isMetricOptionsEmpty={isMetricOptionsEmpty} /> ); diff --git a/static/app/views/performance/landing/widgets/components/selectableList.tsx b/static/app/views/performance/landing/widgets/components/selectableList.tsx index f46164b15e64c6..ed0a19607bcb96 100644 --- a/static/app/views/performance/landing/widgets/components/selectableList.tsx +++ b/static/app/views/performance/landing/widgets/components/selectableList.tsx @@ -82,10 +82,16 @@ export const GrowLink = styled(Link)` display: inherit; `; -export function GenericWidgetEmptyStateWarning({message}: {message: React.ReactNode}) { +export function GenericWidgetEmptyStateWarning({ + message, + title, +}: { + message: React.ReactNode; + title?: string; +}) { return ( - {t('No results found')} + {title ?? t('No results found')} {message} );