[Alerting UI] Changed alerting UIs use new rule APIs. (#96018)

* [Alerting UI] Changed alerting UIs use new rule APIs.

* added unit tests

* fixed types

* fixed types

* fixed types

* fixed due to comments
This commit is contained in:
Yuliia Naumenko 2021-04-07 07:54:12 -07:00 committed by GitHub
parent bb109b533c
commit 76ed8dbeab
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
41 changed files with 2033 additions and 1181 deletions

View file

@ -59,8 +59,13 @@ describe('health check', () => {
it('renders children if keys are enabled', async () => {
useKibanaMock().services.http.get = jest.fn().mockResolvedValue({
isSufficientlySecure: true,
hasPermanentEncryptionKey: true,
is_sufficiently_secure: true,
has_permanent_encryption_key: true,
alerting_framework_heath: {
decryption_health: { status: 'ok', timestamp: '2021-04-01T21:29:22.991Z' },
execution_health: { status: 'ok', timestamp: '2021-04-01T21:29:22.991Z' },
read_health: { status: 'ok', timestamp: '2021-04-01T21:29:22.991Z' },
},
isAlertsAvailable: true,
});
const { queryByText } = render(
@ -78,8 +83,13 @@ describe('health check', () => {
test('renders warning if TLS is required', async () => {
useKibanaMock().services.http.get = jest.fn().mockImplementation(async () => ({
isSufficientlySecure: false,
hasPermanentEncryptionKey: true,
is_sufficiently_secure: false,
has_permanent_encryption_key: true,
alerting_framework_heath: {
decryption_health: { status: 'ok', timestamp: '2021-04-01T21:29:22.991Z' },
execution_health: { status: 'ok', timestamp: '2021-04-01T21:29:22.991Z' },
read_health: { status: 'ok', timestamp: '2021-04-01T21:29:22.991Z' },
},
isAlertsAvailable: true,
}));
const { queryAllByText } = render(
@ -110,8 +120,13 @@ describe('health check', () => {
test('renders warning if encryption key is ephemeral', async () => {
useKibanaMock().services.http.get = jest.fn().mockImplementation(async () => ({
isSufficientlySecure: true,
hasPermanentEncryptionKey: false,
is_sufficiently_secure: true,
has_permanent_encryption_key: false,
alerting_framework_heath: {
decryption_health: { status: 'ok', timestamp: '2021-04-01T21:29:22.991Z' },
execution_health: { status: 'ok', timestamp: '2021-04-01T21:29:22.991Z' },
read_health: { status: 'ok', timestamp: '2021-04-01T21:29:22.991Z' },
},
isAlertsAvailable: true,
}));
const { queryByText, queryByRole } = render(
@ -139,8 +154,13 @@ describe('health check', () => {
test('renders warning if encryption key is ephemeral and keys are disabled', async () => {
useKibanaMock().services.http.get = jest.fn().mockImplementation(async () => ({
isSufficientlySecure: false,
hasPermanentEncryptionKey: false,
is_sufficiently_secure: false,
has_permanent_encryption_key: false,
alerting_framework_heath: {
decryption_health: { status: 'ok', timestamp: '2021-04-01T21:29:22.991Z' },
execution_health: { status: 'ok', timestamp: '2021-04-01T21:29:22.991Z' },
read_health: { status: 'ok', timestamp: '2021-04-01T21:29:22.991Z' },
},
isAlertsAvailable: true,
}));

View file

@ -15,12 +15,12 @@ import { i18n } from '@kbn/i18n';
import { EuiEmptyPrompt, EuiCode } from '@elastic/eui';
import { DocLinksStart } from 'kibana/public';
import { alertingFrameworkHealth } from '../lib/alert_api';
import './health_check.scss';
import { useHealthContext } from '../context/health_context';
import { useKibana } from '../../common/lib/kibana';
import { CenterJustifiedSpinner } from './center_justified_spinner';
import { triggersActionsUiHealth } from '../../common/lib/health_api';
import { alertingFrameworkHealth } from '../lib/alert_api';
interface Props {
inFlyout?: boolean;

View file

@ -7,7 +7,10 @@
import { i18n } from '@kbn/i18n';
export { LEGACY_BASE_ALERT_API_PATH } from '../../../../alerting/common';
export {
BASE_ALERTING_API_PATH,
INTERNAL_BASE_ALERTING_API_PATH,
} from '../../../../alerting/common';
export { BASE_ACTION_API_PATH } from '../../../../actions/common';
export type Section = 'connectors' | 'rules';

View file

@ -1,875 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { Alert, AlertType, AlertUpdates } from '../../types';
import { httpServiceMock } from '../../../../../../src/core/public/mocks';
import {
createAlert,
deleteAlerts,
disableAlerts,
enableAlerts,
disableAlert,
enableAlert,
loadAlert,
loadAlertAggregations,
loadAlerts,
loadAlertState,
loadAlertTypes,
muteAlerts,
unmuteAlerts,
muteAlert,
unmuteAlert,
updateAlert,
muteAlertInstance,
unmuteAlertInstance,
alertingFrameworkHealth,
mapFiltersToKql,
} from './alert_api';
import uuid from 'uuid';
import { AlertNotifyWhenType, ALERTS_FEATURE_ID } from '../../../../alerting/common';
const http = httpServiceMock.createStartContract();
beforeEach(() => jest.resetAllMocks());
describe('loadAlertTypes', () => {
test('should call get alert types API', async () => {
const resolvedValue: AlertType[] = [
{
id: 'test',
name: 'Test',
actionVariables: {
context: [{ name: 'var1', description: 'val1' }],
state: [{ name: 'var2', description: 'val2' }],
params: [{ name: 'var3', description: 'val3' }],
},
producer: ALERTS_FEATURE_ID,
actionGroups: [{ id: 'default', name: 'Default' }],
recoveryActionGroup: { id: 'recovered', name: 'Recovered' },
defaultActionGroupId: 'default',
authorizedConsumers: {},
minimumLicenseRequired: 'basic',
enabledInLicense: true,
},
];
http.get.mockResolvedValueOnce(resolvedValue);
const result = await loadAlertTypes({ http });
expect(result).toEqual(resolvedValue);
expect(http.get.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"/api/alerts/list_alert_types",
]
`);
});
});
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);
expect(await loadAlert({ http, alertId })).toEqual(resolvedValue);
expect(http.get).toHaveBeenCalledWith(`/api/alerts/alert/${alertId}`);
});
});
describe('loadAlertState', () => {
test('should call get API with base parameters', async () => {
const alertId = uuid.v4();
const resolvedValue = {
alertTypeState: {
some: 'value',
},
alertInstances: {
first_instance: {},
second_instance: {},
},
};
http.get.mockResolvedValueOnce(resolvedValue);
expect(await loadAlertState({ http, alertId })).toEqual(resolvedValue);
expect(http.get).toHaveBeenCalledWith(`/api/alerts/alert/${alertId}/state`);
});
test('should parse AlertInstances', async () => {
const alertId = uuid.v4();
const resolvedValue = {
alertTypeState: {
some: 'value',
},
alertInstances: {
first_instance: {
state: {},
meta: {
lastScheduledActions: {
group: 'first_group',
date: '2020-02-09T23:15:41.941Z',
},
},
},
},
};
http.get.mockResolvedValueOnce(resolvedValue);
expect(await loadAlertState({ http, alertId })).toEqual({
...resolvedValue,
alertInstances: {
first_instance: {
state: {},
meta: {
lastScheduledActions: {
group: 'first_group',
date: new Date('2020-02-09T23:15:41.941Z'),
},
},
},
},
});
expect(http.get).toHaveBeenCalledWith(`/api/alerts/alert/${alertId}/state`);
});
test('should handle empty response from api', async () => {
const alertId = uuid.v4();
http.get.mockResolvedValueOnce('');
expect(await loadAlertState({ http, alertId })).toEqual({});
expect(http.get).toHaveBeenCalledWith(`/api/alerts/alert/${alertId}/state`);
});
});
describe('loadAlerts', () => {
test('should call find API with base parameters', async () => {
const resolvedValue = {
page: 1,
perPage: 10,
total: 0,
data: [],
};
http.get.mockResolvedValueOnce(resolvedValue);
const result = await loadAlerts({ http, page: { index: 0, size: 10 } });
expect(result).toEqual(resolvedValue);
expect(http.get.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"/api/alerts/_find",
Object {
"query": Object {
"default_search_operator": "AND",
"filter": undefined,
"page": 1,
"per_page": 10,
"search": undefined,
"search_fields": undefined,
"sort_field": "name",
"sort_order": "asc",
},
},
]
`);
});
test('should call find API with searchText', async () => {
const resolvedValue = {
page: 1,
perPage: 10,
total: 0,
data: [],
};
http.get.mockResolvedValueOnce(resolvedValue);
const result = await loadAlerts({ http, searchText: 'apples', page: { index: 0, size: 10 } });
expect(result).toEqual(resolvedValue);
expect(http.get.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"/api/alerts/_find",
Object {
"query": Object {
"default_search_operator": "AND",
"filter": undefined,
"page": 1,
"per_page": 10,
"search": "apples",
"search_fields": "[\\"name\\",\\"tags\\"]",
"sort_field": "name",
"sort_order": "asc",
},
},
]
`);
});
test('should call find API with actionTypesFilter', async () => {
const resolvedValue = {
page: 1,
perPage: 10,
total: 0,
data: [],
};
http.get.mockResolvedValueOnce(resolvedValue);
const result = await loadAlerts({
http,
searchText: 'foo',
page: { index: 0, size: 10 },
});
expect(result).toEqual(resolvedValue);
expect(http.get.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"/api/alerts/_find",
Object {
"query": Object {
"default_search_operator": "AND",
"filter": undefined,
"page": 1,
"per_page": 10,
"search": "foo",
"search_fields": "[\\"name\\",\\"tags\\"]",
"sort_field": "name",
"sort_order": "asc",
},
},
]
`);
});
test('should call find API with typesFilter', async () => {
const resolvedValue = {
page: 1,
perPage: 10,
total: 0,
data: [],
};
http.get.mockResolvedValueOnce(resolvedValue);
const result = await loadAlerts({
http,
typesFilter: ['foo', 'bar'],
page: { index: 0, size: 10 },
});
expect(result).toEqual(resolvedValue);
expect(http.get.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"/api/alerts/_find",
Object {
"query": Object {
"default_search_operator": "AND",
"filter": "alert.attributes.alertTypeId:(foo or bar)",
"page": 1,
"per_page": 10,
"search": undefined,
"search_fields": undefined,
"sort_field": "name",
"sort_order": "asc",
},
},
]
`);
});
test('should call find API with actionTypesFilter and typesFilter', async () => {
const resolvedValue = {
page: 1,
perPage: 10,
total: 0,
data: [],
};
http.get.mockResolvedValueOnce(resolvedValue);
const result = await loadAlerts({
http,
searchText: 'baz',
typesFilter: ['foo', 'bar'],
page: { index: 0, size: 10 },
});
expect(result).toEqual(resolvedValue);
expect(http.get.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"/api/alerts/_find",
Object {
"query": Object {
"default_search_operator": "AND",
"filter": "alert.attributes.alertTypeId:(foo or bar)",
"page": 1,
"per_page": 10,
"search": "baz",
"search_fields": "[\\"name\\",\\"tags\\"]",
"sort_field": "name",
"sort_order": "asc",
},
},
]
`);
});
test('should call find API with searchText and tagsFilter and typesFilter', async () => {
const resolvedValue = {
page: 1,
perPage: 10,
total: 0,
data: [],
};
http.get.mockResolvedValueOnce(resolvedValue);
const result = await loadAlerts({
http,
searchText: 'apples, foo, baz',
typesFilter: ['foo', 'bar'],
page: { index: 0, size: 10 },
});
expect(result).toEqual(resolvedValue);
expect(http.get.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"/api/alerts/_find",
Object {
"query": Object {
"default_search_operator": "AND",
"filter": "alert.attributes.alertTypeId:(foo or bar)",
"page": 1,
"per_page": 10,
"search": "apples, foo, baz",
"search_fields": "[\\"name\\",\\"tags\\"]",
"sort_field": "name",
"sort_order": "asc",
},
},
]
`);
});
});
describe('loadAlertAggregations', () => {
test('should call aggregate API with base parameters', async () => {
const resolvedValue = {
alertExecutionStatus: {
ok: 4,
active: 2,
error: 1,
pending: 1,
unknown: 0,
},
};
http.get.mockResolvedValueOnce(resolvedValue);
const result = await loadAlertAggregations({ http });
expect(result).toEqual(resolvedValue);
expect(http.get.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"/api/alerts/_aggregate",
Object {
"query": Object {
"default_search_operator": "AND",
"filter": undefined,
"search": undefined,
"search_fields": undefined,
},
},
]
`);
});
test('should call aggregate API with searchText', async () => {
const resolvedValue = {
alertExecutionStatus: {
ok: 4,
active: 2,
error: 1,
pending: 1,
unknown: 0,
},
};
http.get.mockResolvedValueOnce(resolvedValue);
const result = await loadAlertAggregations({ http, searchText: 'apples' });
expect(result).toEqual(resolvedValue);
expect(http.get.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"/api/alerts/_aggregate",
Object {
"query": Object {
"default_search_operator": "AND",
"filter": undefined,
"search": "apples",
"search_fields": "[\\"name\\",\\"tags\\"]",
},
},
]
`);
});
test('should call aggregate API with actionTypesFilter', async () => {
const resolvedValue = {
alertExecutionStatus: {
ok: 4,
active: 2,
error: 1,
pending: 1,
unknown: 0,
},
};
http.get.mockResolvedValueOnce(resolvedValue);
const result = await loadAlertAggregations({
http,
searchText: 'foo',
actionTypesFilter: ['action', 'type'],
});
expect(result).toEqual(resolvedValue);
expect(http.get.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"/api/alerts/_aggregate",
Object {
"query": Object {
"default_search_operator": "AND",
"filter": "(alert.attributes.actions:{ actionTypeId:action } OR alert.attributes.actions:{ actionTypeId:type })",
"search": "foo",
"search_fields": "[\\"name\\",\\"tags\\"]",
},
},
]
`);
});
test('should call aggregate API with typesFilter', async () => {
const resolvedValue = {
alertExecutionStatus: {
ok: 4,
active: 2,
error: 1,
pending: 1,
unknown: 0,
},
};
http.get.mockResolvedValueOnce(resolvedValue);
const result = await loadAlertAggregations({
http,
typesFilter: ['foo', 'bar'],
});
expect(result).toEqual(resolvedValue);
expect(http.get.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"/api/alerts/_aggregate",
Object {
"query": Object {
"default_search_operator": "AND",
"filter": "alert.attributes.alertTypeId:(foo or bar)",
"search": undefined,
"search_fields": undefined,
},
},
]
`);
});
test('should call aggregate API with actionTypesFilter and typesFilter', async () => {
const resolvedValue = {
alertExecutionStatus: {
ok: 4,
active: 2,
error: 1,
pending: 1,
unknown: 0,
},
};
http.get.mockResolvedValueOnce(resolvedValue);
const result = await loadAlertAggregations({
http,
searchText: 'baz',
actionTypesFilter: ['action', 'type'],
typesFilter: ['foo', 'bar'],
});
expect(result).toEqual(resolvedValue);
expect(http.get.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"/api/alerts/_aggregate",
Object {
"query": Object {
"default_search_operator": "AND",
"filter": "alert.attributes.alertTypeId:(foo or bar) and (alert.attributes.actions:{ actionTypeId:action } OR alert.attributes.actions:{ actionTypeId:type })",
"search": "baz",
"search_fields": "[\\"name\\",\\"tags\\"]",
},
},
]
`);
});
});
describe('deleteAlerts', () => {
test('should call delete API for each alert', async () => {
const ids = ['1', '2', '3'];
const result = await deleteAlerts({ http, ids });
expect(result).toEqual({ errors: [], successes: [undefined, undefined, undefined] });
expect(http.delete.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
"/api/alerts/alert/1",
],
Array [
"/api/alerts/alert/2",
],
Array [
"/api/alerts/alert/3",
],
]
`);
});
});
describe('createAlert', () => {
test('should call create alert API', async () => {
const alertToCreate: AlertUpdates = {
name: 'test',
consumer: 'alerts',
tags: ['foo'],
enabled: true,
alertTypeId: 'test',
schedule: {
interval: '1m',
},
actions: [],
params: {},
throttle: null,
notifyWhen: 'onActionGroupChange' as AlertNotifyWhenType,
createdAt: new Date('1970-01-01T00:00:00.000Z'),
updatedAt: new Date('1970-01-01T00:00:00.000Z'),
apiKeyOwner: null,
createdBy: null,
updatedBy: null,
muteAll: false,
mutedInstanceIds: [],
};
const resolvedValue = {
...alertToCreate,
id: '123',
createdBy: null,
updatedBy: null,
muteAll: false,
mutedInstanceIds: [],
executionStatus: {
status: 'unknown',
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
},
};
http.post.mockResolvedValueOnce(resolvedValue);
const result = await createAlert({ http, alert: alertToCreate });
expect(result).toEqual(resolvedValue);
expect(http.post.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"/api/alerts/alert",
Object {
"body": "{\\"name\\":\\"test\\",\\"consumer\\":\\"alerts\\",\\"tags\\":[\\"foo\\"],\\"enabled\\":true,\\"alertTypeId\\":\\"test\\",\\"schedule\\":{\\"interval\\":\\"1m\\"},\\"actions\\":[],\\"params\\":{},\\"throttle\\":null,\\"notifyWhen\\":\\"onActionGroupChange\\",\\"createdAt\\":\\"1970-01-01T00:00:00.000Z\\",\\"updatedAt\\":\\"1970-01-01T00:00:00.000Z\\",\\"apiKeyOwner\\":null,\\"createdBy\\":null,\\"updatedBy\\":null,\\"muteAll\\":false,\\"mutedInstanceIds\\":[]}",
},
]
`);
});
});
describe('updateAlert', () => {
test('should call alert update API', async () => {
const alertToUpdate = {
throttle: '1m',
consumer: 'alerts',
name: 'test',
tags: ['foo'],
schedule: {
interval: '1m',
},
params: {},
actions: [],
createdAt: new Date('1970-01-01T00:00:00.000Z'),
updatedAt: new Date('1970-01-01T00:00:00.000Z'),
apiKey: null,
apiKeyOwner: null,
notifyWhen: 'onThrottleInterval' as AlertNotifyWhenType,
};
const resolvedValue: Alert = {
...alertToUpdate,
id: '123',
enabled: true,
alertTypeId: 'test',
createdBy: null,
updatedBy: null,
muteAll: false,
mutedInstanceIds: [],
executionStatus: {
status: 'unknown',
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
},
};
http.put.mockResolvedValueOnce(resolvedValue);
const result = await updateAlert({ http, id: '123', alert: alertToUpdate });
expect(result).toEqual(resolvedValue);
expect(http.put.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"/api/alerts/alert/123",
Object {
"body": "{\\"throttle\\":\\"1m\\",\\"name\\":\\"test\\",\\"tags\\":[\\"foo\\"],\\"schedule\\":{\\"interval\\":\\"1m\\"},\\"params\\":{},\\"actions\\":[],\\"notifyWhen\\":\\"onThrottleInterval\\"}",
},
]
`);
});
});
describe('enableAlert', () => {
test('should call enable alert API', async () => {
const result = await enableAlert({ http, id: '1' });
expect(result).toEqual(undefined);
expect(http.post.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
"/api/alerts/alert/1/_enable",
],
]
`);
});
});
describe('disableAlert', () => {
test('should call disable alert API', async () => {
const result = await disableAlert({ http, id: '1' });
expect(result).toEqual(undefined);
expect(http.post.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
"/api/alerts/alert/1/_disable",
],
]
`);
});
});
describe('muteAlertInstance', () => {
test('should call mute instance alert API', async () => {
const result = await muteAlertInstance({ http, id: '1', instanceId: '123' });
expect(result).toEqual(undefined);
expect(http.post.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
"/api/alerts/alert/1/alert_instance/123/_mute",
],
]
`);
});
});
describe('unmuteAlertInstance', () => {
test('should call mute instance alert API', async () => {
const result = await unmuteAlertInstance({ http, id: '1', instanceId: '123' });
expect(result).toEqual(undefined);
expect(http.post.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
"/api/alerts/alert/1/alert_instance/123/_unmute",
],
]
`);
});
});
describe('muteAlert', () => {
test('should call mute alert API', async () => {
const result = await muteAlert({ http, id: '1' });
expect(result).toEqual(undefined);
expect(http.post.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
"/api/alerts/alert/1/_mute_all",
],
]
`);
});
});
describe('unmuteAlert', () => {
test('should call unmute alert API', async () => {
const result = await unmuteAlert({ http, id: '1' });
expect(result).toEqual(undefined);
expect(http.post.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
"/api/alerts/alert/1/_unmute_all",
],
]
`);
});
});
describe('enableAlerts', () => {
test('should call enable alert API per alert', async () => {
const ids = ['1', '2', '3'];
const result = await enableAlerts({ http, ids });
expect(result).toEqual(undefined);
expect(http.post.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
"/api/alerts/alert/1/_enable",
],
Array [
"/api/alerts/alert/2/_enable",
],
Array [
"/api/alerts/alert/3/_enable",
],
]
`);
});
});
describe('disableAlerts', () => {
test('should call disable alert API per alert', async () => {
const ids = ['1', '2', '3'];
const result = await disableAlerts({ http, ids });
expect(result).toEqual(undefined);
expect(http.post.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
"/api/alerts/alert/1/_disable",
],
Array [
"/api/alerts/alert/2/_disable",
],
Array [
"/api/alerts/alert/3/_disable",
],
]
`);
});
});
describe('muteAlerts', () => {
test('should call mute alert API per alert', async () => {
const ids = ['1', '2', '3'];
const result = await muteAlerts({ http, ids });
expect(result).toEqual(undefined);
expect(http.post.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
"/api/alerts/alert/1/_mute_all",
],
Array [
"/api/alerts/alert/2/_mute_all",
],
Array [
"/api/alerts/alert/3/_mute_all",
],
]
`);
});
});
describe('unmuteAlerts', () => {
test('should call unmute alert API per alert', async () => {
const ids = ['1', '2', '3'];
const result = await unmuteAlerts({ http, ids });
expect(result).toEqual(undefined);
expect(http.post.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
"/api/alerts/alert/1/_unmute_all",
],
Array [
"/api/alerts/alert/2/_unmute_all",
],
Array [
"/api/alerts/alert/3/_unmute_all",
],
]
`);
});
});
describe('alertingFrameworkHealth', () => {
test('should call alertingFrameworkHealth API', async () => {
const result = await alertingFrameworkHealth({ http });
expect(result).toEqual(undefined);
expect(http.get.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
"/api/alerts/_health",
],
]
`);
});
});
describe('mapFiltersToKql', () => {
test('should handle no filters', () => {
expect(mapFiltersToKql({})).toEqual([]);
});
test('should handle typesFilter', () => {
expect(
mapFiltersToKql({
typesFilter: ['type', 'filter'],
})
).toEqual(['alert.attributes.alertTypeId:(type or filter)']);
});
test('should handle actionTypesFilter', () => {
expect(
mapFiltersToKql({
actionTypesFilter: ['action', 'types', 'filter'],
})
).toEqual([
'(alert.attributes.actions:{ actionTypeId:action } OR alert.attributes.actions:{ actionTypeId:types } OR alert.attributes.actions:{ actionTypeId:filter })',
]);
});
test('should handle alertStatusesFilter', () => {
expect(
mapFiltersToKql({
alertStatusesFilter: ['alert', 'statuses', 'filter'],
})
).toEqual(['alert.attributes.executionStatus.status:(alert or statuses or filter)']);
});
test('should handle typesFilter and actionTypesFilter', () => {
expect(
mapFiltersToKql({
typesFilter: ['type', 'filter'],
actionTypesFilter: ['action', 'types', 'filter'],
})
).toEqual([
'alert.attributes.alertTypeId:(type or filter)',
'(alert.attributes.actions:{ actionTypeId:action } OR alert.attributes.actions:{ actionTypeId:types } OR alert.attributes.actions:{ actionTypeId:filter })',
]);
});
test('should handle typesFilter, actionTypesFilter and alertStatusesFilter', () => {
expect(
mapFiltersToKql({
typesFilter: ['type', 'filter'],
actionTypesFilter: ['action', 'types', 'filter'],
alertStatusesFilter: ['alert', 'statuses', 'filter'],
})
).toEqual([
'alert.attributes.alertTypeId:(type or filter)',
'(alert.attributes.actions:{ actionTypeId:action } OR alert.attributes.actions:{ actionTypeId:types } OR alert.attributes.actions:{ actionTypeId:filter })',
'alert.attributes.executionStatus.status:(alert or statuses or filter)',
]);
});
});

View file

@ -1,296 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { HttpSetup } from 'kibana/public';
import { Errors, identity } from 'io-ts';
import { pipe } from 'fp-ts/lib/pipeable';
import { fold } from 'fp-ts/lib/Either';
import { pick } from 'lodash';
import { alertStateSchema, AlertingFrameworkHealth } from '../../../../alerting/common';
import { LEGACY_BASE_ALERT_API_PATH } from '../constants';
import {
Alert,
AlertAggregations,
AlertType,
AlertUpdates,
AlertTaskState,
AlertInstanceSummary,
Pagination,
Sorting,
} from '../../types';
export async function loadAlertTypes({ http }: { http: HttpSetup }): Promise<AlertType[]> {
return await http.get(`${LEGACY_BASE_ALERT_API_PATH}/list_alert_types`);
}
export async function loadAlert({
http,
alertId,
}: {
http: HttpSetup;
alertId: string;
}): Promise<Alert> {
return await http.get(`${LEGACY_BASE_ALERT_API_PATH}/alert/${alertId}`);
}
type EmptyHttpResponse = '';
export async function loadAlertState({
http,
alertId,
}: {
http: HttpSetup;
alertId: string;
}): Promise<AlertTaskState> {
return await http
.get(`${LEGACY_BASE_ALERT_API_PATH}/alert/${alertId}/state`)
.then((state: AlertTaskState | EmptyHttpResponse) => (state ? state : {}))
.then((state: AlertTaskState) => {
return pipe(
alertStateSchema.decode(state),
fold((e: Errors) => {
throw new Error(`Alert "${alertId}" has invalid state`);
}, identity)
);
});
}
export async function loadAlertInstanceSummary({
http,
alertId,
}: {
http: HttpSetup;
alertId: string;
}): Promise<AlertInstanceSummary> {
return await http.get(`${LEGACY_BASE_ALERT_API_PATH}/alert/${alertId}/_instance_summary`);
}
export const mapFiltersToKql = ({
typesFilter,
actionTypesFilter,
alertStatusesFilter,
}: {
typesFilter?: string[];
actionTypesFilter?: string[];
alertStatusesFilter?: string[];
}): string[] => {
const filters = [];
if (typesFilter && typesFilter.length) {
filters.push(`alert.attributes.alertTypeId:(${typesFilter.join(' or ')})`);
}
if (actionTypesFilter && actionTypesFilter.length) {
filters.push(
[
'(',
actionTypesFilter
.map((id) => `alert.attributes.actions:{ actionTypeId:${id} }`)
.join(' OR '),
')',
].join('')
);
}
if (alertStatusesFilter && alertStatusesFilter.length) {
filters.push(`alert.attributes.executionStatus.status:(${alertStatusesFilter.join(' or ')})`);
}
return filters;
};
export async function loadAlerts({
http,
page,
searchText,
typesFilter,
actionTypesFilter,
alertStatusesFilter,
sort = { field: 'name', direction: 'asc' },
}: {
http: HttpSetup;
page: Pagination;
searchText?: string;
typesFilter?: string[];
actionTypesFilter?: string[];
alertStatusesFilter?: string[];
sort?: Sorting;
}): Promise<{
page: number;
perPage: number;
total: number;
data: Alert[];
}> {
const filters = mapFiltersToKql({ typesFilter, actionTypesFilter, alertStatusesFilter });
return await http.get(`${LEGACY_BASE_ALERT_API_PATH}/_find`, {
query: {
page: page.index + 1,
per_page: page.size,
search_fields: searchText ? JSON.stringify(['name', 'tags']) : undefined,
search: searchText,
filter: filters.length ? filters.join(' and ') : undefined,
default_search_operator: 'AND',
sort_field: sort.field,
sort_order: sort.direction,
},
});
}
export async function loadAlertAggregations({
http,
searchText,
typesFilter,
actionTypesFilter,
alertStatusesFilter,
}: {
http: HttpSetup;
searchText?: string;
typesFilter?: string[];
actionTypesFilter?: string[];
alertStatusesFilter?: string[];
}): Promise<AlertAggregations> {
const filters = mapFiltersToKql({ typesFilter, actionTypesFilter, alertStatusesFilter });
return await http.get(`${LEGACY_BASE_ALERT_API_PATH}/_aggregate`, {
query: {
search_fields: searchText ? JSON.stringify(['name', 'tags']) : undefined,
search: searchText,
filter: filters.length ? filters.join(' and ') : undefined,
default_search_operator: 'AND',
},
});
}
export async function deleteAlerts({
ids,
http,
}: {
ids: string[];
http: HttpSetup;
}): Promise<{ successes: string[]; errors: string[] }> {
const successes: string[] = [];
const errors: string[] = [];
await Promise.all(ids.map((id) => http.delete(`${LEGACY_BASE_ALERT_API_PATH}/alert/${id}`))).then(
function (fulfilled) {
successes.push(...fulfilled);
},
function (rejected) {
errors.push(...rejected);
}
);
return { successes, errors };
}
export async function createAlert({
http,
alert,
}: {
http: HttpSetup;
alert: Omit<
AlertUpdates,
'createdBy' | 'updatedBy' | 'muteAll' | 'mutedInstanceIds' | 'executionStatus'
>;
}): Promise<Alert> {
return await http.post(`${LEGACY_BASE_ALERT_API_PATH}/alert`, {
body: JSON.stringify(alert),
});
}
export async function updateAlert({
http,
alert,
id,
}: {
http: HttpSetup;
alert: Pick<
AlertUpdates,
'throttle' | 'name' | 'tags' | 'schedule' | 'params' | 'actions' | 'notifyWhen'
>;
id: string;
}): Promise<Alert> {
return await http.put(`${LEGACY_BASE_ALERT_API_PATH}/alert/${id}`, {
body: JSON.stringify(
pick(alert, ['throttle', 'name', 'tags', 'schedule', 'params', 'actions', 'notifyWhen'])
),
});
}
export async function enableAlert({ id, http }: { id: string; http: HttpSetup }): Promise<void> {
await http.post(`${LEGACY_BASE_ALERT_API_PATH}/alert/${id}/_enable`);
}
export async function enableAlerts({
ids,
http,
}: {
ids: string[];
http: HttpSetup;
}): Promise<void> {
await Promise.all(ids.map((id) => enableAlert({ id, http })));
}
export async function disableAlert({ id, http }: { id: string; http: HttpSetup }): Promise<void> {
await http.post(`${LEGACY_BASE_ALERT_API_PATH}/alert/${id}/_disable`);
}
export async function disableAlerts({
ids,
http,
}: {
ids: string[];
http: HttpSetup;
}): Promise<void> {
await Promise.all(ids.map((id) => disableAlert({ id, http })));
}
export async function muteAlertInstance({
id,
instanceId,
http,
}: {
id: string;
instanceId: string;
http: HttpSetup;
}): Promise<void> {
await http.post(`${LEGACY_BASE_ALERT_API_PATH}/alert/${id}/alert_instance/${instanceId}/_mute`);
}
export async function unmuteAlertInstance({
id,
instanceId,
http,
}: {
id: string;
instanceId: string;
http: HttpSetup;
}): Promise<void> {
await http.post(`${LEGACY_BASE_ALERT_API_PATH}/alert/${id}/alert_instance/${instanceId}/_unmute`);
}
export async function muteAlert({ id, http }: { id: string; http: HttpSetup }): Promise<void> {
await http.post(`${LEGACY_BASE_ALERT_API_PATH}/alert/${id}/_mute_all`);
}
export async function muteAlerts({ ids, http }: { ids: string[]; http: HttpSetup }): Promise<void> {
await Promise.all(ids.map((id) => muteAlert({ http, id })));
}
export async function unmuteAlert({ id, http }: { id: string; http: HttpSetup }): Promise<void> {
await http.post(`${LEGACY_BASE_ALERT_API_PATH}/alert/${id}/_unmute_all`);
}
export async function unmuteAlerts({
ids,
http,
}: {
ids: string[];
http: HttpSetup;
}): Promise<void> {
await Promise.all(ids.map((id) => unmuteAlert({ id, http })));
}
export async function alertingFrameworkHealth({
http,
}: {
http: HttpSetup;
}): Promise<AlertingFrameworkHealth> {
return await http.get(`${LEGACY_BASE_ALERT_API_PATH}/_health`);
}

View file

@ -0,0 +1,212 @@
/*
* 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 { httpServiceMock } from '../../../../../../../src/core/public/mocks';
import { loadAlertAggregations } from './aggregate';
const http = httpServiceMock.createStartContract();
describe('loadAlertAggregations', () => {
beforeEach(() => jest.resetAllMocks());
test('should call aggregate API with base parameters', async () => {
const resolvedValue = {
rule_execution_status: {
ok: 4,
active: 2,
error: 1,
pending: 1,
unknown: 0,
},
};
http.get.mockResolvedValueOnce(resolvedValue);
const result = await loadAlertAggregations({ http });
expect(result).toEqual({
alertExecutionStatus: {
ok: 4,
active: 2,
error: 1,
pending: 1,
unknown: 0,
},
});
expect(http.get.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"/internal/alerting/rules/_aggregate",
Object {
"query": Object {
"default_search_operator": "AND",
"filter": undefined,
"search": undefined,
"search_fields": undefined,
},
},
]
`);
});
test('should call aggregate API with searchText', async () => {
const resolvedValue = {
rule_execution_status: {
ok: 4,
active: 2,
error: 1,
pending: 1,
unknown: 0,
},
};
http.get.mockResolvedValueOnce(resolvedValue);
const result = await loadAlertAggregations({ http, searchText: 'apples' });
expect(result).toEqual({
alertExecutionStatus: {
ok: 4,
active: 2,
error: 1,
pending: 1,
unknown: 0,
},
});
expect(http.get.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"/internal/alerting/rules/_aggregate",
Object {
"query": Object {
"default_search_operator": "AND",
"filter": undefined,
"search": "apples",
"search_fields": "[\\"name\\",\\"tags\\"]",
},
},
]
`);
});
test('should call aggregate API with actionTypesFilter', async () => {
const resolvedValue = {
rule_execution_status: {
ok: 4,
active: 2,
error: 1,
pending: 1,
unknown: 0,
},
};
http.get.mockResolvedValueOnce(resolvedValue);
const result = await loadAlertAggregations({
http,
searchText: 'foo',
actionTypesFilter: ['action', 'type'],
});
expect(result).toEqual({
alertExecutionStatus: {
ok: 4,
active: 2,
error: 1,
pending: 1,
unknown: 0,
},
});
expect(http.get.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"/internal/alerting/rules/_aggregate",
Object {
"query": Object {
"default_search_operator": "AND",
"filter": "(alert.attributes.actions:{ actionTypeId:action } OR alert.attributes.actions:{ actionTypeId:type })",
"search": "foo",
"search_fields": "[\\"name\\",\\"tags\\"]",
},
},
]
`);
});
test('should call aggregate API with typesFilter', async () => {
const resolvedValue = {
rule_execution_status: {
ok: 4,
active: 2,
error: 1,
pending: 1,
unknown: 0,
},
};
http.get.mockResolvedValueOnce(resolvedValue);
const result = await loadAlertAggregations({
http,
typesFilter: ['foo', 'bar'],
});
expect(result).toEqual({
alertExecutionStatus: {
ok: 4,
active: 2,
error: 1,
pending: 1,
unknown: 0,
},
});
expect(http.get.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"/internal/alerting/rules/_aggregate",
Object {
"query": Object {
"default_search_operator": "AND",
"filter": "alert.attributes.alertTypeId:(foo or bar)",
"search": undefined,
"search_fields": undefined,
},
},
]
`);
});
test('should call aggregate API with actionTypesFilter and typesFilter', async () => {
const resolvedValue = {
rule_execution_status: {
ok: 4,
active: 2,
error: 1,
pending: 1,
unknown: 0,
},
};
http.get.mockResolvedValueOnce(resolvedValue);
const result = await loadAlertAggregations({
http,
searchText: 'baz',
actionTypesFilter: ['action', 'type'],
typesFilter: ['foo', 'bar'],
});
expect(result).toEqual({
alertExecutionStatus: {
ok: 4,
active: 2,
error: 1,
pending: 1,
unknown: 0,
},
});
expect(http.get.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"/internal/alerting/rules/_aggregate",
Object {
"query": Object {
"default_search_operator": "AND",
"filter": "alert.attributes.alertTypeId:(foo or bar) and (alert.attributes.actions:{ actionTypeId:action } OR alert.attributes.actions:{ actionTypeId:type })",
"search": "baz",
"search_fields": "[\\"name\\",\\"tags\\"]",
},
},
]
`);
});
});

View file

@ -0,0 +1,44 @@
/*
* 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 { HttpSetup } from 'kibana/public';
import { AlertAggregations } from '../../../types';
import { INTERNAL_BASE_ALERTING_API_PATH } from '../../constants';
import { mapFiltersToKql } from './map_filters_to_kql';
import { RewriteRequestCase } from '../../../../../actions/common';
const rewriteBodyRes: RewriteRequestCase<AlertAggregations> = ({
rule_execution_status: alertExecutionStatus,
...rest
}: any) => ({
...rest,
alertExecutionStatus,
});
export async function loadAlertAggregations({
http,
searchText,
typesFilter,
actionTypesFilter,
alertStatusesFilter,
}: {
http: HttpSetup;
searchText?: string;
typesFilter?: string[];
actionTypesFilter?: string[];
alertStatusesFilter?: string[];
}): Promise<AlertAggregations> {
const filters = mapFiltersToKql({ typesFilter, actionTypesFilter, alertStatusesFilter });
const res = await http.get(`${INTERNAL_BASE_ALERTING_API_PATH}/rules/_aggregate`, {
query: {
search_fields: searchText ? JSON.stringify(['name', 'tags']) : undefined,
search: searchText,
filter: filters.length ? filters.join(' and ') : undefined,
default_search_operator: 'AND',
},
});
return rewriteBodyRes(res);
}

View file

@ -0,0 +1,58 @@
/*
* 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 { httpServiceMock } from '../../../../../../../src/core/public/mocks';
import { AlertInstanceSummary } from '../../../../../alerting/common';
import { loadAlertInstanceSummary } from './alert_summary';
const http = httpServiceMock.createStartContract();
describe('loadAlertInstanceSummary', () => {
test('should call get alert types API', async () => {
const resolvedValue: AlertInstanceSummary = {
instances: {},
consumer: 'alerts',
enabled: true,
errorMessages: [],
id: 'test',
lastRun: '2021-04-01T22:18:27.609Z',
muteAll: false,
name: 'test',
alertTypeId: '.index-threshold',
status: 'OK',
statusEndDate: '2021-04-01T22:19:25.174Z',
statusStartDate: '2021-04-01T21:19:25.174Z',
tags: [],
throttle: null,
};
http.get.mockResolvedValueOnce({
alerts: {},
consumer: 'alerts',
enabled: true,
error_messages: [],
id: 'test',
last_run: '2021-04-01T22:18:27.609Z',
mute_all: false,
name: 'test',
rule_type_id: '.index-threshold',
status: 'OK',
status_end_date: '2021-04-01T22:19:25.174Z',
status_start_date: '2021-04-01T21:19:25.174Z',
tags: [],
throttle: null,
});
const result = await loadAlertInstanceSummary({ http, alertId: 'test' });
expect(result).toEqual(resolvedValue);
expect(http.get.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"/internal/alerting/rule/test/_alert_summary",
]
`);
});
});

View file

@ -0,0 +1,41 @@
/*
* 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 { HttpSetup } from 'kibana/public';
import { INTERNAL_BASE_ALERTING_API_PATH } from '../../constants';
import { AlertInstanceSummary } from '../../../types';
import { RewriteRequestCase } from '../../../../../actions/common';
const rewriteBodyRes: RewriteRequestCase<AlertInstanceSummary> = ({
alerts,
rule_type_id: alertTypeId,
mute_all: muteAll,
status_start_date: statusStartDate,
status_end_date: statusEndDate,
error_messages: errorMessages,
last_run: lastRun,
...rest
}: any) => ({
...rest,
alertTypeId,
muteAll,
statusStartDate,
statusEndDate,
errorMessages,
lastRun,
instances: alerts,
});
export async function loadAlertInstanceSummary({
http,
alertId,
}: {
http: HttpSetup;
alertId: string;
}): Promise<AlertInstanceSummary> {
const res = await http.get(`${INTERNAL_BASE_ALERTING_API_PATH}/rule/${alertId}/_alert_summary`);
return rewriteBodyRes(res);
}

View file

@ -0,0 +1,61 @@
/*
* 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 } from '../../../../../alerting/common';
import { AsApiContract, RewriteRequestCase } from '../../../../../actions/common';
import { Alert, AlertAction } from '../../../types';
const transformAction: RewriteRequestCase<AlertAction> = ({
group,
id,
connector_type_id: actionTypeId,
params,
}) => ({
group,
id,
params,
actionTypeId,
});
const transformExecutionStatus: RewriteRequestCase<AlertExecutionStatus> = ({
last_execution_date: lastExecutionDate,
...rest
}) => ({
lastExecutionDate,
...rest,
});
export const transformAlert: RewriteRequestCase<Alert> = ({
rule_type_id: alertTypeId,
created_by: createdBy,
updated_by: updatedBy,
created_at: createdAt,
updated_at: updatedAt,
api_key_owner: apiKeyOwner,
notify_when: notifyWhen,
mute_all: muteAll,
muted_alert_ids: mutedInstanceIds,
scheduled_task_id: scheduledTaskId,
execution_status: executionStatus,
actions: actions,
...rest
}: any) => ({
alertTypeId,
createdBy,
updatedBy,
createdAt,
updatedAt,
apiKeyOwner,
notifyWhen,
muteAll,
mutedInstanceIds,
executionStatus: executionStatus ? transformExecutionStatus(executionStatus) : undefined,
actions: actions
? actions.map((action: AsApiContract<AlertAction>) => transformAction(action))
: [],
scheduledTaskId,
...rest,
});

View file

@ -0,0 +1,140 @@
/*
* 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 { httpServiceMock } from '../../../../../../../src/core/public/mocks';
import { AlertUpdates } from '../../../types';
import { createAlert } from './create';
const http = httpServiceMock.createStartContract();
describe('createAlert', () => {
beforeEach(() => jest.resetAllMocks());
test('should call create alert API', async () => {
const resolvedValue = {
params: {
aggType: 'count',
termSize: 5,
thresholdComparator: '>',
timeWindowSize: 5,
timeWindowUnit: 'm',
groupBy: 'all',
threshold: [1000],
index: ['.kibana'],
timeField: 'alert.executionStatus.lastExecutionDate',
},
consumer: 'alerts',
schedule: { interval: '1m' },
tags: [],
name: 'test',
rule_type_id: '.index-threshold',
notify_when: 'onActionGroupChange',
actions: [
{
group: 'threshold met',
id: '1',
params: {
level: 'info',
message: 'alert ',
},
connector_type_id: '.server-log',
},
],
scheduled_task_id: '1',
execution_status: { status: 'pending', last_execution_date: '2021-04-01T21:33:13.250Z' },
create_at: '2021-04-01T21:33:13.247Z',
updated_at: '2021-04-01T21:33:13.247Z',
};
const alertToCreate: Omit<
AlertUpdates,
'createdBy' | 'updatedBy' | 'muteAll' | 'mutedInstanceIds' | 'executionStatus'
> = {
params: {
aggType: 'count',
termSize: 5,
thresholdComparator: '>',
timeWindowSize: 5,
timeWindowUnit: 'm',
groupBy: 'all',
threshold: [1000],
index: ['.kibana'],
timeField: 'alert.executionStatus.lastExecutionDate',
},
consumer: 'alerts',
schedule: { interval: '1m' },
tags: [],
name: 'test',
enabled: true,
throttle: null,
alertTypeId: '.index-threshold',
notifyWhen: 'onActionGroupChange',
actions: [
{
group: 'threshold met',
id: '83d4d860-9316-11eb-a145-93ab369a4461',
params: {
level: 'info',
message:
"alert '{{alertName}}' is active for group '{{context.group}}':\n\n- Value: {{context.value}}\n- Conditions Met: {{context.conditions}} over {{params.timeWindowSize}}{{params.timeWindowUnit}}\n- Timestamp: {{context.date}}",
},
actionTypeId: '.server-log',
},
],
createdAt: new Date('2021-04-01T21:33:13.247Z'),
updatedAt: new Date('2021-04-01T21:33:13.247Z'),
apiKeyOwner: '',
};
http.post.mockResolvedValueOnce(resolvedValue);
const result = await createAlert({ http, alert: alertToCreate });
expect(result).toEqual({
actions: [
{
actionTypeId: '.server-log',
group: 'threshold met',
id: '1',
params: {
level: 'info',
message: 'alert ',
},
},
],
alertTypeId: '.index-threshold',
apiKeyOwner: undefined,
consumer: 'alerts',
create_at: '2021-04-01T21:33:13.247Z',
createdAt: undefined,
createdBy: undefined,
executionStatus: {
lastExecutionDate: '2021-04-01T21:33:13.250Z',
status: 'pending',
},
muteAll: undefined,
mutedInstanceIds: undefined,
name: 'test',
notifyWhen: 'onActionGroupChange',
params: {
aggType: 'count',
groupBy: 'all',
index: ['.kibana'],
termSize: 5,
threshold: [1000],
thresholdComparator: '>',
timeField: 'alert.executionStatus.lastExecutionDate',
timeWindowSize: 5,
timeWindowUnit: 'm',
},
schedule: {
interval: '1m',
},
scheduledTaskId: '1',
tags: [],
updatedAt: '2021-04-01T21:33:13.247Z',
updatedBy: undefined,
});
});
});

View file

@ -0,0 +1,44 @@
/*
* 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 { HttpSetup } from 'kibana/public';
import { RewriteResponseCase } from '../../../../../actions/common';
import { Alert, AlertUpdates } from '../../../types';
import { BASE_ALERTING_API_PATH } from '../../constants';
import { transformAlert } from './common_transformations';
type AlertCreateBody = Omit<
AlertUpdates,
'createdBy' | 'updatedBy' | 'muteAll' | 'mutedInstanceIds' | 'executionStatus'
>;
const rewriteBodyRequest: RewriteResponseCase<AlertCreateBody> = ({
alertTypeId,
notifyWhen,
actions,
...res
}): any => ({
...res,
rule_type_id: alertTypeId,
notify_when: notifyWhen,
actions: actions.map(({ group, id, params }) => ({
group,
id,
params,
})),
});
export async function createAlert({
http,
alert,
}: {
http: HttpSetup;
alert: AlertCreateBody;
}): Promise<Alert> {
const res = await http.post(`${BASE_ALERTING_API_PATH}/rule`, {
body: JSON.stringify(rewriteBodyRequest(alert)),
});
return transformAlert(res);
}

View file

@ -0,0 +1,32 @@
/*
* 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 { httpServiceMock } from '../../../../../../../src/core/public/mocks';
import { deleteAlerts } from './delete';
const http = httpServiceMock.createStartContract();
describe('deleteAlerts', () => {
test('should call delete API for each alert', async () => {
const ids = ['1', '2', '3'];
const result = await deleteAlerts({ http, ids });
expect(result).toEqual({ errors: [], successes: [undefined, undefined, undefined] });
expect(http.delete.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
"/api/alerting/rule/1",
],
Array [
"/api/alerting/rule/2",
],
Array [
"/api/alerting/rule/3",
],
]
`);
});
});

View file

@ -0,0 +1,28 @@
/*
* 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 { HttpSetup } from 'kibana/public';
import { BASE_ALERTING_API_PATH } from '../../constants';
export async function deleteAlerts({
ids,
http,
}: {
ids: string[];
http: HttpSetup;
}): Promise<{ successes: string[]; errors: string[] }> {
const successes: string[] = [];
const errors: string[] = [];
await Promise.all(ids.map((id) => http.delete(`${BASE_ALERTING_API_PATH}/rule/${id}`))).then(
function (fulfilled) {
successes.push(...fulfilled);
},
function (rejected) {
errors.push(...rejected);
}
);
return { successes, errors };
}

View file

@ -0,0 +1,47 @@
/*
* 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 { httpServiceMock } from '../../../../../../../src/core/public/mocks';
import { disableAlert, disableAlerts } from './disable';
const http = httpServiceMock.createStartContract();
beforeEach(() => jest.resetAllMocks());
describe('disableAlert', () => {
test('should call disable alert API', async () => {
const result = await disableAlert({ http, id: '1' });
expect(result).toEqual(undefined);
expect(http.post.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
"/api/alerting/rule/1/_disable",
],
]
`);
});
});
describe('disableAlerts', () => {
test('should call disable alert API per alert', async () => {
const ids = ['1', '2', '3'];
const result = await disableAlerts({ http, ids });
expect(result).toEqual(undefined);
expect(http.post.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
"/api/alerting/rule/1/_disable",
],
Array [
"/api/alerting/rule/2/_disable",
],
Array [
"/api/alerting/rule/3/_disable",
],
]
`);
});
});

View file

@ -0,0 +1,22 @@
/*
* 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 { HttpSetup } from 'kibana/public';
import { BASE_ALERTING_API_PATH } from '../../constants';
export async function disableAlert({ id, http }: { id: string; http: HttpSetup }): Promise<void> {
await http.post(`${BASE_ALERTING_API_PATH}/rule/${id}/_disable`);
}
export async function disableAlerts({
ids,
http,
}: {
ids: string[];
http: HttpSetup;
}): Promise<void> {
await Promise.all(ids.map((id) => disableAlert({ id, http })));
}

View file

@ -0,0 +1,47 @@
/*
* 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 { httpServiceMock } from '../../../../../../../src/core/public/mocks';
import { enableAlert, enableAlerts } from './enable';
const http = httpServiceMock.createStartContract();
beforeEach(() => jest.resetAllMocks());
describe('enableAlert', () => {
test('should call enable alert API', async () => {
const result = await enableAlert({ http, id: '1' });
expect(result).toEqual(undefined);
expect(http.post.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
"/api/alerting/rule/1/_enable",
],
]
`);
});
});
describe('enableAlerts', () => {
test('should call enable alert API per alert', async () => {
const ids = ['1', '2', '3'];
const result = await enableAlerts({ http, ids });
expect(result).toEqual(undefined);
expect(http.post.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
"/api/alerting/rule/1/_enable",
],
Array [
"/api/alerting/rule/2/_enable",
],
Array [
"/api/alerting/rule/3/_enable",
],
]
`);
});
});

View file

@ -0,0 +1,22 @@
/*
* 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 { HttpSetup } from 'kibana/public';
import { BASE_ALERTING_API_PATH } from '../../constants';
export async function enableAlert({ id, http }: { id: string; http: HttpSetup }): Promise<void> {
await http.post(`${BASE_ALERTING_API_PATH}/rule/${id}/_enable`);
}
export async function enableAlerts({
ids,
http,
}: {
ids: string[];
http: HttpSetup;
}): Promise<void> {
await Promise.all(ids.map((id) => enableAlert({ id, http })));
}

View file

@ -0,0 +1,99 @@
/*
* 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 { httpServiceMock } from '../../../../../../../src/core/public/mocks';
import { loadAlert } from './get_rule';
import uuid from 'uuid';
const http = httpServiceMock.createStartContract();
describe('loadAlert', () => {
test('should call get API with base parameters', async () => {
const alertId = uuid.v4();
const resolvedValue = {
id: '1',
params: {
aggType: 'count',
termSize: 5,
thresholdComparator: '>',
timeWindowSize: 5,
timeWindowUnit: 'm',
groupBy: 'all',
threshold: [1000],
index: ['.kibana'],
timeField: 'canvas-element.@created',
},
consumer: 'alerts',
schedule: { interval: '1m' },
tags: ['sdfsdf'],
name: 'dfsdfdsf',
enabled: true,
throttle: '1h',
rule_type_id: '.index-threshold',
created_by: 'elastic',
updated_by: 'elastic',
created_at: '2021-04-01T20:29:18.652Z',
updated_at: '2021-04-01T20:33:38.260Z',
api_key_owner: 'elastic',
notify_when: 'onThrottleInterval',
mute_all: false,
muted_alert_ids: [],
scheduled_task_id: '1',
execution_status: { status: 'ok', last_execution_date: '2021-04-01T21:16:46.709Z' },
actions: [
{
group: 'threshold met',
id: '1',
params: { documents: [{ dsfsdf: 1212 }] },
connector_type_id: '.index',
},
],
};
http.get.mockResolvedValueOnce(resolvedValue);
expect(await loadAlert({ http, alertId })).toEqual({
id: '1',
params: {
aggType: 'count',
termSize: 5,
thresholdComparator: '>',
timeWindowSize: 5,
timeWindowUnit: 'm',
groupBy: 'all',
threshold: [1000],
index: ['.kibana'],
timeField: 'canvas-element.@created',
},
consumer: 'alerts',
schedule: { interval: '1m' },
tags: ['sdfsdf'],
name: 'dfsdfdsf',
enabled: true,
throttle: '1h',
alertTypeId: '.index-threshold',
createdBy: 'elastic',
updatedBy: 'elastic',
createdAt: '2021-04-01T20:29:18.652Z',
updatedAt: '2021-04-01T20:33:38.260Z',
apiKeyOwner: 'elastic',
notifyWhen: 'onThrottleInterval',
muteAll: false,
mutedInstanceIds: [],
scheduledTaskId: '1',
executionStatus: { status: 'ok', lastExecutionDate: '2021-04-01T21:16:46.709Z' },
actions: [
{
group: 'threshold met',
id: '1',
params: { documents: [{ dsfsdf: 1212 }] },
actionTypeId: '.index',
},
],
});
expect(http.get).toHaveBeenCalledWith(`/api/alerting/rule/${alertId}`);
});
});

View file

@ -0,0 +1,21 @@
/*
* 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 { HttpSetup } from 'kibana/public';
import { Alert } from '../../../types';
import { BASE_ALERTING_API_PATH } from '../../constants';
import { transformAlert } from './common_transformations';
export async function loadAlert({
http,
alertId,
}: {
http: HttpSetup;
alertId: string;
}): Promise<Alert> {
const res = await http.get(`${BASE_ALERTING_API_PATH}/rule/${alertId}`);
return transformAlert(res);
}

View file

@ -0,0 +1,41 @@
/*
* 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 { httpServiceMock } from '../../../../../../../src/core/public/mocks';
import { alertingFrameworkHealth } from './health';
describe('alertingFrameworkHealth', () => {
const http = httpServiceMock.createStartContract();
test('should call alertingFrameworkHealth API', async () => {
http.get.mockResolvedValueOnce({
is_sufficiently_secure: true,
has_permanent_encryption_key: true,
alerting_framework_heath: {
decryption_health: { status: 'ok', timestamp: '2021-04-01T21:29:22.991Z' },
execution_health: { status: 'ok', timestamp: '2021-04-01T21:29:22.991Z' },
read_health: { status: 'ok', timestamp: '2021-04-01T21:29:22.991Z' },
},
});
const result = await alertingFrameworkHealth({ http });
expect(result).toEqual({
alertingFrameworkHeath: {
decryptionHealth: { status: 'ok', timestamp: '2021-04-01T21:29:22.991Z' },
executionHealth: { status: 'ok', timestamp: '2021-04-01T21:29:22.991Z' },
readHealth: { status: 'ok', timestamp: '2021-04-01T21:29:22.991Z' },
},
hasPermanentEncryptionKey: true,
isSufficientlySecure: true,
});
expect(http.get.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
"/api/alerting/_health",
],
]
`);
});
});

View file

@ -0,0 +1,44 @@
/*
* 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 { HttpSetup } from 'kibana/public';
import { AsApiContract, RewriteRequestCase } from '../../../../../actions/common';
import { AlertingFrameworkHealth, AlertsHealth } from '../../../../../alerting/common';
import { BASE_ALERTING_API_PATH } from '../../constants';
const rewriteAlertingFrameworkHeath: RewriteRequestCase<AlertsHealth> = ({
decryption_health: decryptionHealth,
execution_health: executionHealth,
read_health: readHealth,
...res
}: AsApiContract<AlertsHealth>) => ({
decryptionHealth,
executionHealth,
readHealth,
...res,
});
const rewriteBodyRes: RewriteRequestCase<AlertingFrameworkHealth> = ({
is_sufficiently_secure: isSufficientlySecure,
has_permanent_encryption_key: hasPermanentEncryptionKey,
alerting_framework_heath: alertingFrameworkHeath,
...res
}: AsApiContract<AlertingFrameworkHealth>) => ({
isSufficientlySecure,
hasPermanentEncryptionKey,
alertingFrameworkHeath,
...res,
});
export async function alertingFrameworkHealth({
http,
}: {
http: HttpSetup;
}): Promise<AlertingFrameworkHealth> {
const res = await http.get(`${BASE_ALERTING_API_PATH}/_health`);
const alertingFrameworkHeath = rewriteAlertingFrameworkHeath(res.alerting_framework_heath);
return { ...rewriteBodyRes(res), alertingFrameworkHeath };
}

View file

@ -0,0 +1,24 @@
/*
* 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.
*/
export { alertingFrameworkHealth } from './health';
export { mapFiltersToKql } from './map_filters_to_kql';
export { loadAlertAggregations } from './aggregate';
export { createAlert } from './create';
export { deleteAlerts } from './delete';
export { disableAlert, disableAlerts } from './disable';
export { enableAlert, enableAlerts } from './enable';
export { loadAlert } from './get_rule';
export { loadAlertInstanceSummary } from './alert_summary';
export { muteAlertInstance } from './mute_alert';
export { muteAlert, muteAlerts } from './mute';
export { loadAlertTypes } from './rule_types';
export { loadAlerts } from './rules';
export { loadAlertState } from './state';
export { unmuteAlertInstance } from './unmute_alert';
export { unmuteAlert, unmuteAlerts } from './unmute';
export { updateAlert } from './update';

View file

@ -0,0 +1,68 @@
/*
* 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 { mapFiltersToKql } from './map_filters_to_kql';
describe('mapFiltersToKql', () => {
beforeEach(() => jest.resetAllMocks());
test('should handle no filters', () => {
expect(mapFiltersToKql({})).toEqual([]);
});
test('should handle typesFilter', () => {
expect(
mapFiltersToKql({
typesFilter: ['type', 'filter'],
})
).toEqual(['alert.attributes.alertTypeId:(type or filter)']);
});
test('should handle actionTypesFilter', () => {
expect(
mapFiltersToKql({
actionTypesFilter: ['action', 'types', 'filter'],
})
).toEqual([
'(alert.attributes.actions:{ actionTypeId:action } OR alert.attributes.actions:{ actionTypeId:types } OR alert.attributes.actions:{ actionTypeId:filter })',
]);
});
test('should handle alertStatusesFilter', () => {
expect(
mapFiltersToKql({
alertStatusesFilter: ['alert', 'statuses', 'filter'],
})
).toEqual(['alert.attributes.executionStatus.status:(alert or statuses or filter)']);
});
test('should handle typesFilter and actionTypesFilter', () => {
expect(
mapFiltersToKql({
typesFilter: ['type', 'filter'],
actionTypesFilter: ['action', 'types', 'filter'],
})
).toEqual([
'alert.attributes.alertTypeId:(type or filter)',
'(alert.attributes.actions:{ actionTypeId:action } OR alert.attributes.actions:{ actionTypeId:types } OR alert.attributes.actions:{ actionTypeId:filter })',
]);
});
test('should handle typesFilter, actionTypesFilter and alertStatusesFilter', () => {
expect(
mapFiltersToKql({
typesFilter: ['type', 'filter'],
actionTypesFilter: ['action', 'types', 'filter'],
alertStatusesFilter: ['alert', 'statuses', 'filter'],
})
).toEqual([
'alert.attributes.alertTypeId:(type or filter)',
'(alert.attributes.actions:{ actionTypeId:action } OR alert.attributes.actions:{ actionTypeId:types } OR alert.attributes.actions:{ actionTypeId:filter })',
'alert.attributes.executionStatus.status:(alert or statuses or filter)',
]);
});
});

View file

@ -0,0 +1,36 @@
/*
* 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.
*/
export const mapFiltersToKql = ({
typesFilter,
actionTypesFilter,
alertStatusesFilter,
}: {
typesFilter?: string[];
actionTypesFilter?: string[];
alertStatusesFilter?: string[];
}): string[] => {
const filters = [];
if (typesFilter && typesFilter.length) {
filters.push(`alert.attributes.alertTypeId:(${typesFilter.join(' or ')})`);
}
if (actionTypesFilter && actionTypesFilter.length) {
filters.push(
[
'(',
actionTypesFilter
.map((id) => `alert.attributes.actions:{ actionTypeId:${id} }`)
.join(' OR '),
')',
].join('')
);
}
if (alertStatusesFilter && alertStatusesFilter.length) {
filters.push(`alert.attributes.executionStatus.status:(${alertStatusesFilter.join(' or ')})`);
}
return filters;
};

View file

@ -0,0 +1,47 @@
/*
* 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 { httpServiceMock } from '../../../../../../../src/core/public/mocks';
import { muteAlert, muteAlerts } from './mute';
const http = httpServiceMock.createStartContract();
beforeEach(() => jest.resetAllMocks());
describe('muteAlert', () => {
test('should call mute alert API', async () => {
const result = await muteAlert({ http, id: '1' });
expect(result).toEqual(undefined);
expect(http.post.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
"/api/alerting/rule/1/_mute_all",
],
]
`);
});
});
describe('muteAlerts', () => {
test('should call mute alert API per alert', async () => {
const ids = ['1', '2', '3'];
const result = await muteAlerts({ http, ids });
expect(result).toEqual(undefined);
expect(http.post.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
"/api/alerting/rule/1/_mute_all",
],
Array [
"/api/alerting/rule/2/_mute_all",
],
Array [
"/api/alerting/rule/3/_mute_all",
],
]
`);
});
});

View file

@ -0,0 +1,16 @@
/*
* 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 { HttpSetup } from 'kibana/public';
import { BASE_ALERTING_API_PATH } from '../../constants';
export async function muteAlert({ id, http }: { id: string; http: HttpSetup }): Promise<void> {
await http.post(`${BASE_ALERTING_API_PATH}/rule/${id}/_mute_all`);
}
export async function muteAlerts({ ids, http }: { ids: string[]; http: HttpSetup }): Promise<void> {
await Promise.all(ids.map((id) => muteAlert({ http, id })));
}

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.
*/
import { httpServiceMock } from '../../../../../../../src/core/public/mocks';
import { muteAlertInstance } from './mute_alert';
const http = httpServiceMock.createStartContract();
describe('muteAlertInstance', () => {
test('should call mute instance alert API', async () => {
const result = await muteAlertInstance({ http, id: '1', instanceId: '123' });
expect(result).toEqual(undefined);
expect(http.post.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
"/api/alerting/rule/1/alert/123/_mute",
],
]
`);
});
});

View file

@ -0,0 +1,20 @@
/*
* 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 { HttpSetup } from 'kibana/public';
import { BASE_ALERTING_API_PATH } from '../../constants';
export async function muteAlertInstance({
id,
instanceId,
http,
}: {
id: string;
instanceId: string;
http: HttpSetup;
}): Promise<void> {
await http.post(`${BASE_ALERTING_API_PATH}/rule/${id}/alert/${instanceId}/_mute`);
}

View file

@ -0,0 +1,45 @@
/*
* 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 { AlertType } from '../../../types';
import { httpServiceMock } from '../../../../../../../src/core/public/mocks';
import { loadAlertTypes } from './rule_types';
import { ALERTS_FEATURE_ID } from '../../../../../alerting/common';
const http = httpServiceMock.createStartContract();
describe('loadAlertTypes', () => {
test('should call get alert types API', async () => {
const resolvedValue: AlertType[] = [
{
id: 'test',
name: 'Test',
actionVariables: {
context: [{ name: 'var1', description: 'val1' }],
state: [{ name: 'var2', description: 'val2' }],
params: [{ name: 'var3', description: 'val3' }],
},
producer: ALERTS_FEATURE_ID,
actionGroups: [{ id: 'default', name: 'Default' }],
recoveryActionGroup: { id: 'recovered', name: 'Recovered' },
defaultActionGroupId: 'default',
authorizedConsumers: {},
minimumLicenseRequired: 'basic',
enabledInLicense: true,
},
];
http.get.mockResolvedValueOnce(resolvedValue);
const result = await loadAlertTypes({ http });
expect(result).toEqual(resolvedValue);
expect(http.get.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"/api/alerting/rule_types",
]
`);
});
});

