[Alerting] Add telemetry for HTTP API calls with legacy terminology (#112173)

* Deprecated attributes telemetry

* Add additional term and improve test quality

* PR feedback
This commit is contained in:
Chris Roberson 2021-09-20 13:59:27 -04:00 committed by GitHub
parent b34c54e934
commit 90ec827147
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 265 additions and 4 deletions

View file

@ -11,13 +11,21 @@ import { licenseStateMock } from '../lib/license_state.mock';
import { verifyApiAccess } from '../lib/license_api_access';
import { mockHandlerArguments } from './_mock_handler_arguments';
import { rulesClientMock } from '../rules_client.mock';
import { trackLegacyTerminology } from './lib/track_legacy_terminology';
import { usageCountersServiceMock } from 'src/plugins/usage_collection/server/usage_counters/usage_counters_service.mock';
const rulesClient = rulesClientMock.create();
const mockUsageCountersSetup = usageCountersServiceMock.createSetupContract();
const mockUsageCounter = mockUsageCountersSetup.createUsageCounter('test');
jest.mock('../lib/license_api_access.ts', () => ({
verifyApiAccess: jest.fn(),
}));
jest.mock('./lib/track_legacy_terminology', () => ({
trackLegacyTerminology: jest.fn(),
}));
beforeEach(() => {
jest.resetAllMocks();
});
@ -147,4 +155,39 @@ describe('aggregateRulesRoute', () => {
expect(verifyApiAccess).toHaveBeenCalledWith(licenseState);
});
it('should track calls with deprecated param values', async () => {
const licenseState = licenseStateMock.create();
const router = httpServiceMock.createRouter();
aggregateRulesRoute(router, licenseState, mockUsageCounter);
const aggregateResult = {
alertExecutionStatus: {
ok: 15,
error: 2,
active: 23,
pending: 1,
unknown: 0,
},
};
rulesClient.aggregate.mockResolvedValueOnce(aggregateResult);
const [, handler] = router.get.mock.calls[0];
const [context, req, res] = mockHandlerArguments(
{ rulesClient },
{
params: {},
query: {
search_fields: ['alertTypeId:1', 'message:foo'],
search: 'alertTypeId:2',
},
},
['ok']
);
await handler(context, req, res);
expect(trackLegacyTerminology).toHaveBeenCalledTimes(1);
expect((trackLegacyTerminology as jest.Mock).mock.calls[0][0]).toStrictEqual([
'alertTypeId:2',
['alertTypeId:1', 'message:foo'],
]);
});
});

View file

