From 6ee69853dc518d0735f0b8a9eb3253e39b375000 Mon Sep 17 00:00:00 2001 From: Yaelle Chaudy <42030685+yaelleC@users.noreply.github.com> Date: Mon, 24 Oct 2022 11:09:26 +0200 Subject: [PATCH] Datasource Config Editor: Add event-based funnel for measuring CVR (#55105) * Added start event on adding new DS from datasources/new page * Added end event + tests * revert analytics.ts changes * revert azure tracking changes * changed version in doc * update props name for consistency with plugin-platform PR to come * Updated tests --- .../datasources/state/actions.test.ts | 62 ++++++++++++++++++- .../app/features/datasources/state/actions.ts | 21 +++++++ public/app/features/datasources/tracking.ts | 54 ++++++++++++++++ 3 files changed, 134 insertions(+), 3 deletions(-) create mode 100644 public/app/features/datasources/tracking.ts diff --git a/public/app/features/datasources/state/actions.test.ts b/public/app/features/datasources/state/actions.test.ts index 127e6bdc9ca..81e8cb6bf1c 100644 --- a/public/app/features/datasources/state/actions.test.ts +++ b/public/app/features/datasources/state/actions.test.ts @@ -1,11 +1,12 @@ import { thunkTester } from 'test/core/thunk/thunkTester'; -import { DataSourceSettings } from '@grafana/data'; +import { AppPluginMeta, DataSourceSettings, PluginMetaInfo, PluginType } from '@grafana/data'; import { FetchError } from '@grafana/runtime'; import { ThunkResult, ThunkDispatch } from 'app/types'; import { getMockDataSource } from '../__mocks__'; import * as api from '../api'; +import { trackDataSourceCreated, trackDataSourceTested } from '../tracking'; import { GenericDataSourcePlugin } from '../types'; import { @@ -14,6 +15,7 @@ import { TestDataSourceDependencies, initDataSourceSettings, loadDataSource, + addDataSource, } from './actions'; import { initDataSourceSettingsSucceeded, @@ -22,13 +24,20 @@ import { testDataSourceSucceeded, testDataSourceFailed, dataSourceLoaded, + dataSourcesLoaded, } from './reducers'; jest.mock('../api'); jest.mock('app/core/services/backend_srv'); +jest.mock('app/core/core'); jest.mock('@grafana/runtime', () => ({ ...(jest.requireActual('@grafana/runtime') as unknown as object), - getBackendSrv: jest.fn(), + getDataSourceSrv: jest.fn().mockReturnValue({ reload: jest.fn() }), + getBackendSrv: jest.fn().mockReturnValue({ get: jest.fn() }), +})); +jest.mock('../tracking', () => ({ + trackDataSourceCreated: jest.fn(), + trackDataSourceTested: jest.fn(), })); const getBackendSrvMock = () => @@ -210,6 +219,8 @@ describe('testDataSource', () => { status: '', message: '', }), + type: 'cloudwatch', + uid: 'CW1234', }), } as any), getBackendSrv: getBackendSrvMock, @@ -222,9 +233,15 @@ describe('testDataSource', () => { }; const dispatchedActions = await thunkTester(state) .givenThunk(testDataSource) - .whenThunkIsDispatched('Azure Monitor', dependencies); + .whenThunkIsDispatched('CloudWatch', dependencies); expect(dispatchedActions).toEqual([testDataSourceStarting(), testDataSourceSucceeded(state.testingStatus)]); + expect(trackDataSourceTested).toHaveBeenCalledWith({ + plugin_id: 'cloudwatch', + datasource_uid: 'CW1234', + grafana_version: '1.0', + success: true, + }); }); it('then testDataSourceFailed should be dispatched', async () => { @@ -235,6 +252,8 @@ describe('testDataSource', () => { testDatasource: jest.fn().mockImplementation(() => { throw new Error('Error testing datasource'); }), + type: 'azure-monitor', + uid: 'azM0nit0R', }), } as any), getBackendSrv: getBackendSrvMock, @@ -253,6 +272,12 @@ describe('testDataSource', () => { .whenThunkIsDispatched('Azure Monitor', dependencies); expect(dispatchedActions).toEqual([testDataSourceStarting(), testDataSourceFailed(result)]); + expect(trackDataSourceTested).toHaveBeenCalledWith({ + plugin_id: 'azure-monitor', + datasource_uid: 'azM0nit0R', + grafana_version: '1.0', + success: false, + }); }); it('then testDataSourceFailed should be dispatched with response error message', async () => { @@ -304,3 +329,34 @@ describe('testDataSource', () => { }); }); }); + +describe('addDataSource', () => { + it('it creates a datasource and calls trackDataSourceCreated ', async () => { + const meta: AppPluginMeta = { + id: 'azure-monitor', + module: '', + baseUrl: 'xxx', + info: { version: '1.2.3' } as PluginMetaInfo, + type: PluginType.datasource, + name: 'test DS', + }; + const state = { + dataSources: { + dataSources: [], + }, + }; + const dataSourceMock = { datasource: { uid: 'azure23' }, meta }; + (api.createDataSource as jest.Mock).mockResolvedValueOnce(dataSourceMock); + (api.getDataSources as jest.Mock).mockResolvedValueOnce([]); + + const dispatchedActions = await thunkTester(state).givenThunk(addDataSource).whenThunkIsDispatched(meta); + + expect(dispatchedActions).toEqual([dataSourcesLoaded([])]); + expect(trackDataSourceCreated).toHaveBeenCalledWith({ + plugin_id: 'azure-monitor', + plugin_version: '1.2.3', + datasource_uid: 'azure23', + grafana_version: '1.0', + }); + }); +}); diff --git a/public/app/features/datasources/state/actions.ts b/public/app/features/datasources/state/actions.ts index f8379fd7ae3..800acbf0277 100644 --- a/public/app/features/datasources/state/actions.ts +++ b/public/app/features/datasources/state/actions.ts @@ -1,5 +1,6 @@ import { DataSourcePluginMeta, DataSourceSettings, locationUtil } from '@grafana/data'; import { + config, DataSourceWithBackend, getDataSourceSrv, HealthCheckError, @@ -17,6 +18,7 @@ import { DataSourcePluginCategory, ThunkDispatch, ThunkResult } from 'app/types' import * as api from '../api'; import { DATASOURCES_ROUTES } from '../constants'; +import { trackDataSourceCreated, trackDataSourceTested } from '../tracking'; import { nameExits, findNewName } from '../utils'; import { buildCategories } from './buildCategories'; @@ -107,6 +109,12 @@ export const testDataSource = ( const result = await dsApi.testDatasource(); dispatch(testDataSourceSucceeded(result)); + trackDataSourceTested({ + grafana_version: config.buildInfo.version, + plugin_id: dsApi.type, + datasource_uid: dsApi.uid, + success: true, + }); } catch (err) { let message: string | undefined; let details: HealthCheckResultDetails; @@ -121,6 +129,12 @@ export const testDataSource = ( } dispatch(testDataSourceFailed({ message, details })); + trackDataSourceTested({ + grafana_version: config.buildInfo.version, + plugin_id: dsApi.type, + datasource_uid: dsApi.uid, + success: false, + }); } }); }; @@ -197,6 +211,13 @@ export function addDataSource(plugin: DataSourcePluginMeta, editLink = DATASOURC await getDatasourceSrv().reload(); await contextSrv.fetchUserPermissions(); + trackDataSourceCreated({ + grafana_version: config.buildInfo.version, + plugin_id: plugin.id, + datasource_uid: result.datasource.uid, + plugin_version: result.meta?.info?.version, + }); + locationService.push(editLink.replace(/:uid/gi, result.datasource.uid)); }; } diff --git a/public/app/features/datasources/tracking.ts b/public/app/features/datasources/tracking.ts new file mode 100644 index 00000000000..8323f5d1324 --- /dev/null +++ b/public/app/features/datasources/tracking.ts @@ -0,0 +1,54 @@ +import { reportInteraction } from '@grafana/runtime'; + +/** + * Used to track data source creation via either the specific plugin page `/plugins/` + * or the general datasources page `/datasources/new` + * + * This event corresponds to the start event of our data source creation funnel. + * Combined with the end event, it allows answering questions about: + * - Conversion (percentage of user that successfully set up a data source) + * - Time spent on the config page + * + * Changelog: + * - v9.1.7 : logging datasource, datasource_uid, grafana version + */ +export const trackDataSourceCreated = (props: DataSourceCreatedProps) => { + reportInteraction('grafana_ds_add_datasource_clicked', props); +}; + +type DataSourceCreatedProps = { + grafana_version?: string; + /** The unique id of the newly created data source */ + datasource_uid: string; + /** The datasource id (e.g. Cloudwatch, Loki, Prometheus) */ + plugin_id: string; + /** The plugin version (especially interesting in external plugins - core plugins are aligned with grafana version) */ + plugin_version?: string; +}; + +/** + * Used to track data source testing + * + * This event corresponds to the end event of our data source creation funnel. + * Combined with the start event, it allows answering questions about: + * - Conversion (percentage of user that successfully set up a data source) + * - Time spent on the config page + * + * Changelog: + * - v9.1.7 : logging datasource, datasource_uid, grafana version and success + */ +export const trackDataSourceTested = (props: DataSourceTestedProps) => { + reportInteraction('grafana_ds_test_datasource_clicked', props); +}; + +type DataSourceTestedProps = { + grafana_version?: string; + /** The unique id of the newly created data source */ + datasource_uid: string; + /** The datasource id (e.g. Cloudwatch, Loki, Prometheus) */ + plugin_id: string; + /** The plugin version (especially interesting in external plugins - core plugins are aligned with grafana version) */ + plugin_version?: string; + /** Whether or not the datasource test succeeded = the datasource was successfully configured */ + success: boolean; +};