View file

@ -0,0 +1,39 @@
/*
* 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 { HttpSetup } from 'kibana/public';
import { AlertType } from '../../../types';
import { BASE_ALERTING_API_PATH } from '../../constants';
import { AsApiContract, RewriteRequestCase } from '../../../../../actions/common';
const rewriteResponseRes = (results: Array<AsApiContract<AlertType>>): AlertType[] => {
return results.map((item) => rewriteBodyReq(item));
};
const rewriteBodyReq: RewriteRequestCase<AlertType> = ({
enabled_in_license: enabledInLicense,
recovery_action_group: recoveryActionGroup,
action_groups: actionGroups,
default_action_group_id: defaultActionGroupId,
minimum_license_required: minimumLicenseRequired,
action_variables: actionVariables,
authorized_consumers: authorizedConsumers,
...rest
}: AsApiContract<AlertType>) => ({
enabledInLicense,
recoveryActionGroup,
actionGroups,
defaultActionGroupId,
minimumLicenseRequired,
actionVariables,
authorizedConsumers,
...rest,
});
export async function loadAlertTypes({ http }: { http: HttpSetup }): Promise<AlertType[]> {
const res = await http.get(`${BASE_ALERTING_API_PATH}/rule_types`);
return rewriteResponseRes(res);
}

View file

@ -0,0 +1,242 @@
/*
* 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 { httpServiceMock } from '../../../../../../../src/core/public/mocks';
import { loadAlerts } from './rules';
const http = httpServiceMock.createStartContract();
describe('loadAlerts', () => {
beforeEach(() => jest.resetAllMocks());
test('should call find API with base parameters', async () => {
const resolvedValue = {
page: 1,
per_page: 10,
total: 0,
data: [],
};
http.get.mockResolvedValueOnce(resolvedValue);
const result = await loadAlerts({ http, page: { index: 0, size: 10 } });
expect(result).toEqual({
page: 1,
perPage: 10,
total: 0,
data: [],
});
expect(http.get.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"/api/alerting/rules/_find",
Object {
"query": Object {
"default_search_operator": "AND",
"filter": undefined,
"page": 1,
"per_page": 10,
"search": undefined,
"search_fields": undefined,
"sort_field": "name",
"sort_order": "asc",
},
},
]
`);
});
test('should call find API with searchText', async () => {
const resolvedValue = {
page: 1,
per_page: 10,
total: 0,
data: [],
};
http.get.mockResolvedValueOnce(resolvedValue);
const result = await loadAlerts({ http, searchText: 'apples', page: { index: 0, size: 10 } });
expect(result).toEqual({
page: 1,
perPage: 10,
total: 0,
data: [],
});
expect(http.get.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"/api/alerting/rules/_find",
Object {
"query": Object {
"default_search_operator": "AND",
"filter": undefined,
"page": 1,
"per_page": 10,
"search": "apples",
"search_fields": "[\\"name\\",\\"tags\\"]",
"sort_field": "name",
"sort_order": "asc",
},
},
]
`);
});
test('should call find API with actionTypesFilter', async () => {
const resolvedValue = {
page: 1,
per_page: 10,
total: 0,
data: [],
};
http.get.mockResolvedValueOnce(resolvedValue);
const result = await loadAlerts({
http,
searchText: 'foo',
page: { index: 0, size: 10 },
});
expect(result).toEqual({
page: 1,
perPage: 10,
total: 0,
data: [],
});
expect(http.get.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"/api/alerting/rules/_find",
Object {
"query": Object {
"default_search_operator": "AND",
"filter": undefined,
"page": 1,
"per_page": 10,
"search": "foo",
"search_fields": "[\\"name\\",\\"tags\\"]",
"sort_field": "name",
"sort_order": "asc",
},
},
]
`);
});
test('should call find API with typesFilter', async () => {
const resolvedValue = {
page: 1,
per_page: 10,
total: 0,
data: [],
};
http.get.mockResolvedValueOnce(resolvedValue);
const result = await loadAlerts({
http,
typesFilter: ['foo', 'bar'],
page: { index: 0, size: 10 },
});
expect(result).toEqual({
page: 1,
perPage: 10,
total: 0,
data: [],
});
expect(http.get.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"/api/alerting/rules/_find",
Object {
"query": Object {
"default_search_operator": "AND",
"filter": "alert.attributes.alertTypeId:(foo or bar)",
"page": 1,
"per_page": 10,
"search": undefined,
"search_fields": undefined,
"sort_field": "name",
"sort_order": "asc",
},
},
]
`);
});
test('should call find API with actionTypesFilter and typesFilter', async () => {
const resolvedValue = {
page: 1,
per_page: 10,
total: 0,
data: [],
};
http.get.mockResolvedValueOnce(resolvedValue);
const result = await loadAlerts({
http,
searchText: 'baz',
typesFilter: ['foo', 'bar'],
page: { index: 0, size: 10 },
});
expect(result).toEqual({
page: 1,
perPage: 10,
total: 0,
data: [],
});
expect(http.get.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"/api/alerting/rules/_find",
Object {
"query": Object {
"default_search_operator": "AND",
"filter": "alert.attributes.alertTypeId:(foo or bar)",
"page": 1,
"per_page": 10,
"search": "baz",
"search_fields": "[\\"name\\",\\"tags\\"]",
"sort_field": "name",
"sort_order": "asc",
},
},
]
`);
});
test('should call find API with searchText and tagsFilter and typesFilter', async () => {
const resolvedValue = {
page: 1,
per_page: 10,
total: 0,
data: [],
};
http.get.mockResolvedValueOnce(resolvedValue);
const result = await loadAlerts({
http,
searchText: 'apples, foo, baz',
typesFilter: ['foo', 'bar'],
page: { index: 0, size: 10 },
});
expect(result).toEqual({
page: 1,
perPage: 10,
total: 0,
data: [],
});
expect(http.get.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"/api/alerting/rules/_find",
Object {
"query": Object {
"default_search_operator": "AND",
"filter": "alert.attributes.alertTypeId:(foo or bar)",
"page": 1,
"per_page": 10,
"search": "apples, foo, baz",
"search_fields": "[\\"name\\",\\"tags\\"]",
"sort_field": "name",
"sort_order": "asc",
},
},
]
`);
});
});

View file

@ -0,0 +1,59 @@
/*
* 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 { HttpSetup } from 'kibana/public';
import { BASE_ALERTING_API_PATH } from '../../constants';
import { Alert, Pagination, Sorting } from '../../../types';
import { AsApiContract } from '../../../../../actions/common';
import { mapFiltersToKql } from './map_filters_to_kql';
import { transformAlert } from './common_transformations';
const rewriteResponseRes = (results: Array<AsApiContract<Alert>>): Alert[] => {
return results.map((item) => transformAlert(item));
};
export async function loadAlerts({
http,
page,
searchText,
typesFilter,
actionTypesFilter,
alertStatusesFilter,
sort = { field: 'name', direction: 'asc' },
}: {
http: HttpSetup;
page: Pagination;
searchText?: string;
typesFilter?: string[];
actionTypesFilter?: string[];
alertStatusesFilter?: string[];
sort?: Sorting;
}): Promise<{
page: number;
perPage: number;
total: number;
data: Alert[];
}> {
const filters = mapFiltersToKql({ typesFilter, actionTypesFilter, alertStatusesFilter });
const res = await http.get(`${BASE_ALERTING_API_PATH}/rules/_find`, {
query: {
page: page.index + 1,
per_page: page.size,
search_fields: searchText ? JSON.stringify(['name', 'tags']) : undefined,
search: searchText,
filter: filters.length ? filters.join(' and ') : undefined,
default_search_operator: 'AND',
sort_field: sort.field,
sort_order: sort.direction,
},
});
return {
page: res.page,
perPage: res.per_page,
total: res.total,
data: rewriteResponseRes(res.data),
};
}

View file

@ -0,0 +1,101 @@
/*
* 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 { httpServiceMock } from '../../../../../../../src/core/public/mocks';
import { loadAlertState } from './state';
import uuid from 'uuid';
const http = httpServiceMock.createStartContract();
describe('loadAlertState', () => {
beforeEach(() => jest.resetAllMocks());
test('should call get API with base parameters', async () => {
const alertId = uuid.v4();
const resolvedValue = {
alertTypeState: {
some: 'value',
},
alertInstances: {
first_instance: {},
second_instance: {},
},
};
http.get.mockResolvedValueOnce({
rule_type_state: {
some: 'value',
},
alerts: {
first_instance: {},
second_instance: {},
},
});
expect(await loadAlertState({ http, alertId })).toEqual(resolvedValue);
expect(http.get).toHaveBeenCalledWith(`/internal/alerting/rule/${alertId}/state`);
});
test('should parse AlertInstances', async () => {
const alertId = uuid.v4();
const resolvedValue = {
alertTypeState: {
some: 'value',
},
alertInstances: {
first_instance: {
state: {},
meta: {
lastScheduledActions: {
group: 'first_group',
date: '2020-02-09T23:15:41.941Z',
},
},
},
},
};
http.get.mockResolvedValueOnce({
rule_type_state: {
some: 'value',
},
alerts: {
first_instance: {
state: {},
meta: {
lastScheduledActions: {
group: 'first_group',
date: '2020-02-09T23:15:41.941Z',
},
},
},
},
});
expect(await loadAlertState({ http, alertId })).toEqual({
...resolvedValue,
alertInstances: {
first_instance: {
state: {},
meta: {
lastScheduledActions: {
group: 'first_group',
date: new Date('2020-02-09T23:15:41.941Z'),
},
},
},
},
});
expect(http.get).toHaveBeenCalledWith(`/internal/alerting/rule/${alertId}/state`);
});
test('should handle empty response from api', async () => {
const alertId = uuid.v4();
http.get.mockResolvedValueOnce('');
expect(await loadAlertState({ http, alertId })).toEqual({});
expect(http.get).toHaveBeenCalledWith(`/internal/alerting/rule/${alertId}/state`);
});
});

View file

@ -0,0 +1,49 @@
/*
* 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 { HttpSetup } from 'kibana/public';
import { pipe } from 'fp-ts/lib/pipeable';
import { fold } from 'fp-ts/lib/Either';
import { Errors, identity } from 'io-ts';
import { AlertTaskState } from '../../../types';
import { INTERNAL_BASE_ALERTING_API_PATH } from '../../constants';
import { alertStateSchema } from '../../../../../alerting/common';
import { AsApiContract, RewriteRequestCase } from '../../../../../actions/common';
const rewriteBodyRes: RewriteRequestCase<AlertTaskState> = ({
rule_type_state: alertTypeState,
alerts: alertInstances,
previous_started_at: previousStartedAt,
...rest
}: any) => ({
...rest,
alertTypeState,
alertInstances,
previousStartedAt,
});
type EmptyHttpResponse = '';
export async function loadAlertState({
http,
alertId,
}: {
http: HttpSetup;
alertId: string;
}): Promise<AlertTaskState> {
return await http
.get(`${INTERNAL_BASE_ALERTING_API_PATH}/rule/${alertId}/state`)
.then((state: AsApiContract<AlertTaskState> | EmptyHttpResponse) =>
state ? rewriteBodyRes(state) : {}
)
.then((state: AlertTaskState) => {
return pipe(
alertStateSchema.decode(state),
fold((e: Errors) => {
throw new Error(`Alert "${alertId}" has invalid state`);
}, identity)
);
});
}

View file

@ -0,0 +1,47 @@
/*
* 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 { httpServiceMock } from '../../../../../../../src/core/public/mocks';
import { unmuteAlert, unmuteAlerts } from './unmute';
const http = httpServiceMock.createStartContract();
beforeEach(() => jest.resetAllMocks());
describe('unmuteAlerts', () => {
test('should call unmute alert API per alert', async () => {
const ids = ['1', '2', '3'];
const result = await unmuteAlerts({ http, ids });
expect(result).toEqual(undefined);
expect(http.post.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
"/api/alerting/rule/1/_unmute_all",
],
Array [
"/api/alerting/rule/2/_unmute_all",
],
Array [
"/api/alerting/rule/3/_unmute_all",
],
]
`);
});
});
describe('unmuteAlert', () => {
test('should call unmute alert API', async () => {
const result = await unmuteAlert({ http, id: '1' });
expect(result).toEqual(undefined);
expect(http.post.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
"/api/alerting/rule/1/_unmute_all",
],
]
`);
});
});

View file

@ -0,0 +1,22 @@
/*
* 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 { HttpSetup } from 'kibana/public';
import { BASE_ALERTING_API_PATH } from '../../constants';
export async function unmuteAlert({ id, http }: { id: string; http: HttpSetup }): Promise<void> {
await http.post(`${BASE_ALERTING_API_PATH}/rule/${id}/_unmute_all`);
}
export async function unmuteAlerts({
ids,
http,
}: {
ids: string[];
http: HttpSetup;
}): Promise<void> {
await Promise.all(ids.map((id) => unmuteAlert({ id, http })));
}

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.
*/
import { httpServiceMock } from '../../../../../../../src/core/public/mocks';
import { unmuteAlertInstance } from './unmute_alert';
const http = httpServiceMock.createStartContract();
describe('unmuteAlertInstance', () => {
test('should call mute instance alert API', async () => {
const result = await unmuteAlertInstance({ http, id: '1', instanceId: '123' });
expect(result).toEqual(undefined);
expect(http.post.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
"/api/alerting/rule/1/alert/123/_unmute",
],
]
`);
});
});

View file

@ -0,0 +1,20 @@
/*
* 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 { HttpSetup } from 'kibana/public';
import { BASE_ALERTING_API_PATH } from '../../constants';
export async function unmuteAlertInstance({
id,
instanceId,
http,
}: {
id: string;
instanceId: string;
http: HttpSetup;
}): Promise<void> {
await http.post(`${BASE_ALERTING_API_PATH}/rule/${id}/alert/${instanceId}/_unmute`);
}

View file

@ -0,0 +1,60 @@
/*
* 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 { Alert } from '../../../types';
import { httpServiceMock } from '../../../../../../../src/core/public/mocks';
import { updateAlert } from './update';
import { AlertNotifyWhenType } from '../../../../../alerting/common';
const http = httpServiceMock.createStartContract();
describe('updateAlert', () => {
test('should call alert update API', async () => {
const alertToUpdate = {
throttle: '1m',
consumer: 'alerts',
name: 'test',
tags: ['foo'],
schedule: {
interval: '1m',
},
params: {},
actions: [],
createdAt: new Date('1970-01-01T00:00:00.000Z'),
updatedAt: new Date('1970-01-01T00:00:00.000Z'),
apiKey: null,
apiKeyOwner: null,
notifyWhen: 'onThrottleInterval' as AlertNotifyWhenType,
};
const resolvedValue: Alert = {
...alertToUpdate,
id: '123',
enabled: true,
alertTypeId: 'test',
createdBy: null,
updatedBy: null,
muteAll: false,
mutedInstanceIds: [],
executionStatus: {
status: 'unknown',
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
},
};
http.put.mockResolvedValueOnce(resolvedValue);
const result = await updateAlert({ http, id: '123', alert: alertToUpdate });
expect(result).toEqual(resolvedValue);
expect(http.put.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"/api/alerting/rule/123",
Object {
"body": "{\\"throttle\\":\\"1m\\",\\"name\\":\\"test\\",\\"tags\\":[\\"foo\\"],\\"schedule\\":{\\"interval\\":\\"1m\\"},\\"params\\":{},\\"notify_when\\":\\"onThrottleInterval\\",\\"actions\\":[]}",
},
]
`);
});
});

