Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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),
}));
Expand Down Expand Up @@ -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();
});
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -21,6 +22,7 @@ import {
EuiCallOut,
EuiSpacer,
EuiBetaBadge,
EuiButtonEmpty,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
Expand All @@ -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;
Expand All @@ -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);

Expand All @@ -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>
Expand Down Expand Up @@ -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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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
);
Comment on lines +168 to +172
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good approach, should definitely speed things up. 👍

// 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();

Expand Down
38 changes: 38 additions & 0 deletions x-pack/test/functional_with_es_ssl/services/alerting/alerts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`);

Expand Down