mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[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:
parent
825e35dcba
commit
b4c44a135e
11 changed files with 698 additions and 115 deletions
25
x-pack/examples/alerting_example/common/types.ts
Normal file
25
x-pack/examples/alerting_example/common/types.ts
Normal 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>;
|
||||
}>;
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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'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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
111
x-pack/plugins/alerting/public/lib/common_transformations.ts
Normal file
111
x-pack/plugins/alerting/public/lib/common_transformations.ts
Normal 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,
|
||||
};
|
||||
}
|
|
@ -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', {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue