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>> {
|
export class AnnotationChangeEvent extends BusEventWithPayload<Partial<AnnotationEvent>> {
|
||||||
static type = 'annotation-event';
|
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 { Subject } from 'rxjs';
|
||||||
|
|
||||||
import { FetchError, locationService, setEchoSrv } from '@grafana/runtime';
|
import { FetchError, locationService, setEchoSrv } from '@grafana/runtime';
|
||||||
|
import appEvents from 'app/core/app_events';
|
||||||
import { getBackendSrv } from 'app/core/services/backend_srv';
|
import { getBackendSrv } from 'app/core/services/backend_srv';
|
||||||
import { keybindingSrv } from 'app/core/services/keybindingSrv';
|
import { keybindingSrv } from 'app/core/services/keybindingSrv';
|
||||||
import { variableAdapters } from 'app/features/variables/adapters';
|
import { variableAdapters } from 'app/features/variables/adapters';
|
||||||
@ -42,6 +43,25 @@ jest.mock('app/core/services/context_srv', () => ({
|
|||||||
user: { orgId: 1, orgName: 'TestOrg' },
|
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());
|
variableAdapters.register(createConstantVariableAdapter());
|
||||||
const mockStore = configureMockStore([thunk]);
|
const mockStore = configureMockStore([thunk]);
|
||||||
@ -77,11 +97,73 @@ function describeInitScenario(description: string, scenarioFn: ScenarioFn) {
|
|||||||
id: 2,
|
id: 2,
|
||||||
targets: [
|
targets: [
|
||||||
{
|
{
|
||||||
|
datasource: {
|
||||||
|
type: 'grafana-azure-monitor-datasource',
|
||||||
|
uid: 'DSwithQueriesOnInitDashboard',
|
||||||
|
name: 'azMonitor',
|
||||||
|
},
|
||||||
|
queryType: 'Azure Log Analytics',
|
||||||
refId: 'A',
|
refId: 'A',
|
||||||
expr: 'old expr',
|
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: {
|
templating: {
|
||||||
list: [constantBuilder().build()],
|
list: [constantBuilder().build()],
|
||||||
@ -241,9 +323,65 @@ describeInitScenario('Initializing existing dashboard', (ctx) => {
|
|||||||
|
|
||||||
ctx.setup(() => {
|
ctx.setup(() => {
|
||||||
ctx.storeState.user.orgId = 12;
|
ctx.storeState.user.orgId = 12;
|
||||||
|
ctx.storeState.user.user = { id: 34 };
|
||||||
ctx.storeState.explore.left.queries = mockQueries;
|
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', () => {
|
it('Should send action dashboardInitFetching', () => {
|
||||||
expect(ctx.actions[0].type).toBe(dashboardInitFetching.type);
|
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 { config, isFetchError, locationService } from '@grafana/runtime';
|
||||||
import { notifyApp } from 'app/core/actions';
|
import { notifyApp } from 'app/core/actions';
|
||||||
|
import appEvents from 'app/core/app_events';
|
||||||
import { createErrorNotification } from 'app/core/copy/appNotification';
|
import { createErrorNotification } from 'app/core/copy/appNotification';
|
||||||
import { backendSrv } from 'app/core/services/backend_srv';
|
import { backendSrv } from 'app/core/services/backend_srv';
|
||||||
import { keybindingSrv } from 'app/core/services/keybindingSrv';
|
import { keybindingSrv } from 'app/core/services/keybindingSrv';
|
||||||
@ -17,6 +18,7 @@ import { initVariablesTransaction } from '../../variables/state/actions';
|
|||||||
import { getIfExistsLastKey } from '../../variables/state/selectors';
|
import { getIfExistsLastKey } from '../../variables/state/selectors';
|
||||||
|
|
||||||
import { DashboardModel } from './DashboardModel';
|
import { DashboardModel } from './DashboardModel';
|
||||||
|
import { PanelModel } from './PanelModel';
|
||||||
import { emitDashboardViewEvent } from './analyticsProcessor';
|
import { emitDashboardViewEvent } from './analyticsProcessor';
|
||||||
import { dashboardInitCompleted, dashboardInitFailed, dashboardInitFetching, dashboardInitServices } from './reducers';
|
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.
|
* 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)
|
* 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);
|
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
|
// yay we are done
|
||||||
dispatch(dashboardInitCompleted(dashboard));
|
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 { ConfigEditor } from './components/ConfigEditor';
|
||||||
import AzureMonitorQueryEditor from './components/QueryEditor';
|
import AzureMonitorQueryEditor from './components/QueryEditor';
|
||||||
import Datasource from './datasource';
|
import Datasource from './datasource';
|
||||||
|
import { id } from './plugin.json';
|
||||||
import { AzureMonitorQuery, AzureDataSourceJsonData } from './types';
|
import { AzureMonitorQuery, AzureDataSourceJsonData } from './types';
|
||||||
|
|
||||||
export const plugin = new DataSourcePlugin<Datasource, AzureMonitorQuery, AzureDataSourceJsonData>(Datasource)
|
export const plugin = new DataSourcePlugin<Datasource, AzureMonitorQuery, AzureDataSourceJsonData>(Datasource)
|
||||||
.setConfigEditor(ConfigEditor)
|
.setConfigEditor(ConfigEditor)
|
||||||
.setQueryEditor(AzureMonitorQueryEditor);
|
.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