View file

@ -0,0 +1,52 @@
/*
* 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 { HttpSetup } from 'kibana/public';
import { pick } from 'lodash';
import { BASE_ALERTING_API_PATH } from '../../constants';
import { Alert, AlertUpdates } from '../../../types';
import { RewriteResponseCase } from '../../../../../actions/common';
import { transformAlert } from './common_transformations';
type AlertUpdatesBody = Pick<
AlertUpdates,
'name' | 'tags' | 'schedule' | 'actions' | 'params' | 'throttle' | 'notifyWhen'
>;
const rewriteBodyRequest: RewriteResponseCase<AlertUpdatesBody> = ({
notifyWhen,
actions,
...res
}): any => ({
...res,
notify_when: notifyWhen,
actions: actions.map(({ group, id, params }) => ({
group,
id,
params,
})),
});
export async function updateAlert({
http,
alert,
id,
}: {
http: HttpSetup;
alert: Pick<
AlertUpdates,
'throttle' | 'name' | 'tags' | 'schedule' | 'params' | 'actions' | 'notifyWhen'
>;
id: string;
}): Promise<Alert> {
const res = await http.put(`${BASE_ALERTING_API_PATH}/rule/${id}`, {
body: JSON.stringify(
rewriteBodyRequest(
pick(alert, ['throttle', 'name', 'tags', 'schedule', 'params', 'actions', 'notifyWhen'])
)
),
});
return transformAlert(res);
}