Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Revert "Revert "[Metrics UI] Add Metrics Anomaly Alert Type (#89244)"" #90889

Merged
merged 2 commits into from
Feb 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 41 additions & 2 deletions x-pack/plugins/infra/common/alerting/metrics/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@
* 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 = '>',
Expand All @@ -34,6 +35,26 @@ 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<typeof metricAnomalyNodeTypeRT>;
metric: rt.TypeOf<typeof metricAnomalyMetricRT>;
alertInterval?: string;
sourceId?: string;
threshold: Exclude<ANOMALY_THRESHOLD, ANOMALY_THRESHOLD.LOW>;
influencerFilter: rt.TypeOf<typeof metricAnomalyInfluencerFilterRT> | undefined;
}

// Alert Preview API
const baseAlertRequestParamsRT = rt.intersection([
rt.partial({
Expand All @@ -51,7 +72,6 @@ const baseAlertRequestParamsRT = rt.intersection([
rt.literal('M'),
rt.literal('y'),
]),
criteria: rt.array(rt.any),
alertInterval: rt.string,
alertThrottle: rt.string,
alertOnNoData: rt.boolean,
Expand All @@ -65,6 +85,7 @@ const metricThresholdAlertPreviewRequestParamsRT = rt.intersection([
}),
rt.type({
alertType: rt.literal(METRIC_THRESHOLD_ALERT_TYPE_ID),
criteria: rt.array(rt.any),
}),
]);
export type MetricThresholdAlertPreviewRequestParams = rt.TypeOf<
Expand All @@ -76,15 +97,33 @@ 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<typeof alertPreviewRequestParamsRT>;

Expand Down
56 changes: 32 additions & 24 deletions x-pack/plugins/infra/common/infra_ml/anomaly_results.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,44 @@
* 2.0.
*/

export const ML_SEVERITY_SCORES = {
warning: 3,
minor: 25,
major: 50,
critical: 75,
};
export enum ANOMALY_SEVERITY {
CRITICAL = 'critical',
MAJOR = 'major',
MINOR = 'minor',
WARNING = 'warning',
LOW = 'low',
UNKNOWN = 'unknown',
}

export type MLSeverityScoreCategories = keyof typeof ML_SEVERITY_SCORES;
export enum ANOMALY_THRESHOLD {
CRITICAL = 75,
MAJOR = 50,
MINOR = 25,
WARNING = 3,
LOW = 0,
}

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 SEVERITY_COLORS = {
CRITICAL: '#fe5050',
MAJOR: '#fba740',
MINOR: '#fdec25',
WARNING: '#8bc8fb',
LOW: '#d2e9f7',
BLANK: '#ffffff',
};

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';
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;
} else {
// Category is too low to include
return undefined;
return ANOMALY_SEVERITY.LOW;
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ interface Props {
alertInterval: string;
alertThrottle: string;
alertType: PreviewableAlertTypes;
alertParams: { criteria: any[]; sourceId: string } & Record<string, any>;
alertParams: { criteria?: any[]; sourceId: string } & Record<string, any>;
validate: (params: any) => ValidationResult;
showNoDataResults?: boolean;
groupByDisplayName?: string;
Expand Down Expand Up @@ -109,6 +109,7 @@ export const AlertPreview: React.FC<Props> = (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)
Expand All @@ -124,7 +125,7 @@ export const AlertPreview: React.FC<Props> = (props) => {
}, [previewResult, showNoDataResults]);

const hasWarningThreshold = useMemo(
() => alertParams.criteria?.some((c) => Reflect.has(c, 'warningThreshold')),
() => alertParams.criteria?.some((c) => Reflect.has(c, 'warningThreshold')) ?? false,
[alertParams]
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ 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_INVENTORY_THRESHOLD_ALERT_TYPE_ID
| typeof METRIC_ANOMALY_ALERT_TYPE_ID;

export async function getAlertPreview({
fetch,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/*
* 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<VisibleFlyoutType>(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 (
<>
<EuiPopover
panelPaddingSize="none"
anchorPosition="downLeft"
button={
<EuiButtonEmpty iconSide={'right'} iconType={'arrowDown'} onClick={openPopover}>
<FormattedMessage id="xpack.infra.alerting.alertsButton" defaultMessage="Alerts" />
</EuiButtonEmpty>
}
isOpen={popoverOpen}
closePopover={closePopover}
>
<EuiContextMenu initialPanelId={0} panels={panels} />
</EuiPopover>
<AlertFlyout visibleFlyoutType={visibleFlyoutType} onClose={closeFlyout} />
</>
);
};

interface AlertFlyoutProps {
visibleFlyoutType: VisibleFlyoutType;
onClose(): void;
}

const AlertFlyout = ({ visibleFlyoutType, onClose }: AlertFlyoutProps) => {
switch (visibleFlyoutType) {
case 'inventory':
return <PrefilledInventoryAlertFlyout onClose={onClose} />;
case 'threshold':
return <PrefilledThresholdAlertFlyout onClose={onClose} />;
case 'anomaly':
return <PrefilledAnomalyAlertFlyout onClose={onClose} />;
default:
return null;
}
};
Loading