Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[7.x] [RAC] Populate common rule fields in alert helpers (#108679) #110232

Merged
merged 1 commit into from
Aug 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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_CREATED_AT]: {
type: 'date',
Expand Down Expand Up @@ -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',
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