mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[Alerting] Encourage type safe usage of Alerting (#86623)
This PR encourages type safe usage of the Alerting framework by replacing the current default Params/State/InstanceState/InstanceContext types (which are `AlertTypeParams`/`AlertTypeState`/etc.) with `never`. This means that code can continue to omit the specific types for these fields, as long as they aren't referenced. Once an alert developer wishes to actually reference the parameters (or state/context), then they have to specify the type. This PR also changed the typing of the `AlertTypeParams` and `AlertTypeState` from `Record<string, any>` to `Record<string, unknown>`, to ensure that where these catch-all types are used they will at least enforce `unknown` rather than `any`. This change broke some usage in both @elastic/kibana-alerting-services plugins, but also other plugins in the Stack/Solutions. I tried to fix these where I could, but some of these require new types and refactoring in other teams' code, which I decided is best done by the team who own and maintain that code - I've added explicit `TODO` comments in all of these places, describing the required fix. This PR also introduced a Generics based typing for the `Alert` type so that the `params` field can be typed as something other than `AlertTypeParams`.
This commit is contained in:
parent
61a0b00802
commit
2db76660ce
85 changed files with 1044 additions and 523 deletions
|
@ -4,11 +4,13 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { AlertTypeParams } from '../../../plugins/alerts/common';
|
||||
|
||||
export const ALERTING_EXAMPLE_APP_ID = 'AlertingExample';
|
||||
|
||||
// always firing
|
||||
export const DEFAULT_INSTANCES_TO_GENERATE = 5;
|
||||
export interface AlwaysFiringParams {
|
||||
export interface AlwaysFiringParams extends AlertTypeParams {
|
||||
instances?: number;
|
||||
thresholds?: {
|
||||
small?: number;
|
||||
|
|
|
@ -23,7 +23,7 @@ import { withRouter, RouteComponentProps } from 'react-router-dom';
|
|||
import { CoreStart } from 'kibana/public';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { Alert, AlertTaskState, BASE_ALERT_API_PATH } from '../../../../plugins/alerts/common';
|
||||
import { ALERTING_EXAMPLE_APP_ID } from '../../common/constants';
|
||||
import { ALERTING_EXAMPLE_APP_ID, AlwaysFiringParams } from '../../common/constants';
|
||||
|
||||
type Props = RouteComponentProps & {
|
||||
http: CoreStart['http'];
|
||||
|
@ -34,7 +34,7 @@ function hasCraft(state: any): state is { craft: string } {
|
|||
return state && state.craft;
|
||||
}
|
||||
export const ViewPeopleInSpaceAlertPage = withRouter(({ http, id }: Props) => {
|
||||
const [alert, setAlert] = useState<Alert | null>(null);
|
||||
const [alert, setAlert] = useState<Alert<AlwaysFiringParams> | null>(null);
|
||||
const [alertState, setAlertState] = useState<AlertTaskState | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
@ -38,7 +38,11 @@ function getCraftFilter(craft: string) {
|
|||
craft === Craft.OuterSpace ? true : craft === person.craft;
|
||||
}
|
||||
|
||||
export const alertType: AlertType = {
|
||||
export const alertType: AlertType<
|
||||
{ outerSpaceCapacity: number; craft: string; op: string },
|
||||
{ peopleInSpace: number },
|
||||
{ craft: string }
|
||||
> = {
|
||||
id: 'example.people-in-space',
|
||||
name: 'People In Space Right Now',
|
||||
actionGroups: [{ id: 'default', name: 'default' }],
|
||||
|
|
|
@ -7,10 +7,8 @@
|
|||
import { SavedObjectAttribute, SavedObjectAttributes } from 'kibana/server';
|
||||
import { AlertNotifyWhenType } from './alert_notify_when_type';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export type AlertTypeState = Record<string, any>;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export type AlertTypeParams = Record<string, any>;
|
||||
export type AlertTypeState = Record<string, unknown>;
|
||||
export type AlertTypeParams = Record<string, unknown>;
|
||||
|
||||
export interface IntervalSchedule extends SavedObjectAttributes {
|
||||
interval: string;
|
||||
|
@ -52,7 +50,7 @@ export interface AlertAggregations {
|
|||
alertExecutionStatus: { [status: string]: number };
|
||||
}
|
||||
|
||||
export interface Alert {
|
||||
export interface Alert<Params extends AlertTypeParams = never> {
|
||||
id: string;
|
||||
enabled: boolean;
|
||||
name: string;
|
||||
|
@ -61,7 +59,7 @@ export interface Alert {
|
|||
consumer: string;
|
||||
schedule: IntervalSchedule;
|
||||
actions: AlertAction[];
|
||||
params: AlertTypeParams;
|
||||
params: Params;
|
||||
scheduledTaskId?: string;
|
||||
createdBy: string | null;
|
||||
updatedBy: string | null;
|
||||
|
@ -76,7 +74,7 @@ export interface Alert {
|
|||
executionStatus: AlertExecutionStatus;
|
||||
}
|
||||
|
||||
export type SanitizedAlert = Omit<Alert, 'apiKey'>;
|
||||
export type SanitizedAlert<Params extends AlertTypeParams = never> = Omit<Alert<Params>, 'apiKey'>;
|
||||
|
||||
export enum HealthStatus {
|
||||
OK = 'ok',
|
||||
|
|
|
@ -4,12 +4,16 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { AlertInstanceContext, AlertInstanceState } from '../types';
|
||||
import { AlertInstance } from './alert_instance';
|
||||
|
||||
export function createAlertInstanceFactory(alertInstances: Record<string, AlertInstance>) {
|
||||
return (id: string): AlertInstance => {
|
||||
export function createAlertInstanceFactory<
|
||||
InstanceState extends AlertInstanceState,
|
||||
InstanceContext extends AlertInstanceContext
|
||||
>(alertInstances: Record<string, AlertInstance<InstanceState, InstanceContext>>) {
|
||||
return (id: string): AlertInstance<InstanceState, InstanceContext> => {
|
||||
if (!alertInstances[id]) {
|
||||
alertInstances[id] = new AlertInstance();
|
||||
alertInstances[id] = new AlertInstance<InstanceState, InstanceContext>();
|
||||
}
|
||||
|
||||
return alertInstances[id];
|
||||
|
|
|
@ -32,7 +32,7 @@ export interface ConstructorOptions {
|
|||
|
||||
export interface RegistryAlertType
|
||||
extends Pick<
|
||||
NormalizedAlertType,
|
||||
UntypedNormalizedAlertType,
|
||||
| 'name'
|
||||
| 'actionGroups'
|
||||
| 'recoveryActionGroup'
|
||||
|
@ -66,16 +66,23 @@ const alertIdSchema = schema.string({
|
|||
});
|
||||
|
||||
export type NormalizedAlertType<
|
||||
Params extends AlertTypeParams = AlertTypeParams,
|
||||
State extends AlertTypeState = AlertTypeState,
|
||||
InstanceState extends AlertInstanceState = AlertInstanceState,
|
||||
InstanceContext extends AlertInstanceContext = AlertInstanceContext
|
||||
Params extends AlertTypeParams,
|
||||
State extends AlertTypeState,
|
||||
InstanceState extends AlertInstanceState,
|
||||
InstanceContext extends AlertInstanceContext
|
||||
> = Omit<AlertType<Params, State, InstanceState, InstanceContext>, 'recoveryActionGroup'> &
|
||||
Pick<Required<AlertType<Params, State, InstanceState, InstanceContext>>, 'recoveryActionGroup'>;
|
||||
|
||||
export type UntypedNormalizedAlertType = NormalizedAlertType<
|
||||
AlertTypeParams,
|
||||
AlertTypeState,
|
||||
AlertInstanceState,
|
||||
AlertInstanceContext
|
||||
>;
|
||||
|
||||
export class AlertTypeRegistry {
|
||||
private readonly taskManager: TaskManagerSetupContract;
|
||||
private readonly alertTypes: Map<string, NormalizedAlertType> = new Map();
|
||||
private readonly alertTypes: Map<string, UntypedNormalizedAlertType> = new Map();
|
||||
private readonly taskRunnerFactory: TaskRunnerFactory;
|
||||
private readonly licenseState: ILicenseState;
|
||||
private readonly licensing: LicensingPluginSetup;
|
||||
|
@ -96,10 +103,10 @@ export class AlertTypeRegistry {
|
|||
}
|
||||
|
||||
public register<
|
||||
Params extends AlertTypeParams = AlertTypeParams,
|
||||
State extends AlertTypeState = AlertTypeState,
|
||||
InstanceState extends AlertInstanceState = AlertInstanceState,
|
||||
InstanceContext extends AlertInstanceContext = AlertInstanceContext
|
||||
Params extends AlertTypeParams,
|
||||
State extends AlertTypeState,
|
||||
InstanceState extends AlertInstanceState,
|
||||
InstanceContext extends AlertInstanceContext
|
||||
>(alertType: AlertType<Params, State, InstanceState, InstanceContext>) {
|
||||
if (this.has(alertType.id)) {
|
||||
throw new Error(
|
||||
|
@ -113,14 +120,22 @@ export class AlertTypeRegistry {
|
|||
}
|
||||
alertType.actionVariables = normalizedActionVariables(alertType.actionVariables);
|
||||
|
||||
const normalizedAlertType = augmentActionGroupsWithReserved(alertType as AlertType);
|
||||
const normalizedAlertType = augmentActionGroupsWithReserved<
|
||||
Params,
|
||||
State,
|
||||
InstanceState,
|
||||
InstanceContext
|
||||
>(alertType);
|
||||
|
||||
this.alertTypes.set(alertIdSchema.validate(alertType.id), normalizedAlertType);
|
||||
this.alertTypes.set(
|
||||
alertIdSchema.validate(alertType.id),
|
||||
normalizedAlertType as UntypedNormalizedAlertType
|
||||
);
|
||||
this.taskManager.registerTaskDefinitions({
|
||||
[`alerting:${alertType.id}`]: {
|
||||
title: alertType.name,
|
||||
createTaskRunner: (context: RunContext) =>
|
||||
this.taskRunnerFactory.create(normalizedAlertType, context),
|
||||
this.taskRunnerFactory.create(normalizedAlertType as UntypedNormalizedAlertType, context),
|
||||
},
|
||||
});
|
||||
// No need to notify usage on basic alert types
|
||||
|
@ -170,7 +185,7 @@ export class AlertTypeRegistry {
|
|||
producer,
|
||||
minimumLicenseRequired,
|
||||
},
|
||||
]: [string, NormalizedAlertType]) => ({
|
||||
]: [string, UntypedNormalizedAlertType]) => ({
|
||||
id,
|
||||
name,
|
||||
actionGroups,
|
||||
|
|
|
@ -23,13 +23,13 @@ import {
|
|||
RawAlert,
|
||||
AlertTypeRegistry,
|
||||
AlertAction,
|
||||
AlertType,
|
||||
IntervalSchedule,
|
||||
SanitizedAlert,
|
||||
AlertTaskState,
|
||||
AlertInstanceSummary,
|
||||
AlertExecutionStatusValues,
|
||||
AlertNotifyWhenType,
|
||||
AlertTypeParams,
|
||||
} from '../types';
|
||||
import {
|
||||
validateAlertTypeParams,
|
||||
|
@ -44,7 +44,7 @@ import { EncryptedSavedObjectsClient } from '../../../encrypted_saved_objects/se
|
|||
import { TaskManagerStartContract } from '../../../task_manager/server';
|
||||
import { taskInstanceToAlertTaskInstance } from '../task_runner/alert_task_instance';
|
||||
import { deleteTaskIfItExists } from '../lib/delete_task_if_it_exists';
|
||||
import { RegistryAlertType } from '../alert_type_registry';
|
||||
import { RegistryAlertType, UntypedNormalizedAlertType } from '../alert_type_registry';
|
||||
import { AlertsAuthorization, WriteOperations, ReadOperations } from '../authorization';
|
||||
import { IEventLogClient } from '../../../../plugins/event_log/server';
|
||||
import { parseIsoOrRelativeDate } from '../lib/iso_or_relative_date';
|
||||
|
@ -127,16 +127,16 @@ interface AggregateResult {
|
|||
alertExecutionStatus: { [status: string]: number };
|
||||
}
|
||||
|
||||
export interface FindResult {
|
||||
export interface FindResult<Params extends AlertTypeParams> {
|
||||
page: number;
|
||||
perPage: number;
|
||||
total: number;
|
||||
data: SanitizedAlert[];
|
||||
data: Array<SanitizedAlert<Params>>;
|
||||
}
|
||||
|
||||
export interface CreateOptions {
|
||||
export interface CreateOptions<Params extends AlertTypeParams> {
|
||||
data: Omit<
|
||||
Alert,
|
||||
Alert<Params>,
|
||||
| 'id'
|
||||
| 'createdBy'
|
||||
| 'updatedBy'
|
||||
|
@ -154,14 +154,14 @@ export interface CreateOptions {
|
|||
};
|
||||
}
|
||||
|
||||
interface UpdateOptions {
|
||||
interface UpdateOptions<Params extends AlertTypeParams> {
|
||||
id: string;
|
||||
data: {
|
||||
name: string;
|
||||
tags: string[];
|
||||
schedule: IntervalSchedule;
|
||||
actions: NormalizedAlertAction[];
|
||||
params: Record<string, unknown>;
|
||||
params: Params;
|
||||
throttle: string | null;
|
||||
notifyWhen: AlertNotifyWhenType | null;
|
||||
};
|
||||
|
@ -223,7 +223,10 @@ export class AlertsClient {
|
|||
this.auditLogger = auditLogger;
|
||||
}
|
||||
|
||||
public async create({ data, options }: CreateOptions): Promise<Alert> {
|
||||
public async create<Params extends AlertTypeParams = never>({
|
||||
data,
|
||||
options,
|
||||
}: CreateOptions<Params>): Promise<Alert<Params>> {
|
||||
const id = SavedObjectsUtils.generateId();
|
||||
|
||||
try {
|
||||
|
@ -248,7 +251,10 @@ export class AlertsClient {
|
|||
// Throws an error if alert type isn't registered
|
||||
const alertType = this.alertTypeRegistry.get(data.alertTypeId);
|
||||
|
||||
const validatedAlertTypeParams = validateAlertTypeParams(alertType, data.params);
|
||||
const validatedAlertTypeParams = validateAlertTypeParams(
|
||||
data.params,
|
||||
alertType.validate?.params
|
||||
);
|
||||
const username = await this.getUserName();
|
||||
|
||||
const createdAPIKey = data.enabled
|
||||
|
@ -334,10 +340,14 @@ export class AlertsClient {
|
|||
});
|
||||
createdAlert.attributes.scheduledTaskId = scheduledTask.id;
|
||||
}
|
||||
return this.getAlertFromRaw(createdAlert.id, createdAlert.attributes, references);
|
||||
return this.getAlertFromRaw<Params>(createdAlert.id, createdAlert.attributes, references);
|
||||
}
|
||||
|
||||
public async get({ id }: { id: string }): Promise<SanitizedAlert> {
|
||||
public async get<Params extends AlertTypeParams = never>({
|
||||
id,
|
||||
}: {
|
||||
id: string;
|
||||
}): Promise<SanitizedAlert<Params>> {
|
||||
const result = await this.unsecuredSavedObjectsClient.get<RawAlert>('alert', id);
|
||||
try {
|
||||
await this.authorization.ensureAuthorized(
|
||||
|
@ -361,7 +371,7 @@ export class AlertsClient {
|
|||
savedObject: { type: 'alert', id },
|
||||
})
|
||||
);
|
||||
return this.getAlertFromRaw(result.id, result.attributes, result.references);
|
||||
return this.getAlertFromRaw<Params>(result.id, result.attributes, result.references);
|
||||
}
|
||||
|
||||
public async getAlertState({ id }: { id: string }): Promise<AlertTaskState | void> {
|
||||
|
@ -426,9 +436,9 @@ export class AlertsClient {
|
|||
});
|
||||
}
|
||||
|
||||
public async find({
|
||||
public async find<Params extends AlertTypeParams = never>({
|
||||
options: { fields, ...options } = {},
|
||||
}: { options?: FindOptions } = {}): Promise<FindResult> {
|
||||
}: { options?: FindOptions } = {}): Promise<FindResult<Params>> {
|
||||
let authorizationTuple;
|
||||
try {
|
||||
authorizationTuple = await this.authorization.getFindAuthorizationFilter();
|
||||
|
@ -475,7 +485,7 @@ export class AlertsClient {
|
|||
);
|
||||
throw error;
|
||||
}
|
||||
return this.getAlertFromRaw(
|
||||
return this.getAlertFromRaw<Params>(
|
||||
id,
|
||||
fields ? (pick(attributes, fields) as RawAlert) : attributes,
|
||||
references
|
||||
|
@ -605,15 +615,21 @@ export class AlertsClient {
|
|||
return removeResult;
|
||||
}
|
||||
|
||||
public async update({ id, data }: UpdateOptions): Promise<PartialAlert> {
|
||||
public async update<Params extends AlertTypeParams = never>({
|
||||
id,
|
||||
data,
|
||||
}: UpdateOptions<Params>): Promise<PartialAlert<Params>> {
|
||||
return await retryIfConflicts(
|
||||
this.logger,
|
||||
`alertsClient.update('${id}')`,
|
||||
async () => await this.updateWithOCC({ id, data })
|
||||
async () => await this.updateWithOCC<Params>({ id, data })
|
||||
);
|
||||
}
|
||||
|
||||
private async updateWithOCC({ id, data }: UpdateOptions): Promise<PartialAlert> {
|
||||
private async updateWithOCC<Params extends AlertTypeParams>({
|
||||
id,
|
||||
data,
|
||||
}: UpdateOptions<Params>): Promise<PartialAlert<Params>> {
|
||||
let alertSavedObject: SavedObject<RawAlert>;
|
||||
|
||||
try {
|
||||
|
@ -658,7 +674,7 @@ export class AlertsClient {
|
|||
|
||||
this.alertTypeRegistry.ensureAlertTypeEnabled(alertSavedObject.attributes.alertTypeId);
|
||||
|
||||
const updateResult = await this.updateAlert({ id, data }, alertSavedObject);
|
||||
const updateResult = await this.updateAlert<Params>({ id, data }, alertSavedObject);
|
||||
|
||||
await Promise.all([
|
||||
alertSavedObject.attributes.apiKey
|
||||
|
@ -692,14 +708,17 @@ export class AlertsClient {
|
|||
return updateResult;
|
||||
}
|
||||
|
||||
private async updateAlert(
|
||||
{ id, data }: UpdateOptions,
|
||||
private async updateAlert<Params extends AlertTypeParams>(
|
||||
{ id, data }: UpdateOptions<Params>,
|
||||
{ attributes, version }: SavedObject<RawAlert>
|
||||
): Promise<PartialAlert> {
|
||||
): Promise<PartialAlert<Params>> {
|
||||
const alertType = this.alertTypeRegistry.get(attributes.alertTypeId);
|
||||
|
||||
// Validate
|
||||
const validatedAlertTypeParams = validateAlertTypeParams(alertType, data.params);
|
||||
const validatedAlertTypeParams = validateAlertTypeParams(
|
||||
data.params,
|
||||
alertType.validate?.params
|
||||
);
|
||||
this.validateActions(alertType, data.actions);
|
||||
|
||||
const { actions, references } = await this.denormalizeActions(data.actions);
|
||||
|
@ -1343,7 +1362,7 @@ export class AlertsClient {
|
|||
}) as Alert['actions'];
|
||||
}
|
||||
|
||||
private getAlertFromRaw(
|
||||
private getAlertFromRaw<Params extends AlertTypeParams>(
|
||||
id: string,
|
||||
rawAlert: RawAlert,
|
||||
references: SavedObjectReference[] | undefined
|
||||
|
@ -1351,14 +1370,14 @@ export class AlertsClient {
|
|||
// In order to support the partial update API of Saved Objects we have to support
|
||||
// partial updates of an Alert, but when we receive an actual RawAlert, it is safe
|
||||
// to cast the result to an Alert
|
||||
return this.getPartialAlertFromRaw(id, rawAlert, references) as Alert;
|
||||
return this.getPartialAlertFromRaw<Params>(id, rawAlert, references) as Alert;
|
||||
}
|
||||
|
||||
private getPartialAlertFromRaw(
|
||||
private getPartialAlertFromRaw<Params extends AlertTypeParams>(
|
||||
id: string,
|
||||
{ createdAt, updatedAt, meta, notifyWhen, scheduledTaskId, ...rawAlert }: Partial<RawAlert>,
|
||||
references: SavedObjectReference[] | undefined
|
||||
): PartialAlert {
|
||||
): PartialAlert<Params> {
|
||||
// Not the prettiest code here, but if we want to use most of the
|
||||
// alert fields from the rawAlert using `...rawAlert` kind of access, we
|
||||
// need to specifically delete the executionStatus as it's a different type
|
||||
|
@ -1386,7 +1405,10 @@ export class AlertsClient {
|
|||
};
|
||||
}
|
||||
|
||||
private validateActions(alertType: AlertType, actions: NormalizedAlertAction[]): void {
|
||||
private validateActions(
|
||||
alertType: UntypedNormalizedAlertType,
|
||||
actions: NormalizedAlertAction[]
|
||||
): void {
|
||||
const { actionGroups: alertTypeActionGroups } = alertType;
|
||||
const usedAlertActionGroups = actions.map((action) => action.group);
|
||||
const availableAlertTypeActionGroups = new Set(map(alertTypeActionGroups, 'id'));
|
||||
|
|
|
@ -59,7 +59,11 @@ beforeEach(() => {
|
|||
|
||||
setGlobalDate();
|
||||
|
||||
function getMockData(overwrites: Record<string, unknown> = {}): CreateOptions['data'] {
|
||||
function getMockData(
|
||||
overwrites: Record<string, unknown> = {}
|
||||
): CreateOptions<{
|
||||
bar: boolean;
|
||||
}>['data'] {
|
||||
return {
|
||||
enabled: true,
|
||||
name: 'abc',
|
||||
|
@ -93,7 +97,11 @@ describe('create()', () => {
|
|||
});
|
||||
|
||||
describe('authorization', () => {
|
||||
function tryToExecuteOperation(options: CreateOptions): Promise<unknown> {
|
||||
function tryToExecuteOperation(
|
||||
options: CreateOptions<{
|
||||
bar: boolean;
|
||||
}>
|
||||
): Promise<unknown> {
|
||||
unsecuredSavedObjectsClient.bulkGet.mockResolvedValueOnce({
|
||||
saved_objects: [
|
||||
{
|
||||
|
|
|
@ -635,11 +635,11 @@ export class EventsFactory {
|
|||
}
|
||||
}
|
||||
|
||||
function createAlert(overrides: Partial<SanitizedAlert>): SanitizedAlert {
|
||||
function createAlert(overrides: Partial<SanitizedAlert>): SanitizedAlert<{ bar: boolean }> {
|
||||
return { ...BaseAlert, ...overrides };
|
||||
}
|
||||
|
||||
const BaseAlert: SanitizedAlert = {
|
||||
const BaseAlert: SanitizedAlert<{ bar: boolean }> = {
|
||||
id: 'alert-123',
|
||||
alertTypeId: '123',
|
||||
schedule: { interval: '10s' },
|
||||
|
|
|
@ -9,7 +9,7 @@ import { IEvent } from '../../../event_log/server';
|
|||
import { EVENT_LOG_ACTIONS, EVENT_LOG_PROVIDER, LEGACY_EVENT_LOG_ACTIONS } from '../plugin';
|
||||
|
||||
export interface AlertInstanceSummaryFromEventLogParams {
|
||||
alert: SanitizedAlert;
|
||||
alert: SanitizedAlert<{ bar: boolean }>;
|
||||
events: IEvent[];
|
||||
dateStart: string;
|
||||
dateEnd: string;
|
||||
|
|
|
@ -8,51 +8,19 @@ import { schema } from '@kbn/config-schema';
|
|||
import { validateAlertTypeParams } from './validate_alert_type_params';
|
||||
|
||||
test('should return passed in params when validation not defined', () => {
|
||||
const result = validateAlertTypeParams(
|
||||
{
|
||||
id: 'my-alert-type',
|
||||
name: 'My description',
|
||||
actionGroups: [
|
||||
{
|
||||
id: 'default',
|
||||
name: 'Default',
|
||||
},
|
||||
],
|
||||
defaultActionGroupId: 'default',
|
||||
minimumLicenseRequired: 'basic',
|
||||
async executor() {},
|
||||
producer: 'alerts',
|
||||
},
|
||||
{
|
||||
foo: true,
|
||||
}
|
||||
);
|
||||
const result = validateAlertTypeParams({
|
||||
foo: true,
|
||||
});
|
||||
expect(result).toEqual({ foo: true });
|
||||
});
|
||||
|
||||
test('should validate and apply defaults when params is valid', () => {
|
||||
const result = validateAlertTypeParams(
|
||||
{
|
||||
id: 'my-alert-type',
|
||||
name: 'My description',
|
||||
actionGroups: [
|
||||
{
|
||||
id: 'default',
|
||||
name: 'Default',
|
||||
},
|
||||
],
|
||||
defaultActionGroupId: 'default',
|
||||
minimumLicenseRequired: 'basic',
|
||||
validate: {
|
||||
params: schema.object({
|
||||
param1: schema.string(),
|
||||
param2: schema.string({ defaultValue: 'default-value' }),
|
||||
}),
|
||||
},
|
||||
async executor() {},
|
||||
producer: 'alerts',
|
||||
},
|
||||
{ param1: 'value' }
|
||||
{ param1: 'value' },
|
||||
schema.object({
|
||||
param1: schema.string(),
|
||||
param2: schema.string({ defaultValue: 'default-value' }),
|
||||
})
|
||||
);
|
||||
expect(result).toEqual({
|
||||
param1: 'value',
|
||||
|
@ -63,26 +31,10 @@ test('should validate and apply defaults when params is valid', () => {
|
|||
test('should validate and throw error when params is invalid', () => {
|
||||
expect(() =>
|
||||
validateAlertTypeParams(
|
||||
{
|
||||
id: 'my-alert-type',
|
||||
name: 'My description',
|
||||
actionGroups: [
|
||||
{
|
||||
id: 'default',
|
||||
name: 'Default',
|
||||
},
|
||||
],
|
||||
defaultActionGroupId: 'default',
|
||||
minimumLicenseRequired: 'basic',
|
||||
validate: {
|
||||
params: schema.object({
|
||||
param1: schema.string(),
|
||||
}),
|
||||
},
|
||||
async executor() {},
|
||||
producer: 'alerts',
|
||||
},
|
||||
{}
|
||||
{},
|
||||
schema.object({
|
||||
param1: schema.string(),
|
||||
})
|
||||
)
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
`"params invalid: [param1]: expected value of type [string] but got [undefined]"`
|
||||
|
|
|
@ -5,15 +5,14 @@
|
|||
*/
|
||||
|
||||
import Boom from '@hapi/boom';
|
||||
import { AlertType, AlertExecutorOptions } from '../types';
|
||||
import { AlertTypeParams, AlertTypeParamsValidator } from '../types';
|
||||
|
||||
export function validateAlertTypeParams(
|
||||
alertType: AlertType,
|
||||
params: Record<string, unknown>
|
||||
): AlertExecutorOptions['params'] {
|
||||
const validator = alertType.validate && alertType.validate.params;
|
||||
export function validateAlertTypeParams<Params extends AlertTypeParams>(
|
||||
params: Record<string, unknown>,
|
||||
validator?: AlertTypeParamsValidator<Params>
|
||||
): Params {
|
||||
if (!validator) {
|
||||
return params as AlertExecutorOptions['params'];
|
||||
return params as Params;
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
elasticsearchServiceMock,
|
||||
savedObjectsClientMock,
|
||||
} from '../../../../src/core/server/mocks';
|
||||
import { AlertInstanceContext, AlertInstanceState } from './types';
|
||||
|
||||
export { alertsClientMock };
|
||||
|
||||
|
@ -30,8 +31,14 @@ const createStartMock = () => {
|
|||
return mock;
|
||||
};
|
||||
|
||||
export type AlertInstanceMock = jest.Mocked<AlertInstance>;
|
||||
const createAlertInstanceFactoryMock = () => {
|
||||
export type AlertInstanceMock<
|
||||
State extends AlertInstanceState = AlertInstanceState,
|
||||
Context extends AlertInstanceContext = AlertInstanceContext
|
||||
> = jest.Mocked<AlertInstance<State, Context>>;
|
||||
const createAlertInstanceFactoryMock = <
|
||||
InstanceState extends AlertInstanceState = AlertInstanceState,
|
||||
InstanceContext extends AlertInstanceContext = AlertInstanceContext
|
||||
>() => {
|
||||
const mock = {
|
||||
hasScheduledActions: jest.fn(),
|
||||
isThrottled: jest.fn(),
|
||||
|
@ -50,14 +57,17 @@ const createAlertInstanceFactoryMock = () => {
|
|||
mock.unscheduleActions.mockReturnValue(mock);
|
||||
mock.scheduleActions.mockReturnValue(mock);
|
||||
|
||||
return (mock as unknown) as AlertInstanceMock;
|
||||
return (mock as unknown) as AlertInstanceMock<InstanceState, InstanceContext>;
|
||||
};
|
||||
|
||||
const createAlertServicesMock = () => {
|
||||
const alertInstanceFactoryMock = createAlertInstanceFactoryMock();
|
||||
const createAlertServicesMock = <
|
||||
InstanceState extends AlertInstanceState = AlertInstanceState,
|
||||
InstanceContext extends AlertInstanceContext = AlertInstanceContext
|
||||
>() => {
|
||||
const alertInstanceFactoryMock = createAlertInstanceFactoryMock<InstanceState, InstanceContext>();
|
||||
return {
|
||||
alertInstanceFactory: jest
|
||||
.fn<jest.Mocked<AlertInstance>, [string]>()
|
||||
.fn<jest.Mocked<AlertInstance<InstanceState, InstanceContext>>, [string]>()
|
||||
.mockReturnValue(alertInstanceFactoryMock),
|
||||
callCluster: elasticsearchServiceMock.createLegacyScopedClusterClient().callAsCurrentUser,
|
||||
getLegacyScopedClusterClient: jest.fn(),
|
||||
|
|
|
@ -49,7 +49,7 @@ describe('createAlertRoute', () => {
|
|||
],
|
||||
};
|
||||
|
||||
const createResult: Alert = {
|
||||
const createResult: Alert<{ bar: boolean }> = {
|
||||
...mockedAlert,
|
||||
enabled: true,
|
||||
muteAll: false,
|
||||
|
|
|
@ -16,7 +16,13 @@ import { ILicenseState } from '../lib/license_state';
|
|||
import { verifyApiAccess } from '../lib/license_api_access';
|
||||
import { validateDurationSchema } from '../lib';
|
||||
import { handleDisabledApiKeysError } from './lib/error_handler';
|
||||
import { Alert, AlertNotifyWhenType, BASE_ALERT_API_PATH, validateNotifyWhenType } from '../types';
|
||||
import {
|
||||
Alert,
|
||||
AlertNotifyWhenType,
|
||||
AlertTypeParams,
|
||||
BASE_ALERT_API_PATH,
|
||||
validateNotifyWhenType,
|
||||
} from '../types';
|
||||
import { AlertTypeDisabledError } from '../lib/errors/alert_type_disabled';
|
||||
|
||||
export const bodySchema = schema.object({
|
||||
|
@ -65,7 +71,9 @@ export const createAlertRoute = (router: IRouter, licenseState: ILicenseState) =
|
|||
const alert = req.body;
|
||||
const notifyWhen = alert?.notifyWhen ? (alert.notifyWhen as AlertNotifyWhenType) : null;
|
||||
try {
|
||||
const alertRes: Alert = await alertsClient.create({ data: { ...alert, notifyWhen } });
|
||||
const alertRes: Alert<AlertTypeParams> = await alertsClient.create<AlertTypeParams>({
|
||||
data: { ...alert, notifyWhen },
|
||||
});
|
||||
return res.ok({
|
||||
body: alertRes,
|
||||
});
|
||||
|
|
|
@ -22,7 +22,9 @@ beforeEach(() => {
|
|||
});
|
||||
|
||||
describe('getAlertRoute', () => {
|
||||
const mockedAlert: Alert = {
|
||||
const mockedAlert: Alert<{
|
||||
bar: true;
|
||||
}> = {
|
||||
id: '1',
|
||||
alertTypeId: '1',
|
||||
schedule: { interval: '10s' },
|
||||
|
|
|
@ -9,7 +9,9 @@ import { AlertTaskInstance, taskInstanceToAlertTaskInstance } from './alert_task
|
|||
import uuid from 'uuid';
|
||||
import { SanitizedAlert } from '../types';
|
||||
|
||||
const alert: SanitizedAlert = {
|
||||
const alert: SanitizedAlert<{
|
||||
bar: boolean;
|
||||
}> = {
|
||||
id: 'alert-123',
|
||||
alertTypeId: '123',
|
||||
schedule: { interval: '10s' },
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
alertParamsSchema,
|
||||
alertStateSchema,
|
||||
AlertTaskParams,
|
||||
AlertTypeParams,
|
||||
} from '../../common';
|
||||
|
||||
export interface AlertTaskInstance extends ConcreteTaskInstance {
|
||||
|
@ -23,9 +24,9 @@ export interface AlertTaskInstance extends ConcreteTaskInstance {
|
|||
const enumerateErrorFields = (e: t.Errors) =>
|
||||
`${e.map(({ context }) => context.map(({ key }) => key).join('.'))}`;
|
||||
|
||||
export function taskInstanceToAlertTaskInstance(
|
||||
export function taskInstanceToAlertTaskInstance<Params extends AlertTypeParams>(
|
||||
taskInstance: ConcreteTaskInstance,
|
||||
alert?: SanitizedAlert
|
||||
alert?: SanitizedAlert<Params>
|
||||
): AlertTaskInstance {
|
||||
return {
|
||||
...taskInstance,
|
||||
|
|
|
@ -4,8 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { AlertType } from '../types';
|
||||
import { createExecutionHandler } from './create_execution_handler';
|
||||
import { createExecutionHandler, CreateExecutionHandlerOptions } from './create_execution_handler';
|
||||
import { loggingSystemMock } from '../../../../../src/core/server/mocks';
|
||||
import {
|
||||
actionsMock,
|
||||
|
@ -16,12 +15,19 @@ import { eventLoggerMock } from '../../../event_log/server/event_logger.mock';
|
|||
import { KibanaRequest } from 'kibana/server';
|
||||
import { asSavedObjectExecutionSource } from '../../../actions/server';
|
||||
import { InjectActionParamsOpts } from './inject_action_params';
|
||||
import { UntypedNormalizedAlertType } from '../alert_type_registry';
|
||||
import {
|
||||
AlertTypeParams,
|
||||
AlertTypeState,
|
||||
AlertInstanceState,
|
||||
AlertInstanceContext,
|
||||
} from '../types';
|
||||
|
||||
jest.mock('./inject_action_params', () => ({
|
||||
injectActionParams: jest.fn(),
|
||||
}));
|
||||
|
||||
const alertType: AlertType = {
|
||||
const alertType: UntypedNormalizedAlertType = {
|
||||
id: 'test',
|
||||
name: 'Test',
|
||||
actionGroups: [
|
||||
|
@ -39,18 +45,26 @@ const alertType: AlertType = {
|
|||
};
|
||||
|
||||
const actionsClient = actionsClientMock.create();
|
||||
const createExecutionHandlerParams = {
|
||||
actionsPlugin: actionsMock.createStart(),
|
||||
|
||||
const mockActionsPlugin = actionsMock.createStart();
|
||||
const mockEventLogger = eventLoggerMock.create();
|
||||
const createExecutionHandlerParams: jest.Mocked<
|
||||
CreateExecutionHandlerOptions<
|
||||
AlertTypeParams,
|
||||
AlertTypeState,
|
||||
AlertInstanceState,
|
||||
AlertInstanceContext
|
||||
>
|
||||
> = {
|
||||
actionsPlugin: mockActionsPlugin,
|
||||
spaceId: 'default',
|
||||
alertId: '1',
|
||||
alertName: 'name-of-alert',
|
||||
tags: ['tag-A', 'tag-B'],
|
||||
apiKey: 'MTIzOmFiYw==',
|
||||
spaceIdToNamespace: jest.fn().mockReturnValue(undefined),
|
||||
getBasePath: jest.fn().mockReturnValue(undefined),
|
||||
alertType,
|
||||
logger: loggingSystemMock.create().get(),
|
||||
eventLogger: eventLoggerMock.create(),
|
||||
eventLogger: mockEventLogger,
|
||||
actions: [
|
||||
{
|
||||
id: '1',
|
||||
|
@ -79,12 +93,10 @@ beforeEach(() => {
|
|||
.injectActionParams.mockImplementation(
|
||||
({ actionParams }: InjectActionParamsOpts) => actionParams
|
||||
);
|
||||
createExecutionHandlerParams.actionsPlugin.isActionTypeEnabled.mockReturnValue(true);
|
||||
createExecutionHandlerParams.actionsPlugin.isActionExecutable.mockReturnValue(true);
|
||||
createExecutionHandlerParams.actionsPlugin.getActionsClientWithRequest.mockResolvedValue(
|
||||
actionsClient
|
||||
);
|
||||
createExecutionHandlerParams.actionsPlugin.renderActionParameterTemplates.mockImplementation(
|
||||
mockActionsPlugin.isActionTypeEnabled.mockReturnValue(true);
|
||||
mockActionsPlugin.isActionExecutable.mockReturnValue(true);
|
||||
mockActionsPlugin.getActionsClientWithRequest.mockResolvedValue(actionsClient);
|
||||
mockActionsPlugin.renderActionParameterTemplates.mockImplementation(
|
||||
renderActionParameterTemplatesDefault
|
||||
);
|
||||
});
|
||||
|
@ -97,9 +109,9 @@ test('enqueues execution per selected action', async () => {
|
|||
context: {},
|
||||
alertInstanceId: '2',
|
||||
});
|
||||
expect(
|
||||
createExecutionHandlerParams.actionsPlugin.getActionsClientWithRequest
|
||||
).toHaveBeenCalledWith(createExecutionHandlerParams.request);
|
||||
expect(mockActionsPlugin.getActionsClientWithRequest).toHaveBeenCalledWith(
|
||||
createExecutionHandlerParams.request
|
||||
);
|
||||
expect(actionsClient.enqueueExecution).toHaveBeenCalledTimes(1);
|
||||
expect(actionsClient.enqueueExecution.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
|
@ -124,9 +136,8 @@ test('enqueues execution per selected action', async () => {
|
|||
]
|
||||
`);
|
||||
|
||||
const eventLogger = createExecutionHandlerParams.eventLogger;
|
||||
expect(eventLogger.logEvent).toHaveBeenCalledTimes(1);
|
||||
expect(eventLogger.logEvent.mock.calls).toMatchInlineSnapshot(`
|
||||
expect(mockEventLogger.logEvent).toHaveBeenCalledTimes(1);
|
||||
expect(mockEventLogger.logEvent.mock.calls).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
Object {
|
||||
|
@ -171,9 +182,9 @@ test('enqueues execution per selected action', async () => {
|
|||
|
||||
test(`doesn't call actionsPlugin.execute for disabled actionTypes`, async () => {
|
||||
// Mock two calls, one for check against actions[0] and the second for actions[1]
|
||||
createExecutionHandlerParams.actionsPlugin.isActionExecutable.mockReturnValueOnce(false);
|
||||
createExecutionHandlerParams.actionsPlugin.isActionTypeEnabled.mockReturnValueOnce(false);
|
||||
createExecutionHandlerParams.actionsPlugin.isActionTypeEnabled.mockReturnValueOnce(true);
|
||||
mockActionsPlugin.isActionExecutable.mockReturnValueOnce(false);
|
||||
mockActionsPlugin.isActionTypeEnabled.mockReturnValueOnce(false);
|
||||
mockActionsPlugin.isActionTypeEnabled.mockReturnValueOnce(true);
|
||||
const executionHandler = createExecutionHandler({
|
||||
...createExecutionHandlerParams,
|
||||
actions: [
|
||||
|
@ -214,9 +225,9 @@ test(`doesn't call actionsPlugin.execute for disabled actionTypes`, async () =>
|
|||
});
|
||||
|
||||
test('trow error error message when action type is disabled', async () => {
|
||||
createExecutionHandlerParams.actionsPlugin.preconfiguredActions = [];
|
||||
createExecutionHandlerParams.actionsPlugin.isActionExecutable.mockReturnValue(false);
|
||||
createExecutionHandlerParams.actionsPlugin.isActionTypeEnabled.mockReturnValue(false);
|
||||
mockActionsPlugin.preconfiguredActions = [];
|
||||
mockActionsPlugin.isActionExecutable.mockReturnValue(false);
|
||||
mockActionsPlugin.isActionTypeEnabled.mockReturnValue(false);
|
||||
const executionHandler = createExecutionHandler({
|
||||
...createExecutionHandlerParams,
|
||||
actions: [
|
||||
|
@ -243,7 +254,7 @@ test('trow error error message when action type is disabled', async () => {
|
|||
|
||||
expect(actionsClient.enqueueExecution).toHaveBeenCalledTimes(0);
|
||||
|
||||
createExecutionHandlerParams.actionsPlugin.isActionExecutable.mockImplementation(() => true);
|
||||
mockActionsPlugin.isActionExecutable.mockImplementation(() => true);
|
||||
const executionHandlerForPreconfiguredAction = createExecutionHandler({
|
||||
...createExecutionHandlerParams,
|
||||
actions: [...createExecutionHandlerParams.actions],
|
||||
|
|
|
@ -15,14 +15,20 @@ import { EVENT_LOG_ACTIONS } from '../plugin';
|
|||
import { injectActionParams } from './inject_action_params';
|
||||
import {
|
||||
AlertAction,
|
||||
AlertTypeParams,
|
||||
AlertTypeState,
|
||||
AlertInstanceState,
|
||||
AlertInstanceContext,
|
||||
AlertType,
|
||||
AlertTypeParams,
|
||||
RawAlert,
|
||||
} from '../types';
|
||||
import { NormalizedAlertType } from '../alert_type_registry';
|
||||
|
||||
interface CreateExecutionHandlerOptions {
|
||||
export interface CreateExecutionHandlerOptions<
|
||||
Params extends AlertTypeParams,
|
||||
State extends AlertTypeState,
|
||||
InstanceState extends AlertInstanceState,
|
||||
InstanceContext extends AlertInstanceContext
|
||||
> {
|
||||
alertId: string;
|
||||
alertName: string;
|
||||
tags?: string[];
|
||||
|
@ -30,7 +36,7 @@ interface CreateExecutionHandlerOptions {
|
|||
actions: AlertAction[];
|
||||
spaceId: string;
|
||||
apiKey: RawAlert['apiKey'];
|
||||
alertType: AlertType;
|
||||
alertType: NormalizedAlertType<Params, State, InstanceState, InstanceContext>;
|
||||
logger: Logger;
|
||||
eventLogger: IEventLogger;
|
||||
request: KibanaRequest;
|
||||
|
@ -45,7 +51,12 @@ interface ExecutionHandlerOptions {
|
|||
state: AlertInstanceState;
|
||||
}
|
||||
|
||||
export function createExecutionHandler({
|
||||
export function createExecutionHandler<
|
||||
Params extends AlertTypeParams,
|
||||
State extends AlertTypeState,
|
||||
InstanceState extends AlertInstanceState,
|
||||
InstanceContext extends AlertInstanceContext
|
||||
>({
|
||||
logger,
|
||||
alertId,
|
||||
alertName,
|
||||
|
@ -58,7 +69,7 @@ export function createExecutionHandler({
|
|||
eventLogger,
|
||||
request,
|
||||
alertParams,
|
||||
}: CreateExecutionHandlerOptions) {
|
||||
}: CreateExecutionHandlerOptions<Params, State, InstanceState, InstanceContext>) {
|
||||
const alertTypeActionGroups = new Map(
|
||||
alertType.actionGroups.map((actionGroup) => [actionGroup.id, actionGroup.name])
|
||||
);
|
||||
|
|
|
@ -6,7 +6,13 @@
|
|||
|
||||
import sinon from 'sinon';
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { AlertExecutorOptions } from '../types';
|
||||
import {
|
||||
AlertExecutorOptions,
|
||||
AlertTypeParams,
|
||||
AlertTypeState,
|
||||
AlertInstanceState,
|
||||
AlertInstanceContext,
|
||||
} from '../types';
|
||||
import {
|
||||
ConcreteTaskInstance,
|
||||
isUnrecoverableError,
|
||||
|
@ -28,9 +34,9 @@ import { IEventLogger } from '../../../event_log/server';
|
|||
import { SavedObjectsErrorHelpers } from '../../../../../src/core/server';
|
||||
import { Alert, RecoveredActionGroup } from '../../common';
|
||||
import { omit } from 'lodash';
|
||||
import { NormalizedAlertType } from '../alert_type_registry';
|
||||
import { UntypedNormalizedAlertType } from '../alert_type_registry';
|
||||
import { alertTypeRegistryMock } from '../alert_type_registry.mock';
|
||||
const alertType = {
|
||||
const alertType: jest.Mocked<UntypedNormalizedAlertType> = {
|
||||
id: 'test',
|
||||
name: 'My test alert',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }, RecoveredActionGroup],
|
||||
|
@ -91,7 +97,7 @@ describe('Task Runner', () => {
|
|||
alertTypeRegistry,
|
||||
};
|
||||
|
||||
const mockedAlertTypeSavedObject: Alert = {
|
||||
const mockedAlertTypeSavedObject: Alert<AlertTypeParams> = {
|
||||
id: '1',
|
||||
consumer: 'bar',
|
||||
createdAt: new Date('2019-02-12T21:01:22.479Z'),
|
||||
|
@ -150,7 +156,7 @@ describe('Task Runner', () => {
|
|||
|
||||
test('successfully executes the task', async () => {
|
||||
const taskRunner = new TaskRunner(
|
||||
alertType as NormalizedAlertType,
|
||||
alertType,
|
||||
{
|
||||
...mockedTaskInstance,
|
||||
state: {
|
||||
|
@ -254,14 +260,21 @@ describe('Task Runner', () => {
|
|||
taskRunnerFactoryInitializerParams.actionsPlugin.isActionTypeEnabled.mockReturnValue(true);
|
||||
taskRunnerFactoryInitializerParams.actionsPlugin.isActionExecutable.mockReturnValue(true);
|
||||
alertType.executor.mockImplementation(
|
||||
({ services: executorServices }: AlertExecutorOptions) => {
|
||||
async ({
|
||||
services: executorServices,
|
||||
}: AlertExecutorOptions<
|
||||
AlertTypeParams,
|
||||
AlertTypeState,
|
||||
AlertInstanceState,
|
||||
AlertInstanceContext
|
||||
>) => {
|
||||
executorServices
|
||||
.alertInstanceFactory('1')
|
||||
.scheduleActionsWithSubGroup('default', 'subDefault');
|
||||
}
|
||||
);
|
||||
const taskRunner = new TaskRunner(
|
||||
alertType as NormalizedAlertType,
|
||||
alertType,
|
||||
mockedTaskInstance,
|
||||
taskRunnerFactoryInitializerParams
|
||||
);
|
||||
|
@ -407,12 +420,19 @@ describe('Task Runner', () => {
|
|||
taskRunnerFactoryInitializerParams.actionsPlugin.isActionTypeEnabled.mockReturnValue(true);
|
||||
taskRunnerFactoryInitializerParams.actionsPlugin.isActionExecutable.mockReturnValue(true);
|
||||
alertType.executor.mockImplementation(
|
||||
({ services: executorServices }: AlertExecutorOptions) => {
|
||||
async ({
|
||||
services: executorServices,
|
||||
}: AlertExecutorOptions<
|
||||
AlertTypeParams,
|
||||
AlertTypeState,
|
||||
AlertInstanceState,
|
||||
AlertInstanceContext
|
||||
>) => {
|
||||
executorServices.alertInstanceFactory('1').scheduleActions('default');
|
||||
}
|
||||
);
|
||||
const taskRunner = new TaskRunner(
|
||||
alertType as NormalizedAlertType,
|
||||
alertType,
|
||||
mockedTaskInstance,
|
||||
taskRunnerFactoryInitializerParams
|
||||
);
|
||||
|
@ -516,13 +536,20 @@ describe('Task Runner', () => {
|
|||
taskRunnerFactoryInitializerParams.actionsPlugin.isActionTypeEnabled.mockReturnValue(true);
|
||||
taskRunnerFactoryInitializerParams.actionsPlugin.isActionExecutable.mockReturnValue(true);
|
||||
alertType.executor.mockImplementation(
|
||||
({ services: executorServices }: AlertExecutorOptions) => {
|
||||
async ({
|
||||
services: executorServices,
|
||||
}: AlertExecutorOptions<
|
||||
AlertTypeParams,
|
||||
AlertTypeState,
|
||||
AlertInstanceState,
|
||||
AlertInstanceContext
|
||||
>) => {
|
||||
executorServices.alertInstanceFactory('1').scheduleActions('default');
|
||||
executorServices.alertInstanceFactory('2').scheduleActions('default');
|
||||
}
|
||||
);
|
||||
const taskRunner = new TaskRunner(
|
||||
alertType as NormalizedAlertType,
|
||||
alertType,
|
||||
mockedTaskInstance,
|
||||
taskRunnerFactoryInitializerParams
|
||||
);
|
||||
|
@ -562,12 +589,19 @@ describe('Task Runner', () => {
|
|||
taskRunnerFactoryInitializerParams.actionsPlugin.isActionTypeEnabled.mockReturnValue(true);
|
||||
taskRunnerFactoryInitializerParams.actionsPlugin.isActionExecutable.mockReturnValue(true);
|
||||
alertType.executor.mockImplementation(
|
||||
({ services: executorServices }: AlertExecutorOptions) => {
|
||||
async ({
|
||||
services: executorServices,
|
||||
}: AlertExecutorOptions<
|
||||
AlertTypeParams,
|
||||
AlertTypeState,
|
||||
AlertInstanceState,
|
||||
AlertInstanceContext
|
||||
>) => {
|
||||
executorServices.alertInstanceFactory('1').scheduleActions('default');
|
||||
}
|
||||
);
|
||||
const taskRunner = new TaskRunner(
|
||||
alertType as NormalizedAlertType,
|
||||
alertType,
|
||||
{
|
||||
...mockedTaskInstance,
|
||||
state: {
|
||||
|
@ -656,12 +690,19 @@ describe('Task Runner', () => {
|
|||
taskRunnerFactoryInitializerParams.actionsPlugin.isActionTypeEnabled.mockReturnValue(true);
|
||||
taskRunnerFactoryInitializerParams.actionsPlugin.isActionExecutable.mockReturnValue(true);
|
||||
alertType.executor.mockImplementation(
|
||||
({ services: executorServices }: AlertExecutorOptions) => {
|
||||
async ({
|
||||
services: executorServices,
|
||||
}: AlertExecutorOptions<
|
||||
AlertTypeParams,
|
||||
AlertTypeState,
|
||||
AlertInstanceState,
|
||||
AlertInstanceContext
|
||||
>) => {
|
||||
executorServices.alertInstanceFactory('1').scheduleActions('default');
|
||||
}
|
||||
);
|
||||
const taskRunner = new TaskRunner(
|
||||
alertType as NormalizedAlertType,
|
||||
alertType,
|
||||
{
|
||||
...mockedTaskInstance,
|
||||
state: {
|
||||
|
@ -696,14 +737,21 @@ describe('Task Runner', () => {
|
|||
taskRunnerFactoryInitializerParams.actionsPlugin.isActionTypeEnabled.mockReturnValue(true);
|
||||
taskRunnerFactoryInitializerParams.actionsPlugin.isActionExecutable.mockReturnValue(true);
|
||||
alertType.executor.mockImplementation(
|
||||
({ services: executorServices }: AlertExecutorOptions) => {
|
||||
async ({
|
||||
services: executorServices,
|
||||
}: AlertExecutorOptions<
|
||||
AlertTypeParams,
|
||||
AlertTypeState,
|
||||
AlertInstanceState,
|
||||
AlertInstanceContext
|
||||
>) => {
|
||||
executorServices
|
||||
.alertInstanceFactory('1')
|
||||
.scheduleActionsWithSubGroup('default', 'subgroup1');
|
||||
}
|
||||
);
|
||||
const taskRunner = new TaskRunner(
|
||||
alertType as NormalizedAlertType,
|
||||
alertType,
|
||||
{
|
||||
...mockedTaskInstance,
|
||||
state: {
|
||||
|
@ -744,12 +792,19 @@ describe('Task Runner', () => {
|
|||
taskRunnerFactoryInitializerParams.actionsPlugin.isActionTypeEnabled.mockReturnValue(true);
|
||||
taskRunnerFactoryInitializerParams.actionsPlugin.isActionExecutable.mockReturnValue(true);
|
||||
alertType.executor.mockImplementation(
|
||||
({ services: executorServices }: AlertExecutorOptions) => {
|
||||
async ({
|
||||
services: executorServices,
|
||||
}: AlertExecutorOptions<
|
||||
AlertTypeParams,
|
||||
AlertTypeState,
|
||||
AlertInstanceState,
|
||||
AlertInstanceContext
|
||||
>) => {
|
||||
executorServices.alertInstanceFactory('1').scheduleActions('default');
|
||||
}
|
||||
);
|
||||
const taskRunner = new TaskRunner(
|
||||
alertType as NormalizedAlertType,
|
||||
alertType,
|
||||
mockedTaskInstance,
|
||||
taskRunnerFactoryInitializerParams
|
||||
);
|
||||
|
@ -912,12 +967,19 @@ describe('Task Runner', () => {
|
|||
taskRunnerFactoryInitializerParams.actionsPlugin.isActionExecutable.mockReturnValue(true);
|
||||
|
||||
alertType.executor.mockImplementation(
|
||||
({ services: executorServices }: AlertExecutorOptions) => {
|
||||
async ({
|
||||
services: executorServices,
|
||||
}: AlertExecutorOptions<
|
||||
AlertTypeParams,
|
||||
AlertTypeState,
|
||||
AlertInstanceState,
|
||||
AlertInstanceContext
|
||||
>) => {
|
||||
executorServices.alertInstanceFactory('1').scheduleActions('default');
|
||||
}
|
||||
);
|
||||
const taskRunner = new TaskRunner(
|
||||
alertType as NormalizedAlertType,
|
||||
alertType,
|
||||
{
|
||||
...mockedTaskInstance,
|
||||
state: {
|
||||
|
@ -1012,12 +1074,19 @@ describe('Task Runner', () => {
|
|||
};
|
||||
|
||||
alertTypeWithCustomRecovery.executor.mockImplementation(
|
||||
({ services: executorServices }: AlertExecutorOptions) => {
|
||||
async ({
|
||||
services: executorServices,
|
||||
}: AlertExecutorOptions<
|
||||
AlertTypeParams,
|
||||
AlertTypeState,
|
||||
AlertInstanceState,
|
||||
AlertInstanceContext
|
||||
>) => {
|
||||
executorServices.alertInstanceFactory('1').scheduleActions('default');
|
||||
}
|
||||
);
|
||||
const taskRunner = new TaskRunner(
|
||||
alertTypeWithCustomRecovery as NormalizedAlertType,
|
||||
alertTypeWithCustomRecovery,
|
||||
{
|
||||
...mockedTaskInstance,
|
||||
state: {
|
||||
|
@ -1103,13 +1172,20 @@ describe('Task Runner', () => {
|
|||
|
||||
test('persists alertInstances passed in from state, only if they are scheduled for execution', async () => {
|
||||
alertType.executor.mockImplementation(
|
||||
({ services: executorServices }: AlertExecutorOptions) => {
|
||||
async ({
|
||||
services: executorServices,
|
||||
}: AlertExecutorOptions<
|
||||
AlertTypeParams,
|
||||
AlertTypeState,
|
||||
AlertInstanceState,
|
||||
AlertInstanceContext
|
||||
>) => {
|
||||
executorServices.alertInstanceFactory('1').scheduleActions('default');
|
||||
}
|
||||
);
|
||||
const date = new Date().toISOString();
|
||||
const taskRunner = new TaskRunner(
|
||||
alertType as NormalizedAlertType,
|
||||
alertType,
|
||||
{
|
||||
...mockedTaskInstance,
|
||||
state: {
|
||||
|
@ -1239,7 +1315,7 @@ describe('Task Runner', () => {
|
|||
param1: schema.string(),
|
||||
}),
|
||||
},
|
||||
} as NormalizedAlertType,
|
||||
},
|
||||
mockedTaskInstance,
|
||||
taskRunnerFactoryInitializerParams
|
||||
);
|
||||
|
@ -1267,7 +1343,7 @@ describe('Task Runner', () => {
|
|||
|
||||
test('uses API key when provided', async () => {
|
||||
const taskRunner = new TaskRunner(
|
||||
alertType as NormalizedAlertType,
|
||||
alertType,
|
||||
mockedTaskInstance,
|
||||
taskRunnerFactoryInitializerParams
|
||||
);
|
||||
|
@ -1300,7 +1376,7 @@ describe('Task Runner', () => {
|
|||
|
||||
test(`doesn't use API key when not provided`, async () => {
|
||||
const taskRunner = new TaskRunner(
|
||||
alertType as NormalizedAlertType,
|
||||
alertType,
|
||||
mockedTaskInstance,
|
||||
taskRunnerFactoryInitializerParams
|
||||
);
|
||||
|
@ -1330,7 +1406,7 @@ describe('Task Runner', () => {
|
|||
|
||||
test('rescheduled the Alert if the schedule has update during a task run', async () => {
|
||||
const taskRunner = new TaskRunner(
|
||||
alertType as NormalizedAlertType,
|
||||
alertType,
|
||||
mockedTaskInstance,
|
||||
taskRunnerFactoryInitializerParams
|
||||
);
|
||||
|
@ -1365,13 +1441,20 @@ describe('Task Runner', () => {
|
|||
|
||||
test('recovers gracefully when the AlertType executor throws an exception', async () => {
|
||||
alertType.executor.mockImplementation(
|
||||
({ services: executorServices }: AlertExecutorOptions) => {
|
||||
async ({
|
||||
services: executorServices,
|
||||
}: AlertExecutorOptions<
|
||||
AlertTypeParams,
|
||||
AlertTypeState,
|
||||
AlertInstanceState,
|
||||
AlertInstanceContext
|
||||
>) => {
|
||||
throw new Error('OMG');
|
||||
}
|
||||
);
|
||||
|
||||
const taskRunner = new TaskRunner(
|
||||
alertType as NormalizedAlertType,
|
||||
alertType,
|
||||
mockedTaskInstance,
|
||||
taskRunnerFactoryInitializerParams
|
||||
);
|
||||
|
@ -1438,7 +1521,7 @@ describe('Task Runner', () => {
|
|||
});
|
||||
|
||||
const taskRunner = new TaskRunner(
|
||||
alertType as NormalizedAlertType,
|
||||
alertType,
|
||||
mockedTaskInstance,
|
||||
taskRunnerFactoryInitializerParams
|
||||
);
|
||||
|
@ -1497,7 +1580,7 @@ describe('Task Runner', () => {
|
|||
});
|
||||
|
||||
const taskRunner = new TaskRunner(
|
||||
alertType as NormalizedAlertType,
|
||||
alertType,
|
||||
mockedTaskInstance,
|
||||
taskRunnerFactoryInitializerParams
|
||||
);
|
||||
|
@ -1564,7 +1647,7 @@ describe('Task Runner', () => {
|
|||
});
|
||||
|
||||
const taskRunner = new TaskRunner(
|
||||
alertType as NormalizedAlertType,
|
||||
alertType,
|
||||
mockedTaskInstance,
|
||||
taskRunnerFactoryInitializerParams
|
||||
);
|
||||
|
@ -1631,7 +1714,7 @@ describe('Task Runner', () => {
|
|||
});
|
||||
|
||||
const taskRunner = new TaskRunner(
|
||||
alertType as NormalizedAlertType,
|
||||
alertType,
|
||||
mockedTaskInstance,
|
||||
taskRunnerFactoryInitializerParams
|
||||
);
|
||||
|
@ -1701,7 +1784,7 @@ describe('Task Runner', () => {
|
|||
const legacyTaskInstance = omit(mockedTaskInstance, 'schedule');
|
||||
|
||||
const taskRunner = new TaskRunner(
|
||||
alertType as NormalizedAlertType,
|
||||
alertType,
|
||||
legacyTaskInstance,
|
||||
taskRunnerFactoryInitializerParams
|
||||
);
|
||||
|
@ -1733,13 +1816,20 @@ describe('Task Runner', () => {
|
|||
};
|
||||
|
||||
alertType.executor.mockImplementation(
|
||||
({ services: executorServices }: AlertExecutorOptions) => {
|
||||
async ({
|
||||
services: executorServices,
|
||||
}: AlertExecutorOptions<
|
||||
AlertTypeParams,
|
||||
AlertTypeState,
|
||||
AlertInstanceState,
|
||||
AlertInstanceContext
|
||||
>) => {
|
||||
throw new Error('OMG');
|
||||
}
|
||||
);
|
||||
|
||||
const taskRunner = new TaskRunner(
|
||||
alertType as NormalizedAlertType,
|
||||
alertType,
|
||||
{
|
||||
...mockedTaskInstance,
|
||||
state: originalAlertSate,
|
||||
|
@ -1770,7 +1860,7 @@ describe('Task Runner', () => {
|
|||
});
|
||||
|
||||
const taskRunner = new TaskRunner(
|
||||
alertType as NormalizedAlertType,
|
||||
alertType,
|
||||
mockedTaskInstance,
|
||||
taskRunnerFactoryInitializerParams
|
||||
);
|
||||
|
|
|
@ -26,7 +26,6 @@ import {
|
|||
RawAlertInstance,
|
||||
AlertTaskState,
|
||||
Alert,
|
||||
AlertExecutorOptions,
|
||||
SanitizedAlert,
|
||||
AlertExecutionStatus,
|
||||
AlertExecutionStatusErrorReasons,
|
||||
|
@ -39,7 +38,13 @@ import { IEvent, IEventLogger, SAVED_OBJECT_REL_PRIMARY } from '../../../event_l
|
|||
import { isAlertSavedObjectNotFoundError } from '../lib/is_alert_not_found_error';
|
||||
import { AlertsClient } from '../alerts_client';
|
||||
import { partiallyUpdateAlert } from '../saved_objects';
|
||||
import { ActionGroup } from '../../common';
|
||||
import {
|
||||
ActionGroup,
|
||||
AlertTypeParams,
|
||||
AlertTypeState,
|
||||
AlertInstanceState,
|
||||
AlertInstanceContext,
|
||||
} from '../../common';
|
||||
import { NormalizedAlertType } from '../alert_type_registry';
|
||||
|
||||
const FALLBACK_RETRY_INTERVAL = '5m';
|
||||
|
@ -55,15 +60,20 @@ interface AlertTaskInstance extends ConcreteTaskInstance {
|
|||
state: AlertTaskState;
|
||||
}
|
||||
|
||||
export class TaskRunner {
|
||||
export class TaskRunner<
|
||||
Params extends AlertTypeParams,
|
||||
State extends AlertTypeState,
|
||||
InstanceState extends AlertInstanceState,
|
||||
InstanceContext extends AlertInstanceContext
|
||||
> {
|
||||
private context: TaskRunnerContext;
|
||||
private logger: Logger;
|
||||
private taskInstance: AlertTaskInstance;
|
||||
private alertType: NormalizedAlertType;
|
||||
private alertType: NormalizedAlertType<Params, State, InstanceState, InstanceContext>;
|
||||
private readonly alertTypeRegistry: AlertTypeRegistry;
|
||||
|
||||
constructor(
|
||||
alertType: NormalizedAlertType,
|
||||
alertType: NormalizedAlertType<Params, State, InstanceState, InstanceContext>,
|
||||
taskInstance: ConcreteTaskInstance,
|
||||
context: TaskRunnerContext
|
||||
) {
|
||||
|
@ -131,8 +141,8 @@ export class TaskRunner {
|
|||
tags: string[] | undefined,
|
||||
spaceId: string,
|
||||
apiKey: RawAlert['apiKey'],
|
||||
actions: Alert['actions'],
|
||||
alertParams: RawAlert['params']
|
||||
actions: Alert<Params>['actions'],
|
||||
alertParams: Params
|
||||
) {
|
||||
return createExecutionHandler({
|
||||
alertId,
|
||||
|
@ -152,7 +162,7 @@ export class TaskRunner {
|
|||
|
||||
async executeAlertInstance(
|
||||
alertInstanceId: string,
|
||||
alertInstance: AlertInstance,
|
||||
alertInstance: AlertInstance<InstanceState, InstanceContext>,
|
||||
executionHandler: ReturnType<typeof createExecutionHandler>
|
||||
) {
|
||||
const {
|
||||
|
@ -168,8 +178,8 @@ export class TaskRunner {
|
|||
|
||||
async executeAlertInstances(
|
||||
services: Services,
|
||||
alert: SanitizedAlert,
|
||||
params: AlertExecutorOptions['params'],
|
||||
alert: SanitizedAlert<Params>,
|
||||
params: Params,
|
||||
executionHandler: ReturnType<typeof createExecutionHandler>,
|
||||
spaceId: string,
|
||||
event: Event
|
||||
|
@ -190,9 +200,12 @@ export class TaskRunner {
|
|||
} = this.taskInstance;
|
||||
const namespace = this.context.spaceIdToNamespace(spaceId);
|
||||
|
||||
const alertInstances = mapValues<Record<string, RawAlertInstance>, AlertInstance>(
|
||||
const alertInstances = mapValues<
|
||||
Record<string, RawAlertInstance>,
|
||||
AlertInstance<InstanceState, InstanceContext>
|
||||
>(
|
||||
alertRawInstances,
|
||||
(rawAlertInstance) => new AlertInstance(rawAlertInstance)
|
||||
(rawAlertInstance) => new AlertInstance<InstanceState, InstanceContext>(rawAlertInstance)
|
||||
);
|
||||
const originalAlertInstances = cloneDeep(alertInstances);
|
||||
|
||||
|
@ -205,10 +218,12 @@ export class TaskRunner {
|
|||
alertId,
|
||||
services: {
|
||||
...services,
|
||||
alertInstanceFactory: createAlertInstanceFactory(alertInstances),
|
||||
alertInstanceFactory: createAlertInstanceFactory<InstanceState, InstanceContext>(
|
||||
alertInstances
|
||||
),
|
||||
},
|
||||
params,
|
||||
state: alertTypeState,
|
||||
state: alertTypeState as State,
|
||||
startedAt: this.taskInstance.startedAt!,
|
||||
previousStartedAt: previousStartedAt ? new Date(previousStartedAt) : null,
|
||||
spaceId,
|
||||
|
@ -232,12 +247,15 @@ export class TaskRunner {
|
|||
event.event.outcome = 'success';
|
||||
|
||||
// Cleanup alert instances that are no longer scheduling actions to avoid over populating the alertInstances object
|
||||
const instancesWithScheduledActions = pickBy(alertInstances, (alertInstance: AlertInstance) =>
|
||||
alertInstance.hasScheduledActions()
|
||||
const instancesWithScheduledActions = pickBy(
|
||||
alertInstances,
|
||||
(alertInstance: AlertInstance<InstanceState, InstanceContext>) =>
|
||||
alertInstance.hasScheduledActions()
|
||||
);
|
||||
const recoveredAlertInstances = pickBy(
|
||||
alertInstances,
|
||||
(alertInstance: AlertInstance) => !alertInstance.hasScheduledActions()
|
||||
(alertInstance: AlertInstance<InstanceState, InstanceContext>) =>
|
||||
!alertInstance.hasScheduledActions()
|
||||
);
|
||||
|
||||
logActiveAndRecoveredInstances({
|
||||
|
@ -272,7 +290,10 @@ export class TaskRunner {
|
|||
const instancesToExecute =
|
||||
notifyWhen === 'onActionGroupChange'
|
||||
? Object.entries(instancesWithScheduledActions).filter(
|
||||
([alertInstanceName, alertInstance]: [string, AlertInstance]) => {
|
||||
([alertInstanceName, alertInstance]: [
|
||||
string,
|
||||
AlertInstance<InstanceState, InstanceContext>
|
||||
]) => {
|
||||
const shouldExecuteAction = alertInstance.scheduledActionGroupOrSubgroupHasChanged();
|
||||
if (!shouldExecuteAction) {
|
||||
this.logger.debug(
|
||||
|
@ -283,7 +304,10 @@ export class TaskRunner {
|
|||
}
|
||||
)
|
||||
: Object.entries(instancesWithScheduledActions).filter(
|
||||
([alertInstanceName, alertInstance]: [string, AlertInstance]) => {
|
||||
([alertInstanceName, alertInstance]: [
|
||||
string,
|
||||
AlertInstance<InstanceState, InstanceContext>
|
||||
]) => {
|
||||
const throttled = alertInstance.isThrottled(throttle);
|
||||
const muted = mutedInstanceIdsSet.has(alertInstanceName);
|
||||
const shouldExecuteAction = !throttled && !muted;
|
||||
|
@ -299,8 +323,9 @@ export class TaskRunner {
|
|||
);
|
||||
|
||||
await Promise.all(
|
||||
instancesToExecute.map(([id, alertInstance]: [string, AlertInstance]) =>
|
||||
this.executeAlertInstance(id, alertInstance, executionHandler)
|
||||
instancesToExecute.map(
|
||||
([id, alertInstance]: [string, AlertInstance<InstanceState, InstanceContext>]) =>
|
||||
this.executeAlertInstance(id, alertInstance, executionHandler)
|
||||
)
|
||||
);
|
||||
} else {
|
||||
|
@ -309,17 +334,17 @@ export class TaskRunner {
|
|||
|
||||
return {
|
||||
alertTypeState: updatedAlertTypeState || undefined,
|
||||
alertInstances: mapValues<Record<string, AlertInstance>, RawAlertInstance>(
|
||||
instancesWithScheduledActions,
|
||||
(alertInstance) => alertInstance.toRaw()
|
||||
),
|
||||
alertInstances: mapValues<
|
||||
Record<string, AlertInstance<InstanceState, InstanceContext>>,
|
||||
RawAlertInstance
|
||||
>(instancesWithScheduledActions, (alertInstance) => alertInstance.toRaw()),
|
||||
};
|
||||
}
|
||||
|
||||
async validateAndExecuteAlert(
|
||||
services: Services,
|
||||
apiKey: RawAlert['apiKey'],
|
||||
alert: SanitizedAlert,
|
||||
alert: SanitizedAlert<Params>,
|
||||
event: Event
|
||||
) {
|
||||
const {
|
||||
|
@ -327,7 +352,7 @@ export class TaskRunner {
|
|||
} = this.taskInstance;
|
||||
|
||||
// Validate
|
||||
const validatedParams = validateAlertTypeParams(this.alertType, alert.params);
|
||||
const validatedParams = validateAlertTypeParams(alert.params, this.alertType.validate?.params);
|
||||
const executionHandler = this.getExecutionHandler(
|
||||
alertId,
|
||||
alert.name,
|
||||
|
@ -359,7 +384,7 @@ export class TaskRunner {
|
|||
}
|
||||
const [services, alertsClient] = this.getServicesWithSpaceLevelPermissions(spaceId, apiKey);
|
||||
|
||||
let alert: SanitizedAlert;
|
||||
let alert: SanitizedAlert<Params>;
|
||||
|
||||
// Ensure API key is still valid and user has access
|
||||
try {
|
||||
|
@ -501,19 +526,23 @@ export class TaskRunner {
|
|||
}
|
||||
}
|
||||
|
||||
interface GenerateNewAndRecoveredInstanceEventsParams {
|
||||
interface GenerateNewAndRecoveredInstanceEventsParams<
|
||||
InstanceState extends AlertInstanceState,
|
||||
InstanceContext extends AlertInstanceContext
|
||||
> {
|
||||
eventLogger: IEventLogger;
|
||||
originalAlertInstances: Dictionary<AlertInstance>;
|
||||
currentAlertInstances: Dictionary<AlertInstance>;
|
||||
recoveredAlertInstances: Dictionary<AlertInstance>;
|
||||
originalAlertInstances: Dictionary<AlertInstance<InstanceState, InstanceContext>>;
|
||||
currentAlertInstances: Dictionary<AlertInstance<InstanceState, InstanceContext>>;
|
||||
recoveredAlertInstances: Dictionary<AlertInstance<InstanceState, InstanceContext>>;
|
||||
alertId: string;
|
||||
alertLabel: string;
|
||||
namespace: string | undefined;
|
||||
}
|
||||
|
||||
function generateNewAndRecoveredInstanceEvents(
|
||||
params: GenerateNewAndRecoveredInstanceEventsParams
|
||||
) {
|
||||
function generateNewAndRecoveredInstanceEvents<
|
||||
InstanceState extends AlertInstanceState,
|
||||
InstanceContext extends AlertInstanceContext
|
||||
>(params: GenerateNewAndRecoveredInstanceEventsParams<InstanceState, InstanceContext>) {
|
||||
const {
|
||||
eventLogger,
|
||||
alertId,
|
||||
|
@ -584,16 +613,22 @@ function generateNewAndRecoveredInstanceEvents(
|
|||
}
|
||||
}
|
||||
|
||||
interface ScheduleActionsForRecoveredInstancesParams {
|
||||
interface ScheduleActionsForRecoveredInstancesParams<
|
||||
InstanceState extends AlertInstanceState,
|
||||
InstanceContext extends AlertInstanceContext
|
||||
> {
|
||||
logger: Logger;
|
||||
recoveryActionGroup: ActionGroup;
|
||||
recoveredAlertInstances: Dictionary<AlertInstance>;
|
||||
recoveredAlertInstances: Dictionary<AlertInstance<InstanceState, InstanceContext>>;
|
||||
executionHandler: ReturnType<typeof createExecutionHandler>;
|
||||
mutedInstanceIdsSet: Set<string>;
|
||||
alertLabel: string;
|
||||
}
|
||||
|
||||
function scheduleActionsForRecoveredInstances(params: ScheduleActionsForRecoveredInstancesParams) {
|
||||
function scheduleActionsForRecoveredInstances<
|
||||
InstanceState extends AlertInstanceState,
|
||||
InstanceContext extends AlertInstanceContext
|
||||
>(params: ScheduleActionsForRecoveredInstancesParams<InstanceState, InstanceContext>) {
|
||||
const {
|
||||
logger,
|
||||
recoveryActionGroup,
|
||||
|
@ -623,14 +658,20 @@ function scheduleActionsForRecoveredInstances(params: ScheduleActionsForRecovere
|
|||
}
|
||||
}
|
||||
|
||||
interface LogActiveAndRecoveredInstancesParams {
|
||||
interface LogActiveAndRecoveredInstancesParams<
|
||||
InstanceState extends AlertInstanceState,
|
||||
InstanceContext extends AlertInstanceContext
|
||||
> {
|
||||
logger: Logger;
|
||||
activeAlertInstances: Dictionary<AlertInstance>;
|
||||
recoveredAlertInstances: Dictionary<AlertInstance>;
|
||||
activeAlertInstances: Dictionary<AlertInstance<InstanceState, InstanceContext>>;
|
||||
recoveredAlertInstances: Dictionary<AlertInstance<InstanceState, InstanceContext>>;
|
||||
alertLabel: string;
|
||||
}
|
||||
|
||||
function logActiveAndRecoveredInstances(params: LogActiveAndRecoveredInstancesParams) {
|
||||
function logActiveAndRecoveredInstances<
|
||||
InstanceState extends AlertInstanceState,
|
||||
InstanceContext extends AlertInstanceContext
|
||||
>(params: LogActiveAndRecoveredInstancesParams<InstanceState, InstanceContext>) {
|
||||
const { logger, activeAlertInstances, recoveredAlertInstances, alertLabel } = params;
|
||||
const activeInstanceIds = Object.keys(activeAlertInstances);
|
||||
const recoveredInstanceIds = Object.keys(recoveredAlertInstances);
|
||||
|
|
|
@ -16,10 +16,10 @@ import {
|
|||
import { actionsMock } from '../../../actions/server/mocks';
|
||||
import { alertsMock, alertsClientMock } from '../mocks';
|
||||
import { eventLoggerMock } from '../../../event_log/server/event_logger.mock';
|
||||
import { NormalizedAlertType } from '../alert_type_registry';
|
||||
import { UntypedNormalizedAlertType } from '../alert_type_registry';
|
||||
import { alertTypeRegistryMock } from '../alert_type_registry.mock';
|
||||
|
||||
const alertType: NormalizedAlertType = {
|
||||
const alertType: UntypedNormalizedAlertType = {
|
||||
id: 'test',
|
||||
name: 'My test alert',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
|
|
|
@ -17,7 +17,7 @@ import { AlertTypeRegistry, GetServicesFunction, SpaceIdToNamespaceFunction } fr
|
|||
import { TaskRunner } from './task_runner';
|
||||
import { IEventLogger } from '../../../event_log/server';
|
||||
import { AlertsClient } from '../alerts_client';
|
||||
import { NormalizedAlertType } from '../alert_type_registry';
|
||||
import { UntypedNormalizedAlertType } from '../alert_type_registry';
|
||||
|
||||
export interface TaskRunnerContext {
|
||||
logger: Logger;
|
||||
|
@ -44,7 +44,7 @@ export class TaskRunnerFactory {
|
|||
this.taskRunnerContext = taskRunnerContext;
|
||||
}
|
||||
|
||||
public create(alertType: NormalizedAlertType, { taskInstance }: RunContext) {
|
||||
public create(alertType: UntypedNormalizedAlertType, { taskInstance }: RunContext) {
|
||||
if (!this.isInitialized) {
|
||||
throw new Error('TaskRunnerFactory not initialized');
|
||||
}
|
||||
|
|
|
@ -61,10 +61,10 @@ export interface AlertServices<
|
|||
}
|
||||
|
||||
export interface AlertExecutorOptions<
|
||||
Params extends AlertTypeParams = AlertTypeParams,
|
||||
State extends AlertTypeState = AlertTypeState,
|
||||
InstanceState extends AlertInstanceState = AlertInstanceState,
|
||||
InstanceContext extends AlertInstanceContext = AlertInstanceContext
|
||||
Params extends AlertTypeParams = never,
|
||||
State extends AlertTypeState = never,
|
||||
InstanceState extends AlertInstanceState = never,
|
||||
InstanceContext extends AlertInstanceContext = never
|
||||
> {
|
||||
alertId: string;
|
||||
startedAt: Date;
|
||||
|
@ -85,26 +85,28 @@ export interface ActionVariable {
|
|||
description: string;
|
||||
}
|
||||
|
||||
// signature of the alert type executor function
|
||||
export type ExecutorType<
|
||||
Params,
|
||||
State,
|
||||
InstanceState extends AlertInstanceState = AlertInstanceState,
|
||||
InstanceContext extends AlertInstanceContext = AlertInstanceContext
|
||||
Params extends AlertTypeParams = never,
|
||||
State extends AlertTypeState = never,
|
||||
InstanceState extends AlertInstanceState = never,
|
||||
InstanceContext extends AlertInstanceContext = never
|
||||
> = (
|
||||
options: AlertExecutorOptions<Params, State, InstanceState, InstanceContext>
|
||||
) => Promise<State | void>;
|
||||
|
||||
export interface AlertTypeParamsValidator<Params extends AlertTypeParams> {
|
||||
validate: (object: unknown) => Params;
|
||||
}
|
||||
export interface AlertType<
|
||||
Params extends AlertTypeParams = AlertTypeParams,
|
||||
State extends AlertTypeState = AlertTypeState,
|
||||
InstanceState extends AlertInstanceState = AlertInstanceState,
|
||||
InstanceContext extends AlertInstanceContext = AlertInstanceContext
|
||||
Params extends AlertTypeParams = never,
|
||||
State extends AlertTypeState = never,
|
||||
InstanceState extends AlertInstanceState = never,
|
||||
InstanceContext extends AlertInstanceContext = never
|
||||
> {
|
||||
id: string;
|
||||
name: string;
|
||||
validate?: {
|
||||
params?: { validate: (object: unknown) => Params };
|
||||
params?: AlertTypeParamsValidator<Params>;
|
||||
};
|
||||
actionGroups: ActionGroup[];
|
||||
defaultActionGroupId: ActionGroup['id'];
|
||||
|
@ -119,6 +121,13 @@ export interface AlertType<
|
|||
minimumLicenseRequired: LicenseType;
|
||||
}
|
||||
|
||||
export type UntypedAlertType = AlertType<
|
||||
AlertTypeParams,
|
||||
AlertTypeState,
|
||||
AlertInstanceState,
|
||||
AlertInstanceContext
|
||||
>;
|
||||
|
||||
export interface RawAlertAction extends SavedObjectAttributes {
|
||||
group: string;
|
||||
actionRef: string;
|
||||
|
@ -142,7 +151,8 @@ export interface RawAlertExecutionStatus extends SavedObjectAttributes {
|
|||
};
|
||||
}
|
||||
|
||||
export type PartialAlert = Pick<Alert, 'id'> & Partial<Omit<Alert, 'id'>>;
|
||||
export type PartialAlert<Params extends AlertTypeParams = never> = Pick<Alert<Params>, 'id'> &
|
||||
Partial<Omit<Alert<Params>, 'id'>>;
|
||||
|
||||
export interface RawAlert extends SavedObjectAttributes {
|
||||
enabled: boolean;
|
||||
|
|
|
@ -5,13 +5,21 @@
|
|||
*/
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID } from '../../../server/lib/alerting/inventory_metric_threshold/types';
|
||||
import {
|
||||
InventoryMetricConditions,
|
||||
METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID,
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
} from '../../../server/lib/alerting/inventory_metric_threshold/types';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { AlertTypeModel } from '../../../../triggers_actions_ui/public/types';
|
||||
import { AlertTypeParams } from '../../../../alerts/common';
|
||||
import { validateMetricThreshold } from './components/validation';
|
||||
|
||||
export function createInventoryMetricAlertType(): AlertTypeModel {
|
||||
interface InventoryMetricAlertTypeParams extends AlertTypeParams {
|
||||
criteria: InventoryMetricConditions[];
|
||||
}
|
||||
|
||||
export function createInventoryMetricAlertType(): AlertTypeModel<InventoryMetricAlertTypeParams> {
|
||||
return {
|
||||
id: METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID,
|
||||
description: i18n.translate('xpack.infra.metrics.inventory.alertFlyout.alertDescription', {
|
||||
|
|
|
@ -8,10 +8,18 @@ import React from 'react';
|
|||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { AlertTypeModel } from '../../../../triggers_actions_ui/public/types';
|
||||
import { validateMetricThreshold } from './components/validation';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { METRIC_THRESHOLD_ALERT_TYPE_ID } from '../../../server/lib/alerting/metric_threshold/types';
|
||||
import { AlertTypeParams } from '../../../../alerts/common';
|
||||
import {
|
||||
MetricExpressionParams,
|
||||
METRIC_THRESHOLD_ALERT_TYPE_ID,
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
} from '../../../server/lib/alerting/metric_threshold/types';
|
||||
|
||||
export function createMetricThresholdAlertType(): AlertTypeModel {
|
||||
interface MetricThresholdAlertTypeParams extends AlertTypeParams {
|
||||
criteria: MetricExpressionParams[];
|
||||
}
|
||||
|
||||
export function createMetricThresholdAlertType(): AlertTypeModel<MetricThresholdAlertTypeParams> {
|
||||
return {
|
||||
id: METRIC_THRESHOLD_ALERT_TYPE_ID,
|
||||
description: i18n.translate('xpack.infra.metrics.alertFlyout.alertDescription', {
|
||||
|
|
|
@ -9,7 +9,11 @@ import moment from 'moment';
|
|||
import { getCustomMetricLabel } from '../../../../common/formatters/get_custom_metric_label';
|
||||
import { toMetricOpt } from '../../../../common/snapshot_metric_i18n';
|
||||
import { AlertStates, InventoryMetricConditions } from './types';
|
||||
import { RecoveredActionGroup } from '../../../../../alerts/common';
|
||||
import {
|
||||
AlertInstanceContext,
|
||||
AlertInstanceState,
|
||||
RecoveredActionGroup,
|
||||
} from '../../../../../alerts/common';
|
||||
import { AlertExecutorOptions } from '../../../../../alerts/server';
|
||||
import { InventoryItemType, SnapshotMetricType } from '../../../../common/inventory_models/types';
|
||||
import { InfraBackendLibs } from '../../infra_types';
|
||||
|
@ -35,7 +39,15 @@ interface InventoryMetricThresholdParams {
|
|||
export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) => async ({
|
||||
services,
|
||||
params,
|
||||
}: AlertExecutorOptions) => {
|
||||
}: AlertExecutorOptions<
|
||||
/**
|
||||
* TODO: Remove this use of `any` by utilizing a proper type
|
||||
*/
|
||||
Record<string, any>,
|
||||
Record<string, any>,
|
||||
AlertInstanceState,
|
||||
AlertInstanceContext
|
||||
>) => {
|
||||
const {
|
||||
criteria,
|
||||
filterQuery,
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { AlertType } from '../../../../../alerts/server';
|
||||
import { AlertType, AlertInstanceState, AlertInstanceContext } from '../../../../../alerts/server';
|
||||
import {
|
||||
createInventoryMetricThresholdExecutor,
|
||||
FIRED_ACTIONS,
|
||||
|
@ -40,7 +40,17 @@ const condition = schema.object({
|
|||
),
|
||||
});
|
||||
|
||||
export const registerMetricInventoryThresholdAlertType = (libs: InfraBackendLibs): AlertType => ({
|
||||
export const registerMetricInventoryThresholdAlertType = (
|
||||
libs: InfraBackendLibs
|
||||
): AlertType<
|
||||
/**
|
||||
* TODO: Remove this use of `any` by utilizing a proper type
|
||||
*/
|
||||
Record<string, any>,
|
||||
Record<string, any>,
|
||||
AlertInstanceState,
|
||||
AlertInstanceContext
|
||||
> => ({
|
||||
id: METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID,
|
||||
name: i18n.translate('xpack.infra.metrics.inventory.alertName', {
|
||||
defaultMessage: 'Inventory',
|
||||
|
|
|
@ -9,7 +9,10 @@ import {
|
|||
AlertExecutorOptions,
|
||||
AlertServices,
|
||||
AlertInstance,
|
||||
AlertTypeParams,
|
||||
AlertTypeState,
|
||||
AlertInstanceContext,
|
||||
AlertInstanceState,
|
||||
} from '../../../../../alerts/server';
|
||||
import {
|
||||
AlertStates,
|
||||
|
@ -34,6 +37,14 @@ import { getIntervalInSeconds } from '../../../utils/get_interval_in_seconds';
|
|||
import { decodeOrThrow } from '../../../../common/runtime_types';
|
||||
import { UNGROUPED_FACTORY_KEY } from '../common/utils';
|
||||
|
||||
type LogThresholdAlertServices = AlertServices<AlertInstanceState, AlertInstanceContext>;
|
||||
type LogThresholdAlertExecutorOptions = AlertExecutorOptions<
|
||||
AlertTypeParams,
|
||||
AlertTypeState,
|
||||
AlertInstanceState,
|
||||
AlertInstanceContext
|
||||
>;
|
||||
|
||||
const COMPOSITE_GROUP_SIZE = 40;
|
||||
|
||||
const checkValueAgainstComparatorMap: {
|
||||
|
@ -46,7 +57,7 @@ const checkValueAgainstComparatorMap: {
|
|||
};
|
||||
|
||||
export const createLogThresholdExecutor = (libs: InfraBackendLibs) =>
|
||||
async function ({ services, params }: AlertExecutorOptions) {
|
||||
async function ({ services, params }: LogThresholdAlertExecutorOptions) {
|
||||
const { alertInstanceFactory, savedObjectsClient, callCluster } = services;
|
||||
const { sources } = libs;
|
||||
|
||||
|
@ -88,8 +99,8 @@ async function executeAlert(
|
|||
alertParams: CountAlertParams,
|
||||
timestampField: string,
|
||||
indexPattern: string,
|
||||
callCluster: AlertServices['callCluster'],
|
||||
alertInstanceFactory: AlertServices['alertInstanceFactory']
|
||||
callCluster: LogThresholdAlertServices['callCluster'],
|
||||
alertInstanceFactory: LogThresholdAlertServices['alertInstanceFactory']
|
||||
) {
|
||||
const query = getESQuery(alertParams, timestampField, indexPattern);
|
||||
|
||||
|
@ -118,8 +129,8 @@ async function executeRatioAlert(
|
|||
alertParams: RatioAlertParams,
|
||||
timestampField: string,
|
||||
indexPattern: string,
|
||||
callCluster: AlertServices['callCluster'],
|
||||
alertInstanceFactory: AlertServices['alertInstanceFactory']
|
||||
callCluster: LogThresholdAlertServices['callCluster'],
|
||||
alertInstanceFactory: LogThresholdAlertServices['alertInstanceFactory']
|
||||
) {
|
||||
// Ratio alert params are separated out into two standard sets of alert params
|
||||
const numeratorParams: AlertParams = {
|
||||
|
@ -175,7 +186,7 @@ const getESQuery = (
|
|||
export const processUngroupedResults = (
|
||||
results: UngroupedSearchQueryResponse,
|
||||
params: CountAlertParams,
|
||||
alertInstanceFactory: AlertExecutorOptions['services']['alertInstanceFactory'],
|
||||
alertInstanceFactory: LogThresholdAlertExecutorOptions['services']['alertInstanceFactory'],
|
||||
alertInstaceUpdater: AlertInstanceUpdater
|
||||
) => {
|
||||
const { count, criteria } = params;
|
||||
|
@ -204,7 +215,7 @@ export const processUngroupedRatioResults = (
|
|||
numeratorResults: UngroupedSearchQueryResponse,
|
||||
denominatorResults: UngroupedSearchQueryResponse,
|
||||
params: RatioAlertParams,
|
||||
alertInstanceFactory: AlertExecutorOptions['services']['alertInstanceFactory'],
|
||||
alertInstanceFactory: LogThresholdAlertExecutorOptions['services']['alertInstanceFactory'],
|
||||
alertInstaceUpdater: AlertInstanceUpdater
|
||||
) => {
|
||||
const { count, criteria } = params;
|
||||
|
@ -259,7 +270,7 @@ const getReducedGroupByResults = (
|
|||
export const processGroupByResults = (
|
||||
results: GroupedSearchQueryResponse['aggregations']['groups']['buckets'],
|
||||
params: CountAlertParams,
|
||||
alertInstanceFactory: AlertExecutorOptions['services']['alertInstanceFactory'],
|
||||
alertInstanceFactory: LogThresholdAlertExecutorOptions['services']['alertInstanceFactory'],
|
||||
alertInstaceUpdater: AlertInstanceUpdater
|
||||
) => {
|
||||
const { count, criteria } = params;
|
||||
|
@ -292,7 +303,7 @@ export const processGroupByRatioResults = (
|
|||
numeratorResults: GroupedSearchQueryResponse['aggregations']['groups']['buckets'],
|
||||
denominatorResults: GroupedSearchQueryResponse['aggregations']['groups']['buckets'],
|
||||
params: RatioAlertParams,
|
||||
alertInstanceFactory: AlertExecutorOptions['services']['alertInstanceFactory'],
|
||||
alertInstanceFactory: LogThresholdAlertExecutorOptions['services']['alertInstanceFactory'],
|
||||
alertInstaceUpdater: AlertInstanceUpdater
|
||||
) => {
|
||||
const { count, criteria } = params;
|
||||
|
@ -599,11 +610,17 @@ const getQueryMappingForComparator = (comparator: Comparator) => {
|
|||
return queryMappings[comparator];
|
||||
};
|
||||
|
||||
const getUngroupedResults = async (query: object, callCluster: AlertServices['callCluster']) => {
|
||||
const getUngroupedResults = async (
|
||||
query: object,
|
||||
callCluster: LogThresholdAlertServices['callCluster']
|
||||
) => {
|
||||
return decodeOrThrow(UngroupedSearchQueryResponseRT)(await callCluster('search', query));
|
||||
};
|
||||
|
||||
const getGroupedResults = async (query: object, callCluster: AlertServices['callCluster']) => {
|
||||
const getGroupedResults = async (
|
||||
query: object,
|
||||
callCluster: LogThresholdAlertServices['callCluster']
|
||||
) => {
|
||||
let compositeGroupBuckets: GroupedSearchQueryResponse['aggregations']['groups']['buckets'] = [];
|
||||
let lastAfterKey: GroupedSearchQueryResponse['aggregations']['groups']['after_key'] | undefined;
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ import {
|
|||
import { InfraSource } from '../../../../../common/http_api/source_api';
|
||||
import { InfraDatabaseSearchResponse } from '../../../adapters/framework/adapter_types';
|
||||
import { createAfterKeyHandler } from '../../../../utils/create_afterkey_handler';
|
||||
import { AlertServices, AlertExecutorOptions } from '../../../../../../alerts/server';
|
||||
import { AlertServices } from '../../../../../../alerts/server';
|
||||
import { getAllCompositeData } from '../../../../utils/get_all_composite_data';
|
||||
import { DOCUMENT_COUNT_I18N } from '../../common/messages';
|
||||
import { UNGROUPED_FACTORY_KEY } from '../../common/utils';
|
||||
|
@ -35,17 +35,19 @@ interface CompositeAggregationsResponse {
|
|||
};
|
||||
}
|
||||
|
||||
export const evaluateAlert = (
|
||||
export interface EvaluatedAlertParams {
|
||||
criteria: MetricExpressionParams[];
|
||||
groupBy: string | undefined | string[];
|
||||
filterQuery: string | undefined;
|
||||
}
|
||||
|
||||
export const evaluateAlert = <Params extends EvaluatedAlertParams = EvaluatedAlertParams>(
|
||||
callCluster: AlertServices['callCluster'],
|
||||
params: AlertExecutorOptions['params'],
|
||||
params: Params,
|
||||
config: InfraSource['configuration'],
|
||||
timeframe?: { start: number; end: number }
|
||||
) => {
|
||||
const { criteria, groupBy, filterQuery } = params as {
|
||||
criteria: MetricExpressionParams[];
|
||||
groupBy: string | undefined | string[];
|
||||
filterQuery: string | undefined;
|
||||
};
|
||||
const { criteria, groupBy, filterQuery } = params;
|
||||
return Promise.all(
|
||||
criteria.map(async (criterion) => {
|
||||
const currentValues = await getMetric(
|
||||
|
|
|
@ -7,13 +7,13 @@ import { createMetricThresholdExecutor, FIRED_ACTIONS } from './metric_threshold
|
|||
import { Comparator, AlertStates } from './types';
|
||||
import * as mocks from './test_mocks';
|
||||
import { RecoveredActionGroup } from '../../../../../alerts/common';
|
||||
import { AlertExecutorOptions } from '../../../../../alerts/server';
|
||||
import {
|
||||
alertsMock,
|
||||
AlertServicesMock,
|
||||
AlertInstanceMock,
|
||||
} from '../../../../../alerts/server/mocks';
|
||||
import { InfraSources } from '../../sources';
|
||||
import { MetricThresholdAlertExecutorOptions } from './register_metric_threshold_alert_type';
|
||||
|
||||
interface AlertTestInstance {
|
||||
instance: AlertInstanceMock;
|
||||
|
@ -23,11 +23,23 @@ interface AlertTestInstance {
|
|||
|
||||
let persistAlertInstances = false;
|
||||
|
||||
const mockOptions = {
|
||||
alertId: '',
|
||||
startedAt: new Date(),
|
||||
previousStartedAt: null,
|
||||
state: {},
|
||||
spaceId: '',
|
||||
name: '',
|
||||
tags: [],
|
||||
createdBy: null,
|
||||
updatedBy: null,
|
||||
};
|
||||
|
||||
describe('The metric threshold alert type', () => {
|
||||
describe('querying the entire infrastructure', () => {
|
||||
const instanceID = '*';
|
||||
const execute = (comparator: Comparator, threshold: number[], sourceId: string = 'default') =>
|
||||
executor({
|
||||
executor(({
|
||||
services,
|
||||
params: {
|
||||
sourceId,
|
||||
|
@ -39,7 +51,10 @@ describe('The metric threshold alert type', () => {
|
|||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
/**
|
||||
* TODO: Remove this use of `as` by utilizing a proper type
|
||||
*/
|
||||
} as unknown) as MetricThresholdAlertExecutorOptions);
|
||||
test('alerts as expected with the > comparator', async () => {
|
||||
await execute(Comparator.GT, [0.75]);
|
||||
expect(mostRecentAction(instanceID).id).toBe(FIRED_ACTIONS.id);
|
||||
|
@ -109,6 +124,7 @@ describe('The metric threshold alert type', () => {
|
|||
describe('querying with a groupBy parameter', () => {
|
||||
const execute = (comparator: Comparator, threshold: number[]) =>
|
||||
executor({
|
||||
...mockOptions,
|
||||
services,
|
||||
params: {
|
||||
groupBy: 'something',
|
||||
|
@ -159,6 +175,7 @@ describe('The metric threshold alert type', () => {
|
|||
groupBy: string = ''
|
||||
) =>
|
||||
executor({
|
||||
...mockOptions,
|
||||
services,
|
||||
params: {
|
||||
groupBy,
|
||||
|
@ -216,6 +233,7 @@ describe('The metric threshold alert type', () => {
|
|||
const instanceID = '*';
|
||||
const execute = (comparator: Comparator, threshold: number[]) =>
|
||||
executor({
|
||||
...mockOptions,
|
||||
services,
|
||||
params: {
|
||||
criteria: [
|
||||
|
@ -242,6 +260,7 @@ describe('The metric threshold alert type', () => {
|
|||
const instanceID = '*';
|
||||
const execute = (comparator: Comparator, threshold: number[]) =>
|
||||
executor({
|
||||
...mockOptions,
|
||||
services,
|
||||
params: {
|
||||
criteria: [
|
||||
|
@ -268,6 +287,7 @@ describe('The metric threshold alert type', () => {
|
|||
const instanceID = '*';
|
||||
const execute = (comparator: Comparator, threshold: number[]) =>
|
||||
executor({
|
||||
...mockOptions,
|
||||
services,
|
||||
params: {
|
||||
criteria: [
|
||||
|
@ -294,6 +314,7 @@ describe('The metric threshold alert type', () => {
|
|||
const instanceID = '*';
|
||||
const execute = (alertOnNoData: boolean) =>
|
||||
executor({
|
||||
...mockOptions,
|
||||
services,
|
||||
params: {
|
||||
criteria: [
|
||||
|
@ -323,6 +344,7 @@ describe('The metric threshold alert type', () => {
|
|||
const instanceID = '*';
|
||||
const execute = () =>
|
||||
executor({
|
||||
...mockOptions,
|
||||
services,
|
||||
params: {
|
||||
criteria: [
|
||||
|
@ -348,6 +370,7 @@ describe('The metric threshold alert type', () => {
|
|||
const instanceID = '*';
|
||||
const execute = (threshold: number[]) =>
|
||||
executor({
|
||||
...mockOptions,
|
||||
services,
|
||||
params: {
|
||||
criteria: [
|
||||
|
@ -392,6 +415,7 @@ describe('The metric threshold alert type', () => {
|
|||
const instanceID = '*';
|
||||
const execute = () =>
|
||||
executor({
|
||||
...mockOptions,
|
||||
services,
|
||||
params: {
|
||||
sourceId: 'default',
|
||||
|
@ -435,10 +459,7 @@ const mockLibs: any = {
|
|||
configuration: createMockStaticConfiguration({}),
|
||||
};
|
||||
|
||||
const executor = createMetricThresholdExecutor(mockLibs) as (opts: {
|
||||
params: AlertExecutorOptions['params'];
|
||||
services: { callCluster: AlertExecutorOptions['params']['callCluster'] };
|
||||
}) => Promise<void>;
|
||||
const executor = createMetricThresholdExecutor(mockLibs);
|
||||
|
||||
const services: AlertServicesMock = alertsMock.createAlertServices();
|
||||
services.callCluster.mockImplementation(async (_: string, { body, index }: any) => {
|
||||
|
|
|
@ -7,7 +7,6 @@ import { first, last } from 'lodash';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import moment from 'moment';
|
||||
import { RecoveredActionGroup } from '../../../../../alerts/common';
|
||||
import { AlertExecutorOptions } from '../../../../../alerts/server';
|
||||
import { InfraBackendLibs } from '../../infra_types';
|
||||
import {
|
||||
buildErrorAlertReason,
|
||||
|
@ -18,10 +17,16 @@ import {
|
|||
} from '../common/messages';
|
||||
import { createFormatter } from '../../../../common/formatters';
|
||||
import { AlertStates } from './types';
|
||||
import { evaluateAlert } from './lib/evaluate_alert';
|
||||
import { evaluateAlert, EvaluatedAlertParams } from './lib/evaluate_alert';
|
||||
import {
|
||||
MetricThresholdAlertExecutorOptions,
|
||||
MetricThresholdAlertType,
|
||||
} from './register_metric_threshold_alert_type';
|
||||
|
||||
export const createMetricThresholdExecutor = (libs: InfraBackendLibs) =>
|
||||
async function (options: AlertExecutorOptions) {
|
||||
export const createMetricThresholdExecutor = (
|
||||
libs: InfraBackendLibs
|
||||
): MetricThresholdAlertType['executor'] =>
|
||||
async function (options: MetricThresholdAlertExecutorOptions) {
|
||||
const { services, params } = options;
|
||||
const { criteria } = params;
|
||||
if (criteria.length === 0) throw new Error('Cannot execute an alert with 0 conditions');
|
||||
|
@ -36,7 +41,11 @@ export const createMetricThresholdExecutor = (libs: InfraBackendLibs) =>
|
|||
sourceId || 'default'
|
||||
);
|
||||
const config = source.configuration;
|
||||
const alertResults = await evaluateAlert(services.callCluster, params, config);
|
||||
const alertResults = await evaluateAlert(
|
||||
services.callCluster,
|
||||
params as EvaluatedAlertParams,
|
||||
config
|
||||
);
|
||||
|
||||
// Because each alert result has the same group definitions, just grab the groups from the first one.
|
||||
const groups = Object.keys(first(alertResults)!);
|
||||
|
|
|
@ -5,7 +5,12 @@
|
|||
*/
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { AlertType } from '../../../../../alerts/server';
|
||||
import {
|
||||
AlertType,
|
||||
AlertInstanceState,
|
||||
AlertInstanceContext,
|
||||
AlertExecutorOptions,
|
||||
} from '../../../../../alerts/server';
|
||||
import { METRIC_EXPLORER_AGGREGATIONS } from '../../../../common/http_api/metrics_explorer';
|
||||
import { createMetricThresholdExecutor, FIRED_ACTIONS } from './metric_threshold_executor';
|
||||
import { METRIC_THRESHOLD_ALERT_TYPE_ID, Comparator } from './types';
|
||||
|
@ -21,7 +26,26 @@ import {
|
|||
thresholdActionVariableDescription,
|
||||
} from '../common/messages';
|
||||
|
||||
export function registerMetricThresholdAlertType(libs: InfraBackendLibs): AlertType {
|
||||
export type MetricThresholdAlertType = AlertType<
|
||||
/**
|
||||
* TODO: Remove this use of `any` by utilizing a proper type
|
||||
*/
|
||||
Record<string, any>,
|
||||
Record<string, any>,
|
||||
AlertInstanceState,
|
||||
AlertInstanceContext
|
||||
>;
|
||||
export type MetricThresholdAlertExecutorOptions = AlertExecutorOptions<
|
||||
/**
|
||||
* TODO: Remove this use of `any` by utilizing a proper type
|
||||
*/
|
||||
Record<string, any>,
|
||||
Record<string, any>,
|
||||
AlertInstanceState,
|
||||
AlertInstanceContext
|
||||
>;
|
||||
|
||||
export function registerMetricThresholdAlertType(libs: InfraBackendLibs): MetricThresholdAlertType {
|
||||
const baseCriterion = {
|
||||
threshold: schema.arrayOf(schema.number()),
|
||||
comparator: oneOfLiterals(Object.values(Comparator)),
|
||||
|
|
|
@ -4,14 +4,14 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { Alert, SanitizedAlert } from '../../../alerts/common';
|
||||
import { Alert, AlertTypeParams, SanitizedAlert } from '../../../alerts/common';
|
||||
import { AlertParamType, AlertMessageTokenType, AlertSeverity } from '../enums';
|
||||
|
||||
export type CommonAlert = Alert | SanitizedAlert;
|
||||
export type CommonAlert = Alert<AlertTypeParams> | SanitizedAlert<AlertTypeParams>;
|
||||
|
||||
export interface CommonAlertStatus {
|
||||
states: CommonAlertState[];
|
||||
rawAlert: Alert | SanitizedAlert;
|
||||
rawAlert: Alert<AlertTypeParams> | SanitizedAlert<AlertTypeParams>;
|
||||
}
|
||||
|
||||
export interface CommonAlertState {
|
||||
|
|
|
@ -9,8 +9,9 @@ import { i18n } from '@kbn/i18n';
|
|||
import { Expression, Props } from '../components/duration/expression';
|
||||
import { AlertTypeModel, ValidationResult } from '../../../../triggers_actions_ui/public';
|
||||
import { ALERT_CCR_READ_EXCEPTIONS, ALERT_DETAILS } from '../../../common/constants';
|
||||
import { AlertTypeParams } from '../../../../alerts/common';
|
||||
|
||||
interface ValidateOptions {
|
||||
interface ValidateOptions extends AlertTypeParams {
|
||||
duration: string;
|
||||
}
|
||||
|
||||
|
@ -30,7 +31,7 @@ const validate = (inputValues: ValidateOptions): ValidationResult => {
|
|||
return validationResult;
|
||||
};
|
||||
|
||||
export function createCCRReadExceptionsAlertType(): AlertTypeModel {
|
||||
export function createCCRReadExceptionsAlertType(): AlertTypeModel<ValidateOptions> {
|
||||
return {
|
||||
id: ALERT_CCR_READ_EXCEPTIONS,
|
||||
description: ALERT_DETAILS[ALERT_CCR_READ_EXCEPTIONS].description,
|
||||
|
|
|
@ -5,9 +5,11 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { AlertTypeParams } from '../../../../../alerts/common';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { ValidationResult } from '../../../../../triggers_actions_ui/public/types';
|
||||
|
||||
export type MonitoringAlertTypeParams = ValidateOptions & AlertTypeParams;
|
||||
interface ValidateOptions {
|
||||
duration: string;
|
||||
threshold: number;
|
||||
|
|
|
@ -7,10 +7,10 @@ import React from 'react';
|
|||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { AlertTypeModel } from '../../../../triggers_actions_ui/public/types';
|
||||
import { ALERT_CPU_USAGE, ALERT_DETAILS } from '../../../common/constants';
|
||||
import { validate } from '../components/duration/validation';
|
||||
import { validate, MonitoringAlertTypeParams } from '../components/duration/validation';
|
||||
import { Expression, Props } from '../components/duration/expression';
|
||||
|
||||
export function createCpuUsageAlertType(): AlertTypeModel {
|
||||
export function createCpuUsageAlertType(): AlertTypeModel<MonitoringAlertTypeParams> {
|
||||
return {
|
||||
id: ALERT_CPU_USAGE,
|
||||
description: ALERT_DETAILS[ALERT_CPU_USAGE].description,
|
||||
|
|
|
@ -5,14 +5,14 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { validate } from '../components/duration/validation';
|
||||
import { validate, MonitoringAlertTypeParams } from '../components/duration/validation';
|
||||
import { Expression, Props } from '../components/duration/expression';
|
||||
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { AlertTypeModel } from '../../../../triggers_actions_ui/public/types';
|
||||
import { ALERT_DISK_USAGE, ALERT_DETAILS } from '../../../common/constants';
|
||||
|
||||
export function createDiskUsageAlertType(): AlertTypeModel {
|
||||
export function createDiskUsageAlertType(): AlertTypeModel<MonitoringAlertTypeParams> {
|
||||
return {
|
||||
id: ALERT_DISK_USAGE,
|
||||
description: ALERT_DETAILS[ALERT_DISK_USAGE].description,
|
||||
|
|
|
@ -5,14 +5,14 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { validate } from '../components/duration/validation';
|
||||
import { validate, MonitoringAlertTypeParams } from '../components/duration/validation';
|
||||
import { Expression, Props } from '../components/duration/expression';
|
||||
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { AlertTypeModel } from '../../../../triggers_actions_ui/public/types';
|
||||
import { ALERT_MEMORY_USAGE, ALERT_DETAILS } from '../../../common/constants';
|
||||
|
||||
export function createMemoryUsageAlertType(): AlertTypeModel {
|
||||
export function createMemoryUsageAlertType(): AlertTypeModel<MonitoringAlertTypeParams> {
|
||||
return {
|
||||
id: ALERT_MEMORY_USAGE,
|
||||
description: ALERT_DETAILS[ALERT_MEMORY_USAGE].description,
|
||||
|
|
|
@ -13,7 +13,7 @@ import {
|
|||
AlertsClient,
|
||||
AlertServices,
|
||||
} from '../../../alerts/server';
|
||||
import { Alert, RawAlertInstance, SanitizedAlert } from '../../../alerts/common';
|
||||
import { Alert, AlertTypeParams, RawAlertInstance, SanitizedAlert } from '../../../alerts/common';
|
||||
import { ActionsClient } from '../../../actions/server';
|
||||
import {
|
||||
AlertState,
|
||||
|
@ -135,7 +135,7 @@ export class BaseAlert {
|
|||
alertsClient: AlertsClient,
|
||||
actionsClient: ActionsClient,
|
||||
actions: AlertEnableAction[]
|
||||
): Promise<Alert> {
|
||||
): Promise<Alert<AlertTypeParams>> {
|
||||
const existingAlertData = await alertsClient.find({
|
||||
options: {
|
||||
search: this.alertOptions.id,
|
||||
|
@ -170,7 +170,7 @@ export class BaseAlert {
|
|||
throttle = '1d',
|
||||
interval = '1m',
|
||||
} = this.alertOptions;
|
||||
return await alertsClient.create({
|
||||
return await alertsClient.create<AlertTypeParams>({
|
||||
data: {
|
||||
enabled: true,
|
||||
tags: [],
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import { Alert } from '../../../../../alerts/common';
|
||||
import { SERVER_APP_ID, NOTIFICATIONS_ID } from '../../../../common/constants';
|
||||
import { CreateNotificationParams } from './types';
|
||||
import { CreateNotificationParams, RuleNotificationAlertTypeParams } from './types';
|
||||
import { addTags } from './add_tags';
|
||||
import { transformRuleToAlertAction } from '../../../../common/detection_engine/transform_actions';
|
||||
|
||||
|
@ -17,8 +17,8 @@ export const createNotifications = async ({
|
|||
ruleAlertId,
|
||||
interval,
|
||||
name,
|
||||
}: CreateNotificationParams): Promise<Alert> =>
|
||||
alertsClient.create({
|
||||
}: CreateNotificationParams): Promise<Alert<RuleNotificationAlertTypeParams>> =>
|
||||
alertsClient.create<RuleNotificationAlertTypeParams>({
|
||||
data: {
|
||||
name,
|
||||
tags: addTags([], ruleAlertId),
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { FindResult } from '../../../../../alerts/server';
|
||||
import { AlertTypeParams, FindResult } from '../../../../../alerts/server';
|
||||
import { NOTIFICATIONS_ID } from '../../../../common/constants';
|
||||
import { FindNotificationParams } from './types';
|
||||
|
||||
|
@ -24,7 +24,7 @@ export const findNotifications = async ({
|
|||
filter,
|
||||
sortField,
|
||||
sortOrder,
|
||||
}: FindNotificationParams): Promise<FindResult> =>
|
||||
}: FindNotificationParams): Promise<FindResult<AlertTypeParams>> =>
|
||||
alertsClient.find({
|
||||
options: {
|
||||
fields,
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { SanitizedAlert } from '../../../../../alerts/common';
|
||||
import { AlertTypeParams, SanitizedAlert } from '../../../../../alerts/common';
|
||||
import { ReadNotificationParams, isAlertType } from './types';
|
||||
import { findNotifications } from './find_notifications';
|
||||
import { INTERNAL_RULE_ALERT_ID_KEY } from '../../../../common/constants';
|
||||
|
@ -13,7 +13,7 @@ export const readNotifications = async ({
|
|||
alertsClient,
|
||||
id,
|
||||
ruleAlertId,
|
||||
}: ReadNotificationParams): Promise<SanitizedAlert | null> => {
|
||||
}: ReadNotificationParams): Promise<SanitizedAlert<AlertTypeParams> | null> => {
|
||||
if (id != null) {
|
||||
try {
|
||||
const notification = await alertsClient.get({ id });
|
||||
|
|
|
@ -8,18 +8,20 @@ import {
|
|||
AlertsClient,
|
||||
PartialAlert,
|
||||
AlertType,
|
||||
AlertTypeParams,
|
||||
AlertTypeState,
|
||||
AlertInstanceState,
|
||||
AlertInstanceContext,
|
||||
AlertExecutorOptions,
|
||||
} from '../../../../../alerts/server';
|
||||
import { Alert } from '../../../../../alerts/common';
|
||||
import { NOTIFICATIONS_ID } from '../../../../common/constants';
|
||||
import { RuleAlertAction } from '../../../../common/detection_engine/types';
|
||||
|
||||
export interface RuleNotificationAlertType extends Alert {
|
||||
params: {
|
||||
ruleAlertId: string;
|
||||
};
|
||||
export interface RuleNotificationAlertTypeParams extends AlertTypeParams {
|
||||
ruleAlertId: string;
|
||||
}
|
||||
export type RuleNotificationAlertType = Alert<RuleNotificationAlertTypeParams>;
|
||||
|
||||
export interface FindNotificationParams {
|
||||
alertsClient: AlertsClient;
|
||||
|
@ -76,32 +78,36 @@ export interface ReadNotificationParams {
|
|||
}
|
||||
|
||||
export const isAlertTypes = (
|
||||
partialAlert: PartialAlert[]
|
||||
partialAlert: Array<PartialAlert<AlertTypeParams>>
|
||||
): partialAlert is RuleNotificationAlertType[] => {
|
||||
return partialAlert.every((rule) => isAlertType(rule));
|
||||
};
|
||||
|
||||
export const isAlertType = (
|
||||
partialAlert: PartialAlert
|
||||
partialAlert: PartialAlert<AlertTypeParams>
|
||||
): partialAlert is RuleNotificationAlertType => {
|
||||
return partialAlert.alertTypeId === NOTIFICATIONS_ID;
|
||||
};
|
||||
|
||||
export type NotificationExecutorOptions = Omit<AlertExecutorOptions, 'params'> & {
|
||||
params: {
|
||||
ruleAlertId: string;
|
||||
};
|
||||
};
|
||||
export type NotificationExecutorOptions = AlertExecutorOptions<
|
||||
RuleNotificationAlertTypeParams,
|
||||
AlertTypeState,
|
||||
AlertInstanceState,
|
||||
AlertInstanceContext
|
||||
>;
|
||||
|
||||
// This returns true because by default a NotificationAlertTypeDefinition is an AlertType
|
||||
// since we are only increasing the strictness of params.
|
||||
export const isNotificationAlertExecutor = (
|
||||
obj: NotificationAlertTypeDefinition
|
||||
): obj is AlertType => {
|
||||
): obj is AlertType<AlertTypeParams, AlertTypeState, AlertInstanceState, AlertInstanceContext> => {
|
||||
return true;
|
||||
};
|
||||
|
||||
export type NotificationAlertTypeDefinition = Omit<AlertType, 'executor'> & {
|
||||
export type NotificationAlertTypeDefinition = Omit<
|
||||
AlertType<AlertTypeParams, AlertTypeState, AlertInstanceState, AlertInstanceContext>,
|
||||
'executor'
|
||||
> & {
|
||||
executor: ({
|
||||
services,
|
||||
params,
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import { PartialAlert } from '../../../../../alerts/server';
|
||||
import { readNotifications } from './read_notifications';
|
||||
import { UpdateNotificationParams } from './types';
|
||||
import { RuleNotificationAlertTypeParams, UpdateNotificationParams } from './types';
|
||||
import { addTags } from './add_tags';
|
||||
import { createNotifications } from './create_notifications';
|
||||
import { transformRuleToAlertAction } from '../../../../common/detection_engine/transform_actions';
|
||||
|
@ -18,11 +18,11 @@ export const updateNotifications = async ({
|
|||
ruleAlertId,
|
||||
name,
|
||||
interval,
|
||||
}: UpdateNotificationParams): Promise<PartialAlert | null> => {
|
||||
}: UpdateNotificationParams): Promise<PartialAlert<RuleNotificationAlertTypeParams> | null> => {
|
||||
const notification = await readNotifications({ alertsClient, id: undefined, ruleAlertId });
|
||||
|
||||
if (interval && notification) {
|
||||
return alertsClient.update({
|
||||
return alertsClient.update<RuleNotificationAlertTypeParams>({
|
||||
id: notification.id,
|
||||
data: {
|
||||
tags: addTags([], ruleAlertId),
|
||||
|
|
|
@ -22,6 +22,8 @@ import { buildRouteValidation } from '../../../../utils/build_validation/route_v
|
|||
import { transformBulkError, createBulkErrorObject, buildSiemResponse } from '../utils';
|
||||
import { updateRulesNotifications } from '../../rules/update_rules_notifications';
|
||||
import { convertCreateAPIToInternalSchema } from '../../schemas/rule_converters';
|
||||
import { RuleTypeParams } from '../../types';
|
||||
import { Alert } from '../../../../../../alerts/common';
|
||||
|
||||
export const createRulesBulkRoute = (router: IRouter, ml: SetupPlugins['ml']) => {
|
||||
router.post(
|
||||
|
@ -95,9 +97,12 @@ export const createRulesBulkRoute = (router: IRouter, ml: SetupPlugins['ml']) =>
|
|||
});
|
||||
}
|
||||
|
||||
const createdRule = await alertsClient.create({
|
||||
/**
|
||||
* TODO: Remove this use of `as` by utilizing the proper type
|
||||
*/
|
||||
const createdRule = (await alertsClient.create({
|
||||
data: internalRule,
|
||||
});
|
||||
})) as Alert<RuleTypeParams>;
|
||||
|
||||
const ruleActions = await updateRulesNotifications({
|
||||
ruleAlertId: createdRule.id,
|
||||
|
|
|
@ -19,6 +19,8 @@ import { createRulesSchema } from '../../../../../common/detection_engine/schema
|
|||
import { newTransformValidate } from './validate';
|
||||
import { createRuleValidateTypeDependents } from '../../../../../common/detection_engine/schemas/request/create_rules_type_dependents';
|
||||
import { convertCreateAPIToInternalSchema } from '../../schemas/rule_converters';
|
||||
import { RuleTypeParams } from '../../types';
|
||||
import { Alert } from '../../../../../../alerts/common';
|
||||
|
||||
export const createRulesRoute = (router: IRouter, ml: SetupPlugins['ml']): void => {
|
||||
router.post(
|
||||
|
@ -85,9 +87,12 @@ export const createRulesRoute = (router: IRouter, ml: SetupPlugins['ml']): void
|
|||
// This will create the endpoint list if it does not exist yet
|
||||
await context.lists?.getExceptionListClient().createEndpointList();
|
||||
|
||||
const createdRule = await alertsClient.create({
|
||||
/**
|
||||
* TODO: Remove this use of `as` by utilizing the proper type
|
||||
*/
|
||||
const createdRule = (await alertsClient.create({
|
||||
data: internalRule,
|
||||
});
|
||||
})) as Alert<RuleTypeParams>;
|
||||
|
||||
const ruleActions = await updateRulesNotifications({
|
||||
ruleAlertId: createdRule.id,
|
||||
|
|
|
@ -31,6 +31,7 @@ import {
|
|||
OutputError,
|
||||
} from '../utils';
|
||||
import { RuleActions } from '../../rule_actions/types';
|
||||
import { RuleTypeParams } from '../../types';
|
||||
|
||||
type PromiseFromStreams = ImportRulesSchemaDecoded | Error;
|
||||
|
||||
|
@ -172,7 +173,7 @@ export const transformAlertsToRules = (alerts: RuleAlertType[]): Array<Partial<R
|
|||
};
|
||||
|
||||
export const transformFindAlerts = (
|
||||
findResults: FindResult,
|
||||
findResults: FindResult<RuleTypeParams>,
|
||||
ruleActions: Array<RuleActions | null>,
|
||||
ruleStatuses?: Array<SavedObjectsFindResponse<IRuleSavedAttributesSavedObjectAttributes>>
|
||||
): {
|
||||
|
@ -203,7 +204,7 @@ export const transformFindAlerts = (
|
|||
};
|
||||
|
||||
export const transform = (
|
||||
alert: PartialAlert,
|
||||
alert: PartialAlert<RuleTypeParams>,
|
||||
ruleActions?: RuleActions | null,
|
||||
ruleStatus?: SavedObject<IRuleSavedAttributesSavedObjectAttributes>
|
||||
): Partial<RulesSchema> | null => {
|
||||
|
@ -220,7 +221,7 @@ export const transform = (
|
|||
|
||||
export const transformOrBulkError = (
|
||||
ruleId: string,
|
||||
alert: PartialAlert,
|
||||
alert: PartialAlert<RuleTypeParams>,
|
||||
ruleActions: RuleActions,
|
||||
ruleStatus?: unknown
|
||||
): Partial<RulesSchema> | BulkError => {
|
||||
|
@ -241,7 +242,7 @@ export const transformOrBulkError = (
|
|||
|
||||
export const transformOrImportError = (
|
||||
ruleId: string,
|
||||
alert: PartialAlert,
|
||||
alert: PartialAlert<RuleTypeParams>,
|
||||
existingImportSuccessError: ImportSuccessError
|
||||
): ImportSuccessError => {
|
||||
if (isAlertType(alert)) {
|
||||
|
|
|
@ -15,6 +15,7 @@ import { RulesSchema } from '../../../../../common/detection_engine/schemas/resp
|
|||
import { getResult, getFindResultStatus } from '../__mocks__/request_responses';
|
||||
import { getListArrayMock } from '../../../../../common/detection_engine/schemas/types/lists.mock';
|
||||
import { getThreatMock } from '../../../../../common/detection_engine/schemas/types/threat.mock';
|
||||
import { RuleTypeParams } from '../../types';
|
||||
|
||||
export const ruleOutput = (): RulesSchema => ({
|
||||
actions: [],
|
||||
|
@ -88,7 +89,7 @@ describe('validate', () => {
|
|||
|
||||
describe('transformValidateFindAlerts', () => {
|
||||
test('it should do a validation correctly of a find alert', () => {
|
||||
const findResult: FindResult = {
|
||||
const findResult: FindResult<RuleTypeParams> = {
|
||||
data: [getResult()],
|
||||
page: 1,
|
||||
perPage: 0,
|
||||
|
@ -111,7 +112,7 @@ describe('validate', () => {
|
|||
});
|
||||
|
||||
test('it should do an in-validation correctly of a partial alert', () => {
|
||||
const findResult: FindResult = {
|
||||
const findResult: FindResult<RuleTypeParams> = {
|
||||
data: [getResult()],
|
||||
page: 1,
|
||||
perPage: 0,
|
||||
|
|
|
@ -31,9 +31,10 @@ import {
|
|||
import { createBulkErrorObject, BulkError } from '../utils';
|
||||
import { transformFindAlerts, transform, transformAlertToRule } from './utils';
|
||||
import { RuleActions } from '../../rule_actions/types';
|
||||
import { RuleTypeParams } from '../../types';
|
||||
|
||||
export const transformValidateFindAlerts = (
|
||||
findResults: FindResult,
|
||||
findResults: FindResult<RuleTypeParams>,
|
||||
ruleActions: Array<RuleActions | null>,
|
||||
ruleStatuses?: Array<SavedObjectsFindResponse<IRuleSavedAttributesSavedObjectAttributes>>
|
||||
): [
|
||||
|
@ -63,7 +64,7 @@ export const transformValidateFindAlerts = (
|
|||
};
|
||||
|
||||
export const transformValidate = (
|
||||
alert: PartialAlert,
|
||||
alert: PartialAlert<RuleTypeParams>,
|
||||
ruleActions?: RuleActions | null,
|
||||
ruleStatus?: SavedObject<IRuleSavedAttributesSavedObjectAttributes>
|
||||
): [RulesSchema | null, string | null] => {
|
||||
|
@ -76,7 +77,7 @@ export const transformValidate = (
|
|||
};
|
||||
|
||||
export const newTransformValidate = (
|
||||
alert: PartialAlert,
|
||||
alert: PartialAlert<RuleTypeParams>,
|
||||
ruleActions?: RuleActions | null,
|
||||
ruleStatus?: SavedObject<IRuleSavedAttributesSavedObjectAttributes>
|
||||
): [FullResponseSchema | null, string | null] => {
|
||||
|
@ -90,7 +91,7 @@ export const newTransformValidate = (
|
|||
|
||||
export const transformValidateBulkError = (
|
||||
ruleId: string,
|
||||
alert: PartialAlert,
|
||||
alert: PartialAlert<RuleTypeParams>,
|
||||
ruleActions?: RuleActions | null,
|
||||
ruleStatus?: SavedObjectsFindResponse<IRuleStatusSOAttributes>
|
||||
): RulesSchema | BulkError => {
|
||||
|
|
|
@ -9,6 +9,7 @@ import { Alert } from '../../../../../alerts/common';
|
|||
import { SERVER_APP_ID, SIGNALS_ID } from '../../../../common/constants';
|
||||
import { CreateRulesOptions } from './types';
|
||||
import { addTags } from './add_tags';
|
||||
import { PartialFilter, RuleTypeParams } from '../types';
|
||||
|
||||
export const createRules = async ({
|
||||
alertsClient,
|
||||
|
@ -59,8 +60,8 @@ export const createRules = async ({
|
|||
version,
|
||||
exceptionsList,
|
||||
actions,
|
||||
}: CreateRulesOptions): Promise<Alert> => {
|
||||
return alertsClient.create({
|
||||
}: CreateRulesOptions): Promise<Alert<RuleTypeParams>> => {
|
||||
return alertsClient.create<RuleTypeParams>({
|
||||
data: {
|
||||
name,
|
||||
tags: addTags(tags, ruleId, immutable),
|
||||
|
@ -95,7 +96,10 @@ export const createRules = async ({
|
|||
severityMapping,
|
||||
threat,
|
||||
threshold,
|
||||
threatFilters,
|
||||
/**
|
||||
* TODO: Fix typing inconsistancy between `RuleTypeParams` and `CreateRulesOptions`
|
||||
*/
|
||||
threatFilters: threatFilters as PartialFilter[] | undefined,
|
||||
threatIndex,
|
||||
threatQuery,
|
||||
concurrentSearches,
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
import { FindResult } from '../../../../../alerts/server';
|
||||
import { SIGNALS_ID } from '../../../../common/constants';
|
||||
import { RuleTypeParams } from '../types';
|
||||
import { FindRuleOptions } from './types';
|
||||
|
||||
export const getFilter = (filter: string | null | undefined) => {
|
||||
|
@ -24,7 +25,7 @@ export const findRules = async ({
|
|||
filter,
|
||||
sortField,
|
||||
sortOrder,
|
||||
}: FindRuleOptions): Promise<FindResult> => {
|
||||
}: FindRuleOptions): Promise<FindResult<RuleTypeParams>> => {
|
||||
return alertsClient.find({
|
||||
options: {
|
||||
fields,
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import { AddPrepackagedRulesSchemaDecoded } from '../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema';
|
||||
import { Alert } from '../../../../../alerts/common';
|
||||
import { Alert, AlertTypeParams } from '../../../../../alerts/common';
|
||||
import { AlertsClient } from '../../../../../alerts/server';
|
||||
import { createRules } from './create_rules';
|
||||
import { PartialFilter } from '../types';
|
||||
|
@ -14,8 +14,8 @@ export const installPrepackagedRules = (
|
|||
alertsClient: AlertsClient,
|
||||
rules: AddPrepackagedRulesSchemaDecoded[],
|
||||
outputIndex: string
|
||||
): Array<Promise<Alert>> =>
|
||||
rules.reduce<Array<Promise<Alert>>>((acc, rule) => {
|
||||
): Array<Promise<Alert<AlertTypeParams>>> =>
|
||||
rules.reduce<Array<Promise<Alert<AlertTypeParams>>>>((acc, rule) => {
|
||||
const {
|
||||
anomaly_threshold: anomalyThreshold,
|
||||
author,
|
||||
|
|
|
@ -9,8 +9,9 @@ import { alertsClientMock } from '../../../../../alerts/server/mocks';
|
|||
import { savedObjectsClientMock } from '../../../../../../../src/core/server/mocks';
|
||||
import { INTERNAL_RULE_ID_KEY, INTERNAL_IMMUTABLE_KEY } from '../../../../common/constants';
|
||||
import { SanitizedAlert } from '../../../../../alerts/common';
|
||||
import { RuleTypeParams } from '../types';
|
||||
|
||||
const rule: SanitizedAlert = {
|
||||
const rule: SanitizedAlert<RuleTypeParams> = {
|
||||
id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd',
|
||||
name: 'Detect Root/Admin Users',
|
||||
tags: [`${INTERNAL_RULE_ID_KEY}:rule-1`, `${INTERNAL_IMMUTABLE_KEY}:false`],
|
||||
|
@ -67,6 +68,8 @@ const rule: SanitizedAlert = {
|
|||
note: '# Investigative notes',
|
||||
version: 1,
|
||||
exceptionsList: [
|
||||
/**
|
||||
TODO: fix this mock. Which the typing has revealed is wrong
|
||||
{
|
||||
field: 'source.ip',
|
||||
values_operator: 'included',
|
||||
|
@ -96,8 +99,31 @@ const rule: SanitizedAlert = {
|
|||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},*/
|
||||
],
|
||||
/**
|
||||
* The fields below were missing as the type was partial and hence not technically correct
|
||||
*/
|
||||
author: [],
|
||||
buildingBlockType: undefined,
|
||||
eventCategoryOverride: undefined,
|
||||
license: undefined,
|
||||
savedId: undefined,
|
||||
interval: undefined,
|
||||
riskScoreMapping: undefined,
|
||||
ruleNameOverride: undefined,
|
||||
name: undefined,
|
||||
severityMapping: undefined,
|
||||
tags: undefined,
|
||||
threshold: undefined,
|
||||
threatFilters: undefined,
|
||||
threatIndex: undefined,
|
||||
threatQuery: undefined,
|
||||
threatMapping: undefined,
|
||||
threatLanguage: undefined,
|
||||
concurrentSearches: undefined,
|
||||
itemsPerSearch: undefined,
|
||||
timestampOverride: undefined,
|
||||
},
|
||||
createdAt: new Date('2019-12-13T16:40:33.400Z'),
|
||||
updatedAt: new Date('2019-12-13T16:40:33.400Z'),
|
||||
|
|
|
@ -13,6 +13,7 @@ import { addTags } from './add_tags';
|
|||
import { calculateVersion, calculateName, calculateInterval, removeUndefined } from './utils';
|
||||
import { ruleStatusSavedObjectsClientFactory } from '../signals/rule_status_saved_objects_client';
|
||||
import { internalRuleUpdate } from '../schemas/rule_schemas';
|
||||
import { RuleTypeParams } from '../types';
|
||||
|
||||
class PatchError extends Error {
|
||||
public readonly statusCode: number;
|
||||
|
@ -71,7 +72,7 @@ export const patchRules = async ({
|
|||
anomalyThreshold,
|
||||
machineLearningJobId,
|
||||
actions,
|
||||
}: PatchRulesOptions): Promise<PartialAlert | null> => {
|
||||
}: PatchRulesOptions): Promise<PartialAlert<RuleTypeParams> | null> => {
|
||||
if (rule == null) {
|
||||
return null;
|
||||
}
|
||||
|
@ -185,10 +186,13 @@ export const patchRules = async ({
|
|||
throw new PatchError(`Applying patch would create invalid rule: ${errors}`, 400);
|
||||
}
|
||||
|
||||
const update = await alertsClient.update({
|
||||
/**
|
||||
* TODO: Remove this use of `as` by utilizing the proper type
|
||||
*/
|
||||
const update = (await alertsClient.update({
|
||||
id: rule.id,
|
||||
data: validated,
|
||||
});
|
||||
})) as PartialAlert<RuleTypeParams>;
|
||||
|
||||
if (rule.enabled && enabled === false) {
|
||||
await alertsClient.disable({ id: rule.id });
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
import { SanitizedAlert } from '../../../../../alerts/common';
|
||||
import { INTERNAL_RULE_ID_KEY } from '../../../../common/constants';
|
||||
import { RuleTypeParams } from '../types';
|
||||
import { findRules } from './find_rules';
|
||||
import { isAlertType, ReadRuleOptions } from './types';
|
||||
|
||||
|
@ -21,7 +22,7 @@ export const readRules = async ({
|
|||
alertsClient,
|
||||
id,
|
||||
ruleId,
|
||||
}: ReadRuleOptions): Promise<SanitizedAlert | null> => {
|
||||
}: ReadRuleOptions): Promise<SanitizedAlert<RuleTypeParams> | null> => {
|
||||
if (id != null) {
|
||||
try {
|
||||
const rule = await alertsClient.get({ id });
|
||||
|
|
|
@ -103,9 +103,7 @@ import { SIGNALS_ID } from '../../../../common/constants';
|
|||
import { RuleTypeParams, PartialFilter } from '../types';
|
||||
import { ListArrayOrUndefined, ListArray } from '../../../../common/detection_engine/schemas/types';
|
||||
|
||||
export interface RuleAlertType extends Alert {
|
||||
params: RuleTypeParams;
|
||||
}
|
||||
export type RuleAlertType = Alert<RuleTypeParams>;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export interface IRuleStatusSOAttributes extends Record<string, any> {
|
||||
|
@ -173,11 +171,15 @@ export interface Clients {
|
|||
alertsClient: AlertsClient;
|
||||
}
|
||||
|
||||
export const isAlertTypes = (partialAlert: PartialAlert[]): partialAlert is RuleAlertType[] => {
|
||||
export const isAlertTypes = (
|
||||
partialAlert: Array<PartialAlert<RuleTypeParams>>
|
||||
): partialAlert is RuleAlertType[] => {
|
||||
return partialAlert.every((rule) => isAlertType(rule));
|
||||
};
|
||||
|
||||
export const isAlertType = (partialAlert: PartialAlert): partialAlert is RuleAlertType => {
|
||||
export const isAlertType = (
|
||||
partialAlert: PartialAlert<RuleTypeParams>
|
||||
): partialAlert is RuleAlertType => {
|
||||
return partialAlert.alertTypeId === SIGNALS_ID;
|
||||
};
|
||||
|
||||
|
@ -305,7 +307,7 @@ export interface PatchRulesOptions {
|
|||
version: VersionOrUndefined;
|
||||
exceptionsList: ListArrayOrUndefined;
|
||||
actions: RuleAlertAction[] | undefined;
|
||||
rule: SanitizedAlert | null;
|
||||
rule: SanitizedAlert<RuleTypeParams> | null;
|
||||
}
|
||||
|
||||
export interface ReadRuleOptions {
|
||||
|
|
|
@ -15,13 +15,14 @@ import { addTags } from './add_tags';
|
|||
import { ruleStatusSavedObjectsClientFactory } from '../signals/rule_status_saved_objects_client';
|
||||
import { typeSpecificSnakeToCamel } from '../schemas/rule_converters';
|
||||
import { InternalRuleUpdate } from '../schemas/rule_schemas';
|
||||
import { RuleTypeParams } from '../types';
|
||||
|
||||
export const updateRules = async ({
|
||||
alertsClient,
|
||||
savedObjectsClient,
|
||||
defaultOutputIndex,
|
||||
ruleUpdate,
|
||||
}: UpdateRulesOptions): Promise<PartialAlert | null> => {
|
||||
}: UpdateRulesOptions): Promise<PartialAlert<RuleTypeParams> | null> => {
|
||||
const existingRule = await readRules({
|
||||
alertsClient,
|
||||
ruleId: ruleUpdate.rule_id,
|
||||
|
@ -77,10 +78,13 @@ export const updateRules = async ({
|
|||
notifyWhen: null,
|
||||
};
|
||||
|
||||
const update = await alertsClient.update({
|
||||
/**
|
||||
* TODO: Remove this use of `as` by utilizing the proper type
|
||||
*/
|
||||
const update = (await alertsClient.update({
|
||||
id: existingRule.id,
|
||||
data: newInternalRule,
|
||||
});
|
||||
})) as PartialAlert<RuleTypeParams>;
|
||||
|
||||
if (existingRule.enabled && enabled === false) {
|
||||
await alertsClient.disable({ id: existingRule.id });
|
||||
|
|
|
@ -8,7 +8,7 @@ import { schema, TypeOf } from '@kbn/config-schema';
|
|||
|
||||
import { DEFAULT_MAX_SIGNALS } from '../../../../common/constants';
|
||||
|
||||
const signalSchema = schema.object({
|
||||
export const signalSchema = schema.object({
|
||||
anomalyThreshold: schema.maybe(schema.number()),
|
||||
author: schema.arrayOf(schema.string(), { defaultValue: [] }),
|
||||
buildingBlockType: schema.nullable(schema.string()),
|
||||
|
|
|
@ -49,7 +49,10 @@ jest.mock('./find_ml_signals');
|
|||
jest.mock('./bulk_create_ml_signals');
|
||||
jest.mock('../../../../common/detection_engine/parse_schedule_dates');
|
||||
|
||||
const getPayload = (ruleAlert: RuleAlertType, services: AlertServicesMock) => ({
|
||||
const getPayload = (
|
||||
ruleAlert: RuleAlertType,
|
||||
services: AlertServicesMock
|
||||
): RuleExecutorOptions => ({
|
||||
alertId: ruleAlert.id,
|
||||
services,
|
||||
params: {
|
||||
|
|
|
@ -66,6 +66,7 @@ import { getIndexVersion } from '../routes/index/get_index_version';
|
|||
import { MIN_EQL_RULE_INDEX_VERSION } from '../routes/index/get_signals_template';
|
||||
import { filterEventsAgainstList } from './filters/filter_events_against_list';
|
||||
import { isOutdated } from '../migrations/helpers';
|
||||
import { RuleTypeParams } from '../types';
|
||||
|
||||
export const signalRulesAlertType = ({
|
||||
logger,
|
||||
|
@ -86,7 +87,16 @@ export const signalRulesAlertType = ({
|
|||
actionGroups: siemRuleActionGroups,
|
||||
defaultActionGroupId: 'default',
|
||||
validate: {
|
||||
params: signalParamsSchema(),
|
||||
/**
|
||||
* TODO: Fix typing inconsistancy between `RuleTypeParams` and `CreateRulesOptions`
|
||||
* Once that's done, you should be able to do:
|
||||
* ```
|
||||
* params: signalParamsSchema(),
|
||||
* ```
|
||||
*/
|
||||
params: (signalParamsSchema() as unknown) as {
|
||||
validate: (object: unknown) => RuleTypeParams;
|
||||
},
|
||||
},
|
||||
producer: SERVER_APP_ID,
|
||||
minimumLicenseRequired: 'basic',
|
||||
|
|
|
@ -11,6 +11,8 @@ import { RulesSchema } from '../../../../common/detection_engine/schemas/respons
|
|||
import {
|
||||
AlertType,
|
||||
AlertTypeState,
|
||||
AlertInstanceState,
|
||||
AlertInstanceContext,
|
||||
AlertExecutorOptions,
|
||||
AlertServices,
|
||||
} from '../../../../../alerts/server';
|
||||
|
@ -128,19 +130,27 @@ export type BaseSignalHit = BaseHit<SignalSource>;
|
|||
|
||||
export type EqlSignalSearchResponse = EqlSearchResponse<SignalSource>;
|
||||
|
||||
export type RuleExecutorOptions = Omit<AlertExecutorOptions, 'params'> & {
|
||||
params: RuleTypeParams;
|
||||
};
|
||||
export type RuleExecutorOptions = AlertExecutorOptions<
|
||||
RuleTypeParams,
|
||||
AlertTypeState,
|
||||
AlertInstanceState,
|
||||
AlertInstanceContext
|
||||
>;
|
||||
|
||||
// This returns true because by default a RuleAlertTypeDefinition is an AlertType
|
||||
// since we are only increasing the strictness of params.
|
||||
export const isAlertExecutor = (obj: SignalRuleAlertTypeDefinition): obj is AlertType => {
|
||||
export const isAlertExecutor = (
|
||||
obj: SignalRuleAlertTypeDefinition
|
||||
): obj is AlertType<RuleTypeParams, AlertTypeState, AlertInstanceState, AlertInstanceContext> => {
|
||||
return true;
|
||||
};
|
||||
|
||||
export type SignalRuleAlertTypeDefinition = Omit<AlertType, 'executor'> & {
|
||||
executor: ({ services, params, state }: RuleExecutorOptions) => Promise<AlertTypeState | void>;
|
||||
};
|
||||
export type SignalRuleAlertTypeDefinition = AlertType<
|
||||
RuleTypeParams,
|
||||
AlertTypeState,
|
||||
AlertInstanceState,
|
||||
AlertInstanceContext
|
||||
>;
|
||||
|
||||
export interface Ancestor {
|
||||
rule?: string;
|
||||
|
|
|
@ -51,10 +51,11 @@ import {
|
|||
import { LegacyCallAPIOptions } from '../../../../../../src/core/server';
|
||||
import { Filter } from '../../../../../../src/plugins/data/server';
|
||||
import { ListArrayOrUndefined } from '../../../common/detection_engine/schemas/types';
|
||||
import { AlertTypeParams } from '../../../../alerts/common';
|
||||
|
||||
export type PartialFilter = Partial<Filter>;
|
||||
|
||||
export interface RuleTypeParams {
|
||||
export interface RuleTypeParams extends AlertTypeParams {
|
||||
anomalyThreshold: AnomalyThresholdOrUndefined;
|
||||
author: AuthorOrUndefined;
|
||||
buildingBlockType: BuildingBlockTypeOrUndefined;
|
||||
|
|
|
@ -4,9 +4,10 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { AlertTypeParams } from '../../../../alerts/common';
|
||||
import { Query } from '../../../../../../src/plugins/data/common';
|
||||
|
||||
export interface GeoContainmentAlertParams {
|
||||
export interface GeoContainmentAlertParams extends AlertTypeParams {
|
||||
index: string;
|
||||
indexId: string;
|
||||
geoField: string;
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { AlertTypeParams } from '../../../../alerts/common';
|
||||
import { Query } from '../../../../../../src/plugins/data/common';
|
||||
|
||||
export enum TrackingEvent {
|
||||
|
@ -12,7 +13,7 @@ export enum TrackingEvent {
|
|||
crossed = 'crossed',
|
||||
}
|
||||
|
||||
export interface GeoThresholdAlertParams {
|
||||
export interface GeoThresholdAlertParams extends AlertTypeParams {
|
||||
index: string;
|
||||
indexId: string;
|
||||
geoField: string;
|
||||
|
|
|
@ -71,6 +71,10 @@ interface KibanaDeps {
|
|||
http: HttpSetup;
|
||||
}
|
||||
|
||||
function isString(value: unknown): value is string {
|
||||
return typeof value === 'string';
|
||||
}
|
||||
|
||||
export const IndexThresholdAlertTypeExpression: React.FunctionComponent<
|
||||
AlertTypeParamsExpressionProps<IndexThresholdAlertParams>
|
||||
> = ({ alertParams, alertInterval, setAlertParams, setAlertProperty, errors, charts, data }) => {
|
||||
|
@ -190,10 +194,10 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent<
|
|||
};
|
||||
})}
|
||||
onChange={async (selected: EuiComboBoxOptionOption[]) => {
|
||||
setAlertParams(
|
||||
'index',
|
||||
selected.map((aSelected) => aSelected.value)
|
||||
);
|
||||
const indicies: string[] = selected
|
||||
.map((aSelected) => aSelected.value)
|
||||
.filter<string>(isString);
|
||||
setAlertParams('index', indicies);
|
||||
const indices = selected.map((s) => s.value as string);
|
||||
|
||||
// reset time field and expression fields if indices are deleted
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { AlertTypeParams } from '../../../../alerts/common';
|
||||
|
||||
export interface Comparator {
|
||||
text: string;
|
||||
value: string;
|
||||
|
@ -24,7 +26,7 @@ export interface GroupByType {
|
|||
validNormalizedTypes: string[];
|
||||
}
|
||||
|
||||
export interface IndexThresholdAlertParams {
|
||||
export interface IndexThresholdAlertParams extends AlertTypeParams {
|
||||
index: string[];
|
||||
timeField?: string;
|
||||
aggType: string;
|
||||
|
|
|
@ -9,7 +9,13 @@ import { schema } from '@kbn/config-schema';
|
|||
import { Logger } from 'src/core/server';
|
||||
import { STACK_ALERTS_FEATURE_ID } from '../../../common';
|
||||
import { getGeoContainmentExecutor } from './geo_containment';
|
||||
import { AlertType } from '../../../../alerts/server';
|
||||
import {
|
||||
AlertType,
|
||||
AlertTypeState,
|
||||
AlertInstanceState,
|
||||
AlertInstanceContext,
|
||||
AlertTypeParams,
|
||||
} from '../../../../alerts/server';
|
||||
import { Query } from '../../../../../../src/plugins/data/common/query';
|
||||
|
||||
export const GEO_CONTAINMENT_ID = '.geo-containment';
|
||||
|
@ -96,7 +102,7 @@ export const ParamsSchema = schema.object({
|
|||
boundaryIndexQuery: schema.maybe(schema.any({})),
|
||||
});
|
||||
|
||||
export interface GeoContainmentParams {
|
||||
export interface GeoContainmentParams extends AlertTypeParams {
|
||||
index: string;
|
||||
indexId: string;
|
||||
geoField: string;
|
||||
|
@ -111,8 +117,34 @@ export interface GeoContainmentParams {
|
|||
indexQuery?: Query;
|
||||
boundaryIndexQuery?: Query;
|
||||
}
|
||||
export interface GeoContainmentState extends AlertTypeState {
|
||||
shapesFilters: Record<string, unknown>;
|
||||
shapesIdsNamesMap: Record<string, unknown>;
|
||||
}
|
||||
export interface GeoContainmentInstanceState extends AlertInstanceState {
|
||||
location: number[];
|
||||
shapeLocationId: string;
|
||||
dateInShape: string | null;
|
||||
docId: string;
|
||||
}
|
||||
export interface GeoContainmentInstanceContext extends AlertInstanceContext {
|
||||
entityId: string;
|
||||
entityDateTime: string | null;
|
||||
entityDocumentId: string;
|
||||
detectionDateTime: string;
|
||||
entityLocation: string;
|
||||
containingBoundaryId: string;
|
||||
containingBoundaryName: unknown;
|
||||
}
|
||||
|
||||
export function getAlertType(logger: Logger): AlertType<GeoContainmentParams> {
|
||||
export type GeoContainmentAlertType = AlertType<
|
||||
GeoContainmentParams,
|
||||
GeoContainmentState,
|
||||
GeoContainmentInstanceState,
|
||||
GeoContainmentInstanceContext
|
||||
>;
|
||||
|
||||
export function getAlertType(logger: Logger): GeoContainmentAlertType {
|
||||
const alertTypeName = i18n.translate('xpack.stackAlerts.geoContainment.alertTypeTitle', {
|
||||
defaultMessage: 'Tracking containment',
|
||||
});
|
||||
|
|
|
@ -8,15 +8,16 @@ import _ from 'lodash';
|
|||
import { SearchResponse } from 'elasticsearch';
|
||||
import { Logger } from 'src/core/server';
|
||||
import { executeEsQueryFactory, getShapesFilters, OTHER_CATEGORY } from './es_query_builder';
|
||||
import { AlertServices, AlertTypeState } from '../../../../alerts/server';
|
||||
import { ActionGroupId, GEO_CONTAINMENT_ID, GeoContainmentParams } from './alert_type';
|
||||
import { AlertServices } from '../../../../alerts/server';
|
||||
import {
|
||||
ActionGroupId,
|
||||
GEO_CONTAINMENT_ID,
|
||||
GeoContainmentInstanceState,
|
||||
GeoContainmentAlertType,
|
||||
GeoContainmentInstanceContext,
|
||||
} from './alert_type';
|
||||
|
||||
export interface LatestEntityLocation {
|
||||
location: number[];
|
||||
shapeLocationId: string;
|
||||
dateInShape: string | null;
|
||||
docId: string;
|
||||
}
|
||||
export type LatestEntityLocation = GeoContainmentInstanceState;
|
||||
|
||||
// Flatten agg results and get latest locations for each entity
|
||||
export function transformResults(
|
||||
|
@ -97,9 +98,10 @@ function getOffsetTime(delayOffsetWithUnits: string, oldTime: Date): Date {
|
|||
export function getActiveEntriesAndGenerateAlerts(
|
||||
prevLocationMap: Record<string, LatestEntityLocation>,
|
||||
currLocationMap: Map<string, LatestEntityLocation>,
|
||||
alertInstanceFactory: (
|
||||
x: string
|
||||
) => { scheduleActions: (x: string, y: Record<string, unknown>) => void },
|
||||
alertInstanceFactory: AlertServices<
|
||||
GeoContainmentInstanceState,
|
||||
GeoContainmentInstanceContext
|
||||
>['alertInstanceFactory'],
|
||||
shapesIdsNamesMap: Record<string, unknown>,
|
||||
currIntervalEndTime: Date
|
||||
) {
|
||||
|
@ -127,23 +129,8 @@ export function getActiveEntriesAndGenerateAlerts(
|
|||
});
|
||||
return allActiveEntriesMap;
|
||||
}
|
||||
|
||||
export const getGeoContainmentExecutor = (log: Logger) =>
|
||||
async function ({
|
||||
previousStartedAt,
|
||||
startedAt,
|
||||
services,
|
||||
params,
|
||||
alertId,
|
||||
state,
|
||||
}: {
|
||||
previousStartedAt: Date | null;
|
||||
startedAt: Date;
|
||||
services: AlertServices;
|
||||
params: GeoContainmentParams;
|
||||
alertId: string;
|
||||
state: AlertTypeState;
|
||||
}): Promise<AlertTypeState> {
|
||||
export const getGeoContainmentExecutor = (log: Logger): GeoContainmentAlertType['executor'] =>
|
||||
async function ({ previousStartedAt, startedAt, services, params, alertId, state }) {
|
||||
const { shapesFilters, shapesIdsNamesMap } = state.shapesFilters
|
||||
? state
|
||||
: await getShapesFilters(
|
||||
|
|
|
@ -6,7 +6,13 @@
|
|||
|
||||
import { Logger } from 'src/core/server';
|
||||
import { AlertingSetup } from '../../types';
|
||||
import { GeoContainmentParams, getAlertType } from './alert_type';
|
||||
import {
|
||||
GeoContainmentParams,
|
||||
GeoContainmentState,
|
||||
GeoContainmentInstanceState,
|
||||
GeoContainmentInstanceContext,
|
||||
getAlertType,
|
||||
} from './alert_type';
|
||||
|
||||
interface RegisterParams {
|
||||
logger: Logger;
|
||||
|
@ -15,5 +21,10 @@ interface RegisterParams {
|
|||
|
||||
export function register(params: RegisterParams) {
|
||||
const { logger, alerts } = params;
|
||||
alerts.registerType<GeoContainmentParams>(getAlertType(logger));
|
||||
alerts.registerType<
|
||||
GeoContainmentParams,
|
||||
GeoContainmentState,
|
||||
GeoContainmentInstanceState,
|
||||
GeoContainmentInstanceContext
|
||||
>(getAlertType(logger));
|
||||
}
|
||||
|
|
|
@ -10,6 +10,8 @@ import sampleJsonResponseWithNesting from './es_sample_response_with_nesting.jso
|
|||
import { getActiveEntriesAndGenerateAlerts, transformResults } from '../geo_containment';
|
||||
import { SearchResponse } from 'elasticsearch';
|
||||
import { OTHER_CATEGORY } from '../es_query_builder';
|
||||
import { alertsMock } from '../../../../../alerts/server/mocks';
|
||||
import { GeoContainmentInstanceContext, GeoContainmentInstanceState } from '../alert_type';
|
||||
|
||||
describe('geo_containment', () => {
|
||||
describe('transformResults', () => {
|
||||
|
@ -191,8 +193,12 @@ describe('geo_containment', () => {
|
|||
const emptyShapesIdsNamesMap = {};
|
||||
|
||||
const alertInstanceFactory = (instanceId: string) => {
|
||||
return {
|
||||
scheduleActions: (actionGroupId: string, context: Record<string, unknown>) => {
|
||||
const alertInstance = alertsMock.createAlertInstanceFactory<
|
||||
GeoContainmentInstanceState,
|
||||
GeoContainmentInstanceContext
|
||||
>();
|
||||
alertInstance.scheduleActions.mockImplementation(
|
||||
(actionGroupId: string, context?: GeoContainmentInstanceContext) => {
|
||||
const contextKeys = Object.keys(expectedContext[0].context);
|
||||
const contextSubset = _.pickBy(context, (v, k) => contextKeys.includes(k));
|
||||
testAlertActionArr.push({
|
||||
|
@ -200,9 +206,12 @@ describe('geo_containment', () => {
|
|||
instanceId,
|
||||
context: contextSubset,
|
||||
});
|
||||
},
|
||||
};
|
||||
return alertInstance;
|
||||
}
|
||||
);
|
||||
return alertInstance;
|
||||
};
|
||||
|
||||
const currentDateTime = new Date();
|
||||
|
||||
it('should use currently active entities if no older entity entries', () => {
|
||||
|
|
|
@ -9,7 +9,13 @@ import { schema } from '@kbn/config-schema';
|
|||
import { Logger } from 'src/core/server';
|
||||
import { STACK_ALERTS_FEATURE_ID } from '../../../common';
|
||||
import { getGeoThresholdExecutor } from './geo_threshold';
|
||||
import { AlertType } from '../../../../alerts/server';
|
||||
import {
|
||||
AlertType,
|
||||
AlertTypeState,
|
||||
AlertInstanceState,
|
||||
AlertInstanceContext,
|
||||
AlertTypeParams,
|
||||
} from '../../../../alerts/server';
|
||||
import { Query } from '../../../../../../src/plugins/data/common/query';
|
||||
|
||||
export const GEO_THRESHOLD_ID = '.geo-threshold';
|
||||
|
@ -155,7 +161,7 @@ export const ParamsSchema = schema.object({
|
|||
boundaryIndexQuery: schema.maybe(schema.any({})),
|
||||
});
|
||||
|
||||
export interface GeoThresholdParams {
|
||||
export interface GeoThresholdParams extends AlertTypeParams {
|
||||
index: string;
|
||||
indexId: string;
|
||||
geoField: string;
|
||||
|
@ -171,8 +177,41 @@ export interface GeoThresholdParams {
|
|||
indexQuery?: Query;
|
||||
boundaryIndexQuery?: Query;
|
||||
}
|
||||
export interface GeoThresholdState extends AlertTypeState {
|
||||
shapesFilters: Record<string, unknown>;
|
||||
shapesIdsNamesMap: Record<string, unknown>;
|
||||
prevLocationArr: GeoThresholdInstanceState[];
|
||||
}
|
||||
export interface GeoThresholdInstanceState extends AlertInstanceState {
|
||||
location: number[];
|
||||
shapeLocationId: string;
|
||||
entityName: string;
|
||||
dateInShape: string | null;
|
||||
docId: string;
|
||||
}
|
||||
export interface GeoThresholdInstanceContext extends AlertInstanceContext {
|
||||
entityId: string;
|
||||
timeOfDetection: number;
|
||||
crossingLine: string;
|
||||
toEntityLocation: string;
|
||||
toEntityDateTime: string | null;
|
||||
toEntityDocumentId: string;
|
||||
toBoundaryId: string;
|
||||
toBoundaryName: unknown;
|
||||
fromEntityLocation: string;
|
||||
fromEntityDateTime: string | null;
|
||||
fromEntityDocumentId: string;
|
||||
fromBoundaryId: string;
|
||||
fromBoundaryName: unknown;
|
||||
}
|
||||
|
||||
export function getAlertType(logger: Logger): AlertType<GeoThresholdParams> {
|
||||
export type GeoThresholdAlertType = AlertType<
|
||||
GeoThresholdParams,
|
||||
GeoThresholdState,
|
||||
GeoThresholdInstanceState,
|
||||
GeoThresholdInstanceContext
|
||||
>;
|
||||
export function getAlertType(logger: Logger): GeoThresholdAlertType {
|
||||
const alertTypeName = i18n.translate('xpack.stackAlerts.geoThreshold.alertTypeTitle', {
|
||||
defaultMessage: 'Tracking threshold',
|
||||
});
|
||||
|
|
|
@ -8,16 +8,14 @@ import _ from 'lodash';
|
|||
import { SearchResponse } from 'elasticsearch';
|
||||
import { Logger } from 'src/core/server';
|
||||
import { executeEsQueryFactory, getShapesFilters, OTHER_CATEGORY } from './es_query_builder';
|
||||
import { AlertServices, AlertTypeState } from '../../../../alerts/server';
|
||||
import { ActionGroupId, GEO_THRESHOLD_ID, GeoThresholdParams } from './alert_type';
|
||||
import {
|
||||
ActionGroupId,
|
||||
GEO_THRESHOLD_ID,
|
||||
GeoThresholdAlertType,
|
||||
GeoThresholdInstanceState,
|
||||
} from './alert_type';
|
||||
|
||||
interface LatestEntityLocation {
|
||||
location: number[];
|
||||
shapeLocationId: string;
|
||||
entityName: string;
|
||||
dateInShape: string | null;
|
||||
docId: string;
|
||||
}
|
||||
export type LatestEntityLocation = GeoThresholdInstanceState;
|
||||
|
||||
// Flatten agg results and get latest locations for each entity
|
||||
export function transformResults(
|
||||
|
@ -172,22 +170,8 @@ function getOffsetTime(delayOffsetWithUnits: string, oldTime: Date): Date {
|
|||
return adjustedDate;
|
||||
}
|
||||
|
||||
export const getGeoThresholdExecutor = (log: Logger) =>
|
||||
async function ({
|
||||
previousStartedAt,
|
||||
startedAt,
|
||||
services,
|
||||
params,
|
||||
alertId,
|
||||
state,
|
||||
}: {
|
||||
previousStartedAt: Date | null;
|
||||
startedAt: Date;
|
||||
services: AlertServices;
|
||||
params: GeoThresholdParams;
|
||||
alertId: string;
|
||||
state: AlertTypeState;
|
||||
}): Promise<AlertTypeState> {
|
||||
export const getGeoThresholdExecutor = (log: Logger): GeoThresholdAlertType['executor'] =>
|
||||
async function ({ previousStartedAt, startedAt, services, params, alertId, state }) {
|
||||
const { shapesFilters, shapesIdsNamesMap } = state.shapesFilters
|
||||
? state
|
||||
: await getShapesFilters(
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { Alert, AlertType } from '../../types';
|
||||
import { Alert, AlertType, AlertUpdates } from '../../types';
|
||||
import { httpServiceMock } from '../../../../../../src/core/public/mocks';
|
||||
import {
|
||||
createAlert,
|
||||
|
@ -538,7 +538,7 @@ describe('deleteAlerts', () => {
|
|||
|
||||
describe('createAlert', () => {
|
||||
test('should call create alert API', async () => {
|
||||
const alertToCreate = {
|
||||
const alertToCreate: AlertUpdates = {
|
||||
name: 'test',
|
||||
consumer: 'alerts',
|
||||
tags: ['foo'],
|
||||
|
@ -553,10 +553,13 @@ describe('createAlert', () => {
|
|||
notifyWhen: 'onActionGroupChange' as AlertNotifyWhenType,
|
||||
createdAt: new Date('1970-01-01T00:00:00.000Z'),
|
||||
updatedAt: new Date('1970-01-01T00:00:00.000Z'),
|
||||
apiKey: null,
|
||||
apiKeyOwner: null,
|
||||
createdBy: null,
|
||||
updatedBy: null,
|
||||
muteAll: false,
|
||||
mutedInstanceIds: [],
|
||||
};
|
||||
const resolvedValue: Alert = {
|
||||
const resolvedValue = {
|
||||
...alertToCreate,
|
||||
id: '123',
|
||||
createdBy: null,
|
||||
|
@ -576,7 +579,7 @@ describe('createAlert', () => {
|
|||
Array [
|
||||
"/api/alerts/alert",
|
||||
Object {
|
||||
"body": "{\\"name\\":\\"test\\",\\"consumer\\":\\"alerts\\",\\"tags\\":[\\"foo\\"],\\"enabled\\":true,\\"alertTypeId\\":\\"test\\",\\"schedule\\":{\\"interval\\":\\"1m\\"},\\"actions\\":[],\\"params\\":{},\\"throttle\\":null,\\"notifyWhen\\":\\"onActionGroupChange\\",\\"createdAt\\":\\"1970-01-01T00:00:00.000Z\\",\\"updatedAt\\":\\"1970-01-01T00:00:00.000Z\\",\\"apiKey\\":null,\\"apiKeyOwner\\":null}",
|
||||
"body": "{\\"name\\":\\"test\\",\\"consumer\\":\\"alerts\\",\\"tags\\":[\\"foo\\"],\\"enabled\\":true,\\"alertTypeId\\":\\"test\\",\\"schedule\\":{\\"interval\\":\\"1m\\"},\\"actions\\":[],\\"params\\":{},\\"throttle\\":null,\\"notifyWhen\\":\\"onActionGroupChange\\",\\"createdAt\\":\\"1970-01-01T00:00:00.000Z\\",\\"updatedAt\\":\\"1970-01-01T00:00:00.000Z\\",\\"apiKeyOwner\\":null,\\"createdBy\\":null,\\"updatedBy\\":null,\\"muteAll\\":false,\\"mutedInstanceIds\\":[]}",
|
||||
},
|
||||
]
|
||||
`);
|
||||
|
|
|
@ -35,12 +35,12 @@ import { AlertEditProps } from './application/sections/alert_form/alert_edit';
|
|||
|
||||
export interface TriggersAndActionsUIPublicPluginSetup {
|
||||
actionTypeRegistry: TypeRegistry<ActionTypeModel>;
|
||||
alertTypeRegistry: TypeRegistry<AlertTypeModel>;
|
||||
alertTypeRegistry: TypeRegistry<AlertTypeModel<any>>;
|
||||
}
|
||||
|
||||
export interface TriggersAndActionsUIPublicPluginStart {
|
||||
actionTypeRegistry: TypeRegistry<ActionTypeModel>;
|
||||
alertTypeRegistry: TypeRegistry<AlertTypeModel>;
|
||||
alertTypeRegistry: TypeRegistry<AlertTypeModel<any>>;
|
||||
getAddConnectorFlyout: (
|
||||
props: Omit<ConnectorAddFlyoutProps, 'actionTypeRegistry'>
|
||||
) => ReactElement<ConnectorAddFlyoutProps>;
|
||||
|
|
|
@ -8,12 +8,12 @@ import type { DocLinksStart } from 'kibana/public';
|
|||
import { ComponentType } from 'react';
|
||||
import { ChartsPluginSetup } from 'src/plugins/charts/public';
|
||||
import { DataPublicPluginStart } from 'src/plugins/data/public';
|
||||
import { ActionGroup, AlertActionParam } from '../../alerts/common';
|
||||
import { ActionGroup, AlertActionParam, AlertTypeParams } from '../../alerts/common';
|
||||
import { ActionType } from '../../actions/common';
|
||||
import { TypeRegistry } from './application/type_registry';
|
||||
import { AlertType as CommonAlertType } from '../../alerts/common';
|
||||
import {
|
||||
SanitizedAlert as Alert,
|
||||
SanitizedAlert,
|
||||
AlertAction,
|
||||
AlertAggregations,
|
||||
AlertTaskState,
|
||||
|
@ -23,6 +23,11 @@ import {
|
|||
AlertingFrameworkHealth,
|
||||
AlertNotifyWhenType,
|
||||
} from '../../alerts/common';
|
||||
|
||||
// In Triggers and Actions we treat all `Alert`s as `SanitizedAlert<AlertTypeParams>`
|
||||
// so the `Params` is a black-box of Record<string, unknown>
|
||||
type Alert = SanitizedAlert<AlertTypeParams>;
|
||||
|
||||
export {
|
||||
Alert,
|
||||
AlertAction,
|
||||
|
@ -169,14 +174,17 @@ export interface AlertTableItem extends Alert {
|
|||
}
|
||||
|
||||
export interface AlertTypeParamsExpressionProps<
|
||||
AlertParamsType = unknown,
|
||||
Params extends AlertTypeParams = AlertTypeParams,
|
||||
MetaData = Record<string, any>
|
||||
> {
|
||||
alertParams: AlertParamsType;
|
||||
alertParams: Params;
|
||||
alertInterval: string;
|
||||
alertThrottle: string;
|
||||
setAlertParams: (property: string, value: any) => void;
|
||||
setAlertProperty: <Key extends keyof Alert>(key: Key, value: Alert[Key] | null) => void;
|
||||
setAlertParams: <Key extends keyof Params>(property: Key, value: Params[Key] | undefined) => void;
|
||||
setAlertProperty: <Prop extends keyof Alert>(
|
||||
key: Prop,
|
||||
value: SanitizedAlert<Params>[Prop] | null
|
||||
) => void;
|
||||
errors: IErrorObject;
|
||||
defaultActionGroupId: string;
|
||||
actionGroups: ActionGroup[];
|
||||
|
@ -185,15 +193,15 @@ export interface AlertTypeParamsExpressionProps<
|
|||
data: DataPublicPluginStart;
|
||||
}
|
||||
|
||||
export interface AlertTypeModel<AlertParamsType = any> {
|
||||
export interface AlertTypeModel<Params extends AlertTypeParams = AlertTypeParams> {
|
||||
id: string;
|
||||
description: string;
|
||||
iconClass: string;
|
||||
documentationUrl: string | ((docLinks: DocLinksStart) => string) | null;
|
||||
validate: (alertParams: AlertParamsType) => ValidationResult;
|
||||
validate: (alertParams: Params) => ValidationResult;
|
||||
alertParamsExpression:
|
||||
| React.FunctionComponent<any>
|
||||
| React.LazyExoticComponent<ComponentType<AlertTypeParamsExpressionProps<AlertParamsType>>>;
|
||||
| React.LazyExoticComponent<ComponentType<AlertTypeParamsExpressionProps<Params>>>;
|
||||
requiresAppContext: boolean;
|
||||
defaultActionMessage?: string;
|
||||
}
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
|
||||
import { Action } from 'redux-actions';
|
||||
import { IHttpFetchError } from 'src/core/public';
|
||||
import { Alert } from '../../../../triggers_actions_ui/public';
|
||||
import { Alert } from '../../../../alerts/common';
|
||||
import { UptimeAlertTypeParams } from '../alerts/alerts';
|
||||
|
||||
export interface AsyncAction<Payload, SuccessPayload> {
|
||||
get: (payload: Payload) => Action<Payload>;
|
||||
|
@ -59,5 +60,5 @@ export interface AlertsResult {
|
|||
page: number;
|
||||
perPage: number;
|
||||
total: number;
|
||||
data: Alert[];
|
||||
data: Array<Alert<UptimeAlertTypeParams>>;
|
||||
}
|
||||
|
|
|
@ -20,10 +20,8 @@ import {
|
|||
fetchMonitorAlertRecords,
|
||||
NewAlertParams,
|
||||
} from '../api/alerts';
|
||||
import {
|
||||
ActionConnector as RawActionConnector,
|
||||
Alert,
|
||||
} from '../../../../triggers_actions_ui/public';
|
||||
import { ActionConnector as RawActionConnector } from '../../../../triggers_actions_ui/public';
|
||||
import { Alert } from '../../../../alerts/common';
|
||||
import { kibanaService } from '../kibana_service';
|
||||
import { monitorIdSelector } from '../selectors';
|
||||
import { AlertsResult, MonitorIdParam } from '../actions/types';
|
||||
|
@ -31,13 +29,22 @@ import { simpleAlertEnabled } from '../../lib/alert_types/alert_messages';
|
|||
|
||||
export type ActionConnector = Omit<RawActionConnector, 'secrets'>;
|
||||
|
||||
export const createAlertAction = createAsyncAction<NewAlertParams, Alert | null>('CREATE ALERT');
|
||||
/**
|
||||
* TODO: Use actual AlertType Params type that's specific to Uptime instead of `any`
|
||||
*/
|
||||
export type UptimeAlertTypeParams = Record<string, any>;
|
||||
|
||||
export const createAlertAction = createAsyncAction<
|
||||
NewAlertParams,
|
||||
Alert<UptimeAlertTypeParams> | null
|
||||
>('CREATE ALERT');
|
||||
export const getConnectorsAction = createAsyncAction<{}, ActionConnector[]>('GET CONNECTORS');
|
||||
export const getMonitorAlertsAction = createAsyncAction<{}, AlertsResult | null>('GET ALERTS');
|
||||
|
||||
export const getAnomalyAlertAction = createAsyncAction<MonitorIdParam, Alert>(
|
||||
'GET EXISTING ALERTS'
|
||||
);
|
||||
export const getAnomalyAlertAction = createAsyncAction<
|
||||
MonitorIdParam,
|
||||
Alert<UptimeAlertTypeParams>
|
||||
>('GET EXISTING ALERTS');
|
||||
export const deleteAlertAction = createAsyncAction<{ alertId: string }, string | null>(
|
||||
'DELETE ALERTS'
|
||||
);
|
||||
|
@ -47,9 +54,9 @@ export const deleteAnomalyAlertAction = createAsyncAction<{ alertId: string }, a
|
|||
|
||||
interface AlertState {
|
||||
connectors: AsyncInitState<ActionConnector[]>;
|
||||
newAlert: AsyncInitState<Alert>;
|
||||
newAlert: AsyncInitState<Alert<UptimeAlertTypeParams>>;
|
||||
alerts: AsyncInitState<AlertsResult>;
|
||||
anomalyAlert: AsyncInitState<Alert>;
|
||||
anomalyAlert: AsyncInitState<Alert<UptimeAlertTypeParams>>;
|
||||
alertDeletion: AsyncInitState<string>;
|
||||
anomalyAlertDeletion: AsyncInitState<boolean>;
|
||||
}
|
||||
|
|
|
@ -9,9 +9,10 @@ import { apiService } from './utils';
|
|||
import { ActionConnector } from '../alerts/alerts';
|
||||
|
||||
import { AlertsResult, MonitorIdParam } from '../actions/types';
|
||||
import { Alert, AlertAction } from '../../../../triggers_actions_ui/public';
|
||||
import { AlertAction } from '../../../../triggers_actions_ui/public';
|
||||
import { API_URLS } from '../../../common/constants';
|
||||
import { MonitorStatusTranslations } from '../../../common/translations';
|
||||
import { Alert, AlertTypeParams } from '../../../../alerts/common';
|
||||
|
||||
const { MONITOR_STATUS } = ACTION_GROUP_DEFINITIONS;
|
||||
|
||||
|
@ -21,7 +22,7 @@ export const fetchConnectors = async () => {
|
|||
return await apiService.get(API_URLS.ALERT_ACTIONS);
|
||||
};
|
||||
|
||||
export interface NewAlertParams {
|
||||
export interface NewAlertParams extends AlertTypeParams {
|
||||
monitorId: string;
|
||||
monitorName?: string;
|
||||
defaultActions: ActionConnector[];
|
||||
|
@ -80,7 +81,9 @@ export const fetchMonitorAlertRecords = async (): Promise<AlertsResult> => {
|
|||
return await apiService.get(API_URLS.ALERTS_FIND, data);
|
||||
};
|
||||
|
||||
export const fetchAlertRecords = async ({ monitorId }: MonitorIdParam): Promise<Alert> => {
|
||||
export const fetchAlertRecords = async ({
|
||||
monitorId,
|
||||
}: MonitorIdParam): Promise<Alert<NewAlertParams>> => {
|
||||
const data = {
|
||||
page: 1,
|
||||
per_page: 500,
|
||||
|
@ -90,7 +93,7 @@ export const fetchAlertRecords = async ({ monitorId }: MonitorIdParam): Promise<
|
|||
sort_order: 'asc',
|
||||
};
|
||||
const alerts = await apiService.get(API_URLS.ALERTS_FIND, data);
|
||||
return alerts.data.find((alert: Alert) => alert.params.monitorId === monitorId);
|
||||
return alerts.data.find((alert: Alert<NewAlertParams>) => alert.params.monitorId === monitorId);
|
||||
};
|
||||
|
||||
export const disableAlertById = async ({ alertId }: { alertId: string }) => {
|
||||
|
|
|
@ -11,7 +11,13 @@ import {
|
|||
getStatusMessage,
|
||||
getUniqueIdsByLoc,
|
||||
} from '../status_check';
|
||||
import { AlertType } from '../../../../../alerts/server';
|
||||
import {
|
||||
AlertType,
|
||||
AlertTypeParams,
|
||||
AlertTypeState,
|
||||
AlertInstanceState,
|
||||
AlertInstanceContext,
|
||||
} from '../../../../../alerts/server';
|
||||
import { IRouter } from 'kibana/server';
|
||||
import { UMServerLibs } from '../../lib';
|
||||
import { UptimeCorePlugins, UptimeCoreSetup } from '../../adapters';
|
||||
|
@ -868,7 +874,7 @@ describe('status check alert', () => {
|
|||
});
|
||||
|
||||
describe('alert factory', () => {
|
||||
let alert: AlertType;
|
||||
let alert: AlertType<AlertTypeParams, AlertTypeState, AlertInstanceState, AlertInstanceContext>;
|
||||
|
||||
beforeEach(() => {
|
||||
const { server, libs, plugins } = bootstrapDependencies();
|
||||
|
|
|
@ -6,10 +6,17 @@
|
|||
|
||||
import { UptimeCorePlugins, UptimeCoreSetup } from '../adapters';
|
||||
import { UMServerLibs } from '../lib';
|
||||
import { AlertType } from '../../../../alerts/server';
|
||||
import { AlertType, AlertInstanceState, AlertInstanceContext } from '../../../../alerts/server';
|
||||
|
||||
export type UptimeAlertTypeParam = Record<string, any>;
|
||||
export type UptimeAlertTypeState = Record<string, any>;
|
||||
export type UptimeAlertTypeFactory = (
|
||||
server: UptimeCoreSetup,
|
||||
libs: UMServerLibs,
|
||||
plugins: UptimeCorePlugins
|
||||
) => AlertType;
|
||||
) => AlertType<
|
||||
UptimeAlertTypeParam,
|
||||
UptimeAlertTypeState,
|
||||
AlertInstanceState,
|
||||
AlertInstanceContext
|
||||
>;
|
||||
|
|
|
@ -5,28 +5,46 @@
|
|||
*/
|
||||
|
||||
import { SavedObjectsClientContract } from 'kibana/server';
|
||||
import { AlertExecutorOptions, AlertType, AlertTypeState } from '../../../../alerts/server';
|
||||
import {
|
||||
AlertExecutorOptions,
|
||||
AlertInstanceState,
|
||||
AlertInstanceContext,
|
||||
} from '../../../../alerts/server';
|
||||
import { savedObjectsAdapter } from '../saved_objects';
|
||||
import { DynamicSettings } from '../../../common/runtime_types';
|
||||
import { createUptimeESClient, UptimeESClient } from '../lib';
|
||||
import { UptimeAlertTypeFactory, UptimeAlertTypeParam, UptimeAlertTypeState } from './types';
|
||||
|
||||
export interface UptimeAlertType extends Omit<AlertType, 'executor' | 'producer'> {
|
||||
export interface UptimeAlertType
|
||||
extends Omit<ReturnType<UptimeAlertTypeFactory>, 'executor' | 'producer'> {
|
||||
executor: ({
|
||||
options,
|
||||
uptimeEsClient,
|
||||
dynamicSettings,
|
||||
}: {
|
||||
options: AlertExecutorOptions;
|
||||
options: AlertExecutorOptions<
|
||||
UptimeAlertTypeParam,
|
||||
UptimeAlertTypeState,
|
||||
AlertInstanceState,
|
||||
AlertInstanceContext
|
||||
>;
|
||||
uptimeEsClient: UptimeESClient;
|
||||
dynamicSettings: DynamicSettings;
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
}) => Promise<AlertTypeState | void>;
|
||||
}) => Promise<UptimeAlertTypeState | void>;
|
||||
}
|
||||
|
||||
export const uptimeAlertWrapper = (uptimeAlert: UptimeAlertType) => ({
|
||||
...uptimeAlert,
|
||||
producer: 'uptime',
|
||||
executor: async (options: AlertExecutorOptions) => {
|
||||
executor: async (
|
||||
options: AlertExecutorOptions<
|
||||
UptimeAlertTypeParam,
|
||||
UptimeAlertTypeState,
|
||||
AlertInstanceState,
|
||||
AlertInstanceContext
|
||||
>
|
||||
) => {
|
||||
const {
|
||||
services: { scopedClusterClient: esClient, savedObjectsClient },
|
||||
} = options;
|
||||
|
|
|
@ -13,6 +13,8 @@ import {
|
|||
AlertType,
|
||||
AlertInstanceState,
|
||||
AlertInstanceContext,
|
||||
AlertTypeState,
|
||||
AlertTypeParams,
|
||||
} from '../../../../../../../plugins/alerts/server';
|
||||
|
||||
export const EscapableStrings = {
|
||||
|
@ -50,7 +52,7 @@ function getAlwaysFiringAlertType() {
|
|||
groupsToScheduleActionsInSeries: schema.maybe(schema.arrayOf(schema.nullable(schema.string()))),
|
||||
});
|
||||
type ParamsType = TypeOf<typeof paramsSchema>;
|
||||
interface State {
|
||||
interface State extends AlertTypeState {
|
||||
groupInSeriesIndex?: number;
|
||||
}
|
||||
interface InstanceState extends AlertInstanceState {
|
||||
|
@ -59,7 +61,7 @@ function getAlwaysFiringAlertType() {
|
|||
interface InstanceContext extends AlertInstanceContext {
|
||||
instanceContextValue: boolean;
|
||||
}
|
||||
const result: AlertType<ParamsType, State, InstanceState, InstanceContext> = {
|
||||
const result: AlertType<ParamsType & AlertTypeParams, State, InstanceState, InstanceContext> = {
|
||||
id: 'test.always-firing',
|
||||
name: 'Test: Always Firing',
|
||||
actionGroups: [
|
||||
|
@ -141,7 +143,7 @@ async function alwaysFiringExecutor(alertExecutorOptions: any) {
|
|||
}
|
||||
|
||||
function getCumulativeFiringAlertType() {
|
||||
interface State {
|
||||
interface State extends AlertTypeState {
|
||||
runCount?: number;
|
||||
}
|
||||
interface InstanceState extends AlertInstanceState {
|
||||
|
@ -175,7 +177,7 @@ function getCumulativeFiringAlertType() {
|
|||
};
|
||||
},
|
||||
};
|
||||
return result as AlertType;
|
||||
return result;
|
||||
}
|
||||
|
||||
function getNeverFiringAlertType() {
|
||||
|
@ -184,7 +186,7 @@ function getNeverFiringAlertType() {
|
|||
reference: schema.string(),
|
||||
});
|
||||
type ParamsType = TypeOf<typeof paramsSchema>;
|
||||
interface State {
|
||||
interface State extends AlertTypeState {
|
||||
globalStateValue: boolean;
|
||||
}
|
||||
const result: AlertType<ParamsType, State, {}, {}> = {
|
||||
|
@ -385,7 +387,7 @@ function getPatternFiringAlertType() {
|
|||
reference: schema.maybe(schema.string()),
|
||||
});
|
||||
type ParamsType = TypeOf<typeof paramsSchema>;
|
||||
interface State {
|
||||
interface State extends AlertTypeState {
|
||||
patternIndex?: number;
|
||||
}
|
||||
const result: AlertType<ParamsType, State, {}, {}> = {
|
||||
|
|
|
@ -27,7 +27,14 @@ export const noopAlertType: AlertType = {
|
|||
producer: 'alerts',
|
||||
};
|
||||
|
||||
export const alwaysFiringAlertType: AlertType = {
|
||||
export const alwaysFiringAlertType: AlertType<
|
||||
{ instances: Array<{ id: string; state: any }> },
|
||||
{
|
||||
globalStateValue: boolean;
|
||||
groupInSeriesIndex: number;
|
||||
},
|
||||
{ instanceStateValue: boolean; globalStateValue: boolean; groupInSeriesIndex: number }
|
||||
> = {
|
||||
id: 'test.always-firing',
|
||||
name: 'Always Firing',
|
||||
actionGroups: [
|
||||
|
@ -37,7 +44,7 @@ export const alwaysFiringAlertType: AlertType = {
|
|||
defaultActionGroupId: 'default',
|
||||
producer: 'alerts',
|
||||
minimumLicenseRequired: 'basic',
|
||||
async executor(alertExecutorOptions: any) {
|
||||
async executor(alertExecutorOptions) {
|
||||
const { services, state, params } = alertExecutorOptions;
|
||||
|
||||
(params.instances || []).forEach((instance: { id: string; state: any }) => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue