diff --git a/static/app/views/alerts/rules/metric/utils/determineSeriesSampleCount.tsx b/static/app/views/alerts/rules/metric/utils/determineSeriesSampleCount.tsx index 5d9401391f1308..0bae87c0e287dd 100644 --- a/static/app/views/alerts/rules/metric/utils/determineSeriesSampleCount.tsx +++ b/static/app/views/alerts/rules/metric/utils/determineSeriesSampleCount.tsx @@ -44,7 +44,7 @@ export function determineSeriesSampleCountAndIsSampled( } const sampleRate = data[i]!.values[j]!.sampleRate; - if (sampleRate === 1) { + if (defined(sampleRate) && sampleRate >= 1) { hasUnsampledInterval = true; } else if (defined(sampleRate) && sampleRate < 1) { hasSampledInterval = true; diff --git a/static/app/views/explore/logs/confidenceFooter.spec.tsx b/static/app/views/explore/logs/confidenceFooter.spec.tsx new file mode 100644 index 00000000000000..0226941ad6fe11 --- /dev/null +++ b/static/app/views/explore/logs/confidenceFooter.spec.tsx @@ -0,0 +1,582 @@ +import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary'; + +import type {ChartInfo} from 'sentry/views/explore/components/chart/types'; +import {ChartType} from 'sentry/views/insights/common/components/chart'; + +import {ConfidenceFooter} from './confidenceFooter'; + +function Wrapper({children}: {children: React.ReactNode}) { + return
{children}
; +} + +describe('ConfidenceFooter', () => { + const rawLogCounts = { + normal: { + count: 100, + isLoading: false, + }, + highAccuracy: { + count: 1000, + isLoading: false, + }, + }; + + function chartInfo(info: Partial) { + return { + chartType: ChartType.LINE, + series: [], + timeseriesResult: {} as any, + yAxis: '', + ...info, + }; + } + + it('loading', () => { + render( + , + {wrapper: Wrapper} + ); + expect(screen.getByTestId('loading-placeholder')).toBeInTheDocument(); + }); + + describe('with raw counts', () => { + describe('unsampled', () => { + describe('without user query', () => { + it('loaded without top events', () => { + render( + , + { + wrapper: Wrapper, + } + ); + expect(screen.getByTestId('wrapper')).toHaveTextContent('100 logs'); + }); + + it('loaded with top events', () => { + render( + , + { + wrapper: Wrapper, + } + ); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + '100 logs for top 5 groups' + ); + }); + }); + + describe('with user query', () => { + it('loaded without top events', () => { + render( + , + { + wrapper: Wrapper, + } + ); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + '100 matches of 1k logs' + ); + }); + + it('loaded with top events', () => { + render( + , + { + wrapper: Wrapper, + } + ); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + '100 matches of 1k logs for top 5 groups' + ); + }); + }); + }); + + describe('sampled', () => { + describe('without user query', () => { + describe('partial scan', () => { + it('loaded 1', async () => { + render( + , + { + wrapper: Wrapper, + } + ); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + 'Estimated from 1 sample of 1k logs' + ); + await userEvent.hover( + screen.getByText((_, element) => element?.textContent === '1 sample') + ); + expect( + await screen.findByText( + /The volume of logs in this time range is too large for us to do a full scan./ + ) + ).toBeInTheDocument(); + expect( + await screen.findByText( + /Try reducing the date range or number of projects to attempt scanning all logs./ + ) + ).toBeInTheDocument(); + }); + + it('loaded 1 with grouping', async () => { + render( + , + { + wrapper: Wrapper, + } + ); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + 'Estimated for top 5 groups from 1 sample of 1k logs' + ); + await userEvent.hover( + screen.getByText((_, element) => element?.textContent === '1 sample') + ); + expect( + await screen.findByText( + /The volume of logs in this time range is too large for us to do a full scan./ + ) + ).toBeInTheDocument(); + expect( + await screen.findByText( + /Try reducing the date range or number of projects to attempt scanning all logs./ + ) + ).toBeInTheDocument(); + }); + + it('loaded 10', async () => { + render( + , + { + wrapper: Wrapper, + } + ); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + 'Estimated from 10 samples of 1k logs' + ); + await userEvent.hover( + screen.getByText((_, element) => element?.textContent === '10 samples') + ); + expect( + await screen.findByText( + /The volume of logs in this time range is too large for us to do a full scan./ + ) + ).toBeInTheDocument(); + expect( + await screen.findByText( + /Try reducing the date range or number of projects to attempt scanning all logs./ + ) + ).toBeInTheDocument(); + }); + + it('loaded 10 with grouping', async () => { + render( + , + { + wrapper: Wrapper, + } + ); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + 'Estimated for top 5 groups from 10 samples of 1k logs' + ); + await userEvent.hover( + screen.getByText((_, element) => element?.textContent === '10 samples') + ); + expect( + await screen.findByText( + /The volume of logs in this time range is too large for us to do a full scan./ + ) + ).toBeInTheDocument(); + expect( + await screen.findByText( + /Try reducing the date range or number of projects to attempt scanning all logs./ + ) + ).toBeInTheDocument(); + }); + }); + + describe('full scan', () => { + it('loaded 1', () => { + render( + , + { + wrapper: Wrapper, + } + ); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + 'Estimated from 1 log' + ); + }); + + it('loaded 1 with grouping', () => { + render( + , + { + wrapper: Wrapper, + } + ); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + 'Estimated for top 5 groups from 1 log' + ); + }); + + it('loaded 10', () => { + render( + , + { + wrapper: Wrapper, + } + ); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + 'Estimated from 10 logs' + ); + }); + + it('loaded 10 with grouping', () => { + render( + , + { + wrapper: Wrapper, + } + ); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + 'Estimated for top 5 groups from 10 logs' + ); + }); + }); + }); + + describe('with user query', () => { + describe('partial scan', () => { + it('loaded 1', async () => { + render( + , + { + wrapper: Wrapper, + } + ); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + 'Estimated from 1 match after scanning 100 samples of 1k logs' + ); + await userEvent.hover( + screen.getByText((_, element) => element?.textContent === '1 match') + ); + expect( + await screen.findByText( + /The volume of logs in this time range is too large for us to do a full scan./ + ) + ).toBeInTheDocument(); + expect( + await screen.findByText( + /Try reducing the date range or number of projects to attempt scanning all logs./ + ) + ).toBeInTheDocument(); + }); + + it('loaded 1 with grouping', async () => { + render( + , + { + wrapper: Wrapper, + } + ); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + 'Estimated for top 5 groups from 1 match after scanning 100 samples of 1k logs' + ); + await userEvent.hover( + screen.getByText((_, element) => element?.textContent === '1 match') + ); + expect( + await screen.findByText( + /The volume of logs in this time range is too large for us to do a full scan./ + ) + ).toBeInTheDocument(); + expect( + await screen.findByText( + /Try reducing the date range or number of projects to attempt scanning all logs./ + ) + ).toBeInTheDocument(); + }); + + it('loaded 10', async () => { + render( + , + { + wrapper: Wrapper, + } + ); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + 'Estimated from 10 matches after scanning 100 samples of 1k logs' + ); + await userEvent.hover( + screen.getByText((_, element) => element?.textContent === '10 matches') + ); + expect( + await screen.findByText( + /The volume of logs in this time range is too large for us to do a full scan./ + ) + ).toBeInTheDocument(); + expect( + await screen.findByText( + /Try reducing the date range or number of projects to attempt scanning all logs./ + ) + ).toBeInTheDocument(); + }); + + it('loaded 10 with grouping', async () => { + render( + , + { + wrapper: Wrapper, + } + ); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + 'Estimated for top 5 groups from 10 matches after scanning 100 samples of 1k logs' + ); + await userEvent.hover( + screen.getByText((_, element) => element?.textContent === '10 matches') + ); + expect( + await screen.findByText( + /The volume of logs in this time range is too large for us to do a full scan./ + ) + ).toBeInTheDocument(); + expect( + await screen.findByText( + /Try reducing the date range or number of projects to attempt scanning all logs./ + ) + ).toBeInTheDocument(); + }); + }); + + describe('full scan', () => { + it('loaded 1', () => { + render( + , + { + wrapper: Wrapper, + } + ); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + 'Estimated from 1 log' + ); + }); + + it('loaded 1 with grouping', () => { + render( + , + { + wrapper: Wrapper, + } + ); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + 'Estimated for top 5 groups from 1 log' + ); + }); + + it('loaded 10', () => { + render( + , + { + wrapper: Wrapper, + } + ); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + 'Estimated from 10 logs' + ); + }); + + it('loaded 10 with grouping', () => { + render( + , + { + wrapper: Wrapper, + } + ); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + 'Estimated for top 5 groups from 10 logs' + ); + }); + }); + }); + }); + }); +}); diff --git a/static/app/views/explore/logs/confidenceFooter.tsx b/static/app/views/explore/logs/confidenceFooter.tsx index 9b2861ee1e8660..e02da34d464467 100644 --- a/static/app/views/explore/logs/confidenceFooter.tsx +++ b/static/app/views/explore/logs/confidenceFooter.tsx @@ -3,10 +3,7 @@ import Count from 'sentry/components/count'; import {t, tct} from 'sentry/locale'; import type {Confidence} from 'sentry/types/organization'; import {defined} from 'sentry/utils'; -import { - Container, - usePreviouslyLoaded, -} from 'sentry/views/explore/components/chart/chartFooter'; +import {Container} from 'sentry/views/explore/components/chart/chartFooter'; import { Placeholder, WarningIcon, @@ -22,12 +19,11 @@ interface ConfidenceFooterProps { } export function ConfidenceFooter({ - chartInfo: currentChartInfo, + chartInfo, hasUserQuery, isLoading, rawLogCounts, }: ConfidenceFooterProps) { - const chartInfo = usePreviouslyLoaded(currentChartInfo, isLoading); return ( 1; - - if (!defined(sampleCount) || isLoading) { + if (isLoading || !defined(sampleCount)) { return ; } + const isTopN = defined(topEvents) && topEvents > 1; const noSampling = defined(isSampled) && !isSampled; - const matchingLogsCount = - sampleCount > 1 - ? t('%s matches', ) - : t('%s match', ); - const downsampledLogsCount = rawLogCounts.normal.count ? ( - rawLogCounts.normal.count > 1 ? ( - t('%s samples', ) - ) : ( - t('%s sample', ) - ) - ) : ( - - ); - const allLogsCount = rawLogCounts.highAccuracy.count ? ( - rawLogCounts.highAccuracy.count > 1 ? ( - t('%s logs', ) - ) : ( - t('%s log', ) - ) - ) : ( - - ); - if (dataScanned === 'full') { + // No sampling happened, so don't mention estimations. + if (noSampling) { if (!hasUserQuery) { + const matchingLogsCount = + sampleCount > 1 + ? t('%s logs', ) + : t('%s log', ); + if (isTopN) { - return tct('Log count for top [topEvents] groups: [matchingLogsCount]', { + return tct('[matchingLogsCount] for top [topEvents] groups', { + matchingLogsCount, topEvents, - matchingLogsCount: , }); } - return tct('Log count: [matchingLogsCount]', { - matchingLogsCount: , - }); + return matchingLogsCount; } - // For logs, if the full data was scanned, we can assume that no - // extrapolation happened and we should remove mentions of extrapolation. + const matchingLogsCount = + sampleCount > 1 + ? t('%s matches', ) + : t('%s match', ); + + const totalLogsCount = rawLogCounts.highAccuracy.count ? ( + rawLogCounts.highAccuracy.count > 1 ? ( + t('%s logs', ) + ) : ( + t('%s log', ) + ) + ) : ( + + ); + if (isTopN) { - return tct('[matchingLogsCount] for top [topEvents] groups in [allLogsCount]', { - topEvents, + return tct('[matchingLogsCount] of [totalLogsCount] for top [topEvents] groups', { matchingLogsCount, - allLogsCount, + totalLogsCount, + topEvents, }); } - return tct('[matchingLogsCount] in [allLogsCount]', { + return tct('[matchingLogsCount] of [totalLogsCount]', { matchingLogsCount, - allLogsCount, + totalLogsCount, }); } - const downsampledTooltip = ; + const maybeWarning = + dataScanned === 'partial' ? tct('[warning] ', {warning: }) : null; + const maybeTooltip = + dataScanned === 'partial' ? : null; + + // no user query means it's showing the total number of logs scanned + // so no need to mention how many matched + if (!hasUserQuery) { + // partial scans means that we didnt scan all the data so it's useful + // to mention the total number of logs available + if (dataScanned === 'partial') { + const matchingLogsCount = + sampleCount > 1 + ? t('%s samples', ) + : t('%s sample', ); + + const totalLogsCount = rawLogCounts.highAccuracy.count ? ( + rawLogCounts.highAccuracy.count > 1 ? ( + t('%s logs', ) + ) : ( + t('%s log', ) + ) + ) : ( + + ); + + if (isTopN) { + return tct( + '[maybeWarning]Estimated for top [topEvents] groups from [maybeTooltip:[matchingLogsCount]] of [totalLogsCount]', + { + maybeWarning, + topEvents, + maybeTooltip, + matchingLogsCount, + totalLogsCount, + } + ); + } + + return tct( + '[maybeWarning]Estimated from [maybeTooltip:[matchingLogsCount]] of [totalLogsCount]', + { + maybeWarning, + maybeTooltip, + matchingLogsCount, + totalLogsCount, + } + ); + } + + // otherwise, a full scan was done + // full scan means we scanned all the data available so no need to repeat that information twice + + const matchingLogsCount = + sampleCount > 1 + ? t('%s logs', ) + : t('%s log', ); + + if (isTopN) { + return tct( + '[maybeWarning]Estimated for top [topEvents] groups from [maybeTooltip:[matchingLogsCount]]', + { + maybeWarning, + topEvents, + maybeTooltip, + matchingLogsCount, + } + ); + } + + return tct('[maybeWarning]Estimated from [maybeTooltip:[matchingLogsCount]]', { + maybeWarning, + maybeTooltip, + matchingLogsCount, + }); + } + + // otherwise, a user query was specified + // with a user query, it means we should tell the user how many of the scanned logs + // matched the user query + + // partial scans means that we didnt scan all the data so it's useful + // to mention the total number of logs available + if (dataScanned === 'partial') { + const matchingLogsCount = + sampleCount > 1 + ? t('%s matches', ) + : t('%s match', ); + + const scannedLogsCount = rawLogCounts.normal.count ? ( + rawLogCounts.normal.count > 1 ? ( + t('%s samples', ) + ) : ( + t('%s sample', ) + ) + ) : ( + + ); + + const totalLogsCount = rawLogCounts.highAccuracy.count ? ( + rawLogCounts.highAccuracy.count > 1 ? ( + t('%s logs', ) + ) : ( + t('%s log', ) + ) + ) : ( + + ); + + if (isTopN) { + return tct( + '[maybeWarning]Estimated for top [topEvents] groups from [maybeTooltip:[matchingLogsCount]] after scanning [scannedLogsCount] of [totalLogsCount]', + { + maybeWarning, + topEvents, + maybeTooltip, + matchingLogsCount, + scannedLogsCount, + totalLogsCount, + } + ); + } + + return tct( + '[maybeWarning]Estimated from [maybeTooltip:[matchingLogsCount]] after scanning [scannedLogsCount] of [totalLogsCount]', + { + maybeWarning, + maybeTooltip, + matchingLogsCount, + scannedLogsCount, + totalLogsCount, + } + ); + } + + // otherwise, a full scan was done + // full scan means we scanned all the data available so no need to repeat that information twice + + const matchingLogsCount = + sampleCount > 1 + ? t('%s matches', ) + : t('%s match', ); + + const totalLogsCount = rawLogCounts.highAccuracy.count ? ( + rawLogCounts.highAccuracy.count > 1 ? ( + t('%s logs', ) + ) : ( + t('%s log', ) + ) + ) : ( + + ); if (isTopN) { return tct( - '[warning] Extrapolated from [matchingLogsCount] for top [topEvents] groups after scanning [tooltip:[downsampledLogsCount] of [allLogsCount]]', + '[maybeWarning]Estimated for top [topEvents] groups from [maybeTooltip:[matchingLogsCount]] of [totalLogsCount]', { - warning: , + maybeWarning, topEvents, + maybeTooltip, matchingLogsCount, - downsampledLogsCount, - allLogsCount, - tooltip: downsampledTooltip, + totalLogsCount, } ); } return tct( - '[warning] Extrapolated from [matchingLogsCount] after scanning [tooltip:[downsampledLogsCount] of [allLogsCount]]', + '[maybeWarning]Estimated from [maybeTooltip:[matchingLogsCount]] of [totalLogsCount]', { - warning: , + maybeWarning, + maybeTooltip, matchingLogsCount, - downsampledLogsCount, - allLogsCount, - tooltip: downsampledTooltip, + totalLogsCount, } ); } @@ -168,6 +307,7 @@ function DownsampledTooltip({ 'The volume of logs in this time range is too large for us to do a full scan.' )}
+
{t( 'Try reducing the date range or number of projects to attempt scanning all logs.' )} diff --git a/static/app/views/explore/spans/charts/confidenceFooter.spec.tsx b/static/app/views/explore/spans/charts/confidenceFooter.spec.tsx index bedb365866c2e1..94e620333ec9d5 100644 --- a/static/app/views/explore/spans/charts/confidenceFooter.spec.tsx +++ b/static/app/views/explore/spans/charts/confidenceFooter.spec.tsx @@ -7,105 +7,1039 @@ function Wrapper({children}: {children: React.ReactNode}) { } describe('ConfidenceFooter', () => { - describe('low confidence', () => { - it('renders for full scan without grouping', async () => { - render( - , - {wrapper: Wrapper} - ); - - expect(screen.getByTestId('wrapper')).toHaveTextContent( - 'Extrapolated from 100 spans' - ); - await userEvent.hover(screen.getByText('100')); - expect( - await screen.findByText( - /You may not have enough span samples for a high accuracy extrapolation of your query./ - ) - ).toBeInTheDocument(); - }); - it('renders for full scan with grouping', async () => { - render( - , - {wrapper: Wrapper} - ); - - expect(screen.getByTestId('wrapper')).toHaveTextContent( - 'Extrapolated from 100 spans for top 5 groups' - ); - await userEvent.hover(screen.getByText('100')); - expect( - await screen.findByText( - /You may not have enough span samples for a high accuracy extrapolation of your query./ - ) - ).toBeInTheDocument(); - }); + it('loading', () => { + render(, {wrapper: Wrapper}); + expect(screen.getByTestId('loading-placeholder')).toBeInTheDocument(); }); - describe('high confidence', () => { - it('renders for full scan without grouping', () => { - render( - , - {wrapper: Wrapper} - ); - - expect(screen.getByTestId('wrapper')).toHaveTextContent('Span count: 100'); + describe('without raw counts', () => { + describe('low confidence', () => { + it('loaded 1', async () => { + render(, { + wrapper: Wrapper, + }); + expect(screen.getByTestId('wrapper')).toHaveTextContent('Estimated from 1 span'); + await userEvent.hover( + screen.getByText((_, element) => element?.textContent === '1 span') + ); + expect( + await screen.findByText( + /You may not have enough span samples for a high accuracy estimation of your query./ + ) + ).toBeInTheDocument(); + expect( + await screen.findByText( + /You can try adjusting your query by increasing the chart's time interval./ + ) + ).toBeInTheDocument(); + }); + + it('loaded 1 with grouping', async () => { + render(, { + wrapper: Wrapper, + }); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + 'Estimated for top 5 groups from 1 span' + ); + await userEvent.hover( + screen.getByText((_, element) => element?.textContent === '1 span') + ); + expect( + await screen.findByText( + /You may not have enough span samples for a high accuracy estimation of your query./ + ) + ).toBeInTheDocument(); + expect( + await screen.findByText( + /You can try adjusting your query by increasing the chart's time interval./ + ) + ).toBeInTheDocument(); + }); + + it('loaded 10', async () => { + render(, { + wrapper: Wrapper, + }); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + 'Estimated from 10 spans' + ); + await userEvent.hover( + screen.getByText((_, element) => element?.textContent === '10 spans') + ); + expect( + await screen.findByText( + /You may not have enough span samples for a high accuracy estimation of your query./ + ) + ).toBeInTheDocument(); + expect( + await screen.findByText( + /You can try adjusting your query by increasing the chart's time interval./ + ) + ).toBeInTheDocument(); + }); + + it('loaded 10 with grouping', async () => { + render(, { + wrapper: Wrapper, + }); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + 'Estimated for top 5 groups from 10 spans' + ); + await userEvent.hover( + screen.getByText((_, element) => element?.textContent === '10 spans') + ); + expect( + await screen.findByText( + /You may not have enough span samples for a high accuracy estimation of your query./ + ) + ).toBeInTheDocument(); + expect( + await screen.findByText( + /You can try adjusting your query by increasing the chart's time interval./ + ) + ).toBeInTheDocument(); + }); }); - it('renders for full scan with grouping', () => { - render( - , - {wrapper: Wrapper} - ); - - expect(screen.getByTestId('wrapper')).toHaveTextContent( - 'Span count for top 5 groups: 100' - ); + + describe('high confidence', () => { + it('loaded 1', () => { + render(, { + wrapper: Wrapper, + }); + expect(screen.getByTestId('wrapper')).toHaveTextContent('Estimated from 1 span'); + }); + + it('loaded 1 with grouping', () => { + render(, { + wrapper: Wrapper, + }); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + 'Estimated for top 5 groups from 1 span' + ); + }); + + it('loaded 10', () => { + render(, { + wrapper: Wrapper, + }); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + 'Estimated from 10 spans' + ); + }); + + it('loaded 10 with grouping', () => { + render(, { + wrapper: Wrapper, + }); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + 'Estimated for top 5 groups from 10 spans' + ); + }); }); }); - describe('unextrapolated', () => { - it('unextrapolated loading', () => { - render(, {wrapper: Wrapper}); + describe('with raw counts', () => { + const rawSpanCounts = { + normal: { + count: 100, + isLoading: false, + }, + highAccuracy: { + count: 1000, + isLoading: false, + }, + }; + + describe('unextrapolated', () => { + describe('without user query', () => { + it('loaded without top events', () => { + render( + , + { + wrapper: Wrapper, + } + ); + expect(screen.getByTestId('wrapper')).toHaveTextContent('100 spans'); + }); + + it('loaded with top events', () => { + render( + , + { + wrapper: Wrapper, + } + ); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + '100 spans for top 5 groups' + ); + }); + }); + + describe('with user query', () => { + it('loaded without top events', () => { + render( + , + { + wrapper: Wrapper, + } + ); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + '100 matches of 1k spans' + ); + }); - expect(screen.getByTestId('loading-placeholder')).toBeInTheDocument(); + it('loaded with top events', () => { + render( + , + { + wrapper: Wrapper, + } + ); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + '100 matches of 1k spans for top 5 groups' + ); + }); + }); }); - it('unextrapolated loaded', () => { - render(, { - wrapper: Wrapper, + describe('unsampled', () => { + describe('without user query', () => { + it('loaded without top events', () => { + render( + , + { + wrapper: Wrapper, + } + ); + expect(screen.getByTestId('wrapper')).toHaveTextContent('100 spans'); + }); + + it('loaded with top events', () => { + render( + , + { + wrapper: Wrapper, + } + ); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + '100 spans for top 5 groups' + ); + }); }); - expect(screen.getByTestId('wrapper')).toHaveTextContent('Span count: 100'); + describe('with user query', () => { + it('loaded without top events', () => { + render( + , + { + wrapper: Wrapper, + } + ); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + '100 matches of 1k spans' + ); + }); + + it('loaded with top events', () => { + render( + , + { + wrapper: Wrapper, + } + ); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + '100 matches of 1k spans for top 5 groups' + ); + }); + }); }); - it('unextrapolated loaded with grouping', () => { - render(, { - wrapper: Wrapper, + describe('without user query', () => { + describe('partial scan', () => { + describe('low confidence', () => { + it('loaded 1', async () => { + render( + , + {wrapper: Wrapper} + ); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + 'Estimated from 1 sample of 1k spans' + ); + await userEvent.hover( + screen.getByText((_, element) => element?.textContent === '1 sample') + ); + expect( + await screen.findByText( + /You may not have enough span samples for a high accuracy estimation of your query./ + ) + ).toBeInTheDocument(); + expect( + await screen.findByText( + /You can try adjusting your query by narrowing the date range or increasing the chart's time interval./ + ) + ).toBeInTheDocument(); + }); + + it('loaded 1 with grouping', async () => { + render( + , + {wrapper: Wrapper} + ); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + 'Estimated for top 5 groups from 1 sample of 1k spans' + ); + await userEvent.hover( + screen.getByText((_, element) => element?.textContent === '1 sample') + ); + expect( + await screen.findByText( + /You may not have enough span samples for a high accuracy estimation of your query./ + ) + ).toBeInTheDocument(); + expect( + await screen.findByText( + /You can try adjusting your query by narrowing the date range or increasing the chart's time interval./ + ) + ).toBeInTheDocument(); + }); + + it('loaded 10', async () => { + render( + , + {wrapper: Wrapper} + ); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + 'Estimated from 10 samples of 1k spans' + ); + await userEvent.hover( + screen.getByText((_, element) => element?.textContent === '10 samples') + ); + expect( + await screen.findByText( + /You may not have enough span samples for a high accuracy estimation of your query./ + ) + ).toBeInTheDocument(); + expect( + await screen.findByText( + /You can try adjusting your query by narrowing the date range or increasing the chart's time interval./ + ) + ).toBeInTheDocument(); + }); + + it('loaded 10 with grouping', async () => { + render( + , + {wrapper: Wrapper} + ); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + 'Estimated for top 5 groups from 10 samples of 1k spans' + ); + await userEvent.hover( + screen.getByText((_, element) => element?.textContent === '10 samples') + ); + expect( + await screen.findByText( + /You may not have enough span samples for a high accuracy estimation of your query./ + ) + ).toBeInTheDocument(); + expect( + await screen.findByText( + /You can try adjusting your query by narrowing the date range or increasing the chart's time interval./ + ) + ).toBeInTheDocument(); + }); + }); + + describe('high confidence', () => { + it('loaded 1', () => { + render( + , + {wrapper: Wrapper} + ); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + 'Estimated from 1 sample of 1k spans' + ); + }); + + it('loaded 1 with grouping', () => { + render( + , + {wrapper: Wrapper} + ); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + 'Estimated for top 5 groups from 1 sample of 1k spans' + ); + }); + + it('loaded 10', () => { + render( + , + {wrapper: Wrapper} + ); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + 'Estimated from 10 samples of 1k spans' + ); + }); + + it('loaded 10 with grouping', () => { + render( + , + {wrapper: Wrapper} + ); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + 'Estimated for top 5 groups from 10 samples of 1k spans' + ); + }); + }); }); - expect(screen.getByTestId('wrapper')).toHaveTextContent( - 'Span count for top 5 groups: 100' - ); + describe('full scan', () => { + describe('low confidence', () => { + it('loaded 1', async () => { + render( + , + {wrapper: Wrapper} + ); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + 'Estimated from 1 span' + ); + await userEvent.hover( + screen.getByText((_, element) => element?.textContent === '1 span') + ); + expect( + await screen.findByText( + /You may not have enough span samples for a high accuracy estimation of your query./ + ) + ).toBeInTheDocument(); + expect( + await screen.findByText( + /You can try adjusting your query by increasing the chart's time interval./ + ) + ).toBeInTheDocument(); + }); + + it('loaded 1 with grouping', async () => { + render( + , + {wrapper: Wrapper} + ); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + 'Estimated for top 5 groups from 1 span' + ); + await userEvent.hover( + screen.getByText((_, element) => element?.textContent === '1 span') + ); + expect( + await screen.findByText( + /You may not have enough span samples for a high accuracy estimation of your query./ + ) + ).toBeInTheDocument(); + expect( + await screen.findByText( + /You can try adjusting your query by increasing the chart's time interval./ + ) + ).toBeInTheDocument(); + }); + + it('loaded 10', async () => { + render( + , + {wrapper: Wrapper} + ); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + 'Estimated from 10 spans' + ); + await userEvent.hover( + screen.getByText((_, element) => element?.textContent === '10 spans') + ); + expect( + await screen.findByText( + /You may not have enough span samples for a high accuracy estimation of your query./ + ) + ).toBeInTheDocument(); + expect( + await screen.findByText( + /You can try adjusting your query by increasing the chart's time interval./ + ) + ).toBeInTheDocument(); + }); + + it('loaded 10 with grouping', async () => { + render( + , + {wrapper: Wrapper} + ); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + 'Estimated for top 5 groups from 10 spans' + ); + await userEvent.hover( + screen.getByText((_, element) => element?.textContent === '10 spans') + ); + expect( + await screen.findByText( + /You may not have enough span samples for a high accuracy estimation of your query./ + ) + ).toBeInTheDocument(); + expect( + await screen.findByText( + /You can try adjusting your query by increasing the chart's time interval./ + ) + ).toBeInTheDocument(); + }); + }); + + describe('high confidence', () => { + it('loaded 1', () => { + render( + , + {wrapper: Wrapper} + ); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + 'Estimated from 1 span' + ); + }); + + it('loaded 1 with grouping', () => { + render( + , + {wrapper: Wrapper} + ); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + 'Estimated for top 5 groups from 1 span' + ); + }); + + it('loaded 10', () => { + render( + , + {wrapper: Wrapper} + ); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + 'Estimated from 10 spans' + ); + }); + + it('loaded 10 with grouping', () => { + render( + , + {wrapper: Wrapper} + ); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + 'Estimated for top 5 groups from 10 spans' + ); + }); + }); + }); + }); + + describe('with user query', () => { + describe('partial scan', () => { + describe('low confidence', () => { + it('loaded 1', async () => { + render( + , + {wrapper: Wrapper} + ); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + 'Estimated from 1 match after scanning 100 samples of 1k spans' + ); + await userEvent.hover( + screen.getByText((_, element) => element?.textContent === '1 match') + ); + expect( + await screen.findByText( + /You may not have enough span samples for a high accuracy estimation of your query./ + ) + ).toBeInTheDocument(); + expect( + await screen.findByText( + /You can try adjusting your query by narrowing the date range, removing filters or increasing the chart's time interval./ + ) + ).toBeInTheDocument(); + }); + + it('loaded 1 with grouping', async () => { + render( + , + {wrapper: Wrapper} + ); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + 'Estimated for top 5 groups from 1 match after scanning 100 samples of 1k spans' + ); + await userEvent.hover( + screen.getByText((_, element) => element?.textContent === '1 match') + ); + expect( + await screen.findByText( + /You may not have enough span samples for a high accuracy estimation of your query./ + ) + ).toBeInTheDocument(); + expect( + await screen.findByText( + /You can try adjusting your query by narrowing the date range, removing filters or increasing the chart's time interval./ + ) + ).toBeInTheDocument(); + }); + + it('loaded 10', async () => { + render( + , + {wrapper: Wrapper} + ); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + 'Estimated from 10 matches after scanning 100 samples of 1k spans' + ); + await userEvent.hover( + screen.getByText((_, element) => element?.textContent === '10 matches') + ); + expect( + await screen.findByText( + /You may not have enough span samples for a high accuracy estimation of your query./ + ) + ).toBeInTheDocument(); + expect( + await screen.findByText( + /You can try adjusting your query by narrowing the date range, removing filters or increasing the chart's time interval./ + ) + ).toBeInTheDocument(); + }); + + it('loaded 10 with grouping', async () => { + render( + , + {wrapper: Wrapper} + ); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + 'Estimated for top 5 groups from 10 matches after scanning 100 samples of 1k spans' + ); + await userEvent.hover( + screen.getByText((_, element) => element?.textContent === '10 matches') + ); + expect( + await screen.findByText( + /You may not have enough span samples for a high accuracy estimation of your query./ + ) + ).toBeInTheDocument(); + expect( + await screen.findByText( + /You can try adjusting your query by narrowing the date range, removing filters or increasing the chart's time interval./ + ) + ).toBeInTheDocument(); + }); + }); + + describe('high confidence', () => { + it('loaded 1', () => { + render( + , + {wrapper: Wrapper} + ); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + 'Estimated from 1 match after scanning 100 samples of 1k spans' + ); + }); + + it('loaded 1 with grouping', () => { + render( + , + {wrapper: Wrapper} + ); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + 'Estimated for top 5 groups from 1 match after scanning 100 samples of 1k spans' + ); + }); + + it('loaded 10', () => { + render( + , + {wrapper: Wrapper} + ); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + 'Estimated from 10 matches after scanning 100 samples of 1k spans' + ); + }); + + it('loaded 10 with grouping', () => { + render( + , + {wrapper: Wrapper} + ); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + 'Estimated for top 5 groups from 10 matches after scanning 100 samples of 1k spans' + ); + }); + }); + }); + + describe('full scan', () => { + describe('low confidence', () => { + it('loaded 1', async () => { + render( + , + {wrapper: Wrapper} + ); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + 'Estimated from 1 match of 1k spans' + ); + await userEvent.hover( + screen.getByText((_, element) => element?.textContent === '1 match') + ); + expect( + await screen.findByText( + /You may not have enough span samples for a high accuracy estimation of your query./ + ) + ).toBeInTheDocument(); + expect( + await screen.findByText( + /You can try adjusting your query by removing filters or increasing the chart's time interval./ + ) + ).toBeInTheDocument(); + }); + + it('loaded 1 with grouping', async () => { + render( + , + {wrapper: Wrapper} + ); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + 'Estimated for top 5 groups from 1 match of 1k spans' + ); + await userEvent.hover( + screen.getByText((_, element) => element?.textContent === '1 match') + ); + expect( + await screen.findByText( + /You may not have enough span samples for a high accuracy estimation of your query./ + ) + ).toBeInTheDocument(); + expect( + await screen.findByText( + /You can try adjusting your query by removing filters or increasing the chart's time interval./ + ) + ).toBeInTheDocument(); + }); + + it('loaded 10', async () => { + render( + , + {wrapper: Wrapper} + ); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + 'Estimated from 10 matches of 1k spans' + ); + await userEvent.hover( + screen.getByText((_, element) => element?.textContent === '10 matches') + ); + expect( + await screen.findByText( + /You may not have enough span samples for a high accuracy estimation of your query./ + ) + ).toBeInTheDocument(); + expect( + await screen.findByText( + /You can try adjusting your query by removing filters or increasing the chart's time interval./ + ) + ).toBeInTheDocument(); + }); + + it('loaded 10 with grouping', async () => { + render( + , + {wrapper: Wrapper} + ); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + 'Estimated for top 5 groups from 10 matches of 1k spans' + ); + await userEvent.hover( + screen.getByText((_, element) => element?.textContent === '10 matches') + ); + expect( + await screen.findByText( + /You may not have enough span samples for a high accuracy estimation of your query./ + ) + ).toBeInTheDocument(); + expect( + await screen.findByText( + /You can try adjusting your query by removing filters or increasing the chart's time interval./ + ) + ).toBeInTheDocument(); + }); + }); + + describe('high confidence', () => { + it('loaded 1', () => { + render( + , + {wrapper: Wrapper} + ); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + 'Estimated from 1 match of 1k spans' + ); + }); + + it('loaded 1 with grouping', () => { + render( + , + {wrapper: Wrapper} + ); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + 'Estimated for top 5 groups from 1 match of 1k spans' + ); + }); + + it('loaded 10', () => { + render( + , + {wrapper: Wrapper} + ); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + 'Estimated from 10 matches of 1k spans' + ); + }); + + it('loaded 10 with grouping', () => { + render( + , + {wrapper: Wrapper} + ); + expect(screen.getByTestId('wrapper')).toHaveTextContent( + 'Estimated for top 5 groups from 10 matches of 1k spans' + ); + }); + }); + }); }); }); }); diff --git a/static/app/views/explore/spans/charts/confidenceFooter.tsx b/static/app/views/explore/spans/charts/confidenceFooter.tsx index 96db5f43123660..e32d1531dee45c 100644 --- a/static/app/views/explore/spans/charts/confidenceFooter.tsx +++ b/static/app/views/explore/spans/charts/confidenceFooter.tsx @@ -6,7 +6,6 @@ import Count from 'sentry/components/count'; import {t, tct} from 'sentry/locale'; import type {Confidence} from 'sentry/types/organization'; import {defined} from 'sentry/utils'; -import usePrevious from 'sentry/utils/usePrevious'; import { Placeholder, WarningIcon, @@ -26,13 +25,11 @@ type Props = { }; export function ConfidenceFooter(props: Props) { - const previousProps = usePrevious(props, props.isLoading); - return ( - {confidenceMessage(props.isLoading ? previousProps : props)} - ); + return {confidenceMessage(props)}; } function confidenceMessage({ + dataScanned, extrapolate, rawSpanCounts, sampleCount, @@ -42,135 +39,270 @@ function confidenceMessage({ isLoading, userQuery, }: Props) { - const isTopN = defined(topEvents) && topEvents > 1; - - if (!defined(sampleCount) || isLoading) { + if (isLoading || !defined(sampleCount)) { return ; } - if ( - // Extrapolation disabled, so don't mention extrapolation. - (defined(extrapolate) && !extrapolate) || - // High confidence without user query means we're in a default query. - // We check for high confidence here because we still want to show the - // tooltip here if it's low confidence - (confidence === 'high' && !userQuery) - ) { - return isTopN - ? tct('Span count for top [topEvents] groups: [matchingSpansCount]', { - topEvents, - matchingSpansCount: , - }) - : tct('Span count: [matchingSpansCount]', { - matchingSpansCount: , - }); - } - + const isTopN = defined(topEvents) && topEvents > 1; const noSampling = defined(isSampled) && !isSampled; - const lowAccuracyFullSampleCount = <_LowAccuracyFullTooltip noSampling={noSampling} />; + + const maybeWarning = + confidence === 'low' ? tct('[warning] ', {warning: }) : null; + const maybeTooltip = + confidence === 'low' ? ( + <_LowAccuracyFullTooltip + noSampling={noSampling} + dataScanned={dataScanned} + userQuery={userQuery} + /> + ) : null; // The multi query mode does not fetch the raw span counts // so make sure to have a backup when this happens. if (!defined(rawSpanCounts)) { const matchingSpansCount = - sampleCount > 1 - ? t('%s spans', ) - : t('%s span', ); + sampleCount === 1 + ? t('%s span', ) + : t('%s spans', ); + + if (isTopN) { + return tct( + '[maybeWarning]Estimated for top [topEvents] groups from [maybeTooltip:[matchingSpansCount]]', + { + maybeWarning, + topEvents, + maybeTooltip, + matchingSpansCount, + } + ); + } + + return tct('[maybeWarning]Estimated from [maybeTooltip:[matchingSpansCount]]', { + maybeWarning, + maybeTooltip, + matchingSpansCount, + }); + } + + if ( + // Extrapolation disabled, so don't mention estimations. + (defined(extrapolate) && !extrapolate) || + // No sampling happened, so don't mention estimations. + noSampling + ) { + if (!userQuery) { + const matchingSpansCount = + sampleCount > 1 + ? t('%s spans', ) + : t('%s span', ); - if (confidence === 'high') { if (isTopN) { - return tct('Extrapolated from [matchingSpansCount] for top [topEvents] groups', { + return tct('[matchingSpansCount] for top [topEvents] groups', { topEvents, matchingSpansCount, }); } - return tct('Extrapolated from [matchingSpansCount]', { + return matchingSpansCount; + } + + const matchingSpansCount = + sampleCount > 1 + ? t('%s matches', ) + : t('%s match', ); + + const totalSpansCount = rawSpanCounts.highAccuracy.count ? ( + rawSpanCounts.highAccuracy.count > 1 ? ( + t('%s spans', ) + ) : ( + t('%s span', ) + ) + ) : ( + + ); + + if (isTopN) { + return tct('[matchingSpansCount] of [totalSpansCount] for top [topEvents] groups', { matchingSpansCount, + totalSpansCount, + topEvents, }); } + return tct('[matchingSpansCount] of [totalSpansCount]', { + matchingSpansCount, + totalSpansCount, + }); + } + + // no user query means it's showing the total number of spans scanned + // so no need to mention how many matched + if (!userQuery) { + // partial scans means that we didnt scan all the data so it's useful + // to mention the total number of spans available + if (dataScanned === 'partial') { + const matchingSpansCount = + sampleCount > 1 + ? t('%s samples', ) + : t('%s sample', ); + + const totalSpansCount = rawSpanCounts.highAccuracy.count ? ( + rawSpanCounts.highAccuracy.count > 1 ? ( + t('%s spans', ) + ) : ( + t('%s span', ) + ) + ) : ( + + ); + + if (isTopN) { + return tct( + '[maybeWarning]Estimated for top [topEvents] groups from [maybeTooltip:[matchingSpansCount]] of [totalSpansCount]', + { + maybeWarning, + topEvents, + maybeTooltip, + matchingSpansCount, + totalSpansCount, + } + ); + } + + return tct( + '[maybeWarning]Estimated from [maybeTooltip:[matchingSpansCount]] of [totalSpansCount]', + { + maybeWarning, + maybeTooltip, + matchingSpansCount, + totalSpansCount, + } + ); + } + + // otherwise, a full scan was done + // full scan means we scanned all the data available so no need to repeat that information twice + + const matchingSpansCount = + sampleCount > 1 + ? t('%s spans', ) + : t('%s span', ); + if (isTopN) { return tct( - 'Extrapolated from [tooltip:[matchingSpansCount]] for top [topEvents] groups', + '[maybeWarning]Estimated for top [topEvents] groups from [maybeTooltip:[matchingSpansCount]]', { + maybeWarning, + maybeTooltip, topEvents, matchingSpansCount, - tooltip: lowAccuracyFullSampleCount, } ); } - return tct('Extrapolated from [tooltip:[matchingSpansCount]]', { + return tct('[maybeWarning]Estimated from [maybeTooltip:[matchingSpansCount]]', { + maybeWarning, + maybeTooltip, matchingSpansCount, - tooltip: lowAccuracyFullSampleCount, }); } - const matchingSpansCount = - sampleCount > 1 - ? t('%s matches', ) - : t('%s match', ); + // otherwise, a user query was specified + // with a user query, it means we should tell the user how many of the scanned spans + // matched the user query - const downSampledSpansCount = rawSpanCounts.normal.count ? ( - rawSpanCounts.normal.count > 1 ? ( - t('%s samples', ) + // partial scans means that we didnt scan all the data so it's useful + // to mention the total number of spans available + if (dataScanned === 'partial') { + const matchingSpansCount = + sampleCount > 1 + ? t('%s matches', ) + : t('%s match', ); + + const scannedSpansCount = rawSpanCounts.normal.count ? ( + rawSpanCounts.normal.count > 1 ? ( + t('%s samples', ) + ) : ( + t('%s sample', ) + ) ) : ( - t('%s sample', ) - ) - ) : ( - - ); - const allSpansCount = rawSpanCounts.highAccuracy.count ? ( - rawSpanCounts.highAccuracy.count > 1 ? ( - t('%s spans', ) + + ); + + const totalSpansCount = rawSpanCounts.highAccuracy.count ? ( + rawSpanCounts.highAccuracy.count > 1 ? ( + t('%s spans', ) + ) : ( + t('%s span', ) + ) ) : ( - t('%s span', ) - ) - ) : ( - - ); + + ); - if (confidence === 'high') { if (isTopN) { return tct( - 'Extrapolated from [matchingSpansCount] for top [topEvents] groups in [allSpansCount]', + '[maybeWarning]Estimated for top [topEvents] groups from [maybeTooltip:[matchingSpansCount]] after scanning [scannedSpansCount] of [totalSpansCount]', { - topEvents, + maybeWarning, + maybeTooltip, matchingSpansCount, - allSpansCount, + scannedSpansCount, + totalSpansCount, + topEvents, } ); } - return tct('Extrapolated from [matchingSpansCount] in [allSpansCount]', { - matchingSpansCount, - allSpansCount, - }); + return tct( + '[maybeWarning]Estimated from [maybeTooltip:[matchingSpansCount]] after scanning [scannedSpansCount] of [totalSpansCount]', + { + maybeWarning, + maybeTooltip, + matchingSpansCount, + scannedSpansCount, + totalSpansCount, + } + ); } + // otherwise, a full scan was done + // full scan means we scanned all the data available so no need to repeat that information twice + + const matchingSpansCount = + sampleCount > 1 + ? t('%s matches', ) + : t('%s match', ); + + const totalSpansCount = rawSpanCounts.highAccuracy.count ? ( + rawSpanCounts.highAccuracy.count > 1 ? ( + t('%s spans', ) + ) : ( + t('%s span', ) + ) + ) : ( + + ); + if (isTopN) { return tct( - '[warning] Extrapolated from [matchingSpansCount] for top [topEvents] groups after scanning [tooltip:[downSampledSpansCount] of [allSpansCount]]', + '[maybeWarning]Estimated for top [topEvents] groups from [maybeTooltip:[matchingSpansCount]] of [totalSpansCount]', { - warning: , + maybeWarning, topEvents, + maybeTooltip, matchingSpansCount, - downSampledSpansCount, - allSpansCount, - tooltip: lowAccuracyFullSampleCount, + totalSpansCount, } ); } return tct( - '[warning] Extrapolated from [matchingSpansCount] after scanning [tooltip:[downSampledSpansCount] of [allSpansCount]]', + '[maybeWarning]Estimated from [maybeTooltip:[matchingSpansCount]] of [totalSpansCount]', { - warning: , + maybeWarning, + maybeTooltip, matchingSpansCount, - downSampledSpansCount, - allSpansCount, - tooltip: lowAccuracyFullSampleCount, + totalSpansCount, } ); } @@ -178,24 +310,42 @@ function confidenceMessage({ function _LowAccuracyFullTooltip({ noSampling, children, + dataScanned, + userQuery, }: { noSampling: boolean; children?: React.ReactNode; + dataScanned?: 'full' | 'partial'; + userQuery?: string; }) { return ( {t( - 'You may not have enough span samples for a high accuracy extrapolation of your query.' + 'You may not have enough span samples for a high accuracy estimation of your query.' )}
- {t( - "You can try adjusting your query by narrowing the date range, removing filters or increasing the chart's time interval." - )} +
+ {dataScanned === 'partial' && userQuery + ? t( + "You can try adjusting your query by narrowing the date range, removing filters or increasing the chart's time interval." + ) + : dataScanned === 'partial' + ? t( + "You can try adjusting your query by narrowing the date range or increasing the chart's time interval." + ) + : userQuery + ? t( + "You can try adjusting your query by removing filters or increasing the chart's time interval." + ) + : t( + "You can try adjusting your query by increasing the chart's time interval." + )} {/* Do not show if no sampling happened to the data points in the series as they are already at 100% sampling */} {!noSampling && ( +

{t( 'You can also increase your sampling rates to get more samples and accurate trends.' @@ -204,7 +354,7 @@ function _LowAccuracyFullTooltip({ )} } - maxWidth={270} + maxWidth={300} showUnderline > {children}