diff --git a/packages/kbn-rule-data-utils/src/alerts_as_data_status.ts b/packages/kbn-rule-data-utils/src/alerts_as_data_status.ts
new file mode 100644
index 0000000000000..cb36ce339e79a
--- /dev/null
+++ b/packages/kbn-rule-data-utils/src/alerts_as_data_status.ts
@@ -0,0 +1,12 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+export const ALERT_STATUS_ACTIVE = 'active';
+export const ALERT_STATUS_RECOVERED = 'recovered';
+
+export type AlertStatus = typeof ALERT_STATUS_ACTIVE | typeof ALERT_STATUS_RECOVERED;
diff --git a/packages/kbn-rule-data-utils/src/index.ts b/packages/kbn-rule-data-utils/src/index.ts
index ef06d5777b5ab..a08216e59401c 100644
--- a/packages/kbn-rule-data-utils/src/index.ts
+++ b/packages/kbn-rule-data-utils/src/index.ts
@@ -9,3 +9,4 @@
export * from './technical_field_names';
export * from './alerts_as_data_rbac';
export * from './alerts_as_data_severity';
+export * from './alerts_as_data_status';
diff --git a/x-pack/plugins/observability/public/pages/alerts/index.tsx b/x-pack/plugins/observability/public/pages/alerts/index.tsx
index 6e2323bb4c54b..45a8dd842ee27 100644
--- a/x-pack/plugins/observability/public/pages/alerts/index.tsx
+++ b/x-pack/plugins/observability/public/pages/alerts/index.tsx
@@ -6,11 +6,12 @@
*/
import { EuiButtonEmpty, EuiCallOut, EuiFlexGroup, EuiFlexItem, EuiLink } from '@elastic/eui';
+import { IndexPatternBase } from '@kbn/es-query';
import { i18n } from '@kbn/i18n';
+import { ALERT_STATUS, ALERT_STATUS_ACTIVE } from '@kbn/rule-data-utils';
import React, { useCallback, useRef } from 'react';
import { useHistory } from 'react-router-dom';
import useAsync from 'react-use/lib/useAsync';
-import { IndexPatternBase } from '@kbn/es-query';
import { ParsedTechnicalFields } from '../../../../rule_registry/common/parse_technical_fields';
import type { AlertWorkflowStatus } from '../../../common/typings';
import { ExperimentalBadge } from '../../components/shared/experimental_badge';
@@ -21,8 +22,8 @@ import { RouteParams } from '../../routes';
import { callObservabilityApi } from '../../services/call_observability_api';
import { AlertsSearchBar } from './alerts_search_bar';
import { AlertsTableTGrid } from './alerts_table_t_grid';
-import { WorkflowStatusFilter } from './workflow_status_filter';
import './styles.scss';
+import { WorkflowStatusFilter } from './workflow_status_filter';
export interface TopAlert {
fields: ParsedTechnicalFields;
@@ -45,7 +46,7 @@ export function AlertsPage({ routeParams }: AlertsPageProps) {
query: {
rangeFrom = 'now-15m',
rangeTo = 'now',
- kuery = 'kibana.alert.status: "open"', // TODO change hardcoded values as part of another PR
+ kuery = `${ALERT_STATUS}: "${ALERT_STATUS_ACTIVE}"`,
workflowStatus = 'open',
},
} = routeParams;
diff --git a/x-pack/plugins/observability/public/pages/alerts/parse_alert.ts b/x-pack/plugins/observability/public/pages/alerts/parse_alert.ts
index 4e99bdb0ee32d..6b4240c9ad346 100644
--- a/x-pack/plugins/observability/public/pages/alerts/parse_alert.ts
+++ b/x-pack/plugins/observability/public/pages/alerts/parse_alert.ts
@@ -18,6 +18,7 @@ import {
ALERT_RULE_NAME as ALERT_RULE_NAME_NON_TYPED,
// @ts-expect-error
} from '@kbn/rule-data-utils/target_node/technical_field_names';
+import { ALERT_STATUS_ACTIVE } from '@kbn/rule-data-utils';
import type { TopAlert } from '.';
import { parseTechnicalFields } from '../../../../rule_registry/common/parse_technical_fields';
import { asDuration, asPercent } from '../../../common/utils/formatters';
@@ -42,7 +43,7 @@ export const parseAlert = (observabilityRuleTypeRegistry: ObservabilityRuleTypeR
return {
...formatted,
fields: parsedFields,
- active: parsedFields[ALERT_STATUS] !== 'closed',
+ active: parsedFields[ALERT_STATUS] === ALERT_STATUS_ACTIVE,
start: new Date(parsedFields[ALERT_START] ?? 0).getTime(),
};
};
diff --git a/x-pack/plugins/observability/public/pages/alerts/render_cell_value.tsx b/x-pack/plugins/observability/public/pages/alerts/render_cell_value.tsx
index 691bfc984b9cb..0430c750c8862 100644
--- a/x-pack/plugins/observability/public/pages/alerts/render_cell_value.tsx
+++ b/x-pack/plugins/observability/public/pages/alerts/render_cell_value.tsx
@@ -26,7 +26,7 @@ import {
TIMESTAMP,
// @ts-expect-error importing from a place other than root because we want to limit what we import from this package
} from '@kbn/rule-data-utils/target_node/technical_field_names';
-
+import { ALERT_STATUS_ACTIVE, ALERT_STATUS_RECOVERED } from '@kbn/rule-data-utils';
import type { CellValueElementProps, TimelineNonEcsData } from '../../../../timelines/common';
import { TimestampTooltip } from '../../components/shared/timestamp_tooltip';
import { asDuration } from '../../../common/utils/formatters';
@@ -82,7 +82,7 @@ export const getRenderCellValue = ({
switch (columnId) {
case ALERT_STATUS:
switch (value) {
- case 'open':
+ case ALERT_STATUS_ACTIVE:
return (
{i18n.translate('xpack.observability.alertsTGrid.statusActiveDescription', {
@@ -90,7 +90,7 @@ export const getRenderCellValue = ({
})}
);
- case 'closed':
+ case ALERT_STATUS_RECOVERED:
return (
diff --git a/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.ts b/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.ts
index 29f03024b79f5..03d96a24bedd3 100644
--- a/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.ts
+++ b/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.ts
@@ -18,15 +18,15 @@ export const technicalRuleFieldMap = {
),
[Fields.ALERT_RULE_TYPE_ID]: { type: 'keyword', required: true },
[Fields.ALERT_RULE_CONSUMER]: { type: 'keyword', required: true },
- [Fields.ALERT_RULE_PRODUCER]: { type: 'keyword' },
+ [Fields.ALERT_RULE_PRODUCER]: { type: 'keyword', required: true },
[Fields.SPACE_IDS]: { type: 'keyword', array: true, required: true },
- [Fields.ALERT_UUID]: { type: 'keyword' },
- [Fields.ALERT_ID]: { type: 'keyword' },
+ [Fields.ALERT_UUID]: { type: 'keyword', required: true },
+ [Fields.ALERT_ID]: { type: 'keyword', required: true },
[Fields.ALERT_START]: { type: 'date' },
[Fields.ALERT_END]: { type: 'date' },
[Fields.ALERT_DURATION]: { type: 'long' },
[Fields.ALERT_SEVERITY]: { type: 'keyword' },
- [Fields.ALERT_STATUS]: { type: 'keyword' },
+ [Fields.ALERT_STATUS]: { type: 'keyword', required: true },
[Fields.ALERT_EVALUATION_THRESHOLD]: { type: 'scaled_float', scaling_factor: 100 },
[Fields.ALERT_EVALUATION_VALUE]: { type: 'scaled_float', scaling_factor: 100 },
[Fields.VERSION]: {
@@ -87,12 +87,12 @@ export const technicalRuleFieldMap = {
[Fields.ALERT_RULE_CATEGORY]: {
type: 'keyword',
array: false,
- required: false,
+ required: true,
},
[Fields.ALERT_RULE_UUID]: {
type: 'keyword',
array: false,
- required: false,
+ required: true,
},
[Fields.ALERT_RULE_CREATED_AT]: {
type: 'date',
@@ -132,7 +132,7 @@ export const technicalRuleFieldMap = {
[Fields.ALERT_RULE_NAME]: {
type: 'keyword',
array: false,
- required: false,
+ required: true,
},
[Fields.ALERT_RULE_NOTE]: {
type: 'keyword',
diff --git a/x-pack/plugins/rule_registry/server/index.ts b/x-pack/plugins/rule_registry/server/index.ts
index 2a609aa3bef7e..e49b2a4d5abed 100644
--- a/x-pack/plugins/rule_registry/server/index.ts
+++ b/x-pack/plugins/rule_registry/server/index.ts
@@ -19,7 +19,6 @@ export * from './config';
export * from './rule_data_plugin_service';
export * from './rule_data_client';
-export { getRuleData, RuleExecutorData } from './utils/get_rule_executor_data';
export { createLifecycleRuleTypeFactory } from './utils/create_lifecycle_rule_type_factory';
export {
LifecycleRuleExecutor,
diff --git a/x-pack/plugins/rule_registry/server/routes/get_alert_by_id.test.ts b/x-pack/plugins/rule_registry/server/routes/get_alert_by_id.test.ts
index 372fb09661259..d8640cf5dfe82 100644
--- a/x-pack/plugins/rule_registry/server/routes/get_alert_by_id.test.ts
+++ b/x-pack/plugins/rule_registry/server/routes/get_alert_by_id.test.ts
@@ -6,16 +6,22 @@
*/
import {
+ ALERT_ID,
+ ALERT_RULE_CATEGORY,
ALERT_RULE_CONSUMER,
+ ALERT_RULE_NAME,
+ ALERT_RULE_PRODUCER,
ALERT_RULE_RISK_SCORE,
+ ALERT_RULE_TYPE_ID,
+ ALERT_RULE_UUID,
ALERT_STATUS,
+ ALERT_STATUS_ACTIVE,
+ ALERT_UUID,
ECS_VERSION,
- ALERT_RULE_TYPE_ID,
SPACE_IDS,
TIMESTAMP,
VERSION,
} from '@kbn/rule-data-utils';
-
import { BASE_RAC_ALERTS_API_PATH } from '../../common/constants';
import { ParsedTechnicalFields } from '../../common/parse_technical_fields';
import { getAlertByIdRoute } from './get_alert_by_id';
@@ -24,14 +30,20 @@ import { getReadRequest } from './__mocks__/request_responses';
import { requestMock, serverMock } from './__mocks__/server';
const getMockAlert = (): ParsedTechnicalFields => ({
- [TIMESTAMP]: '2021-06-21T21:33:05.713Z',
- [ECS_VERSION]: '1.0.0',
- [VERSION]: '7.13.0',
- [ALERT_RULE_TYPE_ID]: 'apm.error_rate',
+ [ALERT_ID]: 'fake-alert-id',
+ [ALERT_RULE_CATEGORY]: 'apm.error_rate',
[ALERT_RULE_CONSUMER]: 'apm',
- [ALERT_STATUS]: 'open',
+ [ALERT_RULE_NAME]: 'Check error rate',
+ [ALERT_RULE_PRODUCER]: 'apm',
[ALERT_RULE_RISK_SCORE]: 20,
+ [ALERT_RULE_TYPE_ID]: 'fake-rule-type-id',
+ [ALERT_RULE_UUID]: 'fake-rule-uuid',
+ [ALERT_STATUS]: ALERT_STATUS_ACTIVE,
+ [ALERT_UUID]: 'fake-alert-uuid',
+ [ECS_VERSION]: '1.0.0',
[SPACE_IDS]: ['fake-space-id'],
+ [TIMESTAMP]: '2021-06-21T21:33:05.713Z',
+ [VERSION]: '7.13.0',
});
describe('getAlertByIdRoute', () => {
diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.test.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.test.ts
index 2d0ca3e328a13..c1a4fccaf205b 100644
--- a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.test.ts
+++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.test.ts
@@ -6,29 +6,25 @@
*/
import { loggerMock } from '@kbn/logging/mocks';
-import {
- elasticsearchServiceMock,
- savedObjectsClientMock,
-} from '../../../../../src/core/server/mocks';
-import {
- AlertExecutorOptions,
- AlertInstanceContext,
- AlertInstanceState,
- AlertTypeParams,
- AlertTypeState,
-} from '../../../alerting/server';
-import { alertsMock } from '../../../alerting/server/mocks';
import {
ALERT_ID,
+ ALERT_RULE_CATEGORY,
+ ALERT_RULE_CONSUMER,
+ ALERT_RULE_NAME,
+ ALERT_RULE_PRODUCER,
+ ALERT_RULE_TYPE_ID,
+ ALERT_RULE_UUID,
ALERT_STATUS,
+ ALERT_STATUS_ACTIVE,
+ ALERT_STATUS_RECOVERED,
+ ALERT_UUID,
EVENT_ACTION,
EVENT_KIND,
- ALERT_RULE_TYPE_ID,
- ALERT_RULE_CONSUMER,
SPACE_IDS,
} from '../../common/technical_rule_data_field_names';
import { createRuleDataClientMock } from '../rule_data_client/rule_data_client.mock';
import { createLifecycleExecutor } from './create_lifecycle_executor';
+import { createDefaultAlertExecutorOptions } from './rule_executor_test_utils';
describe('createLifecycleExecutor', () => {
it('wraps and unwraps the original executor state', async () => {
@@ -95,14 +91,14 @@ describe('createLifecycleExecutor', () => {
{ index: { _id: expect.any(String) } },
expect.objectContaining({
[ALERT_ID]: 'TEST_ALERT_0',
- [ALERT_STATUS]: 'open',
+ [ALERT_STATUS]: ALERT_STATUS_ACTIVE,
[EVENT_ACTION]: 'open',
[EVENT_KIND]: 'signal',
}),
{ index: { _id: expect.any(String) } },
expect.objectContaining({
[ALERT_ID]: 'TEST_ALERT_1',
- [ALERT_STATUS]: 'open',
+ [ALERT_STATUS]: ALERT_STATUS_ACTIVE,
[EVENT_ACTION]: 'open',
[EVENT_KIND]: 'signal',
}),
@@ -192,14 +188,14 @@ describe('createLifecycleExecutor', () => {
{ index: { _id: 'TEST_ALERT_0_UUID' } },
expect.objectContaining({
[ALERT_ID]: 'TEST_ALERT_0',
- [ALERT_STATUS]: 'open',
+ [ALERT_STATUS]: ALERT_STATUS_ACTIVE,
[EVENT_ACTION]: 'active',
[EVENT_KIND]: 'signal',
}),
{ index: { _id: 'TEST_ALERT_1_UUID' } },
expect.objectContaining({
[ALERT_ID]: 'TEST_ALERT_1',
- [ALERT_STATUS]: 'open',
+ [ALERT_STATUS]: ALERT_STATUS_ACTIVE,
[EVENT_ACTION]: 'active',
[EVENT_KIND]: 'signal',
}),
@@ -220,6 +216,8 @@ describe('createLifecycleExecutor', () => {
});
it('updates existing documents for recovered alerts', async () => {
+ // NOTE: the documents should actually also be updated for recurring,
+ // active alerts (see elastic/kibana#108670)
const logger = loggerMock.create();
const ruleDataClientMock = createRuleDataClientMock();
ruleDataClientMock.getReader().search.mockResolvedValue({
@@ -229,8 +227,14 @@ describe('createLifecycleExecutor', () => {
fields: {
'@timestamp': '',
[ALERT_ID]: 'TEST_ALERT_0',
+ [ALERT_UUID]: 'ALERT_0_UUID',
+ [ALERT_RULE_CATEGORY]: 'RULE_TYPE_NAME',
[ALERT_RULE_CONSUMER]: 'CONSUMER',
+ [ALERT_RULE_NAME]: 'NAME',
+ [ALERT_RULE_PRODUCER]: 'PRODUCER',
[ALERT_RULE_TYPE_ID]: 'RULE_TYPE_ID',
+ [ALERT_RULE_UUID]: 'RULE_UUID',
+ [ALERT_STATUS]: ALERT_STATUS_ACTIVE,
[SPACE_IDS]: ['fake-space-id'],
labels: { LABEL_0_KEY: 'LABEL_0_VALUE' }, // this must show up in the written doc
},
@@ -239,8 +243,14 @@ describe('createLifecycleExecutor', () => {
fields: {
'@timestamp': '',
[ALERT_ID]: 'TEST_ALERT_1',
+ [ALERT_UUID]: 'ALERT_1_UUID',
+ [ALERT_RULE_CATEGORY]: 'RULE_TYPE_NAME',
[ALERT_RULE_CONSUMER]: 'CONSUMER',
+ [ALERT_RULE_NAME]: 'NAME',
+ [ALERT_RULE_PRODUCER]: 'PRODUCER',
[ALERT_RULE_TYPE_ID]: 'RULE_TYPE_ID',
+ [ALERT_RULE_UUID]: 'RULE_UUID',
+ [ALERT_STATUS]: ALERT_STATUS_ACTIVE,
[SPACE_IDS]: ['fake-space-id'],
labels: { LABEL_0_KEY: 'LABEL_0_VALUE' }, // this must not show up in the written doc
},
@@ -290,7 +300,7 @@ describe('createLifecycleExecutor', () => {
{ index: { _id: 'TEST_ALERT_0_UUID' } },
expect.objectContaining({
[ALERT_ID]: 'TEST_ALERT_0',
- [ALERT_STATUS]: 'closed',
+ [ALERT_STATUS]: ALERT_STATUS_RECOVERED,
labels: { LABEL_0_KEY: 'LABEL_0_VALUE' },
[EVENT_ACTION]: 'close',
[EVENT_KIND]: 'signal',
@@ -298,7 +308,7 @@ describe('createLifecycleExecutor', () => {
{ index: { _id: 'TEST_ALERT_1_UUID' } },
expect.objectContaining({
[ALERT_ID]: 'TEST_ALERT_1',
- [ALERT_STATUS]: 'open',
+ [ALERT_STATUS]: ALERT_STATUS_ACTIVE,
[EVENT_ACTION]: 'active',
[EVENT_KIND]: 'signal',
}),
@@ -326,62 +336,3 @@ type TestRuleState = Record & {
const initialRuleState: TestRuleState = {
aRuleStateKey: 'INITIAL_RULE_STATE_VALUE',
};
-
-const createDefaultAlertExecutorOptions = <
- Params extends AlertTypeParams = never,
- State extends AlertTypeState = never,
- InstanceState extends AlertInstanceState = {},
- InstanceContext extends AlertInstanceContext = {},
- ActionGroupIds extends string = ''
->({
- alertId = 'ALERT_ID',
- ruleName = 'ALERT_RULE_NAME',
- params,
- state,
- createdAt = new Date(),
- startedAt = new Date(),
- updatedAt = new Date(),
-}: {
- alertId?: string;
- ruleName?: string;
- params: Params;
- state: State;
- createdAt?: Date;
- startedAt?: Date;
- updatedAt?: Date;
-}): AlertExecutorOptions => ({
- alertId,
- createdBy: 'CREATED_BY',
- startedAt,
- name: ruleName,
- rule: {
- updatedBy: null,
- tags: [],
- name: ruleName,
- createdBy: null,
- actions: [],
- enabled: true,
- consumer: 'CONSUMER',
- producer: 'ALERT_PRODUCER',
- schedule: { interval: '1m' },
- throttle: null,
- createdAt,
- updatedAt,
- notifyWhen: null,
- ruleTypeId: 'RULE_TYPE_ID',
- ruleTypeName: 'RULE_TYPE_NAME',
- },
- tags: [],
- params,
- spaceId: 'SPACE_ID',
- services: {
- alertInstanceFactory: alertsMock.createAlertServices()
- .alertInstanceFactory,
- savedObjectsClient: savedObjectsClientMock.create(),
- scopedClusterClient: elasticsearchServiceMock.createScopedClusterClient(),
- },
- state,
- updatedBy: null,
- previousStartedAt: null,
- namespace: undefined,
-});
diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts
index a3e830d6e0b2f..97337e3a5e09e 100644
--- a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts
+++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts
@@ -9,7 +9,6 @@ import type { Logger } from '@kbn/logging';
import type { PublicContract } from '@kbn/utility-types';
import { getOrElse } from 'fp-ts/lib/Either';
import * as rt from 'io-ts';
-import { Mutable } from 'utility-types';
import { v4 } from 'uuid';
import {
AlertExecutorOptions,
@@ -24,22 +23,34 @@ import {
ALERT_DURATION,
ALERT_END,
ALERT_ID,
- ALERT_RULE_CONSUMER,
- ALERT_RULE_TYPE_ID,
ALERT_RULE_UUID,
ALERT_START,
ALERT_STATUS,
+ ALERT_STATUS_ACTIVE,
+ ALERT_STATUS_RECOVERED,
ALERT_UUID,
ALERT_WORKFLOW_STATUS,
EVENT_ACTION,
EVENT_KIND,
- SPACE_IDS,
TIMESTAMP,
VERSION,
} from '../../common/technical_rule_data_field_names';
import { IRuleDataClient } from '../rule_data_client';
import { AlertExecutorOptionsWithExtraServices } from '../types';
-import { getRuleData } from './get_rule_executor_data';
+import {
+ CommonAlertFieldName,
+ CommonAlertIdFieldName,
+ getCommonAlertFields,
+} from './get_common_alert_fields';
+
+type ImplicitTechnicalFieldName = CommonAlertFieldName | CommonAlertIdFieldName;
+
+type ExplicitTechnicalAlertFields = Partial<
+ Omit
+>;
+
+type ExplicitAlertFields = Record & // every field can have values of arbitrary types
+ ExplicitTechnicalAlertFields; // but technical fields must obey their respective type
export type LifecycleAlertService<
InstanceState extends AlertInstanceState = never,
@@ -47,7 +58,7 @@ export type LifecycleAlertService<
ActionGroupIds extends string = never
> = (alert: {
id: string;
- fields: Record & Partial>;
+ fields: ExplicitAlertFields;
}) => AlertInstance;
export interface LifecycleAlertServices<
@@ -129,14 +140,10 @@ export const createLifecycleExecutor = (
>
): Promise> => {
const {
- rule,
services: { alertInstanceFactory },
state: previousState,
- spaceId,
} = options;
- const ruleExecutorData = getRuleData(options);
-
const state = getOrElse(
(): WrappedLifecycleRuleState => ({
wrapped: previousState as State,
@@ -144,9 +151,9 @@ export const createLifecycleExecutor = (
})
)(wrappedStateRt().decode(previousState));
- const currentAlerts: Record> = {};
+ const commonRuleFields = getCommonAlertFields(options);
- const timestamp = options.startedAt.toISOString();
+ const currentAlerts: Record = {};
const lifecycleAlertServices: LifecycleAlertServices<
InstanceState,
@@ -154,12 +161,8 @@ export const createLifecycleExecutor = (
ActionGroupIds
> = {
alertWithLifecycle: ({ id, fields }) => {
- currentAlerts[id] = {
- ...fields,
- [ALERT_ID]: id,
- [ALERT_RULE_TYPE_ID]: rule.ruleTypeId,
- [ALERT_RULE_CONSUMER]: rule.consumer,
- };
+ currentAlerts[id] = fields;
+
return alertInstanceFactory(id);
},
};
@@ -199,7 +202,7 @@ export const createLifecycleExecutor = (
filter: [
{
term: {
- [ALERT_RULE_UUID]: ruleExecutorData[ALERT_RULE_UUID],
+ [ALERT_RULE_UUID]: commonRuleFields[ALERT_RULE_UUID],
},
},
{
@@ -227,12 +230,10 @@ export const createLifecycleExecutor = (
hits.hits.forEach((hit) => {
const fields = parseTechnicalFields(hit.fields);
- const alertId = fields[ALERT_ID]!;
+ const alertId = fields[ALERT_ID];
alertsDataMap[alertId] = {
+ ...commonRuleFields,
...fields,
- [ALERT_ID]: alertId,
- [ALERT_RULE_TYPE_ID]: rule.ruleTypeId,
- [ALERT_RULE_CONSUMER]: rule.consumer,
};
});
}
@@ -244,59 +245,28 @@ export const createLifecycleExecutor = (
logger.warn(`Could not find alert data for ${alertId}`);
}
- const event: Mutable = {
- ...alertData,
- ...ruleExecutorData,
- [TIMESTAMP]: timestamp,
- [EVENT_KIND]: 'signal',
- [ALERT_RULE_CONSUMER]: rule.consumer,
- [ALERT_ID]: alertId,
- [VERSION]: ruleDataClient.kibanaVersion,
- } as ParsedTechnicalFields;
-
const isNew = !state.trackedAlerts[alertId];
const isRecovered = !currentAlerts[alertId];
- const isActiveButNotNew = !isNew && !isRecovered;
const isActive = !isRecovered;
const { alertUuid, started } = state.trackedAlerts[alertId] ?? {
alertUuid: v4(),
- started: timestamp,
+ started: commonRuleFields[TIMESTAMP],
+ };
+ const event: ParsedTechnicalFields = {
+ ...alertData,
+ ...commonRuleFields,
+ [ALERT_DURATION]: (options.startedAt.getTime() - new Date(started).getTime()) * 1000,
+ [ALERT_ID]: alertId,
+ [ALERT_START]: started,
+ [ALERT_STATUS]: isActive ? ALERT_STATUS_ACTIVE : ALERT_STATUS_RECOVERED,
+ [ALERT_WORKFLOW_STATUS]: alertData[ALERT_WORKFLOW_STATUS] ?? 'open',
+ [ALERT_UUID]: alertUuid,
+ [EVENT_KIND]: 'signal',
+ [EVENT_ACTION]: isNew ? 'open' : isActive ? 'active' : 'close',
+ [VERSION]: ruleDataClient.kibanaVersion,
+ ...(isRecovered ? { [ALERT_END]: commonRuleFields[TIMESTAMP] } : {}),
};
-
- event[ALERT_START] = started;
- event[ALERT_UUID] = alertUuid;
- event[ALERT_WORKFLOW_STATUS] = event[ALERT_WORKFLOW_STATUS] ?? 'open';
-
- // not sure why typescript needs the non-null assertion here
- // we already assert the value is not undefined with the ternary
- // still getting an error with the ternary.. strange.
-
- event[SPACE_IDS] =
- event[SPACE_IDS] == null
- ? [spaceId]
- : [spaceId, ...event[SPACE_IDS]!.filter((sid) => sid !== spaceId)];
-
- if (isNew) {
- event[EVENT_ACTION] = 'open';
- }
-
- if (isRecovered) {
- event[ALERT_END] = timestamp;
- event[EVENT_ACTION] = 'close';
- event[ALERT_STATUS] = 'closed';
- }
-
- if (isActiveButNotNew) {
- event[EVENT_ACTION] = 'active';
- }
-
- if (isActive) {
- event[ALERT_STATUS] = 'open';
- }
-
- event[ALERT_DURATION] =
- (options.startedAt.getTime() - new Date(event[ALERT_START]!).getTime()) * 1000;
return event;
});
diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts
index 71a0dee5deac7..2b138ae723305 100644
--- a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts
+++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts
@@ -6,7 +6,13 @@
*/
import { schema } from '@kbn/config-schema';
-import { ALERT_DURATION, ALERT_STATUS, ALERT_UUID } from '@kbn/rule-data-utils';
+import {
+ ALERT_DURATION,
+ ALERT_STATUS,
+ ALERT_STATUS_ACTIVE,
+ ALERT_STATUS_RECOVERED,
+ ALERT_UUID,
+} from '@kbn/rule-data-utils';
import { loggerMock } from '@kbn/logging/mocks';
import { castArray, omit, mapValues } from 'lodash';
import { RuleDataClient } from '../rule_data_client';
@@ -177,7 +183,9 @@ describe('createLifecycleRuleTypeFactory', () => {
expect(evaluationDocuments.length).toBe(0);
expect(alertDocuments.length).toBe(2);
- expect(alertDocuments.every((doc) => doc[ALERT_STATUS] === 'open')).toBeTruthy();
+ expect(
+ alertDocuments.every((doc) => doc[ALERT_STATUS] === ALERT_STATUS_ACTIVE)
+ ).toBeTruthy();
expect(alertDocuments.every((doc) => doc[ALERT_DURATION] === 0)).toBeTruthy();
@@ -198,7 +206,7 @@ describe('createLifecycleRuleTypeFactory', () => {
"kibana.alert.rule.rule_type_id": "ruleTypeId",
"kibana.alert.rule.uuid": "alertId",
"kibana.alert.start": "2021-06-16T09:01:00.000Z",
- "kibana.alert.status": "open",
+ "kibana.alert.status": "active",
"kibana.alert.workflow_status": "open",
"kibana.space_ids": Array [
"spaceId",
@@ -222,7 +230,7 @@ describe('createLifecycleRuleTypeFactory', () => {
"kibana.alert.rule.rule_type_id": "ruleTypeId",
"kibana.alert.rule.uuid": "alertId",
"kibana.alert.start": "2021-06-16T09:01:00.000Z",
- "kibana.alert.status": "open",
+ "kibana.alert.status": "active",
"kibana.alert.workflow_status": "open",
"kibana.space_ids": Array [
"spaceId",
@@ -284,7 +292,9 @@ describe('createLifecycleRuleTypeFactory', () => {
expect(evaluationDocuments.length).toBe(0);
expect(alertDocuments.length).toBe(2);
- expect(alertDocuments.every((doc) => doc[ALERT_STATUS] === 'open')).toBeTruthy();
+ expect(
+ alertDocuments.every((doc) => doc[ALERT_STATUS] === ALERT_STATUS_ACTIVE)
+ ).toBeTruthy();
expect(alertDocuments.every((doc) => doc['event.action'] === 'active')).toBeTruthy();
expect(alertDocuments.every((doc) => doc[ALERT_DURATION] > 0)).toBeTruthy();
@@ -362,10 +372,10 @@ describe('createLifecycleRuleTypeFactory', () => {
);
expect(opbeansJavaAlertDoc['event.action']).toBe('active');
- expect(opbeansJavaAlertDoc[ALERT_STATUS]).toBe('open');
+ expect(opbeansJavaAlertDoc[ALERT_STATUS]).toBe(ALERT_STATUS_ACTIVE);
expect(opbeansNodeAlertDoc['event.action']).toBe('close');
- expect(opbeansNodeAlertDoc[ALERT_STATUS]).toBe('closed');
+ expect(opbeansNodeAlertDoc[ALERT_STATUS]).toBe(ALERT_STATUS_RECOVERED);
});
});
});
diff --git a/x-pack/plugins/rule_registry/server/utils/create_persistence_rule_type_factory.ts b/x-pack/plugins/rule_registry/server/utils/create_persistence_rule_type_factory.ts
index 30e17f1afca54..1fa51d98c8ab5 100644
--- a/x-pack/plugins/rule_registry/server/utils/create_persistence_rule_type_factory.ts
+++ b/x-pack/plugins/rule_registry/server/utils/create_persistence_rule_type_factory.ts
@@ -6,6 +6,7 @@
*/
import { ALERT_ID, VERSION } from '@kbn/rule-data-utils';
+import { getCommonAlertFields } from './get_common_alert_fields';
import { CreatePersistenceRuleTypeFactory } from './persistence_types';
export const createPersistenceRuleTypeFactory: CreatePersistenceRuleTypeFactory = ({
@@ -24,13 +25,16 @@ export const createPersistenceRuleTypeFactory: CreatePersistenceRuleTypeFactory
logger.debug(`Found ${numAlerts} alerts.`);
if (ruleDataClient.isWriteEnabled() && numAlerts) {
+ const commonRuleFields = getCommonAlertFields(options);
+
const response = await ruleDataClient.getWriter().bulk({
body: alerts.flatMap((event) => [
{ index: {} },
{
- ...event.fields,
[ALERT_ID]: event.id,
[VERSION]: ruleDataClient.kibanaVersion,
+ ...commonRuleFields,
+ ...event.fields,
},
]),
refresh,
diff --git a/x-pack/plugins/rule_registry/server/utils/get_common_alert_fields.ts b/x-pack/plugins/rule_registry/server/utils/get_common_alert_fields.ts
new file mode 100644
index 0000000000000..8bba639636ba6
--- /dev/null
+++ b/x-pack/plugins/rule_registry/server/utils/get_common_alert_fields.ts
@@ -0,0 +1,57 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { Values } from '@kbn/utility-types';
+import { AlertExecutorOptions } from '../../../alerting/server';
+import { ParsedTechnicalFields } from '../../common/parse_technical_fields';
+import {
+ ALERT_ID,
+ ALERT_UUID,
+ ALERT_RULE_CATEGORY,
+ ALERT_RULE_CONSUMER,
+ ALERT_RULE_NAME,
+ ALERT_RULE_PRODUCER,
+ ALERT_RULE_TYPE_ID,
+ ALERT_RULE_UUID,
+ SPACE_IDS,
+ TAGS,
+ TIMESTAMP,
+} from '../../common/technical_rule_data_field_names';
+
+const commonAlertFieldNames = [
+ ALERT_RULE_CATEGORY,
+ ALERT_RULE_CONSUMER,
+ ALERT_RULE_NAME,
+ ALERT_RULE_PRODUCER,
+ ALERT_RULE_TYPE_ID,
+ ALERT_RULE_UUID,
+ SPACE_IDS,
+ TAGS,
+ TIMESTAMP,
+];
+export type CommonAlertFieldName = Values;
+
+const commonAlertIdFieldNames = [ALERT_ID, ALERT_UUID];
+export type CommonAlertIdFieldName = Values;
+
+export type CommonAlertFields = Pick;
+
+export const getCommonAlertFields = (
+ options: AlertExecutorOptions
+): CommonAlertFields => {
+ return {
+ [ALERT_RULE_CATEGORY]: options.rule.ruleTypeName,
+ [ALERT_RULE_CONSUMER]: options.rule.consumer,
+ [ALERT_RULE_NAME]: options.rule.name,
+ [ALERT_RULE_PRODUCER]: options.rule.producer,
+ [ALERT_RULE_TYPE_ID]: options.rule.ruleTypeId,
+ [ALERT_RULE_UUID]: options.alertId,
+ [SPACE_IDS]: [options.spaceId],
+ [TAGS]: options.tags,
+ [TIMESTAMP]: options.startedAt.toISOString(),
+ };
+};
diff --git a/x-pack/plugins/rule_registry/server/utils/get_rule_executor_data.ts b/x-pack/plugins/rule_registry/server/utils/get_rule_executor_data.ts
deleted file mode 100644
index 13f0b27e85c3b..0000000000000
--- a/x-pack/plugins/rule_registry/server/utils/get_rule_executor_data.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { AlertExecutorOptions } from '../../../alerting/server';
-import {
- ALERT_RULE_PRODUCER,
- ALERT_RULE_CATEGORY,
- ALERT_RULE_TYPE_ID,
- ALERT_RULE_NAME,
- ALERT_RULE_UUID,
- TAGS,
-} from '../../common/technical_rule_data_field_names';
-
-export interface RuleExecutorData {
- [ALERT_RULE_CATEGORY]: string;
- [ALERT_RULE_TYPE_ID]: string;
- [ALERT_RULE_UUID]: string;
- [ALERT_RULE_NAME]: string;
- [ALERT_RULE_PRODUCER]: string;
- [TAGS]: string[];
-}
-
-export function getRuleData(options: AlertExecutorOptions) {
- return {
- [ALERT_RULE_TYPE_ID]: options.rule.ruleTypeId,
- [ALERT_RULE_UUID]: options.alertId,
- [ALERT_RULE_CATEGORY]: options.rule.ruleTypeName,
- [ALERT_RULE_NAME]: options.rule.name,
- [TAGS]: options.tags,
- [ALERT_RULE_PRODUCER]: options.rule.producer,
- };
-}
diff --git a/x-pack/plugins/rule_registry/server/utils/rule_executor_test_utils.ts b/x-pack/plugins/rule_registry/server/utils/rule_executor_test_utils.ts
new file mode 100644
index 0000000000000..b74fa27879f3d
--- /dev/null
+++ b/x-pack/plugins/rule_registry/server/utils/rule_executor_test_utils.ts
@@ -0,0 +1,77 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+import {
+ elasticsearchServiceMock,
+ savedObjectsClientMock,
+} from '../../../../../src/core/server/mocks';
+import {
+ AlertExecutorOptions,
+ AlertInstanceContext,
+ AlertInstanceState,
+ AlertTypeParams,
+ AlertTypeState,
+} from '../../../alerting/server';
+import { alertsMock } from '../../../alerting/server/mocks';
+
+export const createDefaultAlertExecutorOptions = <
+ Params extends AlertTypeParams = never,
+ State extends AlertTypeState = never,
+ InstanceState extends AlertInstanceState = {},
+ InstanceContext extends AlertInstanceContext = {},
+ ActionGroupIds extends string = ''
+>({
+ alertId = 'ALERT_ID',
+ ruleName = 'ALERT_RULE_NAME',
+ params,
+ state,
+ createdAt = new Date(),
+ startedAt = new Date(),
+ updatedAt = new Date(),
+}: {
+ alertId?: string;
+ ruleName?: string;
+ params: Params;
+ state: State;
+ createdAt?: Date;
+ startedAt?: Date;
+ updatedAt?: Date;
+}): AlertExecutorOptions => ({
+ alertId,
+ createdBy: 'CREATED_BY',
+ startedAt,
+ name: ruleName,
+ rule: {
+ updatedBy: null,
+ tags: [],
+ name: ruleName,
+ createdBy: null,
+ actions: [],
+ enabled: true,
+ consumer: 'CONSUMER',
+ producer: 'ALERT_PRODUCER',
+ schedule: { interval: '1m' },
+ throttle: null,
+ createdAt,
+ updatedAt,
+ notifyWhen: null,
+ ruleTypeId: 'RULE_TYPE_ID',
+ ruleTypeName: 'RULE_TYPE_NAME',
+ },
+ tags: [],
+ params,
+ spaceId: 'SPACE_ID',
+ services: {
+ alertInstanceFactory: alertsMock.createAlertServices()
+ .alertInstanceFactory,
+ savedObjectsClient: savedObjectsClientMock.create(),
+ scopedClusterClient: elasticsearchServiceMock.createScopedClusterClient(),
+ },
+ state,
+ updatedBy: null,
+ previousStartedAt: null,
+ namespace: undefined,
+});
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/rule_registry_adapter/rule_registry_log_client/parse_rule_execution_log.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/rule_registry_adapter/rule_registry_log_client/parse_rule_execution_log.ts
index 0c533ed026901..cbc6e570e936f 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/rule_registry_adapter/rule_registry_log_client/parse_rule_execution_log.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/rule_registry_adapter/rule_registry_log_client/parse_rule_execution_log.ts
@@ -33,5 +33,8 @@ export const parseRuleExecutionLog = (input: unknown) => {
/**
* @deprecated RuleExecutionEvent is kept here only as a reference. It will be superseded with EventLog implementation
+ *
+ * It's marked as `Partial` because the field map is not yet appropriate for
+ * execution log events.
*/
-export type RuleExecutionEvent = ReturnType;
+export type RuleExecutionEvent = Partial>;
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/rule_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/rule_type.ts
index 1a8389d450ab3..d56344b7707db 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/rule_type.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/rule_type.ts
@@ -19,6 +19,9 @@ import { AlertAttributes } from '../../signals/types';
import { createRuleMock } from './rule';
import { listMock } from '../../../../../../lists/server/mocks';
import { RuleParams } from '../../schemas/rule_schemas';
+// this is only used in tests
+// eslint-disable-next-line @kbn/eslint/no-restricted-paths
+import { createDefaultAlertExecutorOptions } from '../../../../../../rule_registry/server/utils/rule_executor_test_utils';
export const createRuleTypeMocks = (
ruleType: string = 'query',
@@ -90,10 +93,12 @@ export const createRuleTypeMocks = (
scheduleActions,
executor: async ({ params }: { params: Record }) => {
return alertExecutor({
+ ...createDefaultAlertExecutorOptions({
+ params,
+ alertId: v4(),
+ state: {},
+ }),
services,
- params,
- alertId: v4(),
- startedAt: new Date(),
});
},
};
diff --git a/x-pack/test/apm_api_integration/tests/alerts/rule_registry.ts b/x-pack/test/apm_api_integration/tests/alerts/rule_registry.ts
index 678aebf502869..73e0669c0e3a8 100644
--- a/x-pack/test/apm_api_integration/tests/alerts/rule_registry.ts
+++ b/x-pack/test/apm_api_integration/tests/alerts/rule_registry.ts
@@ -392,7 +392,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
"apm.transaction_error_rate",
],
"kibana.alert.status": Array [
- "open",
+ "active",
],
"kibana.alert.workflow_status": Array [
"open",
@@ -461,7 +461,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
any
>;
- expect(recoveredAlertEvent[ALERT_STATUS]?.[0]).to.eql('closed');
+ expect(recoveredAlertEvent[ALERT_STATUS]?.[0]).to.eql('recovered');
expect(recoveredAlertEvent[ALERT_DURATION]?.[0]).to.be.greaterThan(0);
expect(new Date(recoveredAlertEvent[ALERT_END]?.[0]).getTime()).to.be.greaterThan(0);
@@ -502,7 +502,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
"apm.transaction_error_rate",
],
"kibana.alert.status": Array [
- "closed",
+ "recovered",
],
"kibana.alert.workflow_status": Array [
"open",