mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
Integrate RuleExecutionLogClient with new rule types (#107624)
This commit is contained in:
parent
9b55868a15
commit
0f2d837a2b
23 changed files with 222 additions and 145 deletions
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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) {
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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}*`;
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
|
|
@ -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',
|
||||
});
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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];
|
||||
};
|
||||
|
|
|
@ -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'],
|
||||
});
|
||||
};
|
|
@ -34,6 +34,9 @@ export interface FindBulkResponse {
|
|||
[key: string]: IRuleStatusSOAttributes[] | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* @pdeprecated Use RuleExecutionLogClient instead
|
||||
*/
|
||||
export const ruleStatusSavedObjectsClientFactory = (
|
||||
savedObjectsClient: SavedObjectsClientContract
|
||||
): RuleStatusSavedObjectsClient => ({
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue