Skip to content

Commit

Permalink
[Monitoring] Out of the box alerting (#68805)
Browse files Browse the repository at this point in the history
* First draft, not quite working but a good start

* More working

* Support configuring throttle

* Get the other alerts working too

* More

* Separate into individual files

* Menu support as well as better integration in existing UIs

* Red borders!

* New overview style, and renamed alert

* more visual updates

* Update cpu usage and improve settings configuration in UI

* Convert cluster health and license expiration alert to use legacy data model

* Remove most of the custom UI and use the flyout

* Add the actual alerts

* Remove more code

* Fix formatting

* Fix up some errors

* Remove unnecessary code

* Updates

* add more links here

* Fix up linkage

* Added nodes changed alert

* Most of the version mismatch working

* Add kibana mismatch

* UI tweaks

* Add timestamp

* Support actions in the enable api

* Move this around

* Better support for changing legacy alerts

* Add missing files

* Update alerts

* Enable alerts whenever any page is visited in SM

* Tweaks

* Use more practical default

* Remove the buggy renderer and ensure setup mode can show all alerts

* Updates

* Remove unnecessary code

* Remove some dead code

* Cleanup

* Fix snapshot

* Fixes

* Fixes

* Fix test

* Add alerts to kibana and logstash listing pages

* Fix test

* Add disable/mute options

* Tweaks

* Fix linting

* Fix i18n

* Adding a couple tests

* Fix localization

* Use http

* Ensure we properly handle when an alert is resolved

* Fix tests

* Hide legacy alerts if not the right license

* Design tweaks

* Fix tests

* PR feedback

* Moar tests

* Fix i18n

* Ensure we have a control over the messaging

* Fix translations

* Tweaks

* More localization

* Copy changes

* Type
  • Loading branch information
chrisronline authored Jul 14, 2020
1 parent 8da80fe commit 06b1820
Show file tree
Hide file tree
Showing 149 changed files with 7,524 additions and 5,861 deletions.
4 changes: 0 additions & 4 deletions x-pack/legacy/plugins/monitoring/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,13 @@

import Hapi from 'hapi';
import { config } from './config';
import { KIBANA_ALERTING_ENABLED } from '../../../plugins/monitoring/common/constants';

/**
* Invokes plugin modules to instantiate the Monitoring plugin for Kibana
* @param kibana {Object} Kibana plugin instance
* @return {Object} Monitoring UI Kibana plugin object
*/
const deps = ['kibana', 'elasticsearch', 'xpack_main'];
if (KIBANA_ALERTING_ENABLED) {
deps.push(...['alerts', 'actions']);
}
export const monitoring = (kibana: any) => {
return new kibana.Plugin({
require: deps,
Expand Down
51 changes: 32 additions & 19 deletions x-pack/plugins/monitoring/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ export const INDEX_PATTERN = '.monitoring-*-6-*,.monitoring-*-7-*';
export const INDEX_PATTERN_KIBANA = '.monitoring-kibana-6-*,.monitoring-kibana-7-*';
export const INDEX_PATTERN_LOGSTASH = '.monitoring-logstash-6-*,.monitoring-logstash-7-*';
export const INDEX_PATTERN_BEATS = '.monitoring-beats-6-*,.monitoring-beats-7-*';
export const INDEX_ALERTS = '.monitoring-alerts-6,.monitoring-alerts-7';
export const INDEX_ALERTS = '.monitoring-alerts-6*,.monitoring-alerts-7*';
export const INDEX_PATTERN_ELASTICSEARCH = '.monitoring-es-6-*,.monitoring-es-7-*';

// This is the unique token that exists in monitoring indices collected by metricbeat
Expand Down Expand Up @@ -222,41 +222,54 @@ export const TELEMETRY_COLLECTION_INTERVAL = 86400000;
* as the only way to see the new UI and actually run Kibana alerts. It will
* be false until all alerts have been migrated, then it will be removed
*/
export const KIBANA_ALERTING_ENABLED = false;
export const KIBANA_CLUSTER_ALERTS_ENABLED = false;

/**
* The prefix for all alert types used by monitoring
*/
export const ALERT_TYPE_PREFIX = 'monitoring_';
export const ALERT_PREFIX = 'monitoring_';
export const ALERT_LICENSE_EXPIRATION = `${ALERT_PREFIX}alert_license_expiration`;
export const ALERT_CLUSTER_HEALTH = `${ALERT_PREFIX}alert_cluster_health`;
export const ALERT_CPU_USAGE = `${ALERT_PREFIX}alert_cpu_usage`;
export const ALERT_NODES_CHANGED = `${ALERT_PREFIX}alert_nodes_changed`;
export const ALERT_ELASTICSEARCH_VERSION_MISMATCH = `${ALERT_PREFIX}alert_elasticsearch_version_mismatch`;
export const ALERT_KIBANA_VERSION_MISMATCH = `${ALERT_PREFIX}alert_kibana_version_mismatch`;
export const ALERT_LOGSTASH_VERSION_MISMATCH = `${ALERT_PREFIX}alert_logstash_version_mismatch`;

/**
* This is the alert type id for the license expiration alert
*/
export const ALERT_TYPE_LICENSE_EXPIRATION = `${ALERT_TYPE_PREFIX}alert_type_license_expiration`;
/**
* This is the alert type id for the cluster state alert
* A listing of all alert types
*/
export const ALERT_TYPE_CLUSTER_STATE = `${ALERT_TYPE_PREFIX}alert_type_cluster_state`;
export const ALERTS = [
ALERT_LICENSE_EXPIRATION,
ALERT_CLUSTER_HEALTH,
ALERT_CPU_USAGE,
ALERT_NODES_CHANGED,
ALERT_ELASTICSEARCH_VERSION_MISMATCH,
ALERT_KIBANA_VERSION_MISMATCH,
ALERT_LOGSTASH_VERSION_MISMATCH,
];

/**
* A listing of all alert types
* A list of all legacy alerts, which means they are powered by watcher
*/
export const ALERT_TYPES = [ALERT_TYPE_LICENSE_EXPIRATION, ALERT_TYPE_CLUSTER_STATE];
export const LEGACY_ALERTS = [
ALERT_LICENSE_EXPIRATION,
ALERT_CLUSTER_HEALTH,
ALERT_NODES_CHANGED,
ALERT_ELASTICSEARCH_VERSION_MISMATCH,
ALERT_KIBANA_VERSION_MISMATCH,
ALERT_LOGSTASH_VERSION_MISMATCH,
];

/**
* Matches the id for the built-in in email action type
* See x-pack/plugins/actions/server/builtin_action_types/email.ts
*/
export const ALERT_ACTION_TYPE_EMAIL = '.email';

/**
* The number of alerts that have been migrated
*/
export const NUMBER_OF_MIGRATED_ALERTS = 2;

/**
* The advanced settings config name for the email address
* Matches the id for the built-in in log action type
* See x-pack/plugins/actions/server/builtin_action_types/log.ts
*/
export const MONITORING_CONFIG_ALERTING_EMAIL_ADDRESS = 'monitoring:alertingEmailAddress';
export const ALERT_ACTION_TYPE_LOG = '.server-log';

export const ALERT_EMAIL_SERVICES = ['gmail', 'hotmail', 'icloud', 'outlook365', 'ses', 'yahoo'];
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,25 @@
* you may not use this file except in compliance with the Elastic License.
*/

export enum AlertClusterStateState {
export enum AlertClusterHealthType {
Green = 'green',
Red = 'red',
Yellow = 'yellow',
}

export enum AlertCommonPerClusterMessageTokenType {
export enum AlertSeverity {
Success = 'success',
Danger = 'danger',
Warning = 'warning',
}

export enum AlertMessageTokenType {
Time = 'time',
Link = 'link',
DocLink = 'docLink',
}

export enum AlertParamType {
Duration = 'duration',
Percentage = 'percentage',
}
4 changes: 2 additions & 2 deletions x-pack/plugins/monitoring/common/formatting.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ export const LARGE_ABBREVIATED = '0,0.[0]a';
* @param date Either a numeric Unix timestamp or a {@code Date} object
* @returns The date formatted using 'LL LTS'
*/
export function formatDateTimeLocal(date, useUTC = false) {
export function formatDateTimeLocal(date, useUTC = false, timezone = null) {
return useUTC
? moment.utc(date).format('LL LTS')
: moment.tz(date, moment.tz.guess()).format('LL LTS');
: moment.tz(date, timezone || moment.tz.guess()).format('LL LTS');
}

/**
Expand Down
48 changes: 48 additions & 0 deletions x-pack/plugins/monitoring/common/types.ts
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 { Alert } from '../../alerts/common';
import { AlertParamType } from './enums';

export interface CommonBaseAlert {
type: string;
label: string;
paramDetails: CommonAlertParamDetails;
rawAlert: Alert;
isLegacy: boolean;
}

export interface CommonAlertStatus {
exists: boolean;
enabled: boolean;
states: CommonAlertState[];
alert: CommonBaseAlert;
}

export interface CommonAlertState {
firing: boolean;
state: any;
meta: any;
}

// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface CommonAlertFilter {}

export interface CommonAlertCpuUsageFilter extends CommonAlertFilter {
nodeUuid: string;
}

export interface CommonAlertParamDetail {
label: string;
type: AlertParamType;
}

export interface CommonAlertParamDetails {
[name: string]: CommonAlertParamDetail;
}

export interface CommonAlertParams {
[name: string]: string | number;
}
13 changes: 11 additions & 2 deletions x-pack/plugins/monitoring/kibana.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,17 @@
"version": "8.0.0",
"kibanaVersion": "kibana",
"configPath": ["monitoring"],
"requiredPlugins": ["licensing", "features", "data", "navigation", "kibanaLegacy"],
"optionalPlugins": ["alerts", "actions", "infra", "telemetryCollectionManager", "usageCollection", "home", "cloud"],
"requiredPlugins": [
"licensing",
"features",
"data",
"navigation",
"kibanaLegacy",
"triggers_actions_ui",
"alerts",
"actions"
],
"optionalPlugins": ["infra", "telemetryCollectionManager", "usageCollection", "home", "cloud"],
"server": true,
"ui": true,
"requiredBundles": ["kibanaUtils", "home", "alerts", "kibanaReact", "licenseManagement"]
Expand Down
179 changes: 179 additions & 0 deletions x-pack/plugins/monitoring/public/alerts/badge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
/*
* 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, { Fragment } from 'react';
import { i18n } from '@kbn/i18n';
import {
EuiContextMenu,
EuiPopover,
EuiBadge,
EuiFlexGrid,
EuiFlexItem,
EuiText,
} from '@elastic/eui';
import { CommonAlertStatus, CommonAlertState } from '../../common/types';
import { AlertSeverity } from '../../common/enums';
// @ts-ignore
import { formatDateTimeLocal } from '../../common/formatting';
import { AlertState } from '../../server/alerts/types';
import { AlertPanel } from './panel';
import { Legacy } from '../legacy_shims';
import { isInSetupMode } from '../lib/setup_mode';

function getDateFromState(states: CommonAlertState[]) {
const timestamp = states[0].state.ui.triggeredMS;
const tz = Legacy.shims.uiSettings.get('dateFormat:tz');
return formatDateTimeLocal(timestamp, false, tz === 'Browser' ? null : tz);
}

export const numberOfAlertsLabel = (count: number) => `${count} alert${count > 1 ? 's' : ''}`;

interface Props {
alerts: { [alertTypeId: string]: CommonAlertStatus };
}
export const AlertsBadge: React.FC<Props> = (props: Props) => {
const [showPopover, setShowPopover] = React.useState<AlertSeverity | boolean | null>(null);
const inSetupMode = isInSetupMode();
const alerts = Object.values(props.alerts).filter(Boolean);

if (alerts.length === 0) {
return null;
}

const badges = [];

if (inSetupMode) {
const button = (
<EuiBadge
iconType="bell"
onClickAriaLabel={numberOfAlertsLabel(alerts.length)}
onClick={() => setShowPopover(true)}
>
{numberOfAlertsLabel(alerts.length)}
</EuiBadge>
);
const panels = [
{
id: 0,
title: i18n.translate('xpack.monitoring.alerts.badge.panelTitle', {
defaultMessage: 'Alerts',
}),
items: alerts.map(({ alert }, index) => {
return {
name: <EuiText>{alert.label}</EuiText>,
panel: index + 1,
};
}),
},
...alerts.map((alertStatus, index) => {
return {
id: index + 1,
title: alertStatus.alert.label,
width: 400,
content: <AlertPanel alert={alertStatus} />,
};
}),
];

badges.push(
<EuiPopover
id="monitoringAlertMenu"
button={button}
isOpen={showPopover === true}
closePopover={() => setShowPopover(null)}
panelPaddingSize="none"
withTitle
anchorPosition="downLeft"
>
<EuiContextMenu initialPanelId={0} panels={panels} />
</EuiPopover>
);
} else {
const byType = {
[AlertSeverity.Danger]: [] as CommonAlertStatus[],
[AlertSeverity.Warning]: [] as CommonAlertStatus[],
[AlertSeverity.Success]: [] as CommonAlertStatus[],
};

for (const alert of alerts) {
for (const alertState of alert.states) {
const state = alertState.state as AlertState;
byType[state.ui.severity].push(alert);
}
}

const typesToShow = [AlertSeverity.Danger, AlertSeverity.Warning];
for (const type of typesToShow) {
const list = byType[type];
if (list.length === 0) {
continue;
}

const button = (
<EuiBadge
iconType="bell"
color={type}
onClickAriaLabel={numberOfAlertsLabel(list.length)}
onClick={() => setShowPopover(type)}
>
{numberOfAlertsLabel(list.length)}
</EuiBadge>
);

const panels = [
{
id: 0,
title: `Alerts`,
items: list.map(({ alert, states }, index) => {
return {
name: (
<Fragment>
<EuiText size="s">
<h4>{getDateFromState(states)}</h4>
</EuiText>
<EuiText>{alert.label}</EuiText>
</Fragment>
),
panel: index + 1,
};
}),
},
...list.map((alertStatus, index) => {
return {
id: index + 1,
title: getDateFromState(alertStatus.states),
width: 400,
content: <AlertPanel alert={alertStatus} />,
};
}),
];

badges.push(
<EuiPopover
id="monitoringAlertMenu"
button={button}
isOpen={showPopover === type}
closePopover={() => setShowPopover(null)}
panelPaddingSize="none"
withTitle
anchorPosition="downLeft"
>
<EuiContextMenu initialPanelId={0} panels={panels} />
</EuiPopover>
);
}
}

return (
<EuiFlexGrid>
{badges.map((badge, index) => (
<EuiFlexItem key={index} grow={false}>
{badge}
</EuiFlexItem>
))}
</EuiFlexGrid>
);
};
Loading

0 comments on commit 06b1820

Please sign in to comment.