Skip to content

Commit

Permalink
Edit alert flyout (#58964)
Browse files Browse the repository at this point in the history
* Implemented edit alert functionality

* Added unit tests

* Added functional test for edit alert

* Fixed failed tests

* Fixed edit api

* Fixed due to comments

* Fixed functional test

* Fixed tests

* Fixed add alert

* Small type fix

* Fixed jest test

* Fixed type check

* Fixed bugs with interval and throttle + index threshold expression
  • Loading branch information
YulNaumenko committed Mar 4, 2020
1 parent ac4f8f4 commit daf6226
Show file tree
Hide file tree
Showing 21 changed files with 508 additions and 99 deletions.

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -132,16 +132,24 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent<IndexThr
}
);

const setDefaultExpressionValues = () => {
const setDefaultExpressionValues = async () => {
setAlertProperty('params', {
aggType: DEFAULT_VALUES.AGGREGATION_TYPE,
termSize: DEFAULT_VALUES.TERM_SIZE,
thresholdComparator: DEFAULT_VALUES.THRESHOLD_COMPARATOR,
timeWindowSize: DEFAULT_VALUES.TIME_WINDOW_SIZE,
timeWindowUnit: DEFAULT_VALUES.TIME_WINDOW_UNIT,
groupBy: DEFAULT_VALUES.GROUP_BY,
threshold: DEFAULT_VALUES.THRESHOLD,
...alertParams,
aggType: aggType ?? DEFAULT_VALUES.AGGREGATION_TYPE,
termSize: termSize ?? DEFAULT_VALUES.TERM_SIZE,
thresholdComparator: thresholdComparator ?? DEFAULT_VALUES.THRESHOLD_COMPARATOR,
timeWindowSize: timeWindowSize ?? DEFAULT_VALUES.TIME_WINDOW_SIZE,
timeWindowUnit: timeWindowUnit ?? DEFAULT_VALUES.TIME_WINDOW_UNIT,
groupBy: groupBy ?? DEFAULT_VALUES.GROUP_BY,
threshold: threshold ?? DEFAULT_VALUES.THRESHOLD,
});
if (index.length > 0) {
const currentEsFields = await getFields(index);
const timeFields = getTimeFieldOptions(currentEsFields as any);

setEsFields(currentEsFields);
setTimeFieldOptions([firstFieldOption, ...timeFields]);
}
};

const getFields = async (indexes: string[]) => {
Expand Down Expand Up @@ -258,7 +266,17 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent<IndexThr
// reset time field and expression fields if indices are deleted
if (indices.length === 0) {
setTimeFieldOptions([firstFieldOption]);
setDefaultExpressionValues();
setAlertProperty('params', {
...alertParams,
index: indices,
aggType: DEFAULT_VALUES.AGGREGATION_TYPE,
termSize: DEFAULT_VALUES.TERM_SIZE,
thresholdComparator: DEFAULT_VALUES.THRESHOLD_COMPARATOR,
timeWindowSize: DEFAULT_VALUES.TIME_WINDOW_SIZE,
timeWindowUnit: DEFAULT_VALUES.TIME_WINDOW_UNIT,
groupBy: DEFAULT_VALUES.GROUP_BY,
threshold: DEFAULT_VALUES.THRESHOLD,
});
return;
}
const currentEsFields = await getFields(indices);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ import { TypeRegistry } from '../type_registry';
import { AlertTypeModel, ActionTypeModel } from '../../types';

export interface AlertsContextValue {
addFlyoutVisible: boolean;
setAddFlyoutVisibility: React.Dispatch<React.SetStateAction<boolean>>;
reloadAlerts?: () => Promise<void>;
http: HttpSetup;
alertTypeRegistry: TypeRegistry<AlertTypeModel>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -443,7 +443,7 @@ describe('updateAlert', () => {
Array [
"/api/alert/123",
Object {
"body": "{\\"throttle\\":\\"1m\\",\\"consumer\\":\\"alerting\\",\\"name\\":\\"test\\",\\"tags\\":[\\"foo\\"],\\"schedule\\":{\\"interval\\":\\"1m\\"},\\"params\\":{},\\"actions\\":[],\\"createdAt\\":\\"1970-01-01T00:00:00.000Z\\",\\"updatedAt\\":\\"1970-01-01T00:00:00.000Z\\",\\"apiKey\\":null,\\"apiKeyOwner\\":null}",
"body": "{\\"throttle\\":\\"1m\\",\\"name\\":\\"test\\",\\"tags\\":[\\"foo\\"],\\"schedule\\":{\\"interval\\":\\"1m\\"},\\"params\\":{},\\"actions\\":[]}",
},
]
`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { HttpSetup } from 'kibana/public';
import * as t from 'io-ts';
import { pipe } from 'fp-ts/lib/pipeable';
import { fold } from 'fp-ts/lib/Either';
import { pick } from 'lodash';
import { alertStateSchema } from '../../../../alerting/common';
import { BASE_ALERT_API_PATH } from '../constants';
import { Alert, AlertType, AlertWithoutId, AlertTaskState } from '../../types';
Expand Down Expand Up @@ -126,7 +127,9 @@ export async function updateAlert({
id: string;
}): Promise<Alert> {
return await http.put(`${BASE_ALERT_API_PATH}/${id}`, {
body: JSON.stringify(alert),
body: JSON.stringify(
pick(alert, ['throttle', 'name', 'tags', 'schedule', 'params', 'actions'])
),
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,6 @@ describe('alert_add', () => {
wrapper = mountWithIntl(
<AlertsContextProvider
value={{
addFlyoutVisible: true,
setAddFlyoutVisibility: state => {},
reloadAlerts: () => {
return new Promise<void>(() => {});
},
Expand All @@ -81,7 +79,11 @@ describe('alert_add', () => {
uiSettings: deps.uiSettings,
}}
>
<AlertAdd consumer={'alerting'} />
<AlertAdd
consumer={'alerting'}
addFlyoutVisible={true}
setAddFlyoutVisibility={state => {}}
/>
</AlertsContextProvider>
);
// Wait for active space to resolve before requesting the component to update
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,19 @@ import { createAlert } from '../../lib/alert_api';

interface AlertAddProps {
consumer: string;
addFlyoutVisible: boolean;
setAddFlyoutVisibility: React.Dispatch<React.SetStateAction<boolean>>;
alertTypeId?: string;
canChangeTrigger?: boolean;
}

export const AlertAdd = ({ consumer, canChangeTrigger, alertTypeId }: AlertAddProps) => {
export const AlertAdd = ({
consumer,
addFlyoutVisible,
setAddFlyoutVisibility,
canChangeTrigger,
alertTypeId,
}: AlertAddProps) => {
const initialAlert = ({
params: {},
consumer,
Expand All @@ -51,8 +59,6 @@ export const AlertAdd = ({ consumer, canChangeTrigger, alertTypeId }: AlertAddPr
};

const {
addFlyoutVisible,
setAddFlyoutVisibility,
reloadAlerts,
http,
toastNotifications,
Expand All @@ -74,7 +80,7 @@ export const AlertAdd = ({ consumer, canChangeTrigger, alertTypeId }: AlertAddPr
return null;
}

const alertType = alertTypeRegistry.get(alert.alertTypeId);
const alertType = alert.alertTypeId ? alertTypeRegistry.get(alert.alertTypeId) : null;
const errors = {
...(alertType ? alertType.validate(alert.params).errors : []),
...validateBaseProperties(alert).errors,
Expand Down Expand Up @@ -106,7 +112,7 @@ export const AlertAdd = ({ consumer, canChangeTrigger, alertTypeId }: AlertAddPr
const newAlert = await createAlert({ http, alert });
if (toastNotifications) {
toastNotifications.addSuccess(
i18n.translate('xpack.triggersActionsUI.sections.alertForm.saveSuccessNotificationText', {
i18n.translate('xpack.triggersActionsUI.sections.alertAdd.saveSuccessNotificationText', {
defaultMessage: "Saved '{alertName}'",
values: {
alertName: newAlert.name,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import * as React from 'react';
import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers';
import { act } from 'react-dom/test-utils';
import { coreMock } from '../../../../../../../src/core/public/mocks';
import { actionTypeRegistryMock } from '../../action_type_registry.mock';
import { ValidationResult } from '../../../types';
import { AlertsContextProvider } from '../../context/alerts_context';
import { alertTypeRegistryMock } from '../../alert_type_registry.mock';
import { ReactWrapper } from 'enzyme';
import { AlertEdit } from './alert_edit';
const actionTypeRegistry = actionTypeRegistryMock.create();
const alertTypeRegistry = alertTypeRegistryMock.create();

describe('alert_edit', () => {
let deps: any;
let wrapper: ReactWrapper<any>;

beforeAll(async () => {
const mockes = coreMock.createSetup();
deps = {
toastNotifications: mockes.notifications.toasts,
http: mockes.http,
uiSettings: mockes.uiSettings,
actionTypeRegistry: actionTypeRegistry as any,
alertTypeRegistry: alertTypeRegistry as any,
};
const alertType = {
id: 'my-alert-type',
iconClass: 'test',
name: 'test-alert',
validate: (): ValidationResult => {
return { errors: {} };
},
alertParamsExpression: () => <React.Fragment />,
};

const actionTypeModel = {
id: 'my-action-type',
iconClass: 'test',
selectMessage: 'test',
validateConnector: (): ValidationResult => {
return { errors: {} };
},
validateParams: (): ValidationResult => {
const validationResult = { errors: {} };
return validationResult;
},
actionConnectorFields: null,
actionParamsFields: null,
};

const alert = {
id: 'ab5661e0-197e-45ee-b477-302d89193b5e',
params: {
aggType: 'average',
threshold: [1000, 5000],
index: 'kibana_sample_data_flights',
timeField: 'timestamp',
aggField: 'DistanceMiles',
window: '1s',
comparator: 'between',
},
consumer: 'alerting',
alertTypeId: 'my-alert-type',
enabled: false,
schedule: { interval: '1m' },
actions: [
{
actionTypeId: 'my-action-type',
group: 'threshold met',
params: { message: 'Alert [{{ctx.metadata.name}}] has exceeded the threshold' },
message: 'Alert [{{ctx.metadata.name}}] has exceeded the threshold',
id: '917f5d41-fbc4-4056-a8ad-ac592f7dcee2',
},
],
tags: [],
name: 'test alert',
throttle: null,
apiKeyOwner: null,
createdBy: 'elastic',
updatedBy: 'elastic',
createdAt: new Date(),
muteAll: false,
mutedInstanceIds: [],
updatedAt: new Date(),
};
actionTypeRegistry.get.mockReturnValueOnce(actionTypeModel);
actionTypeRegistry.has.mockReturnValue(true);
alertTypeRegistry.list.mockReturnValue([alertType]);
alertTypeRegistry.get.mockReturnValue(alertType);
alertTypeRegistry.has.mockReturnValue(true);
actionTypeRegistry.list.mockReturnValue([actionTypeModel]);
actionTypeRegistry.has.mockReturnValue(true);

wrapper = mountWithIntl(
<AlertsContextProvider
value={{
reloadAlerts: () => {
return new Promise<void>(() => {});
},
http: deps!.http,
actionTypeRegistry: deps!.actionTypeRegistry,
alertTypeRegistry: deps!.alertTypeRegistry,
toastNotifications: deps!.toastNotifications,
uiSettings: deps!.uiSettings,
}}
>
<AlertEdit
editFlyoutVisible={true}
setEditFlyoutVisibility={() => {}}
initialAlert={alert}
/>
</AlertsContextProvider>
);
// Wait for active space to resolve before requesting the component to update
await act(async () => {
await nextTick();
wrapper.update();
});
});

it('renders alert add flyout', () => {
expect(wrapper.find('[data-test-subj="editAlertFlyoutTitle"]').exists()).toBeTruthy();
expect(wrapper.find('[data-test-subj="saveEditedAlertButton"]').exists()).toBeTruthy();
});
});
Loading

0 comments on commit daf6226

Please sign in to comment.