Explore: Fix queries (cached & non) count in usage insights (#78097)

* Fix: Fix queries (cached & non) count in usage insights

* also keep deprecated error property

* Fix & refactor tests
pull/76102/head
Giordano Ricci 2 years ago committed by GitHub
parent 0a5a86f20b
commit 42a3f36c18
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 296
      public/app/features/query/state/queryAnalytics.test.ts
  2. 31
      public/app/features/query/state/queryAnalytics.ts

@ -2,11 +2,12 @@ import {
CoreApp, CoreApp,
DataFrame, DataFrame,
DataQueryError, DataQueryError,
DataQueryRequest, getDefaultTimeRange,
DataSourceApi, DataSourceApi,
dateTime, dateTime,
LoadingState, LoadingState,
PanelData, PanelData,
DataQueryRequest,
} from '@grafana/data'; } from '@grafana/data';
import { MetaAnalyticsEventName, reportMetaAnalytics } from '@grafana/runtime'; import { MetaAnalyticsEventName, reportMetaAnalytics } from '@grafana/runtime';
@ -84,155 +85,196 @@ const multipleDataframesWithSameRefId = [
}, },
]; ];
function getTestData(requestApp: string, series: DataFrame[] = []): PanelData { function getTestData(
overrides: Partial<DataQueryRequest> = {},
series?: DataFrame[],
errors?: DataQueryError[]
): PanelData {
const now = dateTime(); const now = dateTime();
return { return {
request: { request: {
app: requestApp, app: CoreApp.Dashboard,
panelId: 2,
startTime: now.unix(), startTime: now.unix(),
endTime: now.add(1, 's').unix(), endTime: now.add(1, 's').unix(),
} as DataQueryRequest, interval: '1s',
series, intervalMs: 1000,
state: LoadingState.Done, range: getDefaultTimeRange(),
timeRange: { requestId: '1',
from: dateTime(), scopedVars: {},
to: dateTime(), targets: [],
raw: { from: '1h', to: 'now' }, timezone: 'utc',
...overrides,
}, },
}; series: series || [],
}
function getTestDataForExplore(requestApp: string, series: DataFrame[] = []): PanelData {
const now = dateTime();
const error: DataQueryError = { message: 'test error' };
return {
request: {
app: requestApp,
startTime: now.unix(),
endTime: now.add(1, 's').unix(),
} as DataQueryRequest,
series,
state: LoadingState.Done, state: LoadingState.Done,
timeRange: { timeRange: getDefaultTimeRange(),
from: dateTime(), errors,
to: dateTime(),
raw: { from: '1h', to: 'now' },
},
error: error,
}; };
} }
describe('emitDataRequestEvent - from a dashboard panel', () => { describe('emitDataRequestEvent', () => {
it('Should report meta analytics', () => { describe('From a dashboard panel', () => {
const data = getTestData(CoreApp.Dashboard); it('Should report meta analytics', () => {
emitDataRequestEvent(datasource)(data); const data = getTestData({
expect(reportMetaAnalytics).toBeCalledTimes(1);
expect(reportMetaAnalytics).toBeCalledWith(
expect.objectContaining({
eventName: MetaAnalyticsEventName.DataRequest,
datasourceName: datasource.name,
datasourceUid: datasource.uid,
datasourceType: datasource.type,
source: 'dashboard',
panelId: 2, panelId: 2,
dashboardUid: 'test', // from dashboard srv });
dataSize: 0, emitDataRequestEvent(datasource)(data);
duration: 1,
totalQueries: 0,
cachedQueries: 0,
})
);
});
it('Should report meta analytics with counts for cached and total queries', () => { expect(reportMetaAnalytics).toBeCalledTimes(1);
const data = getTestData(CoreApp.Dashboard, partiallyCachedSeries); expect(reportMetaAnalytics).toBeCalledWith(
emitDataRequestEvent(datasource)(data); expect.objectContaining({
eventName: MetaAnalyticsEventName.DataRequest,
expect(reportMetaAnalytics).toBeCalledTimes(1); datasourceName: datasource.name,
expect(reportMetaAnalytics).toBeCalledWith( datasourceUid: datasource.uid,
expect.objectContaining({ datasourceType: datasource.type,
eventName: MetaAnalyticsEventName.DataRequest, source: CoreApp.Dashboard,
datasourceName: datasource.name, panelId: 2,
datasourceUid: datasource.uid, dashboardUid: 'test', // from dashboard srv
datasourceType: datasource.type, dataSize: 0,
source: 'dashboard', duration: 1,
panelId: 2, totalQueries: 0,
dashboardUid: 'test', cachedQueries: 0,
dataSize: 2, })
duration: 1, );
totalQueries: 2, });
cachedQueries: 1,
}) it('Should report meta analytics with counts for cached and total queries', () => {
); const data = getTestData(
{
panelId: 2,
},
partiallyCachedSeries
);
emitDataRequestEvent(datasource)(data);
expect(reportMetaAnalytics).toBeCalledTimes(1);
expect(reportMetaAnalytics).toBeCalledWith(
expect.objectContaining({
eventName: MetaAnalyticsEventName.DataRequest,
datasourceName: datasource.name,
datasourceUid: datasource.uid,
datasourceType: datasource.type,
source: CoreApp.Dashboard,
panelId: 2,
dashboardUid: 'test',
dataSize: 2,
duration: 1,
totalQueries: 2,
cachedQueries: 1,
})
);
});
it('Should report meta analytics with counts for cached and total queries when same refId spread across multiple DataFrames', () => {
const data = getTestData(
{
panelId: 2,
},
multipleDataframesWithSameRefId
);
emitDataRequestEvent(datasource)(data);
expect(reportMetaAnalytics).toBeCalledTimes(1);
expect(reportMetaAnalytics).toBeCalledWith(
expect.objectContaining({
eventName: MetaAnalyticsEventName.DataRequest,
datasourceName: datasource.name,
datasourceUid: datasource.uid,
datasourceType: datasource.type,
source: CoreApp.Dashboard,
panelId: 2,
dashboardUid: 'test', // from dashboard srv
dataSize: 2,
duration: 1,
totalQueries: 1,
cachedQueries: 1,
})
);
});
it('Should not report meta analytics twice if the request receives multiple responses', () => {
const data = getTestData();
const fn = emitDataRequestEvent(datasource);
fn(data);
fn(data);
expect(reportMetaAnalytics).toBeCalledTimes(1);
});
it('Should not report meta analytics in edit mode', () => {
mockGetUrlSearchParams.mockImplementationOnce(() => {
return { editPanel: 2 };
});
const data = getTestData();
emitDataRequestEvent(datasource)(data);
expect(reportMetaAnalytics).not.toBeCalled();
});
}); });
it('Should report meta analytics with counts for cached and total queries when same refId spread across multiple DataFrames', () => { // Previously we filtered out Explore and Correlations events due to too many errors being generated while a user is building a query
const data = getTestData(CoreApp.Dashboard, multipleDataframesWithSameRefId); // This tests that we send an event for both queries but do not record errors
emitDataRequestEvent(datasource)(data); describe('From Explore', () => {
const data = getTestData(
expect(reportMetaAnalytics).toBeCalledTimes(1); {
expect(reportMetaAnalytics).toBeCalledWith( app: CoreApp.Explore,
expect.objectContaining({ },
eventName: MetaAnalyticsEventName.DataRequest, undefined,
datasourceName: datasource.name, [{ message: 'test error' }]
datasourceUid: datasource.uid,
datasourceType: datasource.type,
source: 'dashboard',
panelId: 2,
dashboardUid: 'test', // from dashboard srv
dataSize: 2,
duration: 1,
totalQueries: 1,
cachedQueries: 1,
})
); );
});
it('Should not report meta analytics twice if the request receives multiple responses', () => { it('Should report meta analytics', () => {
const data = getTestData(CoreApp.Dashboard); emitDataRequestEvent(datasource)(data);
const fn = emitDataRequestEvent(datasource);
fn(data);
fn(data);
expect(reportMetaAnalytics).toBeCalledTimes(1);
});
it('Should not report meta analytics in edit mode', () => { expect(reportMetaAnalytics).toBeCalledTimes(1);
mockGetUrlSearchParams.mockImplementationOnce(() => { expect(reportMetaAnalytics).toBeCalledWith(
return { editPanel: 2 }; expect.objectContaining({
eventName: MetaAnalyticsEventName.DataRequest,
source: CoreApp.Explore,
datasourceName: 'test',
datasourceUid: 'test',
dataSize: 0,
duration: 1,
totalQueries: 0,
})
);
});
it('Should not report errors', () => {
emitDataRequestEvent(datasource)(data);
expect(reportMetaAnalytics).toBeCalledTimes(1);
expect(reportMetaAnalytics).toBeCalledWith(expect.not.objectContaining({ error: 'test error' }));
}); });
const data = getTestData(CoreApp.Dashboard);
emitDataRequestEvent(datasource)(data);
expect(reportMetaAnalytics).not.toBeCalled();
}); });
});
// Previously we filtered out Explore events due to too many errors being generated while a user is building a query // Previously we filtered out Explore and Correlations 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 // This tests that we send an event for both queries but do not record errors
describe('emitDataRequestEvent - from Explore', () => { describe('From Correlations', () => {
it('Should report meta analytics', () => { const data = getTestData(
const data = getTestDataForExplore(CoreApp.Explore); {
emitDataRequestEvent(datasource)(data); app: CoreApp.Correlations,
},
expect(reportMetaAnalytics).toBeCalledTimes(1); undefined,
expect(reportMetaAnalytics).toBeCalledWith( [{ message: 'some error' }]
expect.objectContaining({
eventName: MetaAnalyticsEventName.DataRequest,
source: 'explore',
datasourceName: 'test',
datasourceUid: 'test',
dataSize: 0,
duration: 1,
totalQueries: 0,
})
); );
});
describe('emitDataRequestEvent - from Explore', () => { it('Should report meta analytics', () => {
emitDataRequestEvent(datasource)(data);
expect(reportMetaAnalytics).toBeCalledTimes(1);
expect(reportMetaAnalytics).toBeCalledWith(
expect.objectContaining({
eventName: MetaAnalyticsEventName.DataRequest,
source: CoreApp.Correlations,
datasourceName: 'test',
datasourceUid: 'test',
dataSize: 0,
duration: 1,
totalQueries: 0,
})
);
});
it('Should not report errors', () => { it('Should not report errors', () => {
const data = getTestDataForExplore(CoreApp.Explore);
emitDataRequestEvent(datasource)(data); emitDataRequestEvent(datasource)(data);
expect(reportMetaAnalytics).toBeCalledTimes(1); expect(reportMetaAnalytics).toBeCalledTimes(1);

@ -1,4 +1,4 @@
import { PanelData, LoadingState, DataSourceApi, CoreApp, urlUtil } from '@grafana/data'; import { PanelData, LoadingState, DataSourceApi, urlUtil, CoreApp } from '@grafana/data';
import { reportMetaAnalytics, MetaAnalyticsEventName, DataRequestEventPayload } from '@grafana/runtime'; import { reportMetaAnalytics, MetaAnalyticsEventName, DataRequestEventPayload } from '@grafana/runtime';
import { getDashboardSrv } from '../../dashboard/services/DashboardSrv'; import { getDashboardSrv } from '../../dashboard/services/DashboardSrv';
@ -31,10 +31,10 @@ export function emitDataRequestEvent(datasource: DataSourceApi) {
duration: data.request.endTime! - data.request.startTime, duration: data.request.endTime! - data.request.startTime,
}; };
if (data.request.app === CoreApp.Explore || data.request.app === CoreApp.Correlations) { enrichWithInfo(eventData, data);
enrichWithInfo(eventData, data);
} else { if (data.request.app !== CoreApp.Explore && data.request.app !== CoreApp.Correlations) {
enrichWithDashboardInfo(eventData, data); enrichWithErrorData(eventData, data);
} }
if (data.series && data.series.length > 0) { if (data.series && data.series.length > 0) {
@ -50,11 +50,6 @@ export function emitDataRequestEvent(datasource: DataSourceApi) {
}; };
function enrichWithInfo(eventData: DataRequestEventPayload, data: PanelData) { function enrichWithInfo(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 } = {}; const queryCacheStatus: { [key: string]: boolean } = {};
for (let i = 0; i < data.series.length; i++) { for (let i = 0; i < data.series.length; i++) {
const refId = data.series[i].refId; const refId = data.series[i].refId;
@ -62,12 +57,10 @@ export function emitDataRequestEvent(datasource: DataSourceApi) {
queryCacheStatus[refId] = data.series[i].meta?.isCachedResponse ?? false; queryCacheStatus[refId] = data.series[i].meta?.isCachedResponse ?? false;
} }
} }
const totalQueries = Object.keys(queryCacheStatus).length;
const cachedQueries = Object.values(queryCacheStatus).filter((val) => val === true).length;
eventData.totalQueries = Object.keys(queryCacheStatus).length;
eventData.cachedQueries = Object.values(queryCacheStatus).filter((val) => val === true).length;
eventData.panelId = data.request!.panelId; eventData.panelId = data.request!.panelId;
eventData.totalQueries = totalQueries;
eventData.cachedQueries = cachedQueries;
const dashboard = getDashboardSrv().getCurrent(); const dashboard = getDashboardSrv().getCurrent();
if (dashboard) { if (dashboard) {
@ -76,9 +69,13 @@ export function emitDataRequestEvent(datasource: DataSourceApi) {
eventData.dashboardUid = dashboard.uid; eventData.dashboardUid = dashboard.uid;
eventData.folderName = dashboard.meta.folderTitle; eventData.folderName = dashboard.meta.folderTitle;
} }
}
}
if (data.error) { function enrichWithErrorData(eventData: DataRequestEventPayload, data: PanelData) {
eventData.error = data.error.message; if (data.errors?.length) {
} eventData.error = data.errors.join(', ');
} else if (data.error) {
eventData.error = data.error.message;
} }
} }

Loading…
Cancel
Save