From 82f03282325367c075495baf9811694ced22ebe5 Mon Sep 17 00:00:00 2001 From: Chris Roberson Date: Tue, 9 Feb 2021 18:59:47 -0500 Subject: [PATCH 1/3] Prefix with / (#90836) --- .../server/es_client/monitoring_endpoint_disable_watches.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/monitoring/server/es_client/monitoring_endpoint_disable_watches.ts b/x-pack/plugins/monitoring/server/es_client/monitoring_endpoint_disable_watches.ts index 454379d17848..8e9e8d916e49 100644 --- a/x-pack/plugins/monitoring/server/es_client/monitoring_endpoint_disable_watches.ts +++ b/x-pack/plugins/monitoring/server/es_client/monitoring_endpoint_disable_watches.ts @@ -13,7 +13,7 @@ export function monitoringEndpointDisableWatches(Client: any, _config: any, comp params: {}, urls: [ { - fmt: '_monitoring/migrate/alerts', + fmt: '/_monitoring/migrate/alerts', }, ], method: 'POST', From 4bab95229d91bcc416cd2f719e1301d7f7522496 Mon Sep 17 00:00:00 2001 From: Spencer Date: Tue, 9 Feb 2021 16:09:02 -0800 Subject: [PATCH 2/3] [dev-utils/ci-stats] support disabling ship errors (#90851) Co-authored-by: spalger --- .ci/Jenkinsfile_security_cypress | 3 ++- .ci/es-snapshots/Jenkinsfile_verify_es | 5 ++++- .../src/ci_stats_reporter/ship_ci_stats_cli.ts | 12 ++++++++++-- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/.ci/Jenkinsfile_security_cypress b/.ci/Jenkinsfile_security_cypress index d7f702a56563..f7b02cd1c4ab 100644 --- a/.ci/Jenkinsfile_security_cypress +++ b/.ci/Jenkinsfile_security_cypress @@ -10,7 +10,8 @@ kibanaPipeline(timeoutMinutes: 180) { ) { catchError { withEnv([ - 'CI_PARALLEL_PROCESS_NUMBER=1' + 'CI_PARALLEL_PROCESS_NUMBER=1', + 'IGNORE_SHIP_CI_STATS_ERROR=true', ]) { def job = 'xpack-securityCypress' diff --git a/.ci/es-snapshots/Jenkinsfile_verify_es b/.ci/es-snapshots/Jenkinsfile_verify_es index b40cd91a45c5..736a71b73d14 100644 --- a/.ci/es-snapshots/Jenkinsfile_verify_es +++ b/.ci/es-snapshots/Jenkinsfile_verify_es @@ -26,7 +26,10 @@ kibanaPipeline(timeoutMinutes: 150) { message: "[${SNAPSHOT_VERSION}] ES Snapshot Verification Failure", ) { retryable.enable(2) - withEnv(["ES_SNAPSHOT_MANIFEST=${SNAPSHOT_MANIFEST}"]) { + withEnv([ + "ES_SNAPSHOT_MANIFEST=${SNAPSHOT_MANIFEST}", + 'IGNORE_SHIP_CI_STATS_ERROR=true', + ]) { parallel([ 'kibana-intake-agent': workers.intake('kibana-intake', './test/scripts/jenkins_unit.sh'), 'kibana-oss-agent': workers.functional('kibana-oss-tests', { kibanaPipeline.buildOss() }, [ diff --git a/packages/kbn-dev-utils/src/ci_stats_reporter/ship_ci_stats_cli.ts b/packages/kbn-dev-utils/src/ci_stats_reporter/ship_ci_stats_cli.ts index 1ee78518bb80..4d07b54b8cf0 100644 --- a/packages/kbn-dev-utils/src/ci_stats_reporter/ship_ci_stats_cli.ts +++ b/packages/kbn-dev-utils/src/ci_stats_reporter/ship_ci_stats_cli.ts @@ -22,10 +22,18 @@ export function shipCiStatsCli() { throw createFlagError('expected --metrics to be a string'); } + const maybeFail = (message: string) => { + const error = createFailError(message); + if (process.env.IGNORE_SHIP_CI_STATS_ERROR === 'true') { + error.exitCode = 0; + } + return error; + }; + const reporter = CiStatsReporter.fromEnv(log); if (!reporter.isEnabled()) { - throw createFailError('unable to initilize the CI Stats reporter'); + throw maybeFail('unable to initilize the CI Stats reporter'); } for (const path of metricPaths) { @@ -35,7 +43,7 @@ export function shipCiStatsCli() { if (await reporter.metrics(JSON.parse(json))) { log.success('shipped metrics from', path); } else { - throw createFailError('failed to ship metrics'); + throw maybeFail('failed to ship metrics'); } } }, From 8166becc5555f132636bc1e8662370d1b4bf7b6a Mon Sep 17 00:00:00 2001 From: spalger Date: Tue, 9 Feb 2021 17:15:12 -0700 Subject: [PATCH 3/3] Revert "[Metrics UI] Add Metrics Anomaly Alert Type (#89244)" This reverts commit 0d94968df1b7e9d3fc3d24abcc7797ba70983090. --- .../infra/common/alerting/metrics/types.ts | 43 +-- .../infra/common/infra_ml/anomaly_results.ts | 56 ++- .../common/components/alert_preview.tsx | 3 +- .../common/components/get_alert_preview.ts | 4 +- .../components/metrics_alert_dropdown.tsx | 151 --------- .../inventory/components/alert_dropdown.tsx | 59 ++++ .../inventory/components/alert_flyout.tsx | 18 +- .../inventory/components/node_type.tsx | 2 +- .../components/alert_flyout.tsx | 53 --- .../components/expression.test.tsx | 74 ---- .../metric_anomaly/components/expression.tsx | 320 ------------------ .../components/influencer_filter.tsx | 193 ----------- .../metric_anomaly/components/node_type.tsx | 117 ------- .../components/severity_threshold.tsx | 140 -------- .../metric_anomaly/components/validation.tsx | 35 -- .../public/alerting/metric_anomaly/index.ts | 46 --- .../components/alert_dropdown.tsx | 57 ++++ .../components/alert_flyout.tsx | 11 +- .../logging/log_analysis_setup/index.ts | 1 + .../subscription_splash_content.tsx | 176 ++++++++++ .../source_configuration_settings.tsx | 4 +- .../containers/ml/infra_ml_capabilities.tsx | 4 +- .../containers/with_kuery_autocompletion.tsx | 11 +- .../log_entry_categories/page_content.tsx | 2 +- .../logs/log_entry_rate/page_content.tsx | 2 +- .../infra/public/pages/metrics/index.tsx | 8 +- ...lyout.tsx => anomoly_detection_flyout.tsx} | 0 .../ml/anomaly_detection/flyout_home.tsx | 6 +- .../subscription_splash_content.tsx | 110 +++--- .../metrics_explorer/components/kuery_bar.tsx | 21 +- x-pack/plugins/infra/public/plugin.ts | 2 - x-pack/plugins/infra/public/types.ts | 3 +- .../metric_anomaly/evaluate_condition.ts | 51 --- .../metric_anomaly/metric_anomaly_executor.ts | 142 -------- .../preview_metric_anomaly_alert.ts | 120 ------- .../register_metric_anomaly_alert_type.ts | 110 ------ .../lib/alerting/register_alert_types.ts | 10 +- .../infra/server/lib/infra_ml/common.ts | 17 - .../infra/server/lib/infra_ml/index.ts | 1 - .../lib/infra_ml/metrics_hosts_anomalies.ts | 39 ++- .../lib/infra_ml/metrics_k8s_anomalies.ts | 39 ++- .../server/lib/infra_ml/queries/common.ts | 32 -- .../queries/metrics_hosts_anomalies.ts | 12 +- .../infra_ml/queries/metrics_k8s_anomalies.ts | 12 +- x-pack/plugins/infra/server/plugin.ts | 2 +- .../infra/server/routes/alerting/preview.ts | 51 +-- .../results/metrics_hosts_anomalies.ts | 2 +- .../infra_ml/results/metrics_k8s_anomalies.ts | 2 +- .../translations/translations/ja-JP.json | 11 + .../translations/translations/zh-CN.json | 11 + 50 files changed, 479 insertions(+), 1917 deletions(-) delete mode 100644 x-pack/plugins/infra/public/alerting/common/components/metrics_alert_dropdown.tsx create mode 100644 x-pack/plugins/infra/public/alerting/inventory/components/alert_dropdown.tsx delete mode 100644 x-pack/plugins/infra/public/alerting/metric_anomaly/components/alert_flyout.tsx delete mode 100644 x-pack/plugins/infra/public/alerting/metric_anomaly/components/expression.test.tsx delete mode 100644 x-pack/plugins/infra/public/alerting/metric_anomaly/components/expression.tsx delete mode 100644 x-pack/plugins/infra/public/alerting/metric_anomaly/components/influencer_filter.tsx delete mode 100644 x-pack/plugins/infra/public/alerting/metric_anomaly/components/node_type.tsx delete mode 100644 x-pack/plugins/infra/public/alerting/metric_anomaly/components/severity_threshold.tsx delete mode 100644 x-pack/plugins/infra/public/alerting/metric_anomaly/components/validation.tsx delete mode 100644 x-pack/plugins/infra/public/alerting/metric_anomaly/index.ts create mode 100644 x-pack/plugins/infra/public/alerting/metric_threshold/components/alert_dropdown.tsx create mode 100644 x-pack/plugins/infra/public/components/logging/log_analysis_setup/subscription_splash_content.tsx rename x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/{anomaly_detection_flyout.tsx => anomoly_detection_flyout.tsx} (100%) rename x-pack/plugins/infra/public/{components => pages/metrics/inventory_view/components/ml/anomaly_detection}/subscription_splash_content.tsx (58%) delete mode 100644 x-pack/plugins/infra/server/lib/alerting/metric_anomaly/evaluate_condition.ts delete mode 100644 x-pack/plugins/infra/server/lib/alerting/metric_anomaly/metric_anomaly_executor.ts delete mode 100644 x-pack/plugins/infra/server/lib/alerting/metric_anomaly/preview_metric_anomaly_alert.ts delete mode 100644 x-pack/plugins/infra/server/lib/alerting/metric_anomaly/register_metric_anomaly_alert_type.ts diff --git a/x-pack/plugins/infra/common/alerting/metrics/types.ts b/x-pack/plugins/infra/common/alerting/metrics/types.ts index 7a4edb8f4918..a89f82e931fd 100644 --- a/x-pack/plugins/infra/common/alerting/metrics/types.ts +++ b/x-pack/plugins/infra/common/alerting/metrics/types.ts @@ -4,15 +4,14 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ + import * as rt from 'io-ts'; -import { ANOMALY_THRESHOLD } from '../../infra_ml'; import { ItemTypeRT } from '../../inventory_models/types'; // TODO: Have threshold and inventory alerts import these types from this file instead of from their // local directories export const METRIC_THRESHOLD_ALERT_TYPE_ID = 'metrics.alert.threshold'; export const METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID = 'metrics.alert.inventory.threshold'; -export const METRIC_ANOMALY_ALERT_TYPE_ID = 'metrics.alert.anomaly'; export enum Comparator { GT = '>', @@ -35,26 +34,6 @@ export enum Aggregators { P99 = 'p99', } -const metricAnomalyNodeTypeRT = rt.union([rt.literal('hosts'), rt.literal('k8s')]); -const metricAnomalyMetricRT = rt.union([ - rt.literal('memory_usage'), - rt.literal('network_in'), - rt.literal('network_out'), -]); -const metricAnomalyInfluencerFilterRT = rt.type({ - fieldName: rt.string, - fieldValue: rt.string, -}); - -export interface MetricAnomalyParams { - nodeType: rt.TypeOf; - metric: rt.TypeOf; - alertInterval?: string; - sourceId?: string; - threshold: Exclude; - influencerFilter: rt.TypeOf | undefined; -} - // Alert Preview API const baseAlertRequestParamsRT = rt.intersection([ rt.partial({ @@ -72,6 +51,7 @@ const baseAlertRequestParamsRT = rt.intersection([ rt.literal('M'), rt.literal('y'), ]), + criteria: rt.array(rt.any), alertInterval: rt.string, alertThrottle: rt.string, alertOnNoData: rt.boolean, @@ -85,7 +65,6 @@ const metricThresholdAlertPreviewRequestParamsRT = rt.intersection([ }), rt.type({ alertType: rt.literal(METRIC_THRESHOLD_ALERT_TYPE_ID), - criteria: rt.array(rt.any), }), ]); export type MetricThresholdAlertPreviewRequestParams = rt.TypeOf< @@ -97,33 +76,15 @@ const inventoryAlertPreviewRequestParamsRT = rt.intersection([ rt.type({ nodeType: ItemTypeRT, alertType: rt.literal(METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID), - criteria: rt.array(rt.any), }), ]); export type InventoryAlertPreviewRequestParams = rt.TypeOf< typeof inventoryAlertPreviewRequestParamsRT >; -const metricAnomalyAlertPreviewRequestParamsRT = rt.intersection([ - baseAlertRequestParamsRT, - rt.type({ - nodeType: metricAnomalyNodeTypeRT, - metric: metricAnomalyMetricRT, - threshold: rt.number, - alertType: rt.literal(METRIC_ANOMALY_ALERT_TYPE_ID), - }), - rt.partial({ - influencerFilter: metricAnomalyInfluencerFilterRT, - }), -]); -export type MetricAnomalyAlertPreviewRequestParams = rt.TypeOf< - typeof metricAnomalyAlertPreviewRequestParamsRT ->; - export const alertPreviewRequestParamsRT = rt.union([ metricThresholdAlertPreviewRequestParamsRT, inventoryAlertPreviewRequestParamsRT, - metricAnomalyAlertPreviewRequestParamsRT, ]); export type AlertPreviewRequestParams = rt.TypeOf; diff --git a/x-pack/plugins/infra/common/infra_ml/anomaly_results.ts b/x-pack/plugins/infra/common/infra_ml/anomaly_results.ts index 81e46d85ba22..589e57a1388b 100644 --- a/x-pack/plugins/infra/common/infra_ml/anomaly_results.ts +++ b/x-pack/plugins/infra/common/infra_ml/anomaly_results.ts @@ -5,44 +5,36 @@ * 2.0. */ -export enum ANOMALY_SEVERITY { - CRITICAL = 'critical', - MAJOR = 'major', - MINOR = 'minor', - WARNING = 'warning', - LOW = 'low', - UNKNOWN = 'unknown', -} +export const ML_SEVERITY_SCORES = { + warning: 3, + minor: 25, + major: 50, + critical: 75, +}; -export enum ANOMALY_THRESHOLD { - CRITICAL = 75, - MAJOR = 50, - MINOR = 25, - WARNING = 3, - LOW = 0, -} +export type MLSeverityScoreCategories = keyof typeof ML_SEVERITY_SCORES; -export const SEVERITY_COLORS = { - CRITICAL: '#fe5050', - MAJOR: '#fba740', - MINOR: '#fdec25', - WARNING: '#8bc8fb', - LOW: '#d2e9f7', - BLANK: '#ffffff', +export const ML_SEVERITY_COLORS = { + critical: 'rgb(228, 72, 72)', + major: 'rgb(229, 113, 0)', + minor: 'rgb(255, 221, 0)', + warning: 'rgb(125, 180, 226)', }; -export const getSeverityCategoryForScore = (score: number): ANOMALY_SEVERITY | undefined => { - if (score >= ANOMALY_THRESHOLD.CRITICAL) { - return ANOMALY_SEVERITY.CRITICAL; - } else if (score >= ANOMALY_THRESHOLD.MAJOR) { - return ANOMALY_SEVERITY.MAJOR; - } else if (score >= ANOMALY_THRESHOLD.MINOR) { - return ANOMALY_SEVERITY.MINOR; - } else if (score >= ANOMALY_THRESHOLD.WARNING) { - return ANOMALY_SEVERITY.WARNING; +export const getSeverityCategoryForScore = ( + score: number +): MLSeverityScoreCategories | undefined => { + if (score >= ML_SEVERITY_SCORES.critical) { + return 'critical'; + } else if (score >= ML_SEVERITY_SCORES.major) { + return 'major'; + } else if (score >= ML_SEVERITY_SCORES.minor) { + return 'minor'; + } else if (score >= ML_SEVERITY_SCORES.warning) { + return 'warning'; } else { // Category is too low to include - return ANOMALY_SEVERITY.LOW; + return undefined; } }; diff --git a/x-pack/plugins/infra/public/alerting/common/components/alert_preview.tsx b/x-pack/plugins/infra/public/alerting/common/components/alert_preview.tsx index 0135e185e846..fac87e20dfe7 100644 --- a/x-pack/plugins/infra/public/alerting/common/components/alert_preview.tsx +++ b/x-pack/plugins/infra/public/alerting/common/components/alert_preview.tsx @@ -37,7 +37,7 @@ interface Props { alertInterval: string; alertThrottle: string; alertType: PreviewableAlertTypes; - alertParams: { criteria?: any[]; sourceId: string } & Record; + alertParams: { criteria: any[]; sourceId: string } & Record; validate: (params: any) => ValidationResult; showNoDataResults?: boolean; groupByDisplayName?: string; @@ -109,7 +109,6 @@ export const AlertPreview: React.FC = (props) => { }, [previewLookbackInterval, alertInterval]); const isPreviewDisabled = useMemo(() => { - if (!alertParams.criteria) return false; const validationResult = validate({ criteria: alertParams.criteria } as any); const hasValidationErrors = Object.values(validationResult.errors).some((result) => Object.values(result).some((arr) => Array.isArray(arr) && arr.length) diff --git a/x-pack/plugins/infra/public/alerting/common/components/get_alert_preview.ts b/x-pack/plugins/infra/public/alerting/common/components/get_alert_preview.ts index 2bb98e83cbe7..a1cee1361a18 100644 --- a/x-pack/plugins/infra/public/alerting/common/components/get_alert_preview.ts +++ b/x-pack/plugins/infra/public/alerting/common/components/get_alert_preview.ts @@ -10,15 +10,13 @@ import { INFRA_ALERT_PREVIEW_PATH, METRIC_THRESHOLD_ALERT_TYPE_ID, METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID, - METRIC_ANOMALY_ALERT_TYPE_ID, AlertPreviewRequestParams, AlertPreviewSuccessResponsePayload, } from '../../../../common/alerting/metrics'; export type PreviewableAlertTypes = | typeof METRIC_THRESHOLD_ALERT_TYPE_ID - | typeof METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID - | typeof METRIC_ANOMALY_ALERT_TYPE_ID; + | typeof METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID; export async function getAlertPreview({ fetch, diff --git a/x-pack/plugins/infra/public/alerting/common/components/metrics_alert_dropdown.tsx b/x-pack/plugins/infra/public/alerting/common/components/metrics_alert_dropdown.tsx deleted file mode 100644 index f1236c4fc2c2..000000000000 --- a/x-pack/plugins/infra/public/alerting/common/components/metrics_alert_dropdown.tsx +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { i18n } from '@kbn/i18n'; -import React, { useState, useCallback, useMemo } from 'react'; -import { - EuiPopover, - EuiButtonEmpty, - EuiContextMenu, - EuiContextMenuPanelDescriptor, -} from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { useInfraMLCapabilities } from '../../../containers/ml/infra_ml_capabilities'; -import { PrefilledInventoryAlertFlyout } from '../../inventory/components/alert_flyout'; -import { PrefilledThresholdAlertFlyout } from '../../metric_threshold/components/alert_flyout'; -import { PrefilledAnomalyAlertFlyout } from '../../metric_anomaly/components/alert_flyout'; -import { useLinkProps } from '../../../hooks/use_link_props'; - -type VisibleFlyoutType = 'inventory' | 'threshold' | 'anomaly' | null; - -export const MetricsAlertDropdown = () => { - const [popoverOpen, setPopoverOpen] = useState(false); - const [visibleFlyoutType, setVisibleFlyoutType] = useState(null); - const { hasInfraMLCapabilities } = useInfraMLCapabilities(); - - const closeFlyout = useCallback(() => setVisibleFlyoutType(null), [setVisibleFlyoutType]); - - const manageAlertsLinkProps = useLinkProps({ - app: 'management', - pathname: '/insightsAndAlerting/triggersActions/alerts', - }); - - const panels: EuiContextMenuPanelDescriptor[] = useMemo( - () => [ - { - id: 0, - title: i18n.translate('xpack.infra.alerting.alertDropdownTitle', { - defaultMessage: 'Alerts', - }), - items: [ - { - name: i18n.translate('xpack.infra.alerting.infrastructureDropdownMenu', { - defaultMessage: 'Infrastructure', - }), - panel: 1, - }, - { - name: i18n.translate('xpack.infra.alerting.metricsDropdownMenu', { - defaultMessage: 'Metrics', - }), - panel: 2, - }, - { - name: i18n.translate('xpack.infra.alerting.manageAlerts', { - defaultMessage: 'Manage alerts', - }), - icon: 'tableOfContents', - onClick: manageAlertsLinkProps.onClick, - }, - ], - }, - { - id: 1, - title: i18n.translate('xpack.infra.alerting.infrastructureDropdownTitle', { - defaultMessage: 'Infrastructure alerts', - }), - items: [ - { - name: i18n.translate('xpack.infra.alerting.createInventoryAlertButton', { - defaultMessage: 'Create inventory alert', - }), - onClick: () => setVisibleFlyoutType('inventory'), - }, - ].concat( - hasInfraMLCapabilities - ? { - name: i18n.translate('xpack.infra.alerting.createAnomalyAlertButton', { - defaultMessage: 'Create anomaly alert', - }), - onClick: () => setVisibleFlyoutType('anomaly'), - } - : [] - ), - }, - { - id: 2, - title: i18n.translate('xpack.infra.alerting.metricsDropdownTitle', { - defaultMessage: 'Metrics alerts', - }), - items: [ - { - name: i18n.translate('xpack.infra.alerting.createThresholdAlertButton', { - defaultMessage: 'Create threshold alert', - }), - onClick: () => setVisibleFlyoutType('threshold'), - }, - ], - }, - ], - [manageAlertsLinkProps, setVisibleFlyoutType, hasInfraMLCapabilities] - ); - - const closePopover = useCallback(() => { - setPopoverOpen(false); - }, [setPopoverOpen]); - - const openPopover = useCallback(() => { - setPopoverOpen(true); - }, [setPopoverOpen]); - - return ( - <> - - - - } - isOpen={popoverOpen} - closePopover={closePopover} - > - - - - - ); -}; - -interface AlertFlyoutProps { - visibleFlyoutType: VisibleFlyoutType; - onClose(): void; -} - -const AlertFlyout = ({ visibleFlyoutType, onClose }: AlertFlyoutProps) => { - switch (visibleFlyoutType) { - case 'inventory': - return ; - case 'threshold': - return ; - case 'anomaly': - return ; - default: - return null; - } -}; diff --git a/x-pack/plugins/infra/public/alerting/inventory/components/alert_dropdown.tsx b/x-pack/plugins/infra/public/alerting/inventory/components/alert_dropdown.tsx new file mode 100644 index 000000000000..a7b6c9fb7104 --- /dev/null +++ b/x-pack/plugins/infra/public/alerting/inventory/components/alert_dropdown.tsx @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useState, useCallback } from 'react'; +import { EuiPopover, EuiButtonEmpty, EuiContextMenuItem, EuiContextMenuPanel } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { useAlertPrefillContext } from '../../../alerting/use_alert_prefill'; +import { AlertFlyout } from './alert_flyout'; +import { ManageAlertsContextMenuItem } from './manage_alerts_context_menu_item'; + +export const InventoryAlertDropdown = () => { + const [popoverOpen, setPopoverOpen] = useState(false); + const [flyoutVisible, setFlyoutVisible] = useState(false); + + const { inventoryPrefill } = useAlertPrefillContext(); + const { nodeType, metric, filterQuery } = inventoryPrefill; + + const closePopover = useCallback(() => { + setPopoverOpen(false); + }, [setPopoverOpen]); + + const openPopover = useCallback(() => { + setPopoverOpen(true); + }, [setPopoverOpen]); + + const menuItems = [ + setFlyoutVisible(true)}> + + , + , + ]; + + return ( + <> + + + + } + isOpen={popoverOpen} + closePopover={closePopover} + > + + + + + ); +}; diff --git a/x-pack/plugins/infra/public/alerting/inventory/components/alert_flyout.tsx b/x-pack/plugins/infra/public/alerting/inventory/components/alert_flyout.tsx index 33fe3c7af30c..815e1f2be33f 100644 --- a/x-pack/plugins/infra/public/alerting/inventory/components/alert_flyout.tsx +++ b/x-pack/plugins/infra/public/alerting/inventory/components/alert_flyout.tsx @@ -8,7 +8,8 @@ import React, { useCallback, useContext, useMemo } from 'react'; import { TriggerActionsContext } from '../../../utils/triggers_actions_context'; -import { METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID } from '../../../../common/alerting/metrics'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID } from '../../../../server/lib/alerting/inventory_metric_threshold/types'; import { InfraWaffleMapOptions } from '../../../lib/lib'; import { InventoryItemType } from '../../../../common/inventory_models/types'; import { useAlertPrefillContext } from '../../../alerting/use_alert_prefill'; @@ -48,18 +49,3 @@ export const AlertFlyout = ({ options, nodeType, filter, visible, setVisible }: return <>{visible && AddAlertFlyout}; }; - -export const PrefilledInventoryAlertFlyout = ({ onClose }: { onClose(): void }) => { - const { inventoryPrefill } = useAlertPrefillContext(); - const { nodeType, metric, filterQuery } = inventoryPrefill; - - return ( - - ); -}; diff --git a/x-pack/plugins/infra/public/alerting/inventory/components/node_type.tsx b/x-pack/plugins/infra/public/alerting/inventory/components/node_type.tsx index bd7812acac67..f02f98c49f01 100644 --- a/x-pack/plugins/infra/public/alerting/inventory/components/node_type.tsx +++ b/x-pack/plugins/infra/public/alerting/inventory/components/node_type.tsx @@ -68,7 +68,7 @@ export const NodeTypeExpression = ({ setAggTypePopoverOpen(false)}> { - const { triggersActionsUI } = useContext(TriggerActionsContext); - - const onCloseFlyout = useCallback(() => setVisible(false), [setVisible]); - const AddAlertFlyout = useMemo( - () => - triggersActionsUI && - triggersActionsUI.getAddAlertFlyout({ - consumer: 'infrastructure', - onClose: onCloseFlyout, - canChangeTrigger: false, - alertTypeId: METRIC_ANOMALY_ALERT_TYPE_ID, - metadata: { - metric, - nodeType, - }, - }), - // eslint-disable-next-line react-hooks/exhaustive-deps - [triggersActionsUI, visible] - ); - - return <>{visible && AddAlertFlyout}; -}; - -export const PrefilledAnomalyAlertFlyout = ({ onClose }: { onClose(): void }) => { - const { inventoryPrefill } = useAlertPrefillContext(); - const { nodeType, metric } = inventoryPrefill; - - return ; -}; diff --git a/x-pack/plugins/infra/public/alerting/metric_anomaly/components/expression.test.tsx b/x-pack/plugins/infra/public/alerting/metric_anomaly/components/expression.test.tsx deleted file mode 100644 index ae2c6ed81bad..000000000000 --- a/x-pack/plugins/infra/public/alerting/metric_anomaly/components/expression.test.tsx +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { mountWithIntl, nextTick } from '@kbn/test/jest'; -// We are using this inside a `jest.mock` call. Jest requires dynamic dependencies to be prefixed with `mock` -import { coreMock as mockCoreMock } from 'src/core/public/mocks'; -import React from 'react'; -import { Expression, AlertContextMeta } from './expression'; -import { act } from 'react-dom/test-utils'; - -jest.mock('../../../containers/source/use_source_via_http', () => ({ - useSourceViaHttp: () => ({ - source: { id: 'default' }, - createDerivedIndexPattern: () => ({ fields: [], title: 'metricbeat-*' }), - }), -})); - -jest.mock('../../../hooks/use_kibana', () => ({ - useKibanaContextForPlugin: () => ({ - services: mockCoreMock.createStart(), - }), -})); - -jest.mock('../../../containers/ml/infra_ml_capabilities', () => ({ - useInfraMLCapabilities: () => ({ - isLoading: false, - hasInfraMLCapabilities: true, - }), -})); - -describe('Expression', () => { - async function setup(currentOptions: AlertContextMeta) { - const alertParams = { - metric: undefined, - nodeType: undefined, - threshold: 50, - }; - const wrapper = mountWithIntl( - Reflect.set(alertParams, key, value)} - setAlertProperty={() => {}} - metadata={currentOptions} - /> - ); - - const update = async () => - await act(async () => { - await nextTick(); - wrapper.update(); - }); - - await update(); - - return { wrapper, update, alertParams }; - } - - it('should prefill the alert using the context metadata', async () => { - const currentOptions = { - nodeType: 'pod', - metric: { type: 'tx' }, - }; - const { alertParams } = await setup(currentOptions as AlertContextMeta); - expect(alertParams.nodeType).toBe('k8s'); - expect(alertParams.metric).toBe('network_out'); - }); -}); diff --git a/x-pack/plugins/infra/public/alerting/metric_anomaly/components/expression.tsx b/x-pack/plugins/infra/public/alerting/metric_anomaly/components/expression.tsx deleted file mode 100644 index 5938c7119616..000000000000 --- a/x-pack/plugins/infra/public/alerting/metric_anomaly/components/expression.tsx +++ /dev/null @@ -1,320 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { pick } from 'lodash'; -import React, { useCallback, useState, useMemo, useEffect } from 'react'; -import { EuiFlexGroup, EuiSpacer, EuiText, EuiLoadingContent } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { i18n } from '@kbn/i18n'; -import { useInfraMLCapabilities } from '../../../containers/ml/infra_ml_capabilities'; -import { SubscriptionSplashContent } from '../../../components/subscription_splash_content'; -import { AlertPreview } from '../../common'; -import { - METRIC_ANOMALY_ALERT_TYPE_ID, - MetricAnomalyParams, -} from '../../../../common/alerting/metrics'; -import { euiStyled, EuiThemeProvider } from '../../../../../../../src/plugins/kibana_react/common'; -import { - WhenExpression, - // eslint-disable-next-line @kbn/eslint/no-restricted-paths -} from '../../../../../triggers_actions_ui/public/common'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { IErrorObject } from '../../../../../triggers_actions_ui/public/types'; -import { useSourceViaHttp } from '../../../containers/source/use_source_via_http'; -import { findInventoryModel } from '../../../../common/inventory_models'; -import { InventoryItemType, SnapshotMetricType } from '../../../../common/inventory_models/types'; -import { NodeTypeExpression } from './node_type'; -import { SeverityThresholdExpression } from './severity_threshold'; -import { InfraWaffleMapOptions } from '../../../lib/lib'; -import { ANOMALY_THRESHOLD } from '../../../../common/infra_ml'; - -import { validateMetricAnomaly } from './validation'; -import { InfluencerFilter } from './influencer_filter'; -import { useKibanaContextForPlugin } from '../../../hooks/use_kibana'; - -export interface AlertContextMeta { - metric?: InfraWaffleMapOptions['metric']; - nodeType?: InventoryItemType; -} - -interface Props { - errors: IErrorObject[]; - alertParams: MetricAnomalyParams & { - sourceId: string; - }; - alertInterval: string; - alertThrottle: string; - setAlertParams(key: string, value: any): void; - setAlertProperty(key: string, value: any): void; - metadata: AlertContextMeta; -} - -export const defaultExpression = { - metric: 'memory_usage' as MetricAnomalyParams['metric'], - threshold: ANOMALY_THRESHOLD.MAJOR, - nodeType: 'hosts', - influencerFilter: undefined, -}; - -export const Expression: React.FC = (props) => { - const { hasInfraMLCapabilities, isLoading: isLoadingMLCapabilities } = useInfraMLCapabilities(); - const { http, notifications } = useKibanaContextForPlugin().services; - const { setAlertParams, alertParams, alertInterval, alertThrottle, metadata } = props; - const { source, createDerivedIndexPattern } = useSourceViaHttp({ - sourceId: 'default', - type: 'metrics', - fetch: http.fetch, - toastWarning: notifications.toasts.addWarning, - }); - - const derivedIndexPattern = useMemo(() => createDerivedIndexPattern('metrics'), [ - createDerivedIndexPattern, - ]); - - const [influencerFieldName, updateInfluencerFieldName] = useState( - alertParams.influencerFilter?.fieldName ?? 'host.name' - ); - - useEffect(() => { - setAlertParams('hasInfraMLCapabilities', hasInfraMLCapabilities); - }, [setAlertParams, hasInfraMLCapabilities]); - - useEffect(() => { - if (alertParams.influencerFilter) { - setAlertParams('influencerFilter', { - ...alertParams.influencerFilter, - fieldName: influencerFieldName, - }); - } - }, [influencerFieldName, alertParams, setAlertParams]); - const updateInfluencerFieldValue = useCallback( - (value: string) => { - if (value) { - setAlertParams('influencerFilter', { - ...alertParams.influencerFilter, - fieldValue: value, - }); - } else { - setAlertParams('influencerFilter', undefined); - } - }, - [setAlertParams, alertParams] - ); - - useEffect(() => { - setAlertParams('alertInterval', alertInterval); - }, [setAlertParams, alertInterval]); - - const updateNodeType = useCallback( - (nt: any) => { - setAlertParams('nodeType', nt); - }, - [setAlertParams] - ); - - const updateMetric = useCallback( - (metric: string) => { - setAlertParams('metric', metric); - }, - [setAlertParams] - ); - - const updateSeverityThreshold = useCallback( - (threshold: any) => { - setAlertParams('threshold', threshold); - }, - [setAlertParams] - ); - - const prefillNodeType = useCallback(() => { - const md = metadata; - if (md && md.nodeType) { - setAlertParams( - 'nodeType', - getMLNodeTypeFromInventoryNodeType(md.nodeType) ?? defaultExpression.nodeType - ); - } else { - setAlertParams('nodeType', defaultExpression.nodeType); - } - }, [metadata, setAlertParams]); - - const prefillMetric = useCallback(() => { - const md = metadata; - if (md && md.metric) { - setAlertParams( - 'metric', - getMLMetricFromInventoryMetric(md.metric.type) ?? defaultExpression.metric - ); - } else { - setAlertParams('metric', defaultExpression.metric); - } - }, [metadata, setAlertParams]); - - useEffect(() => { - if (!alertParams.nodeType) { - prefillNodeType(); - } - - if (!alertParams.threshold) { - setAlertParams('threshold', defaultExpression.threshold); - } - - if (!alertParams.metric) { - prefillMetric(); - } - - if (!alertParams.sourceId) { - setAlertParams('sourceId', source?.id || 'default'); - } - }, [metadata, derivedIndexPattern, defaultExpression, source]); // eslint-disable-line react-hooks/exhaustive-deps - - if (isLoadingMLCapabilities) return ; - if (!hasInfraMLCapabilities) return ; - - return ( - // https://github.com/elastic/kibana/issues/89506 - - -

- -

-
- - - - - - - - - - - - - - - - - - - -
- ); -}; - -// required for dynamic import -// eslint-disable-next-line import/no-default-export -export default Expression; - -const StyledExpressionRow = euiStyled(EuiFlexGroup)` - display: flex; - flex-wrap: wrap; - margin: 0 -4px; -`; - -const StyledExpression = euiStyled.div` - padding: 0 4px; -`; - -const getDisplayNameForType = (type: InventoryItemType) => { - const inventoryModel = findInventoryModel(type); - return inventoryModel.displayName; -}; - -export const nodeTypes: { [key: string]: any } = { - hosts: { - text: getDisplayNameForType('host'), - value: 'hosts', - }, - k8s: { - text: getDisplayNameForType('pod'), - value: 'k8s', - }, -}; - -const getMLMetricFromInventoryMetric = (metric: SnapshotMetricType) => { - switch (metric) { - case 'memory': - return 'memory_usage'; - case 'tx': - return 'network_out'; - case 'rx': - return 'network_in'; - default: - return null; - } -}; - -const getMLNodeTypeFromInventoryNodeType = (nodeType: InventoryItemType) => { - switch (nodeType) { - case 'host': - return 'hosts'; - case 'pod': - return 'k8s'; - default: - return null; - } -}; diff --git a/x-pack/plugins/infra/public/alerting/metric_anomaly/components/influencer_filter.tsx b/x-pack/plugins/infra/public/alerting/metric_anomaly/components/influencer_filter.tsx deleted file mode 100644 index 34a917a77dcf..000000000000 --- a/x-pack/plugins/infra/public/alerting/metric_anomaly/components/influencer_filter.tsx +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { debounce } from 'lodash'; -import { i18n } from '@kbn/i18n'; -import React, { useState, useCallback, useEffect, useMemo } from 'react'; -import { first } from 'lodash'; -import { EuiFlexGroup, EuiFormRow, EuiCheckbox, EuiFlexItem, EuiSelect } from '@elastic/eui'; -import { - MetricsExplorerKueryBar, - CurryLoadSuggestionsType, -} from '../../../pages/metrics/metrics_explorer/components/kuery_bar'; -import { MetricAnomalyParams } from '../../../../common/alerting/metrics'; - -interface Props { - fieldName: string; - fieldValue: string; - nodeType: MetricAnomalyParams['nodeType']; - onChangeFieldName: (v: string) => void; - onChangeFieldValue: (v: string) => void; - derivedIndexPattern: Parameters[0]['derivedIndexPattern']; -} - -const FILTER_TYPING_DEBOUNCE_MS = 500; - -export const InfluencerFilter = ({ - fieldName, - fieldValue, - nodeType, - onChangeFieldName, - onChangeFieldValue, - derivedIndexPattern, -}: Props) => { - const fieldNameOptions = useMemo(() => (nodeType === 'k8s' ? k8sFieldNames : hostFieldNames), [ - nodeType, - ]); - - // If initial props contain a fieldValue, assume it was passed in from loaded alertParams, - // and enable the UI element - const [isEnabled, updateIsEnabled] = useState(fieldValue ? true : false); - const [storedFieldValue, updateStoredFieldValue] = useState(fieldValue); - - useEffect( - () => - nodeType === 'k8s' - ? onChangeFieldName(first(k8sFieldNames)!.value) - : onChangeFieldName(first(hostFieldNames)!.value), - [nodeType, onChangeFieldName] - ); - - const onSelectFieldName = useCallback((e) => onChangeFieldName(e.target.value), [ - onChangeFieldName, - ]); - const onUpdateFieldValue = useCallback( - (value) => { - updateStoredFieldValue(value); - onChangeFieldValue(value); - }, - [onChangeFieldValue] - ); - - const toggleEnabled = useCallback(() => { - const nextState = !isEnabled; - updateIsEnabled(nextState); - if (!nextState) { - onChangeFieldValue(''); - } else { - onChangeFieldValue(storedFieldValue); - } - }, [isEnabled, updateIsEnabled, onChangeFieldValue, storedFieldValue]); - - /* eslint-disable-next-line react-hooks/exhaustive-deps */ - const debouncedOnUpdateFieldValue = useCallback( - debounce(onUpdateFieldValue, FILTER_TYPING_DEBOUNCE_MS), - [onUpdateFieldValue] - ); - - const affixFieldNameToQuery: CurryLoadSuggestionsType = (fn) => ( - expression, - cursorPosition, - maxSuggestions - ) => { - // Add the field name to the front of the passed-in query - const prefix = `${fieldName}:`; - // Trim whitespace to prevent AND/OR suggestions - const modifiedExpression = `${prefix}${expression}`.trim(); - // Move the cursor position forward by the length of the field name - const modifiedPosition = cursorPosition + prefix.length; - return fn(modifiedExpression, modifiedPosition, maxSuggestions, (suggestions) => - suggestions - .map((s) => ({ - ...s, - // Remove quotes from suggestions - text: s.text.replace(/\"/g, '').trim(), - // Offset the returned suggestions' cursor positions so that they can be autocompleted accurately - start: s.start - prefix.length, - end: s.end - prefix.length, - })) - // Removing quotes can lead to an already-selected suggestion still coming up in the autocomplete list, - // so filter these out - .filter((s) => !expression.startsWith(s.text)) - ); - }; - - return ( - - } - helpText={ - isEnabled ? ( - <> - {i18n.translate('xpack.infra.metrics.alertFlyout.anomalyFilterHelpText', { - defaultMessage: - 'Limit the scope of your alert trigger to anomalies influenced by certain node(s).', - })} -
- {i18n.translate('xpack.infra.metrics.alertFlyout.anomalyFilterHelpTextExample', { - defaultMessage: 'For example: "my-node-1" or "my-node-*"', - })} - - ) : null - } - fullWidth - display="rowCompressed" - > - {isEnabled ? ( - - - - - - - - - ) : ( - <> - )} -
- ); -}; - -const hostFieldNames = [ - { - value: 'host.name', - text: 'host.name', - }, -]; - -const k8sFieldNames = [ - { - value: 'kubernetes.pod.uid', - text: 'kubernetes.pod.uid', - }, - { - value: 'kubernetes.node.name', - text: 'kubernetes.node.name', - }, - { - value: 'kubernetes.namespace', - text: 'kubernetes.namespace', - }, -]; - -const filterByNodeLabel = i18n.translate('xpack.infra.metrics.alertFlyout.filterByNodeLabel', { - defaultMessage: 'Filter by node', -}); diff --git a/x-pack/plugins/infra/public/alerting/metric_anomaly/components/node_type.tsx b/x-pack/plugins/infra/public/alerting/metric_anomaly/components/node_type.tsx deleted file mode 100644 index 6ddcf8fd5cb6..000000000000 --- a/x-pack/plugins/infra/public/alerting/metric_anomaly/components/node_type.tsx +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { useState } from 'react'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiExpression, EuiPopover, EuiFlexGroup, EuiFlexItem, EuiSelect } from '@elastic/eui'; -import { EuiPopoverTitle, EuiButtonIcon } from '@elastic/eui'; -import { MetricAnomalyParams } from '../../../../common/alerting/metrics'; - -type Node = MetricAnomalyParams['nodeType']; - -interface WhenExpressionProps { - value: Node; - options: { [key: string]: { text: string; value: Node } }; - onChange: (value: Node) => void; - popupPosition?: - | 'upCenter' - | 'upLeft' - | 'upRight' - | 'downCenter' - | 'downLeft' - | 'downRight' - | 'leftCenter' - | 'leftUp' - | 'leftDown' - | 'rightCenter' - | 'rightUp' - | 'rightDown'; -} - -export const NodeTypeExpression = ({ - value, - options, - onChange, - popupPosition, -}: WhenExpressionProps) => { - const [aggTypePopoverOpen, setAggTypePopoverOpen] = useState(false); - - return ( - { - setAggTypePopoverOpen(true); - }} - /> - } - isOpen={aggTypePopoverOpen} - closePopover={() => { - setAggTypePopoverOpen(false); - }} - ownFocus - anchorPosition={popupPosition ?? 'downLeft'} - > -
- setAggTypePopoverOpen(false)}> - - - { - onChange(e.target.value as Node); - setAggTypePopoverOpen(false); - }} - options={Object.values(options).map((o) => o)} - /> -
-
- ); -}; - -interface ClosablePopoverTitleProps { - children: JSX.Element; - onClose: () => void; -} - -export const ClosablePopoverTitle = ({ children, onClose }: ClosablePopoverTitleProps) => { - return ( - - - {children} - - onClose()} - /> - - - - ); -}; diff --git a/x-pack/plugins/infra/public/alerting/metric_anomaly/components/severity_threshold.tsx b/x-pack/plugins/infra/public/alerting/metric_anomaly/components/severity_threshold.tsx deleted file mode 100644 index 2dc561ff172b..000000000000 --- a/x-pack/plugins/infra/public/alerting/metric_anomaly/components/severity_threshold.tsx +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { useState } from 'react'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiExpression, EuiPopover, EuiFlexGroup, EuiFlexItem, EuiSelect } from '@elastic/eui'; -import { EuiPopoverTitle, EuiButtonIcon } from '@elastic/eui'; -import { ANOMALY_THRESHOLD } from '../../../../common/infra_ml'; - -interface WhenExpressionProps { - value: Exclude; - onChange: (value: ANOMALY_THRESHOLD) => void; - popupPosition?: - | 'upCenter' - | 'upLeft' - | 'upRight' - | 'downCenter' - | 'downLeft' - | 'downRight' - | 'leftCenter' - | 'leftUp' - | 'leftDown' - | 'rightCenter' - | 'rightUp' - | 'rightDown'; -} - -const options = { - [ANOMALY_THRESHOLD.CRITICAL]: { - text: i18n.translate('xpack.infra.metrics.alertFlyout.expression.severityScore.criticalLabel', { - defaultMessage: 'Critical', - }), - value: ANOMALY_THRESHOLD.CRITICAL, - }, - [ANOMALY_THRESHOLD.MAJOR]: { - text: i18n.translate('xpack.infra.metrics.alertFlyout.expression.severityScore.majorLabel', { - defaultMessage: 'Major', - }), - value: ANOMALY_THRESHOLD.MAJOR, - }, - [ANOMALY_THRESHOLD.MINOR]: { - text: i18n.translate('xpack.infra.metrics.alertFlyout.expression.severityScore.minorLabel', { - defaultMessage: 'Minor', - }), - value: ANOMALY_THRESHOLD.MINOR, - }, - [ANOMALY_THRESHOLD.WARNING]: { - text: i18n.translate('xpack.infra.metrics.alertFlyout.expression.severityScore.warningLabel', { - defaultMessage: 'Warning', - }), - value: ANOMALY_THRESHOLD.WARNING, - }, -}; - -export const SeverityThresholdExpression = ({ - value, - onChange, - popupPosition, -}: WhenExpressionProps) => { - const [aggTypePopoverOpen, setAggTypePopoverOpen] = useState(false); - - return ( - { - setAggTypePopoverOpen(true); - }} - /> - } - isOpen={aggTypePopoverOpen} - closePopover={() => { - setAggTypePopoverOpen(false); - }} - ownFocus - anchorPosition={popupPosition ?? 'downLeft'} - > -
- setAggTypePopoverOpen(false)}> - - - { - onChange(Number(e.target.value) as ANOMALY_THRESHOLD); - setAggTypePopoverOpen(false); - }} - options={Object.values(options).map((o) => o)} - /> -
-
- ); -}; - -interface ClosablePopoverTitleProps { - children: JSX.Element; - onClose: () => void; -} - -export const ClosablePopoverTitle = ({ children, onClose }: ClosablePopoverTitleProps) => { - return ( - - - {children} - - onClose()} - /> - - - - ); -}; diff --git a/x-pack/plugins/infra/public/alerting/metric_anomaly/components/validation.tsx b/x-pack/plugins/infra/public/alerting/metric_anomaly/components/validation.tsx deleted file mode 100644 index 8e254fb2b67a..000000000000 --- a/x-pack/plugins/infra/public/alerting/metric_anomaly/components/validation.tsx +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { i18n } from '@kbn/i18n'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { ValidationResult } from '../../../../../triggers_actions_ui/public/types'; - -export function validateMetricAnomaly({ - hasInfraMLCapabilities, -}: { - hasInfraMLCapabilities: boolean; -}): ValidationResult { - const validationResult = { errors: {} }; - const errors: { - hasInfraMLCapabilities: string[]; - } = { - hasInfraMLCapabilities: [], - }; - - validationResult.errors = errors; - - if (!hasInfraMLCapabilities) { - errors.hasInfraMLCapabilities.push( - i18n.translate('xpack.infra.metrics.alertFlyout.error.mlCapabilitiesRequired', { - defaultMessage: 'Cannot create an anomaly alert when machine learning is disabled.', - }) - ); - } - - return validationResult; -} diff --git a/x-pack/plugins/infra/public/alerting/metric_anomaly/index.ts b/x-pack/plugins/infra/public/alerting/metric_anomaly/index.ts deleted file mode 100644 index 31fed514bdac..000000000000 --- a/x-pack/plugins/infra/public/alerting/metric_anomaly/index.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { i18n } from '@kbn/i18n'; -import React from 'react'; -import { METRIC_ANOMALY_ALERT_TYPE_ID } from '../../../common/alerting/metrics'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { AlertTypeModel } from '../../../../triggers_actions_ui/public/types'; -import { AlertTypeParams } from '../../../../alerts/common'; -import { validateMetricAnomaly } from './components/validation'; - -interface MetricAnomalyAlertTypeParams extends AlertTypeParams { - hasInfraMLCapabilities: boolean; -} - -export function createMetricAnomalyAlertType(): AlertTypeModel { - return { - id: METRIC_ANOMALY_ALERT_TYPE_ID, - description: i18n.translate('xpack.infra.metrics.anomaly.alertFlyout.alertDescription', { - defaultMessage: 'Alert when the anomaly score exceeds a defined threshold.', - }), - iconClass: 'bell', - documentationUrl(docLinks) { - return `${docLinks.ELASTIC_WEBSITE_URL}guide/en/observability/${docLinks.DOC_LINK_VERSION}/metric-anomaly-alert.html`; - }, - alertParamsExpression: React.lazy(() => import('./components/expression')), - validate: validateMetricAnomaly, - defaultActionMessage: i18n.translate( - 'xpack.infra.metrics.alerting.anomaly.defaultActionMessage', - { - defaultMessage: `\\{\\{alertName\\}\\} is in a state of \\{\\{context.alertState\\}\\} - -\\{\\{context.metric\\}\\} was \\{\\{context.summary\\}\\} than normal at \\{\\{context.timestamp\\}\\} - -Typical value: \\{\\{context.typical\\}\\} -Actual value: \\{\\{context.actual\\}\\} -`, - } - ), - requiresAppContext: false, - }; -} diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/alert_dropdown.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/alert_dropdown.tsx new file mode 100644 index 000000000000..3bbe81122582 --- /dev/null +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/alert_dropdown.tsx @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useState, useCallback } from 'react'; +import { EuiPopover, EuiButtonEmpty, EuiContextMenuItem, EuiContextMenuPanel } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { useAlertPrefillContext } from '../../use_alert_prefill'; +import { AlertFlyout } from './alert_flyout'; +import { ManageAlertsContextMenuItem } from '../../inventory/components/manage_alerts_context_menu_item'; + +export const MetricsAlertDropdown = () => { + const [popoverOpen, setPopoverOpen] = useState(false); + const [flyoutVisible, setFlyoutVisible] = useState(false); + + const { metricThresholdPrefill } = useAlertPrefillContext(); + const { groupBy, filterQuery, metrics } = metricThresholdPrefill; + + const closePopover = useCallback(() => { + setPopoverOpen(false); + }, [setPopoverOpen]); + + const openPopover = useCallback(() => { + setPopoverOpen(true); + }, [setPopoverOpen]); + + const menuItems = [ + setFlyoutVisible(true)}> + + , + , + ]; + + return ( + <> + + + + } + isOpen={popoverOpen} + closePopover={closePopover} + > + + + + + ); +}; diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/alert_flyout.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/alert_flyout.tsx index e7e4ade5257f..929654ecb469 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/alert_flyout.tsx +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/alert_flyout.tsx @@ -7,10 +7,10 @@ import React, { useCallback, useContext, useMemo } from 'react'; import { TriggerActionsContext } from '../../../utils/triggers_actions_context'; -import { METRIC_THRESHOLD_ALERT_TYPE_ID } from '../../../../common/alerting/metrics'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { METRIC_THRESHOLD_ALERT_TYPE_ID } from '../../../../server/lib/alerting/metric_threshold/types'; import { MetricsExplorerSeries } from '../../../../common/http_api/metrics_explorer'; import { MetricsExplorerOptions } from '../../../pages/metrics/metrics_explorer/hooks/use_metrics_explorer_options'; -import { useAlertPrefillContext } from '../../../alerting/use_alert_prefill'; interface Props { visible?: boolean; @@ -42,10 +42,3 @@ export const AlertFlyout = (props: Props) => { return <>{visible && AddAlertFlyout}; }; - -export const PrefilledThresholdAlertFlyout = ({ onClose }: { onClose(): void }) => { - const { metricThresholdPrefill } = useAlertPrefillContext(); - const { groupBy, filterQuery, metrics } = metricThresholdPrefill; - - return ; -}; diff --git a/x-pack/plugins/infra/public/components/logging/log_analysis_setup/index.ts b/x-pack/plugins/infra/public/components/logging/log_analysis_setup/index.ts index db5a996c604f..1bcc9e7157a5 100644 --- a/x-pack/plugins/infra/public/components/logging/log_analysis_setup/index.ts +++ b/x-pack/plugins/infra/public/components/logging/log_analysis_setup/index.ts @@ -14,3 +14,4 @@ export * from './missing_results_privileges_prompt'; export * from './missing_setup_privileges_prompt'; export * from './ml_unavailable_prompt'; export * from './setup_status_unknown_prompt'; +export * from './subscription_splash_content'; diff --git a/x-pack/plugins/infra/public/components/logging/log_analysis_setup/subscription_splash_content.tsx b/x-pack/plugins/infra/public/components/logging/log_analysis_setup/subscription_splash_content.tsx new file mode 100644 index 000000000000..c91c1d82afe9 --- /dev/null +++ b/x-pack/plugins/infra/public/components/logging/log_analysis_setup/subscription_splash_content.tsx @@ -0,0 +1,176 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useEffect } from 'react'; +import { i18n } from '@kbn/i18n'; +import { + EuiPage, + EuiPageBody, + EuiPageContent, + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiTitle, + EuiText, + EuiButton, + EuiButtonEmpty, + EuiImage, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { HttpStart } from 'src/core/public'; +import { LoadingPage } from '../../loading_page'; + +import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; +import { euiStyled } from '../../../../../../../src/plugins/kibana_react/common'; +import { useTrialStatus } from '../../../hooks/use_trial_status'; + +export const SubscriptionSplashContent: React.FC = () => { + const { services } = useKibana<{ http: HttpStart }>(); + const { loadState, isTrialAvailable, checkTrialAvailability } = useTrialStatus(); + + useEffect(() => { + checkTrialAvailability(); + }, [checkTrialAvailability]); + + if (loadState === 'pending') { + return ( + + ); + } + + const canStartTrial = isTrialAvailable && loadState === 'resolved'; + + let title; + let description; + let cta; + + if (canStartTrial) { + title = ( + + ); + + description = ( + + ); + + cta = ( + + + + ); + } else { + title = ( + + ); + + description = ( + + ); + + cta = ( + + + + ); + } + + return ( + + + + + + +

{title}

+
+ + +

{description}

+
+ +
{cta}
+
+ + + +
+ + +

+ +

+
+ + + +
+
+
+
+ ); +}; + +const SubscriptionPage = euiStyled(EuiPage)` + height: 100% +`; + +const SubscriptionPageContent = euiStyled(EuiPageContent)` + max-width: 768px !important; +`; + +const SubscriptionPageFooter = euiStyled.div` + background: ${(props) => props.theme.eui.euiColorLightestShade}; + margin: 0 -${(props) => props.theme.eui.paddingSizes.l} -${(props) => + props.theme.eui.paddingSizes.l}; + padding: ${(props) => props.theme.eui.paddingSizes.l}; +`; diff --git a/x-pack/plugins/infra/public/components/source_configuration/source_configuration_settings.tsx b/x-pack/plugins/infra/public/components/source_configuration/source_configuration_settings.tsx index e63f43470497..4b609a881bd1 100644 --- a/x-pack/plugins/infra/public/components/source_configuration/source_configuration_settings.tsx +++ b/x-pack/plugins/infra/public/components/source_configuration/source_configuration_settings.tsx @@ -75,7 +75,7 @@ export const SourceConfigurationSettings = ({ source, ]); - const { hasInfraMLCapabilities } = useInfraMLCapabilitiesContext(); + const { hasInfraMLCapabilites } = useInfraMLCapabilitiesContext(); if ((isLoading || isUninitialized) && !source) { return ; @@ -128,7 +128,7 @@ export const SourceConfigurationSettings = ({ /> - {hasInfraMLCapabilities && ( + {hasInfraMLCapabilites && ( <> { const hasInfraMLSetupCapabilities = mlCapabilities.capabilities.canCreateJob; const hasInfraMLReadCapabilities = mlCapabilities.capabilities.canGetJobs; - const hasInfraMLCapabilities = + const hasInfraMLCapabilites = mlCapabilities.isPlatinumOrTrialLicense && mlCapabilities.mlFeatureEnabledInSpace; return { - hasInfraMLCapabilities, + hasInfraMLCapabilites, hasInfraMLReadCapabilities, hasInfraMLSetupCapabilities, isLoading, diff --git a/x-pack/plugins/infra/public/containers/with_kuery_autocompletion.tsx b/x-pack/plugins/infra/public/containers/with_kuery_autocompletion.tsx index 1a759950f640..379ac9774c24 100644 --- a/x-pack/plugins/infra/public/containers/with_kuery_autocompletion.tsx +++ b/x-pack/plugins/infra/public/containers/with_kuery_autocompletion.tsx @@ -56,8 +56,7 @@ class WithKueryAutocompletionComponent extends React.Component< private loadSuggestions = async ( expression: string, cursorPosition: number, - maxSuggestions?: number, - transformSuggestions?: (s: QuerySuggestion[]) => QuerySuggestion[] + maxSuggestions?: number ) => { const { indexPattern } = this.props; const language = 'kuery'; @@ -87,10 +86,6 @@ class WithKueryAutocompletionComponent extends React.Component< boolFilter: [], })) || []; - const transformedSuggestions = transformSuggestions - ? transformSuggestions(suggestions) - : suggestions; - this.setState((state) => state.currentRequest && state.currentRequest.expression !== expression && @@ -99,9 +94,7 @@ class WithKueryAutocompletionComponent extends React.Component< : { ...state, currentRequest: null, - suggestions: maxSuggestions - ? transformedSuggestions.slice(0, maxSuggestions) - : transformedSuggestions, + suggestions: maxSuggestions ? suggestions.slice(0, maxSuggestions) : suggestions, } ); }; diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_content.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_content.tsx index 628df397998e..f0fdd79bcd93 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_content.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_content.tsx @@ -7,13 +7,13 @@ import { i18n } from '@kbn/i18n'; import React, { useCallback, useEffect } from 'react'; -import { SubscriptionSplashContent } from '../../../components/subscription_splash_content'; import { isJobStatusWithResults } from '../../../../common/log_analysis'; import { LoadingPage } from '../../../components/loading_page'; import { LogAnalysisSetupStatusUnknownPrompt, MissingResultsPrivilegesPrompt, MissingSetupPrivilegesPrompt, + SubscriptionSplashContent, } from '../../../components/logging/log_analysis_setup'; import { LogAnalysisSetupFlyout, diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page_content.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page_content.tsx index 5fd00527b8b7..4d06d23ef93e 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page_content.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page_content.tsx @@ -7,13 +7,13 @@ import { i18n } from '@kbn/i18n'; import React, { memo, useEffect, useCallback } from 'react'; -import { SubscriptionSplashContent } from '../../../components/subscription_splash_content'; import { isJobStatusWithResults } from '../../../../common/log_analysis'; import { LoadingPage } from '../../../components/loading_page'; import { LogAnalysisSetupStatusUnknownPrompt, MissingResultsPrivilegesPrompt, MissingSetupPrivilegesPrompt, + SubscriptionSplashContent, } from '../../../components/logging/log_analysis_setup'; import { LogAnalysisSetupFlyout, diff --git a/x-pack/plugins/infra/public/pages/metrics/index.tsx b/x-pack/plugins/infra/public/pages/metrics/index.tsx index 8fd32bda7fbc..52c2a70f2d35 100644 --- a/x-pack/plugins/infra/public/pages/metrics/index.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/index.tsx @@ -35,11 +35,12 @@ import { WaffleOptionsProvider } from './inventory_view/hooks/use_waffle_options import { WaffleTimeProvider } from './inventory_view/hooks/use_waffle_time'; import { WaffleFiltersProvider } from './inventory_view/hooks/use_waffle_filters'; -import { MetricsAlertDropdown } from '../../alerting/common/components/metrics_alert_dropdown'; +import { InventoryAlertDropdown } from '../../alerting/inventory/components/alert_dropdown'; +import { MetricsAlertDropdown } from '../../alerting/metric_threshold/components/alert_dropdown'; import { SavedView } from '../../containers/saved_view/saved_view'; import { AlertPrefillProvider } from '../../alerting/use_alert_prefill'; import { InfraMLCapabilitiesProvider } from '../../containers/ml/infra_ml_capabilities'; -import { AnomalyDetectionFlyout } from './inventory_view/components/ml/anomaly_detection/anomaly_detection_flyout'; +import { AnomalyDetectionFlyout } from './inventory_view/components/ml/anomaly_detection/anomoly_detection_flyout'; import { HeaderMenuPortal } from '../../../../observability/public'; import { HeaderActionMenuContext } from '../../utils/header_action_menu_provider'; @@ -82,7 +83,8 @@ export const InfrastructurePage = ({ match }: RouteComponentProps) => { - + + { jobSummaries: k8sJobSummaries, } = useMetricK8sModuleContext(); const { - hasInfraMLCapabilities, + hasInfraMLCapabilites, hasInfraMLReadCapabilities, hasInfraMLSetupCapabilities, } = useInfraMLCapabilitiesContext(); @@ -69,7 +69,7 @@ export const FlyoutHome = (props: Props) => { } }, [fetchK8sJobStatus, fetchHostJobStatus, hasInfraMLReadCapabilities]); - if (!hasInfraMLCapabilities) { + if (!hasInfraMLCapabilites) { return ; } else if (!hasInfraMLReadCapabilities) { return ; diff --git a/x-pack/plugins/infra/public/components/subscription_splash_content.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/subscription_splash_content.tsx similarity index 58% rename from x-pack/plugins/infra/public/components/subscription_splash_content.tsx rename to x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/subscription_splash_content.tsx index a6477dfc7d17..e05759ab57dd 100644 --- a/x-pack/plugins/infra/public/components/subscription_splash_content.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/subscription_splash_content.tsx @@ -22,11 +22,11 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { useKibana } from '../../../../../src/plugins/kibana_react/public'; -import { euiStyled, EuiThemeProvider } from '../../../../../src/plugins/kibana_react/common'; -import { HttpStart } from '../../../../../src/core/public'; -import { useTrialStatus } from '../hooks/use_trial_status'; -import { LoadingPage } from '../components/loading_page'; +import { LoadingPage } from '../../../../../../components/loading_page'; +import { useTrialStatus } from '../../../../../../hooks/use_trial_status'; +import { useKibana } from '../../../../../../../../../../src/plugins/kibana_react/public'; +import { euiStyled } from '../../../../../../../../../../src/plugins/kibana_react/common'; +import { HttpStart } from '../../../../../../../../../../src/core/public'; export const SubscriptionSplashContent: React.FC = () => { const { services } = useKibana<{ http: HttpStart }>(); @@ -102,60 +102,58 @@ export const SubscriptionSplashContent: React.FC = () => { } return ( - - - - - - - -

{title}

-
- - -

{description}

-
- -
{cta}
-
- - - -
- - -

- -

+ + + + + + +

{title}

- + + +

{description}

+
+ +
{cta}
+
+ + + +
+ + +

- - - - - - +

+
+ + + +
+
+
+
); }; diff --git a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/kuery_bar.tsx b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/kuery_bar.tsx index e22c6fa66118..44391568741f 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/kuery_bar.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/kuery_bar.tsx @@ -10,19 +10,7 @@ import { i18n } from '@kbn/i18n'; import React, { useEffect, useState } from 'react'; import { WithKueryAutocompletion } from '../../../../containers/with_kuery_autocompletion'; import { AutocompleteField } from '../../../../components/autocomplete_field'; -import { - esKuery, - IIndexPattern, - QuerySuggestion, -} from '../../../../../../../../src/plugins/data/public'; - -type LoadSuggestionsFn = ( - e: string, - p: number, - m?: number, - transform?: (s: QuerySuggestion[]) => QuerySuggestion[] -) => void; -export type CurryLoadSuggestionsType = (loadSuggestions: LoadSuggestionsFn) => LoadSuggestionsFn; +import { esKuery, IIndexPattern } from '../../../../../../../../src/plugins/data/public'; interface Props { derivedIndexPattern: IIndexPattern; @@ -30,7 +18,6 @@ interface Props { onChange?: (query: string) => void; value?: string | null; placeholder?: string; - curryLoadSuggestions?: CurryLoadSuggestionsType; } function validateQuery(query: string) { @@ -48,7 +35,6 @@ export const MetricsExplorerKueryBar = ({ onChange, value, placeholder, - curryLoadSuggestions = defaultCurryLoadSuggestions, }: Props) => { const [draftQuery, setDraftQuery] = useState(value || ''); const [isValid, setValidation] = useState(true); @@ -87,7 +73,7 @@ export const MetricsExplorerKueryBar = ({ aria-label={placeholder} isLoadingSuggestions={isLoadingSuggestions} isValid={isValid} - loadSuggestions={curryLoadSuggestions(loadSuggestions)} + loadSuggestions={loadSuggestions} onChange={handleChange} onSubmit={onSubmit} placeholder={placeholder || defaultPlaceholder} @@ -98,6 +84,3 @@ export const MetricsExplorerKueryBar = ({ ); }; - -const defaultCurryLoadSuggestions: CurryLoadSuggestionsType = (loadSuggestions) => (...args) => - loadSuggestions(...args); diff --git a/x-pack/plugins/infra/public/plugin.ts b/x-pack/plugins/infra/public/plugin.ts index d4bb83e8668b..8e7d165f8a53 100644 --- a/x-pack/plugins/infra/public/plugin.ts +++ b/x-pack/plugins/infra/public/plugin.ts @@ -10,7 +10,6 @@ import { AppMountParameters, PluginInitializerContext } from 'kibana/public'; import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/public'; import { createMetricThresholdAlertType } from './alerting/metric_threshold'; import { createInventoryMetricAlertType } from './alerting/inventory'; -import { createMetricAnomalyAlertType } from './alerting/metric_anomaly'; import { getAlertType as getLogsAlertType } from './alerting/log_threshold'; import { registerFeatures } from './register_feature'; import { @@ -36,7 +35,6 @@ export class Plugin implements InfraClientPluginClass { pluginsSetup.triggersActionsUi.alertTypeRegistry.register(createInventoryMetricAlertType()); pluginsSetup.triggersActionsUi.alertTypeRegistry.register(getLogsAlertType()); pluginsSetup.triggersActionsUi.alertTypeRegistry.register(createMetricThresholdAlertType()); - pluginsSetup.triggersActionsUi.alertTypeRegistry.register(createMetricAnomalyAlertType()); if (pluginsSetup.observability) { pluginsSetup.observability.dashboard.register({ diff --git a/x-pack/plugins/infra/public/types.ts b/x-pack/plugins/infra/public/types.ts index 4d70676d25e4..b18b6e8a6eba 100644 --- a/x-pack/plugins/infra/public/types.ts +++ b/x-pack/plugins/infra/public/types.ts @@ -23,7 +23,7 @@ import type { ObservabilityPluginStart, } from '../../observability/public'; import type { SpacesPluginStart } from '../../spaces/public'; -import { MlPluginStart, MlPluginSetup } from '../../ml/public'; +import { MlPluginStart } from '../../ml/public'; import type { EmbeddableStart } from '../../../../src/plugins/embeddable/public'; // Our own setup and start contract values @@ -36,7 +36,6 @@ export interface InfraClientSetupDeps { observability: ObservabilityPluginSetup; triggersActionsUi: TriggersAndActionsUIPublicPluginSetup; usageCollection: UsageCollectionSetup; - ml: MlPluginSetup; embeddable: EmbeddableSetup; } diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_anomaly/evaluate_condition.ts b/x-pack/plugins/infra/server/lib/alerting/metric_anomaly/evaluate_condition.ts deleted file mode 100644 index b7ef8ec7d231..000000000000 --- a/x-pack/plugins/infra/server/lib/alerting/metric_anomaly/evaluate_condition.ts +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { MetricAnomalyParams } from '../../../../common/alerting/metrics'; -import { getMetricsHostsAnomalies, getMetricK8sAnomalies } from '../../infra_ml'; -import { MlSystem, MlAnomalyDetectors } from '../../../types'; - -type ConditionParams = Omit & { - spaceId: string; - startTime: number; - endTime: number; - mlSystem: MlSystem; - mlAnomalyDetectors: MlAnomalyDetectors; -}; - -export const evaluateCondition = async ({ - nodeType, - spaceId, - sourceId, - mlSystem, - mlAnomalyDetectors, - startTime, - endTime, - metric, - threshold, - influencerFilter, -}: ConditionParams) => { - const getAnomalies = nodeType === 'k8s' ? getMetricK8sAnomalies : getMetricsHostsAnomalies; - - const result = await getAnomalies( - { - spaceId, - mlSystem, - mlAnomalyDetectors, - }, - sourceId ?? 'default', - threshold, - startTime, - endTime, - metric, - { field: 'anomalyScore', direction: 'desc' }, - { pageSize: 100 }, - influencerFilter - ); - - return result; -}; diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_anomaly/metric_anomaly_executor.ts b/x-pack/plugins/infra/server/lib/alerting/metric_anomaly/metric_anomaly_executor.ts deleted file mode 100644 index ec95aac7268a..000000000000 --- a/x-pack/plugins/infra/server/lib/alerting/metric_anomaly/metric_anomaly_executor.ts +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { i18n } from '@kbn/i18n'; -import { first } from 'lodash'; -import moment from 'moment'; -import { stateToAlertMessage } from '../common/messages'; -import { MetricAnomalyParams } from '../../../../common/alerting/metrics'; -import { MappedAnomalyHit } from '../../infra_ml'; -import { AlertStates } from '../common/types'; -import { - ActionGroup, - AlertInstanceContext, - AlertInstanceState, -} from '../../../../../alerts/common'; -import { AlertExecutorOptions } from '../../../../../alerts/server'; -import { getIntervalInSeconds } from '../../../utils/get_interval_in_seconds'; -import { MetricAnomalyAllowedActionGroups } from './register_metric_anomaly_alert_type'; -import { MlPluginSetup } from '../../../../../ml/server'; -import { KibanaRequest } from '../../../../../../../src/core/server'; -import { InfraBackendLibs } from '../../infra_types'; -import { evaluateCondition } from './evaluate_condition'; - -export const createMetricAnomalyExecutor = (libs: InfraBackendLibs, ml?: MlPluginSetup) => async ({ - services, - params, - startedAt, -}: AlertExecutorOptions< - /** - * TODO: Remove this use of `any` by utilizing a proper type - */ - Record, - Record, - AlertInstanceState, - AlertInstanceContext, - MetricAnomalyAllowedActionGroups ->) => { - if (!ml) { - return; - } - const request = {} as KibanaRequest; - const mlSystem = ml.mlSystemProvider(request, services.savedObjectsClient); - const mlAnomalyDetectors = ml.anomalyDetectorsProvider(request, services.savedObjectsClient); - - const { - metric, - alertInterval, - influencerFilter, - sourceId, - nodeType, - threshold, - } = params as MetricAnomalyParams; - - const alertInstance = services.alertInstanceFactory(`${nodeType}-${metric}`); - - const bucketInterval = getIntervalInSeconds('15m') * 1000; - const alertIntervalInMs = getIntervalInSeconds(alertInterval ?? '1m') * 1000; - - const endTime = startedAt.getTime(); - // Anomalies are bucketed at :00, :15, :30, :45 minutes every hour - const previousBucketStartTime = endTime - (endTime % bucketInterval); - - // If the alert interval is less than 15m, make sure that it actually queries an anomaly bucket - const startTime = Math.min(endTime - alertIntervalInMs, previousBucketStartTime); - - const { data } = await evaluateCondition({ - sourceId: sourceId ?? 'default', - spaceId: 'default', - mlSystem, - mlAnomalyDetectors, - startTime, - endTime, - metric, - threshold, - nodeType, - influencerFilter, - }); - - const shouldAlertFire = data.length > 0; - - if (shouldAlertFire) { - const { startTime: anomalyStartTime, anomalyScore, actual, typical, influencers } = first( - data as MappedAnomalyHit[] - )!; - - alertInstance.scheduleActions(FIRED_ACTIONS_ID, { - alertState: stateToAlertMessage[AlertStates.ALERT], - timestamp: moment(anomalyStartTime).toISOString(), - anomalyScore, - actual, - typical, - metric: metricNameMap[metric], - summary: generateSummaryMessage(actual, typical), - influencers: influencers.join(', '), - }); - } -}; - -export const FIRED_ACTIONS_ID = 'metrics.anomaly.fired'; -export const FIRED_ACTIONS: ActionGroup = { - id: FIRED_ACTIONS_ID, - name: i18n.translate('xpack.infra.metrics.alerting.anomaly.fired', { - defaultMessage: 'Fired', - }), -}; - -const generateSummaryMessage = (actual: number, typical: number) => { - const differential = (Math.max(actual, typical) / Math.min(actual, typical)) - .toFixed(1) - .replace('.0', ''); - if (actual > typical) { - return i18n.translate('xpack.infra.metrics.alerting.anomaly.summaryHigher', { - defaultMessage: '{differential}x higher', - values: { - differential, - }, - }); - } else { - return i18n.translate('xpack.infra.metrics.alerting.anomaly.summaryLower', { - defaultMessage: '{differential}x lower', - values: { - differential, - }, - }); - } -}; - -const metricNameMap = { - memory_usage: i18n.translate('xpack.infra.metrics.alerting.anomaly.memoryUsage', { - defaultMessage: 'Memory usage', - }), - network_in: i18n.translate('xpack.infra.metrics.alerting.anomaly.networkIn', { - defaultMessage: 'Network in', - }), - network_out: i18n.translate('xpack.infra.metrics.alerting.anomaly.networkOut', { - defaultMessage: 'Network out', - }), -}; diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_anomaly/preview_metric_anomaly_alert.ts b/x-pack/plugins/infra/server/lib/alerting/metric_anomaly/preview_metric_anomaly_alert.ts deleted file mode 100644 index 98992701e3bb..000000000000 --- a/x-pack/plugins/infra/server/lib/alerting/metric_anomaly/preview_metric_anomaly_alert.ts +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { Unit } from '@elastic/datemath'; -import { countBy } from 'lodash'; -import { MappedAnomalyHit } from '../../infra_ml'; -import { MlSystem, MlAnomalyDetectors } from '../../../types'; -import { MetricAnomalyParams } from '../../../../common/alerting/metrics'; -import { - TOO_MANY_BUCKETS_PREVIEW_EXCEPTION, - isTooManyBucketsPreviewException, -} from '../../../../common/alerting/metrics'; -import { getIntervalInSeconds } from '../../../utils/get_interval_in_seconds'; -import { evaluateCondition } from './evaluate_condition'; - -interface PreviewMetricAnomalyAlertParams { - mlSystem: MlSystem; - mlAnomalyDetectors: MlAnomalyDetectors; - spaceId: string; - params: MetricAnomalyParams; - sourceId: string; - lookback: Unit; - alertInterval: string; - alertThrottle: string; - alertOnNoData: boolean; -} - -export const previewMetricAnomalyAlert = async ({ - mlSystem, - mlAnomalyDetectors, - spaceId, - params, - sourceId, - lookback, - alertInterval, - alertThrottle, -}: PreviewMetricAnomalyAlertParams) => { - const { metric, threshold, influencerFilter, nodeType } = params as MetricAnomalyParams; - - const alertIntervalInSeconds = getIntervalInSeconds(alertInterval); - const throttleIntervalInSeconds = getIntervalInSeconds(alertThrottle); - const executionsPerThrottle = Math.floor(throttleIntervalInSeconds / alertIntervalInSeconds); - - const lookbackInterval = `1${lookback}`; - const lookbackIntervalInSeconds = getIntervalInSeconds(lookbackInterval); - const endTime = Date.now(); - const startTime = endTime - lookbackIntervalInSeconds * 1000; - - const numberOfExecutions = Math.floor(lookbackIntervalInSeconds / alertIntervalInSeconds); - const bucketIntervalInSeconds = getIntervalInSeconds('15m'); - const bucketsPerExecution = Math.max( - 1, - Math.floor(alertIntervalInSeconds / bucketIntervalInSeconds) - ); - - try { - let anomalies: MappedAnomalyHit[] = []; - const { data } = await evaluateCondition({ - nodeType, - spaceId, - sourceId, - mlSystem, - mlAnomalyDetectors, - startTime, - endTime, - metric, - threshold, - influencerFilter, - }); - anomalies = [...anomalies, ...data]; - - const anomaliesByTime = countBy(anomalies, ({ startTime: anomStartTime }) => anomStartTime); - - let numberOfTimesFired = 0; - let numberOfNotifications = 0; - let throttleTracker = 0; - const notifyWithThrottle = () => { - if (throttleTracker === 0) numberOfNotifications++; - throttleTracker++; - }; - // Mock each alert evaluation - for (let i = 0; i < numberOfExecutions; i++) { - const executionTime = startTime + alertIntervalInSeconds * 1000 * i; - // Get an array of bucket times this mock alert evaluation will be looking at - // Anomalies are bucketed at :00, :15, :30, :45 minutes every hour, - // so this is an array of how many of those times occurred between this evaluation - // and the previous one - const bucketsLookedAt = Array.from(Array(bucketsPerExecution), (_, idx) => { - const previousBucketStartTime = - executionTime - - (executionTime % (bucketIntervalInSeconds * 1000)) - - idx * bucketIntervalInSeconds * 1000; - return previousBucketStartTime; - }); - const anomaliesDetectedInBuckets = bucketsLookedAt.some((bucketTime) => - Reflect.has(anomaliesByTime, bucketTime) - ); - - if (anomaliesDetectedInBuckets) { - numberOfTimesFired++; - notifyWithThrottle(); - } else if (throttleTracker > 0) { - throttleTracker++; - } - if (throttleTracker === executionsPerThrottle) { - throttleTracker = 0; - } - } - - return { fired: numberOfTimesFired, notifications: numberOfNotifications }; - } catch (e) { - if (!isTooManyBucketsPreviewException(e)) throw e; - const { maxBuckets } = e; - throw new Error(`${TOO_MANY_BUCKETS_PREVIEW_EXCEPTION}:${maxBuckets}`); - } -}; diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_anomaly/register_metric_anomaly_alert_type.ts b/x-pack/plugins/infra/server/lib/alerting/metric_anomaly/register_metric_anomaly_alert_type.ts deleted file mode 100644 index 8ac62c125515..000000000000 --- a/x-pack/plugins/infra/server/lib/alerting/metric_anomaly/register_metric_anomaly_alert_type.ts +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { schema } from '@kbn/config-schema'; -import { i18n } from '@kbn/i18n'; -import { MlPluginSetup } from '../../../../../ml/server'; -import { AlertType, AlertInstanceState, AlertInstanceContext } from '../../../../../alerts/server'; -import { - createMetricAnomalyExecutor, - FIRED_ACTIONS, - FIRED_ACTIONS_ID, -} from './metric_anomaly_executor'; -import { METRIC_ANOMALY_ALERT_TYPE_ID } from '../../../../common/alerting/metrics'; -import { InfraBackendLibs } from '../../infra_types'; -import { oneOfLiterals, validateIsStringElasticsearchJSONFilter } from '../common/utils'; -import { alertStateActionVariableDescription } from '../common/messages'; -import { RecoveredActionGroupId } from '../../../../../alerts/common'; - -export type MetricAnomalyAllowedActionGroups = typeof FIRED_ACTIONS_ID; - -export const registerMetricAnomalyAlertType = ( - libs: InfraBackendLibs, - ml?: MlPluginSetup -): AlertType< - /** - * TODO: Remove this use of `any` by utilizing a proper type - */ - Record, - Record, - AlertInstanceState, - AlertInstanceContext, - MetricAnomalyAllowedActionGroups, - RecoveredActionGroupId -> => ({ - id: METRIC_ANOMALY_ALERT_TYPE_ID, - name: i18n.translate('xpack.infra.metrics.anomaly.alertName', { - defaultMessage: 'Infrastructure anomaly', - }), - validate: { - params: schema.object( - { - nodeType: oneOfLiterals(['hosts', 'k8s']), - alertInterval: schema.string(), - metric: oneOfLiterals(['memory_usage', 'network_in', 'network_out']), - threshold: schema.number(), - filterQuery: schema.maybe( - schema.string({ validate: validateIsStringElasticsearchJSONFilter }) - ), - sourceId: schema.string(), - }, - { unknowns: 'allow' } - ), - }, - defaultActionGroupId: FIRED_ACTIONS_ID, - actionGroups: [FIRED_ACTIONS], - producer: 'infrastructure', - minimumLicenseRequired: 'basic', - executor: createMetricAnomalyExecutor(libs, ml), - actionVariables: { - context: [ - { name: 'alertState', description: alertStateActionVariableDescription }, - { - name: 'metric', - description: i18n.translate('xpack.infra.metrics.alerting.anomalyMetricDescription', { - defaultMessage: 'The metric name in the specified condition.', - }), - }, - { - name: 'timestamp', - description: i18n.translate('xpack.infra.metrics.alerting.anomalyTimestampDescription', { - defaultMessage: 'A timestamp of when the anomaly was detected.', - }), - }, - { - name: 'anomalyScore', - description: i18n.translate('xpack.infra.metrics.alerting.anomalyScoreDescription', { - defaultMessage: 'The exact severity score of the detected anomaly.', - }), - }, - { - name: 'actual', - description: i18n.translate('xpack.infra.metrics.alerting.anomalyActualDescription', { - defaultMessage: 'The actual value of the monitored metric at the time of the anomaly.', - }), - }, - { - name: 'typical', - description: i18n.translate('xpack.infra.metrics.alerting.anomalyTypicalDescription', { - defaultMessage: 'The typical value of the monitored metric at the time of the anomaly.', - }), - }, - { - name: 'summary', - description: i18n.translate('xpack.infra.metrics.alerting.anomalySummaryDescription', { - defaultMessage: 'A description of the anomaly, e.g. "2x higher."', - }), - }, - { - name: 'influencers', - description: i18n.translate('xpack.infra.metrics.alerting.anomalyInfluencersDescription', { - defaultMessage: 'A list of node names that influenced the anomaly.', - }), - }, - ], - }, -}); diff --git a/x-pack/plugins/infra/server/lib/alerting/register_alert_types.ts b/x-pack/plugins/infra/server/lib/alerting/register_alert_types.ts index 11fbe269b854..0b4df6805759 100644 --- a/x-pack/plugins/infra/server/lib/alerting/register_alert_types.ts +++ b/x-pack/plugins/infra/server/lib/alerting/register_alert_types.ts @@ -8,21 +8,13 @@ import { PluginSetupContract } from '../../../../alerts/server'; import { registerMetricThresholdAlertType } from './metric_threshold/register_metric_threshold_alert_type'; import { registerMetricInventoryThresholdAlertType } from './inventory_metric_threshold/register_inventory_metric_threshold_alert_type'; -import { registerMetricAnomalyAlertType } from './metric_anomaly/register_metric_anomaly_alert_type'; - import { registerLogThresholdAlertType } from './log_threshold/register_log_threshold_alert_type'; import { InfraBackendLibs } from '../infra_types'; -import { MlPluginSetup } from '../../../../ml/server'; -const registerAlertTypes = ( - alertingPlugin: PluginSetupContract, - libs: InfraBackendLibs, - ml?: MlPluginSetup -) => { +const registerAlertTypes = (alertingPlugin: PluginSetupContract, libs: InfraBackendLibs) => { if (alertingPlugin) { alertingPlugin.registerType(registerMetricThresholdAlertType(libs)); alertingPlugin.registerType(registerMetricInventoryThresholdAlertType(libs)); - alertingPlugin.registerType(registerMetricAnomalyAlertType(libs, ml)); const registerFns = [registerLogThresholdAlertType]; registerFns.forEach((fn) => { diff --git a/x-pack/plugins/infra/server/lib/infra_ml/common.ts b/x-pack/plugins/infra/server/lib/infra_ml/common.ts index 686f27d714cc..0182cb0e4099 100644 --- a/x-pack/plugins/infra/server/lib/infra_ml/common.ts +++ b/x-pack/plugins/infra/server/lib/infra_ml/common.ts @@ -17,23 +17,6 @@ import { import { decodeOrThrow } from '../../../common/runtime_types'; import { startTracingSpan, TracingSpan } from '../../../common/performance_tracing'; -export interface MappedAnomalyHit { - id: string; - anomalyScore: number; - typical: number; - actual: number; - jobId: string; - startTime: number; - duration: number; - influencers: string[]; - categoryId?: string; -} - -export interface InfluencerFilter { - fieldName: string; - fieldValue: string; -} - export async function fetchMlJob(mlAnomalyDetectors: MlAnomalyDetectors, jobId: string) { const finalizeMlGetJobSpan = startTracingSpan('Fetch ml job from ES'); const { diff --git a/x-pack/plugins/infra/server/lib/infra_ml/index.ts b/x-pack/plugins/infra/server/lib/infra_ml/index.ts index 82093b1a359d..d346b71d76aa 100644 --- a/x-pack/plugins/infra/server/lib/infra_ml/index.ts +++ b/x-pack/plugins/infra/server/lib/infra_ml/index.ts @@ -8,4 +8,3 @@ export * from './errors'; export * from './metrics_hosts_anomalies'; export * from './metrics_k8s_anomalies'; -export { MappedAnomalyHit } from './common'; diff --git a/x-pack/plugins/infra/server/lib/infra_ml/metrics_hosts_anomalies.ts b/x-pack/plugins/infra/server/lib/infra_ml/metrics_hosts_anomalies.ts index f6e11f529419..7873fd8e43a7 100644 --- a/x-pack/plugins/infra/server/lib/infra_ml/metrics_hosts_anomalies.ts +++ b/x-pack/plugins/infra/server/lib/infra_ml/metrics_hosts_anomalies.ts @@ -5,10 +5,11 @@ * 2.0. */ +import type { InfraPluginRequestHandlerContext } from '../../types'; import { InfraRequestHandlerContext } from '../../types'; import { TracingSpan, startTracingSpan } from '../../../common/performance_tracing'; -import { fetchMlJob, MappedAnomalyHit, InfluencerFilter } from './common'; -import { getJobId, metricsHostsJobTypes, ANOMALY_THRESHOLD } from '../../../common/infra_ml'; +import { fetchMlJob } from './common'; +import { getJobId, metricsHostsJobTypes } from '../../../common/infra_ml'; import { Sort, Pagination } from '../../../common/http_api/infra_ml'; import type { MlSystem, MlAnomalyDetectors } from '../../types'; import { InsufficientAnomalyMlJobsConfigured, isMlPrivilegesError } from './errors'; @@ -18,6 +19,18 @@ import { createMetricsHostsAnomaliesQuery, } from './queries/metrics_hosts_anomalies'; +interface MappedAnomalyHit { + id: string; + anomalyScore: number; + typical: number; + actual: number; + jobId: string; + startTime: number; + duration: number; + influencers: string[]; + categoryId?: string; +} + async function getCompatibleAnomaliesJobIds( spaceId: string, sourceId: string, @@ -61,15 +74,14 @@ async function getCompatibleAnomaliesJobIds( } export async function getMetricsHostsAnomalies( - context: Required, + context: InfraPluginRequestHandlerContext & { infra: Required }, sourceId: string, - anomalyThreshold: ANOMALY_THRESHOLD, + anomalyThreshold: number, startTime: number, endTime: number, metric: 'memory_usage' | 'network_in' | 'network_out' | undefined, sort: Sort, - pagination: Pagination, - influencerFilter?: InfluencerFilter + pagination: Pagination ) { const finalizeMetricsHostsAnomaliesSpan = startTracingSpan('get metrics hosts entry anomalies'); @@ -77,10 +89,10 @@ export async function getMetricsHostsAnomalies( jobIds, timing: { spans: jobSpans }, } = await getCompatibleAnomaliesJobIds( - context.spaceId, + context.infra.spaceId, sourceId, metric, - context.mlAnomalyDetectors + context.infra.mlAnomalyDetectors ); if (jobIds.length === 0) { @@ -96,14 +108,13 @@ export async function getMetricsHostsAnomalies( hasMoreEntries, timing: { spans: fetchLogEntryAnomaliesSpans }, } = await fetchMetricsHostsAnomalies( - context.mlSystem, + context.infra.mlSystem, anomalyThreshold, jobIds, startTime, endTime, sort, - pagination, - influencerFilter + pagination ); const data = anomalies.map((anomaly) => { @@ -153,13 +164,12 @@ const parseAnomalyResult = (anomaly: MappedAnomalyHit, jobId: string) => { async function fetchMetricsHostsAnomalies( mlSystem: MlSystem, - anomalyThreshold: ANOMALY_THRESHOLD, + anomalyThreshold: number, jobIds: string[], startTime: number, endTime: number, sort: Sort, - pagination: Pagination, - influencerFilter?: InfluencerFilter + pagination: Pagination ) { // We'll request 1 extra entry on top of our pageSize to determine if there are // more entries to be fetched. This avoids scenarios where the client side can't @@ -178,7 +188,6 @@ async function fetchMetricsHostsAnomalies( endTime, sort, pagination: expandedPagination, - influencerFilter, }), jobIds ) diff --git a/x-pack/plugins/infra/server/lib/infra_ml/metrics_k8s_anomalies.ts b/x-pack/plugins/infra/server/lib/infra_ml/metrics_k8s_anomalies.ts index 34039e9107f0..0c87b2f0f8b5 100644 --- a/x-pack/plugins/infra/server/lib/infra_ml/metrics_k8s_anomalies.ts +++ b/x-pack/plugins/infra/server/lib/infra_ml/metrics_k8s_anomalies.ts @@ -5,10 +5,11 @@ * 2.0. */ +import type { InfraPluginRequestHandlerContext } from '../../types'; import { InfraRequestHandlerContext } from '../../types'; import { TracingSpan, startTracingSpan } from '../../../common/performance_tracing'; -import { fetchMlJob, MappedAnomalyHit, InfluencerFilter } from './common'; -import { getJobId, metricsK8SJobTypes, ANOMALY_THRESHOLD } from '../../../common/infra_ml'; +import { fetchMlJob } from './common'; +import { getJobId, metricsK8SJobTypes } from '../../../common/infra_ml'; import { Sort, Pagination } from '../../../common/http_api/infra_ml'; import type { MlSystem, MlAnomalyDetectors } from '../../types'; import { InsufficientAnomalyMlJobsConfigured, isMlPrivilegesError } from './errors'; @@ -18,6 +19,18 @@ import { createMetricsK8sAnomaliesQuery, } from './queries/metrics_k8s_anomalies'; +interface MappedAnomalyHit { + id: string; + anomalyScore: number; + typical: number; + actual: number; + jobId: string; + startTime: number; + influencers: string[]; + duration: number; + categoryId?: string; +} + async function getCompatibleAnomaliesJobIds( spaceId: string, sourceId: string, @@ -61,15 +74,14 @@ async function getCompatibleAnomaliesJobIds( } export async function getMetricK8sAnomalies( - context: Required, + context: InfraPluginRequestHandlerContext & { infra: Required }, sourceId: string, - anomalyThreshold: ANOMALY_THRESHOLD, + anomalyThreshold: number, startTime: number, endTime: number, metric: 'memory_usage' | 'network_in' | 'network_out' | undefined, sort: Sort, - pagination: Pagination, - influencerFilter?: InfluencerFilter + pagination: Pagination ) { const finalizeMetricsK8sAnomaliesSpan = startTracingSpan('get metrics k8s entry anomalies'); @@ -77,10 +89,10 @@ export async function getMetricK8sAnomalies( jobIds, timing: { spans: jobSpans }, } = await getCompatibleAnomaliesJobIds( - context.spaceId, + context.infra.spaceId, sourceId, metric, - context.mlAnomalyDetectors + context.infra.mlAnomalyDetectors ); if (jobIds.length === 0) { @@ -95,14 +107,13 @@ export async function getMetricK8sAnomalies( hasMoreEntries, timing: { spans: fetchLogEntryAnomaliesSpans }, } = await fetchMetricK8sAnomalies( - context.mlSystem, + context.infra.mlSystem, anomalyThreshold, jobIds, startTime, endTime, sort, - pagination, - influencerFilter + pagination ); const data = anomalies.map((anomaly) => { @@ -149,13 +160,12 @@ const parseAnomalyResult = (anomaly: MappedAnomalyHit, jobId: string) => { async function fetchMetricK8sAnomalies( mlSystem: MlSystem, - anomalyThreshold: ANOMALY_THRESHOLD, + anomalyThreshold: number, jobIds: string[], startTime: number, endTime: number, sort: Sort, - pagination: Pagination, - influencerFilter?: InfluencerFilter | undefined + pagination: Pagination ) { // We'll request 1 extra entry on top of our pageSize to determine if there are // more entries to be fetched. This avoids scenarios where the client side can't @@ -174,7 +184,6 @@ async function fetchMetricK8sAnomalies( endTime, sort, pagination: expandedPagination, - influencerFilter, }), jobIds ) diff --git a/x-pack/plugins/infra/server/lib/infra_ml/queries/common.ts b/x-pack/plugins/infra/server/lib/infra_ml/queries/common.ts index 6f996a672a44..b3676fc54aea 100644 --- a/x-pack/plugins/infra/server/lib/infra_ml/queries/common.ts +++ b/x-pack/plugins/infra/server/lib/infra_ml/queries/common.ts @@ -77,35 +77,3 @@ export const createDatasetsFilters = (datasets?: string[]) => }, ] : []; - -export const createInfluencerFilter = ({ - fieldName, - fieldValue, -}: { - fieldName: string; - fieldValue: string; -}) => [ - { - nested: { - path: 'influencers', - query: { - bool: { - must: [ - { - match: { - 'influencers.influencer_field_name': fieldName, - }, - }, - { - query_string: { - fields: ['influencers.influencer_field_values'], - query: fieldValue, - minimum_should_match: 1, - }, - }, - ], - }, - }, - }, - }, -]; diff --git a/x-pack/plugins/infra/server/lib/infra_ml/queries/metrics_hosts_anomalies.ts b/x-pack/plugins/infra/server/lib/infra_ml/queries/metrics_hosts_anomalies.ts index 7808851508a7..45587cd258e5 100644 --- a/x-pack/plugins/infra/server/lib/infra_ml/queries/metrics_hosts_anomalies.ts +++ b/x-pack/plugins/infra/server/lib/infra_ml/queries/metrics_hosts_anomalies.ts @@ -6,7 +6,6 @@ */ import * as rt from 'io-ts'; -import { ANOMALY_THRESHOLD } from '../../../../common/infra_ml'; import { commonSearchSuccessResponseFieldsRT } from '../../../utils/elasticsearch_runtime_types'; import { createJobIdsFilters, @@ -14,9 +13,7 @@ import { createResultTypeFilters, defaultRequestParameters, createAnomalyScoreFilter, - createInfluencerFilter, } from './common'; -import { InfluencerFilter } from '../common'; import { Sort, Pagination } from '../../../../common/http_api/infra_ml'; // TODO: Reassess validity of this against ML docs @@ -35,15 +32,13 @@ export const createMetricsHostsAnomaliesQuery = ({ endTime, sort, pagination, - influencerFilter, }: { jobIds: string[]; - anomalyThreshold: ANOMALY_THRESHOLD; + anomalyThreshold: number; startTime: number; endTime: number; sort: Sort; pagination: Pagination; - influencerFilter?: InfluencerFilter; }) => { const { field } = sort; const { pageSize } = pagination; @@ -55,10 +50,6 @@ export const createMetricsHostsAnomaliesQuery = ({ ...createResultTypeFilters(['record']), ]; - const influencerQuery = influencerFilter - ? { must: createInfluencerFilter(influencerFilter) } - : {}; - const sourceFields = [ 'job_id', 'record_score', @@ -86,7 +77,6 @@ export const createMetricsHostsAnomaliesQuery = ({ query: { bool: { filter: filters, - ...influencerQuery, }, }, search_after: queryCursor, diff --git a/x-pack/plugins/infra/server/lib/infra_ml/queries/metrics_k8s_anomalies.ts b/x-pack/plugins/infra/server/lib/infra_ml/queries/metrics_k8s_anomalies.ts index 54eea067177e..56a4b99e7236 100644 --- a/x-pack/plugins/infra/server/lib/infra_ml/queries/metrics_k8s_anomalies.ts +++ b/x-pack/plugins/infra/server/lib/infra_ml/queries/metrics_k8s_anomalies.ts @@ -6,7 +6,6 @@ */ import * as rt from 'io-ts'; -import { ANOMALY_THRESHOLD } from '../../../../common/infra_ml'; import { commonSearchSuccessResponseFieldsRT } from '../../../utils/elasticsearch_runtime_types'; import { createJobIdsFilters, @@ -14,9 +13,7 @@ import { createResultTypeFilters, defaultRequestParameters, createAnomalyScoreFilter, - createInfluencerFilter, } from './common'; -import { InfluencerFilter } from '../common'; import { Sort, Pagination } from '../../../../common/http_api/infra_ml'; // TODO: Reassess validity of this against ML docs @@ -35,15 +32,13 @@ export const createMetricsK8sAnomaliesQuery = ({ endTime, sort, pagination, - influencerFilter, }: { jobIds: string[]; - anomalyThreshold: ANOMALY_THRESHOLD; + anomalyThreshold: number; startTime: number; endTime: number; sort: Sort; pagination: Pagination; - influencerFilter?: InfluencerFilter; }) => { const { field } = sort; const { pageSize } = pagination; @@ -55,10 +50,6 @@ export const createMetricsK8sAnomaliesQuery = ({ ...createResultTypeFilters(['record']), ]; - const influencerQuery = influencerFilter - ? { must: createInfluencerFilter(influencerFilter) } - : {}; - const sourceFields = [ 'job_id', 'record_score', @@ -85,7 +76,6 @@ export const createMetricsK8sAnomaliesQuery = ({ query: { bool: { filter: filters, - ...influencerQuery, }, }, search_after: queryCursor, diff --git a/x-pack/plugins/infra/server/plugin.ts b/x-pack/plugins/infra/server/plugin.ts index 0ac49e05b36b..99555fa56acd 100644 --- a/x-pack/plugins/infra/server/plugin.ts +++ b/x-pack/plugins/infra/server/plugin.ts @@ -137,7 +137,7 @@ export class InfraServerPlugin implements Plugin { ]); initInfraServer(this.libs); - registerAlertTypes(plugins.alerts, this.libs, plugins.ml); + registerAlertTypes(plugins.alerts, this.libs); core.http.registerRouteHandlerContext( 'infra', diff --git a/x-pack/plugins/infra/server/routes/alerting/preview.ts b/x-pack/plugins/infra/server/routes/alerting/preview.ts index 3da560135eaf..cc2cf4092520 100644 --- a/x-pack/plugins/infra/server/routes/alerting/preview.ts +++ b/x-pack/plugins/infra/server/routes/alerting/preview.ts @@ -9,21 +9,17 @@ import { PreviewResult } from '../../lib/alerting/common/types'; import { METRIC_THRESHOLD_ALERT_TYPE_ID, METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID, - METRIC_ANOMALY_ALERT_TYPE_ID, INFRA_ALERT_PREVIEW_PATH, TOO_MANY_BUCKETS_PREVIEW_EXCEPTION, alertPreviewRequestParamsRT, alertPreviewSuccessResponsePayloadRT, MetricThresholdAlertPreviewRequestParams, InventoryAlertPreviewRequestParams, - MetricAnomalyAlertPreviewRequestParams, } from '../../../common/alerting/metrics'; import { createValidationFunction } from '../../../common/runtime_types'; import { previewInventoryMetricThresholdAlert } from '../../lib/alerting/inventory_metric_threshold/preview_inventory_metric_threshold_alert'; import { previewMetricThresholdAlert } from '../../lib/alerting/metric_threshold/preview_metric_threshold_alert'; -import { previewMetricAnomalyAlert } from '../../lib/alerting/metric_anomaly/preview_metric_anomaly_alert'; import { InfraBackendLibs } from '../../lib/infra_types'; -import { assertHasInfraMlPlugins } from '../../utils/request_context'; export const initAlertPreviewRoute = ({ framework, sources }: InfraBackendLibs) => { const { callWithRequest } = framework; @@ -37,6 +33,8 @@ export const initAlertPreviewRoute = ({ framework, sources }: InfraBackendLibs) }, framework.router.handleLegacyErrors(async (requestContext, request, response) => { const { + criteria, + filterQuery, lookback, sourceId, alertType, @@ -57,11 +55,7 @@ export const initAlertPreviewRoute = ({ framework, sources }: InfraBackendLibs) try { switch (alertType) { case METRIC_THRESHOLD_ALERT_TYPE_ID: { - const { - groupBy, - criteria, - filterQuery, - } = request.body as MetricThresholdAlertPreviewRequestParams; + const { groupBy } = request.body as MetricThresholdAlertPreviewRequestParams; const previewResult = await previewMetricThresholdAlert({ callCluster, params: { criteria, filterQuery, groupBy }, @@ -78,11 +72,7 @@ export const initAlertPreviewRoute = ({ framework, sources }: InfraBackendLibs) }); } case METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID: { - const { - nodeType, - criteria, - filterQuery, - } = request.body as InventoryAlertPreviewRequestParams; + const { nodeType } = request.body as InventoryAlertPreviewRequestParams; const previewResult = await previewInventoryMetricThresholdAlert({ callCluster, params: { criteria, filterQuery, nodeType }, @@ -99,39 +89,6 @@ export const initAlertPreviewRoute = ({ framework, sources }: InfraBackendLibs) body: alertPreviewSuccessResponsePayloadRT.encode(payload), }); } - case METRIC_ANOMALY_ALERT_TYPE_ID: { - assertHasInfraMlPlugins(requestContext); - const { - nodeType, - metric, - threshold, - influencerFilter, - } = request.body as MetricAnomalyAlertPreviewRequestParams; - const { mlAnomalyDetectors, mlSystem, spaceId } = requestContext.infra; - - const previewResult = await previewMetricAnomalyAlert({ - mlAnomalyDetectors, - mlSystem, - spaceId, - params: { nodeType, metric, threshold, influencerFilter }, - lookback, - sourceId: source.id, - alertInterval, - alertThrottle, - alertOnNoData, - }); - - return response.ok({ - body: alertPreviewSuccessResponsePayloadRT.encode({ - numberOfGroups: 1, - resultTotals: { - ...previewResult, - error: 0, - noData: 0, - }, - }), - }); - } default: throw new Error('Unknown alert type'); } diff --git a/x-pack/plugins/infra/server/routes/infra_ml/results/metrics_hosts_anomalies.ts b/x-pack/plugins/infra/server/routes/infra_ml/results/metrics_hosts_anomalies.ts index 6e227cfc12d1..8ec0b83994e1 100644 --- a/x-pack/plugins/infra/server/routes/infra_ml/results/metrics_hosts_anomalies.ts +++ b/x-pack/plugins/infra/server/routes/infra_ml/results/metrics_hosts_anomalies.ts @@ -53,7 +53,7 @@ export const initGetHostsAnomaliesRoute = ({ framework }: InfraBackendLibs) => { hasMoreEntries, timing, } = await getMetricsHostsAnomalies( - requestContext.infra, + requestContext, sourceId, anomalyThreshold, startTime, diff --git a/x-pack/plugins/infra/server/routes/infra_ml/results/metrics_k8s_anomalies.ts b/x-pack/plugins/infra/server/routes/infra_ml/results/metrics_k8s_anomalies.ts index 1c2c4947a02e..d41fa0ffafec 100644 --- a/x-pack/plugins/infra/server/routes/infra_ml/results/metrics_k8s_anomalies.ts +++ b/x-pack/plugins/infra/server/routes/infra_ml/results/metrics_k8s_anomalies.ts @@ -52,7 +52,7 @@ export const initGetK8sAnomaliesRoute = ({ framework }: InfraBackendLibs) => { hasMoreEntries, timing, } = await getMetricK8sAnomalies( - requestContext.infra, + requestContext, sourceId, anomalyThreshold, startTime, diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 018d2d572eea..6e9d0329eaff 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -9676,6 +9676,7 @@ "xpack.infra.alerting.alertFlyout.groupBy.placeholder": "なし(グループなし)", "xpack.infra.alerting.alertFlyout.groupByLabel": "グループ分けの条件", "xpack.infra.alerting.alertsButton": "アラート", + "xpack.infra.alerting.createAlertButton": "アラートの作成", "xpack.infra.alerting.logs.alertsButton": "アラート", "xpack.infra.alerting.logs.createAlertButton": "アラートの作成", "xpack.infra.alerting.logs.manageAlerts": "アラートを管理", @@ -9969,6 +9970,16 @@ "xpack.infra.logs.jumpToTailText": "最も新しいエントリーに移動", "xpack.infra.logs.lastUpdate": "前回の更新 {timestamp}", "xpack.infra.logs.loadingNewEntriesText": "新しいエントリーを読み込み中", + "xpack.infra.logs.logAnalysis.splash.learnMoreLink": "ドキュメンテーションを表示", + "xpack.infra.logs.logAnalysis.splash.learnMoreTitle": "詳細について", + "xpack.infra.logs.logAnalysis.splash.loadingMessage": "ライセンスを確認しています...", + "xpack.infra.logs.logAnalysis.splash.splashImageAlt": "プレースホルダー画像", + "xpack.infra.logs.logAnalysis.splash.startTrialCta": "トライアルを開始", + "xpack.infra.logs.logAnalysis.splash.startTrialDescription": "無料の試用版には、機械学習機能が含まれており、ログで異常を検出することができます。", + "xpack.infra.logs.logAnalysis.splash.startTrialTitle": "異常検知を利用するには、無料の試用版を開始してください", + "xpack.infra.logs.logAnalysis.splash.updateSubscriptionCta": "サブスクリプションのアップグレード", + "xpack.infra.logs.logAnalysis.splash.updateSubscriptionDescription": "機械学習機能を使用するには、プラチナサブスクリプションが必要です。", + "xpack.infra.logs.logAnalysis.splash.updateSubscriptionTitle": "異常検知を利用するには、プラチナサブスクリプションにアップグレードしてください", "xpack.infra.logs.logEntryActionsDetailsButton": "詳細を表示", "xpack.infra.logs.logEntryCategories.analyzeCategoryInMlButtonLabel": "ML で分析", "xpack.infra.logs.logEntryCategories.analyzeCategoryInMlTooltipDescription": "ML アプリでこのカテゴリーを分析します。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 5a9695b8ddc3..eeda70910447 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -9702,6 +9702,7 @@ "xpack.infra.alerting.alertFlyout.groupBy.placeholder": "无内容(未分组)", "xpack.infra.alerting.alertFlyout.groupByLabel": "分组依据", "xpack.infra.alerting.alertsButton": "告警", + "xpack.infra.alerting.createAlertButton": "创建告警", "xpack.infra.alerting.logs.alertsButton": "告警", "xpack.infra.alerting.logs.createAlertButton": "创建告警", "xpack.infra.alerting.logs.manageAlerts": "管理告警", @@ -9996,6 +9997,16 @@ "xpack.infra.logs.jumpToTailText": "跳到最近的条目", "xpack.infra.logs.lastUpdate": "上次更新时间 {timestamp}", "xpack.infra.logs.loadingNewEntriesText": "正在加载新条目", + "xpack.infra.logs.logAnalysis.splash.learnMoreLink": "阅读文档", + "xpack.infra.logs.logAnalysis.splash.learnMoreTitle": "希望了解详情?", + "xpack.infra.logs.logAnalysis.splash.loadingMessage": "正在检查许可证......", + "xpack.infra.logs.logAnalysis.splash.splashImageAlt": "占位符图像", + "xpack.infra.logs.logAnalysis.splash.startTrialCta": "开始试用", + "xpack.infra.logs.logAnalysis.splash.startTrialDescription": "我们的免费试用版包含 Machine Learning 功能,可用于检测日志中的异常。", + "xpack.infra.logs.logAnalysis.splash.startTrialTitle": "要访问异常检测,请启动免费试用版", + "xpack.infra.logs.logAnalysis.splash.updateSubscriptionCta": "升级订阅", + "xpack.infra.logs.logAnalysis.splash.updateSubscriptionDescription": "必须具有白金级订阅,才能使用 Machine Learning 功能。", + "xpack.infra.logs.logAnalysis.splash.updateSubscriptionTitle": "要访问异常检测,请升级到白金级订阅", "xpack.infra.logs.logEntryActionsDetailsButton": "查看详情", "xpack.infra.logs.logEntryCategories.analyzeCategoryInMlButtonLabel": "在 ML 中分析", "xpack.infra.logs.logEntryCategories.analyzeCategoryInMlTooltipDescription": "在 ML 应用中分析此类别。",