mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Datasources: Emit event on dashboard load with queries info (#52052)
Co-authored-by: Levente Balogh <balogh.levente.hu@gmail.com> Co-authored-by: Andres Martinez <andres.martinez@grafana.com>
This commit is contained in:
parent
d7556bd189
commit
dfe33a63fb
@ -40,3 +40,17 @@ export class DataSelectEvent extends BusEventWithPayload<DataHoverPayload> {
|
||||
export class AnnotationChangeEvent extends BusEventWithPayload<Partial<AnnotationEvent>> {
|
||||
static type = 'annotation-event';
|
||||
}
|
||||
|
||||
// Loaded the first time a dashboard is loaded (not on every render)
|
||||
export type DashboardLoadedEventPayload<T> = {
|
||||
dashboardId: string;
|
||||
orgId?: number;
|
||||
userId?: number;
|
||||
grafanaVersion?: string;
|
||||
queries: Record<string, T[]>;
|
||||
};
|
||||
|
||||
/** @alpha */
|
||||
export class DashboardLoadedEvent<T> extends BusEventWithPayload<DashboardLoadedEventPayload<T>> {
|
||||
static type = 'dashboard-loaded';
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import thunk from 'redux-thunk';
|
||||
import { Subject } from 'rxjs';
|
||||
|
||||
import { FetchError, locationService, setEchoSrv } from '@grafana/runtime';
|
||||
import appEvents from 'app/core/app_events';
|
||||
import { getBackendSrv } from 'app/core/services/backend_srv';
|
||||
import { keybindingSrv } from 'app/core/services/keybindingSrv';
|
||||
import { variableAdapters } from 'app/features/variables/adapters';
|
||||
@ -42,6 +43,25 @@ jest.mock('app/core/services/context_srv', () => ({
|
||||
user: { orgId: 1, orgName: 'TestOrg' },
|
||||
},
|
||||
}));
|
||||
jest.mock('@grafana/runtime', () => {
|
||||
const original = jest.requireActual('@grafana/runtime');
|
||||
return {
|
||||
...original,
|
||||
getDataSourceSrv: jest.fn().mockImplementation(() => ({
|
||||
...original.getDataSourceSrv(),
|
||||
getInstanceSettings: jest.fn(),
|
||||
})),
|
||||
};
|
||||
});
|
||||
jest.mock('@grafana/data', () => {
|
||||
const original = jest.requireActual('@grafana/data');
|
||||
return {
|
||||
...original,
|
||||
EventBusSrv: jest.fn().mockImplementation(() => ({
|
||||
publish: jest.fn(),
|
||||
})),
|
||||
};
|
||||
});
|
||||
|
||||
variableAdapters.register(createConstantVariableAdapter());
|
||||
const mockStore = configureMockStore([thunk]);
|
||||
@ -77,11 +97,73 @@ function describeInitScenario(description: string, scenarioFn: ScenarioFn) {
|
||||
id: 2,
|
||||
targets: [
|
||||
{
|
||||
datasource: {
|
||||
type: 'grafana-azure-monitor-datasource',
|
||||
uid: 'DSwithQueriesOnInitDashboard',
|
||||
name: 'azMonitor',
|
||||
},
|
||||
queryType: 'Azure Log Analytics',
|
||||
refId: 'A',
|
||||
expr: 'old expr',
|
||||
},
|
||||
{
|
||||
datasource: {
|
||||
type: 'cloudwatch',
|
||||
uid: '1234',
|
||||
name: 'Cloud Watch',
|
||||
},
|
||||
refId: 'B',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
collapsed: true,
|
||||
gridPos: {
|
||||
h: 1,
|
||||
w: 24,
|
||||
x: 0,
|
||||
y: 8,
|
||||
},
|
||||
id: 22,
|
||||
panels: [
|
||||
{
|
||||
datasource: {
|
||||
type: 'grafana-redshift-datasource',
|
||||
uid: 'V6_lLJf7k',
|
||||
},
|
||||
gridPos: {
|
||||
h: 8,
|
||||
w: 12,
|
||||
x: 12,
|
||||
y: 9,
|
||||
},
|
||||
id: 8,
|
||||
targets: [
|
||||
{
|
||||
datasource: {
|
||||
type: 'grafana-redshift-datasource',
|
||||
uid: 'V6_lLJf7k',
|
||||
},
|
||||
rawSQL: '',
|
||||
refId: 'A',
|
||||
},
|
||||
{
|
||||
datasource: {
|
||||
type: 'grafana-azure-monitor-datasource',
|
||||
uid: 'DSwithQueriesOnInitDashboard',
|
||||
name: 'azMonitor',
|
||||
},
|
||||
queryType: 'Azure Monitor',
|
||||
refId: 'B',
|
||||
},
|
||||
],
|
||||
title: 'Redshift and Azure',
|
||||
type: 'stat',
|
||||
},
|
||||
],
|
||||
title: 'Collapsed Panel',
|
||||
type: 'row',
|
||||
},
|
||||
],
|
||||
templating: {
|
||||
list: [constantBuilder().build()],
|
||||
@ -241,9 +323,65 @@ describeInitScenario('Initializing existing dashboard', (ctx) => {
|
||||
|
||||
ctx.setup(() => {
|
||||
ctx.storeState.user.orgId = 12;
|
||||
ctx.storeState.user.user = { id: 34 };
|
||||
ctx.storeState.explore.left.queries = mockQueries;
|
||||
});
|
||||
|
||||
it('should send dashboard_loaded event', () => {
|
||||
expect(appEvents.publish).toHaveBeenCalledWith({
|
||||
payload: {
|
||||
queries: {
|
||||
cloudwatch: [
|
||||
{
|
||||
datasource: {
|
||||
name: 'Cloud Watch',
|
||||
type: 'cloudwatch',
|
||||
uid: '1234',
|
||||
},
|
||||
refId: 'B',
|
||||
},
|
||||
],
|
||||
'grafana-azure-monitor-datasource': [
|
||||
{
|
||||
datasource: {
|
||||
name: 'azMonitor',
|
||||
type: 'grafana-azure-monitor-datasource',
|
||||
uid: 'DSwithQueriesOnInitDashboard',
|
||||
},
|
||||
expr: 'old expr',
|
||||
queryType: 'Azure Log Analytics',
|
||||
refId: 'A',
|
||||
},
|
||||
{
|
||||
datasource: {
|
||||
name: 'azMonitor',
|
||||
type: 'grafana-azure-monitor-datasource',
|
||||
uid: 'DSwithQueriesOnInitDashboard',
|
||||
},
|
||||
queryType: 'Azure Monitor',
|
||||
refId: 'B',
|
||||
},
|
||||
],
|
||||
'grafana-redshift-datasource': [
|
||||
{
|
||||
datasource: {
|
||||
type: 'grafana-redshift-datasource',
|
||||
uid: 'V6_lLJf7k',
|
||||
},
|
||||
rawSQL: '',
|
||||
refId: 'A',
|
||||
},
|
||||
],
|
||||
},
|
||||
dashboardId: 'DGmvKKxZz',
|
||||
orgId: 12,
|
||||
userId: 34,
|
||||
grafanaVersion: '1.0',
|
||||
},
|
||||
type: 'dashboard-loaded',
|
||||
});
|
||||
});
|
||||
|
||||
it('Should send action dashboardInitFetching', () => {
|
||||
expect(ctx.actions[0].type).toBe(dashboardInitFetching.type);
|
||||
});
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { locationUtil, setWeekStart } from '@grafana/data';
|
||||
import { DataQuery, locationUtil, setWeekStart, DashboardLoadedEvent } from '@grafana/data';
|
||||
import { config, isFetchError, locationService } from '@grafana/runtime';
|
||||
import { notifyApp } from 'app/core/actions';
|
||||
import appEvents from 'app/core/app_events';
|
||||
import { createErrorNotification } from 'app/core/copy/appNotification';
|
||||
import { backendSrv } from 'app/core/services/backend_srv';
|
||||
import { keybindingSrv } from 'app/core/services/keybindingSrv';
|
||||
@ -17,6 +18,7 @@ import { initVariablesTransaction } from '../../variables/state/actions';
|
||||
import { getIfExistsLastKey } from '../../variables/state/selectors';
|
||||
|
||||
import { DashboardModel } from './DashboardModel';
|
||||
import { PanelModel } from './PanelModel';
|
||||
import { emitDashboardViewEvent } from './analyticsProcessor';
|
||||
import { dashboardInitCompleted, dashboardInitFailed, dashboardInitFetching, dashboardInitServices } from './reducers';
|
||||
|
||||
@ -106,6 +108,28 @@ async function fetchDashboard(
|
||||
}
|
||||
}
|
||||
|
||||
const getQueriesByDatasource = (
|
||||
panels: PanelModel[],
|
||||
queries: { [datasourceId: string]: DataQuery[] } = {}
|
||||
): { [datasourceId: string]: DataQuery[] } => {
|
||||
panels.forEach((panel) => {
|
||||
if (panel.panels) {
|
||||
getQueriesByDatasource(panel.panels, queries);
|
||||
} else {
|
||||
panel.targets.forEach((target) => {
|
||||
if (target.datasource?.type) {
|
||||
if (queries[target.datasource.type]) {
|
||||
queries[target.datasource.type].push(target);
|
||||
} else {
|
||||
queries[target.datasource.type] = [target];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return queries;
|
||||
};
|
||||
|
||||
/**
|
||||
* This action (or saga) does everything needed to bootstrap a dashboard & dashboard model.
|
||||
* First it handles the process of fetching the dashboard, correcting the url if required (causing redirects/url updates)
|
||||
@ -213,6 +237,17 @@ export function initDashboard(args: InitDashboardArgs): ThunkResult<void> {
|
||||
setWeekStart(config.bootData.user.weekStart);
|
||||
}
|
||||
|
||||
// Propagate an app-wide event about the dashboard being loaded
|
||||
appEvents.publish(
|
||||
new DashboardLoadedEvent({
|
||||
dashboardId: dashboard.uid,
|
||||
orgId: storeState.user.orgId,
|
||||
userId: storeState.user.user?.id,
|
||||
grafanaVersion: config.buildInfo.version,
|
||||
queries: getQueriesByDatasource(dashboard.panels),
|
||||
})
|
||||
);
|
||||
|
||||
// yay we are done
|
||||
dispatch(dashboardInitCompleted(dashboard));
|
||||
};
|
||||
|
@ -0,0 +1,45 @@
|
||||
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((e, handler) => {
|
||||
// Trigger test event
|
||||
handler(
|
||||
new DashboardLoadedEvent({
|
||||
dashboardId: 'dashboard123',
|
||||
orgId: 1,
|
||||
userId: 2,
|
||||
grafanaVersion: 'v9.0.0',
|
||||
queries: {
|
||||
'grafana-azure-monitor-datasource': [
|
||||
{ queryType: 'Azure Monitor', hide: true },
|
||||
{ queryType: 'Azure Resource Graph', hide: false },
|
||||
],
|
||||
},
|
||||
})
|
||||
);
|
||||
}),
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
describe('queriesOnInitDashboard', () => {
|
||||
it('should report a `grafana_ds_azuremonitor_dashboard_loaded` interaction ', () => {
|
||||
// subscribeDashboardLoadedEvent();
|
||||
expect(reportInteraction).toHaveBeenCalledWith('grafana_ds_azuremonitor_dashboard_loaded', {
|
||||
dashboard_id: 'dashboard123',
|
||||
grafana_version: 'v9.0.0',
|
||||
org_id: 1,
|
||||
user_id: 2,
|
||||
queries: [
|
||||
{ query_type: 'Azure Monitor', hidden: true },
|
||||
{ query_type: 'Azure Resource Graph', hidden: false },
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
@ -1,10 +1,32 @@
|
||||
import { DataSourcePlugin } from '@grafana/data';
|
||||
import { DataSourcePlugin, DashboardLoadedEvent } from '@grafana/data';
|
||||
import { reportInteraction, getAppEvents } from '@grafana/runtime';
|
||||
|
||||
import { ConfigEditor } from './components/ConfigEditor';
|
||||
import AzureMonitorQueryEditor from './components/QueryEditor';
|
||||
import Datasource from './datasource';
|
||||
import { id } from './plugin.json';
|
||||
import { AzureMonitorQuery, AzureDataSourceJsonData } from './types';
|
||||
|
||||
export const plugin = new DataSourcePlugin<Datasource, AzureMonitorQuery, AzureDataSourceJsonData>(Datasource)
|
||||
.setConfigEditor(ConfigEditor)
|
||||
.setQueryEditor(AzureMonitorQueryEditor);
|
||||
|
||||
// Track dashboard loads to RudderStack
|
||||
getAppEvents().subscribe<DashboardLoadedEvent<AzureMonitorQuery>>(
|
||||
DashboardLoadedEvent,
|
||||
({ payload: { dashboardId, orgId, userId, grafanaVersion, queries } }) => {
|
||||
const azureQueries = queries[id];
|
||||
if (azureQueries && azureQueries.length > 0) {
|
||||
reportInteraction('grafana_ds_azuremonitor_dashboard_loaded', {
|
||||
grafana_version: grafanaVersion,
|
||||
dashboard_id: dashboardId,
|
||||
org_id: orgId,
|
||||
user_id: userId,
|
||||
queries: azureQueries.map((query: AzureMonitorQuery) => ({
|
||||
hidden: !!query.hide,
|
||||
query_type: query.queryType,
|
||||
})),
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user