Skip to content

Commit

Permalink
[RAC] Populate common rule fields in alert helpers (elastic#108679)
Browse files Browse the repository at this point in the history
Co-authored-by: mgiota <[email protected]>
  • Loading branch information
2 people authored and kibanamachine committed Aug 26, 2021
1 parent e96edc1 commit 3ab15b6
Show file tree
Hide file tree
Showing 18 changed files with 288 additions and 221 deletions.
12 changes: 12 additions & 0 deletions packages/kbn-rule-data-utils/src/alerts_as_data_status.ts
Original file line number Diff line number Diff line change
@@ -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;
1 change: 1 addition & 0 deletions packages/kbn-rule-data-utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
7 changes: 4 additions & 3 deletions x-pack/plugins/observability/public/pages/alerts/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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;
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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(),
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -82,15 +82,15 @@ export const getRenderCellValue = ({
switch (columnId) {
case ALERT_STATUS:
switch (value) {
case 'open':
case ALERT_STATUS_ACTIVE:
return (
<EuiHealth color="primary" textSize="xs">
{i18n.translate('xpack.observability.alertsTGrid.statusActiveDescription', {
defaultMessage: 'Active',
})}
</EuiHealth>
);
case 'closed':
case ALERT_STATUS_RECOVERED:
return (
<EuiHealth color={theme.eui.euiColorLightShade} textSize="xs">
<EuiText color={theme.eui.euiColorLightShade} size="relative">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]: {
Expand Down Expand Up @@ -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_ID]: {
type: 'keyword',
Expand Down Expand Up @@ -137,7 +137,7 @@ export const technicalRuleFieldMap = {
[Fields.ALERT_RULE_NAME]: {
type: 'keyword',
array: false,
required: false,
required: true,
},
[Fields.ALERT_RULE_NOTE]: {
type: 'keyword',
Expand Down
1 change: 0 additions & 1 deletion x-pack/plugins/rule_registry/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
26 changes: 19 additions & 7 deletions x-pack/plugins/rule_registry/server/routes/get_alert_by_id.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down Expand Up @@ -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',
}),
Expand Down Expand Up @@ -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',
}),
Expand All @@ -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({
Expand All @@ -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
},
Expand All @@ -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
},
Expand Down Expand Up @@ -290,15 +300,15 @@ 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',
}),
{ 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',
}),
Expand Down Expand Up @@ -326,62 +336,3 @@ type TestRuleState = Record<string, unknown> & {
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<Params, State, InstanceState, InstanceContext, ActionGroupIds> => ({
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<InstanceState, InstanceContext>()
.alertInstanceFactory,
savedObjectsClient: savedObjectsClientMock.create(),
scopedClusterClient: elasticsearchServiceMock.createScopedClusterClient(),
},
state,
updatedBy: null,
previousStartedAt: null,
namespace: undefined,
});
Loading

0 comments on commit 3ab15b6

Please sign in to comment.