Integrate RuleExecutionLogClient with new rule types (#107624)

This commit is contained in:
Dmitry Shevchenko 2021-08-04 21:28:12 +02:00 committed by GitHub
parent 9b55868a15
commit 0f2d837a2b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 222 additions and 145 deletions

View file

@ -5,8 +5,10 @@
* 2.0.
*/
import { ruleDataPluginServiceMock } from './rule_data_plugin_service/rule_data_plugin_service.mock';
import { createLifecycleAlertServicesMock } from './utils/lifecycle_alert_services_mock';
export const ruleRegistryMocks = {
createLifecycleAlertServices: createLifecycleAlertServicesMock,
createRuleDataPluginService: ruleDataPluginServiceMock.create,
};

View file

@ -6,11 +6,11 @@
*/
import type { PublicMethodsOf } from '@kbn/utility-types';
import { RuleDataPluginService, RuleDataPluginServiceConstructorOptions } from './';
import { RuleDataPluginService } from './';
type Schema = PublicMethodsOf<RuleDataPluginService>;
const createRuleDataPluginServiceMock = (_: RuleDataPluginServiceConstructorOptions) => {
const createRuleDataPluginService = () => {
const mocked: jest.Mocked<Schema> = {
init: jest.fn(),
isReady: jest.fn(),
@ -27,9 +27,7 @@ const createRuleDataPluginServiceMock = (_: RuleDataPluginServiceConstructorOpti
};
export const ruleDataPluginServiceMock: {
create: (
_: RuleDataPluginServiceConstructorOptions
) => jest.Mocked<PublicMethodsOf<RuleDataPluginService>>;
create: () => jest.Mocked<PublicMethodsOf<RuleDataPluginService>>;
} = {
create: createRuleDataPluginServiceMock,
create: createRuleDataPluginService,
};

View file

@ -6,17 +6,18 @@
*/
import { merge } from 'lodash';
import { RuleDataPluginService } from '../../../../../../rule_registry/server';
import { RuleExecutionStatus } from '../../../../../common/detection_engine/schemas/common/schemas';
import { IRuleStatusSOAttributes } from '../../rules/types';
import { RuleRegistryLogClient } from '../rule_registry_log_client/rule_registry_log_client';
import {
CreateExecutionLogArgs,
ExecutionMetric,
ExecutionMetricArgs,
FindBulkExecutionLogArgs,
FindExecutionLogArgs,
IRuleDataPluginService,
IRuleExecutionLogClient,
LogStatusChangeArgs,
UpdateExecutionLogArgs,
} from '../types';
/**
@ -25,7 +26,7 @@ import {
export class RuleRegistryAdapter implements IRuleExecutionLogClient {
private ruleRegistryClient: RuleRegistryLogClient;
constructor(ruleDataService: RuleDataPluginService) {
constructor(ruleDataService: IRuleDataPluginService) {
this.ruleRegistryClient = new RuleRegistryLogClient(ruleDataService);
}
@ -58,37 +59,45 @@ export class RuleRegistryAdapter implements IRuleExecutionLogClient {
return merge(statusesById, lastErrorsById);
}
public async create(event: IRuleStatusSOAttributes, spaceId: string) {
if (event.status) {
public async create({ attributes, spaceId }: CreateExecutionLogArgs) {
if (attributes.status) {
await this.ruleRegistryClient.logStatusChange({
ruleId: event.alertId,
newStatus: event.status,
ruleId: attributes.alertId,
newStatus: attributes.status,
spaceId,
});
}
if (event.bulkCreateTimeDurations) {
if (attributes.bulkCreateTimeDurations) {
await this.ruleRegistryClient.logExecutionMetric({
ruleId: event.alertId,
ruleId: attributes.alertId,
metric: ExecutionMetric.indexingDurationMax,
value: Math.max(...event.bulkCreateTimeDurations.map(Number)),
value: Math.max(...attributes.bulkCreateTimeDurations.map(Number)),
spaceId,
});
}
if (event.gap) {
if (attributes.gap) {
await this.ruleRegistryClient.logExecutionMetric({
ruleId: event.alertId,
ruleId: attributes.alertId,
metric: ExecutionMetric.executionGap,
value: Number(event.gap),
value: Number(attributes.gap),
spaceId,
});
}
return {
id: '',
type: '',
score: 0,
attributes,
references: [],
};
}
public async update(id: string, event: IRuleStatusSOAttributes, spaceId: string) {
public async update({ attributes, spaceId }: UpdateExecutionLogArgs) {
// execution events are immutable, so we just use 'create' here instead of 'update'
await this.create(event, spaceId);
await this.create({ attributes, spaceId });
}
public async delete(id: string) {

View file

@ -6,18 +6,19 @@
*/
import { SavedObjectsClientContract } from '../../../../../../../../src/core/server';
import { IRuleStatusSOAttributes } from '../../rules/types';
import {
RuleStatusSavedObjectsClient,
ruleStatusSavedObjectsClientFactory,
} from '../../signals/rule_status_saved_objects_client';
import {
CreateExecutionLogArgs,
ExecutionMetric,
ExecutionMetricArgs,
FindBulkExecutionLogArgs,
FindExecutionLogArgs,
IRuleExecutionLogClient,
LogStatusChangeArgs,
UpdateExecutionLogArgs,
} from '../types';
export class SavedObjectsAdapter implements IRuleExecutionLogClient {
@ -41,12 +42,12 @@ export class SavedObjectsAdapter implements IRuleExecutionLogClient {
return this.ruleStatusClient.findBulk(ruleIds, logsCount);
}
public async create(event: IRuleStatusSOAttributes) {
await this.ruleStatusClient.create(event);
public async create({ attributes }: CreateExecutionLogArgs) {
return this.ruleStatusClient.create(attributes);
}
public async update(id: string, event: IRuleStatusSOAttributes) {
await this.ruleStatusClient.update(id, event);
public async update({ id, attributes }: UpdateExecutionLogArgs) {
await this.ruleStatusClient.update(id, attributes);
}
public async delete(id: string) {

View file

@ -6,21 +6,22 @@
*/
import { SavedObjectsClientContract } from '../../../../../../../src/core/server';
import { RuleDataPluginService } from '../../../../../rule_registry/server';
import { IRuleStatusSOAttributes } from '../rules/types';
import { RuleRegistryAdapter } from './adapters/rule_registry_dapter';
import { RuleRegistryAdapter } from './adapters/rule_registry_adapter';
import { SavedObjectsAdapter } from './adapters/saved_objects_adapter';
import {
CreateExecutionLogArgs,
ExecutionMetric,
ExecutionMetricArgs,
FindBulkExecutionLogArgs,
FindExecutionLogArgs,
IRuleDataPluginService,
IRuleExecutionLogClient,
LogStatusChangeArgs,
UpdateExecutionLogArgs,
} from './types';
export interface RuleExecutionLogClientArgs {
ruleDataService: RuleDataPluginService;
ruleDataService: IRuleDataPluginService;
savedObjectsClient: SavedObjectsClientContract;
}
@ -45,14 +46,12 @@ export class RuleExecutionLogClient implements IRuleExecutionLogClient {
return this.client.findBulk(args);
}
// TODO args as an object
public async create(event: IRuleStatusSOAttributes, spaceId: string) {
return this.client.create(event, spaceId);
public async create(args: CreateExecutionLogArgs) {
return this.client.create(args);
}
// TODO args as an object
public async update(id: string, event: IRuleStatusSOAttributes, spaceId: string) {
return this.client.update(id, event, spaceId);
public async update(args: UpdateExecutionLogArgs) {
return this.client.update(args);
}
public async delete(id: string) {

View file

@ -7,14 +7,14 @@
import { TECHNICAL_COMPONENT_TEMPLATE_NAME } from '../../../../../../rule_registry/common/assets';
import { mappingFromFieldMap } from '../../../../../../rule_registry/common/mapping_from_field_map';
import { RuleDataPluginService } from '../../../../../../rule_registry/server';
import { IRuleDataPluginService } from '../types';
import { ruleExecutionFieldMap } from './rule_execution_field_map';
/**
* @deprecated bootstrapRuleExecutionLog is kept here only as a reference. It will be superseded with EventLog implementation
*/
export const bootstrapRuleExecutionLog = async (
ruleDataService: RuleDataPluginService,
ruleDataService: IRuleDataPluginService,
indexAlias: string
) => {
const indexPattern = `${indexAlias}*`;

View file

@ -9,17 +9,22 @@ import { estypes } from '@elastic/elasticsearch';
import { EVENT_ACTION, EVENT_KIND, RULE_ID, SPACE_IDS, TIMESTAMP } from '@kbn/rule-data-utils';
import { once } from 'lodash/fp';
import moment from 'moment';
import { RuleDataClient, RuleDataPluginService } from '../../../../../../rule_registry/server';
import { RuleDataClient } from '../../../../../../rule_registry/server';
import { SERVER_APP_ID } from '../../../../../common/constants';
import { RuleExecutionStatus } from '../../../../../common/detection_engine/schemas/common/schemas';
import { invariant } from '../../../../../common/utils/invariant';
import { IRuleStatusSOAttributes } from '../../rules/types';
import { makeFloatString } from '../../signals/utils';
import { ExecutionMetric, ExecutionMetricArgs, LogStatusChangeArgs } from '../types';
import {
ExecutionMetric,
ExecutionMetricArgs,
IRuleDataPluginService,
LogStatusChangeArgs,
} from '../types';
import {
EVENTS_INDEX_PREFIX,
MESSAGE,
EVENT_SEQUENCE,
MESSAGE,
RULE_STATUS,
RULE_STATUS_SEVERITY,
} from './constants';
@ -65,7 +70,7 @@ export class RuleRegistryLogClient implements IRuleRegistryLogClient {
private sequence = 0;
private ruleDataClient: RuleDataClient;
constructor(ruleDataService: RuleDataPluginService) {
constructor(ruleDataService: IRuleDataPluginService) {
this.ruleDataClient = ruleDataService.getRuleDataClient(
SERVER_APP_ID,
EVENTS_INDEX_PREFIX,
@ -73,7 +78,7 @@ export class RuleRegistryLogClient implements IRuleRegistryLogClient {
);
}
private initialize = once(async (ruleDataService: RuleDataPluginService, indexAlias: string) => {
private initialize = once(async (ruleDataService: IRuleDataPluginService, indexAlias: string) => {
await bootstrapRuleExecutionLog(ruleDataService, indexAlias);
});

View file

@ -5,7 +5,9 @@
* 2.0.
*/
import { SavedObjectsFindResult } from '../../../../../../../src/core/server';
import { PublicMethodsOf } from '@kbn/utility-types';
import { SavedObject, SavedObjectsFindResult } from '../../../../../../../src/core/server';
import { RuleDataPluginService } from '../../../../../rule_registry/server';
import { RuleExecutionStatus } from '../../../../common/detection_engine/schemas/common/schemas';
import { IRuleStatusSOAttributes } from '../rules/types';
@ -16,6 +18,8 @@ export enum ExecutionMetric {
'indexingLookback' = 'indexingLookback',
}
export type IRuleDataPluginService = PublicMethodsOf<RuleDataPluginService>;
export type ExecutionMetricValue<T extends ExecutionMetric> = {
[ExecutionMetric.executionGap]: number;
[ExecutionMetric.searchDurationMax]: number;
@ -43,6 +47,17 @@ export interface LogStatusChangeArgs {
message?: string;
}
export interface UpdateExecutionLogArgs {
id: string;
attributes: IRuleStatusSOAttributes;
spaceId: string;
}
export interface CreateExecutionLogArgs {
attributes: IRuleStatusSOAttributes;
spaceId: string;
}
export interface ExecutionMetricArgs<T extends ExecutionMetric> {
ruleId: string;
spaceId: string;
@ -60,8 +75,8 @@ export interface IRuleExecutionLogClient {
args: FindExecutionLogArgs
) => Promise<Array<SavedObjectsFindResult<IRuleStatusSOAttributes>>>;
findBulk: (args: FindBulkExecutionLogArgs) => Promise<FindBulkExecutionLogResponse>;
create: (event: IRuleStatusSOAttributes, spaceId: string) => Promise<void>;
update: (id: string, event: IRuleStatusSOAttributes, spaceId: string) => Promise<void>;
create: (args: CreateExecutionLogArgs) => Promise<SavedObject<IRuleStatusSOAttributes>>;
update: (args: UpdateExecutionLogArgs) => Promise<void>;
delete: (id: string) => Promise<void>;
// TODO These methods are intended to supersede ones provided by RuleStatusService
logStatusChange: (args: LogStatusChangeArgs) => Promise<void>;

View file

@ -11,10 +11,10 @@ import {
AlertTypeParams,
AlertTypeState,
} from '../../../../../alerting/common';
import { AlertTypeWithExecutor, RuleDataPluginService } from '../../../../../rule_registry/server';
import { AlertTypeWithExecutor } from '../../../../../rule_registry/server';
import { RuleExecutionStatus } from '../../../../common/detection_engine/schemas/common/schemas';
import { RuleExecutionLogClient } from './rule_execution_log_client';
import { IRuleExecutionLogClient } from './types';
import { IRuleDataPluginService, IRuleExecutionLogClient } from './types';
export interface ExecutionLogServices {
ruleExecutionLogClient: IRuleExecutionLogClient;
@ -23,7 +23,7 @@ export interface ExecutionLogServices {
type WithRuleExecutionLog = (args: {
logger: Logger;
ruleDataService: RuleDataPluginService;
ruleDataService: IRuleDataPluginService;
}) => <
TState extends AlertTypeState,
TParams extends AlertTypeParams,

View file

@ -17,6 +17,7 @@ import { ConfigType } from '../../../../config';
import { AlertAttributes } from '../../signals/types';
import { createRuleMock } from './rule';
import { listMock } from '../../../../../../lists/server/mocks';
import { ruleRegistryMocks } from '../../../../../../rule_registry/server/mocks';
export const createRuleTypeMocks = () => {
/* eslint-disable @typescript-eslint/no-explicit-any */
@ -85,6 +86,7 @@ export const createRuleTypeMocks = () => {
})),
isWriteEnabled: jest.fn(() => true),
} as unknown) as RuleDataClient,
ruleDataService: ruleRegistryMocks.createRuleDataPluginService(),
},
services,
scheduleActions,

View file

@ -12,7 +12,6 @@ import { parseScheduleDates } from '@kbn/securitysolution-io-ts-utils';
import { ListArray } from '@kbn/securitysolution-io-ts-list-types';
import { toError } from '@kbn/securitysolution-list-api';
import { createPersistenceRuleTypeFactory } from '../../../../../rule_registry/server';
import { ruleStatusSavedObjectsClientFactory } from '../signals/rule_status_saved_objects_client';
import { ruleStatusServiceFactory } from '../signals/rule_status_service';
import { buildRuleMessageFactory } from './factories/build_rule_message_factory';
import {
@ -33,6 +32,7 @@ import {
import { getNotificationResultsLink } from '../notifications/utils';
import { createResultObject } from './utils';
import { bulkCreateFactory, wrapHitsFactory } from './factories';
import { RuleExecutionLogClient } from '../rule_execution_log/rule_execution_log_client';
/* eslint-disable complexity */
export const createSecurityRuleTypeFactory: CreateSecurityRuleTypeFactory = ({
@ -41,6 +41,7 @@ export const createSecurityRuleTypeFactory: CreateSecurityRuleTypeFactory = ({
logger,
mergeStrategy,
ruleDataClient,
ruleDataService,
}) => (type) => {
const persistenceRuleType = createPersistenceRuleTypeFactory({ ruleDataClient, logger });
return persistenceRuleType({
@ -62,8 +63,9 @@ export const createSecurityRuleTypeFactory: CreateSecurityRuleTypeFactory = ({
const esClient = scopedClusterClient.asCurrentUser;
const ruleStatusClient = ruleStatusSavedObjectsClientFactory(savedObjectsClient);
const ruleStatusClient = new RuleExecutionLogClient({ savedObjectsClient, ruleDataService });
const ruleStatusService = await ruleStatusServiceFactory({
spaceId,
alertId,
ruleStatusClient,
});

View file

@ -41,6 +41,7 @@ describe('Custom query alerts', () => {
logger: dependencies.logger,
mergeStrategy: 'allFields',
ruleDataClient: dependencies.ruleDataClient,
ruleDataService: dependencies.ruleDataService,
version: '1.0.0',
});
@ -88,6 +89,7 @@ describe('Custom query alerts', () => {
logger: dependencies.logger,
mergeStrategy: 'allFields',
ruleDataClient: dependencies.ruleDataClient,
ruleDataService: dependencies.ruleDataService,
version: '1.0.0',
});

View file

@ -7,16 +7,14 @@
import { Logger } from '@kbn/logging';
import { validateNonExact } from '@kbn/securitysolution-io-ts-utils';
import { PersistenceServices, RuleDataClient } from '../../../../../../rule_registry/server';
import { QUERY_ALERT_TYPE_ID } from '../../../../../common/constants';
import { ExperimentalFeatures } from '../../../../../common/experimental_features';
import { ConfigType } from '../../../../config';
import { SetupPlugins } from '../../../../plugin';
import { IRuleDataPluginService } from '../../rule_execution_log/types';
import { queryRuleParams, QueryRuleParams } from '../../schemas/rule_schemas';
import { queryExecutor } from '../../signals/executors/query';
import { createSecurityRuleTypeFactory } from '../create_security_rule_type_factory';
export const createQueryAlertType = (createOptions: {
@ -27,6 +25,7 @@ export const createQueryAlertType = (createOptions: {
mergeStrategy: ConfigType['alertMergeStrategy'];
ruleDataClient: RuleDataClient;
version: string;
ruleDataService: IRuleDataPluginService;
}) => {
const {
experimentalFeatures,
@ -36,6 +35,7 @@ export const createQueryAlertType = (createOptions: {
mergeStrategy,
ruleDataClient,
version,
ruleDataService,
} = createOptions;
const createSecurityRuleType = createSecurityRuleTypeFactory({
indexAlias,
@ -43,6 +43,7 @@ export const createQueryAlertType = (createOptions: {
logger,
mergeStrategy,
ruleDataClient,
ruleDataService,
});
return createSecurityRuleType<QueryRuleParams, {}, PersistenceServices, {}>({
id: QUERY_ALERT_TYPE_ID,

View file

@ -28,6 +28,7 @@ import {
import { BaseHit } from '../../../../common/detection_engine/types';
import { ConfigType } from '../../../config';
import { SetupPlugins } from '../../../plugin';
import { IRuleDataPluginService } from '../rule_execution_log/types';
import { RuleParams } from '../schemas/rule_schemas';
import { BuildRuleMessage } from '../signals/rule_messages';
import { AlertAttributes, BulkCreate, WrapHits } from '../signals/types';
@ -96,6 +97,7 @@ export type CreateSecurityRuleTypeFactory = (options: {
logger: Logger;
mergeStrategy: ConfigType['alertMergeStrategy'];
ruleDataClient: RuleDataClient;
ruleDataService: IRuleDataPluginService;
}) => <
TParams extends RuleParams & { index: string[] | undefined },
TAlertInstanceContext extends AlertInstanceContext,

View file

@ -42,13 +42,13 @@ export const enableRule = async ({
// set current status for this rule to be 'going to run'
if (ruleCurrentStatus && ruleCurrentStatus.length > 0) {
const currentStatusToDisable = ruleCurrentStatus[0];
await ruleStatusClient.update(
currentStatusToDisable.id,
{
await ruleStatusClient.update({
id: currentStatusToDisable.id,
attributes: {
...currentStatusToDisable.attributes,
status: RuleExecutionStatus['going to run'],
},
spaceId
);
spaceId,
});
}
};

View file

@ -8,47 +8,54 @@
import { SavedObject } from 'src/core/server';
import { IRuleStatusSOAttributes } from '../rules/types';
import { RuleStatusSavedObjectsClient } from './rule_status_saved_objects_client';
import { getRuleStatusSavedObjects } from './get_rule_status_saved_objects';
import { RuleExecutionStatus } from '../../../../common/detection_engine/schemas/common/schemas';
import { IRuleExecutionLogClient } from '../rule_execution_log/types';
import { MAX_RULE_STATUSES } from './rule_status_service';
interface RuleStatusParams {
alertId: string;
ruleStatusClient: RuleStatusSavedObjectsClient;
spaceId: string;
ruleStatusClient: IRuleExecutionLogClient;
}
export const createNewRuleStatus = async ({
alertId,
spaceId,
ruleStatusClient,
}: RuleStatusParams): Promise<SavedObject<IRuleStatusSOAttributes>> => {
const now = new Date().toISOString();
return ruleStatusClient.create({
alertId,
statusDate: now,
status: RuleExecutionStatus['going to run'],
lastFailureAt: null,
lastSuccessAt: null,
lastFailureMessage: null,
lastSuccessMessage: null,
gap: null,
bulkCreateTimeDurations: [],
searchAfterTimeDurations: [],
lastLookBackDate: null,
spaceId,
attributes: {
alertId,
statusDate: now,
status: RuleExecutionStatus['going to run'],
lastFailureAt: null,
lastSuccessAt: null,
lastFailureMessage: null,
lastSuccessMessage: null,
gap: null,
bulkCreateTimeDurations: [],
searchAfterTimeDurations: [],
lastLookBackDate: null,
},
});
};
export const getOrCreateRuleStatuses = async ({
spaceId,
alertId,
ruleStatusClient,
}: RuleStatusParams): Promise<Array<SavedObject<IRuleStatusSOAttributes>>> => {
const ruleStatuses = await getRuleStatusSavedObjects({
alertId,
ruleStatusClient,
const ruleStatuses = await ruleStatusClient.find({
spaceId,
ruleId: alertId,
logsCount: MAX_RULE_STATUSES,
});
if (ruleStatuses.length > 0) {
return ruleStatuses;
}
const newStatus = await createNewRuleStatus({ alertId, ruleStatusClient });
const newStatus = await createNewRuleStatus({ alertId, spaceId, ruleStatusClient });
return [newStatus];
};

View file

@ -1,29 +0,0 @@
/*
* 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 { SavedObjectsFindResult } from 'kibana/server';
import { IRuleStatusSOAttributes } from '../rules/types';
import { MAX_RULE_STATUSES } from './rule_status_service';
import { RuleStatusSavedObjectsClient } from './rule_status_saved_objects_client';
interface GetRuleStatusSavedObject {
alertId: string;
ruleStatusClient: RuleStatusSavedObjectsClient;
}
export const getRuleStatusSavedObjects = async ({
alertId,
ruleStatusClient,
}: GetRuleStatusSavedObject): Promise<Array<SavedObjectsFindResult<IRuleStatusSOAttributes>>> => {
return ruleStatusClient.find({
perPage: MAX_RULE_STATUSES,
sortField: 'statusDate',
sortOrder: 'desc',
search: `${alertId}`,
searchFields: ['alertId'],
});
};

View file

@ -34,6 +34,9 @@ export interface FindBulkResponse {
[key: string]: IRuleStatusSOAttributes[] | undefined;
}
/**
* @pdeprecated Use RuleExecutionLogClient instead
*/
export const ruleStatusSavedObjectsClientFactory = (
savedObjectsClient: SavedObjectsClientContract
): RuleStatusSavedObjectsClient => ({

View file

@ -5,7 +5,6 @@
* 2.0.
*/
import { ruleStatusSavedObjectsClientMock } from './__mocks__/rule_status_saved_objects_client.mock';
import {
buildRuleStatusAttributes,
RuleStatusService,
@ -14,6 +13,8 @@ import {
} from './rule_status_service';
import { exampleRuleStatus, exampleFindRuleStatusResponse } from './__mocks__/es_results';
import { RuleExecutionStatus } from '../../../../common/detection_engine/schemas/common/schemas';
import { RuleExecutionLogClient } from '../rule_execution_log/__mocks__/rule_execution_log_client';
import { UpdateExecutionLogArgs } from '../rule_execution_log/types';
const expectIsoDateString = expect.stringMatching(/2.*Z$/);
const buildStatuses = (n: number) =>
@ -87,27 +88,32 @@ describe('buildRuleStatusAttributes', () => {
describe('ruleStatusService', () => {
let currentStatus: ReturnType<typeof exampleRuleStatus>;
let ruleStatusClient: ReturnType<typeof ruleStatusSavedObjectsClientMock.create>;
let ruleStatusClient: ReturnType<typeof RuleExecutionLogClient>;
let service: RuleStatusService;
beforeEach(async () => {
currentStatus = exampleRuleStatus();
ruleStatusClient = ruleStatusSavedObjectsClientMock.create();
ruleStatusClient = new RuleExecutionLogClient();
ruleStatusClient.find.mockResolvedValue(exampleFindRuleStatusResponse([currentStatus]));
service = await ruleStatusServiceFactory({ alertId: 'mock-alert-id', ruleStatusClient });
service = await ruleStatusServiceFactory({
alertId: 'mock-alert-id',
ruleStatusClient,
spaceId: 'default',
});
});
describe('goingToRun', () => {
it('updates the current status to "going to run"', async () => {
await service.goingToRun();
expect(ruleStatusClient.update).toHaveBeenCalledWith(
currentStatus.id,
expect.objectContaining({
expect(ruleStatusClient.update).toHaveBeenCalledWith<[UpdateExecutionLogArgs]>({
id: currentStatus.id,
spaceId: 'default',
attributes: expect.objectContaining({
status: 'going to run',
statusDate: expectIsoDateString,
})
);
}),
});
});
});
@ -115,15 +121,16 @@ describe('ruleStatusService', () => {
it('updates the current status to "succeeded"', async () => {
await service.success('hey, it worked');
expect(ruleStatusClient.update).toHaveBeenCalledWith(
currentStatus.id,
expect.objectContaining({
expect(ruleStatusClient.update).toHaveBeenCalledWith<[UpdateExecutionLogArgs]>({
id: currentStatus.id,
spaceId: 'default',
attributes: expect.objectContaining({
status: 'succeeded',
statusDate: expectIsoDateString,
lastSuccessAt: expectIsoDateString,
lastSuccessMessage: 'hey, it worked',
})
);
}),
});
});
});
@ -136,15 +143,16 @@ describe('ruleStatusService', () => {
it('updates the current status to "failed"', async () => {
await service.error('oh no, it broke');
expect(ruleStatusClient.update).toHaveBeenCalledWith(
currentStatus.id,
expect.objectContaining({
expect(ruleStatusClient.update).toHaveBeenCalledWith<[UpdateExecutionLogArgs]>({
id: currentStatus.id,
spaceId: 'default',
attributes: expect.objectContaining({
status: 'failed',
statusDate: expectIsoDateString,
lastFailureAt: expectIsoDateString,
lastFailureMessage: 'oh no, it broke',
})
);
}),
});
});
it('does not delete statuses if we have less than the max number of statuses', async () => {
@ -158,7 +166,11 @@ describe('ruleStatusService', () => {
ruleStatusClient.find.mockResolvedValue(
exampleFindRuleStatusResponse(buildStatuses(MAX_RULE_STATUSES - 1))
);
service = await ruleStatusServiceFactory({ alertId: 'mock-alert-id', ruleStatusClient });
service = await ruleStatusServiceFactory({
alertId: 'mock-alert-id',
ruleStatusClient,
spaceId: 'default',
});
await service.error('oh no, it broke');
@ -170,7 +182,11 @@ describe('ruleStatusService', () => {
ruleStatusClient.find.mockResolvedValue(
exampleFindRuleStatusResponse(buildStatuses(MAX_RULE_STATUSES))
);
service = await ruleStatusServiceFactory({ alertId: 'mock-alert-id', ruleStatusClient });
service = await ruleStatusServiceFactory({
alertId: 'mock-alert-id',
ruleStatusClient,
spaceId: 'default',
});
await service.error('oh no, it broke');
@ -184,7 +200,11 @@ describe('ruleStatusService', () => {
ruleStatusClient.find.mockResolvedValue(
exampleFindRuleStatusResponse(buildStatuses(MAX_RULE_STATUSES + 1))
);
service = await ruleStatusServiceFactory({ alertId: 'mock-alert-id', ruleStatusClient });
service = await ruleStatusServiceFactory({
alertId: 'mock-alert-id',
ruleStatusClient,
spaceId: 'default',
});
await service.error('oh no, it broke');
@ -200,7 +220,11 @@ describe('ruleStatusService', () => {
ruleStatusClient.find.mockResolvedValue(
exampleFindRuleStatusResponse(buildStatuses(MAX_RULE_STATUSES))
);
service = await ruleStatusServiceFactory({ alertId: 'mock-alert-id', ruleStatusClient });
service = await ruleStatusServiceFactory({
alertId: 'mock-alert-id',
ruleStatusClient,
spaceId: 'default',
});
await service.error('oh no, it broke');
await service.error('oh no, it broke');

View file

@ -9,7 +9,7 @@ import { assertUnreachable } from '../../../../common/utility_types';
import { RuleExecutionStatus } from '../../../../common/detection_engine/schemas/common/schemas';
import { IRuleStatusSOAttributes } from '../rules/types';
import { getOrCreateRuleStatuses } from './get_or_create_rule_statuses';
import { RuleStatusSavedObjectsClient } from './rule_status_saved_objects_client';
import { IRuleExecutionLogClient } from '../rule_execution_log/types';
// 1st is mutable status, followed by 5 most recent failures
export const MAX_RULE_STATUSES = 6;
@ -78,51 +78,69 @@ export const buildRuleStatusAttributes: (
};
export const ruleStatusServiceFactory = async ({
spaceId,
alertId,
ruleStatusClient,
}: {
spaceId: string;
alertId: string;
ruleStatusClient: RuleStatusSavedObjectsClient;
ruleStatusClient: IRuleExecutionLogClient;
}): Promise<RuleStatusService> => {
return {
goingToRun: async () => {
const [currentStatus] = await getOrCreateRuleStatuses({
spaceId,
alertId,
ruleStatusClient,
});
await ruleStatusClient.update(currentStatus.id, {
...currentStatus.attributes,
...buildRuleStatusAttributes(RuleExecutionStatus['going to run']),
await ruleStatusClient.update({
id: currentStatus.id,
attributes: {
...currentStatus.attributes,
...buildRuleStatusAttributes(RuleExecutionStatus['going to run']),
},
spaceId,
});
},
success: async (message, attributes) => {
const [currentStatus] = await getOrCreateRuleStatuses({
spaceId,
alertId,
ruleStatusClient,
});
await ruleStatusClient.update(currentStatus.id, {
...currentStatus.attributes,
...buildRuleStatusAttributes(RuleExecutionStatus.succeeded, message, attributes),
await ruleStatusClient.update({
id: currentStatus.id,
attributes: {
...currentStatus.attributes,
...buildRuleStatusAttributes(RuleExecutionStatus.succeeded, message, attributes),
},
spaceId,
});
},
partialFailure: async (message, attributes) => {
const [currentStatus] = await getOrCreateRuleStatuses({
spaceId,
alertId,
ruleStatusClient,
});
await ruleStatusClient.update(currentStatus.id, {
...currentStatus.attributes,
...buildRuleStatusAttributes(RuleExecutionStatus['partial failure'], message, attributes),
await ruleStatusClient.update({
id: currentStatus.id,
attributes: {
...currentStatus.attributes,
...buildRuleStatusAttributes(RuleExecutionStatus['partial failure'], message, attributes),
},
spaceId,
});
},
error: async (message, attributes) => {
const ruleStatuses = await getOrCreateRuleStatuses({
spaceId,
alertId,
ruleStatusClient,
});
@ -134,8 +152,12 @@ export const ruleStatusServiceFactory = async ({
};
// We always update the newest status, so to 'persist' a failure we push a copy to the head of the list
await ruleStatusClient.update(currentStatus.id, failureAttributes);
const newStatus = await ruleStatusClient.create(failureAttributes);
await ruleStatusClient.update({
id: currentStatus.id,
attributes: failureAttributes,
spaceId,
});
const newStatus = await ruleStatusClient.create({ attributes: failureAttributes, spaceId });
// drop oldest failures
const oldStatuses = [newStatus, ...ruleStatuses].slice(MAX_RULE_STATUSES);

View file

@ -34,6 +34,7 @@ import { mlExecutor } from './executors/ml';
import { getMlRuleParams, getQueryRuleParams } from '../schemas/rule_schemas.mock';
import { ResponseError } from '@elastic/elasticsearch/lib/errors';
import { allowedExperimentalValues } from '../../../../common/experimental_features';
import { ruleRegistryMocks } from '../../../../../rule_registry/server/mocks';
jest.mock('./rule_status_saved_objects_client');
jest.mock('./rule_status_service');
@ -119,6 +120,7 @@ describe('signal_rule_alert_type', () => {
let logger: ReturnType<typeof loggingSystemMock.createLogger>;
let alertServices: AlertServicesMock;
let ruleStatusService: Record<string, jest.Mock>;
let ruleDataService: ReturnType<typeof ruleRegistryMocks.createRuleDataPluginService>;
beforeEach(() => {
alertServices = alertsMock.createAlertServices();
@ -130,6 +132,7 @@ describe('signal_rule_alert_type', () => {
error: jest.fn(),
partialFailure: jest.fn(),
};
ruleDataService = ruleRegistryMocks.createRuleDataPluginService();
(ruleStatusServiceFactory as jest.Mock).mockReturnValue(ruleStatusService);
(getListsClient as jest.Mock).mockReturnValue({
listClient: getListClientMock(),
@ -196,6 +199,7 @@ describe('signal_rule_alert_type', () => {
ml: mlMock,
lists: listMock.createSetup(),
mergeStrategy: 'missingFields',
ruleDataService,
});
});

View file

@ -47,7 +47,6 @@ import {
} from '../notifications/schedule_notification_actions';
import { ruleStatusServiceFactory } from './rule_status_service';
import { buildRuleMessageFactory } from './rule_messages';
import { ruleStatusSavedObjectsClientFactory } from './rule_status_saved_objects_client';
import { getNotificationResultsLink } from '../notifications/utils';
import { TelemetryEventsSender } from '../../telemetry/sender';
import { eqlExecutor } from './executors/eql';
@ -70,6 +69,8 @@ import { wrapHitsFactory } from './wrap_hits_factory';
import { wrapSequencesFactory } from './wrap_sequences_factory';
import { ConfigType } from '../../../config';
import { ExperimentalFeatures } from '../../../../common/experimental_features';
import { RuleExecutionLogClient } from '../rule_execution_log/rule_execution_log_client';
import { IRuleDataPluginService } from '../rule_execution_log/types';
export const signalRulesAlertType = ({
logger,
@ -79,6 +80,7 @@ export const signalRulesAlertType = ({
ml,
lists,
mergeStrategy,
ruleDataService,
}: {
logger: Logger;
eventsTelemetry: TelemetryEventsSender | undefined;
@ -87,6 +89,7 @@ export const signalRulesAlertType = ({
ml: SetupPlugins['ml'];
lists: SetupPlugins['lists'] | undefined;
mergeStrategy: ConfigType['alertMergeStrategy'];
ruleDataService: IRuleDataPluginService;
}): SignalRuleAlertTypeDefinition => {
return {
id: SIGNALS_ID,
@ -124,8 +127,12 @@ export const signalRulesAlertType = ({
const searchAfterSize = Math.min(maxSignals, DEFAULT_SEARCH_AFTER_PAGE_SIZE);
let hasError: boolean = false;
let result = createSearchAfterReturnType();
const ruleStatusClient = ruleStatusSavedObjectsClientFactory(services.savedObjectsClient);
const ruleStatusClient = new RuleExecutionLogClient({
ruleDataService,
savedObjectsClient: services.savedObjectsClient,
});
const ruleStatusService = await ruleStatusServiceFactory({
spaceId,
alertId,
ruleStatusClient,
});

View file

@ -187,7 +187,6 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
getExecutionLogClient: () =>
new RuleExecutionLogClient({
ruleDataService: plugins.ruleRegistry.ruleDataService,
// TODO check if savedObjects.client contains spaceId
savedObjectsClient: context.core.savedObjects.client,
}),
})
@ -262,6 +261,7 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
mergeStrategy: this.config.alertMergeStrategy,
ruleDataClient,
version: this.context.env.packageInfo.version,
ruleDataService,
})
);
}
@ -303,6 +303,7 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
lists: plugins.lists,
mergeStrategy: this.config.alertMergeStrategy,
experimentalFeatures,
ruleDataService: plugins.ruleRegistry.ruleDataService,
});
const ruleNotificationType = rulesNotificationAlertType({
logger: this.logger,