diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/dashboardOnLoadedEvent.ts b/public/app/plugins/datasource/cloudwatch/__mocks__/dashboardOnLoadedEvent.ts new file mode 100644 index 00000000000..07d87478f93 --- /dev/null +++ b/public/app/plugins/datasource/cloudwatch/__mocks__/dashboardOnLoadedEvent.ts @@ -0,0 +1,715 @@ +import { DashboardLoadedEvent } from '@grafana/data'; + +import { CloudWatchQuery } from '../types'; + +export const CloudWatchDashboardLoadedEvent = new DashboardLoadedEvent({ + dashboardId: 'dashboard123', + orgId: 1, + userId: 2, + grafanaVersion: 'v9.0.0', + queries: { + cloudwatch: [ + { + alias: '', + datasource: { + type: 'cloudwatch', + uid: 'abc', + }, + dimensions: {}, + expression: '', + id: '', + matchExact: true, + metricEditorMode: 1, + metricName: '', + metricQueryType: 1, + namespace: '', + period: '', + queryMode: 'Metrics', + refId: 'A', + region: 'us-east-1', + sql: { + from: { + property: { + name: 'AWS/EC2', + type: 'string', + }, + type: 'property', + }, + groupBy: { + expressions: [ + { + property: { + name: 'InstanceId', + type: 'string', + }, + type: 'groupBy', + }, + ], + type: 'and', + }, + limit: 5, + orderBy: { + name: 'AVG', + type: 'function', + }, + orderByDirection: 'DESC', + select: { + name: 'AVG', + parameters: [ + { + name: 'CPUUtilization', + type: 'functionParameter', + }, + ], + type: 'function', + }, + }, + sqlExpression: 'SELECT AVG(CPUUtilization) FROM "AWS/EC2" GROUP BY InstanceId ORDER BY AVG() DESC LIMIT 5', + statistic: 'Average', + }, + { + alias: '', + datasource: { + type: 'cloudwatch', + uid: 'abc', + }, + dimensions: {}, + expression: '', + id: '', + matchExact: true, + metricEditorMode: 1, + metricName: '', + metricQueryType: 1, + namespace: '', + period: '', + queryMode: 'Metrics', + refId: 'A', + region: 'us-east-1', + sql: { + from: { + property: { + name: '$namespace', + type: 'string', + }, + type: 'property', + }, + groupBy: { + expressions: [ + { + property: { + name: '$dimensionkey', + type: 'string', + }, + type: 'groupBy', + }, + ], + type: 'and', + }, + limit: 5, + orderBy: { + name: 'SUM', + type: 'function', + }, + orderByDirection: '$direction', + select: { + name: '$aggregation', + parameters: [ + { + name: '$metric', + type: 'functionParameter', + }, + ], + type: 'function', + }, + }, + sqlExpression: + 'SELECT $aggregation($metric) FROM "$namespace" GROUP BY $dimensionkey ORDER BY SUM() $direction LIMIT 5', + statistic: 'Average', + }, + { + alias: '', + datasource: { + type: 'cloudwatch', + uid: 'abc', + }, + dimensions: {}, + expression: '', + id: '', + matchExact: true, + metricEditorMode: 1, + metricName: '', + metricQueryType: 1, + namespace: '', + period: '900', + queryMode: 'Metrics', + refId: 'A', + region: 'us-east-1', + sql: { + from: { + name: 'SCHEMA', + parameters: [ + { + name: 'AWS/EC2', + type: 'functionParameter', + }, + { + name: 'InstanceId', + type: 'functionParameter', + }, + ], + type: 'function', + }, + orderByDirection: 'DESC', + select: { + name: 'AVG', + parameters: [ + { + name: 'CPUUtilization', + type: 'functionParameter', + }, + ], + type: 'function', + }, + }, + sqlExpression: 'SELECT AVG(CPUUtilization) FROM SCHEMA("AWS/EC2", InstanceId)', + statistic: 'Average', + }, + { + alias: '', + datasource: { + type: 'cloudwatch', + uid: 'abc', + }, + dimensions: {}, + expression: '', + hide: false, + id: '', + matchExact: true, + metricEditorMode: 0, + metricName: '', + metricQueryType: 1, + namespace: '', + period: '', + queryMode: 'Metrics', + refId: 'A', + region: 'default', + sql: { + from: { + property: { + name: 'AWS/EC2', + type: 'string', + }, + type: 'property', + }, + groupBy: { + expressions: [ + { + property: { + name: 'InstanceId', + type: 'string', + }, + type: 'groupBy', + }, + ], + type: 'and', + }, + limit: 5, + orderBy: { + name: 'MAX', + type: 'function', + }, + orderByDirection: 'DESC', + select: { + name: 'AVG', + parameters: [ + { + name: 'CPUUtilization', + type: 'functionParameter', + }, + ], + type: 'function', + }, + }, + sqlExpression: 'SELECT AVG(CPUUtilization) FROM "AWS/EC2" GROUP BY InstanceId ORDER BY MAX() DESC LIMIT 5', + statistic: 'Average', + }, + { + alias: '', + datasource: { + type: 'cloudwatch', + uid: 'abc', + }, + dimensions: {}, + expression: '', + id: '', + matchExact: true, + metricEditorMode: 0, + metricName: '', + metricQueryType: 1, + namespace: '', + period: '', + queryMode: 'Metrics', + refId: 'A', + region: 'us-east-1', + sql: { + from: { + property: { + name: 'AWS/EC2', + type: 'string', + }, + type: 'property', + }, + groupBy: { + expressions: [ + { + property: { + name: 'InstanceId', + type: 'string', + }, + type: 'groupBy', + }, + ], + type: 'and', + }, + limit: 5, + orderBy: { + name: 'AVG', + type: 'function', + }, + orderByDirection: 'DESC', + select: { + name: 'AVG', + parameters: [ + { + name: 'CPUUtilization', + type: 'functionParameter', + }, + ], + type: 'function', + }, + }, + sqlExpression: 'SELECT AVG(CPUUtilization) FROM "AWS/EC2" GROUP BY InstanceId ORDER BY AVG() DESC LIMIT 5', + statistic: 'Average', + }, + { + alias: '', + datasource: { + type: 'cloudwatch', + uid: 'abc', + }, + dimensions: {}, + expression: '', + id: '', + matchExact: true, + metricEditorMode: 0, + metricName: '', + metricQueryType: 1, + namespace: '', + period: '900', + queryMode: 'Metrics', + refId: 'A', + region: 'us-east-1', + sql: { + from: { + name: 'SCHEMA', + parameters: [ + { + name: 'AWS/EC2', + type: 'functionParameter', + }, + { + name: 'InstanceId', + type: 'functionParameter', + }, + ], + type: 'function', + }, + orderByDirection: 'DESC', + select: { + name: 'AVG', + parameters: [ + { + name: 'CPUUtilization', + type: 'functionParameter', + }, + ], + type: 'function', + }, + }, + sqlExpression: 'SELECT AVG(CPUUtilization) FROM SCHEMA("AWS/EC2", InstanceId)', + statistic: 'Average', + }, + { + alias: '', + datasource: { + type: 'cloudwatch', + uid: 'abc', + }, + dimensions: {}, + expression: '', + id: '', + matchExact: true, + metricEditorMode: 1, + metricName: '', + metricQueryType: 1, + namespace: 'AWS/EC2', + period: '300', + queryMode: 'Metrics', + refId: 'A', + region: 'default', + sql: { + select: { + name: 'AVG', + type: 'function', + }, + }, + sqlExpression: 'SELECT AVG(CPUUtilization) FROM "AWS/EC2" GROUP BY InstanceId', + statistic: 'Average', + }, + { + alias: '', + datasource: { + type: 'cloudwatch', + uid: 'abc', + }, + dimensions: {}, + expression: 'REMOVE_EMPTY(SEARCH(\'{"AWS/EC2","InstanceId"} MetricName="CPUUtilization"\', \'Average\', 900))', + id: '', + matchExact: true, + metricEditorMode: 1, + metricName: 'CPUUtilization', + metricQueryType: 0, + namespace: 'AWS/EC2', + period: '', + queryMode: 'Metrics', + refId: 'A', + region: 'default', + sqlExpression: '', + statistic: 'Average', + }, + { + alias: '', + datasource: { + type: 'cloudwatch', + uid: 'abc', + }, + dimensions: { + InstanceId: 'i-123', + }, + expression: 'REMOVE_EMPTY(SEARCH(\'{"AWS/EC2","InstanceId"} MetricName="CPUUtilization"\', \'Average\', 900))', + id: 'a', + matchExact: true, + metricEditorMode: 0, + metricName: 'CPUUtilization', + metricQueryType: 0, + namespace: 'AWS/EC2', + period: '', + queryMode: 'Metrics', + refId: 'A', + region: 'default', + sqlExpression: '', + statistic: 'Average', + }, + { + alias: 'query a times 2', + datasource: { + type: 'cloudwatch', + uid: 'abc', + }, + dimensions: { + InstanceId: 'i-123', + }, + expression: 'a * 2', + hide: false, + id: 'b', + matchExact: true, + metricEditorMode: 1, + metricName: 'CPUUtilization', + metricQueryType: 0, + namespace: 'AWS/EC2', + period: '', + queryMode: 'Metrics', + refId: 'B', + region: 'default', + sqlExpression: '', + statistic: 'Average', + }, + { + datasource: { + type: 'cloudwatch', + uid: 'P7DC3E4760CFAC4AP', + }, + expression: 'fields @timestamp, @message | sort @timestamp desc | limit 300 ', + id: '', + logGroupNames: ['/aws/lambda/hello-world', '/aws/sagemaker/Endpoints/test', '/aws/sagemaker/test'], + namespace: '', + queryMode: 'Logs', + refId: 'A', + region: 'default', + statsGroups: [], + }, + { + alias: '', + datasource: { + type: 'cloudwatch', + uid: 'APZ8b_Fnz', + }, + dimensions: { + InstanceId: '*', + }, + expression: '', + id: '', + matchExact: true, + metricEditorMode: 0, + metricName: 'CPUUtilization', + metricQueryType: 0, + namespace: 'AWS/EC2', + period: '', + queryMode: 'Metrics', + refId: 'A', + region: 'default', + sqlExpression: '', + statistic: 'Average', + }, + { + alias: '{{InstanceId}}', + datasource: { + type: 'cloudwatch', + uid: 'abc', + }, + dimensions: { + InstanceId: '$dimensionValue', + }, + expression: '', + expressionMode: 'math', + hide: false, + id: 'a', + matchExact: true, + metricEditorMode: 0, + metricName: 'CPUUtilization', + metricQueryMode: 'sql', + metricQueryType: 0, + namespace: 'AWS/EC2', + period: '', + queryMode: 'Metrics', + refId: 'B', + region: 'default', + sqlExpression: '', + statistic: 'Average', + }, + { + alias: 'Network Out', + datasource: { + type: 'cloudwatch', + uid: 'abc', + }, + dimensions: { + InstanceId: 'i-123', + }, + expression: '', + expressionMode: 'math', + hide: true, + id: '', + matchExact: true, + metricEditorMode: 0, + metricName: 'NetworkOut', + metricQueryMode: 'metricStat', + metricQueryType: 0, + namespace: 'AWS/EC2', + period: '', + queryMode: 'Metrics', + refId: 'A', + region: 'default', + sqlExpression: '', + statistic: 'Average', + }, + { + alias: '{{InstanceId}}', + datasource: { + type: 'cloudwatch', + uid: 'YEc9mclMk', + }, + dimensions: { + InstanceId: 'i-123', + }, + expression: '', + id: '', + matchExact: true, + metricEditorMode: 0, + metricName: 'CPUUtilization', + metricQueryType: 0, + namespace: 'AWS/EC2', + period: '', + queryMode: 'Metrics', + refId: 'A', + region: 'us-east-2', + sqlExpression: '', + statistic: '$stats', + }, + { + alias: '', + datasource: { + type: 'cloudwatch', + uid: 'abc', + }, + dimensions: { + InstanceId: 'i-123', + }, + expression: '', + id: 'a', + matchExact: true, + metricEditorMode: 0, + metricName: 'CPUUtilization', + metricQueryType: 0, + namespace: 'AWS/EC2', + period: '', + queryMode: 'Metrics', + refId: 'A', + region: 'default', + sqlExpression: '', + statistic: 'Average', + }, + { + alias: '', + datasource: { + type: 'cloudwatch', + uid: 'abc', + }, + dimensions: { + InstanceId: '*', + }, + expression: 'a / 0', + hide: false, + id: '', + matchExact: true, + metricEditorMode: 1, + metricName: 'CPUUtilization', + metricQueryType: 0, + namespace: 'AWS/EC2', + period: '', + queryMode: 'Metrics', + refId: 'B', + region: 'default', + sqlExpression: '', + statistic: 'Average', + }, + { + alias: '', + datasource: { + type: 'cloudwatch', + uid: 'abc', + }, + dimensions: { + InstanceId: '*', + }, + expression: "REMOVE_EMPTY(SEARCH('', 'Average', 900))", + id: '', + matchExact: true, + metricEditorMode: 1, + metricName: 'CPUUtilization', + metricQueryType: 0, + namespace: 'AWS/EC2', + period: '', + queryMode: 'Metrics', + refId: 'A', + region: 'us-east-2', + sqlExpression: '', + statistic: 'Average', + }, + { + alias: '', + datasource: { + type: 'cloudwatch', + uid: 'abc', + }, + dimensions: { + InstanceId: '*', + }, + expression: '', + id: 'a', + matchExact: true, + metricEditorMode: 0, + metricName: 'CPUUtilization', + metricQueryType: 0, + namespace: 'AWS/EC2', + period: '', + queryMode: 'Metrics', + refId: 'A', + region: 'default', + sqlExpression: '', + statistic: 'Average', + }, + { + alias: '', + datasource: { + type: 'cloudwatch', + uid: 'abc', + }, + dimensions: { + InstanceId: '*', + }, + expression: '', + hide: false, + id: 'a', + matchExact: true, + metricEditorMode: 0, + metricName: 'CPUUtilization', + metricQueryType: 0, + namespace: 'AWS/EC2', + period: '', + queryMode: 'Metrics', + refId: 'B', + region: 'default', + sqlExpression: '', + statistic: 'Average', + }, + { + alias: '', + datasource: { + type: 'cloudwatch', + uid: 'abc', + }, + dimensions: { + InstanceId: 'i-123', + }, + expression: '', + id: 'a', + matchExact: true, + metricEditorMode: 0, + metricName: 'CPUUtilization', + metricQueryType: 0, + namespace: 'AWS/EC2', + period: '', + queryMode: 'Metrics', + refId: 'A', + region: 'default', + sqlExpression: '', + statistic: 'Average', + }, + { + alias: '', + datasource: { + type: 'cloudwatch', + uid: 'abc', + }, + dimensions: { + InstanceId: '*', + }, + expression: 'a / ', + hide: false, + id: '', + matchExact: true, + metricEditorMode: 1, + metricName: 'CPUUtilization', + metricQueryType: 0, + namespace: 'AWS/EC2', + period: '', + queryMode: 'Metrics', + refId: 'B', + region: 'default', + sqlExpression: '', + statistic: 'Average', + }, + ] as CloudWatchQuery[], + }, +}); diff --git a/public/app/plugins/datasource/cloudwatch/module.tsx b/public/app/plugins/datasource/cloudwatch/module.tsx index b295691232c..e922010b5f7 100644 --- a/public/app/plugins/datasource/cloudwatch/module.tsx +++ b/public/app/plugins/datasource/cloudwatch/module.tsx @@ -1,10 +1,12 @@ -import { DataSourcePlugin } from '@grafana/data'; +import { DashboardLoadedEvent, DataSourcePlugin } from '@grafana/data'; +import { getAppEvents } from '@grafana/runtime'; import { ConfigEditor } from './components/ConfigEditor'; import LogsCheatSheet from './components/LogsCheatSheet'; import { MetaInspector } from './components/MetaInspector'; import { PanelQueryEditor } from './components/PanelQueryEditor'; import { CloudWatchDatasource } from './datasource'; +import { onDashboardLoadedHandler } from './tracking'; import { CloudWatchJsonData, CloudWatchQuery } from './types'; export const plugin = new DataSourcePlugin( @@ -14,3 +16,6 @@ export const plugin = new DataSourcePlugin>(DashboardLoadedEvent, onDashboardLoadedHandler); diff --git a/public/app/plugins/datasource/cloudwatch/tracking.test.ts b/public/app/plugins/datasource/cloudwatch/tracking.test.ts new file mode 100644 index 00000000000..1807eb5cf5a --- /dev/null +++ b/public/app/plugins/datasource/cloudwatch/tracking.test.ts @@ -0,0 +1,39 @@ +import { DashboardLoadedEvent } from '@grafana/data'; +let handler: (e: DashboardLoadedEvent) => {}; +import { reportInteraction } from '@grafana/runtime'; + +import './module'; +import { CloudWatchDashboardLoadedEvent } from './__mocks__/dashboardOnLoadedEvent'; +import { CloudWatchQuery } from './types'; + +jest.mock('@grafana/runtime', () => { + return { + ...jest.requireActual('@grafana/runtime'), + reportInteraction: jest.fn(), + getAppEvents: () => ({ + subscribe: jest.fn((e, h) => { + handler = h; + }), + }), + }; +}); + +describe('onDashboardLoadedHandler', () => { + it('should report a `grafana_ds_cloudwatch_dashboard_loaded` interaction ', () => { + handler(CloudWatchDashboardLoadedEvent); + expect(reportInteraction).toHaveBeenCalledWith('grafana_ds_cloudwatch_dashboard_loaded', { + dashboard_id: 'dashboard123', + grafana_version: 'v9.0.0', + org_id: 1, + logs_queries_count: 1, + metrics_queries_count: 21, + metrics_query_builder_count: 3, + metrics_query_code_count: 4, + metrics_query_count: 7, + metrics_search_builder_count: 9, + metrics_search_code_count: 5, + metrics_search_count: 14, + metrics_search_match_exact_count: 9, + }); + }); +}); diff --git a/public/app/plugins/datasource/cloudwatch/tracking.ts b/public/app/plugins/datasource/cloudwatch/tracking.ts new file mode 100644 index 00000000000..6f5d7e46899 --- /dev/null +++ b/public/app/plugins/datasource/cloudwatch/tracking.ts @@ -0,0 +1,100 @@ +import { DashboardLoadedEvent } from '@grafana/data'; +import { reportInteraction } from '@grafana/runtime'; + +import { isCloudWatchLogsQuery, isCloudWatchMetricsQuery } from './guards'; +import pluginJson from './plugin.json'; +import { CloudWatchMetricsQuery, CloudWatchQuery, MetricEditorMode, MetricQueryType } from './types'; + +interface CloudWatchOnDashboardLoadedTrackingEvent { + grafana_version?: string; + dashboard_id?: string; + org_id?: number; + + /* The number of CloudWatch logs queries present in the dashboard*/ + logs_queries_count: number; + + /* The number of CloudWatch metrics queries present in the dashboard*/ + metrics_queries_count: number; + + /* The number of queries using the "Search" mode. + Should be measured in relation to metrics_queries_count, e.g metrics_search_count + metrics_query_count = metrics_queries_count */ + metrics_search_count: number; + + /* The number of search queries that are using the builder mode. + Should be measured in relation to metrics_search_count, e.g metrics_search_builder_count + metrics_search_code_count = metrics_search_count */ + metrics_search_builder_count: number; + + /* The number of search queries that are using the code mode. + Should be measured in relation to metrics_search_count, e.g metrics_search_builder_count + metrics_search_code_count = metrics_search_count */ + metrics_search_code_count: number; + + /* The number of search queries that have enabled the `match exact` toggle in the builder mode. + Should be measured in relation to metrics_search_builder_count. + E.g 'Out of 5 metric seach queries (metrics_search_builder_count), 2 had match exact toggle (metrics_search_match_exact_count) enabled */ + metrics_search_match_exact_count: number; + + /* The number of queries using the "Query" mode (AKA Metric Insights). + Should be measured in relation to metrics_queries_count, e.g metrics_search_count + metrics_query_count = metrics_queries_count */ + metrics_query_count: number; + + /* The number of "Insights" queries that are using the builder mode. + Should be measured in relation to metrics_query_count, e.g metrics_query_builder_count + metrics_query_code_count = metrics_query_count */ + metrics_query_builder_count: number; + + /* The number of "Insights" queries that are using the code mode. + Should be measured in relation to metrics_query_count, e.g metrics_query_builder_count + metrics_query_code_count = metrics_query_count */ + metrics_query_code_count: number; +} + +export const onDashboardLoadedHandler = ({ + payload: { dashboardId, orgId, grafanaVersion, queries }, +}: DashboardLoadedEvent) => { + try { + const cloudWatchQueries = queries[pluginJson.id]; + + if (!cloudWatchQueries?.length) { + return; + } + + const logsQueries = cloudWatchQueries.filter(isCloudWatchLogsQuery); + const metricsQueries = cloudWatchQueries.filter(isCloudWatchMetricsQuery); + + const e: CloudWatchOnDashboardLoadedTrackingEvent = { + grafana_version: grafanaVersion, + dashboard_id: dashboardId, + org_id: orgId, + logs_queries_count: logsQueries?.length, + metrics_queries_count: metricsQueries?.length, + metrics_search_count: 0, + metrics_search_builder_count: 0, + metrics_search_code_count: 0, + metrics_search_match_exact_count: 0, + metrics_query_count: 0, + metrics_query_builder_count: 0, + metrics_query_code_count: 0, + }; + + for (const q of metricsQueries) { + e.metrics_search_count += +Boolean(q.metricQueryType === MetricQueryType.Search); + e.metrics_search_builder_count += +isMetricSearchBuilder(q); + e.metrics_search_code_count += +Boolean( + q.metricQueryType === MetricQueryType.Search && q.metricEditorMode === MetricEditorMode.Code + ); + e.metrics_search_match_exact_count += +Boolean(isMetricSearchBuilder(q) && q.matchExact); + e.metrics_query_count += +Boolean(q.metricQueryType === MetricQueryType.Query); + e.metrics_query_builder_count += +Boolean( + q.metricQueryType === MetricQueryType.Query && q.metricEditorMode === MetricEditorMode.Builder + ); + e.metrics_query_code_count += +Boolean( + q.metricQueryType === MetricQueryType.Query && q.metricEditorMode === MetricEditorMode.Code + ); + } + + reportInteraction('grafana_ds_cloudwatch_dashboard_loaded', e); + } catch (error) { + console.error('error in cloudwatch tracking handler', error); + } +}; + +const isMetricSearchBuilder = (q: CloudWatchMetricsQuery) => + Boolean(q.metricQueryType === MetricQueryType.Search && q.metricEditorMode === MetricEditorMode.Builder);