[Alerting] Implemented ability to edit an alert from the alert details page (#64273)

* Implemented ability to edit an alert from the alert details page

* Fixed refresh and tests

* fixed jest tests
This commit is contained in:
Yuliia Naumenko 2020-04-27 11:07:29 -07:00 committed by GitHub
parent 60cb9367a6
commit 75fa843652
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 209 additions and 10 deletions

View file

@ -7,24 +7,55 @@ import * as React from 'react';
import uuid from 'uuid';
import { shallow } from 'enzyme';
import { AlertDetails } from './alert_details';
import { Alert, ActionType } from '../../../../types';
import { EuiTitle, EuiBadge, EuiFlexItem, EuiSwitch, EuiBetaBadge } from '@elastic/eui';
import { Alert, ActionType, AlertTypeRegistryContract } from '../../../../types';
import {
EuiTitle,
EuiBadge,
EuiFlexItem,
EuiSwitch,
EuiBetaBadge,
EuiButtonEmpty,
} from '@elastic/eui';
import { times, random } from 'lodash';
import { i18n } from '@kbn/i18n';
import { ViewInApp } from './view_in_app';
import { PLUGIN } from '../../../constants/plugin';
import { coreMock } from 'src/core/public/mocks';
const mockes = coreMock.createSetup();
jest.mock('../../../app_context', () => ({
useAppDependencies: jest.fn(() => ({
http: jest.fn(),
legacy: {
capabilities: {
get: jest.fn(() => ({})),
},
capabilities: {
get: jest.fn(() => ({})),
},
actionTypeRegistry: jest.fn(),
alertTypeRegistry: jest.fn(() => {
const mocked: jest.Mocked<AlertTypeRegistryContract> = {
has: jest.fn(),
register: jest.fn(),
get: jest.fn(),
list: jest.fn(),
};
return mocked;
}),
toastNotifications: mockes.notifications.toasts,
docLinks: { ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' },
uiSettings: mockes.uiSettings,
dataPlugin: jest.fn(),
charts: jest.fn(),
})),
}));
jest.mock('react-router-dom', () => ({
useHistory: () => ({
push: jest.fn(),
}),
useLocation: () => ({
pathname: '/triggersActions/alerts/',
}),
}));
jest.mock('../../../lib/capabilities', () => ({
hasSaveAlertsCapability: jest.fn(() => true),
}));
@ -232,6 +263,28 @@ describe('alert_details', () => {
).containsMatchingElement(<ViewInApp alert={alert} />)
).toBeTruthy();
});
it('links to the Edit flyout', () => {
const alert = mockAlert();
const alertType = {
id: '.noop',
name: 'No Op',
actionGroups: [{ id: 'default', name: 'Default' }],
actionVariables: { context: [], state: [] },
defaultActionGroupId: 'default',
};
expect(
shallow(
<AlertDetails alert={alert} alertType={alertType} actionTypes={[]} {...mockAlertApis} />
)
.find(EuiButtonEmpty)
.find('[data-test-subj="openEditAlertFlyoutButton"]')
.first()
.exists()
).toBeTruthy();
});
});
});

View file

