CloudWatch: Enable feature adoption tracking in the plugin (#54299)

* improve typing for DashboardLoadedEvent

* cleanup

* add cloudwatch tracking event

* add test

* revert test change

* fix typo

* remove optional

* pr feedback

* reactor test

* revert changes to api and azure

* cleanup

* refactoring test

* pr feedback
This commit is contained in:
Erik Sundell 2022-09-19 08:53:42 +02:00 committed by GitHub
parent 7bca193ecd
commit 469f915b8c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 860 additions and 1 deletions

View File

@ -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[],
},
});

View File

@ -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<CloudWatchDatasource, CloudWatchQuery, CloudWatchJsonData>(
@ -14,3 +16,6 @@ export const plugin = new DataSourcePlugin<CloudWatchDatasource, CloudWatchQuery
.setConfigEditor(ConfigEditor)
.setQueryEditor(PanelQueryEditor)
.setMetadataInspector(MetaInspector);
// Subscribe to on dashboard loaded event so that we can track plugin adoption
getAppEvents().subscribe<DashboardLoadedEvent<CloudWatchQuery>>(DashboardLoadedEvent, onDashboardLoadedHandler);

View File

@ -0,0 +1,39 @@
import { DashboardLoadedEvent } from '@grafana/data';
let handler: (e: DashboardLoadedEvent<CloudWatchQuery>) => {};
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,
});
});
});

View File

@ -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<CloudWatchQuery>) => {
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);