Skip to content

Commit

Permalink
[APM]: Replace error occurrence watchers with Kibana Alerting
Browse files Browse the repository at this point in the history
  • Loading branch information
dgieselaar committed Nov 6, 2019
1 parent 17383b8 commit c6b3361
Show file tree
Hide file tree
Showing 12 changed files with 472 additions and 119 deletions.
8 changes: 8 additions & 0 deletions x-pack/legacy/plugins/apm/common/alerting/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* 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.
*/

export const ERROR_OCCURRENCE_ALERT_TYPE_ID = 'apm.error_occurrence';
export const NOTIFICATION_EMAIL_ACTION_ID = 'apm.notificationEmail';
9 changes: 8 additions & 1 deletion x-pack/legacy/plugins/apm/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,14 @@ import { plugin } from './server/new-platform/index';

export const apm: LegacyPluginInitializer = kibana => {
return new kibana.Plugin({
require: ['kibana', 'elasticsearch', 'xpack_main', 'apm_oss'],
require: [
'kibana',
'elasticsearch',
'xpack_main',
'apm_oss',
'alerting',
'actions'
],
id: 'apm',
configPrefix: 'xpack.apm',
publicDir: resolve(__dirname, 'public'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import {
EuiForm,
EuiFormRow,
EuiLink,
EuiRadio,
EuiSelect,
EuiSpacer,
EuiSwitch,
Expand All @@ -26,15 +25,17 @@ import {
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { padLeft, range } from 'lodash';
import moment from 'moment-timezone';
import React, { Component } from 'react';
import styled from 'styled-components';
import { toastNotifications } from 'ui/notify';
import { NOT_AVAILABLE_LABEL } from '../../../../../common/i18n';
import { KibanaCoreContext } from '../../../../../../observability/public';
import { IUrlParams } from '../../../../context/UrlParamsContext/types';
import { KibanaLink } from '../../../shared/Links/KibanaLink';
import { createErrorGroupWatch, Schedule } from './createErrorGroupWatch';
import { Schedule } from './createErrorGroupWatch';
import { ElasticDocsLink } from '../../../shared/Links/ElasticDocsLink';
import { createErrorOccurrenceAlert } from './createErrorOccurrenceAlert';
import { APMClient } from '../../../../services/rest/createCallApmApi';

type ScheduleKey = keyof Schedule;

Expand All @@ -49,6 +50,7 @@ const SmallInput = styled.div`

interface WatcherFlyoutProps {
urlParams: IUrlParams;
callApmApi: APMClient;
onClose: () => void;
isOpen: boolean;
}
Expand Down Expand Up @@ -149,57 +151,32 @@ export class WatcherFlyout extends Component<
};

public createWatch = () => {
const core = this.context;
const { serviceName } = this.props.urlParams;

if (!serviceName) {
return;
}

const emails = this.state.actions.email
? this.state.emails
.split(',')
.map(email => email.trim())
.filter(email => !!email)
: [];
const email = this.state.actions.email ? this.state.emails : '';

const slackUrl = this.state.actions.slack ? this.state.slackUrl : '';

const schedule =
this.state.schedule === 'interval'
? {
interval: `${this.state.interval.value}${this.state.interval.unit}`
}
: {
daily: { at: `${this.state.daily}` }
};
const timeRange = {
value: this.state.interval.value,
unit: this.state.interval.unit
};

const timeRange =
this.state.schedule === 'interval'
? {
value: this.state.interval.value,
unit: this.state.interval.unit
}
: {
value: 24,
unit: 'h'
};

const apmIndexPatternTitle = core.injectedMetadata.getInjectedVar(
'apmIndexPatternTitle'
) as string;

return createErrorGroupWatch({
emails,
schedule,
return createErrorOccurrenceAlert({
callApmApi: this.props.callApmApi,
email,
serviceName,
slackUrl,
threshold: this.state.threshold,
timeRange,
apmIndexPatternTitle
timeRange
})
.then((id: string) => {
.then(savedObject => {
this.props.onClose();
const id = 'id' in savedObject ? savedObject.id : NOT_AVAILABLE_LABEL;
this.addSuccessToast(id);
})
.catch(e => {
Expand All @@ -210,9 +187,7 @@ export class WatcherFlyout extends Component<
};

public addErrorToast = () => {
const core = this.context;

core.notifications.toasts.addWarning({
toastNotifications.addWarning({
title: i18n.translate(
'xpack.apm.serviceDetails.enableErrorReportsPanel.watchCreationFailedNotificationTitle',
{
Expand All @@ -234,9 +209,7 @@ export class WatcherFlyout extends Component<
};

public addSuccessToast = (id: string) => {
const core = this.context;

core.notifications.toasts.addSuccess({
toastNotifications.addSuccess({
title: i18n.translate(
'xpack.apm.serviceDetails.enableErrorReportsPanel.watchCreatedNotificationTitle',
{
Expand All @@ -255,18 +228,16 @@ export class WatcherFlyout extends Component<
}
}
)}{' '}
<KibanaCoreContext.Provider value={core}>
<KibanaLink
path={`/management/elasticsearch/watcher/watches/watch/${id}`}
>
{i18n.translate(
'xpack.apm.serviceDetails.enableErrorReportsPanel.watchCreatedNotificationText.viewWatchLinkText',
{
defaultMessage: 'View watch'
}
)}
</KibanaLink>
</KibanaCoreContext.Provider>
<KibanaLink
path={`/management/elasticsearch/watcher/watches/watch/${id}`}
>
{i18n.translate(
'xpack.apm.serviceDetails.enableErrorReportsPanel.watchCreatedNotificationText.viewWatchLinkText',
{
defaultMessage: 'View watch'
}
)}
</KibanaLink>
</p>
)
});
Expand All @@ -277,20 +248,6 @@ export class WatcherFlyout extends Component<
return null;
}

const dailyTime = this.state.daily;
const inputTime = `${dailyTime}Z`; // Add tz to make into UTC
const inputFormat = 'HH:mmZ'; // Parse as 24 hour w. tz
const dailyTimeFormatted = moment(inputTime, inputFormat).format('HH:mm'); // Format as 24h
const dailyTime12HourFormatted = moment(inputTime, inputFormat).format(
'hh:mm A (z)'
); // Format as 12h w. tz

// Generate UTC hours for Daily Report select field
const intervalHours = range(24).map(i => {
const hour = padLeft(i.toString(), 2, '0');
return { value: `${hour}:00`, text: `${hour}:00 UTC` };
});

const flyoutBody = (
<EuiText>
<p>
Expand Down Expand Up @@ -367,50 +324,6 @@ export class WatcherFlyout extends Component<
}
)}
</EuiText>
<EuiSpacer size="m" />
<EuiRadio
id="daily"
label={i18n.translate(
'xpack.apm.serviceDetails.enableErrorReportsPanel.dailyReportRadioButtonLabel',
{
defaultMessage: 'Daily report'
}
)}
onChange={() => this.onChangeSchedule('daily')}
checked={this.state.schedule === 'daily'}
/>
<EuiSpacer size="m" />
<EuiFormRow
helpText={i18n.translate(
'xpack.apm.serviceDetails.enableErrorReportsPanel.dailyReportHelpText',
{
defaultMessage:
'The daily report will be sent at {dailyTimeFormatted} / {dailyTime12HourFormatted}.',
values: { dailyTimeFormatted, dailyTime12HourFormatted }
}
)}
compressed
>
<EuiSelect
value={dailyTime}
onChange={this.onChangeDailyUnit}
options={intervalHours}
disabled={this.state.schedule !== 'daily'}
/>
</EuiFormRow>
<EuiSpacer size="m" />
<EuiRadio
id="interval"
label={i18n.translate(
'xpack.apm.serviceDetails.enableErrorReportsPanel.intervalRadioButtonLabel',
{
defaultMessage: 'Interval'
}
)}
onChange={() => this.onChangeSchedule('interval')}
checked={this.state.schedule === 'interval'}
/>
<EuiSpacer size="m" />
<EuiFlexGroup>
<EuiFlexItem grow={false}>
<SmallInput>
Expand Down
Original file line number Diff line number Diff line change
@@ -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 { APMClient } from '../../../../services/rest/createCallApmApi';

export interface Schedule {
interval: string;
}

interface Arguments {
callApmApi: APMClient;
email: string;
serviceName: string;
slackUrl: string;
threshold: number;
timeRange: {
value: number;
unit: string;
};
}

export async function createErrorOccurrenceAlert({
callApmApi,
email,
serviceName,
slackUrl,
threshold,
timeRange
}: Arguments) {
return callApmApi({
pathname: '/api/apm/alerts/error_occurrence',
method: 'POST',
params: {
body: {
serviceName,
threshold,
interval: `${timeRange.value}${timeRange.unit}`,
actions: {
email,
slack: slackUrl
}
}
}
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@ import { IUrlParams } from '../../../../context/UrlParamsContext/types';
import { LicenseContext } from '../../../../context/LicenseContext';
import { MachineLearningFlyout } from './MachineLearningFlyout';
import { WatcherFlyout } from './WatcherFlyout';
import { APMClient } from '../../../../services/rest/createCallApmApi';

interface Props {
urlParams: IUrlParams;
callApmApi: APMClient;
}
interface State {
isPopoverOpen: boolean;
Expand Down Expand Up @@ -162,6 +164,7 @@ export class ServiceIntegrations extends React.Component<Props, State> {
urlParams={this.props.urlParams}
/>
<WatcherFlyout
callApmApi={this.props.callApmApi}
isOpen={this.state.activeFlyout === 'Watcher'}
onClose={this.closeFlyouts}
urlParams={this.props.urlParams}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { ApmHeader } from '../../shared/ApmHeader';
import { ServiceDetailTabs } from './ServiceDetailTabs';
import { ServiceIntegrations } from './ServiceIntegrations';
import { useUrlParams } from '../../../hooks/useUrlParams';
import { useCallApmApi } from '../../../hooks/useCallApmApi';

interface Props {
tab: React.ComponentProps<typeof ServiceDetailTabs>['tab'];
Expand All @@ -18,6 +19,7 @@ interface Props {
export function ServiceDetails({ tab }: Props) {
const { urlParams } = useUrlParams();
const { serviceName } = urlParams;
const callApmApi = useCallApmApi();

return (
<div>
Expand All @@ -29,7 +31,10 @@ export function ServiceDetails({ tab }: Props) {
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<ServiceIntegrations urlParams={urlParams} />
<ServiceIntegrations
urlParams={urlParams}
callApmApi={callApmApi}
/>
</EuiFlexItem>
</EuiFlexGroup>
</ApmHeader>
Expand Down
Loading

0 comments on commit c6b3361

Please sign in to comment.