mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
Denormalize actionTypeId into alert actions for easier filtering (#51628)
* Denormalize actionTypeId for easier filtering of alerts * Add tests * No longer pass actionTypeId for each alert action in APIs * Add tests to ensure denormalizeActions works on multiple actions * Fix ESLint errors
This commit is contained in:
parent
de4269f8d4
commit
ca5f6d78f1
14 changed files with 655 additions and 81 deletions
|
@ -25,6 +25,9 @@
|
|||
"actionRef": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"actionTypeId": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"params": {
|
||||
"enabled": false,
|
||||
"type": "object"
|
||||
|
|
|
@ -74,6 +74,18 @@ describe('create()', () => {
|
|||
actionGroups: ['default'],
|
||||
async executor() {},
|
||||
});
|
||||
savedObjectsClient.bulkGet.mockResolvedValueOnce({
|
||||
saved_objects: [
|
||||
{
|
||||
id: '1',
|
||||
type: 'action',
|
||||
attributes: {
|
||||
actionTypeId: 'test',
|
||||
},
|
||||
references: [],
|
||||
},
|
||||
],
|
||||
});
|
||||
savedObjectsClient.create.mockResolvedValueOnce({
|
||||
id: '1',
|
||||
type: 'alert',
|
||||
|
@ -87,6 +99,7 @@ describe('create()', () => {
|
|||
{
|
||||
group: 'default',
|
||||
actionRef: 'action_0',
|
||||
actionTypeId: 'test',
|
||||
params: {
|
||||
foo: true,
|
||||
},
|
||||
|
@ -133,6 +146,7 @@ describe('create()', () => {
|
|||
Object {
|
||||
"actions": Array [
|
||||
Object {
|
||||
"actionTypeId": "test",
|
||||
"group": "default",
|
||||
"id": "1",
|
||||
"params": Object {
|
||||
|
@ -157,6 +171,7 @@ describe('create()', () => {
|
|||
"actions": Array [
|
||||
Object {
|
||||
"actionRef": "action_0",
|
||||
"actionTypeId": "test",
|
||||
"group": "default",
|
||||
"params": Object {
|
||||
"foo": true,
|
||||
|
@ -224,6 +239,184 @@ describe('create()', () => {
|
|||
`);
|
||||
});
|
||||
|
||||
test('creates an alert with multiple actions', async () => {
|
||||
const alertsClient = new AlertsClient(alertsClientParams);
|
||||
const data = getMockData({
|
||||
actions: [
|
||||
{
|
||||
group: 'default',
|
||||
id: '1',
|
||||
params: {
|
||||
foo: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
group: 'default',
|
||||
id: '1',
|
||||
params: {
|
||||
foo: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
group: 'default',
|
||||
id: '2',
|
||||
params: {
|
||||
foo: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
alertTypeRegistry.get.mockReturnValueOnce({
|
||||
id: '123',
|
||||
name: 'Test',
|
||||
actionGroups: ['default'],
|
||||
async executor() {},
|
||||
});
|
||||
savedObjectsClient.bulkGet.mockResolvedValueOnce({
|
||||
saved_objects: [
|
||||
{
|
||||
id: '1',
|
||||
type: 'action',
|
||||
attributes: {
|
||||
actionTypeId: 'test',
|
||||
},
|
||||
references: [],
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
type: 'action',
|
||||
attributes: {
|
||||
actionTypeId: 'test2',
|
||||
},
|
||||
references: [],
|
||||
},
|
||||
],
|
||||
});
|
||||
savedObjectsClient.create.mockResolvedValueOnce({
|
||||
id: '1',
|
||||
type: 'alert',
|
||||
attributes: {
|
||||
alertTypeId: '123',
|
||||
interval: '10s',
|
||||
params: {
|
||||
bar: true,
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
group: 'default',
|
||||
actionRef: 'action_0',
|
||||
actionTypeId: 'test',
|
||||
params: {
|
||||
foo: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
group: 'default',
|
||||
actionRef: 'action_1',
|
||||
actionTypeId: 'test',
|
||||
params: {
|
||||
foo: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
group: 'default',
|
||||
actionRef: 'action_2',
|
||||
actionTypeId: 'test2',
|
||||
params: {
|
||||
foo: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
references: [
|
||||
{
|
||||
name: 'action_0',
|
||||
type: 'action',
|
||||
id: '1',
|
||||
},
|
||||
{
|
||||
name: 'action_1',
|
||||
type: 'action',
|
||||
id: '1',
|
||||
},
|
||||
{
|
||||
name: 'action_2',
|
||||
type: 'action',
|
||||
id: '2',
|
||||
},
|
||||
],
|
||||
});
|
||||
taskManager.schedule.mockResolvedValueOnce({
|
||||
id: 'task-123',
|
||||
taskType: 'alerting:123',
|
||||
scheduledAt: new Date(),
|
||||
attempts: 1,
|
||||
status: 'idle',
|
||||
runAt: new Date(),
|
||||
startedAt: null,
|
||||
retryAt: null,
|
||||
state: {},
|
||||
params: {},
|
||||
ownerId: null,
|
||||
});
|
||||
savedObjectsClient.update.mockResolvedValueOnce({
|
||||
id: '1',
|
||||
type: 'alert',
|
||||
attributes: {
|
||||
scheduledTaskId: 'task-123',
|
||||
},
|
||||
references: [],
|
||||
});
|
||||
const result = await alertsClient.create({ data });
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"actions": Array [
|
||||
Object {
|
||||
"actionTypeId": "test",
|
||||
"group": "default",
|
||||
"id": "1",
|
||||
"params": Object {
|
||||
"foo": true,
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"actionTypeId": "test",
|
||||
"group": "default",
|
||||
"id": "1",
|
||||
"params": Object {
|
||||
"foo": true,
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"actionTypeId": "test2",
|
||||
"group": "default",
|
||||
"id": "2",
|
||||
"params": Object {
|
||||
"foo": true,
|
||||
},
|
||||
},
|
||||
],
|
||||
"alertTypeId": "123",
|
||||
"id": "1",
|
||||
"interval": "10s",
|
||||
"params": Object {
|
||||
"bar": true,
|
||||
},
|
||||
"scheduledTaskId": "task-123",
|
||||
}
|
||||
`);
|
||||
expect(savedObjectsClient.bulkGet).toHaveBeenCalledWith([
|
||||
{
|
||||
id: '1',
|
||||
type: 'action',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
type: 'action',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('creates a disabled alert', async () => {
|
||||
const alertsClient = new AlertsClient(alertsClientParams);
|
||||
const data = getMockData({ enabled: false });
|
||||
|
@ -233,6 +426,18 @@ describe('create()', () => {
|
|||
actionGroups: ['default'],
|
||||
async executor() {},
|
||||
});
|
||||
savedObjectsClient.bulkGet.mockResolvedValueOnce({
|
||||
saved_objects: [
|
||||
{
|
||||
id: '1',
|
||||
type: 'action',
|
||||
attributes: {
|
||||
actionTypeId: 'test',
|
||||
},
|
||||
references: [],
|
||||
},
|
||||
],
|
||||
});
|
||||
savedObjectsClient.create.mockResolvedValueOnce({
|
||||
id: '1',
|
||||
type: 'alert',
|
||||
|
@ -247,6 +452,7 @@ describe('create()', () => {
|
|||
{
|
||||
group: 'default',
|
||||
actionRef: 'action_0',
|
||||
actionTypeId: 'test',
|
||||
params: {
|
||||
foo: true,
|
||||
},
|
||||
|
@ -266,6 +472,7 @@ describe('create()', () => {
|
|||
Object {
|
||||
"actions": Array [
|
||||
Object {
|
||||
"actionTypeId": "test",
|
||||
"group": "default",
|
||||
"id": "1",
|
||||
"params": Object {
|
||||
|
@ -306,6 +513,23 @@ describe('create()', () => {
|
|||
);
|
||||
});
|
||||
|
||||
test('throws error if loading actions fails', async () => {
|
||||
const alertsClient = new AlertsClient(alertsClientParams);
|
||||
const data = getMockData();
|
||||
alertTypeRegistry.get.mockReturnValueOnce({
|
||||
id: '123',
|
||||
name: 'Test',
|
||||
actionGroups: ['default'],
|
||||
async executor() {},
|
||||
});
|
||||
savedObjectsClient.bulkGet.mockRejectedValueOnce(new Error('Test Error'));
|
||||
await expect(alertsClient.create({ data })).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`"Test Error"`
|
||||
);
|
||||
expect(savedObjectsClient.create).not.toHaveBeenCalled();
|
||||
expect(taskManager.schedule).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('throws error if create saved object fails', async () => {
|
||||
const alertsClient = new AlertsClient(alertsClientParams);
|
||||
const data = getMockData();
|
||||
|
@ -315,6 +539,18 @@ describe('create()', () => {
|
|||
actionGroups: ['default'],
|
||||
async executor() {},
|
||||
});
|
||||
savedObjectsClient.bulkGet.mockResolvedValueOnce({
|
||||
saved_objects: [
|
||||
{
|
||||
id: '1',
|
||||
type: 'action',
|
||||
attributes: {
|
||||
actionTypeId: 'test',
|
||||
},
|
||||
references: [],
|
||||
},
|
||||
],
|
||||
});
|
||||
savedObjectsClient.create.mockRejectedValueOnce(new Error('Test failure'));
|
||||
await expect(alertsClient.create({ data })).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`"Test failure"`
|
||||
|
@ -331,6 +567,18 @@ describe('create()', () => {
|
|||
actionGroups: ['default'],
|
||||
async executor() {},
|
||||
});
|
||||
savedObjectsClient.bulkGet.mockResolvedValueOnce({
|
||||
saved_objects: [
|
||||
{
|
||||
id: '1',
|
||||
type: 'action',
|
||||
attributes: {
|
||||
actionTypeId: 'test',
|
||||
},
|
||||
references: [],
|
||||
},
|
||||
],
|
||||
});
|
||||
savedObjectsClient.create.mockResolvedValueOnce({
|
||||
id: '1',
|
||||
type: 'alert',
|
||||
|
@ -344,6 +592,7 @@ describe('create()', () => {
|
|||
{
|
||||
group: 'default',
|
||||
actionRef: 'action_0',
|
||||
actionTypeId: 'test',
|
||||
params: {
|
||||
foo: true,
|
||||
},
|
||||
|
@ -381,6 +630,18 @@ describe('create()', () => {
|
|||
actionGroups: ['default'],
|
||||
async executor() {},
|
||||
});
|
||||
savedObjectsClient.bulkGet.mockResolvedValueOnce({
|
||||
saved_objects: [
|
||||
{
|
||||
id: '1',
|
||||
type: 'action',
|
||||
attributes: {
|
||||
actionTypeId: 'test',
|
||||
},
|
||||
references: [],
|
||||
},
|
||||
],
|
||||
});
|
||||
savedObjectsClient.create.mockResolvedValueOnce({
|
||||
id: '1',
|
||||
type: 'alert',
|
||||
|
@ -394,6 +655,7 @@ describe('create()', () => {
|
|||
{
|
||||
group: 'default',
|
||||
actionRef: 'action_0',
|
||||
actionTypeId: 'test',
|
||||
params: {
|
||||
foo: true,
|
||||
},
|
||||
|
@ -442,6 +704,18 @@ describe('create()', () => {
|
|||
created: true,
|
||||
result: { id: '123', api_key: 'abc' },
|
||||
});
|
||||
savedObjectsClient.bulkGet.mockResolvedValueOnce({
|
||||
saved_objects: [
|
||||
{
|
||||
id: '1',
|
||||
type: 'action',
|
||||
attributes: {
|
||||
actionTypeId: 'test',
|
||||
},
|
||||
references: [],
|
||||
},
|
||||
],
|
||||
});
|
||||
savedObjectsClient.create.mockResolvedValueOnce({
|
||||
id: '1',
|
||||
type: 'alert',
|
||||
|
@ -455,6 +729,7 @@ describe('create()', () => {
|
|||
{
|
||||
group: 'default',
|
||||
actionRef: 'action_0',
|
||||
actionTypeId: 'test',
|
||||
params: {
|
||||
foo: true,
|
||||
},
|
||||
|
@ -506,6 +781,7 @@ describe('create()', () => {
|
|||
{
|
||||
actionRef: 'action_0',
|
||||
group: 'default',
|
||||
actionTypeId: 'test',
|
||||
params: { foo: true },
|
||||
},
|
||||
],
|
||||
|
@ -1149,6 +1425,18 @@ describe('update()', () => {
|
|||
references: [],
|
||||
version: '123',
|
||||
});
|
||||
savedObjectsClient.bulkGet.mockResolvedValueOnce({
|
||||
saved_objects: [
|
||||
{
|
||||
id: '1',
|
||||
type: 'action',
|
||||
attributes: {
|
||||
actionTypeId: 'test',
|
||||
},
|
||||
references: [],
|
||||
},
|
||||
],
|
||||
});
|
||||
savedObjectsClient.update.mockResolvedValueOnce({
|
||||
id: '1',
|
||||
type: 'alert',
|
||||
|
@ -1162,6 +1450,7 @@ describe('update()', () => {
|
|||
{
|
||||
group: 'default',
|
||||
actionRef: 'action_0',
|
||||
actionTypeId: 'test',
|
||||
params: {
|
||||
foo: true,
|
||||
},
|
||||
|
@ -1201,6 +1490,7 @@ describe('update()', () => {
|
|||
Object {
|
||||
"actions": Array [
|
||||
Object {
|
||||
"actionTypeId": "test",
|
||||
"group": "default",
|
||||
"id": "1",
|
||||
"params": Object {
|
||||
|
@ -1226,6 +1516,7 @@ describe('update()', () => {
|
|||
"actions": Array [
|
||||
Object {
|
||||
"actionRef": "action_0",
|
||||
"actionTypeId": "test",
|
||||
"group": "default",
|
||||
"params": Object {
|
||||
"foo": true,
|
||||
|
@ -1262,6 +1553,183 @@ describe('update()', () => {
|
|||
`);
|
||||
});
|
||||
|
||||
it('updates with multiple actions', async () => {
|
||||
const alertsClient = new AlertsClient(alertsClientParams);
|
||||
alertTypeRegistry.get.mockReturnValueOnce({
|
||||
id: '123',
|
||||
name: 'Test',
|
||||
actionGroups: ['default'],
|
||||
async executor() {},
|
||||
});
|
||||
savedObjectsClient.get.mockResolvedValueOnce({
|
||||
id: '1',
|
||||
type: 'alert',
|
||||
attributes: {
|
||||
enabled: true,
|
||||
alertTypeId: '123',
|
||||
scheduledTaskId: 'task-123',
|
||||
},
|
||||
references: [],
|
||||
version: '123',
|
||||
});
|
||||
savedObjectsClient.bulkGet.mockResolvedValueOnce({
|
||||
saved_objects: [
|
||||
{
|
||||
id: '1',
|
||||
type: 'action',
|
||||
attributes: {
|
||||
actionTypeId: 'test',
|
||||
},
|
||||
references: [],
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
type: 'action',
|
||||
attributes: {
|
||||
actionTypeId: 'test2',
|
||||
},
|
||||
references: [],
|
||||
},
|
||||
],
|
||||
});
|
||||
savedObjectsClient.update.mockResolvedValueOnce({
|
||||
id: '1',
|
||||
type: 'alert',
|
||||
attributes: {
|
||||
enabled: true,
|
||||
interval: '10s',
|
||||
params: {
|
||||
bar: true,
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
group: 'default',
|
||||
actionRef: 'action_0',
|
||||
actionTypeId: 'test',
|
||||
params: {
|
||||
foo: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
group: 'default',
|
||||
actionRef: 'action_1',
|
||||
actionTypeId: 'test',
|
||||
params: {
|
||||
foo: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
group: 'default',
|
||||
actionRef: 'action_2',
|
||||
actionTypeId: 'test2',
|
||||
params: {
|
||||
foo: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
scheduledTaskId: 'task-123',
|
||||
},
|
||||
references: [
|
||||
{
|
||||
name: 'action_0',
|
||||
type: 'action',
|
||||
id: '1',
|
||||
},
|
||||
{
|
||||
name: 'action_1',
|
||||
type: 'action',
|
||||
id: '1',
|
||||
},
|
||||
{
|
||||
name: 'action_2',
|
||||
type: 'action',
|
||||
id: '2',
|
||||
},
|
||||
],
|
||||
});
|
||||
const result = await alertsClient.update({
|
||||
id: '1',
|
||||
data: {
|
||||
interval: '10s',
|
||||
name: 'abc',
|
||||
tags: ['foo'],
|
||||
params: {
|
||||
bar: true,
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
group: 'default',
|
||||
id: '1',
|
||||
params: {
|
||||
foo: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
group: 'default',
|
||||
id: '1',
|
||||
params: {
|
||||
foo: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
group: 'default',
|
||||
id: '2',
|
||||
params: {
|
||||
foo: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"actions": Array [
|
||||
Object {
|
||||
"actionTypeId": "test",
|
||||
"group": "default",
|
||||
"id": "1",
|
||||
"params": Object {
|
||||
"foo": true,
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"actionTypeId": "test",
|
||||
"group": "default",
|
||||
"id": "1",
|
||||
"params": Object {
|
||||
"foo": true,
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"actionTypeId": "test2",
|
||||
"group": "default",
|
||||
"id": "2",
|
||||
"params": Object {
|
||||
"foo": true,
|
||||
},
|
||||
},
|
||||
],
|
||||
"enabled": true,
|
||||
"id": "1",
|
||||
"interval": "10s",
|
||||
"params": Object {
|
||||
"bar": true,
|
||||
},
|
||||
"scheduledTaskId": "task-123",
|
||||
}
|
||||
`);
|
||||
expect(savedObjectsClient.bulkGet).toHaveBeenCalledWith([
|
||||
{
|
||||
id: '1',
|
||||
type: 'action',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
type: 'action',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('calls the createApiKey function', async () => {
|
||||
const alertsClient = new AlertsClient(alertsClientParams);
|
||||
alertTypeRegistry.get.mockReturnValueOnce({
|
||||
|
@ -1281,6 +1749,18 @@ describe('update()', () => {
|
|||
references: [],
|
||||
version: '123',
|
||||
});
|
||||
savedObjectsClient.bulkGet.mockResolvedValueOnce({
|
||||
saved_objects: [
|
||||
{
|
||||
id: '1',
|
||||
type: 'action',
|
||||
attributes: {
|
||||
actionTypeId: 'test',
|
||||
},
|
||||
references: [],
|
||||
},
|
||||
],
|
||||
});
|
||||
alertsClientParams.createAPIKey.mockResolvedValueOnce({
|
||||
created: true,
|
||||
result: { id: '123', api_key: 'abc' },
|
||||
|
@ -1298,6 +1778,7 @@ describe('update()', () => {
|
|||
{
|
||||
group: 'default',
|
||||
actionRef: 'action_0',
|
||||
actionTypeId: 'test',
|
||||
params: {
|
||||
foo: true,
|
||||
},
|
||||
|
@ -1338,6 +1819,7 @@ describe('update()', () => {
|
|||
Object {
|
||||
"actions": Array [
|
||||
Object {
|
||||
"actionTypeId": "test",
|
||||
"group": "default",
|
||||
"id": "1",
|
||||
"params": Object {
|
||||
|
@ -1364,6 +1846,7 @@ describe('update()', () => {
|
|||
"actions": Array [
|
||||
Object {
|
||||
"actionRef": "action_0",
|
||||
"actionTypeId": "test",
|
||||
"group": "default",
|
||||
"params": Object {
|
||||
"foo": true,
|
||||
|
|
|
@ -21,6 +21,7 @@ interface SuccessCreateAPIKeyResult {
|
|||
result: SecurityPluginCreateAPIKeyResult;
|
||||
}
|
||||
export type CreateAPIKeyResult = FailedCreateAPIKeyResult | SuccessCreateAPIKeyResult;
|
||||
type NormalizedAlertAction = Omit<AlertAction, 'actionTypeId'>;
|
||||
|
||||
interface ConstructorOptions {
|
||||
logger: Logger;
|
||||
|
@ -62,9 +63,15 @@ interface CreateOptions {
|
|||
Alert,
|
||||
Exclude<
|
||||
keyof Alert,
|
||||
'createdBy' | 'updatedBy' | 'apiKey' | 'apiKeyOwner' | 'muteAll' | 'mutedInstanceIds'
|
||||
| 'createdBy'
|
||||
| 'updatedBy'
|
||||
| 'apiKey'
|
||||
| 'apiKeyOwner'
|
||||
| 'muteAll'
|
||||
| 'mutedInstanceIds'
|
||||
| 'actions'
|
||||
>
|
||||
>;
|
||||
> & { actions: NormalizedAlertAction[] };
|
||||
options?: {
|
||||
migrationVersion?: Record<string, string>;
|
||||
};
|
||||
|
@ -76,7 +83,7 @@ interface UpdateOptions {
|
|||
name: string;
|
||||
tags: string[];
|
||||
interval: string;
|
||||
actions: AlertAction[];
|
||||
actions: NormalizedAlertAction[];
|
||||
params: Record<string, any>;
|
||||
};
|
||||
}
|
||||
|
@ -117,8 +124,10 @@ export class AlertsClient {
|
|||
|
||||
this.validateActions(alertType, data.actions);
|
||||
|
||||
const { alert: rawAlert, references } = this.getRawAlert({
|
||||
const { references, actions } = await this.denormalizeActions(data.actions);
|
||||
const rawAlert: RawAlert = {
|
||||
...data,
|
||||
actions,
|
||||
createdBy: username,
|
||||
updatedBy: username,
|
||||
apiKeyOwner: apiKey.created && username ? username : undefined,
|
||||
|
@ -128,7 +137,7 @@ export class AlertsClient {
|
|||
params: validatedAlertTypeParams,
|
||||
muteAll: false,
|
||||
mutedInstanceIds: [],
|
||||
});
|
||||
};
|
||||
const createdAlert = await this.savedObjectsClient.create('alert', rawAlert, {
|
||||
...options,
|
||||
references,
|
||||
|
@ -202,7 +211,7 @@ export class AlertsClient {
|
|||
const validatedAlertTypeParams = validateAlertTypeParams(alertType, data.params);
|
||||
this.validateActions(alertType, data.actions);
|
||||
|
||||
const { actions, references } = this.extractReferences(data.actions);
|
||||
const { actions, references } = await this.denormalizeActions(data.actions);
|
||||
const username = await this.getUserName();
|
||||
const updatedObject = await this.savedObjectsClient.update(
|
||||
'alert',
|
||||
|
@ -371,26 +380,6 @@ export class AlertsClient {
|
|||
});
|
||||
}
|
||||
|
||||
private extractReferences(actions: Alert['actions']) {
|
||||
const references: SavedObjectReference[] = [];
|
||||
const rawActions = actions.map((action, i) => {
|
||||
const actionRef = `action_${i}`;
|
||||
references.push({
|
||||
name: actionRef,
|
||||
type: 'action',
|
||||
id: action.id,
|
||||
});
|
||||
return {
|
||||
...omit(action, 'id'),
|
||||
actionRef,
|
||||
};
|
||||
}) as RawAlert['actions'];
|
||||
return {
|
||||
actions: rawActions,
|
||||
references,
|
||||
};
|
||||
}
|
||||
|
||||
private injectReferencesIntoActions(
|
||||
actions: RawAlert['actions'],
|
||||
references: SavedObjectReference[]
|
||||
|
@ -426,19 +415,7 @@ export class AlertsClient {
|
|||
};
|
||||
}
|
||||
|
||||
private getRawAlert(alert: Alert): { alert: RawAlert; references: SavedObjectReference[] } {
|
||||
const { references, actions } = this.extractReferences(alert.actions);
|
||||
return {
|
||||
alert: {
|
||||
...alert,
|
||||
actions,
|
||||
},
|
||||
references,
|
||||
};
|
||||
}
|
||||
|
||||
private validateActions(alertType: AlertType, actions: Alert['actions']) {
|
||||
// TODO: Should also ensure user has access to each action
|
||||
private validateActions(alertType: AlertType, actions: NormalizedAlertAction[]): void {
|
||||
const { actionGroups: alertTypeActionGroups } = alertType;
|
||||
const usedAlertActionGroups = actions.map(action => action.group);
|
||||
const invalidActionGroups = usedAlertActionGroups.filter(
|
||||
|
@ -455,4 +432,41 @@ export class AlertsClient {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async denormalizeActions(
|
||||
alertActions: NormalizedAlertAction[]
|
||||
): Promise<{ actions: RawAlert['actions']; references: SavedObjectReference[] }> {
|
||||
// Fetch action objects in bulk
|
||||
const actionIds = [...new Set(alertActions.map(alertAction => alertAction.id))];
|
||||
const bulkGetOpts = actionIds.map(id => ({ id, type: 'action' }));
|
||||
const bulkGetResult = await this.savedObjectsClient.bulkGet(bulkGetOpts);
|
||||
const actionMap = new Map<string, any>();
|
||||
for (const action of bulkGetResult.saved_objects) {
|
||||
if (action.error) {
|
||||
throw Boom.badRequest(
|
||||
`Failed to load action ${action.id} (${action.error.statusCode}): ${action.error.message}`
|
||||
);
|
||||
}
|
||||
actionMap.set(action.id, action);
|
||||
}
|
||||
// Extract references and set actionTypeId
|
||||
const references: SavedObjectReference[] = [];
|
||||
const actions = alertActions.map(({ id, ...alertAction }, i) => {
|
||||
const actionRef = `action_${i}`;
|
||||
references.push({
|
||||
id,
|
||||
name: actionRef,
|
||||
type: 'action',
|
||||
});
|
||||
return {
|
||||
...alertAction,
|
||||
actionRef,
|
||||
actionTypeId: actionMap.get(id).attributes.actionTypeId,
|
||||
};
|
||||
});
|
||||
return {
|
||||
actions,
|
||||
references,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ const createExecutionHandlerParams = {
|
|||
{
|
||||
id: '1',
|
||||
group: 'default',
|
||||
actionTypeId: 'test',
|
||||
params: {
|
||||
foo: true,
|
||||
contextVal: 'My {{context.value}} goes here',
|
||||
|
|
|
@ -41,6 +41,12 @@ test('creates an alert with proper parameters', async () => {
|
|||
alertsClient.create.mockResolvedValueOnce({
|
||||
...mockedAlert,
|
||||
id: '123',
|
||||
actions: [
|
||||
{
|
||||
...mockedAlert.actions[0],
|
||||
actionTypeId: 'test',
|
||||
},
|
||||
],
|
||||
});
|
||||
const { payload, statusCode } = await server.inject(request);
|
||||
expect(statusCode).toBe(200);
|
||||
|
@ -49,6 +55,7 @@ test('creates an alert with proper parameters', async () => {
|
|||
Object {
|
||||
"actions": Array [
|
||||
Object {
|
||||
"actionTypeId": "test",
|
||||
"group": "default",
|
||||
"id": "2",
|
||||
"params": Object {
|
||||
|
@ -97,33 +104,4 @@ test('creates an alert with proper parameters', async () => {
|
|||
},
|
||||
]
|
||||
`);
|
||||
expect(alertsClient.create).toHaveBeenCalledTimes(1);
|
||||
expect(alertsClient.create.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"data": Object {
|
||||
"actions": Array [
|
||||
Object {
|
||||
"group": "default",
|
||||
"id": "2",
|
||||
"params": Object {
|
||||
"foo": true,
|
||||
},
|
||||
},
|
||||
],
|
||||
"alertTypeId": "1",
|
||||
"enabled": true,
|
||||
"interval": "10s",
|
||||
"name": "abc",
|
||||
"params": Object {
|
||||
"bar": true,
|
||||
},
|
||||
"tags": Array [
|
||||
"foo",
|
||||
],
|
||||
"throttle": null,
|
||||
},
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
|
||||
import Hapi from 'hapi';
|
||||
import Joi from 'joi';
|
||||
import { AlertAction } from '../types';
|
||||
import { getDurationSchema } from '../lib';
|
||||
|
||||
interface ScheduleRequest extends Hapi.Request {
|
||||
|
@ -16,7 +15,11 @@ interface ScheduleRequest extends Hapi.Request {
|
|||
tags: string[];
|
||||
alertTypeId: string;
|
||||
interval: string;
|
||||
actions: AlertAction[];
|
||||
actions: Array<{
|
||||
group: string;
|
||||
id: string;
|
||||
params: Record<string, any>;
|
||||
}>;
|
||||
params: Record<string, any>;
|
||||
throttle: string | null;
|
||||
};
|
||||
|
|
|
@ -21,6 +21,7 @@ const mockedAlert = {
|
|||
{
|
||||
group: 'default',
|
||||
id: '2',
|
||||
actionTypeId: 'test',
|
||||
params: {
|
||||
foo: true,
|
||||
},
|
||||
|
|
|
@ -24,6 +24,7 @@ const mockedResponse = {
|
|||
{
|
||||
group: 'default',
|
||||
id: '2',
|
||||
actionTypeId: 'test',
|
||||
params: {
|
||||
baz: true,
|
||||
},
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
|
||||
import Joi from 'joi';
|
||||
import Hapi from 'hapi';
|
||||
import { AlertAction } from '../types';
|
||||
import { getDurationSchema } from '../lib';
|
||||
|
||||
interface UpdateRequest extends Hapi.Request {
|
||||
|
@ -18,7 +17,11 @@ interface UpdateRequest extends Hapi.Request {
|
|||
name: string;
|
||||
tags: string[];
|
||||
interval: string;
|
||||
actions: AlertAction[];
|
||||
actions: Array<{
|
||||
group: string;
|
||||
id: string;
|
||||
params: Record<string, any>;
|
||||
}>;
|
||||
params: Record<string, any>;
|
||||
throttle: string | null;
|
||||
};
|
||||
|
|
|
@ -49,12 +49,14 @@ export type AlertActionParams = SavedObjectAttributes;
|
|||
export interface AlertAction {
|
||||
group: string;
|
||||
id: string;
|
||||
actionTypeId: string;
|
||||
params: AlertActionParams;
|
||||
}
|
||||
|
||||
export interface RawAlertAction extends SavedObjectAttributes {
|
||||
group: string;
|
||||
actionRef: string;
|
||||
actionTypeId: string;
|
||||
params: AlertActionParams;
|
||||
}
|
||||
|
||||
|
|
|
@ -532,6 +532,7 @@ export default function alertTests({ getService }: FtrProviderContext) {
|
|||
break;
|
||||
case 'space_1_all at space1':
|
||||
case 'superuser at space1':
|
||||
expect(response.statusCode).to.eql(200);
|
||||
// Wait for actions to execute twice before disabling the alert and waiting for tasks to finish
|
||||
await esTestIndexTool.waitForDocs('action:test.index-record', reference, 2);
|
||||
await alertUtils.disable(response.body.id);
|
||||
|
|
|
@ -31,11 +31,32 @@ export default function createAlertTests({ getService }: FtrProviderContext) {
|
|||
const { user, space } = scenario;
|
||||
describe(scenario.id, () => {
|
||||
it('should handle create alert request appropriately', async () => {
|
||||
const { body: createdAction } = await supertest
|
||||
.post(`${getUrlPrefix(space.id)}/api/action`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
name: 'MY action',
|
||||
actionTypeId: 'test.noop',
|
||||
config: {},
|
||||
secrets: {},
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
const response = await supertestWithoutAuth
|
||||
.post(`${getUrlPrefix(space.id)}/api/alert`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.auth(user.username, user.password)
|
||||
.send(getTestAlertData());
|
||||
.send(
|
||||
getTestAlertData({
|
||||
actions: [
|
||||
{
|
||||
id: createdAction.id,
|
||||
group: 'default',
|
||||
params: {},
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
switch (scenario.id) {
|
||||
case 'no_kibana_privileges at space1':
|
||||
|
@ -56,7 +77,14 @@ export default function createAlertTests({ getService }: FtrProviderContext) {
|
|||
id: response.body.id,
|
||||
name: 'abc',
|
||||
tags: ['foo'],
|
||||
actions: [],
|
||||
actions: [
|
||||
{
|
||||
id: createdAction.id,
|
||||
actionTypeId: createdAction.actionTypeId,
|
||||
group: 'default',
|
||||
params: {},
|
||||
},
|
||||
],
|
||||
enabled: true,
|
||||
alertTypeId: 'test.noop',
|
||||
params: {},
|
||||
|
|
|
@ -78,10 +78,32 @@ export default function createFindTests({ getService }: FtrProviderContext) {
|
|||
});
|
||||
|
||||
it('should handle find alert request with filter appropriately', async () => {
|
||||
const { body: createdAction } = await supertest
|
||||
.post(`${getUrlPrefix(space.id)}/api/action`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
name: 'My action',
|
||||
actionTypeId: 'test.noop',
|
||||
config: {},
|
||||
secrets: {},
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
const { body: createdAlert } = await supertest
|
||||
.post(`${getUrlPrefix(space.id)}/api/alert`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send(getTestAlertData())
|
||||
.send(
|
||||
getTestAlertData({
|
||||
enabled: false,
|
||||
actions: [
|
||||
{
|
||||
id: createdAction.id,
|
||||
group: 'default',
|
||||
params: {},
|
||||
},
|
||||
],
|
||||
})
|
||||
)
|
||||
.expect(200);
|
||||
objectRemover.add(space.id, createdAlert.id, 'alert');
|
||||
|
||||
|
@ -89,7 +111,7 @@ export default function createFindTests({ getService }: FtrProviderContext) {
|
|||
.get(
|
||||
`${getUrlPrefix(
|
||||
space.id
|
||||
)}/api/alert/_find?filter=alert.attributes.alertTypeId:test.noop`
|
||||
)}/api/alert/_find?filter=alert.attributes.actions:{ actionTypeId: test.noop }`
|
||||
)
|
||||
.auth(user.username, user.password);
|
||||
|
||||
|
@ -117,11 +139,17 @@ export default function createFindTests({ getService }: FtrProviderContext) {
|
|||
tags: ['foo'],
|
||||
alertTypeId: 'test.noop',
|
||||
interval: '1m',
|
||||
enabled: true,
|
||||
actions: [],
|
||||
enabled: false,
|
||||
actions: [
|
||||
{
|
||||
id: createdAction.id,
|
||||
group: 'default',
|
||||
actionTypeId: 'test.noop',
|
||||
params: {},
|
||||
},
|
||||
],
|
||||
params: {},
|
||||
createdBy: 'elastic',
|
||||
scheduledTaskId: match.scheduledTaskId,
|
||||
throttle: '1m',
|
||||
updatedBy: 'elastic',
|
||||
apiKeyOwner: 'elastic',
|
||||
|
|
|
@ -27,10 +27,31 @@ export default function createAlertTests({ getService }: FtrProviderContext) {
|
|||
}
|
||||
|
||||
it('should handle create alert request appropriately', async () => {
|
||||
const { body: createdAction } = await supertest
|
||||
.post(`${getUrlPrefix(Spaces.space1.id)}/api/action`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
name: 'MY action',
|
||||
actionTypeId: 'test.noop',
|
||||
config: {},
|
||||
secrets: {},
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
const response = await supertest
|
||||
.post(`${getUrlPrefix(Spaces.space1.id)}/api/alert`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send(getTestAlertData());
|
||||
.send(
|
||||
getTestAlertData({
|
||||
actions: [
|
||||
{
|
||||
id: createdAction.id,
|
||||
group: 'default',
|
||||
params: {},
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
expect(response.statusCode).to.eql(200);
|
||||
objectRemover.add(Spaces.space1.id, response.body.id, 'alert');
|
||||
|
@ -38,7 +59,14 @@ export default function createAlertTests({ getService }: FtrProviderContext) {
|
|||
id: response.body.id,
|
||||
name: 'abc',
|
||||
tags: ['foo'],
|
||||
actions: [],
|
||||
actions: [
|
||||
{
|
||||
id: createdAction.id,
|
||||
actionTypeId: createdAction.actionTypeId,
|
||||
group: 'default',
|
||||
params: {},
|
||||
},
|
||||
],
|
||||
enabled: true,
|
||||
alertTypeId: 'test.noop',
|
||||
params: {},
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue