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 Sep 25, 2019
1 parent 48d34ab commit f01d96d
Show file tree
Hide file tree
Showing 10 changed files with 442 additions and 111 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,26 +25,19 @@ import {
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { memoize, 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 { LegacyCoreStart } from 'src/core/public';
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';

type ScheduleKey = keyof Schedule;

const getUserTimezone = memoize((core: LegacyCoreStart): string => {
return core.uiSettings.get('dateFormat:tz') === 'Browser'
? moment.tz.guess()
: core.uiSettings.get('dateFormat:tz');
});

const SmallInput = styled.div`
.euiFormRow {
max-width: 85px;
Expand Down Expand Up @@ -157,57 +149,31 @@ 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 =
this.state.schedule === 'interval'
? {
value: this.state.interval.value,
unit: this.state.interval.unit
}
: {
value: 24,
unit: 'h'
};
const timeRange = {
value: this.state.interval.value,
unit: this.state.interval.unit
};

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

return createErrorGroupWatch({
emails,
schedule,
return createErrorOccurrenceAlert({
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 Down Expand Up @@ -279,24 +245,6 @@ export class WatcherFlyout extends Component<
return null;
}

const core = this.context;
const userTimezoneSetting = getUserTimezone(core);
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)
.tz(userTimezoneSetting)
.format('HH:mm'); // Format as 24h
const dailyTime12HourFormatted = moment(inputTime, inputFormat)
.tz(userTimezoneSetting)
.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 @@ -373,50 +321,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,46 @@
/*
* 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 { callApmApi } from '../../../../services/rest/callApmApi';

export interface Schedule {
interval: string;
}

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

export async function createErrorOccurrenceAlert({
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
}
}
}
});
}
Loading

0 comments on commit f01d96d

Please sign in to comment.