[alerting] replace internal legacy API calls with new APIs (#121048)

resolves https://github.com/elastic/kibana/issues/116939

Removes the remaining calls to the legacy HTTP alerting endpoints by internal Kibana code.
This commit is contained in:
Patrick Mueller 2021-12-16 15:56:02 -05:00 committed by GitHub
parent 825e35dcba
commit b4c44a135e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 698 additions and 115 deletions

View file

@ -0,0 +1,25 @@
/*
* 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.
*/
// We don't have types defined for the response objects from our HTTP APIs,
// so defining what we need here.
export interface Rule<T = never> {
id: string;
name: string;
rule_type_id: string;
schedule: {
interval: string;
};
params: T;
}
export interface RuleTaskState {
alerts: Array<{
state: Record<string, never>;
}>;
}

View file

@ -14,7 +14,7 @@ export function registerNavigation(alerting: AlertingSetup) {
// register default navigation
alerting.registerDefaultNavigation(
ALERTING_EXAMPLE_APP_ID,
(alert: SanitizedAlert) => `/alert/${alert.id}`
(alert: SanitizedAlert) => `/rule/${alert.id}`
);
registerPeopleInSpaceNavigation(alerting);

View file

@ -23,26 +23,27 @@ import { CoreStart } from 'kibana/public';
import { isEmpty } from 'lodash';
import { ALERTING_EXAMPLE_APP_ID } from '../../common/constants';
import {
Alert,
RuleTaskState,
LEGACY_BASE_ALERT_API_PATH,
BASE_ALERTING_API_PATH,
INTERNAL_BASE_ALERTING_API_PATH,
} from '../../../../plugins/alerting/common';
import { Rule, RuleTaskState } from '../../common/types';
type Props = RouteComponentProps & {
http: CoreStart['http'];
id: string;
};
export const ViewAlertPage = withRouter(({ http, id }: Props) => {
const [alert, setAlert] = useState<Alert | null>(null);
const [alert, setAlert] = useState<Rule | null>(null);
const [alertState, setAlertState] = useState<RuleTaskState | null>(null);
useEffect(() => {
if (!alert) {
http.get<Alert | null>(`${LEGACY_BASE_ALERT_API_PATH}/alert/${id}`).then(setAlert);
http.get<Rule | null>(`${BASE_ALERTING_API_PATH}/rule/${id}`).then(setAlert);
}
if (!alertState) {
http
.get<RuleTaskState | null>(`${LEGACY_BASE_ALERT_API_PATH}/alert/${id}/state`)
.get<RuleTaskState | null>(`${INTERNAL_BASE_ALERTING_API_PATH}/rule/${id}/state`)
.then(setAlertState);
}
}, [alert, alertState, http, id]);
@ -60,7 +61,7 @@ export const ViewAlertPage = withRouter(({ http, id }: Props) => {
Rule, whose ID is <EuiTextColor color="accent">{`${alert.id}`}</EuiTextColor>.
</p>
<p>
Its RuleType is <EuiTextColor color="accent">{`${alert.alertTypeId}`}</EuiTextColor> and
Its RuleType is <EuiTextColor color="accent">{`${alert.rule_type_id}`}</EuiTextColor> and
its scheduled to run at an interval of
<EuiTextColor color="accent"> {`${alert.schedule.interval}`}</EuiTextColor>.
</p>
@ -69,7 +70,7 @@ export const ViewAlertPage = withRouter(({ http, id }: Props) => {
<EuiText>
<h2>Alerts</h2>
</EuiText>
{isEmpty(alertState.alertInstances) ? (
{isEmpty(alertState.alerts) ? (
<EuiCallOut title="No Alerts!" color="warning" iconType="help">
<p>This Rule doesn&apos;t have any active alerts at the moment.</p>
</EuiCallOut>
@ -84,7 +85,7 @@ export const ViewAlertPage = withRouter(({ http, id }: Props) => {
</EuiCallOut>
<EuiSpacer size="l" />
<EuiDescriptionList compressed>
{Object.entries(alertState.alertInstances ?? {}).map(([instance, { state }]) => (
{Object.entries(alertState.alerts ?? {}).map(([instance, { state }]) => (
<Fragment>
<EuiDescriptionListTitle>{instance}</EuiDescriptionListTitle>
<EuiDescriptionListDescription>

View file

@ -25,10 +25,10 @@ import { CoreStart } from 'kibana/public';
import { isEmpty } from 'lodash';
import { ALERTING_EXAMPLE_APP_ID, AlwaysFiringParams } from '../../common/constants';
import {
Alert,
RuleTaskState,
LEGACY_BASE_ALERT_API_PATH,
BASE_ALERTING_API_PATH,
INTERNAL_BASE_ALERTING_API_PATH,
} from '../../../../plugins/alerting/common';
import { Rule, RuleTaskState } from '../../common/types';
type Props = RouteComponentProps & {
http: CoreStart['http'];
@ -39,18 +39,18 @@ function hasCraft(state: any): state is { craft: string } {
return state && state.craft;
}
export const ViewPeopleInSpaceAlertPage = withRouter(({ http, id }: Props) => {
const [alert, setAlert] = useState<Alert<AlwaysFiringParams> | null>(null);
const [alert, setAlert] = useState<Rule<AlwaysFiringParams> | null>(null);
const [alertState, setAlertState] = useState<RuleTaskState | null>(null);
useEffect(() => {
if (!alert) {
http
.get<Alert<AlwaysFiringParams> | null>(`${LEGACY_BASE_ALERT_API_PATH}/alert/${id}`)
.get<Rule<AlwaysFiringParams> | null>(`${BASE_ALERTING_API_PATH}/rule/${id}`)
.then(setAlert);
}
if (!alertState) {
http
.get<RuleTaskState | null>(`${LEGACY_BASE_ALERT_API_PATH}/alert/${id}/state`)
.get<RuleTaskState | null>(`${INTERNAL_BASE_ALERTING_API_PATH}/rule/${id}/state`)
.then(setAlertState);
}
}, [alert, alertState, http, id]);
@ -69,7 +69,7 @@ export const ViewPeopleInSpaceAlertPage = withRouter(({ http, id }: Props) => {
<EuiText>
<h2>Alerts</h2>
</EuiText>
{isEmpty(alertState.alertInstances) ? (
{isEmpty(alertState.alerts) ? (
<EuiCallOut title="No Alerts!" color="warning" iconType="help">
<p>
The people in {alert.params.craft} at the moment <b>are not</b> {alert.params.op}{' '}
@ -89,23 +89,21 @@ export const ViewPeopleInSpaceAlertPage = withRouter(({ http, id }: Props) => {
<EuiFlexGroup>
<EuiFlexItem grow={false}>
<EuiStat
title={Object.keys(alertState.alertInstances ?? {}).length}
title={Object.keys(alertState.alerts ?? {}).length}
description={`People in ${alert.params.craft}`}
titleColor="primary"
/>
</EuiFlexItem>
<EuiFlexItem>
<EuiDescriptionList compressed>
{Object.entries(alertState.alertInstances ?? {}).map(
([instance, { state }], index) => (
<Fragment key={index}>
<EuiDescriptionListTitle>{instance}</EuiDescriptionListTitle>
<EuiDescriptionListDescription>
{hasCraft(state) ? state.craft : 'Unknown Craft'}
</EuiDescriptionListDescription>
</Fragment>
)
)}
{Object.entries(alertState.alerts ?? {}).map(([instance, { state }], index) => (
<Fragment key={index}>
<EuiDescriptionListTitle>{instance}</EuiDescriptionListTitle>
<EuiDescriptionListDescription>
{hasCraft(state) ? state.craft : 'Unknown Craft'}
</EuiDescriptionListDescription>
</Fragment>
))}
</EuiDescriptionList>
</EuiFlexItem>
</EuiFlexGroup>

View file

@ -8,6 +8,15 @@
import { LicenseType } from '../../licensing/common/types';
import { RecoveredActionGroupId, DefaultActionGroupId } from './builtin_action_groups';
interface ConsumerPrivileges {
read: boolean;
all: boolean;
}
interface ActionVariable {
name: string;
description: string;
}
export interface RuleType<
ActionGroupIds extends Exclude<string, RecoveredActionGroupId> = DefaultActionGroupId,
RecoveryActionGroupId extends string = RecoveredActionGroupId
@ -16,7 +25,11 @@ export interface RuleType<
name: string;
actionGroups: Array<ActionGroup<ActionGroupIds>>;
recoveryActionGroup: ActionGroup<RecoveryActionGroupId>;
actionVariables: string[];
actionVariables: {
context: ActionVariable[];
state: ActionVariable[];
params: ActionVariable[];
};
defaultActionGroupId: ActionGroupIds;
producer: string;
minimumLicenseRequired: LicenseType;
@ -24,6 +37,8 @@ export interface RuleType<
ruleTaskTimeout?: string;
defaultScheduleInterval?: string;
minimumScheduleInterval?: string;
enabledInLicense: boolean;
authorizedConsumers: Record<string, ConsumerPrivileges>;
}
export interface ActionGroup<ActionGroupIds extends string> {

View file

@ -5,10 +5,9 @@
* 2.0.
*/
import { RuleType, RecoveredActionGroup } from '../common';
import { Alert, RuleType } from '../common';
import { httpServiceMock } from '../../../../src/core/public/mocks';
import { loadAlert, loadAlertType, loadAlertTypes } from './alert_api';
import uuid from 'uuid';
const http = httpServiceMock.createStartContract();
@ -16,26 +15,63 @@ beforeEach(() => jest.resetAllMocks());
describe('loadAlertTypes', () => {
test('should call get alert types API', async () => {
const resolvedValue: RuleType[] = [
{
id: 'test',
name: 'Test',
actionVariables: ['var1'],
actionGroups: [{ id: 'default', name: 'Default' }],
defaultActionGroupId: 'default',
minimumLicenseRequired: 'basic',
isExportable: true,
recoveryActionGroup: RecoveredActionGroup,
producer: 'alerts',
},
];
http.get.mockResolvedValueOnce(resolvedValue);
http.get.mockResolvedValueOnce([getApiRuleType()]);
const result = await loadAlertTypes({ http });
expect(result).toEqual(resolvedValue);
expect(result).toMatchInlineSnapshot(`
Array [
Object {
"actionGroups": Array [
Object {
"id": "default",
"name": "Threshold met",
},
],
"actionVariables": Object {
"context": Array [
Object {
"description": "A pre-constructed message for the alert.",
"name": "message",
},
],
"params": Array [
Object {
"description": "An array of values to use as the threshold; \\"between\\" and \\"notBetween\\" require two values, the others require one.",
"name": "threshold",
},
],
"state": Array [
Object {
"description": "Example state variable",
"name": "example",
},
],
},
"authorizedConsumers": Object {
"alerts": Object {
"all": true,
"read": true,
},
},
"defaultActionGroupId": "default",
"enabledInLicense": true,
"id": ".index-threshold",
"isExportable": true,
"minimumLicenseRequired": "basic",
"name": "Index threshold",
"producer": "stackAlerts",
"recoveryActionGroup": Object {
"id": "recovered",
"name": "Recovered",
},
"ruleTaskTimeout": "5m",
},
]
`);
expect(http.get.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"/api/alerts/list_alert_types",
"/api/alerting/rule_types",
]
`);
});
@ -43,67 +79,256 @@ describe('loadAlertTypes', () => {
describe('loadAlertType', () => {
test('should call get alert types API', async () => {
const alertType: RuleType = {
id: 'test',
name: 'Test',
actionVariables: ['var1'],
actionGroups: [{ id: 'default', name: 'Default' }],
defaultActionGroupId: 'default',
minimumLicenseRequired: 'basic',
isExportable: true,
recoveryActionGroup: RecoveredActionGroup,
producer: 'alerts',
};
http.get.mockResolvedValueOnce([alertType]);
const ruleType = getApiRuleType();
http.get.mockResolvedValueOnce([ruleType]);
await loadAlertType({ http, id: alertType.id });
expect(http.get.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"/api/alerts/list_alert_types",
]
`);
});
test('should find the required alertType', async () => {
const alertType: RuleType = {
id: 'test-another',
name: 'Test Another',
actionVariables: [],
actionGroups: [{ id: 'default', name: 'Default' }],
defaultActionGroupId: 'default',
minimumLicenseRequired: 'basic',
isExportable: true,
recoveryActionGroup: RecoveredActionGroup,
producer: 'alerts',
};
http.get.mockResolvedValueOnce([alertType]);
expect(await loadAlertType({ http, id: 'test-another' })).toEqual(alertType);
const result = await loadAlertType({ http, id: ruleType.id });
expect(result).toEqual(getRuleType());
});
});
describe('loadAlert', () => {
test('should call get API with base parameters', async () => {
const alertId = uuid.v4();
const resolvedValue = {
id: alertId,
name: 'name',
tags: [],
enabled: true,
alertTypeId: '.noop',
schedule: { interval: '1s' },
actions: [],
params: {},
createdBy: null,
updatedBy: null,
throttle: null,
muteAll: false,
mutedInstanceIds: [],
};
http.get.mockResolvedValueOnce(resolvedValue);
const apiRule = getApiRule();
http.get.mockResolvedValueOnce(apiRule);
expect(await loadAlert({ http, alertId })).toEqual(resolvedValue);
expect(http.get).toHaveBeenCalledWith(`/api/alerts/alert/${alertId}`);
const res = await loadAlert({ http, alertId: apiRule.id });
expect(res).toEqual(getRule());
const fixedDate = new Date('2021-12-11T16:59:50.152Z');
res.updatedAt = fixedDate;
res.createdAt = fixedDate;
res.executionStatus.lastExecutionDate = fixedDate;
expect(res).toMatchInlineSnapshot(`
Object {
"actions": Array [
Object {
"actionTypeId": ".server-log",
"group": "threshold met",
"id": "3619a0d0-582b-11ec-8995-2b1578a3bc5d",
"params": Object {
"message": "alert 37: {{context.message}}",
},
},
],
"alertTypeId": ".index-threshold",
"apiKey": null,
"apiKeyOwner": "2889684073",
"consumer": "alerts",
"createdAt": 2021-12-11T16:59:50.152Z,
"createdBy": "elastic",
"enabled": true,
"executionStatus": Object {
"lastDuration": 1194,
"lastExecutionDate": 2021-12-11T16:59:50.152Z,
"status": "ok",
},
"id": "3d534c70-582b-11ec-8995-2b1578a3bc5d",
"muteAll": false,
"mutedInstanceIds": Array [],
"name": "stressing index-threshold 37/200",
"notifyWhen": "onActiveAlert",
"params": Object {
"x": 42,
},
"schedule": Object {
"interval": "1s",
},
"scheduledTaskId": "52125fb0-5895-11ec-ae69-bb65d1a71b72",
"tags": Array [],
"throttle": null,
"updatedAt": 2021-12-11T16:59:50.152Z,
"updatedBy": "2889684073",
}
`);
expect(http.get).toHaveBeenCalledWith(`/api/alerting/rule/${apiRule.id}`);
});
});
function getApiRuleType() {
return {
id: '.index-threshold',
name: 'Index threshold',
producer: 'stackAlerts',
enabled_in_license: true,
recovery_action_group: {
id: 'recovered',
name: 'Recovered',
},
action_groups: [
{
id: 'default',
name: 'Threshold met',
},
],
default_action_group_id: 'default',
minimum_license_required: 'basic',
is_exportable: true,
rule_task_timeout: '5m',
action_variables: {
context: [
{
name: 'message',
description: 'A pre-constructed message for the alert.',
},
],
state: [
{
name: 'example',
description: 'Example state variable',
},
],
params: [
{
name: 'threshold',
description:
'An array of values to use as the threshold; "between" and "notBetween" require two values, the others require one.',
},
],
},
authorized_consumers: {
alerts: {
read: true,
all: true,
},
},
};
}
function getRuleType(): RuleType {
return {
id: '.index-threshold',
name: 'Index threshold',
producer: 'stackAlerts',
enabledInLicense: true,
recoveryActionGroup: {
id: 'recovered',
name: 'Recovered',
},
actionGroups: [
{
id: 'default',
name: 'Threshold met',
},
],
defaultActionGroupId: 'default',
minimumLicenseRequired: 'basic',
isExportable: true,
ruleTaskTimeout: '5m',
actionVariables: {
context: [
{
name: 'message',
description: 'A pre-constructed message for the alert.',
},
],
state: [
{
name: 'example',
description: 'Example state variable',
},
],
params: [
{
name: 'threshold',
description:
'An array of values to use as the threshold; "between" and "notBetween" require two values, the others require one.',
},
],
},
authorizedConsumers: {
alerts: {
read: true,
all: true,
},
},
};
}
const DateNow = Date.now();
const RuleCreateDate = new Date(DateNow - 2000);
const RuleUpdateDate = new Date(DateNow - 1000);
const RuleExecuteDate = new Date(DateNow);
function getApiRule() {
return {
id: '3d534c70-582b-11ec-8995-2b1578a3bc5d',
notify_when: 'onActiveAlert',
rule_type_id: '.index-threshold',
name: 'stressing index-threshold 37/200',
consumer: 'alerts',
tags: [],
enabled: true,
throttle: null,
api_key: null,
api_key_owner: '2889684073',
created_by: 'elastic',
updated_by: '2889684073',
mute_all: false,
muted_alert_ids: [],
schedule: {
interval: '1s',
},
actions: [
{
connector_type_id: '.server-log',
params: {
message: 'alert 37: {{context.message}}',
},
group: 'threshold met',
id: '3619a0d0-582b-11ec-8995-2b1578a3bc5d',
},
],
params: { x: 42 },
updated_at: RuleUpdateDate.toISOString(),
created_at: RuleCreateDate.toISOString(),
scheduled_task_id: '52125fb0-5895-11ec-ae69-bb65d1a71b72',
execution_status: {
status: 'ok',
last_execution_date: RuleExecuteDate.toISOString(),
last_duration: 1194,
},
};
}
function getRule(): Alert<{ x: number }> {
return {
id: '3d534c70-582b-11ec-8995-2b1578a3bc5d',
notifyWhen: 'onActiveAlert',
alertTypeId: '.index-threshold',
name: 'stressing index-threshold 37/200',
consumer: 'alerts',
tags: [],
enabled: true,
throttle: null,
apiKey: null,
apiKeyOwner: '2889684073',
createdBy: 'elastic',
updatedBy: '2889684073',
muteAll: false,
mutedInstanceIds: [],
schedule: {
interval: '1s',
},
actions: [
{
actionTypeId: '.server-log',
params: {
message: 'alert 37: {{context.message}}',
},
group: 'threshold met',
id: '3619a0d0-582b-11ec-8995-2b1578a3bc5d',
},
],
params: { x: 42 },
updatedAt: RuleUpdateDate,
createdAt: RuleCreateDate,
scheduledTaskId: '52125fb0-5895-11ec-ae69-bb65d1a71b72',
executionStatus: {
status: 'ok',
lastExecutionDate: RuleExecuteDate,
lastDuration: 1194,
},
};
}

View file

@ -6,11 +6,16 @@
*/
import { HttpSetup } from 'kibana/public';
import { LEGACY_BASE_ALERT_API_PATH } from '../common';
import { BASE_ALERTING_API_PATH } from '../common';
import type { Alert, RuleType } from '../common';
import { AsApiContract } from '../../actions/common';
import { transformAlert, transformRuleType, ApiAlert } from './lib/common_transformations';
export async function loadAlertTypes({ http }: { http: HttpSetup }): Promise<RuleType[]> {
return await http.get(`${LEGACY_BASE_ALERT_API_PATH}/list_alert_types`);
const res = await http.get<Array<AsApiContract<RuleType>>>(
`${BASE_ALERTING_API_PATH}/rule_types`
);
return res.map((ruleType) => transformRuleType(ruleType));
}
export async function loadAlertType({
@ -20,10 +25,8 @@ export async function loadAlertType({
http: HttpSetup;
id: RuleType['id'];
}): Promise<RuleType | undefined> {
const alertTypes = (await http.get(
`${LEGACY_BASE_ALERT_API_PATH}/list_alert_types`
)) as RuleType[];
return alertTypes.find((type) => type.id === id);
const ruleTypes = await loadAlertTypes({ http });
return ruleTypes.find((type) => type.id === id);
}
export async function loadAlert({
@ -33,5 +36,6 @@ export async function loadAlert({
http: HttpSetup;
alertId: string;
}): Promise<Alert> {
return await http.get(`${LEGACY_BASE_ALERT_API_PATH}/alert/${alertId}`);
const res = await http.get<ApiAlert>(`${BASE_ALERTING_API_PATH}/rule/${alertId}`);
return transformAlert(res);
}

View file

@ -16,11 +16,17 @@ const mockAlertType = (id: string): RuleType => ({
name: id,
actionGroups: [],
recoveryActionGroup: RecoveredActionGroup,
actionVariables: [],
actionVariables: {
context: [],
state: [],
params: [],
},
defaultActionGroupId: 'default',
producer: 'alerts',
minimumLicenseRequired: 'basic',
isExportable: true,
enabledInLicense: true,
authorizedConsumers: { foo: { read: true, all: true } },
});
describe('AlertNavigationRegistry', () => {

View file

@ -0,0 +1,198 @@
/*
* 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 { ApiAlert, transformAlert } from './common_transformations';
import { AlertExecutionStatusErrorReasons } from '../../common';
beforeEach(() => jest.resetAllMocks());
const dateFixed = Date.parse('2021-12-15T12:34:56.789Z');
const dateCreated = new Date(dateFixed - 2000);
const dateUpdated = new Date(dateFixed - 1000);
const dateExecuted = new Date(dateFixed);
describe('common_transformations', () => {
test('transformAlert() with all optional fields', () => {
const apiAlert: ApiAlert = {
id: 'some-id',
name: 'some-name',
enabled: true,
tags: ['tag-1', 'tag-2'],
rule_type_id: 'some-rule-type',
consumer: 'some-consumer',
schedule: { interval: '1s' },
actions: [
{
connector_type_id: 'some-connector-type-id',
group: 'some group',
id: 'some-connector-id',
params: { foo: 'car', bar: [1, 2, 3] },
},
],
params: { bar: 'foo', numbers: { 1: [2, 3] } } as never,
scheduled_task_id: 'some-task-id',
created_by: 'created-by-user',
updated_by: null,
created_at: dateCreated.toISOString(),
updated_at: dateUpdated.toISOString(),
api_key: 'some-api-key',
api_key_owner: 'api-key-user',
throttle: '2s',
notify_when: 'onActiveAlert',
mute_all: false,
muted_alert_ids: ['bob', 'jim'],
execution_status: {
last_execution_date: dateExecuted.toISOString(),
last_duration: 42,
status: 'error',
error: {
reason: AlertExecutionStatusErrorReasons.Unknown,
message: 'this is just a test',
},
},
};
expect(transformAlert(apiAlert)).toMatchInlineSnapshot(`
Object {
"actions": Array [
Object {
"actionTypeId": "some-connector-type-id",
"group": "some group",
"id": "some-connector-id",
"params": Object {
"bar": Array [
1,
2,
3,
],
"foo": "car",
},
},
],
"alertTypeId": "some-rule-type",
"apiKey": "some-api-key",
"apiKeyOwner": "api-key-user",
"consumer": "some-consumer",
"createdAt": 2021-12-15T12:34:54.789Z,
"createdBy": "created-by-user",
"enabled": true,
"executionStatus": Object {
"error": Object {
"message": "this is just a test",
"reason": "unknown",
},
"lastDuration": 42,
"lastExecutionDate": 2021-12-15T12:34:56.789Z,
"status": "error",
},
"id": "some-id",
"muteAll": false,
"mutedInstanceIds": Array [
"bob",
"jim",
],
"name": "some-name",
"notifyWhen": "onActiveAlert",
"params": Object {
"bar": "foo",
"numbers": Object {
"1": Array [
2,
3,
],
},
},
"schedule": Object {
"interval": "1s",
},
"scheduledTaskId": "some-task-id",
"tags": Array [
"tag-1",
"tag-2",
],
"throttle": "2s",
"updatedAt": 2021-12-15T12:34:55.789Z,
"updatedBy": null,
}
`);
});
test('transformAlert() with no optional fields', () => {
const apiAlert: ApiAlert = {
id: 'some-id',
name: 'some-name',
enabled: true,
tags: [],
rule_type_id: 'some-rule-type',
consumer: 'some-consumer',
schedule: { interval: '1s' },
actions: [
{
connector_type_id: 'some-connector-type-id',
group: 'some group',
id: 'some-connector-id',
params: {},
},
],
params: {} as never,
created_by: 'created-by-user',
updated_by: null,
created_at: dateCreated.toISOString(),
updated_at: dateUpdated.toISOString(),
api_key: 'some-api-key',
api_key_owner: 'api-key-user',
throttle: '2s',
notify_when: 'onActiveAlert',
mute_all: false,
muted_alert_ids: ['bob', 'jim'],
execution_status: {
last_execution_date: dateExecuted.toISOString(),
status: 'error',
},
};
expect(transformAlert(apiAlert)).toMatchInlineSnapshot(`
Object {
"actions": Array [
Object {
"actionTypeId": "some-connector-type-id",
"group": "some group",
"id": "some-connector-id",
"params": Object {},
},
],
"alertTypeId": "some-rule-type",
"apiKey": "some-api-key",
"apiKeyOwner": "api-key-user",
"consumer": "some-consumer",
"createdAt": 2021-12-15T12:34:54.789Z,
"createdBy": "created-by-user",
"enabled": true,
"executionStatus": Object {
"lastDuration": undefined,
"lastExecutionDate": 2021-12-15T12:34:56.789Z,
"status": "error",
},
"id": "some-id",
"muteAll": false,
"mutedInstanceIds": Array [
"bob",
"jim",
],
"name": "some-name",
"notifyWhen": "onActiveAlert",
"params": Object {},
"schedule": Object {
"interval": "1s",
},
"scheduledTaskId": undefined,
"tags": Array [],
"throttle": "2s",
"updatedAt": 2021-12-15T12:34:55.789Z,
"updatedBy": null,
}
`);
});
});

View file

@ -0,0 +1,111 @@
/*
* 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 { AlertExecutionStatus, Alert, AlertAction, RuleType } from '../../common';
import { AsApiContract } from '../../../actions/common';
function transformAction(input: AsApiContract<AlertAction>): AlertAction {
const { connector_type_id: actionTypeId, ...rest } = input;
return { actionTypeId, ...rest };
}
// AsApiContract does not deal with object properties that are dates - the
// API version needs to be a string, and the non-API version needs to be a Date
type ApiAlertExecutionStatus = Omit<AsApiContract<AlertExecutionStatus>, 'last_execution_date'> & {
last_execution_date: string;
};
function transformExecutionStatus(input: ApiAlertExecutionStatus): AlertExecutionStatus {
const { last_execution_date: lastExecutionDate, last_duration: lastDuration, ...rest } = input;
return {
lastExecutionDate: new Date(lastExecutionDate),
lastDuration,
...rest,
};
}
// AsApiContract does not deal with object properties that also
// need snake -> camel conversion, Dates, are renamed, etc, so we do by hand
export type ApiAlert = Omit<
AsApiContract<Alert>,
| 'execution_status'
| 'actions'
| 'created_at'
| 'updated_at'
| 'alert_type_id'
| 'muted_instance_ids'
> & {
execution_status: ApiAlertExecutionStatus;
actions: Array<AsApiContract<AlertAction>>;
created_at: string;
updated_at: string;
rule_type_id: string;
muted_alert_ids: string[];
};
export function transformAlert(input: ApiAlert): Alert {
const {
rule_type_id: alertTypeId,
created_by: createdBy,
updated_by: updatedBy,
created_at: createdAt,
updated_at: updatedAt,
api_key: apiKey,
api_key_owner: apiKeyOwner,
notify_when: notifyWhen,
mute_all: muteAll,
muted_alert_ids: mutedInstanceIds,
scheduled_task_id: scheduledTaskId,
execution_status: executionStatusAPI,
actions: actionsAPI,
...rest
} = input;
return {
alertTypeId,
createdBy,
updatedBy,
createdAt: new Date(createdAt),
updatedAt: new Date(updatedAt),
apiKey,
apiKeyOwner,
notifyWhen,
muteAll,
mutedInstanceIds,
executionStatus: transformExecutionStatus(executionStatusAPI),
actions: actionsAPI ? actionsAPI.map((action) => transformAction(action)) : [],
scheduledTaskId,
...rest,
};
}
export function transformRuleType(input: AsApiContract<RuleType>): RuleType {
const {
recovery_action_group: recoveryActionGroup,
action_groups: actionGroups,
default_action_group_id: defaultActionGroupId,
minimum_license_required: minimumLicenseRequired,
action_variables: actionVariables,
rule_task_timeout: ruleTaskTimeout,
is_exportable: isExportable,
authorized_consumers: authorizedConsumers,
enabled_in_license: enabledInLicense,
...rest
} = input;
return {
recoveryActionGroup,
actionGroups,
defaultActionGroupId,
minimumLicenseRequired,
actionVariables,
ruleTaskTimeout,
isExportable,
authorizedConsumers,
enabledInLicense,
...rest,
};
}

View file

@ -12,7 +12,7 @@ import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiSwitch } from '@elastic/eui';
import { CommonAlert } from '../../common/types/alerts';
import { Legacy } from '../legacy_shims';
import { hideBottomBar, showBottomBar } from '../lib/setup_mode';
import { LEGACY_BASE_ALERT_API_PATH } from '../../../alerting/common';
import { BASE_ALERTING_API_PATH } from '../../../alerting/common';
interface Props {
alert: CommonAlert;
@ -28,7 +28,7 @@ export const AlertConfiguration: React.FC<Props> = (props: Props) => {
async function disableAlert() {
setIsSaving(true);
try {
await Legacy.shims.http.post(`${LEGACY_BASE_ALERT_API_PATH}/alert/${alert.id}/_disable`);
await Legacy.shims.http.post(`${BASE_ALERTING_API_PATH}/rule/${alert.id}/_disable`);
} catch (err) {
Legacy.shims.toastNotifications.addDanger({
title: i18n.translate('xpack.monitoring.alerts.panel.disableAlert.errorTitle', {
@ -42,7 +42,7 @@ export const AlertConfiguration: React.FC<Props> = (props: Props) => {
async function enableAlert() {
setIsSaving(true);
try {
await Legacy.shims.http.post(`${LEGACY_BASE_ALERT_API_PATH}/alert/${alert.id}/_enable`);
await Legacy.shims.http.post(`${BASE_ALERTING_API_PATH}/rule/${alert.id}/_enable`);
} catch (err) {
Legacy.shims.toastNotifications.addDanger({
title: i18n.translate('xpack.monitoring.alerts.panel.enableAlert.errorTitle', {
@ -56,7 +56,7 @@ export const AlertConfiguration: React.FC<Props> = (props: Props) => {
async function muteAlert() {
setIsSaving(true);
try {
await Legacy.shims.http.post(`${LEGACY_BASE_ALERT_API_PATH}/alert/${alert.id}/_mute_all`);
await Legacy.shims.http.post(`${BASE_ALERTING_API_PATH}/rule/${alert.id}/_mute_all`);
} catch (err) {
Legacy.shims.toastNotifications.addDanger({
title: i18n.translate('xpack.monitoring.alerts.panel.muteAlert.errorTitle', {
@ -70,7 +70,7 @@ export const AlertConfiguration: React.FC<Props> = (props: Props) => {
async function unmuteAlert() {
setIsSaving(true);
try {
await Legacy.shims.http.post(`${LEGACY_BASE_ALERT_API_PATH}/alert/${alert.id}/_unmute_all`);
await Legacy.shims.http.post(`${BASE_ALERTING_API_PATH}/rule/${alert.id}/_unmute_all`);
} catch (err) {
Legacy.shims.toastNotifications.addDanger({
title: i18n.translate('xpack.monitoring.alerts.panel.ummuteAlert.errorTitle', {