Skip to content

Commit

Permalink
Add APM alert types and UI
Browse files Browse the repository at this point in the history
  • Loading branch information
dgieselaar committed Mar 12, 2020
1 parent 5bf332f commit c0dc3c7
Show file tree
Hide file tree
Showing 16 changed files with 1,040 additions and 3 deletions.
@@ -0,0 +1,48 @@
/*
* 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 { useApmPluginContext } from '../../../../../hooks/useApmPluginContext';
import {
AlertsContextProvider,
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;

const plugin = useApmPluginContext();

return (
<AlertsContextProvider
value={{
http: plugin.core.http,
toastNotifications: plugin.core.notifications.toasts,
actionTypeRegistry:
plugin.plugins.triggers_actions_ui.actionTypeRegistry,
alertTypeRegistry: plugin.plugins.triggers_actions_ui.alertTypeRegistry
}}
>
{alertType ? (
<AlertAdd
addFlyoutVisible={addFlyoutVisible}
setAddFlyoutVisibility={setAddFlyoutVisibility}
consumer="apm"
alertTypeId={alertType}
canChangeTrigger={false}
/>
) : null}
</AlertsContextProvider>
);
}
@@ -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);
}
}}
/>
</>
);
}
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
@@ -0,0 +1,26 @@
/*
* 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 { storiesOf } from '@storybook/react';
import React from 'react';
import { ErrorRateAlertTrigger } from '.';

storiesOf('app/ErrorRateAlertTrigger', module).add('example', props => {
const params = {
rate: 2,
window: '5m'
};

return (
<div style={{ width: 400 }}>
<ErrorRateAlertTrigger
alertParams={params as any}
setAlertParams={() => undefined}
setAlertProperty={() => undefined}
/>
</div>
);
});
@@ -0,0 +1,75 @@
/*
* 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 { EuiFieldNumber } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { ALERT_TYPES_CONFIG } from '../../../../../../../plugins/apm/common/alert_types';
import { DurationField } from '../ServiceAlertTrigger/DurationField';
import { ServiceAlertTrigger } from '../ServiceAlertTrigger';

export interface ErrorRateAlertTriggerParams {
window: string;
threshold: number;
}

interface Props {
alertParams: ErrorRateAlertTriggerParams;
setAlertParams: (key: string, value: any) => void;
setAlertProperty: (key: string, value: any) => void;
}

export function ErrorRateAlertTrigger(props: Props) {
const { setAlertParams, setAlertProperty, alertParams } = props;

const defaults = {
threshold: 2,
window: '5m'
};

const fields = [
{
name: 'threshold',
title: i18n.translate('xpack.apm.errorRateAlertTrigger.setThreshold', {
defaultMessage: 'Set threshold'
}),
field: (
<EuiFieldNumber
value={alertParams.threshold ?? ''}
step={0}
onChange={e =>
setAlertParams('threshold', parseInt(e.target.value, 10))
}
compressed
append={i18n.translate('xpack.apm.errorRateAlertTrigger.errors', {
defaultMessage: 'errors'
})}
/>
)
},
{
name: 'window',
title: i18n.translate('xpack.apm.errorRateAlertTrigger.setWindow', {
defaultMessage: 'Set window'
}),
field: (
<DurationField
duration={alertParams.window}
onChange={duration => setAlertParams('window', duration)}
/>
)
}
];

return (
<ServiceAlertTrigger
alertTypeName={ALERT_TYPES_CONFIG['apm.error_rate'].name}
defaults={defaults}
fields={fields}
setAlertParams={setAlertParams}
setAlertProperty={setAlertProperty}
/>
);
}

0 comments on commit c0dc3c7

Please sign in to comment.