From ac5fe6f0f7c9e2839ad43ac7d055efc378e79ff0 Mon Sep 17 00:00:00 2001 From: Yuliia Naumenko Date: Mon, 27 Apr 2020 15:33:08 -0700 Subject: [PATCH] [Alerting] Implemented ability to edit an alert from the alert details page (#64273) (#64579) * Implemented ability to edit an alert from the alert details page * Fixed refresh and tests * fixed jest tests --- .../components/alert_details.test.tsx | 65 +++++++++++++++++-- .../components/alert_details.tsx | 61 ++++++++++++++++- .../components/alert_instances_route.tsx | 4 +- .../apps/triggers_actions_ui/details.ts | 51 +++++++++++++++ .../services/alerting/alerts.ts | 38 +++++++++++ 5 files changed, 209 insertions(+), 10 deletions(-) diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx index 9da4f059f8967..230b896eeca7d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx @@ -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 = { + 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() ).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( + + ) + .find(EuiButtonEmpty) + .find('[data-test-subj="openEditAlertFlyoutButton"]') + .first() + .exists() + ).toBeTruthy(); + }); }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.tsx index 5bfcf9fd2d4e6..318dd28d92da1 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.tsx @@ -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 = ({ 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 = ({ const [isEnabled, setIsEnabled] = useState(alert.enabled); const [isMuted, setIsMuted] = useState(alert.muteAll); + const [editFlyoutVisible, setEditFlyoutVisibility] = useState(false); + + const setAlert = async () => { + history.push(routeToAlertDetails.replace(`:alertId`, alert.id)); + }; return ( @@ -90,6 +111,42 @@ export const AlertDetails: React.FunctionComponent = ({ + {canSave ? ( + + + {' '} + setEditFlyoutVisibility(true)} + > + + + + + + + + ) : null} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.tsx index 498ecffe9b947..b9d08abae1684 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.tsx @@ -26,13 +26,13 @@ export const AlertInstancesRoute: React.FunctionComponent = requestRefresh, loadAlertState, }) => { - const { http, toastNotifications } = useAppDependencies(); + const { toastNotifications } = useAppDependencies(); const [alertState, setAlertState] = useState(null); useEffect(() => { getAlertState(alert.id, loadAlertState, setAlertState, toastNotifications); - }, [alert, http, loadAlertState, toastNotifications]); + }, [alert, loadAlertState, toastNotifications]); return alertState ? ( diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts index 2c29954528bd5..1b75f4a27766a 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts @@ -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(); diff --git a/x-pack/test/functional_with_es_ssl/services/alerting/alerts.ts b/x-pack/test/functional_with_es_ssl/services/alerting/alerts.ts index 5b506c20e029c..2a0d28f246765 100644 --- a/x-pack/test/functional_with_es_ssl/services/alerting/alerts.ts +++ b/x-pack/test/functional_with_es_ssl/services/alerting/alerts.ts @@ -22,6 +22,44 @@ export class Alerts { }); } + public async createAlertWithActions( + name: string, + alertTypeId: string, + params?: Record, + actions?: Array<{ + id: string; + group: string; + params: Record; + }>, + tags?: string[], + consumer?: string, + schedule?: Record, + 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}`);