Skip to content

Commit 0f1c61d

Browse files
authored
ref(tracemetrics): Add more analytics events for metrics (#102824)
This adds two primary events for when the users land on the page. Closes LOGS-473
1 parent 6211629 commit 0f1c61d

File tree

5 files changed

+353
-2
lines changed

5 files changed

+353
-2
lines changed

static/app/utils/analytics/metricsAnalyticsEvent.tsx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,33 @@ import type {Organization} from 'sentry/types/organization';
22
import type {PlatformKey} from 'sentry/types/project';
33

44
export type MetricsAnalyticsEventParameters = {
5+
'metrics.explorer.metadata': {
6+
datetime_selection: string;
7+
environment_count: number;
8+
has_exceeded_performance_usage_limit: boolean | null;
9+
interval: string;
10+
metric_panels_with_filters_count: number;
11+
metric_panels_with_group_bys_count: number;
12+
metric_queries_count: number;
13+
project_count: number;
14+
};
15+
'metrics.explorer.panel.metadata': {
16+
columns: readonly string[];
17+
columns_count: number;
18+
confidences: string[];
19+
dataScanned: string;
20+
dataset: string;
21+
empty_buckets_percentage: number[];
22+
interval: string;
23+
query_status: 'success' | 'error' | 'pending';
24+
sample_counts: number[];
25+
table_result_length: number;
26+
table_result_missing_root: number;
27+
table_result_mode: 'metric samples' | 'aggregates';
28+
table_result_sort: string[];
29+
user_queries: string;
30+
user_queries_count: number;
31+
};
532
'metrics.explorer.setup_button_clicked': {
633
organization: Organization;
734
platform: PlatformKey | 'unknown';
@@ -37,6 +64,8 @@ export type MetricsAnalyticsEventParameters = {
3764
type MetricsAnalyticsEventKey = keyof MetricsAnalyticsEventParameters;
3865

3966
export const metricsAnalyticsEventMap: Record<MetricsAnalyticsEventKey, string | null> = {
67+
'metrics.explorer.metadata': 'Metric Explorer Pageload Metadata',
68+
'metrics.explorer.panel.metadata': 'Metric Explorer Panel Metadata',
4069
'metrics.explorer.setup_button_clicked': 'Metrics Setup Button Clicked',
4170
'metrics.nav.rendered': 'Metrics Nav Rendered',
4271
'metrics.onboarding': 'Metrics Explore Empty State (Onboarding)',

static/app/views/explore/hooks/useAnalytics.tsx

Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import type {Sort} from 'sentry/utils/discover/fields';
99
import {DiscoverDatasets} from 'sentry/utils/discover/types';
1010
import {MutableSearch} from 'sentry/utils/tokenizeSearch';
1111
import useOrganization from 'sentry/utils/useOrganization';
12+
import usePageFilters from 'sentry/utils/usePageFilters';
1213
import type {TimeSeries} from 'sentry/views/dashboards/widgets/common/types';
1314
import {useLogsAutoRefreshEnabled} from 'sentry/views/explore/contexts/logs/logsAutoRefreshContext';
1415
import {Mode} from 'sentry/views/explore/contexts/pageParamsContext/mode';
@@ -19,6 +20,8 @@ import type {TracesTableResult} from 'sentry/views/explore/hooks/useExploreTrace
1920
import {useTopEvents} from 'sentry/views/explore/hooks/useTopEvents';
2021
import {type useLogsAggregatesTable} from 'sentry/views/explore/logs/useLogsAggregatesTable';
2122
import type {UseInfiniteLogsQueryResult} from 'sentry/views/explore/logs/useLogsQuery';
23+
import {useMetricAggregatesTable} from 'sentry/views/explore/metrics/hooks/useMetricAggregatesTable';
24+
import {useMetricSamplesTable} from 'sentry/views/explore/metrics/hooks/useMetricSamplesTable';
2225
import type {ReadableExploreQueryParts} from 'sentry/views/explore/multiQueryMode/locationUtils';
2326
import {
2427
useQueryParamsFields,
@@ -27,6 +30,7 @@ import {
2730
useQueryParamsTitle,
2831
useQueryParamsVisualizes,
2932
} from 'sentry/views/explore/queryParams/context';
33+
import type {ReadableQueryParams} from 'sentry/views/explore/queryParams/readableQueryParams';
3034
import {Visualize} from 'sentry/views/explore/queryParams/visualize';
3135
import {useSpansDataset} from 'sentry/views/explore/spans/spansQueryParams';
3236
import {
@@ -685,6 +689,260 @@ function computeEmptyBuckets(
685689
});
686690
}
687691

692+
export function useMetricsPanelAnalytics({
693+
interval,
694+
isTopN,
695+
metricAggregatesTableResult,
696+
metricSamplesTableResult,
697+
metricTimeseriesResult,
698+
mode,
699+
yAxis,
700+
sortBys,
701+
aggregateSortBys,
702+
}: {
703+
aggregateSortBys: readonly Sort[];
704+
interval: string;
705+
isTopN: boolean;
706+
metricAggregatesTableResult: ReturnType<typeof useMetricAggregatesTable>;
707+
metricSamplesTableResult: ReturnType<typeof useMetricSamplesTable>;
708+
metricTimeseriesResult: ReturnType<typeof useSortedTimeSeries>;
709+
mode: Mode;
710+
sortBys: readonly Sort[];
711+
yAxis: string;
712+
}) {
713+
const organization = useOrganization();
714+
715+
const dataset = DiscoverDatasets.METRICS;
716+
const dataScanned = metricSamplesTableResult.result.meta?.dataScanned ?? '';
717+
const search = useQueryParamsSearch();
718+
const query = useQueryParamsQuery();
719+
const fields = useQueryParamsFields();
720+
721+
const tableError =
722+
mode === Mode.AGGREGATE
723+
? (metricAggregatesTableResult.result.error?.message ?? '')
724+
: (metricSamplesTableResult.error?.message ?? '');
725+
const query_status = tableError ? 'error' : 'success';
726+
727+
const aggregatesResultLengthBox = useBox(
728+
metricAggregatesTableResult.result.data?.length || 0
729+
);
730+
const resultLengthBox = useBox(metricSamplesTableResult.result.data?.length || 0);
731+
const fieldsBox = useBox(fields);
732+
const yAxesBox = useBox([yAxis]);
733+
const sortBysBox = useBox(sortBys.map(formatSort));
734+
const aggregateSortBysBox = useBox(aggregateSortBys.map(formatSort));
735+
736+
const timeseriesData = useBox(metricTimeseriesResult.data);
737+
738+
useEffect(() => {
739+
if (mode !== Mode.SAMPLES) {
740+
return;
741+
}
742+
743+
if (metricSamplesTableResult.result.isFetching) {
744+
return;
745+
}
746+
747+
trackAnalytics('metrics.explorer.panel.metadata', {
748+
organization,
749+
dataset,
750+
dataScanned,
751+
columns: fieldsBox.current,
752+
columns_count: fieldsBox.current.length,
753+
confidences: computeConfidence(yAxesBox.current, timeseriesData.current),
754+
empty_buckets_percentage: computeEmptyBuckets(
755+
yAxesBox.current,
756+
timeseriesData.current
757+
),
758+
interval,
759+
query_status,
760+
sample_counts: computeVisualizeSampleTotals(
761+
yAxesBox.current,
762+
timeseriesData.current,
763+
isTopN
764+
),
765+
table_result_length: resultLengthBox.current,
766+
table_result_missing_root: 0,
767+
table_result_mode: 'metric samples',
768+
table_result_sort: sortBysBox.current,
769+
user_queries: search.formatString(),
770+
user_queries_count: search.tokens.length,
771+
});
772+
773+
info(
774+
fmt`metric.explorer.panel.metadata:
775+
organization: ${organization.slug}
776+
dataScanned: ${dataScanned}
777+
dataset: ${dataset}
778+
query: ${query}
779+
fields: ${fieldsBox.current}
780+
query_status: ${query_status}
781+
result_length: ${String(resultLengthBox.current)}
782+
user_queries: ${search.formatString()}
783+
user_queries_count: ${String(search.tokens.length)}
784+
`,
785+
{isAnalytics: true}
786+
);
787+
}, [
788+
organization,
789+
dataset,
790+
dataScanned,
791+
query,
792+
fieldsBox,
793+
interval,
794+
query_status,
795+
isTopN,
796+
metricSamplesTableResult.result.isFetching,
797+
search,
798+
timeseriesData,
799+
mode,
800+
resultLengthBox,
801+
sortBysBox,
802+
yAxesBox,
803+
]);
804+
805+
useEffect(() => {
806+
if (mode !== Mode.AGGREGATE) {
807+
return;
808+
}
809+
810+
if (metricAggregatesTableResult.result.isPending) {
811+
return;
812+
}
813+
814+
trackAnalytics('metrics.explorer.panel.metadata', {
815+
organization,
816+
dataset,
817+
dataScanned,
818+
columns: fieldsBox.current,
819+
columns_count: fieldsBox.current.length,
820+
confidences: computeConfidence([yAxis], timeseriesData.current),
821+
empty_buckets_percentage: computeEmptyBuckets([yAxis], timeseriesData.current),
822+
interval,
823+
query_status,
824+
sample_counts: computeVisualizeSampleTotals(
825+
[yAxis],
826+
timeseriesData.current,
827+
isTopN
828+
),
829+
table_result_length: aggregatesResultLengthBox.current,
830+
table_result_missing_root: 0,
831+
table_result_mode: 'aggregates',
832+
table_result_sort: aggregateSortBysBox.current,
833+
user_queries: search.formatString(),
834+
user_queries_count: search.tokens.length,
835+
});
836+
837+
info(
838+
fmt`metric.explorer.panel.metadata:
839+
organization: ${organization.slug}
840+
dataScanned: ${dataScanned}
841+
dataset: ${dataset}
842+
query: ${query}
843+
fields: ${fieldsBox.current}
844+
query_status: ${query_status}
845+
result_length: ${String(aggregatesResultLengthBox.current)}
846+
user_queries: ${search.formatString()}
847+
user_queries_count: ${String(search.tokens.length)}
848+
`,
849+
{isAnalytics: true}
850+
);
851+
}, [
852+
aggregateSortBysBox,
853+
aggregatesResultLengthBox,
854+
dataScanned,
855+
dataset,
856+
fieldsBox,
857+
interval,
858+
isTopN,
859+
metricAggregatesTableResult.result.isPending,
860+
timeseriesData,
861+
mode,
862+
organization,
863+
query,
864+
query_status,
865+
search,
866+
yAxis,
867+
]);
868+
}
869+
870+
export function useMetricsAnalytics({
871+
interval,
872+
metricQueries,
873+
}: {
874+
interval: string;
875+
metricQueries: Array<{queryParams: ReadableQueryParams}>;
876+
}) {
877+
const organization = useOrganization();
878+
const {selection} = usePageFilters();
879+
880+
const {
881+
data: {hasExceededPerformanceUsageLimit},
882+
isLoading: isLoadingSubscriptionDetails,
883+
} = usePerformanceSubscriptionDetails({traceItemDataset: 'default'});
884+
885+
const metricQueriesCount = useBox(metricQueries.length);
886+
const metricPanelsWithGroupBysCount = useBox(
887+
metricQueries.filter(mq =>
888+
mq.queryParams.groupBys.some((gb: string) => gb.trim().length > 0)
889+
).length
890+
);
891+
const metricPanelsWithFiltersCount = useBox(
892+
metricQueries.filter(mq => mq.queryParams.query.trim().length > 0).length
893+
);
894+
895+
useEffect(() => {
896+
if (isLoadingSubscriptionDetails) {
897+
return;
898+
}
899+
900+
const datetimeSelection = `${selection.datetime.start || ''}-${selection.datetime.end || ''}-${selection.datetime.period || ''}`;
901+
const projectCount = selection.projects.length;
902+
const environmentCount = selection.environments.length;
903+
904+
trackAnalytics('metrics.explorer.metadata', {
905+
organization,
906+
datetime_selection: datetimeSelection,
907+
environment_count: environmentCount,
908+
has_exceeded_performance_usage_limit: hasExceededPerformanceUsageLimit,
909+
interval,
910+
metric_panels_with_filters_count: metricPanelsWithFiltersCount.current,
911+
metric_panels_with_group_bys_count: metricPanelsWithGroupBysCount.current,
912+
metric_queries_count: metricQueriesCount.current,
913+
project_count: projectCount,
914+
});
915+
916+
info(
917+
fmt`metrics.explorer.metadata:
918+
organization: ${organization.slug}
919+
datetime_selection: ${datetimeSelection}
920+
environment_count: ${String(environmentCount)}
921+
interval: ${interval}
922+
metric_queries_count: ${String(metricQueriesCount.current)}
923+
metric_panels_with_group_bys_count: ${String(metricPanelsWithGroupBysCount.current)}
924+
metric_panels_with_filters_count: ${String(metricPanelsWithFiltersCount.current)}
925+
project_count: ${String(projectCount)}
926+
has_exceeded_performance_usage_limit: ${String(hasExceededPerformanceUsageLimit)}
927+
`,
928+
{isAnalytics: true}
929+
);
930+
}, [
931+
hasExceededPerformanceUsageLimit,
932+
interval,
933+
isLoadingSubscriptionDetails,
934+
metricQueriesCount,
935+
metricPanelsWithGroupBysCount,
936+
metricPanelsWithFiltersCount,
937+
organization,
938+
selection.datetime.end,
939+
selection.datetime.period,
940+
selection.datetime.start,
941+
selection.environments.length,
942+
selection.projects.length,
943+
]);
944+
}
945+
688946
function useBox<T>(value: T): RefObject<T> {
689947
const box = useRef(value);
690948
box.current = value;

static/app/views/explore/metrics/metricPanel/index.tsx

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,26 @@ import {useState} from 'react';
22

33
import Panel from 'sentry/components/panels/panel';
44
import PanelBody from 'sentry/components/panels/panelBody';
5+
import {useMetricsPanelAnalytics} from 'sentry/views/explore/hooks/useAnalytics';
6+
import {useChartInterval} from 'sentry/views/explore/hooks/useChartInterval';
7+
import {useTopEvents} from 'sentry/views/explore/hooks/useTopEvents';
8+
import {TraceSamplesTableColumns} from 'sentry/views/explore/metrics/constants';
9+
import {useMetricAggregatesTable} from 'sentry/views/explore/metrics/hooks/useMetricAggregatesTable';
10+
import {useMetricSamplesTable} from 'sentry/views/explore/metrics/hooks/useMetricSamplesTable';
511
import {useMetricTimeseries} from 'sentry/views/explore/metrics/hooks/useMetricTimeseries';
612
import {useTableOrientationControl} from 'sentry/views/explore/metrics/hooks/useOrientationControl';
713
import {SideBySideOrientation} from 'sentry/views/explore/metrics/metricPanel/sideBySideOrientation';
814
import {StackedOrientation} from 'sentry/views/explore/metrics/metricPanel/stackedOrientation';
915
import {type TraceMetric} from 'sentry/views/explore/metrics/metricQuery';
16+
import {getMetricTableColumnType} from 'sentry/views/explore/metrics/utils';
17+
import {
18+
useQueryParamsAggregateSortBys,
19+
useQueryParamsMode,
20+
useQueryParamsSortBys,
21+
} from 'sentry/views/explore/queryParams/context';
22+
23+
const RESULT_LIMIT = 50;
24+
const TWO_MINUTE_DELAY = 120;
1025

1126
interface MetricPanelProps {
1227
queryIndex: number;
@@ -25,6 +40,41 @@ export function MetricPanel({traceMetric, queryIndex}: MetricPanelProps) {
2540
enabled: Boolean(traceMetric.name),
2641
});
2742

43+
const columns = TraceSamplesTableColumns;
44+
const fields = columns.filter(c => getMetricTableColumnType(c) !== 'stat');
45+
46+
const metricSamplesTableResult = useMetricSamplesTable({
47+
disabled: !traceMetric?.name,
48+
limit: RESULT_LIMIT,
49+
traceMetric,
50+
fields,
51+
ingestionDelaySeconds: TWO_MINUTE_DELAY,
52+
});
53+
54+
const metricAggregatesTableResult = useMetricAggregatesTable({
55+
enabled: Boolean(traceMetric.name),
56+
limit: RESULT_LIMIT,
57+
traceMetric,
58+
});
59+
60+
const mode = useQueryParamsMode();
61+
const sortBys = useQueryParamsSortBys();
62+
const aggregateSortBys = useQueryParamsAggregateSortBys();
63+
const [interval] = useChartInterval();
64+
const topEvents = useTopEvents();
65+
66+
useMetricsPanelAnalytics({
67+
interval,
68+
isTopN: !!topEvents,
69+
metricAggregatesTableResult,
70+
metricSamplesTableResult,
71+
metricTimeseriesResult: timeseriesResult,
72+
mode,
73+
yAxis: traceMetric.name || '',
74+
sortBys,
75+
aggregateSortBys,
76+
});
77+
2878
return (
2979
<Panel>
3080
<PanelBody>

0 commit comments

Comments
 (0)