= (props: Props) => {
- const { alerts, stateFilter = () => true } = props;
+ const { alerts } = props;
+ const inSetupMode = isInSetupMode(React.useContext(SetupModeContext));
+
+ if (inSetupMode) {
+ return null;
+ }
- const callouts = TYPES.map((type) => {
- const list = [];
- for (const alertTypeId of Object.keys(alerts)) {
- const alertInstance = alerts[alertTypeId];
- for (const { firing, state } of alertInstance.states) {
- if (firing && stateFilter(state) && state.ui.severity === type.severity) {
- list.push(state);
- }
- }
+ const list = [];
+ for (const alertTypeId of Object.keys(alerts)) {
+ const alertInstance = alerts[alertTypeId];
+ for (const state of alertInstance.states) {
+ list.push({
+ alert: alertInstance,
+ state,
+ });
}
+ }
- 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) => (
- - {replaceTokens(step)}
- )
- )}
-
- ) : null;
+ if (list.length === 0) {
+ return null;
+ }
- return (
- -
- {replaceTokens(state.ui.message)}
- {nextStepsUi}
-
- );
- })}
-
-
-
-
- );
- }
+ const accordions = list.map((status, index) => {
+ const buttonContent = (
+
+
+
+
+
+
+
+
+ {replaceTokens(status.state.state.ui.message)}
+
+
+
+
+ );
+
+ const accordion = (
+
+
+ {(status.state.state.ui.message.nextSteps || []).map((step: AlertMessage) => {
+ return {}} label={replaceTokens(step)} />;
+ })}
+ }
+ />
+
+
+ );
+
+ const spacer = index !== list.length - 1 ? : null;
+ return (
+
+ {accordion}
+ {spacer}
+
+ );
});
- return {callouts};
+
+ return (
+
+ {accordions}
+
+
+ );
};
diff --git a/x-pack/plugins/monitoring/public/alerts/configuration.tsx b/x-pack/plugins/monitoring/public/alerts/configuration.tsx
new file mode 100644
index 0000000000000..c570e2c840f01
--- /dev/null
+++ b/x-pack/plugins/monitoring/public/alerts/configuration.tsx
@@ -0,0 +1,166 @@
+/*
+ * 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, useMemo } from 'react';
+import { i18n } from '@kbn/i18n';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiSwitch } from '@elastic/eui';
+import { CommonAlert } from '../../common/types/alerts';
+import { Legacy } from '../legacy_shims';
+import { hideBottomBar, showBottomBar } from '../lib/setup_mode';
+import { BASE_ALERT_API_PATH } from '../../../alerts/common';
+
+interface Props {
+ alert: CommonAlert;
+ compressed?: boolean;
+}
+export const AlertConfiguration: React.FC = (props: Props) => {
+ const { alert, compressed } = props;
+ const [showFlyout, setShowFlyout] = React.useState(false);
+ const [isEnabled, setIsEnabled] = React.useState(alert.enabled);
+ const [isMuted, setIsMuted] = React.useState(alert.muteAll);
+ const [isSaving, setIsSaving] = React.useState(false);
+
+ async function disableAlert() {
+ setIsSaving(true);
+ try {
+ await Legacy.shims.http.post(`${BASE_ALERT_API_PATH}/alert/${alert.id}/_disable`);
+ } catch (err) {
+ Legacy.shims.toastNotifications.addDanger({
+ title: i18n.translate('xpack.monitoring.alerts.panel.disableAlert.errorTitle', {
+ defaultMessage: `Unable to disable alert`,
+ }),
+ text: err.message,
+ });
+ }
+ setIsSaving(false);
+ }
+ async function enableAlert() {
+ setIsSaving(true);
+ try {
+ await Legacy.shims.http.post(`${BASE_ALERT_API_PATH}/alert/${alert.id}/_enable`);
+ } catch (err) {
+ Legacy.shims.toastNotifications.addDanger({
+ title: i18n.translate('xpack.monitoring.alerts.panel.enableAlert.errorTitle', {
+ defaultMessage: `Unable to enable alert`,
+ }),
+ text: err.message,
+ });
+ }
+ setIsSaving(false);
+ }
+ async function muteAlert() {
+ setIsSaving(true);
+ try {
+ await Legacy.shims.http.post(`${BASE_ALERT_API_PATH}/alert/${alert.id}/_mute_all`);
+ } catch (err) {
+ Legacy.shims.toastNotifications.addDanger({
+ title: i18n.translate('xpack.monitoring.alerts.panel.muteAlert.errorTitle', {
+ defaultMessage: `Unable to mute alert`,
+ }),
+ text: err.message,
+ });
+ }
+ setIsSaving(false);
+ }
+ async function unmuteAlert() {
+ setIsSaving(true);
+ try {
+ await Legacy.shims.http.post(`${BASE_ALERT_API_PATH}/alert/${alert.id}/_unmute_all`);
+ } catch (err) {
+ Legacy.shims.toastNotifications.addDanger({
+ title: i18n.translate('xpack.monitoring.alerts.panel.ummuteAlert.errorTitle', {
+ defaultMessage: `Unable to unmute alert`,
+ }),
+ text: err.message,
+ });
+ }
+ setIsSaving(false);
+ }
+
+ const flyoutUi = useMemo(
+ () =>
+ showFlyout &&
+ Legacy.shims.triggersActionsUi.getEditAlertFlyout({
+ initialAlert: alert,
+ onClose: () => {
+ setShowFlyout(false);
+ showBottomBar();
+ },
+ }),
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ [showFlyout]
+ );
+
+ return (
+
+
+
+ {
+ setShowFlyout(true);
+ hideBottomBar();
+ }}
+ >
+ {i18n.translate('xpack.monitoring.alerts.panel.editAlert', {
+ defaultMessage: `Edit alert`,
+ })}
+
+
+
+ {
+ if (isEnabled) {
+ setIsEnabled(false);
+ await disableAlert();
+ } else {
+ setIsEnabled(true);
+ await enableAlert();
+ }
+ }}
+ label={
+
+ }
+ />
+
+
+ {
+ if (isMuted) {
+ setIsMuted(false);
+ await unmuteAlert();
+ } else {
+ setIsMuted(true);
+ await muteAlert();
+ }
+ }}
+ label={
+
+ }
+ />
+
+
+ {flyoutUi}
+
+ );
+};
diff --git a/x-pack/plugins/monitoring/public/alerts/context.ts b/x-pack/plugins/monitoring/public/alerts/context.ts
new file mode 100644
index 0000000000000..1017a4ade6c73
--- /dev/null
+++ b/x-pack/plugins/monitoring/public/alerts/context.ts
@@ -0,0 +1,16 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import { AlertsByName } from './types';
+
+export interface IAlertsContext {
+ allAlerts: AlertsByName;
+}
+
+export const AlertsContext = React.createContext({
+ allAlerts: {} as AlertsByName,
+});
diff --git a/x-pack/plugins/monitoring/public/alerts/lib/__snapshots__/get_alert_panels_by_category.test.tsx.snap b/x-pack/plugins/monitoring/public/alerts/lib/__snapshots__/get_alert_panels_by_category.test.tsx.snap
new file mode 100644
index 0000000000000..75637b5bfd6c2
--- /dev/null
+++ b/x-pack/plugins/monitoring/public/alerts/lib/__snapshots__/get_alert_panels_by_category.test.tsx.snap
@@ -0,0 +1,1287 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`getAlertPanelsByCategory non setup mode should allow for state filtering 1`] = `
+Array [
+ Object {
+ "id": 0,
+ "items": Array [
+ Object {
+ "name":
+
+ Resource utilization
+ (
+ 1
+ )
+
+ ,
+ "panel": 1,
+ },
+ ],
+ "title": "Alerts",
+ },
+ Object {
+ "id": 1,
+ "items": Array [
+ Object {
+ "name":
+ monitoring_alert_cpu_usage_label
+ (
+ 1
+ )
+ ,
+ "panel": 2,
+ },
+ ],
+ "title": "Resource utilization",
+ },
+ Object {
+ "id": 2,
+ "items": Array [
+ Object {
+ "name":
+
+
+ triggered:0
+
+
+
+ es_name_0
+
+ ,
+ "panel": 3,
+ },
+ Object {
+ "isSeparator": true,
+ },
+ ],
+ "title": "monitoring_alert_cpu_usage_label",
+ },
+ Object {
+ "content": ,
+ "id": 3,
+ "title": "monitoring_alert_cpu_usage_label",
+ "width": 400,
+ },
+]
+`;
+
+exports[`getAlertPanelsByCategory non setup mode should not show any alert if none are firing 1`] = `
+Array [
+ Object {
+ "id": 0,
+ "items": Array [],
+ "title": "Alerts",
+ },
+]
+`;
+
+exports[`getAlertPanelsByCategory non setup mode should properly group for alerts in a single category 1`] = `
+Array [
+ Object {
+ "id": 0,
+ "items": Array [
+ Object {
+ "name":
+
+ Resource utilization
+ (
+ 2
+ )
+
+ ,
+ "panel": 1,
+ },
+ ],
+ "title": "Alerts",
+ },
+ Object {
+ "id": 1,
+ "items": Array [
+ Object {
+ "name":
+ monitoring_alert_jvm_memory_usage_label
+ (
+ 2
+ )
+ ,
+ "panel": 2,
+ },
+ ],
+ "title": "Resource utilization",
+ },
+ Object {
+ "id": 2,
+ "items": Array [
+ Object {
+ "name":
+
+
+ triggered:1
+
+
+
+ es_name_1
+
+ ,
+ "panel": 3,
+ },
+ Object {
+ "isSeparator": true,
+ },
+ Object {
+ "name":
+
+
+ triggered:0
+
+
+
+ es_name_0
+
+ ,
+ "panel": 4,
+ },
+ Object {
+ "isSeparator": true,
+ },
+ ],
+ "title": "monitoring_alert_jvm_memory_usage_label",
+ },
+ Object {
+ "content": ,
+ "id": 3,
+ "title": "monitoring_alert_jvm_memory_usage_label",
+ "width": 400,
+ },
+ Object {
+ "content": ,
+ "id": 4,
+ "title": "monitoring_alert_jvm_memory_usage_label",
+ "width": 400,
+ },
+]
+`;
+
+exports[`getAlertPanelsByCategory non setup mode should properly group for alerts in each category 1`] = `
+Array [
+ Object {
+ "id": 0,
+ "items": Array [
+ Object {
+ "name":
+
+ Cluster health
+ (
+ 2
+ )
+
+ ,
+ "panel": 1,
+ },
+ Object {
+ "name":
+
+ Resource utilization
+ (
+ 1
+ )
+
+ ,
+ "panel": 2,
+ },
+ Object {
+ "name":
+
+ Errors and exceptions
+ (
+ 2
+ )
+
+ ,
+ "panel": 3,
+ },
+ ],
+ "title": "Alerts",
+ },
+ Object {
+ "id": 1,
+ "items": Array [
+ Object {
+ "name":
+ monitoring_alert_nodes_changed_label
+ (
+ 2
+ )
+ ,
+ "panel": 4,
+ },
+ ],
+ "title": "Cluster health",
+ },
+ Object {
+ "id": 2,
+ "items": Array [
+ Object {
+ "name":
+ monitoring_alert_disk_usage_label
+ (
+ 1
+ )
+ ,
+ "panel": 5,
+ },
+ ],
+ "title": "Resource utilization",
+ },
+ Object {
+ "id": 3,
+ "items": Array [
+ Object {
+ "name":
+ monitoring_alert_license_expiration_label
+ (
+ 2
+ )
+ ,
+ "panel": 6,
+ },
+ ],
+ "title": "Errors and exceptions",
+ },
+ Object {
+ "id": 4,
+ "items": Array [
+ Object {
+ "name":
+
+
+ triggered:1
+
+
+
+ es_name_1
+
+ ,
+ "panel": 7,
+ },
+ Object {
+ "isSeparator": true,
+ },
+ Object {
+ "name":
+
+
+ triggered:0
+
+
+
+ es_name_0
+
+ ,
+ "panel": 8,
+ },
+ Object {
+ "isSeparator": true,
+ },
+ ],
+ "title": "monitoring_alert_nodes_changed_label",
+ },
+ Object {
+ "id": 5,
+ "items": Array [
+ Object {
+ "name":
+
+
+ triggered:0
+
+
+
+ es_name_0
+
+ ,
+ "panel": 9,
+ },
+ Object {
+ "isSeparator": true,
+ },
+ ],
+ "title": "monitoring_alert_disk_usage_label",
+ },
+ Object {
+ "id": 6,
+ "items": Array [
+ Object {
+ "name":
+
+
+ triggered:1
+
+
+
+ es_name_1
+
+ ,
+ "panel": 10,
+ },
+ Object {
+ "isSeparator": true,
+ },
+ Object {
+ "name":
+
+
+ triggered:0
+
+
+
+ es_name_0
+
+ ,
+ "panel": 11,
+ },
+ Object {
+ "isSeparator": true,
+ },
+ ],
+ "title": "monitoring_alert_license_expiration_label",
+ },
+ Object {
+ "content": ,
+ "id": 7,
+ "title": "monitoring_alert_nodes_changed_label",
+ "width": 400,
+ },
+ Object {
+ "content": ,
+ "id": 8,
+ "title": "monitoring_alert_nodes_changed_label",
+ "width": 400,
+ },
+ Object {
+ "content": ,
+ "id": 9,
+ "title": "monitoring_alert_disk_usage_label",
+ "width": 400,
+ },
+ Object {
+ "content": ,
+ "id": 10,
+ "title": "monitoring_alert_license_expiration_label",
+ "width": 400,
+ },
+ Object {
+ "content": ,
+ "id": 11,
+ "title": "monitoring_alert_license_expiration_label",
+ "width": 400,
+ },
+]
+`;
+
+exports[`getAlertPanelsByCategory setup mode should properly group for alerts in a single category 1`] = `
+Array [
+ Object {
+ "id": 0,
+ "items": Array [
+ Object {
+ "name":
+ Resource utilization
+ ,
+ "panel": 1,
+ },
+ ],
+ "title": "Alerts",
+ },
+ Object {
+ "id": 1,
+ "items": Array [
+ Object {
+ "name":
+ monitoring_alert_jvm_memory_usage_label
+ ,
+ "panel": 2,
+ },
+ ],
+ "title": "Resource utilization",
+ },
+ Object {
+ "content": ,
+ "id": 2,
+ "title": "monitoring_alert_jvm_memory_usage_label",
+ "width": 400,
+ },
+]
+`;
+
+exports[`getAlertPanelsByCategory setup mode should properly group for alerts in each category 1`] = `
+Array [
+ Object {
+ "id": 0,
+ "items": Array [
+ Object {
+ "name":
+ Cluster health
+ ,
+ "panel": 1,
+ },
+ Object {
+ "name":
+ Resource utilization
+ ,
+ "panel": 2,
+ },
+ Object {
+ "name":
+ Errors and exceptions
+ ,
+ "panel": 3,
+ },
+ ],
+ "title": "Alerts",
+ },
+ Object {
+ "id": 1,
+ "items": Array [
+ Object {
+ "name":
+ monitoring_alert_nodes_changed_label
+ ,
+ "panel": 4,
+ },
+ ],
+ "title": "Cluster health",
+ },
+ Object {
+ "id": 2,
+ "items": Array [
+ Object {
+ "name":
+ monitoring_alert_disk_usage_label
+ ,
+ "panel": 5,
+ },
+ ],
+ "title": "Resource utilization",
+ },
+ Object {
+ "id": 3,
+ "items": Array [
+ Object {
+ "name":
+ monitoring_alert_license_expiration_label
+ ,
+ "panel": 6,
+ },
+ ],
+ "title": "Errors and exceptions",
+ },
+ Object {
+ "content": ,
+ "id": 4,
+ "title": "monitoring_alert_nodes_changed_label",
+ "width": 400,
+ },
+ Object {
+ "content": ,
+ "id": 5,
+ "title": "monitoring_alert_disk_usage_label",
+ "width": 400,
+ },
+ Object {
+ "content": ,
+ "id": 6,
+ "title": "monitoring_alert_license_expiration_label",
+ "width": 400,
+ },
+]
+`;
+
+exports[`getAlertPanelsByCategory setup mode should still show alerts if none are firing 1`] = `
+Array [
+ Object {
+ "id": 0,
+ "items": Array [
+ Object {
+ "name":
+ Cluster health
+ ,
+ "panel": 1,
+ },
+ Object {
+ "name":
+ Resource utilization
+ ,
+ "panel": 2,
+ },
+ Object {
+ "name":
+ Errors and exceptions
+ ,
+ "panel": 3,
+ },
+ ],
+ "title": "Alerts",
+ },
+ Object {
+ "id": 1,
+ "items": Array [
+ Object {
+ "name":
+ monitoring_alert_logstash_version_mismatch_label
+ ,
+ "panel": 4,
+ },
+ ],
+ "title": "Cluster health",
+ },
+ Object {
+ "id": 2,
+ "items": Array [
+ Object {
+ "name":
+ monitoring_alert_cpu_usage_label
+ ,
+ "panel": 5,
+ },
+ ],
+ "title": "Resource utilization",
+ },
+ Object {
+ "id": 3,
+ "items": Array [
+ Object {
+ "name":
+ monitoring_alert_thread_pool_write_rejections_label
+ ,
+ "panel": 6,
+ },
+ ],
+ "title": "Errors and exceptions",
+ },
+ Object {
+ "content": ,
+ "id": 4,
+ "title": "monitoring_alert_logstash_version_mismatch_label",
+ "width": 400,
+ },
+ Object {
+ "content": ,
+ "id": 5,
+ "title": "monitoring_alert_cpu_usage_label",
+ "width": 400,
+ },
+ Object {
+ "content": ,
+ "id": 6,
+ "title": "monitoring_alert_thread_pool_write_rejections_label",
+ "width": 400,
+ },
+]
+`;
diff --git a/x-pack/plugins/monitoring/public/alerts/lib/__snapshots__/get_alert_panels_by_node.test.tsx.snap b/x-pack/plugins/monitoring/public/alerts/lib/__snapshots__/get_alert_panels_by_node.test.tsx.snap
new file mode 100644
index 0000000000000..e9e89112a9758
--- /dev/null
+++ b/x-pack/plugins/monitoring/public/alerts/lib/__snapshots__/get_alert_panels_by_node.test.tsx.snap
@@ -0,0 +1,660 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`getAlertPanelsByNode should not show any alert if none are firing 1`] = `
+Array [
+ Object {
+ "id": 0,
+ "items": Array [],
+ "title": "Alerts",
+ },
+]
+`;
+
+exports[`getAlertPanelsByNode should properly group for alerts in a single category 1`] = `
+Array [
+ Object {
+ "id": 0,
+ "items": Array [
+ Object {
+ "name":
+ es_name_0
+ (
+ 1
+ )
+ ,
+ "panel": 1,
+ },
+ Object {
+ "name":
+ es_name_1
+ (
+ 1
+ )
+ ,
+ "panel": 2,
+ },
+ ],
+ "title": "Alerts",
+ },
+ Object {
+ "id": 1,
+ "items": Array [
+ Object {
+ "name":
+
+
+ triggered:0
+
+
+
+ monitoring_alert_jvm_memory_usage_label
+
+ ,
+ "panel": 3,
+ },
+ ],
+ "title": "es_name_0",
+ },
+ Object {
+ "id": 2,
+ "items": Array [
+ Object {
+ "name":
+
+
+ triggered:0
+
+
+
+ monitoring_alert_jvm_memory_usage_label
+
+ ,
+ "panel": 4,
+ },
+ ],
+ "title": "es_name_1",
+ },
+ Object {
+ "content": ,
+ "id": 3,
+ "title": "monitoring_alert_jvm_memory_usage_label",
+ "width": 400,
+ },
+ Object {
+ "content": ,
+ "id": 4,
+ "title": "monitoring_alert_jvm_memory_usage_label",
+ "width": 400,
+ },
+]
+`;
+
+exports[`getAlertPanelsByNode should properly group for alerts in each category 1`] = `
+Array [
+ Object {
+ "id": 0,
+ "items": Array [
+ Object {
+ "name":
+ es_name_0
+ (
+ 3
+ )
+ ,
+ "panel": 1,
+ },
+ Object {
+ "name":
+ es_name_1
+ (
+ 2
+ )
+ ,
+ "panel": 2,
+ },
+ ],
+ "title": "Alerts",
+ },
+ Object {
+ "id": 1,
+ "items": Array [
+ Object {
+ "name":
+
+
+ triggered:0
+
+
+
+ monitoring_alert_nodes_changed_label
+
+ ,
+ "panel": 3,
+ },
+ Object {
+ "name":
+
+
+ triggered:0
+
+
+
+ monitoring_alert_disk_usage_label
+
+ ,
+ "panel": 4,
+ },
+ Object {
+ "name":
+
+
+ triggered:0
+
+
+
+ monitoring_alert_license_expiration_label
+
+ ,
+ "panel": 5,
+ },
+ ],
+ "title": "es_name_0",
+ },
+ Object {
+ "id": 2,
+ "items": Array [
+ Object {
+ "name":
+
+
+ triggered:0
+
+
+
+ monitoring_alert_nodes_changed_label
+
+ ,
+ "panel": 6,
+ },
+ Object {
+ "name":
+
+
+ triggered:0
+
+
+
+ monitoring_alert_license_expiration_label
+
+ ,
+ "panel": 7,
+ },
+ ],
+ "title": "es_name_1",
+ },
+ Object {
+ "content": ,
+ "id": 3,
+ "title": "monitoring_alert_nodes_changed_label",
+ "width": 400,
+ },
+ Object {
+ "content": ,
+ "id": 4,
+ "title": "monitoring_alert_disk_usage_label",
+ "width": 400,
+ },
+ Object {
+ "content": ,
+ "id": 5,
+ "title": "monitoring_alert_license_expiration_label",
+ "width": 400,
+ },
+ Object {
+ "content": ,
+ "id": 6,
+ "title": "monitoring_alert_nodes_changed_label",
+ "width": 400,
+ },
+ Object {
+ "content": ,
+ "id": 7,
+ "title": "monitoring_alert_license_expiration_label",
+ "width": 400,
+ },
+]
+`;
diff --git a/x-pack/plugins/monitoring/public/alerts/lib/get_alert_panels_by_category.test.tsx b/x-pack/plugins/monitoring/public/alerts/lib/get_alert_panels_by_category.test.tsx
new file mode 100644
index 0000000000000..16b20119c9607
--- /dev/null
+++ b/x-pack/plugins/monitoring/public/alerts/lib/get_alert_panels_by_category.test.tsx
@@ -0,0 +1,212 @@
+/*
+ * 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 {
+ ALERTS,
+ ALERT_CPU_USAGE,
+ ALERT_LOGSTASH_VERSION_MISMATCH,
+ ALERT_THREAD_POOL_WRITE_REJECTIONS,
+} from '../../../common/constants';
+import { AlertSeverity } from '../../../common/enums';
+import { getAlertPanelsByCategory } from './get_alert_panels_by_category';
+import {
+ ALERT_LICENSE_EXPIRATION,
+ ALERT_NODES_CHANGED,
+ ALERT_DISK_USAGE,
+ ALERT_MEMORY_USAGE,
+} from '../../../common/constants';
+import { AlertsByName } from '../types';
+import { AlertExecutionStatusValues } from '../../../../alerts/common';
+import { AlertState } from '../../../common/types/alerts';
+
+jest.mock('../../legacy_shims', () => ({
+ Legacy: {
+ shims: {
+ uiSettings: {
+ get: () => '',
+ },
+ },
+ },
+}));
+
+jest.mock('../../../common/formatting', () => ({
+ getDateFromNow: (timestamp: number) => `triggered:${timestamp}`,
+ getCalendar: (timestamp: number) => `triggered:${timestamp}`,
+}));
+
+const mockAlert = {
+ id: '',
+ enabled: true,
+ tags: [],
+ consumer: '',
+ schedule: { interval: '1m' },
+ actions: [],
+ params: {},
+ createdBy: null,
+ updatedBy: null,
+ createdAt: new Date('2020-12-08'),
+ updatedAt: new Date('2020-12-08'),
+ apiKey: null,
+ apiKeyOwner: null,
+ throttle: null,
+ muteAll: false,
+ mutedInstanceIds: [],
+ executionStatus: {
+ status: AlertExecutionStatusValues[0],
+ lastExecutionDate: new Date('2020-12-08'),
+ },
+ notifyWhen: null,
+};
+
+function getAllAlerts() {
+ return ALERTS.reduce((accum: AlertsByName, alertType) => {
+ accum[alertType] = {
+ states: [],
+ rawAlert: {
+ alertTypeId: alertType,
+ name: `${alertType}_label`,
+ ...mockAlert,
+ },
+ };
+ return accum;
+ }, {});
+}
+
+describe('getAlertPanelsByCategory', () => {
+ const ui = {
+ isFiring: false,
+ severity: AlertSeverity.Danger,
+ message: { text: '' },
+ resolvedMS: 0,
+ lastCheckedMS: 0,
+ triggeredMS: 0,
+ };
+
+ const cluster = { clusterUuid: '1', clusterName: 'one' };
+
+ function getAlert(type: string, firingCount: number) {
+ const states = [];
+
+ for (let fi = 0; fi < firingCount; fi++) {
+ states.push({
+ firing: true,
+ meta: {},
+ state: {
+ cluster,
+ ui: {
+ ...ui,
+ triggeredMS: fi,
+ },
+ nodeId: `es${fi}`,
+ nodeName: `es_name_${fi}`,
+ },
+ });
+ }
+
+ return {
+ states,
+ rawAlert: {
+ alertTypeId: type,
+ name: `${type}_label`,
+ ...mockAlert,
+ },
+ };
+ }
+
+ const alertsContext = {
+ allAlerts: getAllAlerts(),
+ };
+
+ const stateFilter = (state: AlertState) => true;
+ const panelTitle = 'Alerts';
+
+ describe('non setup mode', () => {
+ it('should properly group for alerts in each category', () => {
+ const alerts = [
+ getAlert(ALERT_NODES_CHANGED, 2),
+ getAlert(ALERT_DISK_USAGE, 1),
+ getAlert(ALERT_LICENSE_EXPIRATION, 2),
+ ];
+ const result = getAlertPanelsByCategory(
+ panelTitle,
+ false,
+ alerts,
+ alertsContext,
+ stateFilter
+ );
+ expect(result).toMatchSnapshot();
+ });
+
+ it('should properly group for alerts in a single category', () => {
+ const alerts = [getAlert(ALERT_MEMORY_USAGE, 2)];
+ const result = getAlertPanelsByCategory(
+ panelTitle,
+ false,
+ alerts,
+ alertsContext,
+ stateFilter
+ );
+ expect(result).toMatchSnapshot();
+ });
+
+ it('should not show any alert if none are firing', () => {
+ const alerts = [
+ getAlert(ALERT_LOGSTASH_VERSION_MISMATCH, 0),
+ getAlert(ALERT_CPU_USAGE, 0),
+ getAlert(ALERT_THREAD_POOL_WRITE_REJECTIONS, 0),
+ ];
+ const result = getAlertPanelsByCategory(
+ panelTitle,
+ false,
+ alerts,
+ alertsContext,
+ stateFilter
+ );
+ expect(result).toMatchSnapshot();
+ });
+
+ it('should allow for state filtering', () => {
+ const alerts = [getAlert(ALERT_CPU_USAGE, 2)];
+ const customStateFilter = (state: AlertState) => state.nodeName === 'es_name_0';
+ const result = getAlertPanelsByCategory(
+ panelTitle,
+ false,
+ alerts,
+ alertsContext,
+ customStateFilter
+ );
+ expect(result).toMatchSnapshot();
+ });
+ });
+
+ describe('setup mode', () => {
+ it('should properly group for alerts in each category', () => {
+ const alerts = [
+ getAlert(ALERT_NODES_CHANGED, 2),
+ getAlert(ALERT_DISK_USAGE, 1),
+ getAlert(ALERT_LICENSE_EXPIRATION, 2),
+ ];
+ const result = getAlertPanelsByCategory(panelTitle, true, alerts, alertsContext, stateFilter);
+ expect(result).toMatchSnapshot();
+ });
+
+ it('should properly group for alerts in a single category', () => {
+ const alerts = [getAlert(ALERT_MEMORY_USAGE, 2)];
+ const result = getAlertPanelsByCategory(panelTitle, true, alerts, alertsContext, stateFilter);
+ expect(result).toMatchSnapshot();
+ });
+
+ it('should still show alerts if none are firing', () => {
+ const alerts = [
+ getAlert(ALERT_LOGSTASH_VERSION_MISMATCH, 0),
+ getAlert(ALERT_CPU_USAGE, 0),
+ getAlert(ALERT_THREAD_POOL_WRITE_REJECTIONS, 0),
+ ];
+ const result = getAlertPanelsByCategory(panelTitle, true, alerts, alertsContext, stateFilter);
+ expect(result).toMatchSnapshot();
+ });
+ });
+});
diff --git a/x-pack/plugins/monitoring/public/alerts/lib/get_alert_panels_by_category.tsx b/x-pack/plugins/monitoring/public/alerts/lib/get_alert_panels_by_category.tsx
new file mode 100644
index 0000000000000..82a1a1f841a22
--- /dev/null
+++ b/x-pack/plugins/monitoring/public/alerts/lib/get_alert_panels_by_category.tsx
@@ -0,0 +1,228 @@
+/*
+ * 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 { EuiText, EuiToolTip } from '@elastic/eui';
+import { AlertPanel } from '../panel';
+import { ALERT_PANEL_MENU } from '../../../common/constants';
+import { getDateFromNow, getCalendar } from '../../../common/formatting';
+import { IAlertsContext } from '../context';
+import { AlertState, CommonAlertStatus } from '../../../common/types/alerts';
+import { PanelItem } from '../types';
+import { sortByNewestAlert } from './sort_by_newest_alert';
+import { Legacy } from '../../legacy_shims';
+
+export function getAlertPanelsByCategory(
+ panelTitle: string,
+ inSetupMode: boolean,
+ alerts: CommonAlertStatus[],
+ alertsContext: IAlertsContext,
+ stateFilter: (state: AlertState) => boolean
+) {
+ const menu = [];
+ for (const category of ALERT_PANEL_MENU) {
+ let categoryFiringAlertCount = 0;
+ if (inSetupMode) {
+ const alertsInCategory = [];
+ for (const categoryAlert of category.alerts) {
+ if (
+ Boolean(alerts.find(({ rawAlert }) => rawAlert.alertTypeId === categoryAlert.alertName))
+ ) {
+ alertsInCategory.push(categoryAlert);
+ }
+ }
+ if (alertsInCategory.length > 0) {
+ menu.push({
+ ...category,
+ alerts: alertsInCategory.map(({ alertName }) => {
+ const alertStatus = alertsContext.allAlerts[alertName];
+ return {
+ alert: alertStatus.rawAlert,
+ states: [],
+ alertName,
+ };
+ }),
+ alertCount: 0,
+ });
+ }
+ } else {
+ const firingAlertsInCategory = [];
+ for (const { alertName } of category.alerts) {
+ const foundAlert = alerts.find(
+ ({ rawAlert: { alertTypeId } }) => alertName === alertTypeId
+ );
+ if (foundAlert && foundAlert.states.length > 0) {
+ const states = foundAlert.states.filter(({ state }) => stateFilter(state));
+ if (states.length > 0) {
+ firingAlertsInCategory.push({
+ alert: foundAlert.rawAlert,
+ states: foundAlert.states,
+ alertName,
+ });
+ categoryFiringAlertCount += states.length;
+ }
+ }
+ }
+
+ if (firingAlertsInCategory.length > 0) {
+ menu.push({
+ ...category,
+ alertCount: categoryFiringAlertCount,
+ alerts: firingAlertsInCategory,
+ });
+ }
+ }
+ }
+
+ for (const item of menu) {
+ for (const alert of item.alerts) {
+ alert.states.sort(sortByNewestAlert);
+ }
+ }
+
+ const panels: PanelItem[] = [
+ {
+ id: 0,
+ title: panelTitle,
+ items: [
+ ...menu.map((category, index) => {
+ const name = inSetupMode ? (
+ {category.label}
+ ) : (
+
+
+ {category.label} ({category.alertCount})
+
+
+ );
+ return {
+ name,
+ panel: index + 1,
+ };
+ }),
+ ],
+ },
+ ];
+
+ if (inSetupMode) {
+ let secondaryPanelIndex = menu.length;
+ let tertiaryPanelIndex = menu.length;
+ let nodeIndex = 0;
+ for (const category of menu) {
+ panels.push({
+ id: nodeIndex + 1,
+ title: `${category.label}`,
+ items: category.alerts.map(({ alertName }) => {
+ const alertStatus = alertsContext.allAlerts[alertName];
+ return {
+ name: {alertStatus.rawAlert.name},
+ panel: ++secondaryPanelIndex,
+ };
+ }),
+ });
+ nodeIndex++;
+ }
+
+ for (const category of menu) {
+ for (const { alert, alertName } of category.alerts) {
+ const alertStatus = alertsContext.allAlerts[alertName];
+ panels.push({
+ id: ++tertiaryPanelIndex,
+ title: `${alert.name}`,
+ width: 400,
+ content: ,
+ });
+ }
+ }
+ } else {
+ let primaryPanelIndex = menu.length;
+ let nodeIndex = 0;
+ for (const category of menu) {
+ panels.push({
+ id: nodeIndex + 1,
+ title: `${category.label}`,
+ items: category.alerts.map(({ alertName, states }) => {
+ const filteredStates = states.filter(({ state }) => stateFilter(state));
+ const alertStatus = alertsContext.allAlerts[alertName];
+ const name = inSetupMode ? (
+ {alertStatus.rawAlert.name}
+ ) : (
+
+ {alertStatus.rawAlert.name} ({filteredStates.length})
+
+ );
+ return {
+ name,
+ panel: ++primaryPanelIndex,
+ };
+ }),
+ });
+ nodeIndex++;
+ }
+
+ let secondaryPanelIndex = menu.length;
+ let tertiaryPanelIndex = menu.reduce((count, category) => {
+ count += category.alerts.length;
+ return count;
+ }, menu.length);
+ for (const category of menu) {
+ for (const { alert, states } of category.alerts) {
+ const items = [];
+ for (const alertState of states.filter(({ state }) => stateFilter(state))) {
+ items.push({
+ name: (
+
+
+
+ {getDateFromNow(
+ alertState.state.ui.triggeredMS,
+ Legacy.shims.uiSettings.get('dateFormat:tz')
+ )}
+
+
+ {alertState.state.nodeName}
+
+ ),
+ panel: ++tertiaryPanelIndex,
+ });
+ items.push({
+ isSeparator: true as const,
+ });
+ }
+
+ panels.push({
+ id: ++secondaryPanelIndex,
+ title: `${alert.name}`,
+ items,
+ });
+ }
+ }
+
+ let tertiaryPanelIndex2 = menu.reduce((count, category) => {
+ count += category.alerts.length;
+ return count;
+ }, menu.length);
+ for (const category of menu) {
+ for (const { alert, states } of category.alerts) {
+ for (const state of states.filter(({ state: _state }) => stateFilter(_state))) {
+ panels.push({
+ id: ++tertiaryPanelIndex2,
+ title: `${alert.name}`,
+ width: 400,
+ content: ,
+ });
+ }
+ }
+ }
+ }
+
+ return panels;
+}
diff --git a/x-pack/plugins/monitoring/public/alerts/lib/get_alert_panels_by_node.test.tsx b/x-pack/plugins/monitoring/public/alerts/lib/get_alert_panels_by_node.test.tsx
new file mode 100644
index 0000000000000..be6ccb1e0981b
--- /dev/null
+++ b/x-pack/plugins/monitoring/public/alerts/lib/get_alert_panels_by_node.test.tsx
@@ -0,0 +1,128 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import {
+ ALERT_CPU_USAGE,
+ ALERT_LOGSTASH_VERSION_MISMATCH,
+ ALERT_THREAD_POOL_WRITE_REJECTIONS,
+} from '../../../common/constants';
+import { AlertSeverity } from '../../../common/enums';
+import { getAlertPanelsByNode } from './get_alert_panels_by_node';
+import {
+ ALERT_LICENSE_EXPIRATION,
+ ALERT_NODES_CHANGED,
+ ALERT_DISK_USAGE,
+ ALERT_MEMORY_USAGE,
+} from '../../../common/constants';
+import { AlertExecutionStatusValues } from '../../../../alerts/common';
+import { AlertState } from '../../../common/types/alerts';
+
+jest.mock('../../legacy_shims', () => ({
+ Legacy: {
+ shims: {
+ uiSettings: {
+ get: () => '',
+ },
+ },
+ },
+}));
+
+jest.mock('../../../common/formatting', () => ({
+ getDateFromNow: (timestamp: number) => `triggered:${timestamp}`,
+ getCalendar: (timestamp: number) => `triggered:${timestamp}`,
+}));
+
+const mockAlert = {
+ id: '',
+ enabled: true,
+ tags: [],
+ consumer: '',
+ schedule: { interval: '1m' },
+ actions: [],
+ params: {},
+ createdBy: null,
+ updatedBy: null,
+ createdAt: new Date('2020-12-08'),
+ updatedAt: new Date('2020-12-08'),
+ apiKey: null,
+ apiKeyOwner: null,
+ throttle: null,
+ muteAll: false,
+ mutedInstanceIds: [],
+ executionStatus: {
+ status: AlertExecutionStatusValues[0],
+ lastExecutionDate: new Date('2020-12-08'),
+ },
+ notifyWhen: null,
+};
+
+describe('getAlertPanelsByNode', () => {
+ const ui = {
+ isFiring: false,
+ severity: AlertSeverity.Danger,
+ message: { text: '' },
+ resolvedMS: 0,
+ lastCheckedMS: 0,
+ triggeredMS: 0,
+ };
+
+ const cluster = { clusterUuid: '1', clusterName: 'one' };
+
+ function getAlert(type: string, firingCount: number) {
+ const states = [];
+
+ for (let fi = 0; fi < firingCount; fi++) {
+ states.push({
+ firing: true,
+ meta: {},
+ state: {
+ cluster,
+ ui,
+ nodeId: `es${fi}`,
+ nodeName: `es_name_${fi}`,
+ },
+ });
+ }
+
+ return {
+ rawAlert: {
+ alertTypeId: type,
+ name: `${type}_label`,
+ ...mockAlert,
+ },
+ states,
+ };
+ }
+
+ const panelTitle = 'Alerts';
+ const stateFilter = (state: AlertState) => true;
+
+ it('should properly group for alerts in each category', () => {
+ const alerts = [
+ getAlert(ALERT_NODES_CHANGED, 2),
+ getAlert(ALERT_DISK_USAGE, 1),
+ getAlert(ALERT_LICENSE_EXPIRATION, 2),
+ ];
+ const result = getAlertPanelsByNode(panelTitle, alerts, stateFilter);
+ expect(result).toMatchSnapshot();
+ });
+
+ it('should properly group for alerts in a single category', () => {
+ const alerts = [getAlert(ALERT_MEMORY_USAGE, 2)];
+ const result = getAlertPanelsByNode(panelTitle, alerts, stateFilter);
+ expect(result).toMatchSnapshot();
+ });
+
+ it('should not show any alert if none are firing', () => {
+ const alerts = [
+ getAlert(ALERT_LOGSTASH_VERSION_MISMATCH, 0),
+ getAlert(ALERT_CPU_USAGE, 0),
+ getAlert(ALERT_THREAD_POOL_WRITE_REJECTIONS, 0),
+ ];
+ const result = getAlertPanelsByNode(panelTitle, alerts, stateFilter);
+ expect(result).toMatchSnapshot();
+ });
+});
diff --git a/x-pack/plugins/monitoring/public/alerts/lib/get_alert_panels_by_node.tsx b/x-pack/plugins/monitoring/public/alerts/lib/get_alert_panels_by_node.tsx
new file mode 100644
index 0000000000000..c48706f4edcb9
--- /dev/null
+++ b/x-pack/plugins/monitoring/public/alerts/lib/get_alert_panels_by_node.tsx
@@ -0,0 +1,138 @@
+/*
+ * 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 { EuiText, EuiToolTip } from '@elastic/eui';
+import { AlertPanel } from '../panel';
+import {
+ CommonAlertStatus,
+ CommonAlertState,
+ CommonAlert,
+ AlertState,
+} from '../../../common/types/alerts';
+import { getDateFromNow, getCalendar } from '../../../common/formatting';
+import { PanelItem } from '../types';
+import { sortByNewestAlert } from './sort_by_newest_alert';
+import { Legacy } from '../../legacy_shims';
+
+export function getAlertPanelsByNode(
+ panelTitle: string,
+ alerts: CommonAlertStatus[],
+ stateFilter: (state: AlertState) => boolean
+) {
+ const alertsByNodes: {
+ [uuid: string]: {
+ [alertName: string]: {
+ alert: CommonAlert;
+ states: CommonAlertState[];
+ count: number;
+ };
+ };
+ } = {};
+ const statesByNodes: {
+ [uuid: string]: CommonAlertState[];
+ } = {};
+
+ for (const { states, rawAlert } of alerts) {
+ const { alertTypeId } = rawAlert;
+ for (const alertState of states.filter(({ state: _state }) => stateFilter(_state))) {
+ const { state } = alertState;
+ statesByNodes[state.nodeId] = statesByNodes[state.nodeId] || [];
+ statesByNodes[state.nodeId].push(alertState);
+
+ alertsByNodes[state.nodeId] = alertsByNodes[state.nodeId] || {};
+ alertsByNodes[state.nodeId][alertTypeId] = alertsByNodes[alertState.state.nodeId][
+ alertTypeId
+ ] || { alert: rawAlert, states: [], count: 0 };
+ alertsByNodes[state.nodeId][alertTypeId].count++;
+ alertsByNodes[state.nodeId][alertTypeId].states.push(alertState);
+ }
+ }
+
+ for (const types of Object.values(alertsByNodes)) {
+ for (const { states } of Object.values(types)) {
+ states.sort(sortByNewestAlert);
+ }
+ }
+
+ const nodeCount = Object.keys(statesByNodes).length;
+ let secondaryPanelIndex = nodeCount;
+ let tertiaryPanelIndex = nodeCount;
+ const panels: PanelItem[] = [
+ {
+ id: 0,
+ title: panelTitle,
+ items: [
+ ...Object.keys(statesByNodes).map((nodeUuid, index) => {
+ const states = (statesByNodes[nodeUuid] as CommonAlertState[]).filter(({ state }) =>
+ stateFilter(state)
+ );
+ return {
+ name: (
+
+ {states[0].state.nodeName} ({states.length})
+
+ ),
+ panel: index + 1,
+ };
+ }),
+ ],
+ },
+ ...Object.keys(statesByNodes).reduce((accum: PanelItem[], nodeUuid, nodeIndex) => {
+ const alertsForNode = Object.values(alertsByNodes[nodeUuid]);
+ const panelItems = [];
+ let title = '';
+ for (const { alert, states } of alertsForNode) {
+ for (const alertState of states) {
+ title = alertState.state.nodeName;
+ panelItems.push({
+ name: (
+
+
+
+ {getDateFromNow(
+ alertState.state.ui.triggeredMS,
+ Legacy.shims.uiSettings.get('dateFormat:tz')
+ )}
+
+
+ {alert.name}
+
+ ),
+ panel: ++secondaryPanelIndex,
+ });
+ }
+ }
+ accum.push({
+ id: nodeIndex + 1,
+ title,
+ items: panelItems,
+ });
+ return accum;
+ }, []),
+ ...Object.keys(statesByNodes).reduce((accum: PanelItem[], nodeUuid, nodeIndex) => {
+ const alertsForNode = Object.values(alertsByNodes[nodeUuid]);
+ for (const { alert, states } of alertsForNode) {
+ for (const alertState of states) {
+ accum.push({
+ id: ++tertiaryPanelIndex,
+ title: alert.name,
+ width: 400,
+ content: ,
+ });
+ }
+ }
+ return accum;
+ }, []),
+ ];
+
+ return panels;
+}
diff --git a/x-pack/plugins/monitoring/public/alerts/lib/sort_by_newest_alert.test.ts b/x-pack/plugins/monitoring/public/alerts/lib/sort_by_newest_alert.test.ts
new file mode 100644
index 0000000000000..29981c3ed32fe
--- /dev/null
+++ b/x-pack/plugins/monitoring/public/alerts/lib/sort_by_newest_alert.test.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 { AlertSeverity } from '../../../common/enums';
+import { sortByNewestAlert } from './sort_by_newest_alert';
+
+describe('sortByNewestAlert', () => {
+ const ui = {
+ isFiring: false,
+ severity: AlertSeverity.Danger,
+ message: { text: '' },
+ resolvedMS: 0,
+ lastCheckedMS: 0,
+ triggeredMS: 0,
+ };
+
+ const cluster = { clusterUuid: '1', clusterName: 'one' };
+
+ it('should sort properly', () => {
+ const a = {
+ firing: false,
+ meta: {},
+ state: {
+ cluster,
+ ui: {
+ ...ui,
+ triggeredMS: 2,
+ },
+ nodeId: `es1`,
+ nodeName: `es_name_1`,
+ },
+ };
+ const b = {
+ firing: false,
+ meta: {},
+ state: {
+ cluster,
+ ui: {
+ ...ui,
+ triggeredMS: 1,
+ },
+ nodeId: `es1`,
+ nodeName: `es_name_1`,
+ },
+ };
+ expect(sortByNewestAlert(a, b)).toBe(-1);
+ });
+});
diff --git a/x-pack/plugins/monitoring/public/alerts/lib/sort_by_newest_alert.ts b/x-pack/plugins/monitoring/public/alerts/lib/sort_by_newest_alert.ts
new file mode 100644
index 0000000000000..f5a20e0bae37e
--- /dev/null
+++ b/x-pack/plugins/monitoring/public/alerts/lib/sort_by_newest_alert.ts
@@ -0,0 +1,14 @@
+/*
+ * 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 } from '../../../common/types/alerts';
+
+export function sortByNewestAlert(a: CommonAlertState, b: CommonAlertState) {
+ if (a.state.ui.triggeredMS === b.state.ui.triggeredMS) {
+ return 0;
+ }
+ return a.state.ui.triggeredMS < b.state.ui.triggeredMS ? 1 : -1;
+}
diff --git a/x-pack/plugins/monitoring/public/alerts/panel.tsx b/x-pack/plugins/monitoring/public/alerts/panel.tsx
index b480e46215108..139010a3d2446 100644
--- a/x-pack/plugins/monitoring/public/alerts/panel.tsx
+++ b/x-pack/plugins/monitoring/public/alerts/panel.tsx
@@ -3,186 +3,39 @@
* 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, useMemo } from 'react';
-import { i18n } from '@kbn/i18n';
-import { FormattedMessage } from '@kbn/i18n/react';
+import React, { Fragment } from 'react';
import {
EuiSpacer,
- EuiButton,
- EuiFlexGroup,
- EuiFlexItem,
- EuiSwitch,
EuiTitle,
EuiHorizontalRule,
EuiListGroup,
EuiListGroupItem,
} from '@elastic/eui';
-import { CommonAlertStatus, CommonAlertState, AlertMessage } from '../../common/types/alerts';
-import { Legacy } from '../legacy_shims';
+import { CommonAlert, CommonAlertState, AlertMessage } from '../../common/types/alerts';
import { replaceTokens } from './lib/replace_tokens';
-import { isInSetupMode, hideBottomBar, showBottomBar } from '../lib/setup_mode';
-import { BASE_ALERT_API_PATH } from '../../../alerts/common';
+import { isInSetupMode } from '../lib/setup_mode';
import { SetupModeContext } from '../components/setup_mode/setup_mode_context';
+import { AlertConfiguration } from './configuration';
interface Props {
- alert: CommonAlertStatus;
+ alert: CommonAlert;
alertState?: CommonAlertState;
}
export const AlertPanel: React.FC = (props: Props) => {
- const {
- alert: { rawAlert },
- alertState,
- } = props;
-
- const [showFlyout, setShowFlyout] = React.useState(false);
- const [isEnabled, setIsEnabled] = React.useState(rawAlert?.enabled);
- const [isMuted, setIsMuted] = React.useState(rawAlert?.muteAll);
- const [isSaving, setIsSaving] = React.useState(false);
+ const { alert, alertState } = props;
const inSetupMode = isInSetupMode(React.useContext(SetupModeContext));
- const flyoutUi = useMemo(
- () =>
- showFlyout &&
- Legacy.shims.triggersActionsUi.getEditAlertFlyout({
- initialAlert: rawAlert,
- onClose: () => {
- setShowFlyout(false);
- showBottomBar();
- },
- }),
- // eslint-disable-next-line react-hooks/exhaustive-deps
- [showFlyout]
- );
-
- if (!rawAlert) {
+ if (!alert) {
return null;
}
- async function disableAlert() {
- setIsSaving(true);
- try {
- await Legacy.shims.http.post(`${BASE_ALERT_API_PATH}/alert/${rawAlert.id}/_disable`);
- } catch (err) {
- Legacy.shims.toastNotifications.addDanger({
- title: i18n.translate('xpack.monitoring.alerts.panel.disableAlert.errorTitle', {
- defaultMessage: `Unable to disable alert`,
- }),
- text: err.message,
- });
- }
- setIsSaving(false);
- }
- async function enableAlert() {
- setIsSaving(true);
- try {
- await Legacy.shims.http.post(`${BASE_ALERT_API_PATH}/alert/${rawAlert.id}/_enable`);
- } catch (err) {
- Legacy.shims.toastNotifications.addDanger({
- title: i18n.translate('xpack.monitoring.alerts.panel.enableAlert.errorTitle', {
- defaultMessage: `Unable to enable alert`,
- }),
- text: err.message,
- });
- }
- setIsSaving(false);
- }
- async function muteAlert() {
- setIsSaving(true);
- try {
- await Legacy.shims.http.post(`${BASE_ALERT_API_PATH}/alert/${rawAlert.id}/_mute_all`);
- } catch (err) {
- Legacy.shims.toastNotifications.addDanger({
- title: i18n.translate('xpack.monitoring.alerts.panel.muteAlert.errorTitle', {
- defaultMessage: `Unable to mute alert`,
- }),
- text: err.message,
- });
- }
- setIsSaving(false);
- }
- async function unmuteAlert() {
- setIsSaving(true);
- try {
- await Legacy.shims.http.post(`${BASE_ALERT_API_PATH}/alert/${rawAlert.id}/_unmute_all`);
- } catch (err) {
- Legacy.shims.toastNotifications.addDanger({
- title: i18n.translate('xpack.monitoring.alerts.panel.ummuteAlert.errorTitle', {
- defaultMessage: `Unable to unmute alert`,
- }),
- text: err.message,
- });
- }
- setIsSaving(false);
- }
-
- const configurationUi = (
-
-
-
- {
- setShowFlyout(true);
- hideBottomBar();
- }}
- >
- {i18n.translate('xpack.monitoring.alerts.panel.editAlert', {
- defaultMessage: `Edit alert`,
- })}
-
-
-
- {
- if (isEnabled) {
- setIsEnabled(false);
- await disableAlert();
- } else {
- setIsEnabled(true);
- await enableAlert();
- }
- }}
- label={
-
- }
- />
-
-
- {
- if (isMuted) {
- setIsMuted(false);
- await unmuteAlert();
- } else {
- setIsMuted(true);
- await muteAlert();
- }
- }}
- label={
-
- }
- />
-
-
- {flyoutUi}
-
- );
-
if (inSetupMode || !alertState) {
- return {configurationUi}
;
+ return (
+
+ );
}
const nextStepsUi =
@@ -204,7 +57,9 @@ export const AlertPanel: React.FC = (props: Props) => {
{nextStepsUi}
- {configurationUi}
+
);
};
diff --git a/x-pack/plugins/monitoring/public/alerts/types.ts b/x-pack/plugins/monitoring/public/alerts/types.ts
new file mode 100644
index 0000000000000..80918b5d8767a
--- /dev/null
+++ b/x-pack/plugins/monitoring/public/alerts/types.ts
@@ -0,0 +1,29 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import React from 'react';
+import { CommonAlertStatus } from '../../common/types/alerts';
+
+export interface AlertsByName {
+ [name: string]: CommonAlertStatus;
+}
+
+export interface PanelItem {
+ id: number;
+ title: string;
+ width?: number;
+ content?: React.ReactElement;
+ items?: Array;
+}
+
+export interface ContextMenuItem {
+ name: React.ReactElement;
+ panel?: number;
+ onClick?: () => void;
+}
+
+export interface ContextMenuItemSeparator {
+ isSeparator: true;
+}
diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/node/advanced.js b/x-pack/plugins/monitoring/public/components/elasticsearch/node/advanced.js
index c16f55b438c35..5f45f53adddee 100644
--- a/x-pack/plugins/monitoring/public/components/elasticsearch/node/advanced.js
+++ b/x-pack/plugins/monitoring/public/components/elasticsearch/node/advanced.js
@@ -18,7 +18,7 @@ import { NodeDetailStatus } from '../node_detail_status';
import { MonitoringTimeseriesContainer } from '../../chart';
import { AlertsCallout } from '../../../alerts/callout';
-export const AdvancedNode = ({ nodeSummary, metrics, alerts, nodeId, ...props }) => {
+export const AdvancedNode = ({ nodeSummary, metrics, alerts, ...props }) => {
const metricsToShow = [
metrics.node_gc,
metrics.node_gc_time,
@@ -44,7 +44,7 @@ export const AdvancedNode = ({ nodeSummary, metrics, alerts, nodeId, ...props })
- state.nodeId === nodeId} />
+
{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 7cf7227f50202..111d036e84706 100644
--- a/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js
+++ b/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js
@@ -128,7 +128,6 @@ const getColumns = (showCgroupMetricsElasticsearch, setupMode, clusterUuid, aler
defaultMessage: 'Alerts',
}),
field: 'alerts',
- // width: '175px',
sortable: true,
render: (_field, node) => {
return (
diff --git a/x-pack/plugins/monitoring/public/views/base_controller.js b/x-pack/plugins/monitoring/public/views/base_controller.js
index 18c3a59d6b9da..f9c6cfb6024da 100644
--- a/x-pack/plugins/monitoring/public/views/base_controller.js
+++ b/x-pack/plugins/monitoring/public/views/base_controller.js
@@ -13,6 +13,7 @@ import { Legacy } from '../legacy_shims';
import { PromiseWithCancel } from '../../common/cancel_promise';
import { SetupModeFeature } from '../../common/enums';
import { updateSetupModeData, isSetupModeFeatureEnabled } from '../lib/setup_mode';
+import { AlertsContext } from '../alerts/context';
import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public';
/**
@@ -243,11 +244,13 @@ export class MonitoringViewBaseController {
const wrappedComponent = (
- {!this._isDataInitialized ? (
-
- ) : (
- component
- )}
+
+ {!this._isDataInitialized ? (
+
+ ) : (
+ component
+ )}
+
);
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 586261eecb250..2f0aa67e4e16b 100644
--- a/x-pack/plugins/monitoring/public/views/elasticsearch/node/index.js
+++ b/x-pack/plugins/monitoring/public/views/elasticsearch/node/index.js
@@ -14,6 +14,7 @@ import { uiRoutes } from '../../../angular/helpers/routes';
import { routeInitProvider } from '../../../lib/route_init';
import { getPageData } from './get_page_data';
import template from './index.html';
+import { SetupModeRenderer } from '../../../components/renderers';
import { Node } from '../../../components/elasticsearch/node/node';
import { labels } from '../../../components/elasticsearch/shard_allocation/lib/labels';
import { nodesByIndices } from '../../../components/elasticsearch/shard_allocation/transformers/nodes_by_indices';
@@ -26,7 +27,9 @@ import {
ALERT_MISSING_MONITORING_DATA,
ALERT_DISK_USAGE,
ALERT_MEMORY_USAGE,
+ ELASTICSEARCH_SYSTEM_ID,
} from '../../../../common/constants';
+import { SetupModeContext } from '../../../components/setup_mode/setup_mode_context';
uiRoutes.when('/elasticsearch/nodes/:node', {
template,
@@ -122,14 +125,26 @@ uiRoutes.when('/elasticsearch/nodes/:node', {
$scope.labels = labels.node;
this.renderReact(
- (
+
+ {flyoutComponent}
+
+ {bottomBarComponent}
+
+ )}
/>
);
}
diff --git a/x-pack/plugins/monitoring/server/alerts/base_alert.ts b/x-pack/plugins/monitoring/server/alerts/base_alert.ts
index 1d8de2bab015c..4f989b37421ef 100644
--- a/x-pack/plugins/monitoring/server/alerts/base_alert.ts
+++ b/x-pack/plugins/monitoring/server/alerts/base_alert.ts
@@ -42,6 +42,7 @@ import { mapLegacySeverity } from '../lib/alerts/map_legacy_severity';
interface LegacyOptions {
watchName: string;
+ nodeNameLabel: string;
changeDataValues?: Partial;
}
@@ -322,6 +323,7 @@ export class BaseAlert {
shouldFire: !legacyAlert.resolved_timestamp,
severity: mapLegacySeverity(legacyAlert.metadata.severity),
meta: legacyAlert,
+ nodeName: this.alertOptions.legacy!.nodeNameLabel,
...this.alertOptions.legacy!.changeDataValues,
};
});
@@ -394,6 +396,7 @@ export class BaseAlert {
}
const cluster = clusters.find((c: AlertCluster) => c.clusterUuid === item.clusterUuid);
const alertState: AlertState = this.getDefaultAlertState(cluster!, item);
+ alertState.nodeName = item.nodeName;
alertState.ui.triggeredMS = currentUTC;
alertState.ui.isFiring = true;
alertState.ui.severity = item.severity;
diff --git a/x-pack/plugins/monitoring/server/alerts/cluster_health_alert.test.ts b/x-pack/plugins/monitoring/server/alerts/cluster_health_alert.test.ts
index a4e9f56109698..fbf81bc3513f6 100644
--- a/x-pack/plugins/monitoring/server/alerts/cluster_health_alert.test.ts
+++ b/x-pack/plugins/monitoring/server/alerts/cluster_health_alert.test.ts
@@ -119,6 +119,7 @@ describe('ClusterHealthAlert', () => {
{
cluster: { clusterUuid: 'abc123', clusterName: 'testCluster' },
ccs: undefined,
+ nodeName: 'Elasticsearch cluster alert',
ui: {
isFiring: true,
message: {
diff --git a/x-pack/plugins/monitoring/server/alerts/cluster_health_alert.ts b/x-pack/plugins/monitoring/server/alerts/cluster_health_alert.ts
index 3b375654548d8..d7c33ae85cc9d 100644
--- a/x-pack/plugins/monitoring/server/alerts/cluster_health_alert.ts
+++ b/x-pack/plugins/monitoring/server/alerts/cluster_health_alert.ts
@@ -38,6 +38,9 @@ export class ClusterHealthAlert extends BaseAlert {
name: LEGACY_ALERT_DETAILS[ALERT_CLUSTER_HEALTH].label,
legacy: {
watchName: 'elasticsearch_cluster_status',
+ nodeNameLabel: i18n.translate('xpack.monitoring.alerts.clusterHealth.nodeNameLabel', {
+ defaultMessage: 'Elasticsearch cluster alert',
+ }),
},
actionVariables: [
{
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 7bdef1ee2c2c4..c587ca52f26ab 100644
--- a/x-pack/plugins/monitoring/server/alerts/cpu_usage_alert.ts
+++ b/x-pack/plugins/monitoring/server/alerts/cpu_usage_alert.ts
@@ -5,6 +5,7 @@
*/
import { i18n } from '@kbn/i18n';
+import numeral from '@elastic/numeral';
import { BaseAlert } from './base_alert';
import {
AlertData,
@@ -16,8 +17,8 @@ import {
AlertMessageTimeToken,
AlertMessageLinkToken,
AlertInstanceState,
- CommonAlertFilter,
CommonAlertParams,
+ CommonAlertFilter,
} from '../../common/types/alerts';
import { AlertInstance } from '../../../alerts/server';
import {
@@ -25,6 +26,8 @@ import {
ALERT_CPU_USAGE,
ALERT_DETAILS,
} from '../../common/constants';
+// @ts-ignore
+import { ROUNDED_FLOAT } from '../../common/formatting';
import { fetchCpuUsageNodeStats } from '../lib/alerts/fetch_cpu_usage_node_stats';
import { getCcsIndexPattern } from '../lib/alerts/get_ccs_index_pattern';
import { AlertMessageTokenType, AlertSeverity } from '../../common/enums';
@@ -121,7 +124,7 @@ export class CpuUsageAlert extends BaseAlert {
defaultMessage: `Node #start_link{nodeName}#end_link is reporting cpu usage of {cpuUsage}% at #absolute`,
values: {
nodeName: stat.nodeName,
- cpuUsage: stat.cpuUsage,
+ cpuUsage: numeral(stat.cpuUsage).format(ROUNDED_FLOAT),
},
}),
nextSteps: [
diff --git a/x-pack/plugins/monitoring/server/alerts/disk_usage_alert.ts b/x-pack/plugins/monitoring/server/alerts/disk_usage_alert.ts
index 133fe261d0791..4a736f27320eb 100644
--- a/x-pack/plugins/monitoring/server/alerts/disk_usage_alert.ts
+++ b/x-pack/plugins/monitoring/server/alerts/disk_usage_alert.ts
@@ -5,6 +5,7 @@
*/
import { i18n } from '@kbn/i18n';
+import numeral from '@elastic/numeral';
import { BaseAlert } from './base_alert';
import {
AlertData,
@@ -15,8 +16,9 @@ import {
AlertMessageTimeToken,
AlertMessageLinkToken,
AlertInstanceState,
- CommonAlertFilter,
CommonAlertParams,
+ AlertDiskUsageNodeStats,
+ CommonAlertFilter,
} from '../../common/types/alerts';
import { AlertInstance } from '../../../alerts/server';
import {
@@ -24,6 +26,8 @@ import {
ALERT_DISK_USAGE,
ALERT_DETAILS,
} from '../../common/constants';
+// @ts-ignore
+import { ROUNDED_FLOAT } from '../../common/formatting';
import { fetchDiskUsageNodeStats } from '../lib/alerts/fetch_disk_usage_node_stats';
import { getCcsIndexPattern } from '../lib/alerts/get_ccs_index_pattern';
import { AlertMessageTokenType, AlertSeverity } from '../../common/enums';
@@ -102,13 +106,13 @@ export class DiskUsageAlert extends BaseAlert {
}
protected getUiMessage(alertState: AlertState, item: AlertData): AlertMessage {
- const stat = item.meta as AlertDiskUsageState;
+ const stat = item.meta as AlertDiskUsageNodeStats;
return {
text: i18n.translate('xpack.monitoring.alerts.diskUsage.ui.firingMessage', {
defaultMessage: `Node #start_link{nodeName}#end_link is reporting disk usage of {diskUsage}% at #absolute`,
values: {
nodeName: stat.nodeName,
- diskUsage: stat.diskUsage,
+ diskUsage: numeral(stat.diskUsage).format(ROUNDED_FLOAT),
},
}),
nextSteps: [
diff --git a/x-pack/plugins/monitoring/server/alerts/elasticsearch_version_mismatch_alert.test.ts b/x-pack/plugins/monitoring/server/alerts/elasticsearch_version_mismatch_alert.test.ts
index 46fdd1fa98563..ed39cbea3381c 100644
--- a/x-pack/plugins/monitoring/server/alerts/elasticsearch_version_mismatch_alert.test.ts
+++ b/x-pack/plugins/monitoring/server/alerts/elasticsearch_version_mismatch_alert.test.ts
@@ -124,6 +124,7 @@ describe('ElasticsearchVersionMismatchAlert', () => {
{
cluster: { clusterUuid: 'abc123', clusterName: 'testCluster' },
ccs: undefined,
+ nodeName: 'Elasticsearch node alert',
ui: {
isFiring: true,
message: {
diff --git a/x-pack/plugins/monitoring/server/alerts/elasticsearch_version_mismatch_alert.ts b/x-pack/plugins/monitoring/server/alerts/elasticsearch_version_mismatch_alert.ts
index 88b5b708d41f3..9002fb54fcf23 100644
--- a/x-pack/plugins/monitoring/server/alerts/elasticsearch_version_mismatch_alert.ts
+++ b/x-pack/plugins/monitoring/server/alerts/elasticsearch_version_mismatch_alert.ts
@@ -26,6 +26,12 @@ export class ElasticsearchVersionMismatchAlert extends BaseAlert {
name: LEGACY_ALERT_DETAILS[ALERT_ELASTICSEARCH_VERSION_MISMATCH].label,
legacy: {
watchName: 'elasticsearch_version_mismatch',
+ nodeNameLabel: i18n.translate(
+ 'xpack.monitoring.alerts.elasticsearchVersionMismatch.nodeNameLabel',
+ {
+ defaultMessage: 'Elasticsearch node alert',
+ }
+ ),
changeDataValues: { severity: AlertSeverity.Warning },
},
interval: '1d',
diff --git a/x-pack/plugins/monitoring/server/alerts/kibana_version_mismatch_alert.test.ts b/x-pack/plugins/monitoring/server/alerts/kibana_version_mismatch_alert.test.ts
index 2367b53330ec5..96464e16f8c72 100644
--- a/x-pack/plugins/monitoring/server/alerts/kibana_version_mismatch_alert.test.ts
+++ b/x-pack/plugins/monitoring/server/alerts/kibana_version_mismatch_alert.test.ts
@@ -126,6 +126,7 @@ describe('KibanaVersionMismatchAlert', () => {
{
cluster: { clusterUuid: 'abc123', clusterName: 'testCluster' },
ccs: undefined,
+ nodeName: 'Kibana instance alert',
ui: {
isFiring: true,
message: {
diff --git a/x-pack/plugins/monitoring/server/alerts/kibana_version_mismatch_alert.ts b/x-pack/plugins/monitoring/server/alerts/kibana_version_mismatch_alert.ts
index c9e5786484899..0d394b8f45ecc 100644
--- a/x-pack/plugins/monitoring/server/alerts/kibana_version_mismatch_alert.ts
+++ b/x-pack/plugins/monitoring/server/alerts/kibana_version_mismatch_alert.ts
@@ -26,6 +26,12 @@ export class KibanaVersionMismatchAlert extends BaseAlert {
name: LEGACY_ALERT_DETAILS[ALERT_KIBANA_VERSION_MISMATCH].label,
legacy: {
watchName: 'kibana_version_mismatch',
+ nodeNameLabel: i18n.translate(
+ 'xpack.monitoring.alerts.kibanaVersionMismatch.nodeNameLabel',
+ {
+ defaultMessage: 'Kibana instance alert',
+ }
+ ),
changeDataValues: { severity: AlertSeverity.Warning },
},
interval: '1d',
diff --git a/x-pack/plugins/monitoring/server/alerts/license_expiration_alert.test.ts b/x-pack/plugins/monitoring/server/alerts/license_expiration_alert.test.ts
index f7a3d321b960b..c64b6e4b92984 100644
--- a/x-pack/plugins/monitoring/server/alerts/license_expiration_alert.test.ts
+++ b/x-pack/plugins/monitoring/server/alerts/license_expiration_alert.test.ts
@@ -130,6 +130,7 @@ describe('LicenseExpirationAlert', () => {
{
cluster: { clusterUuid, clusterName },
ccs: undefined,
+ nodeName: 'Elasticsearch cluster alert',
ui: {
isFiring: true,
message: {
diff --git a/x-pack/plugins/monitoring/server/alerts/license_expiration_alert.ts b/x-pack/plugins/monitoring/server/alerts/license_expiration_alert.ts
index 80479023a3a60..9dacba1dfe0ec 100644
--- a/x-pack/plugins/monitoring/server/alerts/license_expiration_alert.ts
+++ b/x-pack/plugins/monitoring/server/alerts/license_expiration_alert.ts
@@ -34,6 +34,9 @@ export class LicenseExpirationAlert extends BaseAlert {
name: LEGACY_ALERT_DETAILS[ALERT_LICENSE_EXPIRATION].label,
legacy: {
watchName: 'xpack_license_expiration',
+ nodeNameLabel: i18n.translate('xpack.monitoring.alerts.licenseExpiration.nodeNameLabel', {
+ defaultMessage: 'Elasticsearch cluster alert',
+ }),
},
interval: '1d',
actionVariables: [
diff --git a/x-pack/plugins/monitoring/server/alerts/logstash_version_mismatch_alert.test.ts b/x-pack/plugins/monitoring/server/alerts/logstash_version_mismatch_alert.test.ts
index a021a0e6fe179..dd23cfc76dc6d 100644
--- a/x-pack/plugins/monitoring/server/alerts/logstash_version_mismatch_alert.test.ts
+++ b/x-pack/plugins/monitoring/server/alerts/logstash_version_mismatch_alert.test.ts
@@ -125,6 +125,7 @@ describe('LogstashVersionMismatchAlert', () => {
{
cluster: { clusterUuid: 'abc123', clusterName: 'testCluster' },
ccs: undefined,
+ nodeName: 'Logstash node alert',
ui: {
isFiring: true,
message: {
diff --git a/x-pack/plugins/monitoring/server/alerts/logstash_version_mismatch_alert.ts b/x-pack/plugins/monitoring/server/alerts/logstash_version_mismatch_alert.ts
index 98640fb6e183a..4eae3cd12eed4 100644
--- a/x-pack/plugins/monitoring/server/alerts/logstash_version_mismatch_alert.ts
+++ b/x-pack/plugins/monitoring/server/alerts/logstash_version_mismatch_alert.ts
@@ -26,6 +26,12 @@ export class LogstashVersionMismatchAlert extends BaseAlert {
name: LEGACY_ALERT_DETAILS[ALERT_LOGSTASH_VERSION_MISMATCH].label,
legacy: {
watchName: 'logstash_version_mismatch',
+ nodeNameLabel: i18n.translate(
+ 'xpack.monitoring.alerts.logstashVersionMismatch.nodeNameLabel',
+ {
+ defaultMessage: 'Logstash node alert',
+ }
+ ),
changeDataValues: { severity: AlertSeverity.Warning },
},
interval: '1d',
diff --git a/x-pack/plugins/monitoring/server/alerts/memory_usage_alert.ts b/x-pack/plugins/monitoring/server/alerts/memory_usage_alert.ts
index 860cd41f9057d..d5ea291aa52ed 100644
--- a/x-pack/plugins/monitoring/server/alerts/memory_usage_alert.ts
+++ b/x-pack/plugins/monitoring/server/alerts/memory_usage_alert.ts
@@ -5,6 +5,7 @@
*/
import { i18n } from '@kbn/i18n';
+import numeral from '@elastic/numeral';
import { BaseAlert } from './base_alert';
import {
AlertData,
@@ -15,8 +16,9 @@ import {
AlertMessageTimeToken,
AlertMessageLinkToken,
AlertInstanceState,
- CommonAlertFilter,
CommonAlertParams,
+ AlertMemoryUsageNodeStats,
+ CommonAlertFilter,
} from '../../common/types/alerts';
import { AlertInstance } from '../../../alerts/server';
import {
@@ -24,6 +26,8 @@ import {
ALERT_MEMORY_USAGE,
ALERT_DETAILS,
} from '../../common/constants';
+// @ts-ignore
+import { ROUNDED_FLOAT } from '../../common/formatting';
import { fetchMemoryUsageNodeStats } from '../lib/alerts/fetch_memory_usage_node_stats';
import { getCcsIndexPattern } from '../lib/alerts/get_ccs_index_pattern';
import { AlertMessageTokenType, AlertSeverity } from '../../common/enums';
@@ -108,13 +112,13 @@ export class MemoryUsageAlert extends BaseAlert {
}
protected getUiMessage(alertState: AlertState, item: AlertData): AlertMessage {
- const stat = item.meta as AlertMemoryUsageState;
+ const stat = item.meta as AlertMemoryUsageNodeStats;
return {
text: i18n.translate('xpack.monitoring.alerts.memoryUsage.ui.firingMessage', {
defaultMessage: `Node #start_link{nodeName}#end_link is reporting JVM memory usage of {memoryUsage}% at #absolute`,
values: {
nodeName: stat.nodeName,
- memoryUsage: stat.memoryUsage,
+ memoryUsage: numeral(stat.memoryUsage).format(ROUNDED_FLOAT),
},
}),
nextSteps: [
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 12bb27ce132d0..6ba4333309f00 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
@@ -128,9 +128,9 @@ describe('MissingMonitoringDataAlert', () => {
{
ccs: undefined,
cluster: { clusterUuid, clusterName },
- gapDuration,
- nodeName,
nodeId,
+ nodeName,
+ gapDuration,
ui: {
isFiring: true,
message: {
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 1c93ff4a28719..b4c8a667a1ce8 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
@@ -12,10 +12,9 @@ import {
AlertCluster,
AlertState,
AlertMessage,
- AlertNodeState,
AlertMessageTimeToken,
- CommonAlertFilter,
CommonAlertParams,
+ CommonAlertFilter,
} from '../../common/types/alerts';
import { AlertInstance } from '../../../alerts/server';
import {
@@ -40,12 +39,12 @@ export class MissingMonitoringDataAlert extends BaseAlert {
super(rawAlert, {
id: ALERT_MISSING_MONITORING_DATA,
name: ALERT_DETAILS[ALERT_MISSING_MONITORING_DATA].label,
+ accessorKey: 'gapDuration',
defaultParams: {
duration: '15m',
limit: '1d',
},
throttle: '6h',
- accessorKey: 'gapDuration',
actionVariables: [
{
name: 'nodes',
@@ -153,7 +152,7 @@ export class MissingMonitoringDataAlert extends BaseAlert {
protected executeActions(
instance: AlertInstance,
- { alertStates }: { alertStates: AlertNodeState[] },
+ { alertStates }: { alertStates: AlertState[] },
item: AlertData | null,
cluster: AlertCluster
) {
diff --git a/x-pack/plugins/monitoring/server/alerts/nodes_changed_alert.test.ts b/x-pack/plugins/monitoring/server/alerts/nodes_changed_alert.test.ts
index 99be91dc293cb..e6017e799cba8 100644
--- a/x-pack/plugins/monitoring/server/alerts/nodes_changed_alert.test.ts
+++ b/x-pack/plugins/monitoring/server/alerts/nodes_changed_alert.test.ts
@@ -137,6 +137,7 @@ describe('NodesChangedAlert', () => {
{
cluster: { clusterUuid, clusterName },
ccs: undefined,
+ nodeName: 'Elasticsearch nodes alert',
ui: {
isFiring: true,
message: {
diff --git a/x-pack/plugins/monitoring/server/alerts/nodes_changed_alert.ts b/x-pack/plugins/monitoring/server/alerts/nodes_changed_alert.ts
index 47d5c5ac2c241..09c12d345d930 100644
--- a/x-pack/plugins/monitoring/server/alerts/nodes_changed_alert.ts
+++ b/x-pack/plugins/monitoring/server/alerts/nodes_changed_alert.ts
@@ -26,6 +26,9 @@ export class NodesChangedAlert extends BaseAlert {
name: LEGACY_ALERT_DETAILS[ALERT_NODES_CHANGED].label,
legacy: {
watchName: 'elasticsearch_nodes',
+ nodeNameLabel: i18n.translate('xpack.monitoring.alerts.nodesChanged.nodeNameLabel', {
+ defaultMessage: 'Elasticsearch nodes alert',
+ }),
changeDataValues: { shouldFire: true },
},
actionVariables: [
diff --git a/x-pack/plugins/monitoring/server/alerts/thread_pool_rejections_alert_base.ts b/x-pack/plugins/monitoring/server/alerts/thread_pool_rejections_alert_base.ts
index 2d8ccabaac853..1e539c52eeedc 100644
--- a/x-pack/plugins/monitoring/server/alerts/thread_pool_rejections_alert_base.ts
+++ b/x-pack/plugins/monitoring/server/alerts/thread_pool_rejections_alert_base.ts
@@ -13,8 +13,8 @@ import {
AlertThreadPoolRejectionsState,
AlertMessageTimeToken,
AlertMessageLinkToken,
- CommonAlertFilter,
ThreadPoolRejectionsAlertParams,
+ CommonAlertFilter,
} from '../../common/types/alerts';
import { AlertInstance } from '../../../alerts/server';
import { INDEX_PATTERN_ELASTICSEARCH } from '../../common/constants';
diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_legacy_alerts.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_legacy_alerts.test.ts
index a3743a8ff206f..5055017051816 100644
--- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_legacy_alerts.test.ts
+++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_legacy_alerts.test.ts
@@ -47,6 +47,7 @@ describe('fetchLegacyAlerts', () => {
message,
metadata,
nodes,
+ nodeName: '',
prefix,
},
]);
diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_legacy_alerts.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_legacy_alerts.ts
index fbf7608a737ba..0ea37b4ac4daa 100644
--- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_legacy_alerts.ts
+++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_legacy_alerts.ts
@@ -86,6 +86,7 @@ export async function fetchLegacyAlerts(
message: get(hit, '_source.message'),
resolved_timestamp: get(hit, '_source.resolved_timestamp'),
nodes: get(hit, '_source.nodes'),
+ nodeName: '', // This is set by BaseAlert
metadata: get(hit, '_source.metadata') as LegacyAlertMetadata,
};
return legacyAlert;
diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/__test__/__snapshots__/handle_response.test.js.snap b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/__test__/__snapshots__/handle_response.test.js.snap
index 947db23377e4c..38748af753d31 100644
--- a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/__test__/__snapshots__/handle_response.test.js.snap
+++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/__test__/__snapshots__/handle_response.test.js.snap
@@ -11,6 +11,7 @@ Array [
"shardCount": 6,
"transport_address": "127.0.0.1:9300",
"type": "node",
+ "uuid": "_x_V2YzPQU-a9KRRBxUxZQ",
},
Object {
"isOnline": false,
@@ -21,6 +22,7 @@ Array [
"shardCount": 6,
"transport_address": "127.0.0.1:9301",
"type": "node",
+ "uuid": "DAiX7fFjS3Wii7g2HYKrOg",
},
]
`;
@@ -156,6 +158,7 @@ Array [
"shardCount": 0,
"transport_address": "127.0.0.1:9300",
"type": "master",
+ "uuid": "_x_V2YzPQU-a9KRRBxUxZQ",
},
Object {
"isOnline": true,
@@ -265,6 +268,7 @@ Array [
"shardCount": 0,
"transport_address": "127.0.0.1:9301",
"type": "node",
+ "uuid": "DAiX7fFjS3Wii7g2HYKrOg",
},
]
`;
@@ -286,6 +290,7 @@ Array [
"shardCount": 6,
"transport_address": "127.0.0.1:9300",
"type": "master",
+ "uuid": "_x_V2YzPQU-a9KRRBxUxZQ",
},
Object {
"isOnline": true,
@@ -302,6 +307,7 @@ Array [
"shardCount": 6,
"transport_address": "127.0.0.1:9301",
"type": "node",
+ "uuid": "DAiX7fFjS3Wii7g2HYKrOg",
},
]
`;
@@ -435,6 +441,7 @@ Array [
"shardCount": 6,
"transport_address": "127.0.0.1:9300",
"type": "master",
+ "uuid": "_x_V2YzPQU-a9KRRBxUxZQ",
},
Object {
"isOnline": true,
@@ -544,6 +551,7 @@ Array [
"shardCount": 6,
"transport_address": "127.0.0.1:9301",
"type": "node",
+ "uuid": "DAiX7fFjS3Wii7g2HYKrOg",
},
]
`;
diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_nodes.js b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_nodes.js
index 3766845d39b4f..ac4fcea6150a0 100644
--- a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_nodes.js
+++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_nodes.js
@@ -25,8 +25,9 @@ import { LISTING_METRICS_NAMES, LISTING_METRICS_PATHS } from './nodes_listing_me
*
* @param {Object} req: server request object
* @param {String} esIndexPattern: index pattern for elasticsearch data in monitoring indices
+ * @param {Object} pageOfNodes: server-side paginated current page of ES nodes
* @param {Object} clusterStats: cluster stats from cluster state document
- * @param {Object} shardStats: per-node information about shards
+ * @param {Object} nodesShardCount: per-node information about shards
* @return {Array} node info combined with metrics for each node from handle_response
*/
export async function getNodes(req, esIndexPattern, pageOfNodes, clusterStats, nodesShardCount) {
diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/handle_response.js b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/handle_response.js
index 62cf138c99506..3f82e8ec3e646 100644
--- a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/handle_response.js
+++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/handle_response.js
@@ -47,6 +47,7 @@ export function handleResponse(
// nodesInfo is the source of truth for the nodeIds, where nodesMetrics will lack metrics for offline nodes
const nodes = pageOfNodes.map((node) => ({
+ ...node,
...nodesInfo[node.uuid],
...nodesMetrics[node.uuid],
resolver: node.uuid,
diff --git a/x-pack/plugins/monitoring/server/lib/pagination/filter.js b/x-pack/plugins/monitoring/server/lib/pagination/filter.js
index 7592f2bb3afde..e906081a8eb5a 100644
--- a/x-pack/plugins/monitoring/server/lib/pagination/filter.js
+++ b/x-pack/plugins/monitoring/server/lib/pagination/filter.js
@@ -15,7 +15,7 @@ function defaultFilterFn(value, query) {
export function filter(data, queryText, fields, filterFn = defaultFilterFn) {
return data.filter((item) => {
for (const field of fields) {
- if (filterFn(get(item, field), queryText)) {
+ if (filterFn(get(item, field, ''), queryText)) {
return true;
}
}
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index 86b006ce7cba3..afde4f99a08f0 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -13577,8 +13577,6 @@
"xpack.monitoring.alerts.actionVariables.internalShortMessage": "内部メッセージ(省略あり)はElasticで生成されました。",
"xpack.monitoring.alerts.actionVariables.state": "現在のアラートの状態。",
"xpack.monitoring.alerts.badge.panelTitle": "アラート",
- "xpack.monitoring.alerts.callout.dangerLabel": "危険アラート",
- "xpack.monitoring.alerts.callout.warningLabel": "警告アラート",
"xpack.monitoring.alerts.clusterHealth.action.danger": "見つからないプライマリおよびレプリカシャードを割り当てます。",
"xpack.monitoring.alerts.clusterHealth.action.warning": "見つからないレプリカシャードを割り当てます。",
"xpack.monitoring.alerts.clusterHealth.actionVariables.clusterHealth": "クラスターの正常性。",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index 24da57edffff7..6d2082326b5c7 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -13594,8 +13594,6 @@
"xpack.monitoring.alerts.actionVariables.internalShortMessage": "Elastic 生成的简短内部消息。",
"xpack.monitoring.alerts.actionVariables.state": "告警的当前状态。",
"xpack.monitoring.alerts.badge.panelTitle": "告警",
- "xpack.monitoring.alerts.callout.dangerLabel": "危险告警",
- "xpack.monitoring.alerts.callout.warningLabel": "警告告警",
"xpack.monitoring.alerts.clusterHealth.action.danger": "分配缺失的主分片和副本分片。",
"xpack.monitoring.alerts.clusterHealth.action.warning": "分配缺失的副本分片。",
"xpack.monitoring.alerts.clusterHealth.actionVariables.clusterHealth": "集群的运行状况。",
diff --git a/x-pack/test/api_integration/apis/monitoring/elasticsearch/fixtures/nodes_listing_cgroup.json b/x-pack/test/api_integration/apis/monitoring/elasticsearch/fixtures/nodes_listing_cgroup.json
index b9c0a32beba58..6833e1d3d9bc4 100644
--- a/x-pack/test/api_integration/apis/monitoring/elasticsearch/fixtures/nodes_listing_cgroup.json
+++ b/x-pack/test/api_integration/apis/monitoring/elasticsearch/fixtures/nodes_listing_cgroup.json
@@ -139,7 +139,8 @@
"slope": -1
}
},
- "resolver": "_x_V2YzPQU-a9KRRBxUxZQ"
+ "resolver": "_x_V2YzPQU-a9KRRBxUxZQ",
+ "uuid": "_x_V2YzPQU-a9KRRBxUxZQ"
},
{
"name": "hello02",
@@ -247,7 +248,8 @@
"slope": -1
}
},
- "resolver": "DAiX7fFjS3Wii7g2HYKrOg"
+ "resolver": "DAiX7fFjS3Wii7g2HYKrOg",
+ "uuid": "DAiX7fFjS3Wii7g2HYKrOg"
}
],
"totalNodeCount": 2
diff --git a/x-pack/test/api_integration/apis/monitoring/elasticsearch/fixtures/nodes_listing_green.json b/x-pack/test/api_integration/apis/monitoring/elasticsearch/fixtures/nodes_listing_green.json
index 0a18664faf445..ef9fb46909715 100644
--- a/x-pack/test/api_integration/apis/monitoring/elasticsearch/fixtures/nodes_listing_green.json
+++ b/x-pack/test/api_integration/apis/monitoring/elasticsearch/fixtures/nodes_listing_green.json
@@ -97,6 +97,7 @@
}
},
"resolver": "ENVgDIKRSdCVJo-YqY4kUQ",
+ "uuid": "ENVgDIKRSdCVJo-YqY4kUQ",
"shardCount": 54,
"transport_address": "127.0.0.1:9300",
"type": "master"
@@ -185,6 +186,7 @@
}
},
"resolver": "t9J9jvHpQ2yDw9c1LJ0tHA",
+ "uuid": "t9J9jvHpQ2yDw9c1LJ0tHA",
"shardCount": 54,
"transport_address": "127.0.0.1:9301",
"type": "node"
diff --git a/x-pack/test/api_integration/apis/monitoring/elasticsearch/fixtures/nodes_listing_red.json b/x-pack/test/api_integration/apis/monitoring/elasticsearch/fixtures/nodes_listing_red.json
index d9c04838fab10..f2e129cca32d1 100644
--- a/x-pack/test/api_integration/apis/monitoring/elasticsearch/fixtures/nodes_listing_red.json
+++ b/x-pack/test/api_integration/apis/monitoring/elasticsearch/fixtures/nodes_listing_red.json
@@ -97,6 +97,7 @@
}
},
"resolver": "_WmX0plYQwm2z6tfCPyCQw",
+ "uuid": "_WmX0plYQwm2z6tfCPyCQw",
"shardCount": 23,
"transport_address": "127.0.0.1:9300",
"type": "master"
@@ -107,6 +108,7 @@
"nodeTypeClass": "storage",
"nodeTypeLabel": "Node",
"resolver": "1jxg5T33TWub-jJL4qP0Wg",
+ "uuid": "1jxg5T33TWub-jJL4qP0Wg",
"shardCount": 0,
"transport_address": "127.0.0.1:9302",
"type": "node"