[v9.2.x] UsageInsights: Record events for Explore queries (#59992)
* UsageInsights: Record events for Explore queries (#59931)
* usageinsights: record events for Explore queries
* usageinsights: make the source field optional
It is not logical to have it for an event like the dashboard-view
* usageinsights: add comment to Explore test
Explain why we are reversing a previous decision
(cherry picked from commit 74167b4d44)
* usageinsights: fix merge conflict
* Empty-Commit to trigger build
* usageinsights: fix broken tests after merge
This commit is contained in:
@@ -44,6 +44,7 @@ Logs of usage insights contain the following fields, where the fields followed b
|
||||
| `panelName` | string | Name of the panel of the query. |
|
||||
| `error` | string | Error returned by the query. |
|
||||
| `duration` | number | Duration of the query. |
|
||||
| `source` | string | Source of the query. For example, `dashboard` or `explore`. |
|
||||
| `orgId`\* | number | ID of the user’s organization. |
|
||||
| `orgName`\* | string | Name of the user’s organization. |
|
||||
| `timestamp`\* | string | The date and time that the request was made, in Coordinated Universal Time (UTC) in [RFC3339](https://tools.ietf.org/html/rfc3339#section-5.6) format. |
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { CoreApp } from '@grafana/data';
|
||||
|
||||
import { EchoEvent, EchoEventType } from '../services/EchoSrv';
|
||||
|
||||
/**
|
||||
@@ -20,6 +22,7 @@ export interface DashboardInfo {
|
||||
* @public
|
||||
*/
|
||||
export interface DataRequestInfo extends Partial<DashboardInfo> {
|
||||
source?: CoreApp | string;
|
||||
datasourceName: string;
|
||||
datasourceId: number;
|
||||
datasourceType: string;
|
||||
|
||||
@@ -23,6 +23,7 @@ jest.mock('@grafana/runtime', () => ({
|
||||
getTemplateSrv: () => ({
|
||||
updateTimeRange: jest.fn(),
|
||||
}),
|
||||
reportMetaAnalytics: jest.fn(),
|
||||
}));
|
||||
|
||||
function setupStore(state?: any) {
|
||||
|
||||
@@ -77,6 +77,7 @@ jest.mock('@grafana/runtime', () => ({
|
||||
get: (uid?: string) => datasources.find((ds) => ds.uid === uid) || datasources[0],
|
||||
};
|
||||
},
|
||||
reportMetaAnalytics: jest.fn(),
|
||||
}));
|
||||
|
||||
function setupQueryResponse(state: StoreState) {
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
import { CoreApp, DataFrame, DataQueryRequest, DataSourceApi, dateTime, LoadingState, PanelData } from '@grafana/data';
|
||||
import {
|
||||
CoreApp,
|
||||
DataFrame,
|
||||
DataQueryError,
|
||||
DataQueryRequest,
|
||||
DataSourceApi,
|
||||
dateTime,
|
||||
LoadingState,
|
||||
PanelData,
|
||||
} from '@grafana/data';
|
||||
import { MetaAnalyticsEventName, reportMetaAnalytics } from '@grafana/runtime';
|
||||
|
||||
import { DashboardModel } from '../../dashboard/state';
|
||||
@@ -12,6 +21,7 @@ beforeEach(() => {
|
||||
const datasource = {
|
||||
name: 'test',
|
||||
id: 1,
|
||||
uid: 'test',
|
||||
} as DataSourceApi;
|
||||
|
||||
const dashboardModel = new DashboardModel(
|
||||
@@ -94,6 +104,28 @@ function getTestData(requestApp: string, series: DataFrame[] = []): PanelData {
|
||||
};
|
||||
}
|
||||
|
||||
function getTestDataForExplore(requestApp: string, series: DataFrame[] = []): PanelData {
|
||||
const now = dateTime();
|
||||
const error: DataQueryError = { message: 'test error' };
|
||||
|
||||
return {
|
||||
request: {
|
||||
app: requestApp,
|
||||
dashboardId: 0,
|
||||
startTime: now.unix(),
|
||||
endTime: now.add(1, 's').unix(),
|
||||
} as DataQueryRequest,
|
||||
series,
|
||||
state: LoadingState.Done,
|
||||
timeRange: {
|
||||
from: dateTime(),
|
||||
to: dateTime(),
|
||||
raw: { from: '1h', to: 'now' },
|
||||
},
|
||||
error: error,
|
||||
};
|
||||
}
|
||||
|
||||
describe('emitDataRequestEvent - from a dashboard panel', () => {
|
||||
it('Should report meta analytics', () => {
|
||||
const data = getTestData(CoreApp.Dashboard);
|
||||
@@ -182,10 +214,34 @@ describe('emitDataRequestEvent - from a dashboard panel', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// Previously we filtered out Explore events due to too many errors being generated while a user is building a query
|
||||
// This tests that we send an event for Explore queries but do not record errors
|
||||
describe('emitDataRequestEvent - from Explore', () => {
|
||||
const data = getTestData(CoreApp.Explore);
|
||||
it('Should not report meta analytics', () => {
|
||||
it('Should report meta analytics', () => {
|
||||
const data = getTestDataForExplore(CoreApp.Explore);
|
||||
emitDataRequestEvent(datasource)(data);
|
||||
expect(reportMetaAnalytics).not.toBeCalled();
|
||||
|
||||
expect(reportMetaAnalytics).toBeCalledTimes(1);
|
||||
expect(reportMetaAnalytics).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
eventName: MetaAnalyticsEventName.DataRequest,
|
||||
source: 'explore',
|
||||
datasourceName: 'test',
|
||||
datasourceId: 1,
|
||||
dataSize: 0,
|
||||
duration: 1,
|
||||
totalQueries: 0,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
describe('emitDataRequestEvent - from Explore', () => {
|
||||
it('Should not report errors', () => {
|
||||
const data = getTestDataForExplore(CoreApp.Explore);
|
||||
emitDataRequestEvent(datasource)(data);
|
||||
|
||||
expect(reportMetaAnalytics).toBeCalledTimes(1);
|
||||
expect(reportMetaAnalytics).toBeCalledWith(expect.not.objectContaining({ error: 'test error' }));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { PanelData, LoadingState, DataSourceApi, CoreApp, urlUtil } from '@grafana/data';
|
||||
import { reportMetaAnalytics, MetaAnalyticsEventName, DataRequestEventPayload } from '@grafana/runtime';
|
||||
import { getConfig } from 'app/core/config';
|
||||
|
||||
import { getDashboardSrv } from '../../dashboard/services/DashboardSrv';
|
||||
|
||||
@@ -7,7 +8,7 @@ export function emitDataRequestEvent(datasource: DataSourceApi) {
|
||||
let done = false;
|
||||
|
||||
return (data: PanelData) => {
|
||||
if (!data.request || done || data.request.app === CoreApp.Explore) {
|
||||
if (!data.request || done) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -20,6 +21,40 @@ export function emitDataRequestEvent(datasource: DataSourceApi) {
|
||||
return;
|
||||
}
|
||||
|
||||
const eventData: DataRequestEventPayload = {
|
||||
eventName: MetaAnalyticsEventName.DataRequest,
|
||||
source: data.request.app,
|
||||
datasourceName: datasource.name,
|
||||
datasourceId: datasource.id,
|
||||
datasourceType: datasource.type,
|
||||
dataSize: 0,
|
||||
duration: data.request.endTime! - data.request.startTime,
|
||||
};
|
||||
|
||||
if (data.request.app === CoreApp.Explore) {
|
||||
enrichWithExploreInfo(eventData, data);
|
||||
} else {
|
||||
enrichWithDashboardInfo(eventData, data);
|
||||
}
|
||||
|
||||
if (data.series && data.series.length > 0) {
|
||||
// estimate size
|
||||
eventData.dataSize = data.series.length;
|
||||
}
|
||||
|
||||
reportMetaAnalytics(eventData);
|
||||
|
||||
// this done check is to make sure we do not double emit events in case
|
||||
// there are multiple responses with done state
|
||||
done = true;
|
||||
};
|
||||
|
||||
function enrichWithExploreInfo(eventData: DataRequestEventPayload, data: PanelData) {
|
||||
const totalQueries = Object.keys(data.series).length;
|
||||
eventData.totalQueries = totalQueries;
|
||||
}
|
||||
|
||||
function enrichWithDashboardInfo(eventData: DataRequestEventPayload, data: PanelData) {
|
||||
const queryCacheStatus: { [key: string]: boolean } = {};
|
||||
for (let i = 0; i < data.series.length; i++) {
|
||||
const refId = data.series[i].refId;
|
||||
@@ -30,42 +65,25 @@ export function emitDataRequestEvent(datasource: DataSourceApi) {
|
||||
const totalQueries = Object.keys(queryCacheStatus).length;
|
||||
const cachedQueries = Object.values(queryCacheStatus).filter((val) => val === true).length;
|
||||
|
||||
const eventData: DataRequestEventPayload = {
|
||||
eventName: MetaAnalyticsEventName.DataRequest,
|
||||
datasourceName: datasource.name,
|
||||
datasourceId: datasource.id,
|
||||
datasourceType: datasource.type,
|
||||
panelId: data.request.panelId,
|
||||
dashboardId: data.request.dashboardId,
|
||||
dataSize: 0,
|
||||
duration: data.request.endTime! - data.request.startTime,
|
||||
totalQueries,
|
||||
cachedQueries,
|
||||
};
|
||||
eventData.panelId = data.request!.panelId;
|
||||
eventData.dashboardId = data.request!.dashboardId;
|
||||
eventData.totalQueries = totalQueries;
|
||||
eventData.cachedQueries = cachedQueries;
|
||||
|
||||
// enrich with dashboard info
|
||||
const dashboard = getDashboardSrv().getCurrent();
|
||||
if (dashboard) {
|
||||
eventData.dashboardId = dashboard.id;
|
||||
eventData.dashboardName = dashboard.title;
|
||||
eventData.dashboardUid = dashboard.uid;
|
||||
eventData.folderName = dashboard.meta.folderTitle;
|
||||
eventData.publicDashboardUid = dashboard.meta.publicDashboardUid;
|
||||
}
|
||||
|
||||
if (data.series && data.series.length > 0) {
|
||||
// estimate size
|
||||
eventData.dataSize = data.series.length;
|
||||
if (getConfig().isPublicDashboardView) {
|
||||
eventData.publicDashboardUid = dashboard.meta.publicDashboardUid;
|
||||
}
|
||||
}
|
||||
|
||||
if (data.error) {
|
||||
eventData.error = data.error.message;
|
||||
}
|
||||
|
||||
reportMetaAnalytics(eventData);
|
||||
|
||||
// this done check is to make sure we do not double emit events in case
|
||||
// there are multiple responses with done state
|
||||
done = true;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user