Skip to content

Commit 45be51f

Browse files
Zylphrexandrewshie-sentry
authored andcommitted
feat(logs): Add confidence footer for logs (#96696)
This adds a custom confidence footer for logs. Closes LOGS-211
1 parent 46a0b75 commit 45be51f

File tree

5 files changed

+185
-4
lines changed

5 files changed

+185
-4
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import styled from '@emotion/styled';
2+
3+
import usePrevious from 'sentry/utils/usePrevious';
4+
5+
export function usePreviouslyLoaded<T>(current: T, isLoading: boolean): T {
6+
const previous = usePrevious(current, isLoading);
7+
return isLoading ? previous : current;
8+
}
9+
10+
export const Container = styled('span')`
11+
color: ${p => p.theme.subText};
12+
font-size: ${p => p.theme.fontSize.sm};
13+
`;

static/app/views/explore/components/chart/types.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@ export interface ChartInfo {
1414
isSampled?: boolean | null;
1515
sampleCount?: number;
1616
samplingMode?: SamplingMode;
17+
topEvents?: number;
1718
}
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import {Tooltip} from 'sentry/components/core/tooltip';
2+
import Count from 'sentry/components/count';
3+
import {t, tct} from 'sentry/locale';
4+
import type {Confidence} from 'sentry/types/organization';
5+
import {defined} from 'sentry/utils';
6+
import {
7+
Container,
8+
usePreviouslyLoaded,
9+
} from 'sentry/views/explore/components/chart/chartFooter';
10+
import type {ChartInfo} from 'sentry/views/explore/components/chart/types';
11+
12+
interface ConfidenceFooterProps {
13+
chartInfo: ChartInfo;
14+
isLoading: boolean;
15+
}
16+
17+
export function ConfidenceFooter({
18+
chartInfo: currentChartInfo,
19+
isLoading,
20+
}: ConfidenceFooterProps) {
21+
const chartInfo = usePreviouslyLoaded(currentChartInfo, isLoading);
22+
return (
23+
<Container>
24+
<ConfidenceMessage
25+
confidence={chartInfo.confidence}
26+
dataScanned={chartInfo.dataScanned}
27+
isSampled={chartInfo.isSampled}
28+
sampleCount={chartInfo.sampleCount}
29+
topEvents={chartInfo.topEvents}
30+
/>
31+
</Container>
32+
);
33+
}
34+
35+
interface ConfidenceMessageProps {
36+
confidence?: Confidence;
37+
dataScanned?: 'full' | 'partial';
38+
isSampled?: boolean | null;
39+
sampleCount?: number;
40+
topEvents?: number;
41+
}
42+
43+
function ConfidenceMessage({
44+
sampleCount,
45+
dataScanned,
46+
confidence,
47+
topEvents,
48+
isSampled,
49+
}: ConfidenceMessageProps) {
50+
const isTopN = defined(topEvents) && topEvents > 1;
51+
52+
if (!defined(sampleCount)) {
53+
return isTopN
54+
? t('* Top %s groups extrapolated based on \u2026', topEvents)
55+
: t('* Extrapolated based on \u2026');
56+
}
57+
58+
const noSampling = defined(isSampled) && !isSampled;
59+
const sampleCountComponent = <Count value={sampleCount} />;
60+
61+
if (dataScanned === 'full') {
62+
// For logs, if the full data was scanned, we can assume that no
63+
// extrapolation happened and we should remove mentions of extrapolation.
64+
if (isTopN) {
65+
return tct('Top [topEvents] groups based on [sampleCountComponent] logs', {
66+
topEvents,
67+
sampleCountComponent,
68+
});
69+
}
70+
71+
return tct('Based on [sampleCountComponent] logs', {
72+
sampleCountComponent,
73+
});
74+
}
75+
76+
if (confidence === 'low') {
77+
const lowAccuracyFullSampleCount = <LowAccuracyFullTooltip noSampling={noSampling} />;
78+
79+
if (isTopN) {
80+
return tct(
81+
'Top [topEvents] groups extrapolated based on [tooltip:[sampleCountComponent] logs]',
82+
{
83+
topEvents,
84+
tooltip: lowAccuracyFullSampleCount,
85+
sampleCountComponent,
86+
}
87+
);
88+
}
89+
90+
return tct('Extrapolated based on [tooltip:[sampleCountComponent] logs]', {
91+
tooltip: lowAccuracyFullSampleCount,
92+
sampleCountComponent,
93+
});
94+
}
95+
96+
if (isTopN) {
97+
return tct(
98+
'Top [topEvents] groups extrapolated based on [sampleCountComponent] logs',
99+
{
100+
topEvents,
101+
sampleCountComponent,
102+
}
103+
);
104+
}
105+
106+
return tct('Extrapolated based on [sampleCountComponent] logs', {
107+
sampleCountComponent,
108+
});
109+
}
110+
111+
function LowAccuracyFullTooltip({
112+
noSampling,
113+
children,
114+
}: {
115+
noSampling: boolean;
116+
children?: React.ReactNode;
117+
}) {
118+
return (
119+
<Tooltip
120+
title={
121+
<div>
122+
{t(
123+
'You may not have enough logs for a high accuracy extrapolation of your query.'
124+
)}
125+
<br />
126+
<br />
127+
{t(
128+
"You can try adjusting your query by narrowing the date range, removing filters or increasing the chart's time interval."
129+
)}
130+
<br />
131+
<br />
132+
{t(
133+
'You can also increase your sampling rates to get more samples and accurate trends.'
134+
)}
135+
</div>
136+
}
137+
disabled={noSampling}
138+
maxWidth={270}
139+
showUnderline
140+
>
141+
{children}
142+
</Tooltip>
143+
);
144+
}

static/app/views/explore/logs/logsGraph.tsx

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,25 @@ import {CompactSelect} from 'sentry/components/core/compactSelect';
44
import {Tooltip} from 'sentry/components/core/tooltip';
55
import {IconClock, IconGraph} from 'sentry/icons';
66
import {t} from 'sentry/locale';
7+
import {defined} from 'sentry/utils';
8+
import {determineSeriesSampleCountAndIsSampled} from 'sentry/views/alerts/rules/metric/utils/determineSeriesSampleCount';
79
import {Widget} from 'sentry/views/dashboards/widgets/widget/widget';
810
import {ChartVisualization} from 'sentry/views/explore/components/chart/chartVisualization';
9-
import {useLogsAggregate} from 'sentry/views/explore/contexts/logs/logsPageParams';
11+
import {
12+
useLogsAggregate,
13+
useLogsGroupBy,
14+
} from 'sentry/views/explore/contexts/logs/logsPageParams';
1015
import {
1116
ChartIntervalUnspecifiedStrategy,
1217
useChartInterval,
1318
} from 'sentry/views/explore/hooks/useChartInterval';
19+
import {TOP_EVENTS_LIMIT} from 'sentry/views/explore/hooks/useTopEvents';
20+
import {ConfidenceFooter} from 'sentry/views/explore/logs/confidenceFooter';
1421
import {EXPLORE_CHART_TYPE_OPTIONS} from 'sentry/views/explore/spans/charts';
15-
import {prettifyAggregation} from 'sentry/views/explore/utils';
22+
import {
23+
combineConfidenceForSeries,
24+
prettifyAggregation,
25+
} from 'sentry/views/explore/utils';
1626
import {ChartType} from 'sentry/views/insights/common/components/chart';
1727
import type {useSortedTimeSeries} from 'sentry/views/insights/common/queries/useSortedTimeSeries';
1828

@@ -22,6 +32,7 @@ interface LogsGraphProps {
2232

2333
export function LogsGraph({timeseriesResult}: LogsGraphProps) {
2434
const aggregate = useLogsAggregate();
35+
const groupBy = useLogsGroupBy();
2536

2637
const [chartType, setChartType] = useState<ChartType>(ChartType.BAR);
2738
const [interval, setInterval, intervalOptions] = useChartInterval({
@@ -30,13 +41,21 @@ export function LogsGraph({timeseriesResult}: LogsGraphProps) {
3041

3142
const chartInfo = useMemo(() => {
3243
const series = timeseriesResult.data[aggregate] ?? [];
44+
const isTopEvents = defined(groupBy);
45+
const samplingMeta = determineSeriesSampleCountAndIsSampled(series, isTopEvents);
3346
return {
3447
chartType,
3548
series,
3649
timeseriesResult,
3750
yAxis: aggregate,
51+
confidence: combineConfidenceForSeries(series),
52+
dataScanned: samplingMeta.dataScanned,
53+
isSampled: samplingMeta.isSampled,
54+
sampleCount: samplingMeta.sampleCount,
55+
samplingMode: undefined,
56+
topEvents: isTopEvents ? TOP_EVENTS_LIMIT : undefined,
3857
};
39-
}, [chartType, timeseriesResult, aggregate]);
58+
}, [chartType, timeseriesResult, aggregate, groupBy]);
4059

4160
const Title = (
4261
<Widget.WidgetTitle title={prettifyAggregation(aggregate) ?? aggregate} />
@@ -83,6 +102,9 @@ export function LogsGraph({timeseriesResult}: LogsGraphProps) {
83102
Title={Title}
84103
Actions={Actions}
85104
Visualization={<ChartVisualization chartInfo={chartInfo} />}
105+
Footer={
106+
<ConfidenceFooter chartInfo={chartInfo} isLoading={timeseriesResult.isLoading} />
107+
}
86108
revealActions="always"
87109
/>
88110
);

static/app/views/explore/logs/logsTab.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ import {
6767
ChartIntervalUnspecifiedStrategy,
6868
useChartInterval,
6969
} from 'sentry/views/explore/hooks/useChartInterval';
70+
import {TOP_EVENTS_LIMIT} from 'sentry/views/explore/hooks/useTopEvents';
7071
import {
7172
HiddenColumnEditorLogFields,
7273
HiddenLogSearchFields,
@@ -171,7 +172,7 @@ export function LogsTabContent({
171172
yAxis: [aggregate],
172173
interval,
173174
fields: [...(groupBy ? [groupBy] : []), aggregate],
174-
topEvents: groupBy?.length ? 5 : undefined,
175+
topEvents: groupBy ? TOP_EVENTS_LIMIT : undefined,
175176
orderby,
176177
},
177178
'explore.ourlogs.main-chart',

0 commit comments

Comments
 (0)