@ -4,8 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React, { useState } from 'react';
import React, { useState, Fragment } from 'react';
import { indexBy } from 'lodash';
import { useHistory } from 'react-router-dom';
import {
EuiPageBody,
EuiPageContent,
@ -21,6 +22,7 @@ import {
EuiCallOut,
EuiSpacer,
EuiBetaBadge,
EuiButtonEmpty,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
@ -34,6 +36,9 @@ import {
import { AlertInstancesRouteWithApi } from './alert_instances_route';
import { ViewInApp } from './view_in_app';
import { PLUGIN } from '../../../constants/plugin';
import { AlertEdit } from '../../alert_form';
import { AlertsContextProvider } from '../../../context/alerts_context';
import { routeToAlertDetails } from '../../../constants';
type AlertDetailsProps = {
alert: Alert;
@ -52,7 +57,18 @@ export const AlertDetails: React.FunctionComponent<AlertDetailsProps> = ({
muteAlert,
requestRefresh,
}) => {
const { capabilities } = useAppDependencies();
const history = useHistory();
const {
http,
toastNotifications,
capabilities,
alertTypeRegistry,
actionTypeRegistry,
uiSettings,
docLinks,
charts,
dataPlugin,
} = useAppDependencies();
const canSave = hasSaveAlertsCapability(capabilities);
@ -61,6 +77,11 @@ export const AlertDetails: React.FunctionComponent<AlertDetailsProps> = ({
const [isEnabled, setIsEnabled] = useState<boolean>(alert.enabled);
const [isMuted, setIsMuted] = useState<boolean>(alert.muteAll);
const [editFlyoutVisible, setEditFlyoutVisibility] = useState<boolean>(false);
const setAlert = async () => {
history.push(routeToAlertDetails.replace(`:alertId`, alert.id));
};
return (
<EuiPage>
@ -90,6 +111,42 @@ export const AlertDetails: React.FunctionComponent<AlertDetailsProps> = ({
</EuiPageContentHeaderSection>
<EuiPageContentHeaderSection>
<EuiFlexGroup responsive={false} gutterSize="xs">
{canSave ? (
<EuiFlexItem grow={false}>
<Fragment>
{' '}
<EuiButtonEmpty
data-test-subj="openEditAlertFlyoutButton"
iconType="pencil"
onClick={() => setEditFlyoutVisibility(true)}
>
<FormattedMessage
id="xpack.triggersActionsUI.sections.alertDetails.editAlertButtonLabel"
defaultMessage="Edit"
/>
</EuiButtonEmpty>
<AlertsContextProvider
value={{
http,
actionTypeRegistry,
alertTypeRegistry,
toastNotifications,
uiSettings,
docLinks,
charts,
dataFieldsFormats: dataPlugin.fieldFormats,
reloadAlerts: setAlert,
}}
>
<AlertEdit
initialAlert={alert}
editFlyoutVisible={editFlyoutVisible}
setEditFlyoutVisibility={setEditFlyoutVisibility}
/>
</AlertsContextProvider>
</Fragment>
</EuiFlexItem>
) : null}
<EuiFlexItem grow={false}>
<ViewInApp alert={alert} />
</EuiFlexItem>

View file

@ -26,13 +26,13 @@ export const AlertInstancesRoute: React.FunctionComponent<WithAlertStateProps> =
requestRefresh,
loadAlertState,
}) => {
const { http, toastNotifications } = useAppDependencies();
const { toastNotifications } = useAppDependencies();
const [alertState, setAlertState] = useState<AlertTaskState | null>(null);
useEffect(() => {
getAlertState(alert.id, loadAlertState, setAlertState, toastNotifications);
}, [alert, http, loadAlertState, toastNotifications]);
}, [alert, loadAlertState, toastNotifications]);
return alertState ? (
<AlertInstances requestRefresh={requestRefresh} alert={alert} alertState={alertState} />

View file

@ -17,6 +17,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
const log = getService('log');
const alerting = getService('alerting');
const retry = getService('retry');
const find = getService('find');
describe('Alert Details', function() {
describe('Header', function() {
@ -148,6 +149,56 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
});
});
describe('Edit alert button', function() {
const testRunUuid = uuid.v4();
it('should open edit alert flyout', async () => {
await pageObjects.common.navigateToApp('triggersActions');
const params = {
aggType: 'count',
termSize: 5,
thresholdComparator: '>',
timeWindowSize: 5,
timeWindowUnit: 'm',
groupBy: 'all',
threshold: [1000, 5000],
index: ['.kibana_1'],
timeField: 'alert',
};
const alert = await alerting.alerts.createAlertWithActions(
testRunUuid,
'.index-threshold',
params
);
// refresh to see alert
await browser.refresh();
await pageObjects.header.waitUntilLoadingHasFinished();
// Verify content
await testSubjects.existOrFail('alertsList');
// click on first alert
await pageObjects.triggersActionsUI.clickOnAlertInAlertsList(alert.name);
const editButton = await testSubjects.find('openEditAlertFlyoutButton');
await editButton.click();
const updatedAlertName = `Changed Alert Name ${uuid.v4()}`;
await testSubjects.setValue('alertNameInput', updatedAlertName, {
clearWithKeyboard: true,
});
await find.clickByCssSelector('[data-test-subj="saveEditedAlertButton"]:not(disabled)');
const toastTitle = await pageObjects.common.closeToast();
expect(toastTitle).to.eql(`Updated '${updatedAlertName}'`);
const headingText = await pageObjects.alertDetailsUI.getHeadingText();
expect(headingText).to.be(updatedAlertName);
});
});
describe('View In App', function() {
const testRunUuid = uuid.v4();

View file

@ -22,6 +22,44 @@ export class Alerts {
});
}
public async createAlertWithActions(
name: string,
alertTypeId: string,
params?: Record<string, any>,
actions?: Array<{
id: string;
group: string;
params: Record<string, any>;
}>,
tags?: string[],
consumer?: string,
schedule?: Record<string, any>,
throttle?: string
) {
this.log.debug(`creating alert ${name}`);
const { data: alert, status, statusText } = await this.axios.post(`/api/alert`, {
enabled: true,
name,
tags,
alertTypeId,
consumer: consumer ?? 'bar',
schedule: schedule ?? { interval: '1m' },
throttle: throttle ?? '1m',
actions: actions ?? [],
params: params ?? {},
});
if (status !== 200) {
throw new Error(
`Expected status code of 200, received ${status} ${statusText}: ${util.inspect(alert)}`
);
}
this.log.debug(`created alert ${alert.id}`);
return alert;
}
public async createNoOp(name: string) {
this.log.debug(`creating alert ${name}`);