diff --git a/public/app/plugins/datasource/tempo/module.ts b/public/app/plugins/datasource/tempo/module.ts index 5f71f71d1e3..b29b8e201a6 100644 --- a/public/app/plugins/datasource/tempo/module.ts +++ b/public/app/plugins/datasource/tempo/module.ts @@ -1,11 +1,17 @@ -import { DataSourcePlugin } from '@grafana/data'; +import { DataSourcePlugin, DashboardLoadedEvent } from '@grafana/data'; +import { getAppEvents } from '@grafana/runtime'; import CheatSheet from './CheatSheet'; import { TempoQueryField } from './QueryEditor/QueryField'; import { ConfigEditor } from './configuration/ConfigEditor'; import { TempoDatasource } from './datasource'; +import { onDashboardLoadedHandler } from './tracking'; +import { TempoQuery } from './types'; export const plugin = new DataSourcePlugin(TempoDatasource) .setQueryEditor(TempoQueryField) .setConfigEditor(ConfigEditor) .setQueryEditorHelp(CheatSheet); + +// Subscribe to on dashboard loaded event so that we can track plugin adoption +getAppEvents().subscribe>(DashboardLoadedEvent, onDashboardLoadedHandler); diff --git a/public/app/plugins/datasource/tempo/tracking.test.ts b/public/app/plugins/datasource/tempo/tracking.test.ts new file mode 100644 index 00000000000..bb939757e5f --- /dev/null +++ b/public/app/plugins/datasource/tempo/tracking.test.ts @@ -0,0 +1,113 @@ +import { DashboardLoadedEvent } from '@grafana/data'; +import { reportInteraction } from '@grafana/runtime'; + +import './module'; + +jest.mock('@grafana/runtime', () => { + return { + ...jest.requireActual('@grafana/runtime'), + reportInteraction: jest.fn(), + getAppEvents: () => ({ + subscribe: jest.fn((_, handler) => { + handler( + new DashboardLoadedEvent({ + dashboardId: 'dash', + orgId: 1, + userId: 1, + grafanaVersion: 'v9.4.0', + queries: { + tempo: [ + { + datasource: { type: 'tempo', uid: 'abc' }, + queryType: 'nativeSearch', + refId: 'A', + }, + { + datasource: { type: 'tempo', uid: 'abc' }, + queryType: 'nativeSearch', + spanName: 'HTTP', + refId: 'A', + }, + { + datasource: { type: 'tempo', uid: 'abc' }, + queryType: 'nativeSearch', + spanName: '$var', + refId: 'A', + }, + { + datasource: { type: 'tempo', uid: 'abc' }, + queryType: 'search', + linkedQuery: { + expr: '{}', + }, + refId: 'A', + }, + { + datasource: { type: 'tempo', uid: 'abc' }, + queryType: 'search', + linkedQuery: { + expr: '{$var}', + }, + refId: 'A', + }, + { + datasource: { type: 'tempo', uid: 'abc' }, + queryType: 'serviceMap', + serviceMapQuery: '{}', + refId: 'A', + }, + { + datasource: { type: 'tempo', uid: 'abc' }, + queryType: 'serviceMap', + serviceMapQuery: '{$var}', + refId: 'A', + }, + { + datasource: { type: 'tempo', uid: 'abc' }, + queryType: 'traceql', + query: '{}', + refId: 'A', + }, + { + datasource: { type: 'tempo', uid: 'abc' }, + queryType: 'traceql', + query: '{$var}', + refId: 'A', + }, + { + datasource: { type: 'tempo', uid: 'abc' }, + queryType: 'upload', + refId: 'A', + }, + ], + }, + }) + ); + }), + }), + getTemplateSrv: () => ({ + containsTemplate: (val: string): boolean => { + return val != null && val.includes('$'); + }, + }), + }; +}); + +describe('on dashboard loaded', () => { + it('triggers reportInteraction with grafana_tempo_dashboard_loaded', () => { + expect(reportInteraction).toHaveBeenCalledWith('grafana_tempo_dashboard_loaded', { + grafana_version: 'v9.4.0', + dashboard_id: 'dash', + org_id: 1, + traceql_query_count: 2, + search_query_count: 2, + service_map_query_count: 2, + upload_query_count: 1, + native_search_query_count: 3, + traceql_queries_with_template_variables_count: 1, + search_queries_with_template_variables_count: 1, + service_map_queries_with_template_variables_count: 1, + native_search_queries_with_template_variables_count: 1, + }); + }); +}); diff --git a/public/app/plugins/datasource/tempo/tracking.ts b/public/app/plugins/datasource/tempo/tracking.ts new file mode 100644 index 00000000000..17892de31b4 --- /dev/null +++ b/public/app/plugins/datasource/tempo/tracking.ts @@ -0,0 +1,91 @@ +import { DashboardLoadedEvent } from '@grafana/data'; +import { getTemplateSrv, reportInteraction } from '@grafana/runtime'; + +import pluginJson from './plugin.json'; +import { TempoQuery } from './types'; + +type TempoOnDashboardLoadedTrackingEvent = { + grafana_version?: string; + dashboard_id?: string; + org_id?: number; + native_search_query_count: number; + search_query_count: number; + service_map_query_count: number; + traceql_query_count: number; + upload_query_count: number; + native_search_queries_with_template_variables_count: number; + search_queries_with_template_variables_count: number; + service_map_queries_with_template_variables_count: number; + traceql_queries_with_template_variables_count: number; +}; + +export const onDashboardLoadedHandler = ({ + payload: { dashboardId, orgId, grafanaVersion, queries }, +}: DashboardLoadedEvent) => { + try { + const tempoQueries = queries[pluginJson.id]; + + if (!tempoQueries?.length) { + return; + } + + let stats: TempoOnDashboardLoadedTrackingEvent = { + grafana_version: grafanaVersion, + dashboard_id: dashboardId, + org_id: orgId, + native_search_query_count: 0, + search_query_count: 0, + service_map_query_count: 0, + traceql_query_count: 0, + upload_query_count: 0, + native_search_queries_with_template_variables_count: 0, + search_queries_with_template_variables_count: 0, + service_map_queries_with_template_variables_count: 0, + traceql_queries_with_template_variables_count: 0, + }; + + for (const query of tempoQueries) { + if (query.hide) { + continue; + } + + if (query.queryType === 'nativeSearch') { + stats.native_search_query_count++; + if ( + (query.serviceName && hasTemplateVariables(query.serviceName)) || + (query.spanName && hasTemplateVariables(query.spanName)) || + (query.search && hasTemplateVariables(query.search)) || + (query.minDuration && hasTemplateVariables(query.minDuration)) || + (query.maxDuration && hasTemplateVariables(query.maxDuration)) + ) { + stats.native_search_queries_with_template_variables_count++; + } + } else if (query.queryType === 'search') { + stats.search_query_count++; + if (query.linkedQuery && query.linkedQuery.expr && hasTemplateVariables(query.linkedQuery.expr)) { + stats.search_queries_with_template_variables_count++; + } + } else if (query.queryType === 'serviceMap') { + stats.service_map_query_count++; + if (query.serviceMapQuery && hasTemplateVariables(query.serviceMapQuery)) { + stats.service_map_queries_with_template_variables_count++; + } + } else if (query.queryType === 'traceql') { + stats.traceql_query_count++; + if (hasTemplateVariables(query.query)) { + stats.traceql_queries_with_template_variables_count++; + } + } else if (query.queryType === 'upload') { + stats.upload_query_count++; + } + } + + reportInteraction('grafana_tempo_dashboard_loaded', stats); + } catch (error) { + console.error('error in tempo tracking handler', error); + } +}; + +const hasTemplateVariables = (val: string): boolean => { + return getTemplateSrv().containsTemplate(val); +};