@ -7,10 +7,12 @@
import { IRouter } from 'kibana/server';
import { schema } from '@kbn/config-schema';
import { UsageCounter } from 'src/plugins/usage_collection/server';
import { ILicenseState } from '../lib';
import { AggregateResult, AggregateOptions } from '../rules_client';
import { RewriteResponseCase, RewriteRequestCase, verifyAccessAndContext } from './lib';
import { AlertingRequestHandlerContext, INTERNAL_BASE_ALERTING_API_PATH } from '../types';
import { trackLegacyTerminology } from './lib/track_legacy_terminology';
// config definition
const querySchema = schema.object({
@ -53,7 +55,8 @@ const rewriteBodyRes: RewriteResponseCase<AggregateResult> = ({
export const aggregateRulesRoute = (
router: IRouter<AlertingRequestHandlerContext>,
licenseState: ILicenseState
licenseState: ILicenseState,
usageCounter?: UsageCounter
) => {
router.get(
{
@ -69,6 +72,10 @@ export const aggregateRulesRoute = (
...req.query,
has_reference: req.query.has_reference || undefined,
});
trackLegacyTerminology(
[req.query.search, req.query.search_fields].filter(Boolean) as string[],
usageCounter
);
const aggregateResult = await rulesClient.aggregate({ options });
return res.ok({
body: rewriteBodyRes(aggregateResult),

View file

@ -11,13 +11,21 @@ import { licenseStateMock } from '../lib/license_state.mock';
import { verifyApiAccess } from '../lib/license_api_access';
import { mockHandlerArguments } from './_mock_handler_arguments';
import { rulesClientMock } from '../rules_client.mock';
import { trackLegacyTerminology } from './lib/track_legacy_terminology';
import { usageCountersServiceMock } from 'src/plugins/usage_collection/server/usage_counters/usage_counters_service.mock';
const rulesClient = rulesClientMock.create();
const mockUsageCountersSetup = usageCountersServiceMock.createSetupContract();
const mockUsageCounter = mockUsageCountersSetup.createUsageCounter('test');
jest.mock('../lib/license_api_access.ts', () => ({
verifyApiAccess: jest.fn(),
}));
jest.mock('./lib/track_legacy_terminology', () => ({
trackLegacyTerminology: jest.fn(),
}));
beforeEach(() => {
jest.resetAllMocks();
});
@ -145,4 +153,38 @@ describe('findRulesRoute', () => {
expect(verifyApiAccess).toHaveBeenCalledWith(licenseState);
});
it('should track calls with deprecated param values', async () => {
const licenseState = licenseStateMock.create();
const router = httpServiceMock.createRouter();
findRulesRoute(router, licenseState, mockUsageCounter);
const findResult = {
page: 1,
perPage: 1,
total: 0,
data: [],
};
rulesClient.find.mockResolvedValueOnce(findResult);
const [, handler] = router.get.mock.calls[0];
const [context, req, res] = mockHandlerArguments(
{ rulesClient },
{
params: {},
query: {
search_fields: ['alertTypeId:1', 'message:foo'],
search: 'alertTypeId:2',
sort_field: 'alertTypeId',
},
},
['ok']
);
await handler(context, req, res);
expect(trackLegacyTerminology).toHaveBeenCalledTimes(1);
expect((trackLegacyTerminology as jest.Mock).mock.calls[0][0]).toStrictEqual([
'alertTypeId:2',
['alertTypeId:1', 'message:foo'],
'alertTypeId',
]);
});
});

View file

@ -7,11 +7,13 @@
import { omit } from 'lodash';
import { IRouter } from 'kibana/server';
import { UsageCounter } from 'src/plugins/usage_collection/server';
import { schema } from '@kbn/config-schema';
import { ILicenseState } from '../lib';
import { FindOptions, FindResult } from '../rules_client';
import { RewriteRequestCase, RewriteResponseCase, verifyAccessAndContext } from './lib';
import { AlertTypeParams, AlertingRequestHandlerContext, BASE_ALERTING_API_PATH } from '../types';
import { trackLegacyTerminology } from './lib/track_legacy_terminology';
// query definition
const querySchema = schema.object({
@ -107,7 +109,8 @@ const rewriteBodyRes: RewriteResponseCase<FindResult<AlertTypeParams>> = ({
export const findRulesRoute = (
router: IRouter<AlertingRequestHandlerContext>,
licenseState: ILicenseState
licenseState: ILicenseState,
usageCounter?: UsageCounter
) => {
router.get(
{
@ -120,6 +123,13 @@ export const findRulesRoute = (
verifyAccessAndContext(licenseState, async function (context, req, res) {
const rulesClient = context.alerting.getRulesClient();
trackLegacyTerminology(
[req.query.search, req.query.search_fields, req.query.sort_field].filter(
Boolean
) as string[],
usageCounter
);
const options = rewriteQueryReq({
...req.query,
has_reference: req.query.has_reference || undefined,

View file

@ -38,7 +38,7 @@ export interface RouteOptions {
}
export function defineRoutes(opts: RouteOptions) {
const { router, licenseState, encryptedSavedObjects } = opts;
const { router, licenseState, encryptedSavedObjects, usageCounter } = opts;
defineLegacyRoutes(opts);
createRuleRoute(opts);
@ -49,7 +49,7 @@ export function defineRoutes(opts: RouteOptions) {
aggregateRulesRoute(router, licenseState);
disableRuleRoute(router, licenseState);
enableRuleRoute(router, licenseState);
findRulesRoute(router, licenseState);
findRulesRoute(router, licenseState, usageCounter);
getRuleAlertSummaryRoute(router, licenseState);
getRuleStateRoute(router, licenseState);
healthRoute(router, licenseState, encryptedSavedObjects);

View file

@ -12,6 +12,7 @@ import { verifyApiAccess } from '../../lib/license_api_access';
import { mockHandlerArguments } from './../_mock_handler_arguments';
import { rulesClientMock } from '../../rules_client.mock';
import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage';
import { trackLegacyTerminology } from '../lib/track_legacy_terminology';
import { usageCountersServiceMock } from 'src/plugins/usage_collection/server/usage_counters/usage_counters_service.mock';
const rulesClient = rulesClientMock.create();
@ -26,6 +27,10 @@ jest.mock('../../lib/license_api_access.ts', () => ({
verifyApiAccess: jest.fn(),
}));
jest.mock('../lib/track_legacy_terminology', () => ({
trackLegacyTerminology: jest.fn(),
}));
beforeEach(() => {
jest.resetAllMocks();
});
@ -166,4 +171,29 @@ describe('aggregateAlertRoute', () => {
await handler(context, req, res);
expect(trackLegacyRouteUsage).toHaveBeenCalledWith('aggregate', mockUsageCounter);
});
it('should track calls with deprecated param values', async () => {
const licenseState = licenseStateMock.create();
const router = httpServiceMock.createRouter();
aggregateAlertRoute(router, licenseState, mockUsageCounter);
const [, handler] = router.get.mock.calls[0];
const [context, req, res] = mockHandlerArguments(
{ rulesClient },
{
params: {},
query: {
search_fields: ['alertTypeId:1', 'message:foo'],
search: 'alertTypeId:2',
},
},
['ok']
);
await handler(context, req, res);
expect(trackLegacyTerminology).toHaveBeenCalledTimes(1);
expect((trackLegacyTerminology as jest.Mock).mock.calls[0][0]).toStrictEqual([
'alertTypeId:2',
['alertTypeId:1', 'message:foo'],
]);
});
});

View file

@ -14,6 +14,7 @@ import { LEGACY_BASE_ALERT_API_PATH } from '../../../common';
import { renameKeys } from './../lib/rename_keys';
import { FindOptions } from '../../rules_client';
import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage';
import { trackLegacyTerminology } from '../lib/track_legacy_terminology';
// config definition
const querySchema = schema.object({
@ -55,6 +56,10 @@ export const aggregateAlertRoute = (
const rulesClient = context.alerting.getRulesClient();
trackLegacyRouteUsage('aggregate', usageCounter);
trackLegacyTerminology(
[req.query.search, req.query.search_fields].filter(Boolean) as string[],
usageCounter
);
const query = req.query;
const renameMap = {

View file

@ -12,6 +12,7 @@ import { verifyApiAccess } from '../../lib/license_api_access';
import { mockHandlerArguments } from './../_mock_handler_arguments';
import { rulesClientMock } from '../../rules_client.mock';
import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage';
import { trackLegacyTerminology } from '../lib/track_legacy_terminology';
const rulesClient = rulesClientMock.create();
@ -23,6 +24,10 @@ jest.mock('../../lib/track_legacy_route_usage', () => ({
trackLegacyRouteUsage: jest.fn(),
}));
jest.mock('../lib/track_legacy_terminology', () => ({
trackLegacyTerminology: jest.fn(),
}));
beforeEach(() => {
jest.resetAllMocks();
});
@ -160,4 +165,33 @@ describe('findAlertRoute', () => {
await handler(context, req, res);
expect(trackLegacyRouteUsage).toHaveBeenCalledWith('find', mockUsageCounter);
});
it('should track calls with deprecated param values', async () => {
const licenseState = licenseStateMock.create();
const router = httpServiceMock.createRouter();
const mockUsageCountersSetup = usageCountersServiceMock.createSetupContract();
const mockUsageCounter = mockUsageCountersSetup.createUsageCounter('test');
findAlertRoute(router, licenseState, mockUsageCounter);
const [, handler] = router.get.mock.calls[0];
const [context, req, res] = mockHandlerArguments(
{ rulesClient },
{
params: {},
query: {
search_fields: ['alertTypeId:1', 'message:foo'],
search: 'alertTypeId:2',
sort_field: 'alertTypeId',
},
},
['ok']
);
await handler(context, req, res);
expect(trackLegacyTerminology).toHaveBeenCalledTimes(1);
expect((trackLegacyTerminology as jest.Mock).mock.calls[0][0]).toStrictEqual([
'alertTypeId:2',
['alertTypeId:1', 'message:foo'],
'alertTypeId',
]);
});
});

View file

@ -15,6 +15,7 @@ import { LEGACY_BASE_ALERT_API_PATH } from '../../../common';
import { renameKeys } from './../lib/rename_keys';
import { FindOptions } from '../../rules_client';
import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage';
import { trackLegacyTerminology } from '../lib/track_legacy_terminology';
// config definition
const querySchema = schema.object({
@ -59,6 +60,12 @@ export const findAlertRoute = (
return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' });
}
trackLegacyRouteUsage('find', usageCounter);
trackLegacyTerminology(
[req.query.search, req.query.search_fields, req.query.sort_field].filter(
Boolean
) as string[],
usageCounter
);
const rulesClient = context.alerting.getRulesClient();
const query = req.query;

View file

@ -0,0 +1,48 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { usageCountersServiceMock } from 'src/plugins/usage_collection/server/usage_counters/usage_counters_service.mock';
import { trackLegacyTerminology, LEGACY_TERMS } from './track_legacy_terminology';
describe('trackLegacyTerminology', () => {
it('should call `usageCounter.incrementCounter`', () => {
const mockUsageCountersSetup = usageCountersServiceMock.createSetupContract();
const mockUsageCounter = mockUsageCountersSetup.createUsageCounter('test');
trackLegacyTerminology(
['shouldNotMatch', LEGACY_TERMS.map((lt) => `${lt}foo`)],
mockUsageCounter
);
expect(mockUsageCounter.incrementCounter).toHaveBeenCalledTimes(LEGACY_TERMS.length);
LEGACY_TERMS.forEach((legacyTerm, index) => {
expect((mockUsageCounter.incrementCounter as jest.Mock).mock.calls[index][0]).toStrictEqual({
counterName: `legacyTerm_${legacyTerm}`,
counterType: 'legacyTerminology',
incrementBy: 1,
});
});
});
it('should do nothing if no usage counter is provided', () => {
let err;
try {
trackLegacyTerminology(['test'], undefined);
} catch (e) {
err = e;
}
expect(err).toBeUndefined();
});
it('should do nothing if no terms are provided', () => {
let err;
try {
trackLegacyTerminology([], undefined);
} catch (e) {
err = e;
}
expect(err).toBeUndefined();
});
});

View file

@ -0,0 +1,35 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { flatten } from 'lodash';
import { UsageCounter } from 'src/plugins/usage_collection/server';
export const LEGACY_TERMS = ['alertTypeId', 'actionTypeId'];
export function trackLegacyTerminology(
terms: Array<string | string[]>,
usageCounter?: UsageCounter
) {
if (!usageCounter) {
return null;
}
if (!terms || terms.length === 0) {
return null;
}
for (const legacyTerm of LEGACY_TERMS) {
for (const term of flatten(terms)) {
if (term.includes(legacyTerm)) {
usageCounter.incrementCounter({
counterName: `legacyTerm_${legacyTerm}`,
counterType: 'legacyTerminology',
incrementBy: 1,
});
}
}
}
}