Skip to content

Commit

Permalink
[APM] Threshold alerts (#59566)
Browse files Browse the repository at this point in the history
* Add alerting/actions permissions for APM

* Export TIME_UNITS, getTimeUnitLabel from triggers actions UI plugin

* Add APM alert types and UI

* Review feedback

* Use Expression components for triggers

* Update alert name for transaction duration

* Change defaults for error rate trigger
  • Loading branch information
dgieselaar authored Mar 24, 2020
1 parent 57f9a9f commit 85c0be3
Show file tree
Hide file tree
Showing 45 changed files with 1,198 additions and 181 deletions.
22 changes: 16 additions & 6 deletions x-pack/legacy/plugins/apm/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,24 +91,34 @@ export const apm: LegacyPluginInitializer = kibana => {
navLinkId: 'apm',
app: ['apm', 'kibana'],
catalogue: ['apm'],
// see x-pack/plugins/features/common/feature_kibana_privileges.ts
privileges: {
all: {
api: ['apm', 'apm_write'],
api: ['apm', 'apm_write', 'actions-read', 'alerting-read'],
catalogue: ['apm'],
savedObject: {
all: [],
all: ['action', 'action_task_params'],
read: []
},
ui: ['show', 'save']
ui: [
'show',
'save',
'alerting:show',
'actions:show',
'alerting:save',
'actions:save',
'alerting:delete',
'actions:delete'
]
},
read: {
api: ['apm'],
api: ['apm', 'actions-read', 'alerting-read'],
catalogue: ['apm'],
savedObject: {
all: [],
all: ['action', 'action_task_params'],
read: []
},
ui: ['show']
ui: ['show', 'alerting:show', 'actions:show']
}
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import { APMIndicesPermission } from '../';
import * as hooks from '../../../../hooks/useFetcher';
import {
expectTextsInDocument,
MockApmPluginContextWrapper,
expectTextsNotInDocument
} from '../../../../utils/testHelpers';
import { MockApmPluginContextWrapper } from '../../../../context/ApmPluginContext/MockApmPluginContext';

describe('APMIndicesPermission', () => {
it('returns empty component when api status is loading', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,7 @@ import React from 'react';
import { mockMoment, toJson } from '../../../../../utils/testHelpers';
import { ErrorGroupList } from '../index';
import props from './props.json';
import { IUrlParams } from '../../../../../context/UrlParamsContext/types';
import {
useUiFilters,
UrlParamsContext
} from '../../../../../context/UrlParamsContext';

const mockRefreshTimeRange = jest.fn();
const MockUrlParamsProvider: React.FC<{
params?: IUrlParams;
}> = ({ params = props.urlParams, children }) => (
<UrlParamsContext.Provider
value={{
urlParams: params,
refreshTimeRange: mockRefreshTimeRange,
uiFilters: useUiFilters(params)
}}
children={children}
/>
);
import { MockUrlParamsContextProvider } from '../../../../../context/UrlParamsContext/MockUrlParamsContextProvider';

describe('ErrorGroupOverview -> List', () => {
beforeAll(() => {
Expand All @@ -37,9 +19,9 @@ describe('ErrorGroupOverview -> List', () => {
it('should render empty state', () => {
const storeState = {};
const wrapper = mount(
<MockUrlParamsProvider>
<MockUrlParamsContextProvider>
<ErrorGroupList items={[]} />
</MockUrlParamsProvider>,
</MockUrlParamsContextProvider>,
storeState
);

Expand All @@ -48,9 +30,9 @@ describe('ErrorGroupOverview -> List', () => {

it('should render with data', () => {
const wrapper = mount(
<MockUrlParamsProvider>
<MockUrlParamsContextProvider>
<ErrorGroupList items={props.items} />
</MockUrlParamsProvider>
</MockUrlParamsContextProvider>
);

expect(toJson(wrapper)).toMatchSnapshot();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,4 @@
{
"urlParams": {
"page": 0,
"serviceName": "opbeans-python",
"transactionType": "request",
"start": "2018-01-10T09:51:41.050Z",
"end": "2018-01-10T10:06:41.050Z"
},
"items": [
{
"message": "About to blow up!",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import { shallow } from 'enzyme';
import React from 'react';
import { Home } from '../Home';
import { MockApmPluginContextWrapper } from '../../../utils/testHelpers';
import { MockApmPluginContextWrapper } from '../../../context/ApmPluginContext/MockApmPluginContext';

describe('Home component', () => {
it('should render services', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ import { mount } from 'enzyme';
import React from 'react';
import { MemoryRouter } from 'react-router-dom';
import { ApmPluginContextValue } from '../../../context/ApmPluginContext';
import {
mockApmPluginContextValue,
MockApmPluginContextWrapper
} from '../../../utils/testHelpers';
import { routes } from './route_config';
import { UpdateBreadcrumbs } from './UpdateBreadcrumbs';
import {
MockApmPluginContextWrapper,
mockApmPluginContextValue
} from '../../../context/ApmPluginContext/MockApmPluginContext';

const setBreadcrumbs = jest.fn();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* 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 React from 'react';
import { AlertType } from '../../../../../../../../../plugins/apm/common/alert_types';
import { AlertAdd } from '../../../../../../../../../plugins/triggers_actions_ui/public';

type AlertAddProps = React.ComponentProps<typeof AlertAdd>;

interface Props {
addFlyoutVisible: AlertAddProps['addFlyoutVisible'];
setAddFlyoutVisibility: AlertAddProps['setAddFlyoutVisibility'];
alertType: AlertType | null;
}

export function AlertingFlyout(props: Props) {
const { addFlyoutVisible, setAddFlyoutVisibility, alertType } = props;

return alertType ? (
<AlertAdd
addFlyoutVisible={addFlyoutVisible}
setAddFlyoutVisibility={setAddFlyoutVisibility}
consumer="apm"
alertTypeId={alertType}
canChangeTrigger={false}
/>
) : null;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/*
* 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 {
EuiButtonEmpty,
EuiContextMenu,
EuiPopover,
EuiContextMenuPanelDescriptor
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React, { useState } from 'react';
import { AlertType } from '../../../../../../../../plugins/apm/common/alert_types';
import { AlertingFlyout } from './AlertingFlyout';
import { useApmPluginContext } from '../../../../hooks/useApmPluginContext';

const alertLabel = i18n.translate(
'xpack.apm.serviceDetails.alertsMenu.alerts',
{
defaultMessage: 'Alerts'
}
);

const createThresholdAlertLabel = i18n.translate(
'xpack.apm.serviceDetails.alertsMenu.createThresholdAlert',
{
defaultMessage: 'Create threshold alert'
}
);

const CREATE_THRESHOLD_ALERT_PANEL_ID = 'create_threshold';

interface Props {
canReadAlerts: boolean;
canSaveAlerts: boolean;
}

export function AlertIntegrations(props: Props) {
const { canSaveAlerts, canReadAlerts } = props;

const plugin = useApmPluginContext();

const [popoverOpen, setPopoverOpen] = useState(false);

const [alertType, setAlertType] = useState<AlertType | null>(null);

const button = (
<EuiButtonEmpty
iconType="arrowDown"
iconSide="right"
onClick={() => setPopoverOpen(true)}
>
{i18n.translate('xpack.apm.serviceDetails.alertsMenu.alerts', {
defaultMessage: 'Alerts'
})}
</EuiButtonEmpty>
);

const panels: EuiContextMenuPanelDescriptor[] = [
{
id: 0,
title: alertLabel,
items: [
...(canSaveAlerts
? [
{
name: createThresholdAlertLabel,
panel: CREATE_THRESHOLD_ALERT_PANEL_ID,
icon: 'bell'
}
]
: []),
...(canReadAlerts
? [
{
name: i18n.translate(
'xpack.apm.serviceDetails.alertsMenu.viewActiveAlerts',
{
defaultMessage: 'View active alerts'
}
),
href: plugin.core.http.basePath.prepend(
'/app/kibana#/management/kibana/triggersActions/alerts'
),
icon: 'tableOfContents'
}
]
: [])
]
},
{
id: CREATE_THRESHOLD_ALERT_PANEL_ID,
title: createThresholdAlertLabel,
items: [
{
name: i18n.translate(
'xpack.apm.serviceDetails.alertsMenu.transactionDuration',
{
defaultMessage: 'Transaction duration'
}
),
onClick: () => {
setAlertType(AlertType.TransactionDuration);
}
},
{
name: i18n.translate(
'xpack.apm.serviceDetails.alertsMenu.errorRate',
{
defaultMessage: 'Error rate'
}
),
onClick: () => {
setAlertType(AlertType.ErrorRate);
}
}
]
}
];

return (
<>
<EuiPopover
id="integrations-menu"
button={button}
isOpen={popoverOpen}
closePopover={() => setPopoverOpen(false)}
panelPaddingSize="none"
anchorPosition="downRight"
>
<EuiContextMenu initialPanelId={0} panels={panels} />
</EuiPopover>
<AlertingFlyout
alertType={alertType}
addFlyoutVisible={!!alertType}
setAddFlyoutVisibility={visible => {
if (!visible) {
setAlertType(null);
}
}}
/>
</>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,27 @@ import { ApmHeader } from '../../shared/ApmHeader';
import { ServiceDetailTabs } from './ServiceDetailTabs';
import { ServiceIntegrations } from './ServiceIntegrations';
import { useUrlParams } from '../../../hooks/useUrlParams';
import { AlertIntegrations } from './AlertIntegrations';
import { useApmPluginContext } from '../../../hooks/useApmPluginContext';

interface Props {
tab: React.ComponentProps<typeof ServiceDetailTabs>['tab'];
}

export function ServiceDetails({ tab }: Props) {
const plugin = useApmPluginContext();
const { urlParams } = useUrlParams();
const { serviceName } = urlParams;

const canReadAlerts = !!plugin.core.application.capabilities.apm[
'alerting:show'
];
const canSaveAlerts = !!plugin.core.application.capabilities.apm[
'alerting:save'
];

const isAlertingAvailable = canReadAlerts || canSaveAlerts;

return (
<div>
<ApmHeader>
Expand All @@ -31,6 +43,14 @@ export function ServiceDetails({ tab }: Props) {
<EuiFlexItem grow={false}>
<ServiceIntegrations urlParams={urlParams} />
</EuiFlexItem>
{isAlertingAvailable && (
<EuiFlexItem grow={false}>
<AlertIntegrations
canReadAlerts={canReadAlerts}
canSaveAlerts={canSaveAlerts}
/>
</EuiFlexItem>
)}
</EuiFlexGroup>
</ApmHeader>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ storiesOf('app/ServiceMap/Cytoscape', module)
const height = 640;
const width = 1340;
const serviceName = undefined; // global service map

return (
<Cytoscape
elements={elementsFromResponses}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
import { act, render, wait } from '@testing-library/react';
import cytoscape from 'cytoscape';
import React, { FunctionComponent } from 'react';
import { MockApmPluginContextWrapper } from '../../../utils/testHelpers';
import { CytoscapeContext } from './Cytoscape';
import { EmptyBanner } from './EmptyBanner';
import { MockApmPluginContextWrapper } from '../../../context/ApmPluginContext/MockApmPluginContext';

const cy = cytoscape({});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import { render } from '@testing-library/react';
import React, { FunctionComponent } from 'react';
import { License } from '../../../../../../../plugins/licensing/common/license';
import { LicenseContext } from '../../../context/LicenseContext';
import { MockApmPluginContextWrapper } from '../../../utils/testHelpers';
import { ServiceMap } from './';
import { MockApmPluginContextWrapper } from '../../../context/ApmPluginContext/MockApmPluginContext';

const expiredLicense = new License({
signature: 'test signature',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import React from 'react';
import { shallow } from 'enzyme';
import { ServiceNodeMetrics } from '.';
import { MockApmPluginContextWrapper } from '../../../utils/testHelpers';
import { MockApmPluginContextWrapper } from '../../../context/ApmPluginContext/MockApmPluginContext';

describe('ServiceNodeMetrics', () => {
describe('render', () => {
Expand Down
Loading

0 comments on commit 85c0be3

Please sign in to comment.