[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:
parent
60cb9367a6
commit
75fa843652
5 changed files with 209 additions and 10 deletions
|
@ -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(() => ({})),
|
||||
},
|
||||
},
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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} />
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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}`);
|
||||
|
||||
|
|
Loading…
Reference in a new issue