From 627afe3f6cf503450fe04769d2782e757bc9b460 Mon Sep 17 00:00:00 2001 From: chrisronline Date: Mon, 21 Sep 2020 16:01:55 -0400 Subject: [PATCH 01/16] WIP for alert --- x-pack/plugins/monitoring/common/constants.ts | 2 + .../public/alerts/filter_alert_states.ts | 23 + .../alerts/missing_data_alert/expression.tsx | 61 +++ .../public/alerts/missing_data_alert/index.ts | 7 + .../missing_data_alert/missing_data_alert.tsx | 28 ++ .../alerts/missing_data_alert/validation.tsx | 35 ++ .../components/cluster/overview/apm_panel.js | 29 +- .../cluster/overview/beats_panel.js | 25 +- .../cluster/overview/elasticsearch_panel.js | 2 + .../components/cluster/overview/index.js | 54 +- .../cluster/overview/kibana_panel.js | 8 +- .../cluster/overview/logstash_panel.js | 3 +- x-pack/plugins/monitoring/public/plugin.ts | 2 + .../server/alerts/alerts_factory.ts | 3 + .../plugins/monitoring/server/alerts/index.ts | 1 + .../server/alerts/missing_data_alert.ts | 468 ++++++++++++++++++ .../monitoring/server/alerts/types.d.ts | 15 + .../server/lib/alerts/fetch_missing_data.ts | 291 +++++++++++ 18 files changed, 1042 insertions(+), 15 deletions(-) create mode 100644 x-pack/plugins/monitoring/public/alerts/filter_alert_states.ts create mode 100644 x-pack/plugins/monitoring/public/alerts/missing_data_alert/expression.tsx create mode 100644 x-pack/plugins/monitoring/public/alerts/missing_data_alert/index.ts create mode 100644 x-pack/plugins/monitoring/public/alerts/missing_data_alert/missing_data_alert.tsx create mode 100644 x-pack/plugins/monitoring/public/alerts/missing_data_alert/validation.tsx create mode 100644 x-pack/plugins/monitoring/server/alerts/missing_data_alert.ts create mode 100644 x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_data.ts diff --git a/x-pack/plugins/monitoring/common/constants.ts b/x-pack/plugins/monitoring/common/constants.ts index 2c714080969e4..3f2aadf4f87ec 100644 --- a/x-pack/plugins/monitoring/common/constants.ts +++ b/x-pack/plugins/monitoring/common/constants.ts @@ -235,6 +235,7 @@ 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`; +export const ALERT_MISSING_DATA = `${ALERT_PREFIX}alert_missing_data`; /** * A listing of all alert types @@ -247,6 +248,7 @@ export const ALERTS = [ ALERT_ELASTICSEARCH_VERSION_MISMATCH, ALERT_KIBANA_VERSION_MISMATCH, ALERT_LOGSTASH_VERSION_MISMATCH, + ALERT_MISSING_DATA, ]; /** diff --git a/x-pack/plugins/monitoring/public/alerts/filter_alert_states.ts b/x-pack/plugins/monitoring/public/alerts/filter_alert_states.ts new file mode 100644 index 0000000000000..63714a6921e3f --- /dev/null +++ b/x-pack/plugins/monitoring/public/alerts/filter_alert_states.ts @@ -0,0 +1,23 @@ +/* + * 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 { CommonAlertState, CommonAlertStatus } from '../../common/types'; + +export function filterAlertStates( + alerts: { [type: string]: CommonAlertStatus }, + filter: (type: string, state: CommonAlertState) => boolean +) { + return Object.keys(alerts).reduce( + (accum: { [type: string]: CommonAlertStatus }, type: string) => { + accum[type] = { + ...alerts[type], + states: alerts[type].states.filter((state) => filter(type, state)), + }; + return accum; + }, + {} + ); +} diff --git a/x-pack/plugins/monitoring/public/alerts/missing_data_alert/expression.tsx b/x-pack/plugins/monitoring/public/alerts/missing_data_alert/expression.tsx new file mode 100644 index 0000000000000..7dc6155de529e --- /dev/null +++ b/x-pack/plugins/monitoring/public/alerts/missing_data_alert/expression.tsx @@ -0,0 +1,61 @@ +/* + * 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 { EuiForm, EuiSpacer } from '@elastic/eui'; +import { CommonAlertParamDetails } from '../../../common/types'; +import { AlertParamDuration } from '../flyout_expressions/alert_param_duration'; +import { AlertParamType } from '../../../common/enums'; +import { AlertParamPercentage } from '../flyout_expressions/alert_param_percentage'; + +export interface Props { + alertParams: { [property: string]: any }; + setAlertParams: (property: string, value: any) => void; + setAlertProperty: (property: string, value: any) => void; + errors: { [key: string]: string[] }; + paramDetails: CommonAlertParamDetails; +} + +export const Expression: React.FC = (props) => { + const { alertParams, paramDetails, setAlertParams, errors } = props; + + const alertParamsUi = Object.keys(alertParams).map((alertParamName) => { + const details = paramDetails[alertParamName]; + const value = alertParams[alertParamName]; + + switch (details.type) { + case AlertParamType.Duration: + return ( + + ); + case AlertParamType.Percentage: + return ( + + ); + } + }); + + return ( + + {alertParamsUi} + + + ); +}; diff --git a/x-pack/plugins/monitoring/public/alerts/missing_data_alert/index.ts b/x-pack/plugins/monitoring/public/alerts/missing_data_alert/index.ts new file mode 100644 index 0000000000000..5a7dbda9d2a53 --- /dev/null +++ b/x-pack/plugins/monitoring/public/alerts/missing_data_alert/index.ts @@ -0,0 +1,7 @@ +/* + * 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 { createMissingDataAlertType } from './missing_data_alert'; diff --git a/x-pack/plugins/monitoring/public/alerts/missing_data_alert/missing_data_alert.tsx b/x-pack/plugins/monitoring/public/alerts/missing_data_alert/missing_data_alert.tsx new file mode 100644 index 0000000000000..84b5545db8795 --- /dev/null +++ b/x-pack/plugins/monitoring/public/alerts/missing_data_alert/missing_data_alert.tsx @@ -0,0 +1,28 @@ +/* + * 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'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { AlertTypeModel } from '../../../../triggers_actions_ui/public/types'; +import { validate } from './validation'; +import { ALERT_MISSING_DATA } from '../../../common/constants'; +import { Expression } from './expression'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { MissingDataAlert } from '../../../server/alerts'; + +export function createMissingDataAlertType(): AlertTypeModel { + const alert = new MissingDataAlert(); + return { + id: ALERT_MISSING_DATA, + name: alert.label, + iconClass: 'bell', + alertParamsExpression: (props: any) => ( + + ), + validate, + defaultActionMessage: '{{context.internalFullMessage}}', + requiresAppContext: true, + }; +} diff --git a/x-pack/plugins/monitoring/public/alerts/missing_data_alert/validation.tsx b/x-pack/plugins/monitoring/public/alerts/missing_data_alert/validation.tsx new file mode 100644 index 0000000000000..fe84de9bd00ea --- /dev/null +++ b/x-pack/plugins/monitoring/public/alerts/missing_data_alert/validation.tsx @@ -0,0 +1,35 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { ValidationResult } from '../../../../triggers_actions_ui/public/types'; + +export function validate(opts: any): ValidationResult { + const validationResult = { errors: {} }; + + const errors: { [key: string]: string[] } = { + duration: [], + limit: [], + }; + if (!opts.duration) { + errors.duration.push( + i18n.translate('xpack.monitoring.alerts.missingData.validation.duration', { + defaultMessage: 'A valid duration is required.', + }) + ); + } + if (!opts.limit) { + errors.limit.push( + i18n.translate('xpack.monitoring.alerts.missingData.validation.limit', { + defaultMessage: 'A valid limit is required.', + }) + ); + } + + validationResult.errors = errors; + return validationResult; +} diff --git a/x-pack/plugins/monitoring/public/components/cluster/overview/apm_panel.js b/x-pack/plugins/monitoring/public/components/cluster/overview/apm_panel.js index ccbf0b0ec711d..297e3f1c62718 100644 --- a/x-pack/plugins/monitoring/public/components/cluster/overview/apm_panel.js +++ b/x-pack/plugins/monitoring/public/components/cluster/overview/apm_panel.js @@ -24,14 +24,22 @@ import { EuiFlexGroup, } from '@elastic/eui'; import { formatTimestampToDuration } from '../../../../common'; -import { CALCULATE_DURATION_SINCE, APM_SYSTEM_ID } from '../../../../common/constants'; +import { + CALCULATE_DURATION_SINCE, + APM_SYSTEM_ID, + ALERT_MISSING_DATA, +} from '../../../../common/constants'; import { SetupModeTooltip } from '../../setup_mode/tooltip'; import { getSafeForExternalLink } from '../../../lib/get_safe_for_external_link'; import { isSetupModeFeatureEnabled } from '../../../lib/setup_mode'; import { SetupModeFeature } from '../../../../common/enums'; +import { shouldShowAlertBadge } from '../../../alerts/lib/should_show_alert_badge'; +import { AlertsBadge } from '../../../alerts/badge'; + +const SERVERS_PANEL_ALERTS = [ALERT_MISSING_DATA]; export function ApmPanel(props) { - const { setupMode } = props; + const { setupMode, alerts } = props; const apmsTotal = get(props, 'apms.total') || 0; // Do not show if we are not in setup mode if (apmsTotal === 0 && !setupMode.enabled) { @@ -50,6 +58,16 @@ export function ApmPanel(props) { /> ) : null; + let apmServersAlertStatus = null; + if (shouldShowAlertBadge(alerts, SERVERS_PANEL_ALERTS)) { + const alertsList = SERVERS_PANEL_ALERTS.map((alertType) => alerts[alertType]); + apmServersAlertStatus = ( + + + + ); + } + return ( - {setupModeMetricbeatMigrationTooltip} + + + {setupModeMetricbeatMigrationTooltip} + {apmServersAlertStatus} + + diff --git a/x-pack/plugins/monitoring/public/components/cluster/overview/beats_panel.js b/x-pack/plugins/monitoring/public/components/cluster/overview/beats_panel.js index 3591ad178f4cd..178ff2d5823a0 100644 --- a/x-pack/plugins/monitoring/public/components/cluster/overview/beats_panel.js +++ b/x-pack/plugins/monitoring/public/components/cluster/overview/beats_panel.js @@ -23,13 +23,17 @@ import { ClusterItemContainer, DisabledIfNoDataAndInSetupModeLink } from './help import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { SetupModeTooltip } from '../../setup_mode/tooltip'; -import { BEATS_SYSTEM_ID } from '../../../../common/constants'; +import { ALERT_MISSING_DATA, BEATS_SYSTEM_ID } from '../../../../common/constants'; import { getSafeForExternalLink } from '../../../lib/get_safe_for_external_link'; import { isSetupModeFeatureEnabled } from '../../../lib/setup_mode'; import { SetupModeFeature } from '../../../../common/enums'; +import { shouldShowAlertBadge } from '../../../alerts/lib/should_show_alert_badge'; +import { AlertsBadge } from '../../../alerts/badge'; + +const BEATS_PANEL_ALERTS = [ALERT_MISSING_DATA]; export function BeatsPanel(props) { - const { setupMode } = props; + const { setupMode, alerts } = props; const beatsTotal = get(props, 'beats.total') || 0; // Do not show if we are not in setup mode if (beatsTotal === 0 && !setupMode.enabled) { @@ -47,6 +51,16 @@ export function BeatsPanel(props) { /> ) : null; + let beatsAlertsStatus = null; + if (shouldShowAlertBadge(alerts, BEATS_PANEL_ALERTS)) { + const alertsList = BEATS_PANEL_ALERTS.map((alertType) => alerts[alertType]); + beatsAlertsStatus = ( + + + + ); + } + const beatTypes = props.beats.types.map((beat, index) => { return [ - {setupModeMetricbeatMigrationTooltip} + + + {setupModeMetricbeatMigrationTooltip} + {beatsAlertsStatus} + + {beatTypes} diff --git a/x-pack/plugins/monitoring/public/components/cluster/overview/elasticsearch_panel.js b/x-pack/plugins/monitoring/public/components/cluster/overview/elasticsearch_panel.js index 34e995510cf72..4e5e2506f4680 100644 --- a/x-pack/plugins/monitoring/public/components/cluster/overview/elasticsearch_panel.js +++ b/x-pack/plugins/monitoring/public/components/cluster/overview/elasticsearch_panel.js @@ -41,6 +41,7 @@ import { ALERT_CPU_USAGE, ALERT_NODES_CHANGED, ALERT_ELASTICSEARCH_VERSION_MISMATCH, + ALERT_MISSING_DATA, } from '../../../../common/constants'; import { AlertsBadge } from '../../../alerts/badge'; import { shouldShowAlertBadge } from '../../../alerts/lib/should_show_alert_badge'; @@ -158,6 +159,7 @@ const NODES_PANEL_ALERTS = [ ALERT_CPU_USAGE, ALERT_NODES_CHANGED, ALERT_ELASTICSEARCH_VERSION_MISMATCH, + ALERT_MISSING_DATA, ]; export function ElasticsearchPanel(props) { diff --git a/x-pack/plugins/monitoring/public/components/cluster/overview/index.js b/x-pack/plugins/monitoring/public/components/cluster/overview/index.js index 66701c1dfd95a..b616898223d98 100644 --- a/x-pack/plugins/monitoring/public/components/cluster/overview/index.js +++ b/x-pack/plugins/monitoring/public/components/cluster/overview/index.js @@ -12,7 +12,16 @@ import { BeatsPanel } from './beats_panel'; import { EuiPage, EuiPageBody, EuiScreenReaderOnly } from '@elastic/eui'; import { ApmPanel } from './apm_panel'; import { FormattedMessage } from '@kbn/i18n/react'; -import { STANDALONE_CLUSTER_CLUSTER_UUID } from '../../../../common/constants'; +import { + STANDALONE_CLUSTER_CLUSTER_UUID, + ALERT_MISSING_DATA, + ELASTICSEARCH_SYSTEM_ID, + KIBANA_SYSTEM_ID, + LOGSTASH_SYSTEM_ID, + BEATS_SYSTEM_ID, + APM_SYSTEM_ID, +} from '../../../../common/constants'; +import { filterAlertStates } from '../../../alerts/filter_alert_states'; export function Overview(props) { const isFromStandaloneCluster = props.cluster.cluster_uuid === STANDALONE_CLUSTER_CLUSTER_UUID; @@ -37,12 +46,22 @@ export function Overview(props) { license={props.cluster.license} setupMode={props.setupMode} showLicenseExpiration={props.showLicenseExpiration} - alerts={props.alerts} + alerts={filterAlertStates(props.alerts, (type, { state }) => { + if (type === ALERT_MISSING_DATA) { + return state.stackProduct === ELASTICSEARCH_SYSTEM_ID; + } + return true; + })} /> { + if (type === ALERT_MISSING_DATA) { + return state.stackProduct === KIBANA_SYSTEM_ID; + } + return true; + })} /> ) : null} @@ -50,12 +69,35 @@ export function Overview(props) { { + if (type === ALERT_MISSING_DATA) { + return state.stackProduct === LOGSTASH_SYSTEM_ID; + } + return true; + })} /> - + { + if (type === ALERT_MISSING_DATA) { + return state.stackProduct === BEATS_SYSTEM_ID; + } + return true; + })} + /> - + { + if (type === ALERT_MISSING_DATA) { + return state.stackProduct === APM_SYSTEM_ID; + } + return true; + })} + /> ); diff --git a/x-pack/plugins/monitoring/public/components/cluster/overview/kibana_panel.js b/x-pack/plugins/monitoring/public/components/cluster/overview/kibana_panel.js index 6fa533302db48..cc83085f14bc2 100644 --- a/x-pack/plugins/monitoring/public/components/cluster/overview/kibana_panel.js +++ b/x-pack/plugins/monitoring/public/components/cluster/overview/kibana_panel.js @@ -28,14 +28,18 @@ import { import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { SetupModeTooltip } from '../../setup_mode/tooltip'; -import { KIBANA_SYSTEM_ID, ALERT_KIBANA_VERSION_MISMATCH } from '../../../../common/constants'; +import { + KIBANA_SYSTEM_ID, + ALERT_KIBANA_VERSION_MISMATCH, + ALERT_MISSING_DATA, +} from '../../../../common/constants'; import { getSafeForExternalLink } from '../../../lib/get_safe_for_external_link'; import { AlertsBadge } from '../../../alerts/badge'; import { shouldShowAlertBadge } from '../../../alerts/lib/should_show_alert_badge'; import { isSetupModeFeatureEnabled } from '../../../lib/setup_mode'; import { SetupModeFeature } from '../../../../common/enums'; -const INSTANCES_PANEL_ALERTS = [ALERT_KIBANA_VERSION_MISMATCH]; +const INSTANCES_PANEL_ALERTS = [ALERT_KIBANA_VERSION_MISMATCH, ALERT_MISSING_DATA]; export function KibanaPanel(props) { const setupMode = props.setupMode; diff --git a/x-pack/plugins/monitoring/public/components/cluster/overview/logstash_panel.js b/x-pack/plugins/monitoring/public/components/cluster/overview/logstash_panel.js index 9b4a50271a247..f0939f177076a 100644 --- a/x-pack/plugins/monitoring/public/components/cluster/overview/logstash_panel.js +++ b/x-pack/plugins/monitoring/public/components/cluster/overview/logstash_panel.js @@ -15,6 +15,7 @@ import { LOGSTASH, LOGSTASH_SYSTEM_ID, ALERT_LOGSTASH_VERSION_MISMATCH, + ALERT_MISSING_DATA, } from '../../../../common/constants'; import { @@ -40,7 +41,7 @@ import { shouldShowAlertBadge } from '../../../alerts/lib/should_show_alert_badg import { isSetupModeFeatureEnabled } from '../../../lib/setup_mode'; import { SetupModeFeature } from '../../../../common/enums'; -const NODES_PANEL_ALERTS = [ALERT_LOGSTASH_VERSION_MISMATCH]; +const NODES_PANEL_ALERTS = [ALERT_LOGSTASH_VERSION_MISMATCH, ALERT_MISSING_DATA]; export function LogstashPanel(props) { const { setupMode } = props; diff --git a/x-pack/plugins/monitoring/public/plugin.ts b/x-pack/plugins/monitoring/public/plugin.ts index 05aa75f586241..754235eb457e0 100644 --- a/x-pack/plugins/monitoring/public/plugin.ts +++ b/x-pack/plugins/monitoring/public/plugin.ts @@ -22,6 +22,7 @@ import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/public'; import { MonitoringStartPluginDependencies, MonitoringConfig } from './types'; import { TriggersAndActionsUIPublicPluginSetup } from '../../triggers_actions_ui/public'; import { createCpuUsageAlertType } from './alerts/cpu_usage_alert'; +import { createMissingDataAlertType } from './alerts/missing_data_alert'; import { createLegacyAlertTypes } from './alerts/legacy_alert'; interface MonitoringSetupPluginDependencies { @@ -69,6 +70,7 @@ export class MonitoringPlugin } plugins.triggers_actions_ui.alertTypeRegistry.register(createCpuUsageAlertType()); + plugins.triggers_actions_ui.alertTypeRegistry.register(createMissingDataAlertType()); const legacyAlertTypes = createLegacyAlertTypes(); for (const legacyAlertType of legacyAlertTypes) { plugins.triggers_actions_ui.alertTypeRegistry.register(legacyAlertType); diff --git a/x-pack/plugins/monitoring/server/alerts/alerts_factory.ts b/x-pack/plugins/monitoring/server/alerts/alerts_factory.ts index b91eab05cf912..ab7510a708f09 100644 --- a/x-pack/plugins/monitoring/server/alerts/alerts_factory.ts +++ b/x-pack/plugins/monitoring/server/alerts/alerts_factory.ts @@ -6,6 +6,7 @@ import { CpuUsageAlert, + MissingDataAlert, NodesChangedAlert, ClusterHealthAlert, LicenseExpirationAlert, @@ -18,6 +19,7 @@ import { ALERT_CLUSTER_HEALTH, ALERT_LICENSE_EXPIRATION, ALERT_CPU_USAGE, + ALERT_MISSING_DATA, ALERT_NODES_CHANGED, ALERT_LOGSTASH_VERSION_MISMATCH, ALERT_KIBANA_VERSION_MISMATCH, @@ -29,6 +31,7 @@ export const BY_TYPE = { [ALERT_CLUSTER_HEALTH]: ClusterHealthAlert, [ALERT_LICENSE_EXPIRATION]: LicenseExpirationAlert, [ALERT_CPU_USAGE]: CpuUsageAlert, + [ALERT_MISSING_DATA]: MissingDataAlert, [ALERT_NODES_CHANGED]: NodesChangedAlert, [ALERT_LOGSTASH_VERSION_MISMATCH]: LogstashVersionMismatchAlert, [ALERT_KIBANA_VERSION_MISMATCH]: KibanaVersionMismatchAlert, diff --git a/x-pack/plugins/monitoring/server/alerts/index.ts b/x-pack/plugins/monitoring/server/alerts/index.ts index 048e703d2222c..bc4d443b6ac8f 100644 --- a/x-pack/plugins/monitoring/server/alerts/index.ts +++ b/x-pack/plugins/monitoring/server/alerts/index.ts @@ -6,6 +6,7 @@ export { BaseAlert } from './base_alert'; export { CpuUsageAlert } from './cpu_usage_alert'; +export { MissingDataAlert } from './missing_data_alert'; export { ClusterHealthAlert } from './cluster_health_alert'; export { LicenseExpirationAlert } from './license_expiration_alert'; export { NodesChangedAlert } from './nodes_changed_alert'; diff --git a/x-pack/plugins/monitoring/server/alerts/missing_data_alert.ts b/x-pack/plugins/monitoring/server/alerts/missing_data_alert.ts new file mode 100644 index 0000000000000..5e21855f14698 --- /dev/null +++ b/x-pack/plugins/monitoring/server/alerts/missing_data_alert.ts @@ -0,0 +1,468 @@ +/* + * 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 { IUiSettingsClient, Logger } from 'kibana/server'; +import { i18n } from '@kbn/i18n'; +import moment from 'moment'; +import { BaseAlert } from './base_alert'; +import { + AlertData, + AlertCluster, + AlertState, + AlertMessage, + AlertMissingDataState, + AlertMissingData, + AlertMessageTimeToken, + AlertInstanceState, + AlertMessageDocLinkToken, +} from './types'; +import { AlertInstance, AlertServices } from '../../../alerts/server'; +import { INDEX_PATTERN, ALERT_MISSING_DATA } from '../../common/constants'; +import { getCcsIndexPattern } from '../lib/alerts/get_ccs_index_pattern'; +import { AlertMessageTokenType, AlertSeverity, AlertParamType } from '../../common/enums'; +import { RawAlertInstance } from '../../../alerts/common'; +import { parseDuration } from '../../../alerts/common/parse_duration'; +import { CommonAlertFilter, CommonAlertParams, CommonAlertParamDetail } from '../../common/types'; +import { appendMetricbeatIndex } from '../lib/alerts/append_mb_index'; +import { fetchMissingData } from '../lib/alerts/fetch_missing_data'; + +// const RESOLVED = i18n.translate('xpack.monitoring.alerts.missingData.resolved', { +// defaultMessage: 'resolved', +// }); +const FIRING = i18n.translate('xpack.monitoring.alerts.missingData.firing', { + defaultMessage: 'firing', +}); + +const DEFAULT_DURATION = '5m'; +const DEFAULT_LIMIT = '1d'; + +interface MissingDataParams { + duration: string; + limit: string; +} + +export class MissingDataAlert extends BaseAlert { + public static paramDetails = { + duration: { + label: i18n.translate('xpack.monitoring.alerts.missingData.paramDetails.duration.label', { + defaultMessage: `Notify if data is missing for`, + }), + type: AlertParamType.Duration, + } as CommonAlertParamDetail, + limit: { + label: i18n.translate('xpack.monitoring.alerts.missingData.paramDetails.limit.label', { + defaultMessage: `Look this far back in time for any data`, + }), + type: AlertParamType.Duration, + } as CommonAlertParamDetail, + }; + + public type = ALERT_MISSING_DATA; + public label = i18n.translate('xpack.monitoring.alerts.missingData.label', { + defaultMessage: 'Missing data', + }); + + protected defaultParams: MissingDataParams = { + duration: DEFAULT_DURATION, + limit: DEFAULT_LIMIT, + }; + + protected actionVariables = [ + { + name: 'internalShortMessage', + description: i18n.translate( + 'xpack.monitoring.alerts.missingData.actionVariables.internalShortMessage', + { + defaultMessage: 'The short internal message generated by Elastic.', + } + ), + }, + { + name: 'internalFullMessage', + description: i18n.translate( + 'xpack.monitoring.alerts.missingData.actionVariables.internalFullMessage', + { + defaultMessage: 'The full internal message generated by Elastic.', + } + ), + }, + { + name: 'state', + description: i18n.translate('xpack.monitoring.alerts.missingData.actionVariables.state', { + defaultMessage: 'The current state of the alert.', + }), + }, + { + name: 'stackProducts', + description: i18n.translate( + 'xpack.monitoring.alerts.missingData.actionVariables.stackProducts', + { + defaultMessage: 'The stack products missing data.', + } + ), + }, + { + name: 'count', + description: i18n.translate('xpack.monitoring.alerts.missingData.actionVariables.count', { + defaultMessage: 'The number of stack products missing data.', + }), + }, + { + name: 'clusterName', + description: i18n.translate( + 'xpack.monitoring.alerts.missingData.actionVariables.clusterName', + { + defaultMessage: 'The cluster to which the stack products belong.', + } + ), + }, + { + name: 'action', + description: i18n.translate('xpack.monitoring.alerts.missingData.actionVariables.action', { + defaultMessage: 'The recommended action for this alert.', + }), + }, + { + name: 'actionPlain', + description: i18n.translate( + 'xpack.monitoring.alerts.missingData.actionVariables.actionPlain', + { + defaultMessage: 'The recommended action for this alert, without any markdown.', + } + ), + }, + ]; + + protected async fetchData( + params: CommonAlertParams, + callCluster: any, + clusters: AlertCluster[], + uiSettings: IUiSettingsClient, + availableCcs: string[] + ): Promise { + let indexPattern = appendMetricbeatIndex(this.config, INDEX_PATTERN); + if (availableCcs) { + indexPattern = getCcsIndexPattern(indexPattern, availableCcs); + } + const duration = parseDuration(((params as unknown) as MissingDataParams).duration); + const limit = parseDuration(((params as unknown) as MissingDataParams).limit); + const missingData = await fetchMissingData( + callCluster, + clusters, + indexPattern, + limit, + this.config.ui.max_bucket_size + ); + return missingData.map((missing) => { + return { + instanceKey: `${missing.clusterUuid}:${missing.stackProduct}:${missing.stackProductUuid}`, + clusterUuid: missing.clusterUuid, + shouldFire: missing.gapDuration > duration, + severity: AlertSeverity.Danger, + meta: missing, + ccs: missing.ccs, + }; + }); + } + + protected filterAlertInstance(alertInstance: RawAlertInstance, filters: CommonAlertFilter[]) { + // const alertInstanceState = (alertInstance.state as unknown) as AlertInstanceState; + // if (filters && filters.length) { + // for (const _filter of filters) { + // const filter = _filter as CommonAlertCpuUsageFilter; + // if (filter && filter.nodeUuid) { + // let nodeExistsInStates = false; + // for (const state of alertInstanceState.alertStates) { + // if ((state as AlertMissingDataState).nodeId === filter.nodeUuid) { + // nodeExistsInStates = true; + // break; + // } + // } + // if (!nodeExistsInStates) { + // return false; + // } + // } + // } + // } + return true; + } + + protected getDefaultAlertState(cluster: AlertCluster, item: AlertData): AlertState { + const base = super.getDefaultAlertState(cluster, item); + return { + ...base, + ui: { + ...base.ui, + severity: AlertSeverity.Danger, + }, + }; + } + + protected getUiMessage(alertState: AlertState, item: AlertData): AlertMessage { + const missing = item.meta as AlertMissingData; + if (!alertState.ui.isFiring) { + return { + text: i18n.translate('xpack.monitoring.alerts.missingData.ui.resolvedMessage', { + defaultMessage: `We are no longer detecting that monitoring data is missing for {stackProduct}:{stackProductName}, as of #resolved`, + values: { + stackProduct: missing.stackProduct, + stackProductName: missing.stackProductName, + }, + }), + tokens: [ + { + startToken: '#resolved', + type: AlertMessageTokenType.Time, + isAbsolute: true, + isRelative: false, + timestamp: alertState.ui.resolvedMS, + } as AlertMessageTimeToken, + ], + }; + } + return { + text: i18n.translate('xpack.monitoring.alerts.missingData.ui.firingMessage', { + defaultMessage: `For the past {gapDuration}, we have not detected any monitoring data from {stackProduct}:{stackProductName}, starting at #absolute`, + values: { + gapDuration: moment.duration(missing.gapDuration, 'milliseconds').humanize(), + stackProduct: missing.stackProduct, + stackProductName: missing.stackProductName, + }, + }), + nextSteps: [ + { + text: i18n.translate('xpack.monitoring.alerts.missingData.ui.nextSteps.hotThreads', { + defaultMessage: `#start_linkCheck hot threads#end_link`, + }), + tokens: [ + { + startToken: '#start_link', + endToken: '#end_link', + type: AlertMessageTokenType.DocLink, + partialUrl: `{elasticWebsiteUrl}/guide/en/elasticsearch/reference/{docLinkVersion}/cluster-nodes-hot-threads.html`, + } as AlertMessageDocLinkToken, + ], + }, + { + text: i18n.translate('xpack.monitoring.alerts.missingData.ui.nextSteps.runningTasks', { + defaultMessage: `#start_linkCheck long running tasks#end_link`, + }), + tokens: [ + { + startToken: '#start_link', + endToken: '#end_link', + type: AlertMessageTokenType.DocLink, + partialUrl: `{elasticWebsiteUrl}/guide/en/elasticsearch/reference/{docLinkVersion}/tasks.html`, + } as AlertMessageDocLinkToken, + ], + }, + ], + tokens: [ + { + startToken: '#absolute', + type: AlertMessageTokenType.Time, + isAbsolute: true, + isRelative: false, + timestamp: alertState.ui.triggeredMS, + } as AlertMessageTimeToken, + ], + }; + } + + protected executeActions( + instance: AlertInstance, + instanceState: AlertInstanceState, + item: AlertData | null, + cluster: AlertCluster + ) { + if (instanceState.alertStates.length === 0) { + return; + } + + const ccs = instanceState.alertStates.reduce((accum: string, state): string => { + if (state.ccs) { + return state.ccs; + } + return accum; + }, ''); + + const firingCount = instanceState.alertStates.filter((alertState) => alertState.ui.isFiring) + .length; + const firingStackProducts = instanceState.alertStates + .filter((_state) => (_state as AlertMissingDataState).ui.isFiring) + .map((_state) => { + const state = _state as AlertMissingDataState; + return `${state.stackProduct}:${state.stackProductUuid}`; + }) + .join(','); + if (firingCount > 0) { + const shortActionText = i18n.translate('xpack.monitoring.alerts.missingData.shortAction', { + defaultMessage: + 'Verify these stack products are up and running, then double check the monitoring settings.', + }); + const fullActionText = i18n.translate('xpack.monitoring.alerts.missingData.fullAction', { + defaultMessage: 'View what monitoring data we do have for these stack products.', + }); + const globalState = [`cluster_uuid:${cluster.clusterUuid}`]; + if (ccs) { + globalState.push(`ccs:${ccs}`); + } + const url = `${this.kibanaUrl}/app/monitoring#overview?_g=(${globalState.join(',')})`; + const action = `[${fullActionText}](${url})`; + const internalShortMessage = i18n.translate( + 'xpack.monitoring.alerts.missingData.firing.internalShortMessage', + { + defaultMessage: `We are not detecting monitoring data for {count} stack product(s) in cluster: {clusterName}. {shortActionText}`, + values: { + count: firingCount, + clusterName: cluster.clusterName, + shortActionText, + }, + } + ); + const internalFullMessage = i18n.translate( + 'xpack.monitoring.alerts.missingData.firing.internalFullMessage', + { + defaultMessage: `We are not detecting monitoring data for {count} stack product(s) in cluster: {clusterName}. {action}`, + values: { + count: firingCount, + clusterName: cluster.clusterName, + action, + }, + } + ); + instance.scheduleActions('default', { + internalShortMessage, + internalFullMessage: this.isCloud ? internalShortMessage : internalFullMessage, + state: FIRING, + stackProducts: firingStackProducts, + count: firingCount, + clusterName: cluster.clusterName, + action, + actionPlain: shortActionText, + }); + } else { + // const resolvedCount = instanceState.alertStates.filter( + // (alertState) => !alertState.ui.isFiring + // ).length; + // const resolvedNodes = instanceState.alertStates + // .filter((_state) => !(_state as AlertMissingDataState).ui.isFiring) + // .map((_state) => { + // const state = _state as AlertMissingDataState; + // return `${state.stackProduct}:${state.stackProductUuid}`; + // }) + // .join(','); + // if (resolvedCount > 0) { + // instance.scheduleActions('default', { + // internalShortMessage: i18n.translate( + // 'xpack.monitoring.alerts.missingData.resolved.internalShortMessage', + // { + // defaultMessage: `CPU usage alert is resolved for {count} node(s) in cluster: {clusterName}.`, + // values: { + // count: resolvedCount, + // clusterName: cluster.clusterName, + // }, + // } + // ), + // internalFullMessage: i18n.translate( + // 'xpack.monitoring.alerts.missingData.resolved.internalFullMessage', + // { + // defaultMessage: `CPU usage alert is resolved for {count} node(s) in cluster: {clusterName}.`, + // values: { + // count: resolvedCount, + // clusterName: cluster.clusterName, + // }, + // } + // ), + // state: RESOLVED, + // nodes: resolvedNodes, + // count: resolvedCount, + // clusterName: cluster.clusterName, + // }); + // } + } + } + + protected processData( + data: AlertData[], + clusters: AlertCluster[], + services: AlertServices, + logger: Logger + ) { + for (const cluster of clusters) { + const stackProducts = data.filter((_item) => _item.clusterUuid === cluster.clusterUuid); + if (stackProducts.length === 0) { + continue; + } + + const firingInstances = stackProducts.reduce((list: string[], stackProduct) => { + const missing = stackProduct.meta as AlertMissingData; + if (stackProduct.shouldFire) { + list.push(`${missing.stackProduct}:${missing.stackProductUuid}`); + } + return list; + }, [] as string[]); + firingInstances.sort(); // It doesn't matter how we sort, but keep the order consistent + const instanceId = `${this.type}:${cluster.clusterUuid}:${firingInstances.join(',')}`; + const instance = services.alertInstanceFactory(instanceId); + const instanceState = (instance.getState() as unknown) as AlertInstanceState; + const alertInstanceState: AlertInstanceState = { + alertStates: instanceState?.alertStates || [], + }; + let shouldExecuteActions = false; + for (const stackProduct of stackProducts) { + const missing = stackProduct.meta as AlertMissingData; + let state: AlertMissingDataState; + const indexInState = alertInstanceState.alertStates.findIndex((alertState) => { + const _alertState = alertState as AlertMissingDataState; + return ( + _alertState.cluster.clusterUuid === cluster.clusterUuid && + _alertState.stackProduct === (stackProduct.meta as AlertMissingData).stackProduct && + _alertState.stackProductUuid === + (stackProduct.meta as AlertMissingData).stackProductUuid + ); + }); + if (indexInState > -1) { + state = alertInstanceState.alertStates[indexInState] as AlertMissingDataState; + } else { + state = this.getDefaultAlertState(cluster, stackProduct) as AlertMissingDataState; + } + + state.stackProduct = missing.stackProduct; + state.stackProductUuid = missing.stackProductUuid; + state.gapDuration = missing.gapDuration; + + if (stackProduct.shouldFire) { + state.ui.triggeredMS = new Date().valueOf(); + state.ui.isFiring = true; + state.ui.message = this.getUiMessage(state, stackProduct); + state.ui.severity = stackProduct.severity; + state.ui.resolvedMS = 0; + shouldExecuteActions = true; + } else if (!stackProduct.shouldFire && state.ui.isFiring) { + state.ui.isFiring = false; + state.ui.resolvedMS = new Date().valueOf(); + state.ui.message = this.getUiMessage(state, stackProduct); + shouldExecuteActions = true; + } + + if (indexInState === -1) { + alertInstanceState.alertStates.push(state); + } else { + alertInstanceState.alertStates = [ + ...alertInstanceState.alertStates.slice(0, indexInState), + state, + ...alertInstanceState.alertStates.slice(indexInState + 1), + ]; + } + } + + instance.replaceState(alertInstanceState); + if (shouldExecuteActions) { + this.executeActions(instance, alertInstanceState, null, cluster); + } + } + } +} diff --git a/x-pack/plugins/monitoring/server/alerts/types.d.ts b/x-pack/plugins/monitoring/server/alerts/types.d.ts index b6c8427375841..c84618f775ec4 100644 --- a/x-pack/plugins/monitoring/server/alerts/types.d.ts +++ b/x-pack/plugins/monitoring/server/alerts/types.d.ts @@ -27,6 +27,12 @@ export interface AlertCpuUsageState extends AlertState { nodeName: string; } +export interface AlertMissingDataState extends AlertState { + stackProduct: string; + stackProductUuid: string; + gapDuration: number; +} + export interface AlertUiState { isFiring: boolean; severity: AlertSeverity; @@ -78,6 +84,15 @@ export interface AlertCpuUsageNodeStats { ccs: string | null; } +export interface AlertMissingData { + stackProduct: string; + stackProductUuid: string; + stackProductName: string; + clusterUuid: string; + gapDuration: number; + ccs: string | null; +} + export interface AlertData { instanceKey: string; clusterUuid: string; diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_data.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_data.ts new file mode 100644 index 0000000000000..d58665de23c32 --- /dev/null +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_data.ts @@ -0,0 +1,291 @@ +/* + * 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 { get } from 'lodash'; +import moment from 'moment'; +import { AlertCluster, AlertMissingData } from '../../alerts/types'; +import { + KIBANA_SYSTEM_ID, + BEATS_SYSTEM_ID, + APM_SYSTEM_ID, + LOGSTASH_SYSTEM_ID, + ELASTICSEARCH_SYSTEM_ID, +} from '../../../common/constants'; + +interface IndexBucketESResponse { + key: string; + clusters: { + buckets: ClusterBucketESResponse[]; + }; +} + +interface ClusterBucketESResponse { + key: string; + kibana_uuids: UuidResponse; + logstash_uuids: UuidResponse; + es_uuids: UuidResponse; + beats: { + beats_uuids: UuidResponse; + }; + apms: { + apm_uuids: UuidResponse; + }; +} + +interface UuidResponse { + buckets: UuidBucketESResponse[]; +} + +interface UuidBucketESResponse { + key: string; + top: { + hits: { + hits: TopHitESResponse[]; + }; + }; +} + +interface TopHitESResponse { + _source: { + timestamp: string; + }; +} + +function findNonEmptyBucket(bucket: ClusterBucketESResponse): UuidResponse { + if (bucket.beats.beats_uuids.buckets.length > 0) { + return bucket.beats.beats_uuids; + } + if (bucket.apms.apm_uuids.buckets.length > 0) { + return bucket.apms.apm_uuids; + } + if (bucket.kibana_uuids.buckets.length > 0) { + return bucket.kibana_uuids; + } + if (bucket.logstash_uuids.buckets.length > 0) { + return bucket.logstash_uuids; + } + if (bucket.es_uuids.buckets.length > 0) { + return bucket.es_uuids; + } + return { buckets: [] }; +} + +function getStackProductFromIndex(index: string, bucket: ClusterBucketESResponse) { + if (index.includes('-kibana-')) { + return KIBANA_SYSTEM_ID; + } + if (index.includes('-beats-')) { + if (bucket.apms.apm_uuids.buckets.length > 0) { + return APM_SYSTEM_ID; + } + return BEATS_SYSTEM_ID; + } + if (index.includes('-logstash-')) { + return LOGSTASH_SYSTEM_ID; + } + if (index.includes('-es-')) { + return ELASTICSEARCH_SYSTEM_ID; + } + return ''; +} + +export async function fetchMissingData( + callCluster: any, + clusters: AlertCluster[], + index: string, + limit: number, + size: number +): Promise { + const endMs = +new Date(); + const startMs = endMs - limit; + + const nameFields = [ + 'source_node.name', + 'kibana_stats.kibana.name', + 'logstash_stats.logstash.host', + 'beats_stats.beat.name', + ]; + const topHitsAgg = { + top: { + top_hits: { + size: 2, + sort: [ + { + timestamp: { + order: 'desc', + }, + }, + ], + _source: { + includes: ['timestamp', ...nameFields], + }, + }, + }, + top_hit: { + max: { + field: 'timestamp', + }, + }, + }; + + const params = { + index, + filterPath: ['aggregations.index.buckets'], + body: { + size: 0, + query: { + bool: { + filter: [ + { + terms: { + cluster_uuid: clusters.map((cluster) => cluster.clusterUuid), + }, + }, + { + range: { + timestamp: { + format: 'epoch_millis', + gte: startMs, + lte: endMs, + }, + }, + }, + ], + }, + }, + aggs: { + index: { + terms: { + field: '_index', + size, + }, + aggs: { + clusters: { + terms: { + field: 'cluster_uuid', + size, + }, + aggs: { + es_uuids: { + terms: { + field: 'node_stats.node_id', + size, + order: { top_hit: 'desc' }, + }, + aggs: topHitsAgg, + }, + kibana_uuids: { + terms: { + field: 'kibana_stats.kibana.uuid', + size, + order: { top_hit: 'desc' }, + }, + aggs: topHitsAgg, + }, + beats: { + filter: { + bool: { + must_not: { + term: { + 'beats_stats.beat.type': 'apm-server', + }, + }, + }, + }, + aggs: { + beats_uuids: { + terms: { + field: 'beats_stats.beat.uuid', + size, + order: { top_hit: 'desc' }, + }, + aggs: topHitsAgg, + }, + }, + }, + apms: { + filter: { + bool: { + must: { + term: { + 'beats_stats.beat.type': 'apm-server', + }, + }, + }, + }, + aggs: { + apm_uuids: { + terms: { + field: 'beats_stats.beat.uuid', + size, + order: { top_hit: 'desc' }, + }, + aggs: topHitsAgg, + }, + }, + }, + logstash_uuids: { + terms: { + field: 'logstash_stats.logstash.uuid', + size, + order: { top_hit: 'desc' }, + }, + aggs: topHitsAgg, + }, + }, + }, + }, + }, + }, + }, + }; + + const response = await callCluster('search', params); + const indexBuckets = get(response, 'aggregations.index.buckets', []) as IndexBucketESResponse[]; + const uniqueList: { + [id: string]: AlertMissingData; + } = {}; + for (const indexBucket of indexBuckets) { + const clusterBuckets = indexBucket.clusters.buckets; + for (const clusterBucket of clusterBuckets) { + const clusterUuid = clusterBucket.key; + const uuidBuckets = findNonEmptyBucket(clusterBucket).buckets; + for (const uuidBucket of uuidBuckets) { + const stackProductUuid = uuidBucket.key; + const stackProduct = getStackProductFromIndex(indexBucket.key, clusterBucket); + let differenceInMs = -1; + if (uuidBucket.top.hits.hits.length === 0) { + differenceInMs = -2; + } else if (uuidBucket.top.hits.hits.length === 1) { + differenceInMs = moment().diff(moment(uuidBucket.top.hits.hits[0]._source.timestamp)); + } else { + const first = moment(uuidBucket.top.hits.hits[0]._source.timestamp); + const second = moment(uuidBucket.top.hits.hits[1]._source.timestamp); + differenceInMs = first.diff(second); + } + + let stackProductName = stackProductUuid; + for (const nameField of nameFields) { + stackProductName = get(uuidBucket, `top.hits.hits[0]._source.${nameField}`); + if (stackProductName) { + break; + } + } + + uniqueList[`${clusterUuid}::${stackProduct}::${stackProductUuid}`] = { + stackProduct, + stackProductUuid, + stackProductName, + clusterUuid, + gapDuration: differenceInMs, + ccs: indexBucket.key.includes(':') ? indexBucket.key.split(':')[0] : null, + }; + } + } + } + + const missingData = Object.values(uniqueList); + return missingData; +} From 2f496f13ea3709db936292c671083b2ad0306ed2 Mon Sep 17 00:00:00 2001 From: chrisronline Date: Tue, 22 Sep 2020 15:48:59 -0400 Subject: [PATCH 02/16] Surface alert most places --- x-pack/plugins/monitoring/common/types.ts | 6 +- .../monitoring/public/alerts/badge.tsx | 15 +- .../monitoring/public/alerts/callout.tsx | 15 +- .../monitoring/public/alerts/panel.tsx | 10 +- .../monitoring/public/alerts/status.tsx | 15 +- .../components/beats/listing/listing.js | 39 ++- .../public/components/beats/stats.js | 4 +- .../components/elasticsearch/node/node.js | 11 +- .../components/elasticsearch/nodes/nodes.js | 20 +- .../components/kibana/instances/instances.js | 13 +- .../components/logstash/listing/listing.js | 13 +- .../public/views/beats/beat/index.js | 13 +- .../public/views/beats/listing/index.js | 17 +- .../elasticsearch/node/advanced/index.js | 8 +- .../public/views/elasticsearch/node/index.js | 8 +- .../public/views/elasticsearch/nodes/index.js | 8 +- .../public/views/kibana/instance/index.js | 24 +- .../public/views/kibana/instances/index.js | 8 +- .../views/logstash/node/advanced/index.js | 28 ++- .../public/views/logstash/node/index.js | 24 +- .../public/views/logstash/nodes/index.js | 8 +- .../monitoring/server/alerts/base_alert.ts | 17 +- .../server/alerts/cpu_usage_alert.ts | 4 +- .../server/alerts/missing_data_alert.ts | 234 +++++++++++------- .../monitoring/server/alerts/types.d.ts | 1 + .../server/lib/alerts/fetch_missing_data.ts | 6 +- .../get_listing_link_for_stack_product.ts | 28 +++ .../lib/alerts/get_stack_product_label.ts | 17 ++ .../get_type_label_for_stack_product.ts | 51 ++++ 29 files changed, 524 insertions(+), 141 deletions(-) create mode 100644 x-pack/plugins/monitoring/server/lib/alerts/get_listing_link_for_stack_product.ts create mode 100644 x-pack/plugins/monitoring/server/lib/alerts/get_stack_product_label.ts create mode 100644 x-pack/plugins/monitoring/server/lib/alerts/get_type_label_for_stack_product.ts diff --git a/x-pack/plugins/monitoring/common/types.ts b/x-pack/plugins/monitoring/common/types.ts index f5dc85dce32e1..9fe71eac93b87 100644 --- a/x-pack/plugins/monitoring/common/types.ts +++ b/x-pack/plugins/monitoring/common/types.ts @@ -30,10 +30,14 @@ export interface CommonAlertState { // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface CommonAlertFilter {} -export interface CommonAlertCpuUsageFilter extends CommonAlertFilter { +export interface CommonAlertNodeUuidFilter extends CommonAlertFilter { nodeUuid: string; } +export interface CommonAlertStackProductFilter extends CommonAlertFilter { + stackProduct: string; +} + export interface CommonAlertParamDetail { label: string; type: AlertParamType; diff --git a/x-pack/plugins/monitoring/public/alerts/badge.tsx b/x-pack/plugins/monitoring/public/alerts/badge.tsx index 1d67eebb1705c..cf75939b14efc 100644 --- a/x-pack/plugins/monitoring/public/alerts/badge.tsx +++ b/x-pack/plugins/monitoring/public/alerts/badge.tsx @@ -18,7 +18,7 @@ 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 { AlertMessage, AlertState } from '../../server/alerts/types'; import { AlertPanel } from './panel'; import { Legacy } from '../legacy_shims'; import { isInSetupMode } from '../lib/setup_mode'; @@ -39,9 +39,10 @@ interface AlertInPanel { interface Props { alerts: { [alertTypeId: string]: CommonAlertStatus }; stateFilter: (state: AlertState) => boolean; + nextStepsFilter: (nextStep: AlertMessage) => boolean; } export const AlertsBadge: React.FC = (props: Props) => { - const { stateFilter = () => true } = props; + const { stateFilter = () => true, nextStepsFilter = () => true } = props; const [showPopover, setShowPopover] = React.useState(null); const inSetupMode = isInSetupMode(); const alerts = Object.values(props.alerts).filter(Boolean); @@ -80,7 +81,7 @@ export const AlertsBadge: React.FC = (props: Props) => { id: index + 1, title: alertStatus.alert.label, width: 400, - content: , + content: , }; }), ]; @@ -158,7 +159,13 @@ export const AlertsBadge: React.FC = (props: Props) => { id: index + 1, title: getDateFromState(alertStatus.alertState), width: 400, - content: , + content: ( + + ), }; }), ]; diff --git a/x-pack/plugins/monitoring/public/alerts/callout.tsx b/x-pack/plugins/monitoring/public/alerts/callout.tsx index cad98dd1e6aec..bc9c5e5633ad1 100644 --- a/x-pack/plugins/monitoring/public/alerts/callout.tsx +++ b/x-pack/plugins/monitoring/public/alerts/callout.tsx @@ -32,9 +32,10 @@ const TYPES = [ interface Props { alerts: { [alertTypeId: string]: CommonAlertStatus }; stateFilter: (state: AlertState) => boolean; + nextStepsFilter: (nextStep: AlertMessage) => boolean; } export const AlertsCallout: React.FC = (props: Props) => { - const { alerts, stateFilter = () => true } = props; + const { alerts, stateFilter = () => true, nextStepsFilter = () => true } = props; const callouts = TYPES.map((type) => { const list = []; @@ -49,18 +50,18 @@ export const AlertsCallout: React.FC = (props: Props) => { if (list.length) { return ( - +
    {list.map((state, index) => { const nextStepsUi = state.ui.message.nextSteps && state.ui.message.nextSteps.length ? (
      - {state.ui.message.nextSteps.map( - (step: AlertMessage, nextStepIndex: number) => ( + {state.ui.message.nextSteps + .filter(nextStepsFilter) + .map((step: AlertMessage, nextStepIndex: number) => (
    • {replaceTokens(step)}
    • - ) - )} + ))}
    ) : null; @@ -74,7 +75,7 @@ export const AlertsCallout: React.FC = (props: Props) => {
- +
); } }); diff --git a/x-pack/plugins/monitoring/public/alerts/panel.tsx b/x-pack/plugins/monitoring/public/alerts/panel.tsx index 91604acf115fa..ee605592e9408 100644 --- a/x-pack/plugins/monitoring/public/alerts/panel.tsx +++ b/x-pack/plugins/monitoring/public/alerts/panel.tsx @@ -30,11 +30,13 @@ import { BASE_ALERT_API_PATH } from '../../../alerts/common'; interface Props { alert: CommonAlertStatus; alertState?: CommonAlertState; + nextStepsFilter: (nextStep: AlertMessage) => boolean; } export const AlertPanel: React.FC = (props: Props) => { const { alert: { alert }, alertState, + nextStepsFilter = () => true, } = props; const [showFlyout, setShowFlyout] = React.useState(false); const [isEnabled, setIsEnabled] = React.useState(alert.rawAlert.enabled); @@ -198,9 +200,11 @@ export const AlertPanel: React.FC = (props: Props) => { const nextStepsUi = alertState.state.ui.message.nextSteps && alertState.state.ui.message.nextSteps.length ? ( - {alertState.state.ui.message.nextSteps.map((step: AlertMessage, index: number) => ( - - ))} + {alertState.state.ui.message.nextSteps + .filter(nextStepsFilter) + .map((step: AlertMessage, index: number) => ( + + ))} ) : null; diff --git a/x-pack/plugins/monitoring/public/alerts/status.tsx b/x-pack/plugins/monitoring/public/alerts/status.tsx index 0407ddfecf5e9..dba66df0e4474 100644 --- a/x-pack/plugins/monitoring/public/alerts/status.tsx +++ b/x-pack/plugins/monitoring/public/alerts/status.tsx @@ -9,7 +9,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { CommonAlertStatus } from '../../common/types'; import { AlertSeverity } from '../../common/enums'; -import { AlertState } from '../../server/alerts/types'; +import { AlertMessage, AlertState } from '../../server/alerts/types'; import { AlertsBadge } from './badge'; import { isInSetupMode } from '../lib/setup_mode'; @@ -18,9 +18,16 @@ interface Props { showBadge: boolean; showOnlyCount: boolean; stateFilter: (state: AlertState) => boolean; + nextStepsFilter: (nextStep: AlertMessage) => boolean; } export const AlertsStatus: React.FC = (props: Props) => { - const { alerts, showBadge = false, showOnlyCount = false, stateFilter = () => true } = props; + const { + alerts, + showBadge = false, + showOnlyCount = false, + stateFilter = () => true, + nextStepsFilter = () => true, + } = props; const inSetupMode = isInSetupMode(); if (!alerts) { @@ -71,7 +78,9 @@ export const AlertsStatus: React.FC = (props: Props) => { } if (showBadge || inSetupMode) { - return ; + return ( + + ); } const severity = atLeastOneDanger ? AlertSeverity.Danger : AlertSeverity.Warning; diff --git a/x-pack/plugins/monitoring/public/components/beats/listing/listing.js b/x-pack/plugins/monitoring/public/components/beats/listing/listing.js index be8595e8e6bbe..c373794ac9980 100644 --- a/x-pack/plugins/monitoring/public/components/beats/listing/listing.js +++ b/x-pack/plugins/monitoring/public/components/beats/listing/listing.js @@ -25,10 +25,13 @@ import { SetupModeBadge } from '../../setup_mode/badge'; import { FormattedMessage } from '@kbn/i18n/react'; import { isSetupModeFeatureEnabled } from '../../../lib/setup_mode'; import { SetupModeFeature } from '../../../../common/enums'; +import { AlertsCallout } from '../../../alerts/callout'; +import { AlertsStatus } from '../../../alerts/status'; export class Listing extends PureComponent { getColumns() { const setupMode = this.props.setupMode; + const alerts = this.props.alerts; return [ { @@ -71,6 +74,29 @@ export class Listing extends PureComponent { ); }, }, + { + name: i18n.translate('xpack.monitoring.beats.instances.alertsColumnTitle', { + defaultMessage: 'Alerts', + }), + field: 'alerts', + width: '175px', + sortable: true, + render: (_field, beat) => { + return ( + state.stackProductUuid === beat.uuid} + nextStepsFilter={(nextStep) => { + if (nextStep.text.includes('Beat instances')) { + return false; + } + return true; + }} + /> + ); + }, + }, { name: i18n.translate('xpack.monitoring.beats.instances.typeTitle', { defaultMessage: 'Type', @@ -121,7 +147,7 @@ export class Listing extends PureComponent { } render() { - const { stats, data, sorting, pagination, onTableChange, setupMode } = this.props; + const { stats, data, sorting, pagination, onTableChange, setupMode, alerts } = this.props; let setupModeCallOut = null; if (isSetupModeFeatureEnabled(SetupModeFeature.MetricbeatMigration)) { @@ -154,9 +180,18 @@ export class Listing extends PureComponent { - + {setupModeCallOut} + { + if (nextStep.text.includes('Beat beats')) { + return false; + } + return true; + }} + /> ; + return ; } diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/node/node.js b/x-pack/plugins/monitoring/public/components/elasticsearch/node/node.js index ac1a5212a8d26..17802e64d9199 100644 --- a/x-pack/plugins/monitoring/public/components/elasticsearch/node/node.js +++ b/x-pack/plugins/monitoring/public/components/elasticsearch/node/node.js @@ -77,7 +77,16 @@ export const Node = ({ /> - state.nodeId === nodeId} /> + state.nodeId === nodeId || state.stackProductUuid === nodeId} + nextStepsFilter={(nextStep) => { + if (nextStep.text.includes('Elasticsearch nodes')) { + return false; + } + return true; + }} + /> {metricsToShow.map((metric, index) => ( diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js b/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js index 43512f8e528f6..d281d7456add6 100644 --- a/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js +++ b/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js @@ -34,6 +34,7 @@ import { ListingCallOut } from '../../setup_mode/listing_callout'; import { AlertsStatus } from '../../../alerts/status'; import { isSetupModeFeatureEnabled } from '../../../lib/setup_mode'; import { SetupModeFeature } from '../../../../common/enums'; +import { AlertsCallout } from '../../../alerts/callout'; const getNodeTooltip = (node) => { const { nodeTypeLabel, nodeTypeClass } = node; @@ -138,7 +139,15 @@ const getColumns = (showCgroupMetricsElasticsearch, setupMode, clusterUuid, aler state.nodeId === node.resolver} + stateFilter={(state) => + state.nodeId === node.resolver || state.stackProductUuid === node.resolver + } + nextStepsFilter={(nextStep) => { + if (nextStep.text.includes('Elasticsearch nodes')) { + return false; + } + return true; + }} /> ); }, @@ -459,6 +468,15 @@ export function ElasticsearchNodes({ clusterStatus, showCgroupMetricsElasticsear {renderClusterStatus()} {setupModeCallout} + { + if (nextStep.text.includes('Elasticsearch nodes')) { + return false; + } + return true; + }} + /> { width: '175px', sortable: true, render: () => { - return ; + return ( + { + if (nextStep.text.includes('Kibana instances')) { + return false; + } + return true; + }} + /> + ); }, }, { diff --git a/x-pack/plugins/monitoring/public/components/logstash/listing/listing.js b/x-pack/plugins/monitoring/public/components/logstash/listing/listing.js index 4a1137079ebb4..a5db433bbfe0a 100644 --- a/x-pack/plugins/monitoring/public/components/logstash/listing/listing.js +++ b/x-pack/plugins/monitoring/public/components/logstash/listing/listing.js @@ -84,7 +84,18 @@ export class Listing extends PureComponent { width: '175px', sortable: true, render: () => { - return ; + return ( + { + if (nextStep.text.includes('Logstash nodes')) { + return false; + } + return true; + }} + /> + ); }, }, { diff --git a/x-pack/plugins/monitoring/public/views/beats/beat/index.js b/x-pack/plugins/monitoring/public/views/beats/beat/index.js index 8b5aeb016080f..03debae53989d 100644 --- a/x-pack/plugins/monitoring/public/views/beats/beat/index.js +++ b/x-pack/plugins/monitoring/public/views/beats/beat/index.js @@ -11,7 +11,7 @@ import { routeInitProvider } from '../../../lib/route_init'; import { MonitoringViewBaseController } from '../../'; import { getPageData } from './get_page_data'; import template from './index.html'; -import { CODE_PATH_BEATS } from '../../../../common/constants'; +import { CODE_PATH_BEATS, ALERT_MISSING_DATA, BEATS_SYSTEM_ID } from '../../../../common/constants'; uiRoutes.when('/beats/beat/:beatUuid', { template, @@ -43,6 +43,17 @@ uiRoutes.when('/beats/beat/:beatUuid', { getPageData, $scope, $injector, + alerts: { + shouldFetch: true, + options: { + alertTypeIds: [ALERT_MISSING_DATA], + filters: [ + { + stackProduct: BEATS_SYSTEM_ID, + }, + ], + }, + }, }); this.data = pageData; diff --git a/x-pack/plugins/monitoring/public/views/beats/listing/index.js b/x-pack/plugins/monitoring/public/views/beats/listing/index.js index f82e0a437bd0b..336648f3e86d0 100644 --- a/x-pack/plugins/monitoring/public/views/beats/listing/index.js +++ b/x-pack/plugins/monitoring/public/views/beats/listing/index.js @@ -14,7 +14,7 @@ import template from './index.html'; import React, { Fragment } from 'react'; import { Listing } from '../../../components/beats/listing/listing'; import { SetupModeRenderer } from '../../../components/renderers'; -import { CODE_PATH_BEATS, BEATS_SYSTEM_ID } from '../../../../common/constants'; +import { CODE_PATH_BEATS, BEATS_SYSTEM_ID, ALERT_MISSING_DATA } from '../../../../common/constants'; uiRoutes.when('/beats/beats', { template, @@ -42,15 +42,23 @@ uiRoutes.when('/beats/beats', { reactNodeId: 'monitoringBeatsInstancesApp', $scope, $injector, + alerts: { + shouldFetch: true, + options: { + alertTypeIds: [ALERT_MISSING_DATA], + filters: [ + { + stackProduct: BEATS_SYSTEM_ID, + }, + ], + }, + }, }); this.data = $route.current.locals.pageData; this.scope = $scope; this.injector = $injector; - //Bypassing super.updateData, since this controller loads its own data - this._isDataInitialized = true; - $scope.$watch( () => this.data, () => this.renderComponent() @@ -70,6 +78,7 @@ uiRoutes.when('/beats/beats', { - + { + if (nextStep.text.includes('Kibana instances')) { + return false; + } + return true; + }} + /> diff --git a/x-pack/plugins/monitoring/public/views/kibana/instances/index.js b/x-pack/plugins/monitoring/public/views/kibana/instances/index.js index 7106da0fdabd3..785a3ea575d55 100644 --- a/x-pack/plugins/monitoring/public/views/kibana/instances/index.js +++ b/x-pack/plugins/monitoring/public/views/kibana/instances/index.js @@ -16,6 +16,7 @@ import { KIBANA_SYSTEM_ID, CODE_PATH_KIBANA, ALERT_KIBANA_VERSION_MISMATCH, + ALERT_MISSING_DATA, } from '../../../../common/constants'; uiRoutes.when('/kibana/instances', { @@ -40,7 +41,12 @@ uiRoutes.when('/kibana/instances', { alerts: { shouldFetch: true, options: { - alertTypeIds: [ALERT_KIBANA_VERSION_MISMATCH], + alertTypeIds: [ALERT_KIBANA_VERSION_MISMATCH, ALERT_MISSING_DATA], + filters: [ + { + stackProduct: KIBANA_SYSTEM_ID, + }, + ], }, }, }); diff --git a/x-pack/plugins/monitoring/public/views/logstash/node/advanced/index.js b/x-pack/plugins/monitoring/public/views/logstash/node/advanced/index.js index de4cc1eb7e2d1..f7d2b5d014ea0 100644 --- a/x-pack/plugins/monitoring/public/views/logstash/node/advanced/index.js +++ b/x-pack/plugins/monitoring/public/views/logstash/node/advanced/index.js @@ -26,7 +26,13 @@ import { EuiFlexItem, } from '@elastic/eui'; import { MonitoringTimeseriesContainer } from '../../../../components/chart'; -import { CODE_PATH_LOGSTASH } from '../../../../../common/constants'; +import { + CODE_PATH_LOGSTASH, + ALERT_LOGSTASH_VERSION_MISMATCH, + ALERT_MISSING_DATA, + LOGSTASH_SYSTEM_ID, +} from '../../../../../common/constants'; +import { AlertsCallout } from '../../../../alerts/callout'; function getPageData($injector) { const $http = $injector.get('$http'); @@ -69,6 +75,17 @@ uiRoutes.when('/logstash/node/:uuid/advanced', { reactNodeId: 'monitoringLogstashNodeAdvancedApp', $scope, $injector, + alerts: { + shouldFetch: true, + options: { + alertTypeIds: [ALERT_LOGSTASH_VERSION_MISMATCH, ALERT_MISSING_DATA], + filters: [ + { + stackProduct: LOGSTASH_SYSTEM_ID, + }, + ], + }, + }, }); $scope.$watch( @@ -102,6 +119,15 @@ uiRoutes.when('/logstash/node/:uuid/advanced', { + { + if (nextStep.text.includes('Logstash nodes')) { + return false; + } + return true; + }} + /> {metricsToShow.map((metric, index) => ( diff --git a/x-pack/plugins/monitoring/public/views/logstash/node/index.js b/x-pack/plugins/monitoring/public/views/logstash/node/index.js index 9138a85e58517..4dfb7e61da37e 100644 --- a/x-pack/plugins/monitoring/public/views/logstash/node/index.js +++ b/x-pack/plugins/monitoring/public/views/logstash/node/index.js @@ -26,7 +26,12 @@ import { } from '@elastic/eui'; import { MonitoringTimeseriesContainer } from '../../../components/chart'; import { MonitoringViewBaseController } from '../../base_controller'; -import { CODE_PATH_LOGSTASH, ALERT_LOGSTASH_VERSION_MISMATCH } from '../../../../common/constants'; +import { + CODE_PATH_LOGSTASH, + ALERT_LOGSTASH_VERSION_MISMATCH, + ALERT_MISSING_DATA, + LOGSTASH_SYSTEM_ID, +} from '../../../../common/constants'; import { AlertsCallout } from '../../../alerts/callout'; function getPageData($injector) { @@ -73,7 +78,12 @@ uiRoutes.when('/logstash/node/:uuid', { alerts: { shouldFetch: true, options: { - alertTypeIds: [ALERT_LOGSTASH_VERSION_MISMATCH], + alertTypeIds: [ALERT_LOGSTASH_VERSION_MISMATCH, ALERT_MISSING_DATA], + filters: [ + { + stackProduct: LOGSTASH_SYSTEM_ID, + }, + ], }, }, }); @@ -110,7 +120,15 @@ uiRoutes.when('/logstash/node/:uuid', { - + { + if (nextStep.text.includes('Logstash nodes')) { + return false; + } + return true; + }} + /> {metricsToShow.map((metric, index) => ( diff --git a/x-pack/plugins/monitoring/public/views/logstash/nodes/index.js b/x-pack/plugins/monitoring/public/views/logstash/nodes/index.js index 563d04af55bb2..569e2f70d13fb 100644 --- a/x-pack/plugins/monitoring/public/views/logstash/nodes/index.js +++ b/x-pack/plugins/monitoring/public/views/logstash/nodes/index.js @@ -15,6 +15,7 @@ import { CODE_PATH_LOGSTASH, LOGSTASH_SYSTEM_ID, ALERT_LOGSTASH_VERSION_MISMATCH, + ALERT_MISSING_DATA, } from '../../../../common/constants'; uiRoutes.when('/logstash/nodes', { @@ -39,7 +40,12 @@ uiRoutes.when('/logstash/nodes', { alerts: { shouldFetch: true, options: { - alertTypeIds: [ALERT_LOGSTASH_VERSION_MISMATCH], + alertTypeIds: [ALERT_LOGSTASH_VERSION_MISMATCH, ALERT_MISSING_DATA], + filters: [ + { + stackProduct: LOGSTASH_SYSTEM_ID, + }, + ], }, }, }); diff --git a/x-pack/plugins/monitoring/server/alerts/base_alert.ts b/x-pack/plugins/monitoring/server/alerts/base_alert.ts index f583b4882f83c..4d3776b7a96e8 100644 --- a/x-pack/plugins/monitoring/server/alerts/base_alert.ts +++ b/x-pack/plugins/monitoring/server/alerts/base_alert.ts @@ -197,7 +197,18 @@ export class BaseAlert { } const alertInstance: RawAlertInstance = states.alertInstances[instanceId]; if (alertInstance && this.filterAlertInstance(alertInstance, filters)) { - accum[instanceId] = alertInstance; + accum[instanceId] = { + ...alertInstance, + state: alertInstance.state + ? { + alertStates: (alertInstance.state as AlertInstanceState).alertStates.filter( + (alertState: AlertState) => { + return this.filterAlertState(alertState, filters); + } + ), + } + : alertInstance.state, + }; } return accum; }, @@ -209,6 +220,10 @@ export class BaseAlert { return true; } + protected filterAlertState(alertState: AlertState, filters: CommonAlertFilter[]) { + return true; + } + protected async execute({ services, params, state }: AlertExecutorOptions): Promise { const logger = this.getLogger(this.type); logger.debug( diff --git a/x-pack/plugins/monitoring/server/alerts/cpu_usage_alert.ts b/x-pack/plugins/monitoring/server/alerts/cpu_usage_alert.ts index 5bca84e33da3c..6ed3048e94533 100644 --- a/x-pack/plugins/monitoring/server/alerts/cpu_usage_alert.ts +++ b/x-pack/plugins/monitoring/server/alerts/cpu_usage_alert.ts @@ -27,7 +27,7 @@ import { RawAlertInstance } from '../../../alerts/common'; import { parseDuration } from '../../../alerts/common/parse_duration'; import { CommonAlertFilter, - CommonAlertCpuUsageFilter, + CommonAlertNodeUuidFilter, CommonAlertParams, CommonAlertParamDetail, } from '../../common/types'; @@ -177,7 +177,7 @@ export class CpuUsageAlert extends BaseAlert { const alertInstanceState = (alertInstance.state as unknown) as AlertInstanceState; if (filters && filters.length) { for (const _filter of filters) { - const filter = _filter as CommonAlertCpuUsageFilter; + const filter = _filter as CommonAlertNodeUuidFilter; if (filter && filter.nodeUuid) { let nodeExistsInStates = false; for (const state of alertInstanceState.alertStates) { diff --git a/x-pack/plugins/monitoring/server/alerts/missing_data_alert.ts b/x-pack/plugins/monitoring/server/alerts/missing_data_alert.ts index 5e21855f14698..2636e0653c8f9 100644 --- a/x-pack/plugins/monitoring/server/alerts/missing_data_alert.ts +++ b/x-pack/plugins/monitoring/server/alerts/missing_data_alert.ts @@ -16,7 +16,7 @@ import { AlertMissingData, AlertMessageTimeToken, AlertInstanceState, - AlertMessageDocLinkToken, + AlertMessageLinkToken, } from './types'; import { AlertInstance, AlertServices } from '../../../alerts/server'; import { INDEX_PATTERN, ALERT_MISSING_DATA } from '../../common/constants'; @@ -24,13 +24,22 @@ import { getCcsIndexPattern } from '../lib/alerts/get_ccs_index_pattern'; import { AlertMessageTokenType, AlertSeverity, AlertParamType } from '../../common/enums'; import { RawAlertInstance } from '../../../alerts/common'; import { parseDuration } from '../../../alerts/common/parse_duration'; -import { CommonAlertFilter, CommonAlertParams, CommonAlertParamDetail } from '../../common/types'; +import { + CommonAlertFilter, + CommonAlertParams, + CommonAlertParamDetail, + CommonAlertStackProductFilter, + CommonAlertNodeUuidFilter, +} from '../../common/types'; import { appendMetricbeatIndex } from '../lib/alerts/append_mb_index'; import { fetchMissingData } from '../lib/alerts/fetch_missing_data'; +import { getTypeLabelForStackProduct } from '../lib/alerts/get_type_label_for_stack_product'; +import { getListingLinkForStackProduct } from '../lib/alerts/get_listing_link_for_stack_product'; +import { getStackProductLabel } from '../lib/alerts/get_stack_product_label'; -// const RESOLVED = i18n.translate('xpack.monitoring.alerts.missingData.resolved', { -// defaultMessage: 'resolved', -// }); +const RESOLVED = i18n.translate('xpack.monitoring.alerts.missingData.resolved', { + defaultMessage: 'resolved', +}); const FIRING = i18n.translate('xpack.monitoring.alerts.missingData.firing', { defaultMessage: 'firing', }); @@ -159,33 +168,55 @@ export class MissingDataAlert extends BaseAlert { return { instanceKey: `${missing.clusterUuid}:${missing.stackProduct}:${missing.stackProductUuid}`, clusterUuid: missing.clusterUuid, - shouldFire: missing.gapDuration > duration, + shouldFire: missing.gapDuration > duration && missing.gapDuration <= limit, severity: AlertSeverity.Danger, - meta: missing, + meta: { missing, limit }, ccs: missing.ccs, }; }); } protected filterAlertInstance(alertInstance: RawAlertInstance, filters: CommonAlertFilter[]) { - // const alertInstanceState = (alertInstance.state as unknown) as AlertInstanceState; - // if (filters && filters.length) { - // for (const _filter of filters) { - // const filter = _filter as CommonAlertCpuUsageFilter; - // if (filter && filter.nodeUuid) { - // let nodeExistsInStates = false; - // for (const state of alertInstanceState.alertStates) { - // if ((state as AlertMissingDataState).nodeId === filter.nodeUuid) { - // nodeExistsInStates = true; - // break; - // } - // } - // if (!nodeExistsInStates) { - // return false; - // } - // } - // } - // } + const alertInstanceState = (alertInstance.state as unknown) as AlertInstanceState; + if (filters && filters.length) { + for (const filter of filters) { + const stackProductFilter = filter as CommonAlertStackProductFilter; + if (stackProductFilter && stackProductFilter.stackProduct) { + let existsInState = false; + for (const state of alertInstanceState.alertStates) { + if ((state as AlertMissingDataState).stackProduct === stackProductFilter.stackProduct) { + existsInState = true; + break; + } + } + if (!existsInState) { + return false; + } + } + } + } + return true; + } + + protected filterAlertState(alertState: AlertState, filters: CommonAlertFilter[]) { + const state = alertState as AlertMissingDataState; + if (filters && filters.length) { + for (const filter of filters) { + const stackProductFilter = filter as CommonAlertStackProductFilter; + if (stackProductFilter && stackProductFilter.stackProduct) { + if (state.stackProduct !== stackProductFilter.stackProduct) { + return false; + } + } + + const nodeUuidFilter = filter as CommonAlertNodeUuidFilter; + if (nodeUuidFilter && nodeUuidFilter.nodeUuid) { + if (state.stackProductUuid !== nodeUuidFilter.nodeUuid) { + return false; + } + } + } + } return true; } @@ -201,13 +232,30 @@ export class MissingDataAlert extends BaseAlert { } protected getUiMessage(alertState: AlertState, item: AlertData): AlertMessage { - const missing = item.meta as AlertMissingData; + const { missing, limit } = item.meta as { missing: AlertMissingData; limit: number }; + const globalState = [`cluster_uuid:${item.clusterUuid}`]; + if (item.ccs) { + globalState.push(`ccs:${item.ccs}`); + } if (!alertState.ui.isFiring) { + if (missing.gapDuration > limit) { + return { + text: i18n.translate('xpack.monitoring.alerts.missingData.ui.notQuiteResolvedMessage', { + defaultMessage: `We are still not seeing monitoring data for the {stackProduct} {type}: {stackProductName} and will stop trying. To change this, configure the alert to look farther back for data.`, + values: { + stackProduct: getStackProductLabel(missing.stackProduct), + type: getTypeLabelForStackProduct(missing.stackProduct, false), + stackProductName: missing.stackProductName, + }, + }), + }; + } return { text: i18n.translate('xpack.monitoring.alerts.missingData.ui.resolvedMessage', { - defaultMessage: `We are no longer detecting that monitoring data is missing for {stackProduct}:{stackProductName}, as of #resolved`, + defaultMessage: `We are now seeing monitoring data for the {stackProduct} {type}: {stackProductName}, as of #resolved`, values: { - stackProduct: missing.stackProduct, + stackProduct: getStackProductLabel(missing.stackProduct), + type: getTypeLabelForStackProduct(missing.stackProduct, false), stackProductName: missing.stackProductName, }, }), @@ -224,39 +272,41 @@ export class MissingDataAlert extends BaseAlert { } return { text: i18n.translate('xpack.monitoring.alerts.missingData.ui.firingMessage', { - defaultMessage: `For the past {gapDuration}, we have not detected any monitoring data from {stackProduct}:{stackProductName}, starting at #absolute`, + defaultMessage: `For the past {gapDuration}, we have not detected any monitoring data from the {stackProduct} {type}: {stackProductName}, starting at #absolute`, values: { gapDuration: moment.duration(missing.gapDuration, 'milliseconds').humanize(), - stackProduct: missing.stackProduct, + stackProduct: getStackProductLabel(missing.stackProduct), + type: getTypeLabelForStackProduct(missing.stackProduct, false), stackProductName: missing.stackProductName, }, }), nextSteps: [ { text: i18n.translate('xpack.monitoring.alerts.missingData.ui.nextSteps.hotThreads', { - defaultMessage: `#start_linkCheck hot threads#end_link`, + defaultMessage: `#start_linkView all {stackProduct} {type}#end_link`, + values: { + type: getTypeLabelForStackProduct(missing.stackProduct), + stackProduct: getStackProductLabel(missing.stackProduct), + }, }), tokens: [ { startToken: '#start_link', endToken: '#end_link', - type: AlertMessageTokenType.DocLink, - partialUrl: `{elasticWebsiteUrl}/guide/en/elasticsearch/reference/{docLinkVersion}/cluster-nodes-hot-threads.html`, - } as AlertMessageDocLinkToken, + type: AlertMessageTokenType.Link, + url: `${getListingLinkForStackProduct(missing.stackProduct)}?_g=(${globalState.join( + ',' + )})`, + } as AlertMessageLinkToken, ], }, { text: i18n.translate('xpack.monitoring.alerts.missingData.ui.nextSteps.runningTasks', { - defaultMessage: `#start_linkCheck long running tasks#end_link`, + defaultMessage: `Verify monitoring settings on the {type}`, + values: { + type: getTypeLabelForStackProduct(missing.stackProduct, false), + }, }), - tokens: [ - { - startToken: '#start_link', - endToken: '#end_link', - type: AlertMessageTokenType.DocLink, - partialUrl: `{elasticWebsiteUrl}/guide/en/elasticsearch/reference/{docLinkVersion}/tasks.html`, - } as AlertMessageDocLinkToken, - ], }, ], tokens: [ @@ -294,7 +344,10 @@ export class MissingDataAlert extends BaseAlert { .filter((_state) => (_state as AlertMissingDataState).ui.isFiring) .map((_state) => { const state = _state as AlertMissingDataState; - return `${state.stackProduct}:${state.stackProductUuid}`; + return `${getStackProductLabel(state.stackProduct)} ${getTypeLabelForStackProduct( + state.stackProduct, + false + )}: ${state.stackProductName}`; }) .join(','); if (firingCount > 0) { @@ -314,7 +367,7 @@ export class MissingDataAlert extends BaseAlert { const internalShortMessage = i18n.translate( 'xpack.monitoring.alerts.missingData.firing.internalShortMessage', { - defaultMessage: `We are not detecting monitoring data for {count} stack product(s) in cluster: {clusterName}. {shortActionText}`, + defaultMessage: `We have not detect any monitoring data for {count} stack product(s) in cluster: {clusterName}. {shortActionText}`, values: { count: firingCount, clusterName: cluster.clusterName, @@ -325,7 +378,7 @@ export class MissingDataAlert extends BaseAlert { const internalFullMessage = i18n.translate( 'xpack.monitoring.alerts.missingData.firing.internalFullMessage', { - defaultMessage: `We are not detecting monitoring data for {count} stack product(s) in cluster: {clusterName}. {action}`, + defaultMessage: `We have not detect any monitoring data for {count} stack product(s) in cluster: {clusterName}. {action}`, values: { count: firingCount, clusterName: cluster.clusterName, @@ -344,44 +397,47 @@ export class MissingDataAlert extends BaseAlert { actionPlain: shortActionText, }); } else { - // const resolvedCount = instanceState.alertStates.filter( - // (alertState) => !alertState.ui.isFiring - // ).length; - // const resolvedNodes = instanceState.alertStates - // .filter((_state) => !(_state as AlertMissingDataState).ui.isFiring) - // .map((_state) => { - // const state = _state as AlertMissingDataState; - // return `${state.stackProduct}:${state.stackProductUuid}`; - // }) - // .join(','); - // if (resolvedCount > 0) { - // instance.scheduleActions('default', { - // internalShortMessage: i18n.translate( - // 'xpack.monitoring.alerts.missingData.resolved.internalShortMessage', - // { - // defaultMessage: `CPU usage alert is resolved for {count} node(s) in cluster: {clusterName}.`, - // values: { - // count: resolvedCount, - // clusterName: cluster.clusterName, - // }, - // } - // ), - // internalFullMessage: i18n.translate( - // 'xpack.monitoring.alerts.missingData.resolved.internalFullMessage', - // { - // defaultMessage: `CPU usage alert is resolved for {count} node(s) in cluster: {clusterName}.`, - // values: { - // count: resolvedCount, - // clusterName: cluster.clusterName, - // }, - // } - // ), - // state: RESOLVED, - // nodes: resolvedNodes, - // count: resolvedCount, - // clusterName: cluster.clusterName, - // }); - // } + const resolvedCount = instanceState.alertStates.filter( + (alertState) => !alertState.ui.isFiring + ).length; + const resolvedStackProducts = instanceState.alertStates + .filter((_state) => !(_state as AlertMissingDataState).ui.isFiring) + .map((_state) => { + const state = _state as AlertMissingDataState; + return `${getStackProductLabel(state.stackProduct)} ${getTypeLabelForStackProduct( + state.stackProduct, + false + )}: ${state.stackProductName}`; + }) + .join(','); + if (resolvedCount > 0) { + instance.scheduleActions('default', { + internalShortMessage: i18n.translate( + 'xpack.monitoring.alerts.missingData.resolved.internalShortMessage', + { + defaultMessage: `We are now seeing monitoring data for {count} stack product(s) in cluster: {clusterName}.`, + values: { + count: resolvedCount, + clusterName: cluster.clusterName, + }, + } + ), + internalFullMessage: i18n.translate( + 'xpack.monitoring.alerts.missingData.resolved.internalFullMessage', + { + defaultMessage: `We are now seeing monitoring data for {count} stack product(s) in cluster {clusterName}.`, + values: { + count: resolvedCount, + clusterName: cluster.clusterName, + }, + } + ), + state: RESOLVED, + stackProducts: resolvedStackProducts, + count: resolvedCount, + clusterName: cluster.clusterName, + }); + } } } @@ -398,7 +454,7 @@ export class MissingDataAlert extends BaseAlert { } const firingInstances = stackProducts.reduce((list: string[], stackProduct) => { - const missing = stackProduct.meta as AlertMissingData; + const { missing } = stackProduct.meta as { missing: AlertMissingData; limit: number }; if (stackProduct.shouldFire) { list.push(`${missing.stackProduct}:${missing.stackProductUuid}`); } @@ -413,15 +469,14 @@ export class MissingDataAlert extends BaseAlert { }; let shouldExecuteActions = false; for (const stackProduct of stackProducts) { - const missing = stackProduct.meta as AlertMissingData; + const { missing } = stackProduct.meta as { missing: AlertMissingData; limit: number }; let state: AlertMissingDataState; const indexInState = alertInstanceState.alertStates.findIndex((alertState) => { const _alertState = alertState as AlertMissingDataState; return ( _alertState.cluster.clusterUuid === cluster.clusterUuid && - _alertState.stackProduct === (stackProduct.meta as AlertMissingData).stackProduct && - _alertState.stackProductUuid === - (stackProduct.meta as AlertMissingData).stackProductUuid + _alertState.stackProduct === missing.stackProduct && + _alertState.stackProductUuid === missing.stackProductUuid ); }); if (indexInState > -1) { @@ -432,6 +487,7 @@ export class MissingDataAlert extends BaseAlert { state.stackProduct = missing.stackProduct; state.stackProductUuid = missing.stackProductUuid; + state.stackProductName = missing.stackProductName; state.gapDuration = missing.gapDuration; if (stackProduct.shouldFire) { diff --git a/x-pack/plugins/monitoring/server/alerts/types.d.ts b/x-pack/plugins/monitoring/server/alerts/types.d.ts index c84618f775ec4..610d3dbfdc016 100644 --- a/x-pack/plugins/monitoring/server/alerts/types.d.ts +++ b/x-pack/plugins/monitoring/server/alerts/types.d.ts @@ -30,6 +30,7 @@ export interface AlertCpuUsageState extends AlertState { export interface AlertMissingDataState extends AlertState { stackProduct: string; stackProductUuid: string; + stackProductName: string; gapDuration: number; } diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_data.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_data.ts index d58665de23c32..d3b73028a09c4 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_data.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_data.ts @@ -99,7 +99,7 @@ export async function fetchMissingData( size: number ): Promise { const endMs = +new Date(); - const startMs = endMs - limit; + const startMs = endMs - limit - limit * 0.25; // Go a bit farther back because we need to detect the difference between seeing the monitoring data versus just not looking far enough back const nameFields = [ 'source_node.name', @@ -256,9 +256,7 @@ export async function fetchMissingData( const stackProductUuid = uuidBucket.key; const stackProduct = getStackProductFromIndex(indexBucket.key, clusterBucket); let differenceInMs = -1; - if (uuidBucket.top.hits.hits.length === 0) { - differenceInMs = -2; - } else if (uuidBucket.top.hits.hits.length === 1) { + if (uuidBucket.top.hits.hits.length === 1) { differenceInMs = moment().diff(moment(uuidBucket.top.hits.hits[0]._source.timestamp)); } else { const first = moment(uuidBucket.top.hits.hits[0]._source.timestamp); diff --git a/x-pack/plugins/monitoring/server/lib/alerts/get_listing_link_for_stack_product.ts b/x-pack/plugins/monitoring/server/lib/alerts/get_listing_link_for_stack_product.ts new file mode 100644 index 0000000000000..1936ac1bc6183 --- /dev/null +++ b/x-pack/plugins/monitoring/server/lib/alerts/get_listing_link_for_stack_product.ts @@ -0,0 +1,28 @@ +/* + * 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 { + BEATS_SYSTEM_ID, + ELASTICSEARCH_SYSTEM_ID, + KIBANA_SYSTEM_ID, + LOGSTASH_SYSTEM_ID, + APM_SYSTEM_ID, +} from '../../../common/constants'; + +export function getListingLinkForStackProduct(stackProduct: string) { + switch (stackProduct) { + case ELASTICSEARCH_SYSTEM_ID: + return 'elasticsearch/nodes'; + case LOGSTASH_SYSTEM_ID: + return 'logstash/nodes'; + case KIBANA_SYSTEM_ID: + return 'kibana/instances'; + case BEATS_SYSTEM_ID: + return 'beats/beats'; + case APM_SYSTEM_ID: + return 'apm/instances'; + } + return ''; +} diff --git a/x-pack/plugins/monitoring/server/lib/alerts/get_stack_product_label.ts b/x-pack/plugins/monitoring/server/lib/alerts/get_stack_product_label.ts new file mode 100644 index 0000000000000..9dafd775bac14 --- /dev/null +++ b/x-pack/plugins/monitoring/server/lib/alerts/get_stack_product_label.ts @@ -0,0 +1,17 @@ +/* + * 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 { capitalize } from 'lodash'; +import { APM_SYSTEM_ID, BEATS_SYSTEM_ID } from '../../../common/constants'; + +export function getStackProductLabel(stackProduct: string) { + switch (stackProduct) { + case APM_SYSTEM_ID: + return 'APM'; + case BEATS_SYSTEM_ID: + return 'Beat'; + } + return capitalize(stackProduct); +} diff --git a/x-pack/plugins/monitoring/server/lib/alerts/get_type_label_for_stack_product.ts b/x-pack/plugins/monitoring/server/lib/alerts/get_type_label_for_stack_product.ts new file mode 100644 index 0000000000000..74801de10438f --- /dev/null +++ b/x-pack/plugins/monitoring/server/lib/alerts/get_type_label_for_stack_product.ts @@ -0,0 +1,51 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { + BEATS_SYSTEM_ID, + ELASTICSEARCH_SYSTEM_ID, + KIBANA_SYSTEM_ID, + LOGSTASH_SYSTEM_ID, + APM_SYSTEM_ID, +} from '../../../common/constants'; + +const NODES = i18n.translate('xpack.monitoring.alerts.typeLabel.nodes', { + defaultMessage: 'nodes', +}); + +const INSTANCES = i18n.translate('xpack.monitoring.alerts.typeLabel.instances', { + defaultMessage: 'instances', +}); + +const SERVERS = i18n.translate('xpack.monitoring.alerts.typeLabel.servers', { + defaultMessage: 'servers', +}); + +const NODE = i18n.translate('xpack.monitoring.alerts.typeLabel.node', { + defaultMessage: 'node', +}); + +const INSTANCE = i18n.translate('xpack.monitoring.alerts.typeLabel.instance', { + defaultMessage: 'instance', +}); + +const SERVER = i18n.translate('xpack.monitoring.alerts.typeLabel.server', { + defaultMessage: 'server', +}); + +export function getTypeLabelForStackProduct(stackProduct: string, plural: boolean = true) { + switch (stackProduct) { + case ELASTICSEARCH_SYSTEM_ID: + case LOGSTASH_SYSTEM_ID: + return plural ? NODES : NODE; + case KIBANA_SYSTEM_ID: + case BEATS_SYSTEM_ID: + return plural ? INSTANCES : INSTANCE; + case APM_SYSTEM_ID: + return plural ? SERVERS : SERVER; + } + return 'n/a'; +} From 2190c2a8d885f88df2b2ed01661e6447aeedbc5c Mon Sep 17 00:00:00 2001 From: chrisronline Date: Wed, 23 Sep 2020 14:36:07 -0400 Subject: [PATCH 03/16] Fix up alert placement --- .../public/components/beats/beat/beat.js | 18 +++++++++++++++-- .../components/beats/listing/listing.js | 10 ---------- .../components/beats/overview/overview.js | 3 ++- .../components/elasticsearch/nodes/nodes.js | 10 ---------- .../public/views/beats/beat/index.js | 1 + .../public/views/beats/overview/index.js | 20 +++++++++++++++++-- 6 files changed, 37 insertions(+), 25 deletions(-) diff --git a/x-pack/plugins/monitoring/public/components/beats/beat/beat.js b/x-pack/plugins/monitoring/public/components/beats/beat/beat.js index f489271659bfe..470cdf588ca3d 100644 --- a/x-pack/plugins/monitoring/public/components/beats/beat/beat.js +++ b/x-pack/plugins/monitoring/public/components/beats/beat/beat.js @@ -20,8 +20,9 @@ import { import { i18n } from '@kbn/i18n'; import { SummaryStatus } from '../../summary_status'; import { FormattedMessage } from '@kbn/i18n/react'; +import { AlertsCallout } from '../../../alerts/callout'; -export function Beat({ summary, metrics, ...props }) { +export function Beat({ summary, metrics, alerts, ...props }) { const metricsToShow = [ metrics.beat_event_rates, metrics.beat_fail_rates, @@ -134,13 +135,26 @@ export function Beat({ summary, metrics, ...props }) { - + + { + if (nextStep.text.includes('Beat instances')) { + return false; + } + return true; + }} + />

diff --git a/x-pack/plugins/monitoring/public/components/beats/listing/listing.js b/x-pack/plugins/monitoring/public/components/beats/listing/listing.js index 7816e89b3f564..dc65cd38aac53 100644 --- a/x-pack/plugins/monitoring/public/components/beats/listing/listing.js +++ b/x-pack/plugins/monitoring/public/components/beats/listing/listing.js @@ -26,7 +26,6 @@ import { SetupModeBadge } from '../../setup_mode/badge'; import { FormattedMessage } from '@kbn/i18n/react'; import { isSetupModeFeatureEnabled } from '../../../lib/setup_mode'; import { SetupModeFeature } from '../../../../common/enums'; -import { AlertsCallout } from '../../../alerts/callout'; import { AlertsStatus } from '../../../alerts/status'; export class Listing extends PureComponent { @@ -186,15 +185,6 @@ export class Listing extends PureComponent { {setupModeCallOut} - { - if (nextStep.text.includes('Beat beats')) { - return false; - } - return true; - }} - /> - + {renderLatestActive(latestActive, latestTypes, latestVersions)} diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js b/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js index ff5056eab7b66..41d3a579db5a2 100644 --- a/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js +++ b/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js @@ -34,7 +34,6 @@ import { ListingCallOut } from '../../setup_mode/listing_callout'; import { AlertsStatus } from '../../../alerts/status'; import { isSetupModeFeatureEnabled } from '../../../lib/setup_mode'; import { SetupModeFeature } from '../../../../common/enums'; -import { AlertsCallout } from '../../../alerts/callout'; const getNodeTooltip = (node) => { const { nodeTypeLabel, nodeTypeClass } = node; @@ -469,15 +468,6 @@ export function ElasticsearchNodes({ clusterStatus, showCgroupMetricsElasticsear {renderClusterStatus()} {setupModeCallout} - { - if (nextStep.text.includes('Elasticsearch nodes')) { - return false; - } - return true; - }} - /> { this.renderReact( this.data, (data) => { this.renderReact( - + ); } ); From b24426903db11441e46b30204b234c1929c64db1 Mon Sep 17 00:00:00 2001 From: chrisronline Date: Wed, 23 Sep 2020 14:44:39 -0400 Subject: [PATCH 04/16] Fix tests --- .../server/alerts/alerts_factory.test.ts | 2 +- .../monitoring/server/alerts/base_alert.ts | 20 +++++++++---------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/x-pack/plugins/monitoring/server/alerts/alerts_factory.test.ts b/x-pack/plugins/monitoring/server/alerts/alerts_factory.test.ts index d8fa703c7f785..60693eb42a30e 100644 --- a/x-pack/plugins/monitoring/server/alerts/alerts_factory.test.ts +++ b/x-pack/plugins/monitoring/server/alerts/alerts_factory.test.ts @@ -63,6 +63,6 @@ describe('AlertsFactory', () => { it('should get all', () => { const alerts = AlertsFactory.getAll(); - expect(alerts.length).toBe(7); + expect(alerts.length).toBe(8); }); }); diff --git a/x-pack/plugins/monitoring/server/alerts/base_alert.ts b/x-pack/plugins/monitoring/server/alerts/base_alert.ts index 4d3776b7a96e8..71bf1e843975e 100644 --- a/x-pack/plugins/monitoring/server/alerts/base_alert.ts +++ b/x-pack/plugins/monitoring/server/alerts/base_alert.ts @@ -197,18 +197,16 @@ export class BaseAlert { } const alertInstance: RawAlertInstance = states.alertInstances[instanceId]; if (alertInstance && this.filterAlertInstance(alertInstance, filters)) { - accum[instanceId] = { - ...alertInstance, - state: alertInstance.state - ? { - alertStates: (alertInstance.state as AlertInstanceState).alertStates.filter( - (alertState: AlertState) => { - return this.filterAlertState(alertState, filters); - } - ), + accum[instanceId] = alertInstance; + if (alertInstance.state) { + accum[instanceId].state = { + alertStates: (alertInstance.state as AlertInstanceState).alertStates.filter( + (alertState: AlertState) => { + return this.filterAlertState(alertState, filters); } - : alertInstance.state, - }; + ), + }; + } } return accum; }, From 8c245400d21368d16833ef56e5fd2e47beb7f673 Mon Sep 17 00:00:00 2001 From: chrisronline Date: Wed, 23 Sep 2020 15:03:37 -0400 Subject: [PATCH 05/16] Type fix --- x-pack/plugins/monitoring/public/alerts/callout.tsx | 4 ++-- .../monitoring/public/components/elasticsearch/node/node.js | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/monitoring/public/alerts/callout.tsx b/x-pack/plugins/monitoring/public/alerts/callout.tsx index bc9c5e5633ad1..1ddd41c268456 100644 --- a/x-pack/plugins/monitoring/public/alerts/callout.tsx +++ b/x-pack/plugins/monitoring/public/alerts/callout.tsx @@ -50,7 +50,7 @@ export const AlertsCallout: React.FC = (props: Props) => { if (list.length) { return ( -
+
    {list.map((state, index) => { @@ -75,7 +75,7 @@ export const AlertsCallout: React.FC = (props: Props) => {
-
+ ); } }); diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/node/node.js b/x-pack/plugins/monitoring/public/components/elasticsearch/node/node.js index 17802e64d9199..47e30b71e03d0 100644 --- a/x-pack/plugins/monitoring/public/components/elasticsearch/node/node.js +++ b/x-pack/plugins/monitoring/public/components/elasticsearch/node/node.js @@ -73,7 +73,9 @@ export const Node = ({ state.nodeId === nodeId} + alertsStateFilter={(state) => + state.nodeId === nodeId || state.stackProductUuid === nodeId + } /> From 740ecf7960786b5184e7ae950071c3f368ff85dd Mon Sep 17 00:00:00 2001 From: chrisronline Date: Wed, 23 Sep 2020 15:05:20 -0400 Subject: [PATCH 06/16] Update copy --- x-pack/plugins/monitoring/server/alerts/missing_data_alert.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/monitoring/server/alerts/missing_data_alert.ts b/x-pack/plugins/monitoring/server/alerts/missing_data_alert.ts index 2636e0653c8f9..90b73005623d1 100644 --- a/x-pack/plugins/monitoring/server/alerts/missing_data_alert.ts +++ b/x-pack/plugins/monitoring/server/alerts/missing_data_alert.ts @@ -367,7 +367,7 @@ export class MissingDataAlert extends BaseAlert { const internalShortMessage = i18n.translate( 'xpack.monitoring.alerts.missingData.firing.internalShortMessage', { - defaultMessage: `We have not detect any monitoring data for {count} stack product(s) in cluster: {clusterName}. {shortActionText}`, + defaultMessage: `We have not detected any monitoring data for {count} stack product(s) in cluster: {clusterName}. {shortActionText}`, values: { count: firingCount, clusterName: cluster.clusterName, @@ -378,7 +378,7 @@ export class MissingDataAlert extends BaseAlert { const internalFullMessage = i18n.translate( 'xpack.monitoring.alerts.missingData.firing.internalFullMessage', { - defaultMessage: `We have not detect any monitoring data for {count} stack product(s) in cluster: {clusterName}. {action}`, + defaultMessage: `We have not detected any monitoring data for {count} stack product(s) in cluster: {clusterName}. {action}`, values: { count: firingCount, clusterName: cluster.clusterName, From 8a69629f00110e427ef10938cca2162a92ce2d60 Mon Sep 17 00:00:00 2001 From: chrisronline Date: Thu, 24 Sep 2020 12:55:49 -0400 Subject: [PATCH 07/16] Add alert presence to APM in the UI --- .../components/apm/instance/instance.js | 14 +++- .../public/components/apm/instance/status.js | 3 +- .../components/apm/instances/instances.js | 32 +++++++-- .../public/components/apm/instances/status.js | 3 +- .../public/components/apm/overview/index.js | 4 +- .../public/views/apm/instance/index.js | 35 ++++++---- .../public/views/apm/instances/index.js | 66 ++++++++++--------- .../public/views/apm/overview/index.js | 27 ++++++-- 8 files changed, 123 insertions(+), 61 deletions(-) diff --git a/x-pack/plugins/monitoring/public/components/apm/instance/instance.js b/x-pack/plugins/monitoring/public/components/apm/instance/instance.js index eec24e741ac41..8934bbc41f5f6 100644 --- a/x-pack/plugins/monitoring/public/components/apm/instance/instance.js +++ b/x-pack/plugins/monitoring/public/components/apm/instance/instance.js @@ -18,8 +18,9 @@ import { } from '@elastic/eui'; import { Status } from './status'; import { FormattedMessage } from '@kbn/i18n/react'; +import { AlertsCallout } from '../../../alerts/callout'; -export function ApmServerInstance({ summary, metrics, ...props }) { +export function ApmServerInstance({ summary, metrics, alerts, ...props }) { const seriesToShow = [ metrics.apm_requests, metrics.apm_responses_valid, @@ -58,9 +59,18 @@ export function ApmServerInstance({ summary, metrics, ...props }) {

- + + { + if (nextStep.text.includes('APM servers')) { + return false; + } + return true; + }} + /> {charts} diff --git a/x-pack/plugins/monitoring/public/components/apm/instance/status.js b/x-pack/plugins/monitoring/public/components/apm/instance/status.js index 9b78db54a2aa7..02a15d214ab9b 100644 --- a/x-pack/plugins/monitoring/public/components/apm/instance/status.js +++ b/x-pack/plugins/monitoring/public/components/apm/instance/status.js @@ -14,7 +14,7 @@ import { CALCULATE_DURATION_SINCE } from '../../../../common/constants'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -export function Status({ stats }) { +export function Status({ alerts, stats }) { const { name, output, version, uptime, timeOfLastEvent } = stats; const metrics = [ @@ -78,6 +78,7 @@ export function Status({ stats }) { return ( diff --git a/x-pack/plugins/monitoring/public/components/apm/instances/instances.js b/x-pack/plugins/monitoring/public/components/apm/instances/instances.js index e05ba1878caed..4932fb9068fcc 100644 --- a/x-pack/plugins/monitoring/public/components/apm/instances/instances.js +++ b/x-pack/plugins/monitoring/public/components/apm/instances/instances.js @@ -28,8 +28,9 @@ import { SetupModeBadge } from '../../setup_mode/badge'; import { FormattedMessage } from '@kbn/i18n/react'; import { isSetupModeFeatureEnabled } from '../../../lib/setup_mode'; import { SetupModeFeature } from '../../../../common/enums'; +import { AlertsStatus } from '../../../alerts/status'; -function getColumns(setupMode) { +function getColumns(alerts, setupMode) { return [ { name: i18n.translate('xpack.monitoring.apm.instances.nameTitle', { @@ -71,6 +72,29 @@ function getColumns(setupMode) { ); }, }, + { + name: i18n.translate('xpack.monitoring.beats.instances.alertsColumnTitle', { + defaultMessage: 'Alerts', + }), + field: 'alerts', + width: '175px', + sortable: true, + render: (_field, beat) => { + return ( + state.stackProductUuid === beat.uuid} + nextStepsFilter={(nextStep) => { + if (nextStep.text.includes('APM servers')) { + return false; + } + return true; + }} + /> + ); + }, + }, { name: i18n.translate('xpack.monitoring.apm.instances.outputEnabledTitle', { defaultMessage: 'Output Enabled', @@ -127,7 +151,7 @@ function getColumns(setupMode) { ]; } -export function ApmServerInstances({ apms, setupMode }) { +export function ApmServerInstances({ apms, alerts, setupMode }) { const { pagination, sorting, onTableChange, data } = apms; let setupModeCallout = null; @@ -157,7 +181,7 @@ export function ApmServerInstances({ apms, setupMode }) { - + @@ -165,7 +189,7 @@ export function ApmServerInstances({ apms, setupMode }) { diff --git a/x-pack/plugins/monitoring/public/components/apm/overview/index.js b/x-pack/plugins/monitoring/public/components/apm/overview/index.js index 35efa6ac67ea8..b10592c2a49d2 100644 --- a/x-pack/plugins/monitoring/public/components/apm/overview/index.js +++ b/x-pack/plugins/monitoring/public/components/apm/overview/index.js @@ -19,7 +19,7 @@ import { import { Status } from '../instances/status'; import { FormattedMessage } from '@kbn/i18n/react'; -export function ApmOverview({ stats, metrics, ...props }) { +export function ApmOverview({ stats, metrics, alerts, ...props }) { const seriesToShow = [ metrics.apm_responses_valid, metrics.apm_responses_errors, @@ -54,7 +54,7 @@ export function ApmOverview({ stats, metrics, ...props }) { - + diff --git a/x-pack/plugins/monitoring/public/views/apm/instance/index.js b/x-pack/plugins/monitoring/public/views/apm/instance/index.js index 752c46b18bfb4..052bdde4ba3db 100644 --- a/x-pack/plugins/monitoring/public/views/apm/instance/index.js +++ b/x-pack/plugins/monitoring/public/views/apm/instance/index.js @@ -18,7 +18,7 @@ import { routeInitProvider } from '../../../lib/route_init'; import template from './index.html'; import { MonitoringViewBaseController } from '../../base_controller'; import { ApmServerInstance } from '../../../components/apm/instance'; -import { CODE_PATH_APM } from '../../../../common/constants'; +import { CODE_PATH_APM, ALERT_MISSING_DATA, APM_SYSTEM_ID } from '../../../../common/constants'; uiRoutes.when('/apm/instances/:uuid', { template, @@ -49,6 +49,17 @@ uiRoutes.when('/apm/instances/:uuid', { reactNodeId: 'apmInstanceReact', $scope, $injector, + alerts: { + shouldFetch: true, + options: { + alertTypeIds: [ALERT_MISSING_DATA], + filters: [ + { + stackProduct: APM_SYSTEM_ID, + }, + ], + }, + }, }); $scope.$watch( @@ -63,21 +74,17 @@ uiRoutes.when('/apm/instances/:uuid', { }) ); title($scope.cluster, `APM server - ${get(data, 'apmSummary.name')}`); - this.renderReact(data); + this.renderReact( + + ); } ); } - - renderReact(data) { - const component = ( - - ); - super.renderReact(component); - } }, }); diff --git a/x-pack/plugins/monitoring/public/views/apm/instances/index.js b/x-pack/plugins/monitoring/public/views/apm/instances/index.js index 764e13ccfea8d..b4f1ee2f185c2 100644 --- a/x-pack/plugins/monitoring/public/views/apm/instances/index.js +++ b/x-pack/plugins/monitoring/public/views/apm/instances/index.js @@ -13,7 +13,7 @@ import template from './index.html'; import { ApmServerInstances } from '../../../components/apm/instances'; import { MonitoringViewBaseEuiTableController } from '../..'; import { SetupModeRenderer } from '../../../components/renderers'; -import { APM_SYSTEM_ID, CODE_PATH_APM } from '../../../../common/constants'; +import { APM_SYSTEM_ID, CODE_PATH_APM, ALERT_MISSING_DATA } from '../../../../common/constants'; uiRoutes.when('/apm/instances', { template, @@ -47,6 +47,17 @@ uiRoutes.when('/apm/instances', { reactNodeId: 'apmInstancesReact', $scope, $injector, + alerts: { + shouldFetch: true, + options: { + alertTypeIds: [ALERT_MISSING_DATA], + filters: [ + { + stackProduct: APM_SYSTEM_ID, + }, + ], + }, + }, }); this.scope = $scope; @@ -55,37 +66,32 @@ uiRoutes.when('/apm/instances', { $scope.$watch( () => this.data, (data) => { - this.renderReact(data); + const { pagination, sorting, onTableChange } = this; + this.renderReact( + ( + + {flyoutComponent} + + {bottomBarComponent} + + )} + /> + ); } ); } - - renderReact(data) { - const { pagination, sorting, onTableChange } = this; - - const component = ( - ( - - {flyoutComponent} - - {bottomBarComponent} - - )} - /> - ); - super.renderReact(component); - } }, }); diff --git a/x-pack/plugins/monitoring/public/views/apm/overview/index.js b/x-pack/plugins/monitoring/public/views/apm/overview/index.js index 670acaeacce03..bb5ea38967b60 100644 --- a/x-pack/plugins/monitoring/public/views/apm/overview/index.js +++ b/x-pack/plugins/monitoring/public/views/apm/overview/index.js @@ -12,7 +12,7 @@ import { routeInitProvider } from '../../../lib/route_init'; import template from './index.html'; import { MonitoringViewBaseController } from '../../base_controller'; import { ApmOverview } from '../../../components/apm/overview'; -import { CODE_PATH_APM } from '../../../../common/constants'; +import { CODE_PATH_APM, ALERT_MISSING_DATA, APM_SYSTEM_ID } from '../../../../common/constants'; uiRoutes.when('/apm', { template, @@ -42,19 +42,32 @@ uiRoutes.when('/apm', { reactNodeId: 'apmOverviewReact', $scope, $injector, + alerts: { + shouldFetch: true, + options: { + alertTypeIds: [ALERT_MISSING_DATA], + filters: [ + { + stackProduct: APM_SYSTEM_ID, + }, + ], + }, + }, }); $scope.$watch( () => this.data, (data) => { - this.renderReact(data); + this.renderReact( + + ); } ); } - - renderReact(data) { - const component = ; - super.renderReact(component); - } }, }); From 35c77f5100ab8c7a8783dca937ca691ad1ceaed4 Mon Sep 17 00:00:00 2001 From: chrisronline Date: Thu, 24 Sep 2020 12:56:23 -0400 Subject: [PATCH 08/16] Fetch data a little differently --- .../server/alerts/cpu_usage_alert.ts | 4 +- .../server/alerts/missing_data_alert.ts | 4 +- .../server/lib/alerts/fetch_missing_data.ts | 67 ++++++++++--------- 3 files changed, 43 insertions(+), 32 deletions(-) diff --git a/x-pack/plugins/monitoring/server/alerts/cpu_usage_alert.ts b/x-pack/plugins/monitoring/server/alerts/cpu_usage_alert.ts index 6ed3048e94533..772afab7ce012 100644 --- a/x-pack/plugins/monitoring/server/alerts/cpu_usage_alert.ts +++ b/x-pack/plugins/monitoring/server/alerts/cpu_usage_alert.ts @@ -443,7 +443,9 @@ export class CpuUsageAlert extends BaseAlert { nodeState.nodeName = stat.nodeName; if (node.shouldFire) { - nodeState.ui.triggeredMS = new Date().valueOf(); + if (!nodeState.ui.isFiring) { + nodeState.ui.triggeredMS = new Date().valueOf(); + } nodeState.ui.isFiring = true; nodeState.ui.message = this.getUiMessage(nodeState, node); nodeState.ui.severity = node.severity; diff --git a/x-pack/plugins/monitoring/server/alerts/missing_data_alert.ts b/x-pack/plugins/monitoring/server/alerts/missing_data_alert.ts index 90b73005623d1..823019a0d4f30 100644 --- a/x-pack/plugins/monitoring/server/alerts/missing_data_alert.ts +++ b/x-pack/plugins/monitoring/server/alerts/missing_data_alert.ts @@ -491,7 +491,9 @@ export class MissingDataAlert extends BaseAlert { state.gapDuration = missing.gapDuration; if (stackProduct.shouldFire) { - state.ui.triggeredMS = new Date().valueOf(); + if (!state.ui.isFiring) { + state.ui.triggeredMS = new Date().valueOf(); + } state.ui.isFiring = true; state.ui.message = this.getUiMessage(state, stackProduct); state.ui.severity = stackProduct.severity; diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_data.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_data.ts index d3b73028a09c4..d7c5468e155db 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_data.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_data.ts @@ -40,7 +40,10 @@ interface UuidResponse { interface UuidBucketESResponse { key: string; - top: { + most_recent: { + value: number; + }; + document: { hits: { hits: TopHitESResponse[]; }; @@ -49,7 +52,24 @@ interface UuidBucketESResponse { interface TopHitESResponse { _source: { - timestamp: string; + source_node?: { + name?: string; + }; + kibana_stats?: { + kibana?: { + name?: string; + }; + }; + logstash_stats?: { + logstash?: { + host?: string; + }; + }; + beats_stats?: { + beat?: { + name?: string; + }; + }; }; } @@ -107,10 +127,15 @@ export async function fetchMissingData( 'logstash_stats.logstash.host', 'beats_stats.beat.name', ]; - const topHitsAgg = { - top: { + const subAggs = { + most_recent: { + max: { + field: 'timestamp', + }, + }, + document: { top_hits: { - size: 2, + size: 1, sort: [ { timestamp: { @@ -119,15 +144,10 @@ export async function fetchMissingData( }, ], _source: { - includes: ['timestamp', ...nameFields], + includes: nameFields, }, }, }, - top_hit: { - max: { - field: 'timestamp', - }, - }, }; const params = { @@ -172,17 +192,15 @@ export async function fetchMissingData( terms: { field: 'node_stats.node_id', size, - order: { top_hit: 'desc' }, }, - aggs: topHitsAgg, + aggs: subAggs, }, kibana_uuids: { terms: { field: 'kibana_stats.kibana.uuid', size, - order: { top_hit: 'desc' }, }, - aggs: topHitsAgg, + aggs: subAggs, }, beats: { filter: { @@ -199,9 +217,8 @@ export async function fetchMissingData( terms: { field: 'beats_stats.beat.uuid', size, - order: { top_hit: 'desc' }, }, - aggs: topHitsAgg, + aggs: subAggs, }, }, }, @@ -220,9 +237,8 @@ export async function fetchMissingData( terms: { field: 'beats_stats.beat.uuid', size, - order: { top_hit: 'desc' }, }, - aggs: topHitsAgg, + aggs: subAggs, }, }, }, @@ -230,9 +246,8 @@ export async function fetchMissingData( terms: { field: 'logstash_stats.logstash.uuid', size, - order: { top_hit: 'desc' }, }, - aggs: topHitsAgg, + aggs: subAggs, }, }, }, @@ -255,15 +270,7 @@ export async function fetchMissingData( for (const uuidBucket of uuidBuckets) { const stackProductUuid = uuidBucket.key; const stackProduct = getStackProductFromIndex(indexBucket.key, clusterBucket); - let differenceInMs = -1; - if (uuidBucket.top.hits.hits.length === 1) { - differenceInMs = moment().diff(moment(uuidBucket.top.hits.hits[0]._source.timestamp)); - } else { - const first = moment(uuidBucket.top.hits.hits[0]._source.timestamp); - const second = moment(uuidBucket.top.hits.hits[1]._source.timestamp); - differenceInMs = first.diff(second); - } - + const differenceInMs = moment().diff(moment(uuidBucket.most_recent.value)); let stackProductName = stackProductUuid; for (const nameField of nameFields) { stackProductName = get(uuidBucket, `top.hits.hits[0]._source.${nameField}`); From 0b2db894b0ac458b50b215de8f9eea759bafda12 Mon Sep 17 00:00:00 2001 From: chrisronline Date: Thu, 24 Sep 2020 12:58:36 -0400 Subject: [PATCH 09/16] We don't need moment --- .../plugins/monitoring/server/lib/alerts/fetch_missing_data.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_data.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_data.ts index d7c5468e155db..9968fc5812952 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_data.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_data.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ import { get } from 'lodash'; -import moment from 'moment'; import { AlertCluster, AlertMissingData } from '../../alerts/types'; import { KIBANA_SYSTEM_ID, @@ -270,7 +269,7 @@ export async function fetchMissingData( for (const uuidBucket of uuidBuckets) { const stackProductUuid = uuidBucket.key; const stackProduct = getStackProductFromIndex(indexBucket.key, clusterBucket); - const differenceInMs = moment().diff(moment(uuidBucket.most_recent.value)); + const differenceInMs = +new Date() - uuidBucket.most_recent.value; let stackProductName = stackProductUuid; for (const nameField of nameFields) { stackProductName = get(uuidBucket, `top.hits.hits[0]._source.${nameField}`); From 1ffd812d98470df01887143268f1a0c60a4b3808 Mon Sep 17 00:00:00 2001 From: chrisronline Date: Thu, 24 Sep 2020 15:18:50 -0400 Subject: [PATCH 10/16] Add tests --- .../server/alerts/missing_data_alert.test.ts | 457 ++++++++++++++++++ .../server/alerts/missing_data_alert.ts | 5 +- .../lib/alerts/fetch_missing_data.test.ts | 343 +++++++++++++ .../server/lib/alerts/fetch_missing_data.ts | 45 +- 4 files changed, 826 insertions(+), 24 deletions(-) create mode 100644 x-pack/plugins/monitoring/server/alerts/missing_data_alert.test.ts create mode 100644 x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_data.test.ts diff --git a/x-pack/plugins/monitoring/server/alerts/missing_data_alert.test.ts b/x-pack/plugins/monitoring/server/alerts/missing_data_alert.test.ts new file mode 100644 index 0000000000000..7d9ec13951623 --- /dev/null +++ b/x-pack/plugins/monitoring/server/alerts/missing_data_alert.test.ts @@ -0,0 +1,457 @@ +/* + * 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 { MissingDataAlert } from './missing_data_alert'; +import { ALERT_MISSING_DATA } from '../../common/constants'; +import { fetchMissingData } from '../lib/alerts/fetch_missing_data'; +import { fetchClusters } from '../lib/alerts/fetch_clusters'; + +const RealDate = Date; + +jest.mock('../lib/alerts/fetch_missing_data', () => ({ + fetchMissingData: jest.fn(), +})); +jest.mock('../lib/alerts/fetch_clusters', () => ({ + fetchClusters: jest.fn(), +})); + +describe('MissingDataAlert', () => { + it('should have defaults', () => { + const alert = new MissingDataAlert(); + expect(alert.type).toBe(ALERT_MISSING_DATA); + expect(alert.label).toBe('Missing data'); + expect(alert.defaultThrottle).toBe('1d'); + // @ts-ignore + expect(alert.defaultParams).toStrictEqual({ limit: '1d', duration: '5m' }); + // @ts-ignore + expect(alert.actionVariables).toStrictEqual([ + { + name: 'internalShortMessage', + description: 'The short internal message generated by Elastic.', + }, + { + name: 'internalFullMessage', + description: 'The full internal message generated by Elastic.', + }, + { name: 'state', description: 'The current state of the alert.' }, + { name: 'stackProducts', description: 'The stack products missing data.' }, + { name: 'count', description: 'The number of stack products missing data.' }, + { name: 'clusterName', description: 'The cluster to which the stack products belong.' }, + { name: 'action', description: 'The recommended action for this alert.' }, + { + name: 'actionPlain', + description: 'The recommended action for this alert, without any markdown.', + }, + ]); + }); + + describe('execute', () => { + function FakeDate() {} + FakeDate.prototype.valueOf = () => 1; + + const clusterUuid = 'abc123'; + const clusterName = 'testCluster'; + const stackProduct = 'elasticsearch'; + const stackProductUuid = 'esNode1'; + const stackProductName = 'esName1'; + const gapDuration = 3000001; + const missingData = [ + { + stackProduct, + stackProductUuid, + stackProductName, + clusterUuid, + gapDuration, + }, + { + stackProduct: 'kibana', + stackProductUuid: 'kibanaUuid1', + stackProductName: 'kibanaInstance1', + clusterUuid, + gapDuration: gapDuration + 10, + }, + ]; + const getUiSettingsService = () => ({ + asScopedToClient: jest.fn(), + }); + const getLogger = () => ({ + debug: jest.fn(), + }); + const monitoringCluster = null; + const config = { + ui: { + ccs: { enabled: true }, + container: { elasticsearch: { enabled: false } }, + metricbeat: { index: 'metricbeat-*' }, + }, + }; + const kibanaUrl = 'http://localhost:5601'; + + const replaceState = jest.fn(); + const scheduleActions = jest.fn(); + const getState = jest.fn(); + const executorOptions = { + services: { + callCluster: jest.fn(), + alertInstanceFactory: jest.fn().mockImplementation(() => { + return { + replaceState, + scheduleActions, + getState, + }; + }), + }, + state: {}, + }; + + beforeEach(() => { + // @ts-ignore + Date = FakeDate; + (fetchMissingData as jest.Mock).mockImplementation(() => { + return missingData; + }); + (fetchClusters as jest.Mock).mockImplementation(() => { + return [{ clusterUuid, clusterName }]; + }); + }); + + afterEach(() => { + Date = RealDate; + replaceState.mockReset(); + scheduleActions.mockReset(); + getState.mockReset(); + }); + + it('should fire actions', async () => { + const alert = new MissingDataAlert(); + alert.initializeAlertType( + getUiSettingsService as any, + monitoringCluster as any, + getLogger as any, + config as any, + kibanaUrl, + false + ); + const type = alert.getAlertType(); + await type.executor({ + ...executorOptions, + // @ts-ignore + params: alert.defaultParams, + } as any); + const count = 2; + expect(replaceState).toHaveBeenCalledWith({ + alertStates: [ + { + cluster: { clusterUuid, clusterName }, + gapDuration, + stackProduct, + stackProductName, + stackProductUuid, + ui: { + isFiring: true, + message: { + text: + 'For the past an hour, we have not detected any monitoring data from the Elasticsearch node: esName1, starting at #absolute', + nextSteps: [ + { + text: '#start_linkView all Elasticsearch nodes#end_link', + tokens: [ + { + startToken: '#start_link', + endToken: '#end_link', + type: 'link', + url: 'elasticsearch/nodes?_g=(cluster_uuid:abc123)', + }, + ], + }, + { + text: 'Verify monitoring settings on the node', + }, + ], + tokens: [ + { + startToken: '#absolute', + type: 'time', + isAbsolute: true, + isRelative: false, + timestamp: 1, + }, + ], + }, + severity: 'danger', + resolvedMS: 0, + triggeredMS: 1, + lastCheckedMS: 0, + }, + }, + { + cluster: { clusterUuid, clusterName }, + gapDuration: gapDuration + 10, + stackProduct: 'kibana', + stackProductName: 'kibanaInstance1', + stackProductUuid: 'kibanaUuid1', + ui: { + isFiring: true, + message: { + text: + 'For the past an hour, we have not detected any monitoring data from the Kibana instance: kibanaInstance1, starting at #absolute', + nextSteps: [ + { + text: '#start_linkView all Kibana instances#end_link', + tokens: [ + { + startToken: '#start_link', + endToken: '#end_link', + type: 'link', + url: 'kibana/instances?_g=(cluster_uuid:abc123)', + }, + ], + }, + { + text: 'Verify monitoring settings on the instance', + }, + ], + tokens: [ + { + startToken: '#absolute', + type: 'time', + isAbsolute: true, + isRelative: false, + timestamp: 1, + }, + ], + }, + severity: 'danger', + resolvedMS: 0, + triggeredMS: 1, + lastCheckedMS: 0, + }, + }, + ], + }); + expect(scheduleActions).toHaveBeenCalledWith('default', { + internalFullMessage: `We have not detected any monitoring data for 2 stack product(s) in cluster: testCluster. [View what monitoring data we do have for these stack products.](http://localhost:5601/app/monitoring#overview?_g=(cluster_uuid:abc123))`, + internalShortMessage: `We have not detected any monitoring data for 2 stack product(s) in cluster: testCluster. Verify these stack products are up and running, then double check the monitoring settings.`, + action: `[View what monitoring data we do have for these stack products.](http://localhost:5601/app/monitoring#overview?_g=(cluster_uuid:abc123))`, + actionPlain: + 'Verify these stack products are up and running, then double check the monitoring settings.', + clusterName, + count, + stackProducts: 'Elasticsearch node: esName1, Kibana instance: kibanaInstance1', + state: 'firing', + }); + }); + + it('should not fire actions if under threshold', async () => { + (fetchMissingData as jest.Mock).mockImplementation(() => { + return [ + { + ...missingData[0], + gapDuration: 1, + }, + ]; + }); + const alert = new MissingDataAlert(); + alert.initializeAlertType( + getUiSettingsService as any, + monitoringCluster as any, + getLogger as any, + config as any, + kibanaUrl, + false + ); + const type = alert.getAlertType(); + await type.executor({ + ...executorOptions, + // @ts-ignore + params: alert.defaultParams, + } as any); + expect(replaceState).toHaveBeenCalledWith({ + alertStates: [ + { + cluster: { + clusterUuid, + clusterName, + }, + gapDuration: 1, + stackProduct, + stackProductName, + stackProductUuid, + ui: { + isFiring: false, + lastCheckedMS: 0, + message: null, + resolvedMS: 0, + severity: 'danger', + triggeredMS: 0, + }, + }, + ], + }); + expect(scheduleActions).not.toHaveBeenCalled(); + }); + + it('should resolve with a resolved message', async () => { + (fetchMissingData as jest.Mock).mockImplementation(() => { + return [ + { + ...missingData[0], + gapDuration: 1, + }, + ]; + }); + (getState as jest.Mock).mockImplementation(() => { + return { + alertStates: [ + { + cluster: { + clusterUuid, + clusterName, + }, + ccs: null, + gapDuration: 1, + stackProduct, + stackProductName, + stackProductUuid, + ui: { + isFiring: true, + message: null, + severity: 'danger', + resolvedMS: 0, + triggeredMS: 1, + lastCheckedMS: 0, + }, + }, + ], + }; + }); + const alert = new MissingDataAlert(); + alert.initializeAlertType( + getUiSettingsService as any, + monitoringCluster as any, + getLogger as any, + config as any, + kibanaUrl, + false + ); + const type = alert.getAlertType(); + await type.executor({ + ...executorOptions, + // @ts-ignore + params: alert.defaultParams, + } as any); + const count = 1; + expect(replaceState).toHaveBeenCalledWith({ + alertStates: [ + { + cluster: { clusterUuid, clusterName }, + ccs: null, + gapDuration: 1, + stackProduct, + stackProductName, + stackProductUuid, + ui: { + isFiring: false, + message: { + text: + 'We are now seeing monitoring data for the Elasticsearch node: esName1, as of #resolved', + tokens: [ + { + startToken: '#resolved', + type: 'time', + isAbsolute: true, + isRelative: false, + timestamp: 1, + }, + ], + }, + severity: 'danger', + resolvedMS: 1, + triggeredMS: 1, + lastCheckedMS: 0, + }, + }, + ], + }); + expect(scheduleActions).toHaveBeenCalledWith('default', { + internalFullMessage: `We are now seeing monitoring data for 1 stack product(s) in cluster testCluster.`, + internalShortMessage: `We are now seeing monitoring data for 1 stack product(s) in cluster: testCluster.`, + clusterName, + count, + stackProducts: 'Elasticsearch node: esName1', + state: 'resolved', + }); + }); + + it('should handle ccs', async () => { + const ccs = 'testCluster'; + (fetchMissingData as jest.Mock).mockImplementation(() => { + return [ + { + ...missingData[0], + ccs, + }, + ]; + }); + const alert = new MissingDataAlert(); + alert.initializeAlertType( + getUiSettingsService as any, + monitoringCluster as any, + getLogger as any, + config as any, + kibanaUrl, + false + ); + const type = alert.getAlertType(); + await type.executor({ + ...executorOptions, + // @ts-ignore + params: alert.defaultParams, + } as any); + const count = 1; + expect(scheduleActions).toHaveBeenCalledWith('default', { + internalFullMessage: `We have not detected any monitoring data for 1 stack product(s) in cluster: testCluster. [View what monitoring data we do have for these stack products.](http://localhost:5601/app/monitoring#overview?_g=(cluster_uuid:abc123,ccs:testCluster))`, + internalShortMessage: `We have not detected any monitoring data for 1 stack product(s) in cluster: testCluster. Verify these stack products are up and running, then double check the monitoring settings.`, + action: `[View what monitoring data we do have for these stack products.](http://localhost:5601/app/monitoring#overview?_g=(cluster_uuid:abc123,ccs:testCluster))`, + actionPlain: + 'Verify these stack products are up and running, then double check the monitoring settings.', + clusterName, + count, + stackProducts: 'Elasticsearch node: esName1', + state: 'firing', + }); + }); + + it('should fire with different messaging for cloud', async () => { + const alert = new MissingDataAlert(); + alert.initializeAlertType( + getUiSettingsService as any, + monitoringCluster as any, + getLogger as any, + config as any, + kibanaUrl, + true + ); + const type = alert.getAlertType(); + await type.executor({ + ...executorOptions, + // @ts-ignore + params: alert.defaultParams, + } as any); + const count = 2; + expect(scheduleActions).toHaveBeenCalledWith('default', { + internalFullMessage: `We have not detected any monitoring data for 2 stack product(s) in cluster: testCluster. Verify these stack products are up and running, then double check the monitoring settings.`, + internalShortMessage: `We have not detected any monitoring data for 2 stack product(s) in cluster: testCluster. Verify these stack products are up and running, then double check the monitoring settings.`, + action: `[View what monitoring data we do have for these stack products.](http://localhost:5601/app/monitoring#overview?_g=(cluster_uuid:abc123))`, + actionPlain: + 'Verify these stack products are up and running, then double check the monitoring settings.', + clusterName, + count, + stackProducts: 'Elasticsearch node: esName1, Kibana instance: kibanaInstance1', + state: 'firing', + }); + }); + }); +}); diff --git a/x-pack/plugins/monitoring/server/alerts/missing_data_alert.ts b/x-pack/plugins/monitoring/server/alerts/missing_data_alert.ts index 823019a0d4f30..10c9a483069bf 100644 --- a/x-pack/plugins/monitoring/server/alerts/missing_data_alert.ts +++ b/x-pack/plugins/monitoring/server/alerts/missing_data_alert.ts @@ -162,7 +162,8 @@ export class MissingDataAlert extends BaseAlert { clusters, indexPattern, limit, - this.config.ui.max_bucket_size + this.config.ui.max_bucket_size, + +new Date() ); return missingData.map((missing) => { return { @@ -349,7 +350,7 @@ export class MissingDataAlert extends BaseAlert { false )}: ${state.stackProductName}`; }) - .join(','); + .join(', '); if (firingCount > 0) { const shortActionText = i18n.translate('xpack.monitoring.alerts.missingData.shortAction', { defaultMessage: diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_data.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_data.test.ts new file mode 100644 index 0000000000000..565126ea499cf --- /dev/null +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_data.test.ts @@ -0,0 +1,343 @@ +/* + * 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 { set } from '@elastic/safer-lodash-set'; +import { fetchMissingData } from './fetch_missing_data'; + +function getResponse( + index: string, + uuidKey: 'es_uuids' | 'kibana_uuids' | 'logstash_uuids' | 'beats.beats_uuids' | 'apms.apm_uuids', + clusters: Array<{ + clusterUuid: string; + products: Array<{ + uuid: string; + timestamp: number; + nameSource: any; + }>; + }> +) { + return { + key: index, + clusters: { + buckets: clusters.map((cluster) => { + const result = { + key: cluster.clusterUuid, + }; + set(result, uuidKey, { + buckets: cluster.products.map((product) => { + return { + key: product.uuid, + most_recent: { + value: product.timestamp, + }, + document: { + hits: { + hits: [ + { + _source: product.nameSource, + }, + ], + }, + }, + }; + }), + }); + return result; + }), + }, + }; +} + +describe('fetchMissingData', () => { + let callCluster = jest.fn(); + const index = '.monitoring-*'; + const limit = 100; + const size = 10; + + it('fetch as expected', async () => { + const now = 10; + const clusters = [ + { + clusterUuid: 'clusterUuid1', + clusterName: 'clusterName1', + }, + ]; + callCluster = jest.fn().mockImplementation((...args) => { + return { + aggregations: { + index: { + buckets: [ + getResponse( + '.monitoring-es-*', + 'es_uuids', + clusters.map((cluster) => ({ + clusterUuid: cluster.clusterUuid, + products: [ + { + uuid: 'nodeUuid1', + nameSource: { + source_node: { + name: 'nodeName1', + }, + }, + timestamp: 9, + }, + { + uuid: 'nodeUuid2', + nameSource: { + source_node: { + name: 'nodeName2', + }, + }, + timestamp: 2, + }, + ], + })) + ), + getResponse( + '.monitoring-kibana-*', + 'kibana_uuids', + clusters.map((cluster) => ({ + clusterUuid: cluster.clusterUuid, + products: [ + { + uuid: 'kibanaUuid1', + nameSource: { + kibana_stats: { + kibana: { + name: 'kibanaName1', + }, + }, + }, + timestamp: 4, + }, + ], + })) + ), + getResponse( + '.monitoring-logstash-*', + 'logstash_uuids', + clusters.map((cluster) => ({ + clusterUuid: cluster.clusterUuid, + products: [ + { + uuid: 'logstashUuid1', + nameSource: { + logstash_stats: { + logstash: { + host: 'logstashName1', + }, + }, + }, + timestamp: 2, + }, + ], + })) + ), + getResponse( + '.monitoring-beats-*', + 'beats.beats_uuids', + clusters.map((cluster) => ({ + clusterUuid: cluster.clusterUuid, + products: [ + { + uuid: 'beatUuid1', + nameSource: { + beats_stats: { + beat: { + name: 'beatName1', + }, + }, + }, + timestamp: 0, + }, + ], + })) + ), + getResponse( + '.monitoring-beats-*', + 'apms.apm_uuids', + clusters.map((cluster) => ({ + clusterUuid: cluster.clusterUuid, + products: [ + { + uuid: 'apmUuid1', + nameSource: { + beats_stats: { + beat: { + name: 'apmName1', + }, + }, + }, + timestamp: 1, + }, + ], + })) + ), + ], + }, + }, + }; + }); + const result = await fetchMissingData(callCluster, clusters, index, limit, size, now); + expect(result).toEqual([ + { + stackProduct: 'elasticsearch', + stackProductUuid: 'nodeUuid1', + stackProductName: 'nodeName1', + clusterUuid: 'clusterUuid1', + gapDuration: 1, + ccs: null, + }, + { + stackProduct: 'elasticsearch', + stackProductUuid: 'nodeUuid2', + stackProductName: 'nodeName2', + clusterUuid: 'clusterUuid1', + gapDuration: 8, + ccs: null, + }, + { + stackProduct: 'kibana', + stackProductUuid: 'kibanaUuid1', + stackProductName: 'kibanaName1', + clusterUuid: 'clusterUuid1', + gapDuration: 6, + ccs: null, + }, + { + stackProduct: 'logstash', + stackProductUuid: 'logstashUuid1', + stackProductName: 'logstashName1', + clusterUuid: 'clusterUuid1', + gapDuration: 8, + ccs: null, + }, + { + stackProduct: 'beats', + stackProductUuid: 'beatUuid1', + stackProductName: 'beatName1', + clusterUuid: 'clusterUuid1', + gapDuration: 10, + ccs: null, + }, + { + stackProduct: 'apm', + stackProductUuid: 'apmUuid1', + stackProductName: 'apmName1', + clusterUuid: 'clusterUuid1', + gapDuration: 9, + ccs: null, + }, + ]); + }); + + it('should handle ccs', async () => { + const now = 10; + const clusters = [ + { + clusterUuid: 'clusterUuid1', + clusterName: 'clusterName1', + }, + ]; + callCluster = jest.fn().mockImplementation((...args) => { + return { + aggregations: { + index: { + buckets: [ + getResponse( + 'Monitoring:.monitoring-es-*', + 'es_uuids', + clusters.map((cluster) => ({ + clusterUuid: cluster.clusterUuid, + products: [ + { + uuid: 'nodeUuid1', + nameSource: { + source_node: { + name: 'nodeName1', + }, + }, + timestamp: 9, + }, + ], + })) + ), + ], + }, + }, + }; + }); + const result = await fetchMissingData(callCluster, clusters, index, limit, size, now); + expect(result).toEqual([ + { + stackProduct: 'elasticsearch', + stackProductUuid: 'nodeUuid1', + stackProductName: 'nodeName1', + clusterUuid: 'clusterUuid1', + gapDuration: 1, + ccs: 'Monitoring', + }, + ]); + }); + + it('should not return duplicates', async () => { + const now = 10; + const clusters = [ + { + clusterUuid: 'clusterUuid1', + clusterName: 'clusterName1', + }, + ]; + callCluster = jest.fn().mockImplementation((...args) => { + return { + aggregations: { + index: { + buckets: [ + getResponse( + '.monitoring-es-*', + 'es_uuids', + clusters.map((cluster) => ({ + clusterUuid: cluster.clusterUuid, + products: [ + { + uuid: 'nodeUuid1', + nameSource: { + source_node: { + name: 'nodeName1', + }, + }, + timestamp: 9, + }, + { + uuid: 'nodeUuid1', + nameSource: { + source_node: { + name: 'nodeName1', + }, + }, + timestamp: 2, + }, + ], + })) + ), + ], + }, + }, + }; + }); + const result = await fetchMissingData(callCluster, clusters, index, limit, size, now); + expect(result).toEqual([ + { + stackProduct: 'elasticsearch', + stackProductUuid: 'nodeUuid1', + stackProductName: 'nodeName1', + clusterUuid: 'clusterUuid1', + gapDuration: 8, + ccs: null, + }, + ]); + }); +}); diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_data.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_data.ts index 9968fc5812952..98d8f1cf45df3 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_data.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_data.ts @@ -22,13 +22,13 @@ interface IndexBucketESResponse { interface ClusterBucketESResponse { key: string; - kibana_uuids: UuidResponse; - logstash_uuids: UuidResponse; - es_uuids: UuidResponse; - beats: { + kibana_uuids?: UuidResponse; + logstash_uuids?: UuidResponse; + es_uuids?: UuidResponse; + beats?: { beats_uuids: UuidResponse; }; - apms: { + apms?: { apm_uuids: UuidResponse; }; } @@ -52,40 +52,40 @@ interface UuidBucketESResponse { interface TopHitESResponse { _source: { source_node?: { - name?: string; + name: string; }; kibana_stats?: { - kibana?: { - name?: string; + kibana: { + name: string; }; }; logstash_stats?: { - logstash?: { - host?: string; + logstash: { + host: string; }; }; beats_stats?: { - beat?: { - name?: string; + beat: { + name: string; }; }; }; } function findNonEmptyBucket(bucket: ClusterBucketESResponse): UuidResponse { - if (bucket.beats.beats_uuids.buckets.length > 0) { + if (bucket.beats && bucket.beats.beats_uuids.buckets.length > 0) { return bucket.beats.beats_uuids; } - if (bucket.apms.apm_uuids.buckets.length > 0) { + if (bucket.apms && bucket.apms.apm_uuids.buckets.length > 0) { return bucket.apms.apm_uuids; } - if (bucket.kibana_uuids.buckets.length > 0) { + if (bucket.kibana_uuids && bucket.kibana_uuids.buckets.length > 0) { return bucket.kibana_uuids; } - if (bucket.logstash_uuids.buckets.length > 0) { + if (bucket.logstash_uuids && bucket.logstash_uuids.buckets.length > 0) { return bucket.logstash_uuids; } - if (bucket.es_uuids.buckets.length > 0) { + if (bucket.es_uuids && bucket.es_uuids.buckets.length > 0) { return bucket.es_uuids; } return { buckets: [] }; @@ -96,7 +96,7 @@ function getStackProductFromIndex(index: string, bucket: ClusterBucketESResponse return KIBANA_SYSTEM_ID; } if (index.includes('-beats-')) { - if (bucket.apms.apm_uuids.buckets.length > 0) { + if (bucket.apms && bucket.apms.apm_uuids.buckets.length > 0) { return APM_SYSTEM_ID; } return BEATS_SYSTEM_ID; @@ -115,9 +115,10 @@ export async function fetchMissingData( clusters: AlertCluster[], index: string, limit: number, - size: number + size: number, + nowInMS: number ): Promise { - const endMs = +new Date(); + const endMs = nowInMS; const startMs = endMs - limit - limit * 0.25; // Go a bit farther back because we need to detect the difference between seeing the monitoring data versus just not looking far enough back const nameFields = [ @@ -269,10 +270,10 @@ export async function fetchMissingData( for (const uuidBucket of uuidBuckets) { const stackProductUuid = uuidBucket.key; const stackProduct = getStackProductFromIndex(indexBucket.key, clusterBucket); - const differenceInMs = +new Date() - uuidBucket.most_recent.value; + const differenceInMs = nowInMS - uuidBucket.most_recent.value; let stackProductName = stackProductUuid; for (const nameField of nameFields) { - stackProductName = get(uuidBucket, `top.hits.hits[0]._source.${nameField}`); + stackProductName = get(uuidBucket, `document.hits.hits[0]._source.${nameField}`); if (stackProductName) { break; } From acc8502895d7ca3306c2e0745691349dbc4e323c Mon Sep 17 00:00:00 2001 From: chrisronline Date: Mon, 28 Sep 2020 14:45:09 -0400 Subject: [PATCH 11/16] PR feedback --- .../server/alerts/missing_data_alert.test.ts | 6 +- .../server/alerts/missing_data_alert.ts | 10 ++-- .../lib/alerts/fetch_missing_data.test.ts | 58 ------------------- .../server/lib/alerts/fetch_missing_data.ts | 11 ++-- 4 files changed, 12 insertions(+), 73 deletions(-) diff --git a/x-pack/plugins/monitoring/server/alerts/missing_data_alert.test.ts b/x-pack/plugins/monitoring/server/alerts/missing_data_alert.test.ts index 7d9ec13951623..d30d504fbc97a 100644 --- a/x-pack/plugins/monitoring/server/alerts/missing_data_alert.test.ts +++ b/x-pack/plugins/monitoring/server/alerts/missing_data_alert.test.ts @@ -21,7 +21,7 @@ describe('MissingDataAlert', () => { it('should have defaults', () => { const alert = new MissingDataAlert(); expect(alert.type).toBe(ALERT_MISSING_DATA); - expect(alert.label).toBe('Missing data'); + expect(alert.label).toBe('Missing monitoring data'); expect(alert.defaultThrottle).toBe('1d'); // @ts-ignore expect(alert.defaultParams).toStrictEqual({ limit: '1d', duration: '5m' }); @@ -36,8 +36,8 @@ describe('MissingDataAlert', () => { description: 'The full internal message generated by Elastic.', }, { name: 'state', description: 'The current state of the alert.' }, - { name: 'stackProducts', description: 'The stack products missing data.' }, - { name: 'count', description: 'The number of stack products missing data.' }, + { name: 'stackProducts', description: 'The stack products missing monitoring data.' }, + { name: 'count', description: 'The number of stack products missing monitoring data.' }, { name: 'clusterName', description: 'The cluster to which the stack products belong.' }, { name: 'action', description: 'The recommended action for this alert.' }, { diff --git a/x-pack/plugins/monitoring/server/alerts/missing_data_alert.ts b/x-pack/plugins/monitoring/server/alerts/missing_data_alert.ts index 10c9a483069bf..f7d21d93f8b6b 100644 --- a/x-pack/plugins/monitoring/server/alerts/missing_data_alert.ts +++ b/x-pack/plugins/monitoring/server/alerts/missing_data_alert.ts @@ -56,7 +56,7 @@ export class MissingDataAlert extends BaseAlert { public static paramDetails = { duration: { label: i18n.translate('xpack.monitoring.alerts.missingData.paramDetails.duration.label', { - defaultMessage: `Notify if data is missing for`, + defaultMessage: `Notify if monitoring data is missing for`, }), type: AlertParamType.Duration, } as CommonAlertParamDetail, @@ -70,7 +70,7 @@ export class MissingDataAlert extends BaseAlert { public type = ALERT_MISSING_DATA; public label = i18n.translate('xpack.monitoring.alerts.missingData.label', { - defaultMessage: 'Missing data', + defaultMessage: 'Missing monitoring data', }); protected defaultParams: MissingDataParams = { @@ -108,14 +108,14 @@ export class MissingDataAlert extends BaseAlert { description: i18n.translate( 'xpack.monitoring.alerts.missingData.actionVariables.stackProducts', { - defaultMessage: 'The stack products missing data.', + defaultMessage: 'The stack products missing monitoring data.', } ), }, { name: 'count', description: i18n.translate('xpack.monitoring.alerts.missingData.actionVariables.count', { - defaultMessage: 'The number of stack products missing data.', + defaultMessage: 'The number of stack products missing monitoring data.', }), }, { @@ -169,7 +169,7 @@ export class MissingDataAlert extends BaseAlert { return { instanceKey: `${missing.clusterUuid}:${missing.stackProduct}:${missing.stackProductUuid}`, clusterUuid: missing.clusterUuid, - shouldFire: missing.gapDuration > duration && missing.gapDuration <= limit, + shouldFire: missing.gapDuration > duration, severity: AlertSeverity.Danger, meta: { missing, limit }, ccs: missing.ccs, diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_data.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_data.test.ts index 565126ea499cf..945e089731684 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_data.test.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_data.test.ts @@ -282,62 +282,4 @@ describe('fetchMissingData', () => { }, ]); }); - - it('should not return duplicates', async () => { - const now = 10; - const clusters = [ - { - clusterUuid: 'clusterUuid1', - clusterName: 'clusterName1', - }, - ]; - callCluster = jest.fn().mockImplementation((...args) => { - return { - aggregations: { - index: { - buckets: [ - getResponse( - '.monitoring-es-*', - 'es_uuids', - clusters.map((cluster) => ({ - clusterUuid: cluster.clusterUuid, - products: [ - { - uuid: 'nodeUuid1', - nameSource: { - source_node: { - name: 'nodeName1', - }, - }, - timestamp: 9, - }, - { - uuid: 'nodeUuid1', - nameSource: { - source_node: { - name: 'nodeName1', - }, - }, - timestamp: 2, - }, - ], - })) - ), - ], - }, - }, - }; - }); - const result = await fetchMissingData(callCluster, clusters, index, limit, size, now); - expect(result).toEqual([ - { - stackProduct: 'elasticsearch', - stackProductUuid: 'nodeUuid1', - stackProductName: 'nodeName1', - clusterUuid: 'clusterUuid1', - gapDuration: 8, - ccs: null, - }, - ]); - }); }); diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_data.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_data.ts index 98d8f1cf45df3..62326e33928f5 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_data.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_data.ts @@ -119,7 +119,7 @@ export async function fetchMissingData( nowInMS: number ): Promise { const endMs = nowInMS; - const startMs = endMs - limit - limit * 0.25; // Go a bit farther back because we need to detect the difference between seeing the monitoring data versus just not looking far enough back + const startMs = endMs - limit - 3 * 60 * 1000; // Go a bit farther back because we need to detect the difference between seeing the monitoring data versus just not looking far enough back const nameFields = [ 'source_node.name', @@ -259,9 +259,7 @@ export async function fetchMissingData( const response = await callCluster('search', params); const indexBuckets = get(response, 'aggregations.index.buckets', []) as IndexBucketESResponse[]; - const uniqueList: { - [id: string]: AlertMissingData; - } = {}; + const missingData: AlertMissingData[] = []; for (const indexBucket of indexBuckets) { const clusterBuckets = indexBucket.clusters.buckets; for (const clusterBucket of clusterBuckets) { @@ -279,18 +277,17 @@ export async function fetchMissingData( } } - uniqueList[`${clusterUuid}::${stackProduct}::${stackProductUuid}`] = { + missingData.push({ stackProduct, stackProductUuid, stackProductName, clusterUuid, gapDuration: differenceInMs, ccs: indexBucket.key.includes(':') ? indexBucket.key.split(':')[0] : null, - }; + }); } } } - const missingData = Object.values(uniqueList); return missingData; } From 6514604f87e8609ed93ab0027066cc049f112485 Mon Sep 17 00:00:00 2001 From: chrisronline Date: Mon, 28 Sep 2020 16:03:56 -0400 Subject: [PATCH 12/16] Update copy --- x-pack/plugins/monitoring/server/alerts/missing_data_alert.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/monitoring/server/alerts/missing_data_alert.ts b/x-pack/plugins/monitoring/server/alerts/missing_data_alert.ts index f7d21d93f8b6b..a3b623e270c25 100644 --- a/x-pack/plugins/monitoring/server/alerts/missing_data_alert.ts +++ b/x-pack/plugins/monitoring/server/alerts/missing_data_alert.ts @@ -62,7 +62,7 @@ export class MissingDataAlert extends BaseAlert { } as CommonAlertParamDetail, limit: { label: i18n.translate('xpack.monitoring.alerts.missingData.paramDetails.limit.label', { - defaultMessage: `Look this far back in time for any data`, + defaultMessage: `Look this far back in time for monitoring data`, }), type: AlertParamType.Duration, } as CommonAlertParamDetail, From 9c70e11e6d47a0c2b73ef2fb057ed53ddc9fd576 Mon Sep 17 00:00:00 2001 From: chrisronline Date: Tue, 29 Sep 2020 14:42:43 -0400 Subject: [PATCH 13/16] Fix up bug around grabbing old data --- .../lib/alerts/fetch_missing_data.test.ts | 258 +++++++----------- .../server/lib/alerts/fetch_missing_data.ts | 197 ++++++------- 2 files changed, 194 insertions(+), 261 deletions(-) diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_data.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_data.test.ts index 945e089731684..f72fddcac2f58 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_data.test.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_data.test.ts @@ -3,50 +3,35 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { set } from '@elastic/safer-lodash-set'; import { fetchMissingData } from './fetch_missing_data'; function getResponse( index: string, - uuidKey: 'es_uuids' | 'kibana_uuids' | 'logstash_uuids' | 'beats.beats_uuids' | 'apms.apm_uuids', - clusters: Array<{ - clusterUuid: string; - products: Array<{ - uuid: string; - timestamp: number; - nameSource: any; - }>; + products: Array<{ + uuid: string; + timestamp: number; + nameSource: any; }> ) { return { - key: index, - clusters: { - buckets: clusters.map((cluster) => { - const result = { - key: cluster.clusterUuid, - }; - set(result, uuidKey, { - buckets: cluster.products.map((product) => { - return { - key: product.uuid, - most_recent: { - value: product.timestamp, - }, - document: { - hits: { - hits: [ - { - _source: product.nameSource, - }, - ], - }, + buckets: products.map((product) => { + return { + key: product.uuid, + most_recent: { + value: product.timestamp, + }, + document: { + hits: { + hits: [ + { + _index: index, + _source: product.nameSource, }, - }; - }), - }); - return result; - }), - }, + ], + }, + }, + }; + }), }; } @@ -67,116 +52,87 @@ describe('fetchMissingData', () => { callCluster = jest.fn().mockImplementation((...args) => { return { aggregations: { - index: { - buckets: [ - getResponse( - '.monitoring-es-*', - 'es_uuids', - clusters.map((cluster) => ({ - clusterUuid: cluster.clusterUuid, - products: [ - { - uuid: 'nodeUuid1', - nameSource: { - source_node: { - name: 'nodeName1', - }, - }, - timestamp: 9, + clusters: { + buckets: clusters.map((cluster) => ({ + key: cluster.clusterUuid, + es_uuids: getResponse('.monitoring-es-*', [ + { + uuid: 'nodeUuid1', + nameSource: { + source_node: { + name: 'nodeName1', }, - { - uuid: 'nodeUuid2', - nameSource: { - source_node: { - name: 'nodeName2', - }, - }, - timestamp: 2, + }, + timestamp: 9, + }, + { + uuid: 'nodeUuid2', + nameSource: { + source_node: { + name: 'nodeName2', }, - ], - })) - ), - getResponse( - '.monitoring-kibana-*', - 'kibana_uuids', - clusters.map((cluster) => ({ - clusterUuid: cluster.clusterUuid, - products: [ - { - uuid: 'kibanaUuid1', - nameSource: { - kibana_stats: { - kibana: { - name: 'kibanaName1', - }, - }, + }, + timestamp: 2, + }, + ]), + kibana_uuids: getResponse('.monitoring-kibana-*', [ + { + uuid: 'kibanaUuid1', + nameSource: { + kibana_stats: { + kibana: { + name: 'kibanaName1', }, - timestamp: 4, }, - ], - })) - ), - getResponse( - '.monitoring-logstash-*', - 'logstash_uuids', - clusters.map((cluster) => ({ - clusterUuid: cluster.clusterUuid, - products: [ - { - uuid: 'logstashUuid1', - nameSource: { - logstash_stats: { - logstash: { - host: 'logstashName1', - }, - }, + }, + timestamp: 4, + }, + ]), + logstash_uuids: getResponse('.monitoring-logstash-*', [ + { + uuid: 'logstashUuid1', + nameSource: { + logstash_stats: { + logstash: { + host: 'logstashName1', }, - timestamp: 2, }, - ], - })) - ), - getResponse( - '.monitoring-beats-*', - 'beats.beats_uuids', - clusters.map((cluster) => ({ - clusterUuid: cluster.clusterUuid, - products: [ - { - uuid: 'beatUuid1', - nameSource: { - beats_stats: { - beat: { - name: 'beatName1', - }, + }, + timestamp: 2, + }, + ]), + beats: { + beats_uuids: getResponse('.monitoring-beats-*', [ + { + uuid: 'beatUuid1', + nameSource: { + beats_stats: { + beat: { + name: 'beatName1', }, }, - timestamp: 0, }, - ], - })) - ), - getResponse( - '.monitoring-beats-*', - 'apms.apm_uuids', - clusters.map((cluster) => ({ - clusterUuid: cluster.clusterUuid, - products: [ - { - uuid: 'apmUuid1', - nameSource: { - beats_stats: { - beat: { - name: 'apmName1', - }, + timestamp: 0, + }, + ]), + }, + apms: { + apm_uuids: getResponse('.monitoring-beats-*', [ + { + uuid: 'apmUuid1', + nameSource: { + beats_stats: { + beat: { + name: 'apmName1', + type: 'apm-server', }, }, - timestamp: 1, }, - ], - })) - ), - ], + timestamp: 1, + }, + ]), + }, + })), }, }, }; @@ -245,27 +201,21 @@ describe('fetchMissingData', () => { callCluster = jest.fn().mockImplementation((...args) => { return { aggregations: { - index: { - buckets: [ - getResponse( - 'Monitoring:.monitoring-es-*', - 'es_uuids', - clusters.map((cluster) => ({ - clusterUuid: cluster.clusterUuid, - products: [ - { - uuid: 'nodeUuid1', - nameSource: { - source_node: { - name: 'nodeName1', - }, - }, - timestamp: 9, + clusters: { + buckets: clusters.map((cluster) => ({ + key: cluster.clusterUuid, + es_uuids: getResponse('Monitoring:.monitoring-es-*', [ + { + uuid: 'nodeUuid1', + nameSource: { + source_node: { + name: 'nodeName1', }, - ], - })) - ), - ], + }, + timestamp: 9, + }, + ]), + })), }, }, }; diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_data.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_data.ts index 62326e33928f5..7a739258678fe 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_data.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_data.ts @@ -13,13 +13,6 @@ import { ELASTICSEARCH_SYSTEM_ID, } from '../../../common/constants'; -interface IndexBucketESResponse { - key: string; - clusters: { - buckets: ClusterBucketESResponse[]; - }; -} - interface ClusterBucketESResponse { key: string; kibana_uuids?: UuidResponse; @@ -50,6 +43,7 @@ interface UuidBucketESResponse { } interface TopHitESResponse { + _index: string; _source: { source_node?: { name: string; @@ -67,36 +61,18 @@ interface TopHitESResponse { beats_stats?: { beat: { name: string; + type: string; }; }; }; } -function findNonEmptyBucket(bucket: ClusterBucketESResponse): UuidResponse { - if (bucket.beats && bucket.beats.beats_uuids.buckets.length > 0) { - return bucket.beats.beats_uuids; - } - if (bucket.apms && bucket.apms.apm_uuids.buckets.length > 0) { - return bucket.apms.apm_uuids; - } - if (bucket.kibana_uuids && bucket.kibana_uuids.buckets.length > 0) { - return bucket.kibana_uuids; - } - if (bucket.logstash_uuids && bucket.logstash_uuids.buckets.length > 0) { - return bucket.logstash_uuids; - } - if (bucket.es_uuids && bucket.es_uuids.buckets.length > 0) { - return bucket.es_uuids; - } - return { buckets: [] }; -} - -function getStackProductFromIndex(index: string, bucket: ClusterBucketESResponse) { +function getStackProductFromIndex(index: string, beatType: string) { if (index.includes('-kibana-')) { return KIBANA_SYSTEM_ID; } if (index.includes('-beats-')) { - if (bucket.apms && bucket.apms.apm_uuids.buckets.length > 0) { + if (beatType === 'apm-server') { return APM_SYSTEM_ID; } return BEATS_SYSTEM_ID; @@ -126,6 +102,7 @@ export async function fetchMissingData( 'kibana_stats.kibana.name', 'logstash_stats.logstash.host', 'beats_stats.beat.name', + 'beat_stats.beat.type', ]; const subAggs = { most_recent: { @@ -144,7 +121,7 @@ export async function fetchMissingData( }, ], _source: { - includes: nameFields, + includes: ['_index', ...nameFields], }, }, }, @@ -152,7 +129,7 @@ export async function fetchMissingData( const params = { index, - filterPath: ['aggregations.index.buckets'], + filterPath: ['aggregations.clusters.buckets'], body: { size: 0, query: { @@ -176,81 +153,73 @@ export async function fetchMissingData( }, }, aggs: { - index: { + clusters: { terms: { - field: '_index', + field: 'cluster_uuid', size, }, aggs: { - clusters: { + es_uuids: { terms: { - field: 'cluster_uuid', + field: 'node_stats.node_id', size, }, - aggs: { - es_uuids: { - terms: { - field: 'node_stats.node_id', - size, + aggs: subAggs, + }, + kibana_uuids: { + terms: { + field: 'kibana_stats.kibana.uuid', + size, + }, + aggs: subAggs, + }, + beats: { + filter: { + bool: { + must_not: { + term: { + 'beats_stats.beat.type': 'apm-server', + }, }, - aggs: subAggs, }, - kibana_uuids: { + }, + aggs: { + beats_uuids: { terms: { - field: 'kibana_stats.kibana.uuid', + field: 'beats_stats.beat.uuid', size, }, aggs: subAggs, }, - beats: { - filter: { - bool: { - must_not: { - term: { - 'beats_stats.beat.type': 'apm-server', - }, - }, - }, - }, - aggs: { - beats_uuids: { - terms: { - field: 'beats_stats.beat.uuid', - size, - }, - aggs: subAggs, - }, - }, - }, - apms: { - filter: { - bool: { - must: { - term: { - 'beats_stats.beat.type': 'apm-server', - }, - }, - }, - }, - aggs: { - apm_uuids: { - terms: { - field: 'beats_stats.beat.uuid', - size, - }, - aggs: subAggs, + }, + }, + apms: { + filter: { + bool: { + must: { + term: { + 'beats_stats.beat.type': 'apm-server', }, }, }, - logstash_uuids: { + }, + aggs: { + apm_uuids: { terms: { - field: 'logstash_stats.logstash.uuid', + field: 'beats_stats.beat.uuid', size, }, aggs: subAggs, }, }, }, + logstash_uuids: { + terms: { + field: 'logstash_stats.logstash.uuid', + size, + }, + aggs: subAggs, + }, }, }, }, @@ -258,36 +227,50 @@ export async function fetchMissingData( }; const response = await callCluster('search', params); - const indexBuckets = get(response, 'aggregations.index.buckets', []) as IndexBucketESResponse[]; - const missingData: AlertMissingData[] = []; - for (const indexBucket of indexBuckets) { - const clusterBuckets = indexBucket.clusters.buckets; - for (const clusterBucket of clusterBuckets) { - const clusterUuid = clusterBucket.key; - const uuidBuckets = findNonEmptyBucket(clusterBucket).buckets; - for (const uuidBucket of uuidBuckets) { - const stackProductUuid = uuidBucket.key; - const stackProduct = getStackProductFromIndex(indexBucket.key, clusterBucket); - const differenceInMs = nowInMS - uuidBucket.most_recent.value; - let stackProductName = stackProductUuid; - for (const nameField of nameFields) { - stackProductName = get(uuidBucket, `document.hits.hits[0]._source.${nameField}`); - if (stackProductName) { - break; - } - } + const clusterBuckets = get( + response, + 'aggregations.clusters.buckets', + [] + ) as ClusterBucketESResponse[]; + const uniqueList: { [id: string]: AlertMissingData } = {}; + for (const clusterBucket of clusterBuckets) { + const clusterUuid = clusterBucket.key; + + const uuidBuckets = [ + ...(clusterBucket.es_uuids?.buckets || []), + ...(clusterBucket.kibana_uuids?.buckets || []), + ...(clusterBucket.logstash_uuids?.buckets || []), + ...(clusterBucket.beats?.beats_uuids.buckets || []), + ...(clusterBucket.apms?.apm_uuids.buckets || []), + ]; - missingData.push({ - stackProduct, - stackProductUuid, - stackProductName, - clusterUuid, - gapDuration: differenceInMs, - ccs: indexBucket.key.includes(':') ? indexBucket.key.split(':')[0] : null, - }); + for (const uuidBucket of uuidBuckets) { + const stackProductUuid = uuidBucket.key; + const indexName = get(uuidBucket, `document.hits.hits[0]._index`); + const stackProduct = getStackProductFromIndex( + indexName, + get(uuidBucket, `document.hits.hits[0]._source.beats_stats.beat.type`) + ); + const differenceInMs = nowInMS - uuidBucket.most_recent.value; + let stackProductName = stackProductUuid; + for (const nameField of nameFields) { + stackProductName = get(uuidBucket, `document.hits.hits[0]._source.${nameField}`); + if (stackProductName) { + break; + } } + + uniqueList[`${clusterUuid}${stackProduct}${stackProductUuid}`] = { + stackProduct, + stackProductUuid, + stackProductName, + clusterUuid, + gapDuration: differenceInMs, + ccs: indexName.includes(':') ? indexName.split(':')[0] : null, + }; } } + const missingData = Object.values(uniqueList); return missingData; } From f4c9b67abc0531b3729e5fd25fc5135b01e67b4f Mon Sep 17 00:00:00 2001 From: chrisronline Date: Wed, 30 Sep 2020 14:43:39 -0400 Subject: [PATCH 14/16] PR feedback --- x-pack/plugins/monitoring/common/constants.ts | 4 +- .../expression.tsx | 0 .../index.ts | 2 +- .../missing_monitoring_data_alert.tsx} | 12 ++--- .../validation.tsx | 0 .../components/cluster/overview/apm_panel.js | 4 +- .../cluster/overview/beats_panel.js | 4 +- .../cluster/overview/elasticsearch_panel.js | 4 +- .../components/cluster/overview/index.js | 12 ++--- .../cluster/overview/kibana_panel.js | 4 +- .../cluster/overview/logstash_panel.js | 4 +- x-pack/plugins/monitoring/public/plugin.ts | 4 +- .../public/views/apm/instance/index.js | 8 ++- .../public/views/apm/instances/index.js | 8 ++- .../public/views/apm/overview/index.js | 8 ++- .../public/views/beats/beat/index.js | 8 ++- .../public/views/beats/listing/index.js | 8 ++- .../public/views/beats/overview/index.js | 8 ++- .../elasticsearch/node/advanced/index.js | 4 +- .../public/views/elasticsearch/node/index.js | 4 +- .../public/views/elasticsearch/nodes/index.js | 4 +- .../public/views/kibana/instance/index.js | 4 +- .../public/views/kibana/instances/index.js | 4 +- .../views/logstash/node/advanced/index.js | 4 +- .../public/views/logstash/node/index.js | 4 +- .../public/views/logstash/nodes/index.js | 4 +- .../server/alerts/alerts_factory.test.ts | 2 +- .../server/alerts/alerts_factory.ts | 6 +-- .../monitoring/server/alerts/base_alert.ts | 28 +++++++--- .../plugins/monitoring/server/alerts/index.ts | 2 +- ... => missing_monitoring_data_alert.test.ts} | 34 ++++++------- ...rt.ts => missing_monitoring_data_alert.ts} | 51 ++++++++++++++++--- .../monitoring/server/alerts/types.d.ts | 2 +- .../server/lib/alerts/fetch_clusters.ts | 19 ++++--- ... => fetch_missing_monitoring_data.test.ts} | 8 +-- ...ta.ts => fetch_missing_monitoring_data.ts} | 10 ++-- 36 files changed, 189 insertions(+), 107 deletions(-) rename x-pack/plugins/monitoring/public/alerts/{missing_data_alert => missing_monitoring_data_alert}/expression.tsx (100%) rename x-pack/plugins/monitoring/public/alerts/{missing_data_alert => missing_monitoring_data_alert}/index.ts (73%) rename x-pack/plugins/monitoring/public/alerts/{missing_data_alert/missing_data_alert.tsx => missing_monitoring_data_alert/missing_monitoring_data_alert.tsx} (66%) rename x-pack/plugins/monitoring/public/alerts/{missing_data_alert => missing_monitoring_data_alert}/validation.tsx (100%) rename x-pack/plugins/monitoring/server/alerts/{missing_data_alert.test.ts => missing_monitoring_data_alert.test.ts} (93%) rename x-pack/plugins/monitoring/server/alerts/{missing_data_alert.ts => missing_monitoring_data_alert.ts} (91%) rename x-pack/plugins/monitoring/server/lib/alerts/{fetch_missing_data.test.ts => fetch_missing_monitoring_data.test.ts} (94%) rename x-pack/plugins/monitoring/server/lib/alerts/{fetch_missing_data.ts => fetch_missing_monitoring_data.ts} (94%) diff --git a/x-pack/plugins/monitoring/common/constants.ts b/x-pack/plugins/monitoring/common/constants.ts index d662584bd73fb..860f6439f3fdf 100644 --- a/x-pack/plugins/monitoring/common/constants.ts +++ b/x-pack/plugins/monitoring/common/constants.ts @@ -236,7 +236,7 @@ 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`; -export const ALERT_MISSING_DATA = `${ALERT_PREFIX}alert_missing_data`; +export const ALERT_MISSING_MONITORING_DATA = `${ALERT_PREFIX}alert_missing_monitoring_data`; /** * A listing of all alert types @@ -250,7 +250,7 @@ export const ALERTS = [ ALERT_ELASTICSEARCH_VERSION_MISMATCH, ALERT_KIBANA_VERSION_MISMATCH, ALERT_LOGSTASH_VERSION_MISMATCH, - ALERT_MISSING_DATA, + ALERT_MISSING_MONITORING_DATA, ]; /** diff --git a/x-pack/plugins/monitoring/public/alerts/missing_data_alert/expression.tsx b/x-pack/plugins/monitoring/public/alerts/missing_monitoring_data_alert/expression.tsx similarity index 100% rename from x-pack/plugins/monitoring/public/alerts/missing_data_alert/expression.tsx rename to x-pack/plugins/monitoring/public/alerts/missing_monitoring_data_alert/expression.tsx diff --git a/x-pack/plugins/monitoring/public/alerts/missing_data_alert/index.ts b/x-pack/plugins/monitoring/public/alerts/missing_monitoring_data_alert/index.ts similarity index 73% rename from x-pack/plugins/monitoring/public/alerts/missing_data_alert/index.ts rename to x-pack/plugins/monitoring/public/alerts/missing_monitoring_data_alert/index.ts index 5a7dbda9d2a53..5169601c0e6e3 100644 --- a/x-pack/plugins/monitoring/public/alerts/missing_data_alert/index.ts +++ b/x-pack/plugins/monitoring/public/alerts/missing_monitoring_data_alert/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { createMissingDataAlertType } from './missing_data_alert'; +export { createMissingMonitoringDataAlertType } from './missing_monitoring_data_alert'; diff --git a/x-pack/plugins/monitoring/public/alerts/missing_data_alert/missing_data_alert.tsx b/x-pack/plugins/monitoring/public/alerts/missing_monitoring_data_alert/missing_monitoring_data_alert.tsx similarity index 66% rename from x-pack/plugins/monitoring/public/alerts/missing_data_alert/missing_data_alert.tsx rename to x-pack/plugins/monitoring/public/alerts/missing_monitoring_data_alert/missing_monitoring_data_alert.tsx index 84b5545db8795..bcea98592adb2 100644 --- a/x-pack/plugins/monitoring/public/alerts/missing_data_alert/missing_data_alert.tsx +++ b/x-pack/plugins/monitoring/public/alerts/missing_monitoring_data_alert/missing_monitoring_data_alert.tsx @@ -7,19 +7,19 @@ import React from 'react'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { AlertTypeModel } from '../../../../triggers_actions_ui/public/types'; import { validate } from './validation'; -import { ALERT_MISSING_DATA } from '../../../common/constants'; +import { ALERT_MISSING_MONITORING_DATA } from '../../../common/constants'; import { Expression } from './expression'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { MissingDataAlert } from '../../../server/alerts'; +import { MissingMonitoringDataAlert } from '../../../server/alerts'; -export function createMissingDataAlertType(): AlertTypeModel { - const alert = new MissingDataAlert(); +export function createMissingMonitoringDataAlertType(): AlertTypeModel { + const alert = new MissingMonitoringDataAlert(); return { - id: ALERT_MISSING_DATA, + id: ALERT_MISSING_MONITORING_DATA, name: alert.label, iconClass: 'bell', alertParamsExpression: (props: any) => ( - + ), validate, defaultActionMessage: '{{context.internalFullMessage}}', diff --git a/x-pack/plugins/monitoring/public/alerts/missing_data_alert/validation.tsx b/x-pack/plugins/monitoring/public/alerts/missing_monitoring_data_alert/validation.tsx similarity index 100% rename from x-pack/plugins/monitoring/public/alerts/missing_data_alert/validation.tsx rename to x-pack/plugins/monitoring/public/alerts/missing_monitoring_data_alert/validation.tsx diff --git a/x-pack/plugins/monitoring/public/components/cluster/overview/apm_panel.js b/x-pack/plugins/monitoring/public/components/cluster/overview/apm_panel.js index 790418a16ed83..d0d5a36c3829b 100644 --- a/x-pack/plugins/monitoring/public/components/cluster/overview/apm_panel.js +++ b/x-pack/plugins/monitoring/public/components/cluster/overview/apm_panel.js @@ -27,7 +27,7 @@ import { formatTimestampToDuration } from '../../../../common'; import { CALCULATE_DURATION_SINCE, APM_SYSTEM_ID, - ALERT_MISSING_DATA, + ALERT_MISSING_MONITORING_DATA, } from '../../../../common/constants'; import { SetupModeTooltip } from '../../setup_mode/tooltip'; import { getSafeForExternalLink } from '../../../lib/get_safe_for_external_link'; @@ -36,7 +36,7 @@ import { SetupModeFeature } from '../../../../common/enums'; import { shouldShowAlertBadge } from '../../../alerts/lib/should_show_alert_badge'; import { AlertsBadge } from '../../../alerts/badge'; -const SERVERS_PANEL_ALERTS = [ALERT_MISSING_DATA]; +const SERVERS_PANEL_ALERTS = [ALERT_MISSING_MONITORING_DATA]; export function ApmPanel(props) { const { setupMode, alerts } = props; diff --git a/x-pack/plugins/monitoring/public/components/cluster/overview/beats_panel.js b/x-pack/plugins/monitoring/public/components/cluster/overview/beats_panel.js index 38b6d7f4d1547..628f57a0ffde3 100644 --- a/x-pack/plugins/monitoring/public/components/cluster/overview/beats_panel.js +++ b/x-pack/plugins/monitoring/public/components/cluster/overview/beats_panel.js @@ -23,14 +23,14 @@ import { ClusterItemContainer, DisabledIfNoDataAndInSetupModeLink } from './help import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { SetupModeTooltip } from '../../setup_mode/tooltip'; -import { ALERT_MISSING_DATA, BEATS_SYSTEM_ID } from '../../../../common/constants'; +import { ALERT_MISSING_MONITORING_DATA, BEATS_SYSTEM_ID } from '../../../../common/constants'; import { getSafeForExternalLink } from '../../../lib/get_safe_for_external_link'; import { isSetupModeFeatureEnabled } from '../../../lib/setup_mode'; import { SetupModeFeature } from '../../../../common/enums'; import { shouldShowAlertBadge } from '../../../alerts/lib/should_show_alert_badge'; import { AlertsBadge } from '../../../alerts/badge'; -const BEATS_PANEL_ALERTS = [ALERT_MISSING_DATA]; +const BEATS_PANEL_ALERTS = [ALERT_MISSING_MONITORING_DATA]; export function BeatsPanel(props) { const { setupMode, alerts } = props; diff --git a/x-pack/plugins/monitoring/public/components/cluster/overview/elasticsearch_panel.js b/x-pack/plugins/monitoring/public/components/cluster/overview/elasticsearch_panel.js index 60185a3c20d4c..667f64458b8f9 100644 --- a/x-pack/plugins/monitoring/public/components/cluster/overview/elasticsearch_panel.js +++ b/x-pack/plugins/monitoring/public/components/cluster/overview/elasticsearch_panel.js @@ -43,7 +43,7 @@ import { ALERT_DISK_USAGE, ALERT_NODES_CHANGED, ALERT_ELASTICSEARCH_VERSION_MISMATCH, - ALERT_MISSING_DATA, + ALERT_MISSING_MONITORING_DATA, } from '../../../../common/constants'; import { AlertsBadge } from '../../../alerts/badge'; import { shouldShowAlertBadge } from '../../../alerts/lib/should_show_alert_badge'; @@ -162,7 +162,7 @@ const NODES_PANEL_ALERTS = [ ALERT_DISK_USAGE, ALERT_NODES_CHANGED, ALERT_ELASTICSEARCH_VERSION_MISMATCH, - ALERT_MISSING_DATA, + ALERT_MISSING_MONITORING_DATA, ]; export function ElasticsearchPanel(props) { diff --git a/x-pack/plugins/monitoring/public/components/cluster/overview/index.js b/x-pack/plugins/monitoring/public/components/cluster/overview/index.js index b616898223d98..aebd1cee5f0be 100644 --- a/x-pack/plugins/monitoring/public/components/cluster/overview/index.js +++ b/x-pack/plugins/monitoring/public/components/cluster/overview/index.js @@ -14,7 +14,7 @@ import { ApmPanel } from './apm_panel'; import { FormattedMessage } from '@kbn/i18n/react'; import { STANDALONE_CLUSTER_CLUSTER_UUID, - ALERT_MISSING_DATA, + ALERT_MISSING_MONITORING_DATA, ELASTICSEARCH_SYSTEM_ID, KIBANA_SYSTEM_ID, LOGSTASH_SYSTEM_ID, @@ -47,7 +47,7 @@ export function Overview(props) { setupMode={props.setupMode} showLicenseExpiration={props.showLicenseExpiration} alerts={filterAlertStates(props.alerts, (type, { state }) => { - if (type === ALERT_MISSING_DATA) { + if (type === ALERT_MISSING_MONITORING_DATA) { return state.stackProduct === ELASTICSEARCH_SYSTEM_ID; } return true; @@ -57,7 +57,7 @@ export function Overview(props) { {...props.cluster.kibana} setupMode={props.setupMode} alerts={filterAlertStates(props.alerts, (type, { state }) => { - if (type === ALERT_MISSING_DATA) { + if (type === ALERT_MISSING_MONITORING_DATA) { return state.stackProduct === KIBANA_SYSTEM_ID; } return true; @@ -70,7 +70,7 @@ export function Overview(props) { {...props.cluster.logstash} setupMode={props.setupMode} alerts={filterAlertStates(props.alerts, (type, { state }) => { - if (type === ALERT_MISSING_DATA) { + if (type === ALERT_MISSING_MONITORING_DATA) { return state.stackProduct === LOGSTASH_SYSTEM_ID; } return true; @@ -81,7 +81,7 @@ export function Overview(props) { {...props.cluster.beats} setupMode={props.setupMode} alerts={filterAlertStates(props.alerts, (type, { state }) => { - if (type === ALERT_MISSING_DATA) { + if (type === ALERT_MISSING_MONITORING_DATA) { return state.stackProduct === BEATS_SYSTEM_ID; } return true; @@ -92,7 +92,7 @@ export function Overview(props) { {...props.cluster.apm} setupMode={props.setupMode} alerts={filterAlertStates(props.alerts, (type, { state }) => { - if (type === ALERT_MISSING_DATA) { + if (type === ALERT_MISSING_MONITORING_DATA) { return state.stackProduct === APM_SYSTEM_ID; } return true; diff --git a/x-pack/plugins/monitoring/public/components/cluster/overview/kibana_panel.js b/x-pack/plugins/monitoring/public/components/cluster/overview/kibana_panel.js index 56d6977c92f6b..1f20684bd97d7 100644 --- a/x-pack/plugins/monitoring/public/components/cluster/overview/kibana_panel.js +++ b/x-pack/plugins/monitoring/public/components/cluster/overview/kibana_panel.js @@ -31,7 +31,7 @@ import { SetupModeTooltip } from '../../setup_mode/tooltip'; import { KIBANA_SYSTEM_ID, ALERT_KIBANA_VERSION_MISMATCH, - ALERT_MISSING_DATA, + ALERT_MISSING_MONITORING_DATA, } from '../../../../common/constants'; import { getSafeForExternalLink } from '../../../lib/get_safe_for_external_link'; import { AlertsBadge } from '../../../alerts/badge'; @@ -39,7 +39,7 @@ import { shouldShowAlertBadge } from '../../../alerts/lib/should_show_alert_badg import { isSetupModeFeatureEnabled } from '../../../lib/setup_mode'; import { SetupModeFeature } from '../../../../common/enums'; -const INSTANCES_PANEL_ALERTS = [ALERT_KIBANA_VERSION_MISMATCH, ALERT_MISSING_DATA]; +const INSTANCES_PANEL_ALERTS = [ALERT_KIBANA_VERSION_MISMATCH, ALERT_MISSING_MONITORING_DATA]; export function KibanaPanel(props) { const setupMode = props.setupMode; diff --git a/x-pack/plugins/monitoring/public/components/cluster/overview/logstash_panel.js b/x-pack/plugins/monitoring/public/components/cluster/overview/logstash_panel.js index e5b39266437d9..7c0e04ab5d615 100644 --- a/x-pack/plugins/monitoring/public/components/cluster/overview/logstash_panel.js +++ b/x-pack/plugins/monitoring/public/components/cluster/overview/logstash_panel.js @@ -15,7 +15,7 @@ import { LOGSTASH, LOGSTASH_SYSTEM_ID, ALERT_LOGSTASH_VERSION_MISMATCH, - ALERT_MISSING_DATA, + ALERT_MISSING_MONITORING_DATA, } from '../../../../common/constants'; import { @@ -41,7 +41,7 @@ import { shouldShowAlertBadge } from '../../../alerts/lib/should_show_alert_badg import { isSetupModeFeatureEnabled } from '../../../lib/setup_mode'; import { SetupModeFeature } from '../../../../common/enums'; -const NODES_PANEL_ALERTS = [ALERT_LOGSTASH_VERSION_MISMATCH, ALERT_MISSING_DATA]; +const NODES_PANEL_ALERTS = [ALERT_LOGSTASH_VERSION_MISMATCH, ALERT_MISSING_MONITORING_DATA]; export function LogstashPanel(props) { const { setupMode } = props; diff --git a/x-pack/plugins/monitoring/public/plugin.ts b/x-pack/plugins/monitoring/public/plugin.ts index 38e4e921cc8a7..f4f66185346e8 100644 --- a/x-pack/plugins/monitoring/public/plugin.ts +++ b/x-pack/plugins/monitoring/public/plugin.ts @@ -23,7 +23,7 @@ import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/public'; import { MonitoringStartPluginDependencies, MonitoringConfig } from './types'; import { TriggersAndActionsUIPublicPluginSetup } from '../../triggers_actions_ui/public'; import { createCpuUsageAlertType } from './alerts/cpu_usage_alert'; -import { createMissingDataAlertType } from './alerts/missing_data_alert'; +import { createMissingMonitoringDataAlertType } from './alerts/missing_monitoring_data_alert'; import { createLegacyAlertTypes } from './alerts/legacy_alert'; import { createDiskUsageAlertType } from './alerts/disk_usage_alert'; @@ -73,7 +73,7 @@ export class MonitoringPlugin } plugins.triggers_actions_ui.alertTypeRegistry.register(createCpuUsageAlertType()); - plugins.triggers_actions_ui.alertTypeRegistry.register(createMissingDataAlertType()); + plugins.triggers_actions_ui.alertTypeRegistry.register(createMissingMonitoringDataAlertType()); plugins.triggers_actions_ui.alertTypeRegistry.register(createDiskUsageAlertType()); const legacyAlertTypes = createLegacyAlertTypes(); for (const legacyAlertType of legacyAlertTypes) { diff --git a/x-pack/plugins/monitoring/public/views/apm/instance/index.js b/x-pack/plugins/monitoring/public/views/apm/instance/index.js index 07fe46b3ed7e1..396d4651e0c5e 100644 --- a/x-pack/plugins/monitoring/public/views/apm/instance/index.js +++ b/x-pack/plugins/monitoring/public/views/apm/instance/index.js @@ -18,7 +18,11 @@ import { routeInitProvider } from '../../../lib/route_init'; import template from './index.html'; import { MonitoringViewBaseController } from '../../base_controller'; import { ApmServerInstance } from '../../../components/apm/instance'; -import { CODE_PATH_APM, ALERT_MISSING_DATA, APM_SYSTEM_ID } from '../../../../common/constants'; +import { + CODE_PATH_APM, + ALERT_MISSING_MONITORING_DATA, + APM_SYSTEM_ID, +} from '../../../../common/constants'; uiRoutes.when('/apm/instances/:uuid', { template, @@ -53,7 +57,7 @@ uiRoutes.when('/apm/instances/:uuid', { alerts: { shouldFetch: true, options: { - alertTypeIds: [ALERT_MISSING_DATA], + alertTypeIds: [ALERT_MISSING_MONITORING_DATA], filters: [ { stackProduct: APM_SYSTEM_ID, diff --git a/x-pack/plugins/monitoring/public/views/apm/instances/index.js b/x-pack/plugins/monitoring/public/views/apm/instances/index.js index 1151faa8634e7..75f3ded89a595 100644 --- a/x-pack/plugins/monitoring/public/views/apm/instances/index.js +++ b/x-pack/plugins/monitoring/public/views/apm/instances/index.js @@ -13,7 +13,11 @@ import template from './index.html'; import { ApmServerInstances } from '../../../components/apm/instances'; import { MonitoringViewBaseEuiTableController } from '../..'; import { SetupModeRenderer } from '../../../components/renderers'; -import { APM_SYSTEM_ID, CODE_PATH_APM, ALERT_MISSING_DATA } from '../../../../common/constants'; +import { + APM_SYSTEM_ID, + CODE_PATH_APM, + ALERT_MISSING_MONITORING_DATA, +} from '../../../../common/constants'; uiRoutes.when('/apm/instances', { template, @@ -50,7 +54,7 @@ uiRoutes.when('/apm/instances', { alerts: { shouldFetch: true, options: { - alertTypeIds: [ALERT_MISSING_DATA], + alertTypeIds: [ALERT_MISSING_MONITORING_DATA], filters: [ { stackProduct: APM_SYSTEM_ID, diff --git a/x-pack/plugins/monitoring/public/views/apm/overview/index.js b/x-pack/plugins/monitoring/public/views/apm/overview/index.js index bb5ea38967b60..12821ec432c24 100644 --- a/x-pack/plugins/monitoring/public/views/apm/overview/index.js +++ b/x-pack/plugins/monitoring/public/views/apm/overview/index.js @@ -12,7 +12,11 @@ import { routeInitProvider } from '../../../lib/route_init'; import template from './index.html'; import { MonitoringViewBaseController } from '../../base_controller'; import { ApmOverview } from '../../../components/apm/overview'; -import { CODE_PATH_APM, ALERT_MISSING_DATA, APM_SYSTEM_ID } from '../../../../common/constants'; +import { + CODE_PATH_APM, + ALERT_MISSING_MONITORING_DATA, + APM_SYSTEM_ID, +} from '../../../../common/constants'; uiRoutes.when('/apm', { template, @@ -45,7 +49,7 @@ uiRoutes.when('/apm', { alerts: { shouldFetch: true, options: { - alertTypeIds: [ALERT_MISSING_DATA], + alertTypeIds: [ALERT_MISSING_MONITORING_DATA], filters: [ { stackProduct: APM_SYSTEM_ID, diff --git a/x-pack/plugins/monitoring/public/views/beats/beat/index.js b/x-pack/plugins/monitoring/public/views/beats/beat/index.js index 05153ca9a0325..3e9e4e4b0373d 100644 --- a/x-pack/plugins/monitoring/public/views/beats/beat/index.js +++ b/x-pack/plugins/monitoring/public/views/beats/beat/index.js @@ -11,7 +11,11 @@ import { routeInitProvider } from '../../../lib/route_init'; import { MonitoringViewBaseController } from '../../'; import { getPageData } from './get_page_data'; import template from './index.html'; -import { CODE_PATH_BEATS, ALERT_MISSING_DATA, BEATS_SYSTEM_ID } from '../../../../common/constants'; +import { + CODE_PATH_BEATS, + ALERT_MISSING_MONITORING_DATA, + BEATS_SYSTEM_ID, +} from '../../../../common/constants'; import { Beat } from '../../../components/beats/beat'; uiRoutes.when('/beats/beat/:beatUuid', { @@ -55,7 +59,7 @@ uiRoutes.when('/beats/beat/:beatUuid', { alerts: { shouldFetch: true, options: { - alertTypeIds: [ALERT_MISSING_DATA], + alertTypeIds: [ALERT_MISSING_MONITORING_DATA], filters: [ { stackProduct: BEATS_SYSTEM_ID, diff --git a/x-pack/plugins/monitoring/public/views/beats/listing/index.js b/x-pack/plugins/monitoring/public/views/beats/listing/index.js index 135bcec216e39..f8f0749d6d30e 100644 --- a/x-pack/plugins/monitoring/public/views/beats/listing/index.js +++ b/x-pack/plugins/monitoring/public/views/beats/listing/index.js @@ -14,7 +14,11 @@ import template from './index.html'; import React, { Fragment } from 'react'; import { Listing } from '../../../components/beats/listing/listing'; import { SetupModeRenderer } from '../../../components/renderers'; -import { CODE_PATH_BEATS, BEATS_SYSTEM_ID, ALERT_MISSING_DATA } from '../../../../common/constants'; +import { + CODE_PATH_BEATS, + BEATS_SYSTEM_ID, + ALERT_MISSING_MONITORING_DATA, +} from '../../../../common/constants'; uiRoutes.when('/beats/beats', { template, @@ -49,7 +53,7 @@ uiRoutes.when('/beats/beats', { alerts: { shouldFetch: true, options: { - alertTypeIds: [ALERT_MISSING_DATA], + alertTypeIds: [ALERT_MISSING_MONITORING_DATA], filters: [ { stackProduct: BEATS_SYSTEM_ID, diff --git a/x-pack/plugins/monitoring/public/views/beats/overview/index.js b/x-pack/plugins/monitoring/public/views/beats/overview/index.js index d24b21d0c4b30..ef80d7e77fe6e 100644 --- a/x-pack/plugins/monitoring/public/views/beats/overview/index.js +++ b/x-pack/plugins/monitoring/public/views/beats/overview/index.js @@ -11,7 +11,11 @@ import { routeInitProvider } from '../../../lib/route_init'; import { MonitoringViewBaseController } from '../../'; import { getPageData } from './get_page_data'; import template from './index.html'; -import { CODE_PATH_BEATS, ALERT_MISSING_DATA, BEATS_SYSTEM_ID } from '../../../../common/constants'; +import { + CODE_PATH_BEATS, + ALERT_MISSING_MONITORING_DATA, + BEATS_SYSTEM_ID, +} from '../../../../common/constants'; import { BeatsOverview } from '../../../components/beats/overview'; uiRoutes.when('/beats', { @@ -47,7 +51,7 @@ uiRoutes.when('/beats', { alerts: { shouldFetch: true, options: { - alertTypeIds: [ALERT_MISSING_DATA], + alertTypeIds: [ALERT_MISSING_MONITORING_DATA], filters: [ { stackProduct: BEATS_SYSTEM_ID, diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/node/advanced/index.js b/x-pack/plugins/monitoring/public/views/elasticsearch/node/advanced/index.js index 7e8efb7207ca9..ff7f29c58b2f6 100644 --- a/x-pack/plugins/monitoring/public/views/elasticsearch/node/advanced/index.js +++ b/x-pack/plugins/monitoring/public/views/elasticsearch/node/advanced/index.js @@ -20,7 +20,7 @@ import { MonitoringViewBaseController } from '../../../base_controller'; import { CODE_PATH_ELASTICSEARCH, ALERT_CPU_USAGE, - ALERT_MISSING_DATA, + ALERT_MISSING_MONITORING_DATA, ALERT_DISK_USAGE, } from '../../../../../common/constants'; @@ -72,7 +72,7 @@ uiRoutes.when('/elasticsearch/nodes/:node/advanced', { alerts: { shouldFetch: true, options: { - alertTypeIds: [ALERT_CPU_USAGE, ALERT_DISK_USAGE, ALERT_MISSING_DATA], + alertTypeIds: [ALERT_CPU_USAGE, ALERT_DISK_USAGE, ALERT_MISSING_MONITORING_DATA], filters: [ { nodeUuid: nodeName, diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/node/index.js b/x-pack/plugins/monitoring/public/views/elasticsearch/node/index.js index a73b5cb275813..15b9b7b4c0e4a 100644 --- a/x-pack/plugins/monitoring/public/views/elasticsearch/node/index.js +++ b/x-pack/plugins/monitoring/public/views/elasticsearch/node/index.js @@ -21,7 +21,7 @@ import { MonitoringViewBaseController } from '../../base_controller'; import { CODE_PATH_ELASTICSEARCH, ALERT_CPU_USAGE, - ALERT_MISSING_DATA, + ALERT_MISSING_MONITORING_DATA, ALERT_DISK_USAGE, } from '../../../../common/constants'; @@ -56,7 +56,7 @@ uiRoutes.when('/elasticsearch/nodes/:node', { alerts: { shouldFetch: true, options: { - alertTypeIds: [ALERT_CPU_USAGE, ALERT_DISK_USAGE, ALERT_MISSING_DATA], + alertTypeIds: [ALERT_CPU_USAGE, ALERT_DISK_USAGE, ALERT_MISSING_MONITORING_DATA], filters: [ { nodeUuid: nodeName, diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/nodes/index.js b/x-pack/plugins/monitoring/public/views/elasticsearch/nodes/index.js index 9e81b15a22b2d..ef807bf9b377d 100644 --- a/x-pack/plugins/monitoring/public/views/elasticsearch/nodes/index.js +++ b/x-pack/plugins/monitoring/public/views/elasticsearch/nodes/index.js @@ -19,7 +19,7 @@ import { ELASTICSEARCH_SYSTEM_ID, CODE_PATH_ELASTICSEARCH, ALERT_CPU_USAGE, - ALERT_MISSING_DATA, + ALERT_MISSING_MONITORING_DATA, ALERT_DISK_USAGE, } from '../../../../common/constants'; @@ -88,7 +88,7 @@ uiRoutes.when('/elasticsearch/nodes', { alerts: { shouldFetch: true, options: { - alertTypeIds: [ALERT_CPU_USAGE, ALERT_DISK_USAGE, ALERT_MISSING_DATA], + alertTypeIds: [ALERT_CPU_USAGE, ALERT_DISK_USAGE, ALERT_MISSING_MONITORING_DATA], filters: [ { stackProduct: ELASTICSEARCH_SYSTEM_ID, diff --git a/x-pack/plugins/monitoring/public/views/kibana/instance/index.js b/x-pack/plugins/monitoring/public/views/kibana/instance/index.js index 020f67dbd723d..29852501d1667 100644 --- a/x-pack/plugins/monitoring/public/views/kibana/instance/index.js +++ b/x-pack/plugins/monitoring/public/views/kibana/instance/index.js @@ -30,7 +30,7 @@ import { MonitoringViewBaseController } from '../../base_controller'; import { CODE_PATH_KIBANA, ALERT_KIBANA_VERSION_MISMATCH, - ALERT_MISSING_DATA, + ALERT_MISSING_MONITORING_DATA, KIBANA_SYSTEM_ID, } from '../../../../common/constants'; import { AlertsCallout } from '../../../alerts/callout'; @@ -81,7 +81,7 @@ uiRoutes.when('/kibana/instances/:uuid', { alerts: { shouldFetch: true, options: { - alertTypeIds: [ALERT_KIBANA_VERSION_MISMATCH, ALERT_MISSING_DATA], + alertTypeIds: [ALERT_KIBANA_VERSION_MISMATCH, ALERT_MISSING_MONITORING_DATA], filters: [ { stackProduct: KIBANA_SYSTEM_ID, diff --git a/x-pack/plugins/monitoring/public/views/kibana/instances/index.js b/x-pack/plugins/monitoring/public/views/kibana/instances/index.js index c4f88b92a2b11..fcb2ee53471a1 100644 --- a/x-pack/plugins/monitoring/public/views/kibana/instances/index.js +++ b/x-pack/plugins/monitoring/public/views/kibana/instances/index.js @@ -17,7 +17,7 @@ import { KIBANA_SYSTEM_ID, CODE_PATH_KIBANA, ALERT_KIBANA_VERSION_MISMATCH, - ALERT_MISSING_DATA, + ALERT_MISSING_MONITORING_DATA, } from '../../../../common/constants'; uiRoutes.when('/kibana/instances', { @@ -47,7 +47,7 @@ uiRoutes.when('/kibana/instances', { alerts: { shouldFetch: true, options: { - alertTypeIds: [ALERT_KIBANA_VERSION_MISMATCH, ALERT_MISSING_DATA], + alertTypeIds: [ALERT_KIBANA_VERSION_MISMATCH, ALERT_MISSING_MONITORING_DATA], filters: [ { stackProduct: KIBANA_SYSTEM_ID, diff --git a/x-pack/plugins/monitoring/public/views/logstash/node/advanced/index.js b/x-pack/plugins/monitoring/public/views/logstash/node/advanced/index.js index f389fbf307b86..591db66b2698c 100644 --- a/x-pack/plugins/monitoring/public/views/logstash/node/advanced/index.js +++ b/x-pack/plugins/monitoring/public/views/logstash/node/advanced/index.js @@ -29,7 +29,7 @@ import { MonitoringTimeseriesContainer } from '../../../../components/chart'; import { CODE_PATH_LOGSTASH, ALERT_LOGSTASH_VERSION_MISMATCH, - ALERT_MISSING_DATA, + ALERT_MISSING_MONITORING_DATA, LOGSTASH_SYSTEM_ID, } from '../../../../../common/constants'; import { AlertsCallout } from '../../../../alerts/callout'; @@ -78,7 +78,7 @@ uiRoutes.when('/logstash/node/:uuid/advanced', { alerts: { shouldFetch: true, options: { - alertTypeIds: [ALERT_LOGSTASH_VERSION_MISMATCH, ALERT_MISSING_DATA], + alertTypeIds: [ALERT_LOGSTASH_VERSION_MISMATCH, ALERT_MISSING_MONITORING_DATA], filters: [ { stackProduct: LOGSTASH_SYSTEM_ID, diff --git a/x-pack/plugins/monitoring/public/views/logstash/node/index.js b/x-pack/plugins/monitoring/public/views/logstash/node/index.js index a53267293f4a5..cccae6913052a 100644 --- a/x-pack/plugins/monitoring/public/views/logstash/node/index.js +++ b/x-pack/plugins/monitoring/public/views/logstash/node/index.js @@ -29,7 +29,7 @@ import { MonitoringViewBaseController } from '../../base_controller'; import { CODE_PATH_LOGSTASH, ALERT_LOGSTASH_VERSION_MISMATCH, - ALERT_MISSING_DATA, + ALERT_MISSING_MONITORING_DATA, LOGSTASH_SYSTEM_ID, } from '../../../../common/constants'; import { AlertsCallout } from '../../../alerts/callout'; @@ -78,7 +78,7 @@ uiRoutes.when('/logstash/node/:uuid', { alerts: { shouldFetch: true, options: { - alertTypeIds: [ALERT_LOGSTASH_VERSION_MISMATCH, ALERT_MISSING_DATA], + alertTypeIds: [ALERT_LOGSTASH_VERSION_MISMATCH, ALERT_MISSING_MONITORING_DATA], filters: [ { stackProduct: LOGSTASH_SYSTEM_ID, diff --git a/x-pack/plugins/monitoring/public/views/logstash/nodes/index.js b/x-pack/plugins/monitoring/public/views/logstash/nodes/index.js index 2dd8f1139fca3..20b2f68e2c67e 100644 --- a/x-pack/plugins/monitoring/public/views/logstash/nodes/index.js +++ b/x-pack/plugins/monitoring/public/views/logstash/nodes/index.js @@ -16,7 +16,7 @@ import { CODE_PATH_LOGSTASH, LOGSTASH_SYSTEM_ID, ALERT_LOGSTASH_VERSION_MISMATCH, - ALERT_MISSING_DATA, + ALERT_MISSING_MONITORING_DATA, } from '../../../../common/constants'; uiRoutes.when('/logstash/nodes', { @@ -46,7 +46,7 @@ uiRoutes.when('/logstash/nodes', { alerts: { shouldFetch: true, options: { - alertTypeIds: [ALERT_LOGSTASH_VERSION_MISMATCH, ALERT_MISSING_DATA], + alertTypeIds: [ALERT_LOGSTASH_VERSION_MISMATCH, ALERT_MISSING_MONITORING_DATA], filters: [ { stackProduct: LOGSTASH_SYSTEM_ID, diff --git a/x-pack/plugins/monitoring/server/alerts/alerts_factory.test.ts b/x-pack/plugins/monitoring/server/alerts/alerts_factory.test.ts index 60693eb42a30e..ddc8dcafebd21 100644 --- a/x-pack/plugins/monitoring/server/alerts/alerts_factory.test.ts +++ b/x-pack/plugins/monitoring/server/alerts/alerts_factory.test.ts @@ -63,6 +63,6 @@ describe('AlertsFactory', () => { it('should get all', () => { const alerts = AlertsFactory.getAll(); - expect(alerts.length).toBe(8); + expect(alerts.length).toBe(9); }); }); diff --git a/x-pack/plugins/monitoring/server/alerts/alerts_factory.ts b/x-pack/plugins/monitoring/server/alerts/alerts_factory.ts index 74e11fdda1a8a..05a92cea5469b 100644 --- a/x-pack/plugins/monitoring/server/alerts/alerts_factory.ts +++ b/x-pack/plugins/monitoring/server/alerts/alerts_factory.ts @@ -6,7 +6,7 @@ import { CpuUsageAlert, - MissingDataAlert, + MissingMonitoringDataAlert, DiskUsageAlert, NodesChangedAlert, ClusterHealthAlert, @@ -20,7 +20,7 @@ import { ALERT_CLUSTER_HEALTH, ALERT_LICENSE_EXPIRATION, ALERT_CPU_USAGE, - ALERT_MISSING_DATA, + ALERT_MISSING_MONITORING_DATA, ALERT_DISK_USAGE, ALERT_NODES_CHANGED, ALERT_LOGSTASH_VERSION_MISMATCH, @@ -33,7 +33,7 @@ export const BY_TYPE = { [ALERT_CLUSTER_HEALTH]: ClusterHealthAlert, [ALERT_LICENSE_EXPIRATION]: LicenseExpirationAlert, [ALERT_CPU_USAGE]: CpuUsageAlert, - [ALERT_MISSING_DATA]: MissingDataAlert, + [ALERT_MISSING_MONITORING_DATA]: MissingMonitoringDataAlert, [ALERT_DISK_USAGE]: DiskUsageAlert, [ALERT_NODES_CHANGED]: NodesChangedAlert, [ALERT_LOGSTASH_VERSION_MISMATCH]: LogstashVersionMismatchAlert, diff --git a/x-pack/plugins/monitoring/server/alerts/base_alert.ts b/x-pack/plugins/monitoring/server/alerts/base_alert.ts index fea47d2c88ded..61486626040f7 100644 --- a/x-pack/plugins/monitoring/server/alerts/base_alert.ts +++ b/x-pack/plugins/monitoring/server/alerts/base_alert.ts @@ -239,13 +239,7 @@ export class BaseAlert { return await mbSafeQuery(async () => _callCluster(endpoint, clientParams, options)); }; const availableCcs = this.config.ui.ccs.enabled ? await fetchAvailableCcs(callCluster) : []; - // Support CCS use cases by querying to find available remote clusters - // and then adding those to the index pattern we are searching against - let esIndexPattern = appendMetricbeatIndex(this.config, INDEX_PATTERN_ELASTICSEARCH); - if (availableCcs) { - esIndexPattern = getCcsIndexPattern(esIndexPattern, availableCcs); - } - const clusters = await fetchClusters(callCluster, esIndexPattern); + const clusters = await this.fetchClusters(callCluster, availableCcs, params); const uiSettings = (await this.getUiSettingsService()).asScopedToClient( services.savedObjectsClient ); @@ -254,6 +248,26 @@ export class BaseAlert { return await this.processData(data, clusters, services, logger, state); } + protected async fetchClusters( + callCluster: any, + availableCcs: string[] | undefined = undefined, + params: CommonAlertParams + ) { + let ccs; + if (!availableCcs) { + ccs = this.config.ui.ccs.enabled ? await fetchAvailableCcs(callCluster) : undefined; + } else { + ccs = availableCcs; + } + // Support CCS use cases by querying to find available remote clusters + // and then adding those to the index pattern we are searching against + let esIndexPattern = appendMetricbeatIndex(this.config, INDEX_PATTERN_ELASTICSEARCH); + if (ccs) { + esIndexPattern = getCcsIndexPattern(esIndexPattern, ccs); + } + return await fetchClusters(callCluster, esIndexPattern); + } + protected async fetchData( params: CommonAlertParams, callCluster: any, diff --git a/x-pack/plugins/monitoring/server/alerts/index.ts b/x-pack/plugins/monitoring/server/alerts/index.ts index bc23d9405b2ce..41f6daa38d1dc 100644 --- a/x-pack/plugins/monitoring/server/alerts/index.ts +++ b/x-pack/plugins/monitoring/server/alerts/index.ts @@ -6,7 +6,7 @@ export { BaseAlert } from './base_alert'; export { CpuUsageAlert } from './cpu_usage_alert'; -export { MissingDataAlert } from './missing_data_alert'; +export { MissingMonitoringDataAlert } from './missing_monitoring_data_alert'; export { DiskUsageAlert } from './disk_usage_alert'; export { ClusterHealthAlert } from './cluster_health_alert'; export { LicenseExpirationAlert } from './license_expiration_alert'; diff --git a/x-pack/plugins/monitoring/server/alerts/missing_data_alert.test.ts b/x-pack/plugins/monitoring/server/alerts/missing_monitoring_data_alert.test.ts similarity index 93% rename from x-pack/plugins/monitoring/server/alerts/missing_data_alert.test.ts rename to x-pack/plugins/monitoring/server/alerts/missing_monitoring_data_alert.test.ts index d30d504fbc97a..b0d1b93e72822 100644 --- a/x-pack/plugins/monitoring/server/alerts/missing_data_alert.test.ts +++ b/x-pack/plugins/monitoring/server/alerts/missing_monitoring_data_alert.test.ts @@ -3,24 +3,24 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { MissingDataAlert } from './missing_data_alert'; -import { ALERT_MISSING_DATA } from '../../common/constants'; -import { fetchMissingData } from '../lib/alerts/fetch_missing_data'; +import { MissingMonitoringDataAlert } from './missing_monitoring_data_alert'; +import { ALERT_MISSING_MONITORING_DATA } from '../../common/constants'; +import { fetchMissingMonitoringData } from '../lib/alerts/fetch_missing_monitoring_data'; import { fetchClusters } from '../lib/alerts/fetch_clusters'; const RealDate = Date; -jest.mock('../lib/alerts/fetch_missing_data', () => ({ - fetchMissingData: jest.fn(), +jest.mock('../lib/alerts/fetch_missing_monitoring_data', () => ({ + fetchMissingMonitoringData: jest.fn(), })); jest.mock('../lib/alerts/fetch_clusters', () => ({ fetchClusters: jest.fn(), })); -describe('MissingDataAlert', () => { +describe('MissingMonitoringDataAlert', () => { it('should have defaults', () => { - const alert = new MissingDataAlert(); - expect(alert.type).toBe(ALERT_MISSING_DATA); + const alert = new MissingMonitoringDataAlert(); + expect(alert.type).toBe(ALERT_MISSING_MONITORING_DATA); expect(alert.label).toBe('Missing monitoring data'); expect(alert.defaultThrottle).toBe('1d'); // @ts-ignore @@ -109,7 +109,7 @@ describe('MissingDataAlert', () => { beforeEach(() => { // @ts-ignore Date = FakeDate; - (fetchMissingData as jest.Mock).mockImplementation(() => { + (fetchMissingMonitoringData as jest.Mock).mockImplementation(() => { return missingData; }); (fetchClusters as jest.Mock).mockImplementation(() => { @@ -125,7 +125,7 @@ describe('MissingDataAlert', () => { }); it('should fire actions', async () => { - const alert = new MissingDataAlert(); + const alert = new MissingMonitoringDataAlert(); alert.initializeAlertType( getUiSettingsService as any, monitoringCluster as any, @@ -245,7 +245,7 @@ describe('MissingDataAlert', () => { }); it('should not fire actions if under threshold', async () => { - (fetchMissingData as jest.Mock).mockImplementation(() => { + (fetchMissingMonitoringData as jest.Mock).mockImplementation(() => { return [ { ...missingData[0], @@ -253,7 +253,7 @@ describe('MissingDataAlert', () => { }, ]; }); - const alert = new MissingDataAlert(); + const alert = new MissingMonitoringDataAlert(); alert.initializeAlertType( getUiSettingsService as any, monitoringCluster as any, @@ -294,7 +294,7 @@ describe('MissingDataAlert', () => { }); it('should resolve with a resolved message', async () => { - (fetchMissingData as jest.Mock).mockImplementation(() => { + (fetchMissingMonitoringData as jest.Mock).mockImplementation(() => { return [ { ...missingData[0], @@ -327,7 +327,7 @@ describe('MissingDataAlert', () => { ], }; }); - const alert = new MissingDataAlert(); + const alert = new MissingMonitoringDataAlert(); alert.initializeAlertType( getUiSettingsService as any, monitoringCluster as any, @@ -387,7 +387,7 @@ describe('MissingDataAlert', () => { it('should handle ccs', async () => { const ccs = 'testCluster'; - (fetchMissingData as jest.Mock).mockImplementation(() => { + (fetchMissingMonitoringData as jest.Mock).mockImplementation(() => { return [ { ...missingData[0], @@ -395,7 +395,7 @@ describe('MissingDataAlert', () => { }, ]; }); - const alert = new MissingDataAlert(); + const alert = new MissingMonitoringDataAlert(); alert.initializeAlertType( getUiSettingsService as any, monitoringCluster as any, @@ -425,7 +425,7 @@ describe('MissingDataAlert', () => { }); it('should fire with different messaging for cloud', async () => { - const alert = new MissingDataAlert(); + const alert = new MissingMonitoringDataAlert(); alert.initializeAlertType( getUiSettingsService as any, monitoringCluster as any, diff --git a/x-pack/plugins/monitoring/server/alerts/missing_data_alert.ts b/x-pack/plugins/monitoring/server/alerts/missing_monitoring_data_alert.ts similarity index 91% rename from x-pack/plugins/monitoring/server/alerts/missing_data_alert.ts rename to x-pack/plugins/monitoring/server/alerts/missing_monitoring_data_alert.ts index a3b623e270c25..d9c1f38210b96 100644 --- a/x-pack/plugins/monitoring/server/alerts/missing_data_alert.ts +++ b/x-pack/plugins/monitoring/server/alerts/missing_monitoring_data_alert.ts @@ -19,7 +19,11 @@ import { AlertMessageLinkToken, } from './types'; import { AlertInstance, AlertServices } from '../../../alerts/server'; -import { INDEX_PATTERN, ALERT_MISSING_DATA } from '../../common/constants'; +import { + INDEX_PATTERN, + ALERT_MISSING_MONITORING_DATA, + INDEX_PATTERN_ELASTICSEARCH, +} from '../../common/constants'; import { getCcsIndexPattern } from '../lib/alerts/get_ccs_index_pattern'; import { AlertMessageTokenType, AlertSeverity, AlertParamType } from '../../common/enums'; import { RawAlertInstance } from '../../../alerts/common'; @@ -32,10 +36,12 @@ import { CommonAlertNodeUuidFilter, } from '../../common/types'; import { appendMetricbeatIndex } from '../lib/alerts/append_mb_index'; -import { fetchMissingData } from '../lib/alerts/fetch_missing_data'; +import { fetchMissingMonitoringData } from '../lib/alerts/fetch_missing_monitoring_data'; import { getTypeLabelForStackProduct } from '../lib/alerts/get_type_label_for_stack_product'; import { getListingLinkForStackProduct } from '../lib/alerts/get_listing_link_for_stack_product'; import { getStackProductLabel } from '../lib/alerts/get_stack_product_label'; +import { fetchClusters } from '../lib/alerts/fetch_clusters'; +import { fetchAvailableCcs } from '../lib/alerts/fetch_available_ccs'; const RESOLVED = i18n.translate('xpack.monitoring.alerts.missingData.resolved', { defaultMessage: 'resolved', @@ -47,12 +53,15 @@ const FIRING = i18n.translate('xpack.monitoring.alerts.missingData.firing', { const DEFAULT_DURATION = '5m'; const DEFAULT_LIMIT = '1d'; +// Go a bit farther back because we need to detect the difference between seeing the monitoring data versus just not looking far enough back +const LIMIT_BUFFER = 3 * 60 * 1000; + interface MissingDataParams { duration: string; limit: string; } -export class MissingDataAlert extends BaseAlert { +export class MissingMonitoringDataAlert extends BaseAlert { public static paramDetails = { duration: { label: i18n.translate('xpack.monitoring.alerts.missingData.paramDetails.duration.label', { @@ -68,7 +77,7 @@ export class MissingDataAlert extends BaseAlert { } as CommonAlertParamDetail, }; - public type = ALERT_MISSING_DATA; + public type = ALERT_MISSING_MONITORING_DATA; public label = i18n.translate('xpack.monitoring.alerts.missingData.label', { defaultMessage: 'Missing monitoring data', }); @@ -144,6 +153,32 @@ export class MissingDataAlert extends BaseAlert { }, ]; + protected async fetchClusters( + callCluster: any, + availableCcs: string[] | undefined = undefined, + params: CommonAlertParams + ) { + const limit = parseDuration(((params as unknown) as MissingDataParams).limit); + let ccs; + if (!availableCcs) { + ccs = this.config.ui.ccs.enabled ? await fetchAvailableCcs(callCluster) : undefined; + } else { + ccs = availableCcs; + } + // Support CCS use cases by querying to find available remote clusters + // and then adding those to the index pattern we are searching against + let esIndexPattern = appendMetricbeatIndex(this.config, INDEX_PATTERN_ELASTICSEARCH); + if (ccs) { + esIndexPattern = getCcsIndexPattern(esIndexPattern, ccs); + } + return await fetchClusters(callCluster, esIndexPattern, { + timestamp: { + format: 'epoch_millis', + gte: limit - LIMIT_BUFFER, + }, + }); + } + protected async fetchData( params: CommonAlertParams, callCluster: any, @@ -157,13 +192,15 @@ export class MissingDataAlert extends BaseAlert { } const duration = parseDuration(((params as unknown) as MissingDataParams).duration); const limit = parseDuration(((params as unknown) as MissingDataParams).limit); - const missingData = await fetchMissingData( + const now = +new Date(); + const missingData = await fetchMissingMonitoringData( callCluster, clusters, indexPattern, limit, this.config.ui.max_bucket_size, - +new Date() + now, + now - limit - LIMIT_BUFFER ); return missingData.map((missing) => { return { @@ -442,7 +479,7 @@ export class MissingDataAlert extends BaseAlert { } } - protected processData( + protected async processData( data: AlertData[], clusters: AlertCluster[], services: AlertServices, diff --git a/x-pack/plugins/monitoring/server/alerts/types.d.ts b/x-pack/plugins/monitoring/server/alerts/types.d.ts index cfcd7792d749c..4b78bca9f47ca 100644 --- a/x-pack/plugins/monitoring/server/alerts/types.d.ts +++ b/x-pack/plugins/monitoring/server/alerts/types.d.ts @@ -106,7 +106,7 @@ export interface AlertMissingData { stackProductName: string; clusterUuid: string; gapDuration: number; - ccs: string | null; + ccs?: string; } export interface AlertData { diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_clusters.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_clusters.ts index 48ad31d20a395..d474338bce922 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_clusters.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_clusters.ts @@ -6,7 +6,18 @@ import { get } from 'lodash'; import { AlertCluster } from '../../alerts/types'; -export async function fetchClusters(callCluster: any, index: string): Promise { +interface RangeFilter { + [field: string]: { + format?: string; + gte: string | number; + }; +} + +export async function fetchClusters( + callCluster: any, + index: string, + rangeFilter: RangeFilter = { timestamp: { gte: 'now-2m' } } +): Promise { const params = { index, filterPath: [ @@ -25,11 +36,7 @@ export async function fetchClusters(callCluster: any, index: string): Promise { +describe('fetchMissingMonitoringData', () => { let callCluster = jest.fn(); const index = '.monitoring-*'; const limit = 100; @@ -137,7 +137,7 @@ describe('fetchMissingData', () => { }, }; }); - const result = await fetchMissingData(callCluster, clusters, index, limit, size, now); + const result = await fetchMissingMonitoringData(callCluster, clusters, index, limit, size, now); expect(result).toEqual([ { stackProduct: 'elasticsearch', @@ -220,7 +220,7 @@ describe('fetchMissingData', () => { }, }; }); - const result = await fetchMissingData(callCluster, clusters, index, limit, size, now); + const result = await fetchMissingMonitoringData(callCluster, clusters, index, limit, size, now); expect(result).toEqual([ { stackProduct: 'elasticsearch', diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_data.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_monitoring_data.ts similarity index 94% rename from x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_data.ts rename to x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_monitoring_data.ts index 7a739258678fe..ee7297b1e5a77 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_data.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_monitoring_data.ts @@ -86,16 +86,16 @@ function getStackProductFromIndex(index: string, beatType: string) { return ''; } -export async function fetchMissingData( +export async function fetchMissingMonitoringData( callCluster: any, clusters: AlertCluster[], index: string, limit: number, size: number, - nowInMS: number + nowInMs: number, + startMs: number ): Promise { - const endMs = nowInMS; - const startMs = endMs - limit - 3 * 60 * 1000; // Go a bit farther back because we need to detect the difference between seeing the monitoring data versus just not looking far enough back + const endMs = nowInMs; const nameFields = [ 'source_node.name', @@ -251,7 +251,7 @@ export async function fetchMissingData( indexName, get(uuidBucket, `document.hits.hits[0]._source.beats_stats.beat.type`) ); - const differenceInMs = nowInMS - uuidBucket.most_recent.value; + const differenceInMs = nowInMs - uuidBucket.most_recent.value; let stackProductName = stackProductUuid; for (const nameField of nameFields) { stackProductName = get(uuidBucket, `document.hits.hits[0]._source.${nameField}`); From c262c6ac2db79c80e49af17730614687d27a08a3 Mon Sep 17 00:00:00 2001 From: chrisronline Date: Thu, 1 Oct 2020 09:22:38 -0400 Subject: [PATCH 15/16] PR feedback --- .../alerts/missing_monitoring_data_alert.ts | 76 ++----------------- .../fetch_missing_monitoring_data.test.ts | 20 ++++- .../alerts/fetch_missing_monitoring_data.ts | 1 - 3 files changed, 25 insertions(+), 72 deletions(-) diff --git a/x-pack/plugins/monitoring/server/alerts/missing_monitoring_data_alert.ts b/x-pack/plugins/monitoring/server/alerts/missing_monitoring_data_alert.ts index d9c1f38210b96..6017314f332e6 100644 --- a/x-pack/plugins/monitoring/server/alerts/missing_monitoring_data_alert.ts +++ b/x-pack/plugins/monitoring/server/alerts/missing_monitoring_data_alert.ts @@ -16,7 +16,6 @@ import { AlertMissingData, AlertMessageTimeToken, AlertInstanceState, - AlertMessageLinkToken, } from './types'; import { AlertInstance, AlertServices } from '../../../alerts/server'; import { @@ -42,6 +41,7 @@ import { getListingLinkForStackProduct } from '../lib/alerts/get_listing_link_fo import { getStackProductLabel } from '../lib/alerts/get_stack_product_label'; import { fetchClusters } from '../lib/alerts/fetch_clusters'; import { fetchAvailableCcs } from '../lib/alerts/fetch_available_ccs'; +import { AlertingDefaults, createLink } from './alerts_common'; const RESOLVED = i18n.translate('xpack.monitoring.alerts.missingData.resolved', { defaultMessage: 'resolved', @@ -88,30 +88,6 @@ export class MissingMonitoringDataAlert extends BaseAlert { }; protected actionVariables = [ - { - name: 'internalShortMessage', - description: i18n.translate( - 'xpack.monitoring.alerts.missingData.actionVariables.internalShortMessage', - { - defaultMessage: 'The short internal message generated by Elastic.', - } - ), - }, - { - name: 'internalFullMessage', - description: i18n.translate( - 'xpack.monitoring.alerts.missingData.actionVariables.internalFullMessage', - { - defaultMessage: 'The full internal message generated by Elastic.', - } - ), - }, - { - name: 'state', - description: i18n.translate('xpack.monitoring.alerts.missingData.actionVariables.state', { - defaultMessage: 'The current state of the alert.', - }), - }, { name: 'stackProducts', description: i18n.translate( @@ -127,30 +103,7 @@ export class MissingMonitoringDataAlert extends BaseAlert { defaultMessage: 'The number of stack products missing monitoring data.', }), }, - { - name: 'clusterName', - description: i18n.translate( - 'xpack.monitoring.alerts.missingData.actionVariables.clusterName', - { - defaultMessage: 'The cluster to which the stack products belong.', - } - ), - }, - { - name: 'action', - description: i18n.translate('xpack.monitoring.alerts.missingData.actionVariables.action', { - defaultMessage: 'The recommended action for this alert.', - }), - }, - { - name: 'actionPlain', - description: i18n.translate( - 'xpack.monitoring.alerts.missingData.actionVariables.actionPlain', - { - defaultMessage: 'The recommended action for this alert, without any markdown.', - } - ), - }, + ...Object.values(AlertingDefaults.ALERT_TYPE.context), ]; protected async fetchClusters( @@ -197,7 +150,6 @@ export class MissingMonitoringDataAlert extends BaseAlert { callCluster, clusters, indexPattern, - limit, this.config.ui.max_bucket_size, now, now - limit - LIMIT_BUFFER @@ -271,10 +223,6 @@ export class MissingMonitoringDataAlert extends BaseAlert { protected getUiMessage(alertState: AlertState, item: AlertData): AlertMessage { const { missing, limit } = item.meta as { missing: AlertMissingData; limit: number }; - const globalState = [`cluster_uuid:${item.clusterUuid}`]; - if (item.ccs) { - globalState.push(`ccs:${item.ccs}`); - } if (!alertState.ui.isFiring) { if (missing.gapDuration > limit) { return { @@ -319,27 +267,19 @@ export class MissingMonitoringDataAlert extends BaseAlert { }, }), nextSteps: [ - { - text: i18n.translate('xpack.monitoring.alerts.missingData.ui.nextSteps.hotThreads', { + createLink( + i18n.translate('xpack.monitoring.alerts.missingData.ui.nextSteps.viewAll', { defaultMessage: `#start_linkView all {stackProduct} {type}#end_link`, values: { type: getTypeLabelForStackProduct(missing.stackProduct), stackProduct: getStackProductLabel(missing.stackProduct), }, }), - tokens: [ - { - startToken: '#start_link', - endToken: '#end_link', - type: AlertMessageTokenType.Link, - url: `${getListingLinkForStackProduct(missing.stackProduct)}?_g=(${globalState.join( - ',' - )})`, - } as AlertMessageLinkToken, - ], - }, + getListingLinkForStackProduct(missing.stackProduct), + AlertMessageTokenType.Link + ), { - text: i18n.translate('xpack.monitoring.alerts.missingData.ui.nextSteps.runningTasks', { + text: i18n.translate('xpack.monitoring.alerts.missingData.ui.nextSteps.verifySettings', { defaultMessage: `Verify monitoring settings on the {type}`, values: { type: getTypeLabelForStackProduct(missing.stackProduct, false), diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_monitoring_data.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_monitoring_data.test.ts index 750ec33d028cf..b09f5a88dba9c 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_monitoring_data.test.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_monitoring_data.test.ts @@ -38,7 +38,7 @@ function getResponse( describe('fetchMissingMonitoringData', () => { let callCluster = jest.fn(); const index = '.monitoring-*'; - const limit = 100; + const startMs = 100; const size = 10; it('fetch as expected', async () => { @@ -137,7 +137,14 @@ describe('fetchMissingMonitoringData', () => { }, }; }); - const result = await fetchMissingMonitoringData(callCluster, clusters, index, limit, size, now); + const result = await fetchMissingMonitoringData( + callCluster, + clusters, + index, + size, + now, + startMs + ); expect(result).toEqual([ { stackProduct: 'elasticsearch', @@ -220,7 +227,14 @@ describe('fetchMissingMonitoringData', () => { }, }; }); - const result = await fetchMissingMonitoringData(callCluster, clusters, index, limit, size, now); + const result = await fetchMissingMonitoringData( + callCluster, + clusters, + index, + size, + now, + startMs + ); expect(result).toEqual([ { stackProduct: 'elasticsearch', diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_monitoring_data.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_monitoring_data.ts index ee7297b1e5a77..91fc05137a8c1 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_monitoring_data.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_monitoring_data.ts @@ -90,7 +90,6 @@ export async function fetchMissingMonitoringData( callCluster: any, clusters: AlertCluster[], index: string, - limit: number, size: number, nowInMs: number, startMs: number From 08374e1a02ee791510f730fd31d4fcf81ae85370 Mon Sep 17 00:00:00 2001 From: chrisronline Date: Thu, 1 Oct 2020 09:52:01 -0400 Subject: [PATCH 16/16] Fix tests --- .../alerts/missing_monitoring_data_alert.test.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/monitoring/server/alerts/missing_monitoring_data_alert.test.ts b/x-pack/plugins/monitoring/server/alerts/missing_monitoring_data_alert.test.ts index b0d1b93e72822..4c06d9718c455 100644 --- a/x-pack/plugins/monitoring/server/alerts/missing_monitoring_data_alert.test.ts +++ b/x-pack/plugins/monitoring/server/alerts/missing_monitoring_data_alert.test.ts @@ -27,6 +27,8 @@ describe('MissingMonitoringDataAlert', () => { expect(alert.defaultParams).toStrictEqual({ limit: '1d', duration: '5m' }); // @ts-ignore expect(alert.actionVariables).toStrictEqual([ + { name: 'stackProducts', description: 'The stack products missing monitoring data.' }, + { name: 'count', description: 'The number of stack products missing monitoring data.' }, { name: 'internalShortMessage', description: 'The short internal message generated by Elastic.', @@ -36,9 +38,7 @@ describe('MissingMonitoringDataAlert', () => { description: 'The full internal message generated by Elastic.', }, { name: 'state', description: 'The current state of the alert.' }, - { name: 'stackProducts', description: 'The stack products missing monitoring data.' }, - { name: 'count', description: 'The number of stack products missing monitoring data.' }, - { name: 'clusterName', description: 'The cluster to which the stack products belong.' }, + { name: 'clusterName', description: 'The cluster to which the nodes belong.' }, { name: 'action', description: 'The recommended action for this alert.' }, { name: 'actionPlain', @@ -144,6 +144,7 @@ describe('MissingMonitoringDataAlert', () => { expect(replaceState).toHaveBeenCalledWith({ alertStates: [ { + ccs: undefined, cluster: { clusterUuid, clusterName }, gapDuration, stackProduct, @@ -162,7 +163,7 @@ describe('MissingMonitoringDataAlert', () => { startToken: '#start_link', endToken: '#end_link', type: 'link', - url: 'elasticsearch/nodes?_g=(cluster_uuid:abc123)', + url: 'elasticsearch/nodes', }, ], }, @@ -187,6 +188,7 @@ describe('MissingMonitoringDataAlert', () => { }, }, { + ccs: undefined, cluster: { clusterUuid, clusterName }, gapDuration: gapDuration + 10, stackProduct: 'kibana', @@ -205,7 +207,7 @@ describe('MissingMonitoringDataAlert', () => { startToken: '#start_link', endToken: '#end_link', type: 'link', - url: 'kibana/instances?_g=(cluster_uuid:abc123)', + url: 'kibana/instances', }, ], },